1adc918f51d668fe818468213f64b5d476148bb3
   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 */
  28enum state {
  29        Header,
  30        Log,
  31        Members
  32};
  33
  34static char *rcsdir;
  35
  36static char date[100];
  37static char author[100];
  38static char branch[100];
  39static char ancestor[100];
  40static char tag[100];
  41static char log[32768];
  42static int loglen = 0;
  43static int initial_commit = 1;
  44
  45static void lookup_author(char *n, char **name, char **email)
  46{
  47        /*
  48         * FIXME!!! I'm lazy and stupid.
  49         *
  50         * This could be something like
  51         *
  52         *      printf("lookup_author '%s'\n", n);
  53         *      *name = "$author_name";
  54         *      *email = "$author_email";
  55         *
  56         * and that would allow the script to do its own
  57         * lookups at run-time.
  58         */
  59        *name = n;
  60        *email = n;
  61}
  62
  63static void prepare_commit(void)
  64{
  65        char *author_name, *author_email;
  66        char *src_branch;
  67
  68        lookup_author(author, &author_name, &author_email);
  69
  70        printf("export GIT_COMMITTER_NAME=%s\n", author_name);
  71        printf("export GIT_COMMITTER_EMAIL=%s\n", author_email);
  72        printf("export GIT_COMMITTER_DATE='+0000 %s'\n", date);
  73
  74        printf("export GIT_AUTHOR_NAME=%s\n", author_name);
  75        printf("export GIT_AUTHOR_EMAIL=%s\n", author_email);
  76        printf("export GIT_AUTHOR_DATE='+0000 %s'\n", date);
  77
  78        if (initial_commit)
  79                return;
  80
  81        src_branch = *ancestor ? ancestor : branch;
  82        if (!strcmp(src_branch, "HEAD"))
  83                src_branch = "master";
  84        printf("ln -sf refs/heads/'%s' .git/HEAD\n", src_branch);
  85
  86        /*
  87         * Even if cvsps claims an ancestor, we'll let the new
  88         * branch name take precedence if it already exists
  89         */
  90        if (*ancestor) {
  91                src_branch = branch;
  92                if (!strcmp(src_branch, "HEAD"))
  93                        src_branch = "master";
  94                printf("[ -e .git/refs/heads/'%s' ] && ln -sf refs/heads/'%s' .git/HEAD\n",
  95                        src_branch, src_branch);
  96        }
  97
  98        printf("git-read-tree -m HEAD || exit 1\n");
  99        printf("git-checkout-cache -f -u -a\n");
 100}
 101
 102static void commit(void)
 103{
 104        const char *cmit_parent = initial_commit ? "" : "-p HEAD";
 105        const char *dst_branch;
 106        int i;
 107
 108        printf("tree=$(git-write-tree)\n");
 109        printf("cat > .cmitmsg <<EOFMSG\n");
 110
 111        /* Escape $ characters, and remove control characters */
 112        for (i = 0; i < loglen; i++) {
 113                unsigned char c = log[i];
 114
 115                switch (c) {
 116                case '$':
 117                case '\\':
 118                case '`':
 119                        putchar('\\');
 120                        break;
 121                case 0 ... 31:
 122                        if (c == '\n' || c == '\t')
 123                                break;
 124                case 128 ... 159:
 125                        continue;
 126                }
 127                putchar(c);
 128        }
 129        printf("\nEOFMSG\n");
 130        printf("commit=$(cat .cmitmsg | git-commit-tree $tree %s)\n", cmit_parent);
 131
 132        dst_branch = branch;
 133        if (!strcmp(dst_branch, "HEAD"))
 134                dst_branch = "master";
 135
 136        printf("echo $commit > .git/refs/heads/'%s'\n", dst_branch);
 137
 138        printf("echo 'Committed (to %s):' ; cat .cmitmsg; echo\n", dst_branch);
 139
 140        *date = 0;
 141        *author = 0;
 142        *branch = 0;
 143        *ancestor = 0;
 144        *tag = 0;
 145        loglen = 0;
 146
 147        initial_commit = 0;
 148}
 149
 150static void get_rcs_name(char *rcspathname, char *name, char *dir)
 151{
 152        sprintf(rcspathname, "%s/%s,v", rcsdir, name);
 153        if (!access(rcspathname, R_OK))
 154                return;
 155
 156        sprintf(rcspathname, "%s/Attic/%s,v", rcsdir, name);
 157        if (!access(rcspathname, R_OK))
 158                return;
 159
 160        if (dir) {
 161                sprintf(rcspathname, "%s/%.*s/Attic/%s,v", rcsdir, (int)(dir - name), name, dir+1);
 162                if (!access(rcspathname, R_OK))
 163                        return;
 164        }
 165        fprintf(stderr, "Unable to find RCS file for %s\n", name);
 166        exit(1);
 167}
 168
 169static void update_file(char *line)
 170{
 171        static char rcspathname[4096];
 172        char *name, *version;
 173        char *dir;
 174
 175        while (isspace(*line))
 176                line++;
 177        name = line;
 178        line = strchr(line, ':');
 179        if (!line)
 180                return;
 181        *line++ = 0;
 182        line = strchr(line, '>');
 183        if (!line)
 184                return;
 185        *line++ = 0;
 186        version = line;
 187        line = strchr(line, '(');
 188        if (line) {     /* "(DEAD)" */
 189                printf("git-update-cache --force-remove '%s'\n", name);
 190                return;
 191        }
 192
 193        dir = strrchr(name, '/');
 194        if (dir)
 195                printf("mkdir -p %.*s\n", (int)(dir - name), name);
 196
 197        get_rcs_name(rcspathname, name, dir);
 198                
 199        printf("co -q -p -r%s '%s' > '%s'\n", version, rcspathname, name);
 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
 221        rcsdir = getenv("RCSDIR");
 222        if (!rcsdir) {
 223                fprintf(stderr, "I need an $RCSDIR\n");
 224                exit(1);
 225        }
 226
 227        printf("[ -d .git ] && exit 1\n");
 228        printf("git-init-db\n");
 229        printf("mkdir -p .git/refs/heads\n");
 230        printf("mkdir -p .git/refs/tags\n");
 231        printf("ln -sf refs/heads/master .git/HEAD\n");
 232
 233        while (fgets(line, sizeof(line), stdin) != NULL) {
 234                int linelen = strlen(line);
 235
 236                while (linelen && isspace(line[linelen-1]))
 237                        line[--linelen] = 0;
 238
 239                switch (state) {
 240                struct hdrentry *entry;
 241
 242                case Header:
 243                        if (verbose)
 244                                printf("# H: %s\n", line);
 245                        for (entry = hdrs ; entry->name ; entry++) {
 246                                int len = strlen(entry->name);
 247                                char *val;
 248
 249                                if (memcmp(entry->name, line, len))
 250                                        continue;
 251                                if (!entry->dest) {
 252                                        state = Log;
 253                                        break;
 254                                }
 255                                val = line + len;
 256                                linelen -= len;
 257                                while (isspace(*val)) {
 258                                        val++;
 259                                        linelen--;
 260                                }
 261                                memcpy(entry->dest, val, linelen+1);
 262                                break;
 263                        }
 264                        continue;
 265
 266                case Log:
 267                        if (verbose)
 268                                printf("# L: %s\n", line);
 269                        if (!strcmp(line, "Members:")) {
 270                                while (loglen && isspace(log[loglen-1]))
 271                                        log[--loglen] = 0;
 272                                prepare_commit();
 273                                state = Members;
 274                                continue;
 275                        }
 276                                
 277                        if (loglen + linelen + 5 > sizeof(log))
 278                                continue;
 279                        memcpy(log + loglen, line, linelen);
 280                        loglen += linelen;
 281                        log[loglen++] = '\n';
 282                        continue;
 283
 284                case Members:
 285                        if (verbose)
 286                                printf("# M: %s\n", line);
 287                        if (!linelen) {
 288                                commit();
 289                                state = Header;
 290                                continue;
 291                        }
 292                        update_file(line);
 293                        continue;
 294                }
 295        }
 296        return 0;
 297}