commit-tree.con commit Be much more liberal about the file mode bits. (e447947)
   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
 243/*
 244 * Having more than two parents may be strange, but hey, there's
 245 * no conceptual reason why the file format couldn't accept multi-way
 246 * merges. It might be the "union" of several packages, for example.
 247 *
 248 * I don't really expect that to happen, but this is here to make
 249 * it clear that _conceptually_ it's ok..
 250 */
 251#define MAXPARENT (16)
 252
 253int main(int argc, char **argv)
 254{
 255        int i, len;
 256        int parents = 0;
 257        unsigned char tree_sha1[20];
 258        unsigned char parent_sha1[MAXPARENT][20];
 259        unsigned char commit_sha1[20];
 260        char *gecos, *realgecos;
 261        char *email, realemail[1000];
 262        char date[20], realdate[20];
 263        char *audate;
 264        char comment[1000];
 265        struct passwd *pw;
 266        time_t now;
 267        struct tm *tm;
 268        char *buffer;
 269        unsigned int size;
 270
 271        if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
 272                usage("commit-tree <sha1> [-p <sha1>]* < changelog");
 273
 274        for (i = 2; i < argc; i += 2) {
 275                char *a, *b;
 276                a = argv[i]; b = argv[i+1];
 277                if (!b || strcmp(a, "-p") || get_sha1_hex(b, parent_sha1[parents]))
 278                        usage("commit-tree <sha1> [-p <sha1>]* < changelog");
 279                parents++;
 280        }
 281        if (!parents)
 282                fprintf(stderr, "Committing initial tree %s\n", argv[1]);
 283        pw = getpwuid(getuid());
 284        if (!pw)
 285                die("You don't exist. Go away!");
 286        realgecos = pw->pw_gecos;
 287        len = strlen(pw->pw_name);
 288        memcpy(realemail, pw->pw_name, len);
 289        realemail[len] = '@';
 290        gethostname(realemail+len+1, sizeof(realemail)-len-1);
 291        time(&now);
 292        tm = localtime(&now);
 293
 294        strftime(realdate, sizeof(realdate), "%s %z", tm);
 295        strcpy(date, realdate);
 296
 297        gecos = getenv("AUTHOR_NAME") ? : realgecos;
 298        email = getenv("AUTHOR_EMAIL") ? : realemail;
 299        audate = getenv("AUTHOR_DATE");
 300        if (audate)
 301                parse_rfc2822_date(audate, date, sizeof(date));
 302
 303        remove_special(gecos); remove_special(realgecos);
 304        remove_special(email); remove_special(realemail);
 305
 306        init_buffer(&buffer, &size);
 307        add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));
 308
 309        /*
 310         * NOTE! This ordering means that the same exact tree merged with a
 311         * different order of parents will be a _different_ changeset even
 312         * if everything else stays the same.
 313         */
 314        for (i = 0; i < parents; i++)
 315                add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i]));
 316
 317        /* Person/date information */
 318        add_buffer(&buffer, &size, "author %s <%s> %s\n", gecos, email, date);
 319        add_buffer(&buffer, &size, "committer %s <%s> %s\n\n", realgecos, realemail, realdate);
 320
 321        /* And add the comment */
 322        while (fgets(comment, sizeof(comment), stdin) != NULL)
 323                add_buffer(&buffer, &size, "%s", comment);
 324
 325        finish_buffer("commit ", &buffer, &size);
 326
 327        write_sha1_file(buffer, size, commit_sha1);
 328        printf("%s\n", sha1_to_hex(commit_sha1));
 329        return 0;
 330}