1/*
2 * cvs2git
3 *
4 * Copyright (C) Linus Torvalds 2005
5 */
6
7#include <stdio.h>
8#include <ctype.h>
9#include <string.h>
10#include <stdlib.h>
11#include <unistd.h>
12
13static int verbose = 0;
14
15/*
16 * This is a really stupid program that takes cvsps output, and
17 * generates a a long _shell_script_ that will create the GIT archive
18 * from it.
19 *
20 * You've been warned. I told you it was stupid.
21 *
22 * NOTE NOTE NOTE! In order to do branches correctly, this needs
23 * the fixed cvsps that has the "Ancestor branch" tag output.
24 * Hopefully David Mansfield will update his distribution soon
25 * enough (he's the one who wrote the patch, so at least we don't
26 * have to figt maintainer issues ;)
27 *
28 * Usage:
29 *
30 * TZ=UTC cvsps -A |
31 * git-cvs2git --cvsroot=[root] --module=[module] > script
32 *
33 * Creates a shell script that will generate the .git archive of
34 * the names CVS repository.
35 *
36 * TZ=UTC cvsps -s 1234- -A |
37 * git-cvs2git -u --cvsroot=[root] --module=[module] > script
38 *
39 * Creates a shell script that will update the .git archive with
40 * CVS changes from patchset 1234 until the last one.
41 *
42 * IMPORTANT NOTE ABOUT "cvsps"! This requires version 2.1 or better,
43 * and the "TZ=UTC" and the "-A" flag is required for sane results!
44 */
45enum state {
46 Header,
47 Log,
48 Members
49};
50
51static const char *cvsroot;
52static const char *cvsmodule;
53
54static char date[100];
55static char author[100];
56static char branch[100];
57static char ancestor[100];
58static char tag[100];
59static char log[32768];
60static int loglen = 0;
61static int initial_commit = 1;
62
63static void lookup_author(char *n, char **name, char **email)
64{
65 /*
66 * FIXME!!! I'm lazy and stupid.
67 *
68 * This could be something like
69 *
70 * printf("lookup_author '%s'\n", n);
71 * *name = "$author_name";
72 * *email = "$author_email";
73 *
74 * and that would allow the script to do its own
75 * lookups at run-time.
76 */
77 *name = n;
78 *email = n;
79}
80
81static void prepare_commit(void)
82{
83 char *author_name, *author_email;
84 char *src_branch;
85
86 lookup_author(author, &author_name, &author_email);
87
88 printf("export GIT_COMMITTER_NAME=%s\n", author_name);
89 printf("export GIT_COMMITTER_EMAIL=%s\n", author_email);
90 printf("export GIT_COMMITTER_DATE='+0000 %s'\n", date);
91
92 printf("export GIT_AUTHOR_NAME=%s\n", author_name);
93 printf("export GIT_AUTHOR_EMAIL=%s\n", author_email);
94 printf("export GIT_AUTHOR_DATE='+0000 %s'\n", date);
95
96 if (initial_commit)
97 return;
98
99 src_branch = *ancestor ? ancestor : branch;
100 if (!strcmp(src_branch, "HEAD"))
101 src_branch = "master";
102 printf("ln -sf refs/heads/'%s' .git/HEAD\n", src_branch);
103
104 /*
105 * Even if cvsps claims an ancestor, we'll let the new
106 * branch name take precedence if it already exists
107 */
108 if (*ancestor) {
109 src_branch = branch;
110 if (!strcmp(src_branch, "HEAD"))
111 src_branch = "master";
112 printf("[ -e .git/refs/heads/'%s' ] && ln -sf refs/heads/'%s' .git/HEAD\n",
113 src_branch, src_branch);
114 }
115
116 printf("git-read-tree -m HEAD || exit 1\n");
117 printf("git-checkout-cache -f -u -a\n");
118}
119
120static void commit(void)
121{
122 const char *cmit_parent = initial_commit ? "" : "-p HEAD";
123 const char *dst_branch;
124 char *space;
125 int i;
126
127 printf("tree=$(git-write-tree)\n");
128 printf("cat > .cmitmsg <<EOFMSG\n");
129
130 /* Escape $ characters, and remove control characters */
131 for (i = 0; i < loglen; i++) {
132 unsigned char c = log[i];
133
134 switch (c) {
135 case '$':
136 case '\\':
137 case '`':
138 putchar('\\');
139 break;
140 case 0 ... 31:
141 if (c == '\n' || c == '\t')
142 break;
143 case 128 ... 159:
144 continue;
145 }
146 putchar(c);
147 }
148 printf("\nEOFMSG\n");
149 printf("commit=$(cat .cmitmsg | git-commit-tree $tree %s)\n", cmit_parent);
150
151 dst_branch = branch;
152 if (!strcmp(dst_branch, "HEAD"))
153 dst_branch = "master";
154
155 printf("echo $commit > .git/refs/heads/'%s'\n", dst_branch);
156
157 space = strchr(tag, ' ');
158 if (space)
159 *space = 0;
160 if (strcmp(tag, "(none)"))
161 printf("echo $commit > .git/refs/tags/'%s'\n", tag);
162
163 printf("echo 'Committed (to %s):' ; cat .cmitmsg; echo\n", dst_branch);
164
165 *date = 0;
166 *author = 0;
167 *branch = 0;
168 *ancestor = 0;
169 *tag = 0;
170 loglen = 0;
171
172 initial_commit = 0;
173}
174
175static void update_file(char *line)
176{
177 char *name, *version;
178 char *dir;
179
180 while (isspace(*line))
181 line++;
182 name = line;
183 line = strchr(line, ':');
184 if (!line)
185 return;
186 *line++ = 0;
187 line = strchr(line, '>');
188 if (!line)
189 return;
190 *line++ = 0;
191 version = line;
192 line = strchr(line, '(');
193 if (line) { /* "(DEAD)" */
194 printf("git-update-cache --force-remove '%s'\n", name);
195 return;
196 }
197
198 dir = strrchr(name, '/');
199 if (dir)
200 printf("mkdir -p %.*s\n", (int)(dir - name), name);
201
202 printf("cvs -q -d %s checkout -d .git-tmp -r%s '%s/%s'\n",
203 cvsroot, version, cvsmodule, name);
204 printf("mv -f .git-tmp/%s %s\n", dir ? dir+1 : name, name);
205 printf("rm -rf .git-tmp\n");
206 printf("git-update-cache --add -- '%s'\n", name);
207}
208
209struct hdrentry {
210 const char *name;
211 char *dest;
212} hdrs[] = {
213 { "Date:", date },
214 { "Author:", author },
215 { "Branch:", branch },
216 { "Ancestor branch:", ancestor },
217 { "Tag:", tag },
218 { "Log:", NULL },
219 { NULL, NULL }
220};
221
222int main(int argc, char **argv)
223{
224 static char line[1000];
225 enum state state = Header;
226 int i;
227
228 for (i = 1; i < argc; i++) {
229 const char *arg = argv[i];
230 if (!memcmp(arg, "--cvsroot=", 10)) {
231 cvsroot = arg + 10;
232 continue;
233 }
234 if (!memcmp(arg, "--module=", 9)) {
235 cvsmodule = arg+9;
236 continue;
237 }
238 if (!strcmp(arg, "-v")) {
239 verbose = 1;
240 continue;
241 }
242 if (!strcmp(arg, "-u")) {
243 initial_commit = 0;
244 continue;
245 }
246 }
247
248
249 if (!cvsroot)
250 cvsroot = getenv("CVSROOT");
251
252 if (!cvsmodule || !cvsroot) {
253 fprintf(stderr, "I need a CVSROOT and module name\n");
254 exit(1);
255 }
256
257 if (initial_commit) {
258 printf("[ -d .git ] && exit 1\n");
259 printf("git-init-db\n");
260 printf("mkdir -p .git/refs/heads\n");
261 printf("mkdir -p .git/refs/tags\n");
262 printf("ln -sf refs/heads/master .git/HEAD\n");
263 }
264
265 while (fgets(line, sizeof(line), stdin) != NULL) {
266 int linelen = strlen(line);
267
268 while (linelen && isspace(line[linelen-1]))
269 line[--linelen] = 0;
270
271 switch (state) {
272 struct hdrentry *entry;
273
274 case Header:
275 if (verbose)
276 printf("# H: %s\n", line);
277 for (entry = hdrs ; entry->name ; entry++) {
278 int len = strlen(entry->name);
279 char *val;
280
281 if (memcmp(entry->name, line, len))
282 continue;
283 if (!entry->dest) {
284 state = Log;
285 break;
286 }
287 val = line + len;
288 linelen -= len;
289 while (isspace(*val)) {
290 val++;
291 linelen--;
292 }
293 memcpy(entry->dest, val, linelen+1);
294 break;
295 }
296 continue;
297
298 case Log:
299 if (verbose)
300 printf("# L: %s\n", line);
301 if (!strcmp(line, "Members:")) {
302 while (loglen && isspace(log[loglen-1]))
303 log[--loglen] = 0;
304 prepare_commit();
305 state = Members;
306 continue;
307 }
308
309 if (loglen + linelen + 5 > sizeof(log))
310 continue;
311 memcpy(log + loglen, line, linelen);
312 loglen += linelen;
313 log[loglen++] = '\n';
314 continue;
315
316 case Members:
317 if (verbose)
318 printf("# M: %s\n", line);
319 if (!linelen) {
320 commit();
321 state = Header;
322 continue;
323 }
324 update_file(line);
325 continue;
326 }
327 }
328 return 0;
329}