d438475d03b7ff897a9e2b976a34a75b86c01a80
   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        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
 169static 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
 203struct 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
 216int 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}