commit-tree.con commit Make "commit-tree" check the input objects more carefully. (d0d7cbe)
   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;
  87
  88        for (;;) {
  89                c = *p;
  90                p++;
  91                switch(c) {
  92                case '\n': case '<': case '>':
  93                        continue;
  94                }
  95                *dst++ = c;
  96                if (!c)
  97                        break;
  98        }
  99}
 100
 101static const char *month_names[] = {
 102        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
 103        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
 104};
 105
 106static const char *weekday_names[] = {
 107        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
 108};
 109
 110
 111static char *skipfws(char *str)
 112{
 113        while (isspace(*str))
 114                str++;
 115        return str;
 116}
 117
 118        
 119/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
 120   (i.e. English) day/month names, and it doesn't work correctly with %z. */
 121static void parse_rfc2822_date(char *date, char *result, int maxlen)
 122{
 123        struct tm tm;
 124        char *p;
 125        int i, offset;
 126        time_t then;
 127
 128        memset(&tm, 0, sizeof(tm));
 129
 130        /* Skip day-name */
 131        p = skipfws(date);
 132        if (!isdigit(*p)) {
 133                for (i=0; i<7; i++) {
 134                        if (!strncmp(p,weekday_names[i],3) && p[3] == ',') {
 135                                p = skipfws(p+4);
 136                                goto day;
 137                        }
 138                }
 139                return;
 140        }                                       
 141
 142        /* day */
 143 day:
 144        tm.tm_mday = strtoul(p, &p, 10);
 145
 146        if (tm.tm_mday < 1 || tm.tm_mday > 31)
 147                return;
 148
 149        if (!isspace(*p))
 150                return;
 151
 152        p = skipfws(p);
 153
 154        /* month */
 155
 156        for (i=0; i<12; i++) {
 157                if (!strncmp(p, month_names[i], 3) && isspace(p[3])) {
 158                        tm.tm_mon = i;
 159                        p = skipfws(p+strlen(month_names[i]));
 160                        goto year;
 161                }
 162        }
 163        return; /* Error -- bad month */
 164
 165        /* year */
 166 year:  
 167        tm.tm_year = strtoul(p, &p, 10);
 168
 169        if (!tm.tm_year && !isspace(*p))
 170                return;
 171
 172        if (tm.tm_year > 1900)
 173                tm.tm_year -= 1900;
 174                
 175        p=skipfws(p);
 176
 177        /* hour */
 178        if (!isdigit(*p))
 179                return;
 180        tm.tm_hour = strtoul(p, &p, 10);
 181        
 182        if (!tm.tm_hour > 23)
 183                return;
 184
 185        if (*p != ':')
 186                return; /* Error -- bad time */
 187        p++;
 188
 189        /* minute */
 190        if (!isdigit(*p))
 191                return;
 192        tm.tm_min = strtoul(p, &p, 10);
 193        
 194        if (!tm.tm_min > 59)
 195                return;
 196
 197        if (isspace(*p))
 198                goto zone;
 199
 200        if (*p != ':')
 201                return; /* Error -- bad time */
 202        p++;
 203
 204        /* second */
 205        if (!isdigit(*p))
 206                return;
 207        tm.tm_sec = strtoul(p, &p, 10);
 208        
 209        if (!tm.tm_sec > 59)
 210                return;
 211
 212        if (!isspace(*p))
 213                return;
 214
 215 zone:
 216        p = skipfws(p);
 217
 218        if (*p == '-')
 219                offset = -60;
 220        else if (*p == '+')
 221                offset = 60;
 222        else
 223               return;
 224
 225        if (!isdigit(p[1]) || !isdigit(p[2]) || !isdigit(p[3]) || !isdigit(p[4]))
 226                return;
 227
 228        i = strtoul(p+1, NULL, 10);
 229        offset *= ((i % 100) + ((i / 100) * 60));
 230
 231        if (*(skipfws(p + 5)))
 232                return;
 233
 234        then = mktime(&tm); /* mktime appears to ignore the GMT offset, stupidly */
 235        if (then == -1)
 236                return;
 237
 238        then -= offset;
 239
 240        snprintf(result, maxlen, "%lu %5.5s", then, p);
 241}
 242
 243static void check_valid(unsigned char *sha1, const char *expect)
 244{
 245        void *buf;
 246        char type[20];
 247        unsigned long size;
 248
 249        buf = read_sha1_file(sha1, type, &size);
 250        if (!buf || strcmp(type, expect))
 251                die("%s is not a valid '%s' object", sha1_to_hex(sha1), expect);
 252        free(buf);
 253}
 254
 255/*
 256 * Having more than two parents may be strange, but hey, there's
 257 * no conceptual reason why the file format couldn't accept multi-way
 258 * merges. It might be the "union" of several packages, for example.
 259 *
 260 * I don't really expect that to happen, but this is here to make
 261 * it clear that _conceptually_ it's ok..
 262 */
 263#define MAXPARENT (16)
 264
 265int main(int argc, char **argv)
 266{
 267        int i, len;
 268        int parents = 0;
 269        unsigned char tree_sha1[20];
 270        unsigned char parent_sha1[MAXPARENT][20];
 271        unsigned char commit_sha1[20];
 272        char *gecos, *realgecos;
 273        char *email, realemail[1000];
 274        char date[20], realdate[20];
 275        char *audate;
 276        char comment[1000];
 277        struct passwd *pw;
 278        time_t now;
 279        struct tm *tm;
 280        char *buffer;
 281        unsigned int size;
 282
 283        if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
 284                usage("commit-tree <sha1> [-p <sha1>]* < changelog");
 285
 286        check_valid(tree_sha1, "tree");
 287        for (i = 2; i < argc; i += 2) {
 288                char *a, *b;
 289                a = argv[i]; b = argv[i+1];
 290                if (!b || strcmp(a, "-p") || get_sha1_hex(b, parent_sha1[parents]))
 291                        usage("commit-tree <sha1> [-p <sha1>]* < changelog");
 292                check_valid(parent_sha1[parents], "commit");
 293                parents++;
 294        }
 295        if (!parents)
 296                fprintf(stderr, "Committing initial tree %s\n", argv[1]);
 297        pw = getpwuid(getuid());
 298        if (!pw)
 299                die("You don't exist. Go away!");
 300        realgecos = pw->pw_gecos;
 301        len = strlen(pw->pw_name);
 302        memcpy(realemail, pw->pw_name, len);
 303        realemail[len] = '@';
 304        gethostname(realemail+len+1, sizeof(realemail)-len-1);
 305        time(&now);
 306        tm = localtime(&now);
 307
 308        strftime(realdate, sizeof(realdate), "%s %z", tm);
 309        strcpy(date, realdate);
 310
 311        gecos = getenv("AUTHOR_NAME") ? : realgecos;
 312        email = getenv("AUTHOR_EMAIL") ? : realemail;
 313        audate = getenv("AUTHOR_DATE");
 314        if (audate)
 315                parse_rfc2822_date(audate, date, sizeof(date));
 316
 317        remove_special(gecos); remove_special(realgecos);
 318        remove_special(email); remove_special(realemail);
 319
 320        init_buffer(&buffer, &size);
 321        add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));
 322
 323        /*
 324         * NOTE! This ordering means that the same exact tree merged with a
 325         * different order of parents will be a _different_ changeset even
 326         * if everything else stays the same.
 327         */
 328        for (i = 0; i < parents; i++)
 329                add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i]));
 330
 331        /* Person/date information */
 332        add_buffer(&buffer, &size, "author %s <%s> %s\n", gecos, email, date);
 333        add_buffer(&buffer, &size, "committer %s <%s> %s\n\n", realgecos, realemail, realdate);
 334
 335        /* And add the comment */
 336        while (fgets(comment, sizeof(comment), stdin) != NULL)
 337                add_buffer(&buffer, &size, "%s", comment);
 338
 339        finish_buffer("commit ", &buffer, &size);
 340
 341        write_sha1_file(buffer, size, commit_sha1);
 342        printf("%s\n", sha1_to_hex(commit_sha1));
 343        return 0;
 344}