[PATCH] cvs2git.c: support incremental conversion
[git] / cvs2git.c
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
13 static 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  */
45 enum state {
46         Header,
47         Log,
48         Members
49 };
50
51 static const char *cvsroot;
52 static const char *cvsmodule;
53
54 static char date[100];
55 static char author[100];
56 static char branch[100];
57 static char ancestor[100];
58 static char tag[100];
59 static char log[32768];
60 static int loglen = 0;
61 static int initial_commit = 1;
62
63 static 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
81 static 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
120 static 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
175 static 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
209 struct 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
222 int 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 }