]>
Commit | Line | Data |
---|---|---|
d4f8b390 LT |
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 | | |
31b6d200 | 31 | * git-cvs2git --cvsroot=[root] --module=[module] > script |
d4f8b390 LT |
32 | * |
33 | * Creates a shell script that will generate the .git archive of | |
34 | * the names CVS repository. | |
35 | * | |
31b6d200 PI |
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 | * | |
d4f8b390 LT |
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; | |
32798c70 | 124 | char *space; |
d4f8b390 LT |
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 | ||
32798c70 SV |
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 | ||
d4f8b390 LT |
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 | ||
deb153a7 SV |
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"); | |
d4f8b390 LT |
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 | } | |
31b6d200 PI |
242 | if (!strcmp(arg, "-u")) { |
243 | initial_commit = 0; | |
244 | continue; | |
245 | } | |
d4f8b390 LT |
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 | ||
31b6d200 PI |
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 | } | |
d4f8b390 LT |
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 | } |