/* * cvs2git * * Copyright (C) Linus Torvalds 2005 */ #include #include #include #include #include static int verbose = 0; /* * This is a really stupid program that takes cvsps output, and * generates a a long _shell_script_ that will create the GIT archive * from it. * * You've been warned. I told you it was stupid. * * NOTE NOTE NOTE! In order to do branches correctly, this needs * the fixed cvsps that has the "Ancestor branch" tag output. * Hopefully David Mansfield will update his distribution soon * enough (he's the one who wrote the patch, so at least we don't * have to figt maintainer issues ;) * * Usage: * * TZ=UTC cvsps -A | * git-cvs2git --cvsroot=[root] --module=[module] > script * * Creates a shell script that will generate the .git archive of * the names CVS repository. * * TZ=UTC cvsps -s 1234- -A | * git-cvs2git -u --cvsroot=[root] --module=[module] > script * * Creates a shell script that will update the .git archive with * CVS changes from patchset 1234 until the last one. * * IMPORTANT NOTE ABOUT "cvsps"! This requires version 2.1 or better, * and the "TZ=UTC" and the "-A" flag is required for sane results! */ enum state { Header, Log, Members }; static const char *cvsroot; static const char *cvsmodule; static char date[100]; static char author[100]; static char branch[100]; static char ancestor[100]; static char tag[100]; static char log[32768]; static int loglen = 0; static int initial_commit = 1; static void lookup_author(char *n, char **name, char **email) { /* * FIXME!!! I'm lazy and stupid. * * This could be something like * * printf("lookup_author '%s'\n", n); * *name = "$author_name"; * *email = "$author_email"; * * and that would allow the script to do its own * lookups at run-time. */ *name = n; *email = n; } static void prepare_commit(void) { char *author_name, *author_email; char *src_branch; lookup_author(author, &author_name, &author_email); printf("export GIT_COMMITTER_NAME=%s\n", author_name); printf("export GIT_COMMITTER_EMAIL=%s\n", author_email); printf("export GIT_COMMITTER_DATE='+0000 %s'\n", date); printf("export GIT_AUTHOR_NAME=%s\n", author_name); printf("export GIT_AUTHOR_EMAIL=%s\n", author_email); printf("export GIT_AUTHOR_DATE='+0000 %s'\n", date); if (initial_commit) return; src_branch = *ancestor ? ancestor : branch; if (!strcmp(src_branch, "HEAD")) src_branch = "master"; printf("ln -sf refs/heads/'%s' .git/HEAD\n", src_branch); /* * Even if cvsps claims an ancestor, we'll let the new * branch name take precedence if it already exists */ if (*ancestor) { src_branch = branch; if (!strcmp(src_branch, "HEAD")) src_branch = "master"; printf("[ -e .git/refs/heads/'%s' ] && ln -sf refs/heads/'%s' .git/HEAD\n", src_branch, src_branch); } printf("git-read-tree -m HEAD || exit 1\n"); printf("git-checkout-cache -f -u -a\n"); } static void commit(void) { const char *cmit_parent = initial_commit ? "" : "-p HEAD"; const char *dst_branch; char *space; int i; printf("tree=$(git-write-tree)\n"); printf("cat > .cmitmsg < .git/refs/heads/'%s'\n", dst_branch); space = strchr(tag, ' '); if (space) *space = 0; if (strcmp(tag, "(none)")) printf("echo $commit > .git/refs/tags/'%s'\n", tag); printf("echo 'Committed (to %s):' ; cat .cmitmsg; echo\n", dst_branch); *date = 0; *author = 0; *branch = 0; *ancestor = 0; *tag = 0; loglen = 0; initial_commit = 0; } static void update_file(char *line) { char *name, *version; char *dir; while (isspace(*line)) line++; name = line; line = strchr(line, ':'); if (!line) return; *line++ = 0; line = strchr(line, '>'); if (!line) return; *line++ = 0; version = line; line = strchr(line, '('); if (line) { /* "(DEAD)" */ printf("git-update-cache --force-remove '%s'\n", name); return; } dir = strrchr(name, '/'); if (dir) printf("mkdir -p %.*s\n", (int)(dir - name), name); printf("cvs -q -d %s checkout -d .git-tmp -r%s '%s/%s'\n", cvsroot, version, cvsmodule, name); printf("mv -f .git-tmp/%s %s\n", dir ? dir+1 : name, name); printf("rm -rf .git-tmp\n"); printf("git-update-cache --add -- '%s'\n", name); } struct hdrentry { const char *name; char *dest; } hdrs[] = { { "Date:", date }, { "Author:", author }, { "Branch:", branch }, { "Ancestor branch:", ancestor }, { "Tag:", tag }, { "Log:", NULL }, { NULL, NULL } }; int main(int argc, char **argv) { static char line[1000]; enum state state = Header; int i; for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!memcmp(arg, "--cvsroot=", 10)) { cvsroot = arg + 10; continue; } if (!memcmp(arg, "--module=", 9)) { cvsmodule = arg+9; continue; } if (!strcmp(arg, "-v")) { verbose = 1; continue; } if (!strcmp(arg, "-u")) { initial_commit = 0; continue; } } if (!cvsroot) cvsroot = getenv("CVSROOT"); if (!cvsmodule || !cvsroot) { fprintf(stderr, "I need a CVSROOT and module name\n"); exit(1); } if (initial_commit) { printf("[ -d .git ] && exit 1\n"); printf("git-init-db\n"); printf("mkdir -p .git/refs/heads\n"); printf("mkdir -p .git/refs/tags\n"); printf("ln -sf refs/heads/master .git/HEAD\n"); } while (fgets(line, sizeof(line), stdin) != NULL) { int linelen = strlen(line); while (linelen && isspace(line[linelen-1])) line[--linelen] = 0; switch (state) { struct hdrentry *entry; case Header: if (verbose) printf("# H: %s\n", line); for (entry = hdrs ; entry->name ; entry++) { int len = strlen(entry->name); char *val; if (memcmp(entry->name, line, len)) continue; if (!entry->dest) { state = Log; break; } val = line + len; linelen -= len; while (isspace(*val)) { val++; linelen--; } memcpy(entry->dest, val, linelen+1); break; } continue; case Log: if (verbose) printf("# L: %s\n", line); if (!strcmp(line, "Members:")) { while (loglen && isspace(log[loglen-1])) log[--loglen] = 0; prepare_commit(); state = Members; continue; } if (loglen + linelen + 5 > sizeof(log)) continue; memcpy(log + loglen, line, linelen); loglen += linelen; log[loglen++] = '\n'; continue; case Members: if (verbose) printf("# M: %s\n", line); if (!linelen) { commit(); state = Header; continue; } update_file(line); continue; } } return 0; }