cvs2git: fix character quoting
[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 enum state {
29         Header,
30         Log,
31         Members
32 };
33
34 static char *rcsdir;
35
36 static char date[100];
37 static char author[100];
38 static char branch[100];
39 static char ancestor[100];
40 static char tag[100];
41 static char log[32768];
42 static int loglen = 0;
43 static int initial_commit = 1;
44
45 static void lookup_author(char *n, char **name, char **email)
46 {
47         /*
48          * FIXME!!! I'm lazy and stupid.
49          *
50          * This could be something like
51          *
52          *      printf("lookup_author '%s'\n", n);
53          *      *name = "$author_name";
54          *      *email = "$author_email";
55          *
56          * and that would allow the script to do its own
57          * lookups at run-time.
58          */
59         *name = n;
60         *email = n;
61 }
62
63 static void prepare_commit(void)
64 {
65         char *author_name, *author_email;
66         char *src_branch;
67
68         lookup_author(author, &author_name, &author_email);
69
70         printf("export GIT_COMMITTER_NAME=%s\n", author_name);
71         printf("export GIT_COMMITTER_EMAIL=%s\n", author_email);
72         printf("export GIT_COMMITTER_DATE='+0000 %s'\n", date);
73
74         printf("export GIT_AUTHOR_NAME=%s\n", author_name);
75         printf("export GIT_AUTHOR_EMAIL=%s\n", author_email);
76         printf("export GIT_AUTHOR_DATE='+0000 %s'\n", date);
77
78         if (initial_commit)
79                 return;
80
81         src_branch = *ancestor ? ancestor : branch;
82         if (!strcmp(src_branch, "HEAD"))
83                 src_branch = "master";
84         printf("ln -sf refs/heads/'%s' .git/HEAD\n", src_branch);
85
86         /*
87          * Even if cvsps claims an ancestor, we'll let the new
88          * branch name take precedence if it already exists
89          */
90         if (*ancestor) {
91                 src_branch = branch;
92                 if (!strcmp(src_branch, "HEAD"))
93                         src_branch = "master";
94                 printf("[ -e .git/refs/heads/'%s' ] && ln -sf refs/heads/'%s' .git/HEAD\n",
95                         src_branch, src_branch);
96         }
97
98         printf("git-read-tree -m HEAD || exit 1\n");
99         printf("git-checkout-cache -f -u -a\n");
100 }
101
102 static void commit(void)
103 {
104         const char *cmit_parent = initial_commit ? "" : "-p HEAD";
105         const char *dst_branch;
106         int i;
107
108         printf("tree=$(git-write-tree)\n");
109         printf("cat > .cmitmsg <<EOFMSG\n");
110
111         /* Escape $ characters, and remove control characters */
112         for (i = 0; i < loglen; i++) {
113                 unsigned char c = log[i];
114
115                 switch (c) {
116                 case '$':
117                 case '\\':
118                 case '`':
119                         putchar('\\');
120                         break;
121                 case 0 ... 31:
122                         if (c == '\n' || c == '\t')
123                                 break;
124                 case 128 ... 159:
125                         continue;
126                 }
127                 putchar(c);
128         }
129         printf("\nEOFMSG\n");
130         printf("commit=$(cat .cmitmsg | git-commit-tree $tree %s)\n", cmit_parent);
131
132         dst_branch = branch;
133         if (!strcmp(dst_branch, "HEAD"))
134                 dst_branch = "master";
135
136         printf("echo $commit > .git/refs/heads/'%s'\n", dst_branch);
137
138         printf("echo 'Committed (to %s):' ; cat .cmitmsg; echo\n", dst_branch);
139
140         *date = 0;
141         *author = 0;
142         *branch = 0;
143         *ancestor = 0;
144         *tag = 0;
145         loglen = 0;
146
147         initial_commit = 0;
148 }
149
150 static void get_rcs_name(char *rcspathname, char *name, char *dir)
151 {
152         sprintf(rcspathname, "%s/%s,v", rcsdir, name);
153         if (!access(rcspathname, R_OK))
154                 return;
155
156         sprintf(rcspathname, "%s/Attic/%s,v", rcsdir, name);
157         if (!access(rcspathname, R_OK))
158                 return;
159
160         if (dir) {
161                 sprintf(rcspathname, "%s/%.*s/Attic/%s,v", rcsdir, (int)(dir - name), name, dir+1);
162                 if (!access(rcspathname, R_OK))
163                         return;
164         }
165         fprintf(stderr, "Unable to find RCS file for %s\n", name);
166         exit(1);
167 }
168
169 static void update_file(char *line)
170 {
171         static char rcspathname[4096];
172         char *name, *version;
173         char *dir;
174
175         while (isspace(*line))
176                 line++;
177         name = line;
178         line = strchr(line, ':');
179         if (!line)
180                 return;
181         *line++ = 0;
182         line = strchr(line, '>');
183         if (!line)
184                 return;
185         *line++ = 0;
186         version = line;
187         line = strchr(line, '(');
188         if (line) {     /* "(DEAD)" */
189                 printf("git-update-cache --force-remove '%s'\n", name);
190                 return;
191         }
192
193         dir = strrchr(name, '/');
194         if (dir)
195                 printf("mkdir -p %.*s\n", (int)(dir - name), name);
196
197         get_rcs_name(rcspathname, name, dir);
198                 
199         printf("co -q -p -r%s '%s' > '%s'\n", version, rcspathname, name);
200         printf("git-update-cache --add -- '%s'\n", name);
201 }
202
203 struct hdrentry {
204         const char *name;
205         char *dest;
206 } hdrs[] = {
207         { "Date:", date },
208         { "Author:", author },
209         { "Branch:", branch },
210         { "Ancestor branch:", ancestor },
211         { "Tag:", tag },
212         { "Log:", NULL },
213         { NULL, NULL }
214 };
215
216 int main(int argc, char **argv)
217 {
218         static char line[1000];
219         enum state state = Header;
220
221         rcsdir = getenv("RCSDIR");
222         if (!rcsdir) {
223                 fprintf(stderr, "I need an $RCSDIR\n");
224                 exit(1);
225         }
226
227         printf("[ -d .git ] && exit 1\n");
228         printf("git-init-db\n");
229         printf("mkdir -p .git/refs/heads\n");
230         printf("mkdir -p .git/refs/tags\n");
231         printf("ln -sf refs/heads/master .git/HEAD\n");
232
233         while (fgets(line, sizeof(line), stdin) != NULL) {
234                 int linelen = strlen(line);
235
236                 while (linelen && isspace(line[linelen-1]))
237                         line[--linelen] = 0;
238
239                 switch (state) {
240                 struct hdrentry *entry;
241
242                 case Header:
243                         if (verbose)
244                                 printf("# H: %s\n", line);
245                         for (entry = hdrs ; entry->name ; entry++) {
246                                 int len = strlen(entry->name);
247                                 char *val;
248
249                                 if (memcmp(entry->name, line, len))
250                                         continue;
251                                 if (!entry->dest) {
252                                         state = Log;
253                                         break;
254                                 }
255                                 val = line + len;
256                                 linelen -= len;
257                                 while (isspace(*val)) {
258                                         val++;
259                                         linelen--;
260                                 }
261                                 memcpy(entry->dest, val, linelen+1);
262                                 break;
263                         }
264                         continue;
265
266                 case Log:
267                         if (verbose)
268                                 printf("# L: %s\n", line);
269                         if (!strcmp(line, "Members:")) {
270                                 while (loglen && isspace(log[loglen-1]))
271                                         log[--loglen] = 0;
272                                 prepare_commit();
273                                 state = Members;
274                                 continue;
275                         }
276                                 
277                         if (loglen + linelen + 5 > sizeof(log))
278                                 continue;
279                         memcpy(log + loglen, line, linelen);
280                         loglen += linelen;
281                         log[loglen++] = '\n';
282                         continue;
283
284                 case Members:
285                         if (verbose)
286                                 printf("# M: %s\n", line);
287                         if (!linelen) {
288                                 commit();
289                                 state = Header;
290                                 continue;
291                         }
292                         update_file(line);
293                         continue;
294                 }
295         }
296         return 0;
297 }