[PATCH] ssh-push.c: Fix handling of ssh://host/path URLs
[git.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  *              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  * IMPORTANT NOTE ABOUT "cvsps"! This requires version 2.1 or better,
37  * and the "TZ=UTC" and the "-A" flag is required for sane results!
38  */
39 enum state {
40         Header,
41         Log,
42         Members
43 };
44
45 static const char *cvsroot;
46 static const char *cvsmodule;
47
48 static char date[100];
49 static char author[100];
50 static char branch[100];
51 static char ancestor[100];
52 static char tag[100];
53 static char log[32768];
54 static int loglen = 0;
55 static int initial_commit = 1;
56
57 static void lookup_author(char *n, char **name, char **email)
58 {
59         /*
60          * FIXME!!! I'm lazy and stupid.
61          *
62          * This could be something like
63          *
64          *      printf("lookup_author '%s'\n", n);
65          *      *name = "$author_name";
66          *      *email = "$author_email";
67          *
68          * and that would allow the script to do its own
69          * lookups at run-time.
70          */
71         *name = n;
72         *email = n;
73 }
74
75 static void prepare_commit(void)
76 {
77         char *author_name, *author_email;
78         char *src_branch;
79
80         lookup_author(author, &author_name, &author_email);
81
82         printf("export GIT_COMMITTER_NAME=%s\n", author_name);
83         printf("export GIT_COMMITTER_EMAIL=%s\n", author_email);
84         printf("export GIT_COMMITTER_DATE='+0000 %s'\n", date);
85
86         printf("export GIT_AUTHOR_NAME=%s\n", author_name);
87         printf("export GIT_AUTHOR_EMAIL=%s\n", author_email);
88         printf("export GIT_AUTHOR_DATE='+0000 %s'\n", date);
89
90         if (initial_commit)
91                 return;
92
93         src_branch = *ancestor ? ancestor : branch;
94         if (!strcmp(src_branch, "HEAD"))
95                 src_branch = "master";
96         printf("ln -sf refs/heads/'%s' .git/HEAD\n", src_branch);
97
98         /*
99          * Even if cvsps claims an ancestor, we'll let the new
100          * branch name take precedence if it already exists
101          */
102         if (*ancestor) {
103                 src_branch = branch;
104                 if (!strcmp(src_branch, "HEAD"))
105                         src_branch = "master";
106                 printf("[ -e .git/refs/heads/'%s' ] && ln -sf refs/heads/'%s' .git/HEAD\n",
107                         src_branch, src_branch);
108         }
109
110         printf("git-read-tree -m HEAD || exit 1\n");
111         printf("git-checkout-cache -f -u -a\n");
112 }
113
114 static void commit(void)
115 {
116         const char *cmit_parent = initial_commit ? "" : "-p HEAD";
117         const char *dst_branch;
118         char *space;
119         int i;
120
121         printf("tree=$(git-write-tree)\n");
122         printf("cat > .cmitmsg <<EOFMSG\n");
123
124         /* Escape $ characters, and remove control characters */
125         for (i = 0; i < loglen; i++) {
126                 unsigned char c = log[i];
127
128                 switch (c) {
129                 case '$':
130                 case '\\':
131                 case '`':
132                         putchar('\\');
133                         break;
134                 case 0 ... 31:
135                         if (c == '\n' || c == '\t')
136                                 break;
137                 case 128 ... 159:
138                         continue;
139                 }
140                 putchar(c);
141         }
142         printf("\nEOFMSG\n");
143         printf("commit=$(cat .cmitmsg | git-commit-tree $tree %s)\n", cmit_parent);
144
145         dst_branch = branch;
146         if (!strcmp(dst_branch, "HEAD"))
147                 dst_branch = "master";
148
149         printf("echo $commit > .git/refs/heads/'%s'\n", dst_branch);
150
151         space = strchr(tag, ' ');
152         if (space)
153                 *space = 0;
154         if (strcmp(tag, "(none)"))
155                 printf("echo $commit > .git/refs/tags/'%s'\n", tag);
156
157         printf("echo 'Committed (to %s):' ; cat .cmitmsg; echo\n", dst_branch);
158
159         *date = 0;
160         *author = 0;
161         *branch = 0;
162         *ancestor = 0;
163         *tag = 0;
164         loglen = 0;
165
166         initial_commit = 0;
167 }
168
169 static void update_file(char *line)
170 {
171         char *name, *version;
172         char *dir;
173
174         while (isspace(*line))
175                 line++;
176         name = line;
177         line = strchr(line, ':');
178         if (!line)
179                 return;
180         *line++ = 0;
181         line = strchr(line, '>');
182         if (!line)
183                 return;
184         *line++ = 0;
185         version = line;
186         line = strchr(line, '(');
187         if (line) {     /* "(DEAD)" */
188                 printf("git-update-cache --force-remove '%s'\n", name);
189                 return;
190         }
191
192         dir = strrchr(name, '/');
193         if (dir)
194                 printf("mkdir -p %.*s\n", (int)(dir - name), name);
195
196         printf("cvs -q -d %s checkout -d .git-tmp -r%s '%s/%s'\n", 
197                 cvsroot, version, cvsmodule, name);
198         printf("mv -f .git-tmp/%s %s\n", dir ? dir+1 : name, name);
199         printf("rm -rf .git-tmp\n");
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         int i;
221
222         for (i = 1; i < argc; i++) {
223                 const char *arg = argv[i];
224                 if (!memcmp(arg, "--cvsroot=", 10)) {
225                         cvsroot = arg + 10;
226                         continue;
227                 }
228                 if (!memcmp(arg, "--module=", 9)) {
229                         cvsmodule = arg+9;
230                         continue;
231                 } 
232                 if (!strcmp(arg, "-v")) {
233                         verbose = 1;
234                         continue;
235                 }
236         }
237
238
239         if (!cvsroot)
240                 cvsroot = getenv("CVSROOT");
241
242         if (!cvsmodule || !cvsroot) {
243                 fprintf(stderr, "I need a CVSROOT and module name\n");
244                 exit(1);
245         }
246
247         printf("[ -d .git ] && exit 1\n");
248         printf("git-init-db\n");
249         printf("mkdir -p .git/refs/heads\n");
250         printf("mkdir -p .git/refs/tags\n");
251         printf("ln -sf refs/heads/master .git/HEAD\n");
252
253         while (fgets(line, sizeof(line), stdin) != NULL) {
254                 int linelen = strlen(line);
255
256                 while (linelen && isspace(line[linelen-1]))
257                         line[--linelen] = 0;
258
259                 switch (state) {
260                 struct hdrentry *entry;
261
262                 case Header:
263                         if (verbose)
264                                 printf("# H: %s\n", line);
265                         for (entry = hdrs ; entry->name ; entry++) {
266                                 int len = strlen(entry->name);
267                                 char *val;
268
269                                 if (memcmp(entry->name, line, len))
270                                         continue;
271                                 if (!entry->dest) {
272                                         state = Log;
273                                         break;
274                                 }
275                                 val = line + len;
276                                 linelen -= len;
277                                 while (isspace(*val)) {
278                                         val++;
279                                         linelen--;
280                                 }
281                                 memcpy(entry->dest, val, linelen+1);
282                                 break;
283                         }
284                         continue;
285
286                 case Log:
287                         if (verbose)
288                                 printf("# L: %s\n", line);
289                         if (!strcmp(line, "Members:")) {
290                                 while (loglen && isspace(log[loglen-1]))
291                                         log[--loglen] = 0;
292                                 prepare_commit();
293                                 state = Members;
294                                 continue;
295                         }
296                                 
297                         if (loglen + linelen + 5 > sizeof(log))
298                                 continue;
299                         memcpy(log + loglen, line, linelen);
300                         loglen += linelen;
301                         log[loglen++] = '\n';
302                         continue;
303
304                 case Members:
305                         if (verbose)
306                                 printf("# M: %s\n", line);
307                         if (!linelen) {
308                                 commit();
309                                 state = Header;
310                                 continue;
311                         }
312                         update_file(line);
313                         continue;
314                 }
315         }
316         return 0;
317 }