cvs2git.con commit Fix typo in git-checkout-script. (9661c25)
   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 *              git-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 *      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 *
  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 */
  45enum state {
  46        Header,
  47        Log,
  48        Members
  49};
  50
  51static const char *cvsroot;
  52static const char *cvsmodule;
  53
  54static char date[100];
  55static char author[100];
  56static char branch[100];
  57static char ancestor[100];
  58static char tag[100];
  59static char log[32768];
  60static int loglen = 0;
  61static int initial_commit = 1;
  62
  63static 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
  81static 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
 120static void commit(void)
 121{
 122        const char *cmit_parent = initial_commit ? "" : "-p HEAD";
 123        const char *dst_branch;
 124        char *space;
 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
 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
 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
 175static 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
 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");
 206        printf("git-update-cache --add -- '%s'\n", name);
 207}
 208
 209struct 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
 222int 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                }
 242                if (!strcmp(arg, "-u")) {
 243                        initial_commit = 0;
 244                        continue;
 245                }
 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
 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        }
 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}