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 * 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 */
39enum state {
40 Header,
41 Log,
42 Members
43};
44
45static const char *cvsroot;
46static const char *cvsmodule;
47
48static char date[100];
49static char author[100];
50static char branch[100];
51static char ancestor[100];
52static char tag[100];
53static char log[32768];
54static int loglen = 0;
55static int initial_commit = 1;
56
57static 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
75static 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
114static void commit(void)
115{
116 const char *cmit_parent = initial_commit ? "" : "-p HEAD";
117 const char *dst_branch;
118 int i;
119
120 printf("tree=$(git-write-tree)\n");
121 printf("cat > .cmitmsg <<EOFMSG\n");
122
123 /* Escape $ characters, and remove control characters */
124 for (i = 0; i < loglen; i++) {
125 unsigned char c = log[i];
126
127 switch (c) {
128 case '$':
129 case '\\':
130 case '`':
131 putchar('\\');
132 break;
133 case 0 ... 31:
134 if (c == '\n' || c == '\t')
135 break;
136 case 128 ... 159:
137 continue;
138 }
139 putchar(c);
140 }
141 printf("\nEOFMSG\n");
142 printf("commit=$(cat .cmitmsg | git-commit-tree $tree %s)\n", cmit_parent);
143
144 dst_branch = branch;
145 if (!strcmp(dst_branch, "HEAD"))
146 dst_branch = "master";
147
148 printf("echo $commit > .git/refs/heads/'%s'\n", dst_branch);
149
150 printf("echo 'Committed (to %s):' ; cat .cmitmsg; echo\n", dst_branch);
151
152 *date = 0;
153 *author = 0;
154 *branch = 0;
155 *ancestor = 0;
156 *tag = 0;
157 loglen = 0;
158
159 initial_commit = 0;
160}
161
162static void update_file(char *line)
163{
164 char *name, *version;
165 char *dir;
166
167 while (isspace(*line))
168 line++;
169 name = line;
170 line = strchr(line, ':');
171 if (!line)
172 return;
173 *line++ = 0;
174 line = strchr(line, '>');
175 if (!line)
176 return;
177 *line++ = 0;
178 version = line;
179 line = strchr(line, '(');
180 if (line) { /* "(DEAD)" */
181 printf("git-update-cache --force-remove '%s'\n", name);
182 return;
183 }
184
185 dir = strrchr(name, '/');
186 if (dir)
187 printf("mkdir -p %.*s\n", (int)(dir - name), name);
188
189 printf("cvs -q -d %s checkout -r%s -p '%s/%s' > '%s'\n", cvsroot, version, cvsmodule, name, name);
190 printf("git-update-cache --add -- '%s'\n", name);
191}
192
193struct hdrentry {
194 const char *name;
195 char *dest;
196} hdrs[] = {
197 { "Date:", date },
198 { "Author:", author },
199 { "Branch:", branch },
200 { "Ancestor branch:", ancestor },
201 { "Tag:", tag },
202 { "Log:", NULL },
203 { NULL, NULL }
204};
205
206int main(int argc, char **argv)
207{
208 static char line[1000];
209 enum state state = Header;
210 int i;
211
212 for (i = 1; i < argc; i++) {
213 const char *arg = argv[i];
214 if (!memcmp(arg, "--cvsroot=", 10)) {
215 cvsroot = arg + 10;
216 continue;
217 }
218 if (!memcmp(arg, "--module=", 9)) {
219 cvsmodule = arg+9;
220 continue;
221 }
222 if (!strcmp(arg, "-v")) {
223 verbose = 1;
224 continue;
225 }
226 }
227
228
229 if (!cvsroot)
230 cvsroot = getenv("CVSROOT");
231
232 if (!cvsmodule || !cvsroot) {
233 fprintf(stderr, "I need a CVSROOT and module name\n");
234 exit(1);
235 }
236
237 printf("[ -d .git ] && exit 1\n");
238 printf("git-init-db\n");
239 printf("mkdir -p .git/refs/heads\n");
240 printf("mkdir -p .git/refs/tags\n");
241 printf("ln -sf refs/heads/master .git/HEAD\n");
242
243 while (fgets(line, sizeof(line), stdin) != NULL) {
244 int linelen = strlen(line);
245
246 while (linelen && isspace(line[linelen-1]))
247 line[--linelen] = 0;
248
249 switch (state) {
250 struct hdrentry *entry;
251
252 case Header:
253 if (verbose)
254 printf("# H: %s\n", line);
255 for (entry = hdrs ; entry->name ; entry++) {
256 int len = strlen(entry->name);
257 char *val;
258
259 if (memcmp(entry->name, line, len))
260 continue;
261 if (!entry->dest) {
262 state = Log;
263 break;
264 }
265 val = line + len;
266 linelen -= len;
267 while (isspace(*val)) {
268 val++;
269 linelen--;
270 }
271 memcpy(entry->dest, val, linelen+1);
272 break;
273 }
274 continue;
275
276 case Log:
277 if (verbose)
278 printf("# L: %s\n", line);
279 if (!strcmp(line, "Members:")) {
280 while (loglen && isspace(log[loglen-1]))
281 log[--loglen] = 0;
282 prepare_commit();
283 state = Members;
284 continue;
285 }
286
287 if (loglen + linelen + 5 > sizeof(log))
288 continue;
289 memcpy(log + loglen, line, linelen);
290 loglen += linelen;
291 log[loglen++] = '\n';
292 continue;
293
294 case Members:
295 if (verbose)
296 printf("# M: %s\n", line);
297 if (!linelen) {
298 commit();
299 state = Header;
300 continue;
301 }
302 update_file(line);
303 continue;
304 }
305 }
306 return 0;
307}