commit-tree.con commit [PATCH] Add some functions for commit lists (dd97f85)
   1/*
   2 * GIT - The information manager from hell
   3 *
   4 * Copyright (C) Linus Torvalds, 2005
   5 */
   6#include "cache.h"
   7
   8#include <pwd.h>
   9#include <time.h>
  10#include <string.h>
  11#include <ctype.h>
  12#include <time.h>
  13
  14#define BLOCKING (1ul << 14)
  15#define ORIG_OFFSET (40)
  16
  17/*
  18 * Leave space at the beginning to insert the tag
  19 * once we know how big things are.
  20 *
  21 * FIXME! Share the code with "write-tree.c"
  22 */
  23static void init_buffer(char **bufp, unsigned int *sizep)
  24{
  25        char *buf = malloc(BLOCKING);
  26        memset(buf, 0, ORIG_OFFSET);
  27        *sizep = ORIG_OFFSET;
  28        *bufp = buf;
  29}
  30
  31static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
  32{
  33        char one_line[2048];
  34        va_list args;
  35        int len;
  36        unsigned long alloc, size, newsize;
  37        char *buf;
  38
  39        va_start(args, fmt);
  40        len = vsnprintf(one_line, sizeof(one_line), fmt, args);
  41        va_end(args);
  42        size = *sizep;
  43        newsize = size + len;
  44        alloc = (size + 32767) & ~32767;
  45        buf = *bufp;
  46        if (newsize > alloc) {
  47                alloc = (newsize + 32767) & ~32767;
  48                buf = realloc(buf, alloc);
  49                *bufp = buf;
  50        }
  51        *sizep = newsize;
  52        memcpy(buf + size, one_line, len);
  53}
  54
  55static int prepend_integer(char *buffer, unsigned val, int i)
  56{
  57        buffer[--i] = '\0';
  58        do {
  59                buffer[--i] = '0' + (val % 10);
  60                val /= 10;
  61        } while (val);
  62        return i;
  63}
  64
  65static void finish_buffer(char *tag, char **bufp, unsigned int *sizep)
  66{
  67        int taglen;
  68        int offset;
  69        char *buf = *bufp;
  70        unsigned int size = *sizep;
  71
  72        offset = prepend_integer(buf, size - ORIG_OFFSET, ORIG_OFFSET);
  73        taglen = strlen(tag);
  74        offset -= taglen;
  75        buf += offset;
  76        size -= offset;
  77        memcpy(buf, tag, taglen);
  78
  79        *bufp = buf;
  80        *sizep = size;
  81}
  82
  83static void remove_special(char *p)
  84{
  85        char c;
  86        char *dst = p, *src = p;
  87
  88        for (;;) {
  89                c = *src;
  90                src++;
  91                switch(c) {
  92                case '\n': case '<': case '>':
  93                        continue;
  94                }
  95                *dst++ = c;
  96                if (!c)
  97                        break;
  98        }
  99
 100        /*
 101         * Go back, and remove crud from the end: some people
 102         * have commas etc in their gecos field
 103         */
 104        dst--;
 105        while (--dst >= p) {
 106                unsigned char c = *dst;
 107                switch (c) {
 108                case ',': case ';': case '.':
 109                        *dst = 0;
 110                        continue;
 111                }
 112                break;
 113        }
 114}
 115
 116static const char *month_names[] = {
 117        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
 118        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
 119};
 120
 121static const char *weekday_names[] = {
 122        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
 123};
 124
 125
 126static char *skipfws(char *str)
 127{
 128        while (isspace(*str))
 129                str++;
 130        return str;
 131}
 132
 133        
 134/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
 135   (i.e. English) day/month names, and it doesn't work correctly with %z. */
 136static void parse_rfc2822_date(char *date, char *result, int maxlen)
 137{
 138        struct tm tm;
 139        char *p;
 140        int i, offset;
 141        time_t then;
 142
 143        memset(&tm, 0, sizeof(tm));
 144
 145        /* Skip day-name */
 146        p = skipfws(date);
 147        if (!isdigit(*p)) {
 148                for (i=0; i<7; i++) {
 149                        if (!strncmp(p,weekday_names[i],3) && p[3] == ',') {
 150                                p = skipfws(p+4);
 151                                goto day;
 152                        }
 153                }
 154                return;
 155        }                                       
 156
 157        /* day */
 158 day:
 159        tm.tm_mday = strtoul(p, &p, 10);
 160
 161        if (tm.tm_mday < 1 || tm.tm_mday > 31)
 162                return;
 163
 164        if (!isspace(*p))
 165                return;
 166
 167        p = skipfws(p);
 168
 169        /* month */
 170
 171        for (i=0; i<12; i++) {
 172                if (!strncmp(p, month_names[i], 3) && isspace(p[3])) {
 173                        tm.tm_mon = i;
 174                        p = skipfws(p+strlen(month_names[i]));
 175                        goto year;
 176                }
 177        }
 178        return; /* Error -- bad month */
 179
 180        /* year */
 181 year:  
 182        tm.tm_year = strtoul(p, &p, 10);
 183
 184        if (!tm.tm_year && !isspace(*p))
 185                return;
 186
 187        if (tm.tm_year > 1900)
 188                tm.tm_year -= 1900;
 189                
 190        p=skipfws(p);
 191
 192        /* hour */
 193        if (!isdigit(*p))
 194                return;
 195        tm.tm_hour = strtoul(p, &p, 10);
 196        
 197        if (!tm.tm_hour > 23)
 198                return;
 199
 200        if (*p != ':')
 201                return; /* Error -- bad time */
 202        p++;
 203
 204        /* minute */
 205        if (!isdigit(*p))
 206                return;
 207        tm.tm_min = strtoul(p, &p, 10);
 208        
 209        if (!tm.tm_min > 59)
 210                return;
 211
 212        if (isspace(*p))
 213                goto zone;
 214
 215        if (*p != ':')
 216                return; /* Error -- bad time */
 217        p++;
 218
 219        /* second */
 220        if (!isdigit(*p))
 221                return;
 222        tm.tm_sec = strtoul(p, &p, 10);
 223        
 224        if (!tm.tm_sec > 59)
 225                return;
 226
 227        if (!isspace(*p))
 228                return;
 229
 230 zone:
 231        p = skipfws(p);
 232
 233        if (*p == '-')
 234                offset = -60;
 235        else if (*p == '+')
 236                offset = 60;
 237        else
 238               return;
 239
 240        if (!isdigit(p[1]) || !isdigit(p[2]) || !isdigit(p[3]) || !isdigit(p[4]))
 241                return;
 242
 243        i = strtoul(p+1, NULL, 10);
 244        offset *= ((i % 100) + ((i / 100) * 60));
 245
 246        if (*(skipfws(p + 5)))
 247                return;
 248
 249        then = mktime(&tm); /* mktime appears to ignore the GMT offset, stupidly */
 250        if (then == -1)
 251                return;
 252
 253        then -= offset;
 254
 255        snprintf(result, maxlen, "%lu %5.5s", then, p);
 256}
 257
 258static void check_valid(unsigned char *sha1, const char *expect)
 259{
 260        void *buf;
 261        char type[20];
 262        unsigned long size;
 263
 264        buf = read_sha1_file(sha1, type, &size);
 265        if (!buf || strcmp(type, expect))
 266                die("%s is not a valid '%s' object", sha1_to_hex(sha1), expect);
 267        free(buf);
 268}
 269
 270/*
 271 * Having more than two parents is not strange at all, and this is
 272 * how multi-way merges are represented.
 273 */
 274#define MAXPARENT (16)
 275
 276static char *commit_tree_usage = "commit-tree <sha1> [-p <sha1>]* < changelog";
 277
 278int main(int argc, char **argv)
 279{
 280        int i, len;
 281        int parents = 0;
 282        unsigned char tree_sha1[20];
 283        unsigned char parent_sha1[MAXPARENT][20];
 284        unsigned char commit_sha1[20];
 285        char *gecos, *realgecos, *commitgecos;
 286        char *email, *commitemail, realemail[1000];
 287        char date[20], realdate[20];
 288        char *audate;
 289        char comment[1000];
 290        struct passwd *pw;
 291        time_t now;
 292        struct tm *tm;
 293        char *buffer;
 294        unsigned int size;
 295
 296        if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
 297                usage(commit_tree_usage);
 298
 299        check_valid(tree_sha1, "tree");
 300        for (i = 2; i < argc; i += 2) {
 301                char *a, *b;
 302                a = argv[i]; b = argv[i+1];
 303                if (!b || strcmp(a, "-p") || get_sha1_hex(b, parent_sha1[parents]))
 304                        usage(commit_tree_usage);
 305                check_valid(parent_sha1[parents], "commit");
 306                parents++;
 307        }
 308        if (!parents)
 309                fprintf(stderr, "Committing initial tree %s\n", argv[1]);
 310        pw = getpwuid(getuid());
 311        if (!pw)
 312                die("You don't exist. Go away!");
 313        realgecos = pw->pw_gecos;
 314        len = strlen(pw->pw_name);
 315        memcpy(realemail, pw->pw_name, len);
 316        realemail[len] = '@';
 317        gethostname(realemail+len+1, sizeof(realemail)-len-1);
 318        if (!strchr(realemail+len+1, '.')) {
 319                strcat(realemail, ".");
 320                getdomainname(realemail+strlen(realemail), sizeof(realemail)-strlen(realemail)-1);
 321        }
 322        time(&now);
 323        tm = localtime(&now);
 324
 325        strftime(realdate, sizeof(realdate), "%s %z", tm);
 326        strcpy(date, realdate);
 327
 328        commitgecos = getenv("COMMIT_AUTHOR_NAME") ? : realgecos;
 329        commitemail = getenv("COMMIT_AUTHOR_EMAIL") ? : realemail;
 330        gecos = getenv("AUTHOR_NAME") ? : realgecos;
 331        email = getenv("AUTHOR_EMAIL") ? : realemail;
 332        audate = getenv("AUTHOR_DATE");
 333        if (audate)
 334                parse_rfc2822_date(audate, date, sizeof(date));
 335
 336        remove_special(gecos); remove_special(realgecos); remove_special(commitgecos);
 337        remove_special(email); remove_special(realemail); remove_special(commitemail);
 338
 339        init_buffer(&buffer, &size);
 340        add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));
 341
 342        /*
 343         * NOTE! This ordering means that the same exact tree merged with a
 344         * different order of parents will be a _different_ changeset even
 345         * if everything else stays the same.
 346         */
 347        for (i = 0; i < parents; i++)
 348                add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i]));
 349
 350        /* Person/date information */
 351        add_buffer(&buffer, &size, "author %s <%s> %s\n", gecos, email, date);
 352        add_buffer(&buffer, &size, "committer %s <%s> %s\n\n", commitgecos, commitemail, realdate);
 353
 354        /* And add the comment */
 355        while (fgets(comment, sizeof(comment), stdin) != NULL)
 356                add_buffer(&buffer, &size, "%s", comment);
 357
 358        finish_buffer("commit ", &buffer, &size);
 359
 360        write_sha1_file(buffer, size, commit_sha1);
 361        printf("%s\n", sha1_to_hex(commit_sha1));
 362        return 0;
 363}