7c23b9b506bc50521a053d20e8960f857f1ae31a
   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#include <curl/curl.h>
  14
  15#define BLOCKING (1ul << 14)
  16
  17/*
  18 * FIXME! Share the code with "write-tree.c"
  19 */
  20static void init_buffer(char **bufp, unsigned int *sizep)
  21{
  22        char *buf = xmalloc(BLOCKING);
  23        *sizep = 0;
  24        *bufp = buf;
  25}
  26
  27static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
  28{
  29        char one_line[2048];
  30        va_list args;
  31        int len;
  32        unsigned long alloc, size, newsize;
  33        char *buf;
  34
  35        va_start(args, fmt);
  36        len = vsnprintf(one_line, sizeof(one_line), fmt, args);
  37        va_end(args);
  38        size = *sizep;
  39        newsize = size + len;
  40        alloc = (size + 32767) & ~32767;
  41        buf = *bufp;
  42        if (newsize > alloc) {
  43                alloc = (newsize + 32767) & ~32767;
  44                buf = xrealloc(buf, alloc);
  45                *bufp = buf;
  46        }
  47        *sizep = newsize;
  48        memcpy(buf + size, one_line, len);
  49}
  50
  51static void remove_special(char *p)
  52{
  53        char c;
  54        char *dst = p, *src = p;
  55
  56        for (;;) {
  57                c = *src;
  58                src++;
  59                switch(c) {
  60                case '\n': case '<': case '>':
  61                        continue;
  62                }
  63                *dst++ = c;
  64                if (!c)
  65                        break;
  66        }
  67
  68        /*
  69         * Go back, and remove crud from the end: some people
  70         * have commas etc in their gecos field
  71         */
  72        dst--;
  73        while (--dst >= p) {
  74                unsigned char c = *dst;
  75                switch (c) {
  76                case ',': case ';': case '.':
  77                        *dst = 0;
  78                        continue;
  79                }
  80                break;
  81        }
  82}
  83
  84/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
  85   (i.e. English) day/month names, and it doesn't work correctly with %z. */
  86static void parse_date(char *date, time_t *now, char *result, int maxlen)
  87{
  88        char *p;
  89        time_t then;
  90
  91        if ((then = curl_getdate(date, now)) == 0)
  92                return;
  93
  94        /* find the timezone at the end */
  95        p = date + strlen(date);
  96        while (p > date && isdigit(*--p))
  97                ;
  98        if ((*p == '+' || *p == '-') && strlen(p) == 5)
  99                snprintf(result, maxlen, "%lu %5.5s", then, p);
 100}
 101
 102static void check_valid(unsigned char *sha1, const char *expect)
 103{
 104        void *buf;
 105        char type[20];
 106        unsigned long size;
 107
 108        buf = read_sha1_file(sha1, type, &size);
 109        if (!buf || strcmp(type, expect))
 110                die("%s is not a valid '%s' object", sha1_to_hex(sha1), expect);
 111        free(buf);
 112}
 113
 114/*
 115 * Having more than two parents is not strange at all, and this is
 116 * how multi-way merges are represented.
 117 */
 118#define MAXPARENT (16)
 119
 120static char *commit_tree_usage = "commit-tree <sha1> [-p <sha1>]* < changelog";
 121
 122int main(int argc, char **argv)
 123{
 124        int i, len;
 125        int parents = 0;
 126        unsigned char tree_sha1[20];
 127        unsigned char parent_sha1[MAXPARENT][20];
 128        unsigned char commit_sha1[20];
 129        char *gecos, *realgecos, *commitgecos;
 130        char *email, *commitemail, realemail[1000];
 131        char date[20], realdate[20];
 132        char *audate;
 133        char comment[1000];
 134        struct passwd *pw;
 135        time_t now;
 136        struct tm *tm;
 137        char *buffer;
 138        unsigned int size;
 139
 140        if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
 141                usage(commit_tree_usage);
 142
 143        check_valid(tree_sha1, "tree");
 144        for (i = 2; i < argc; i += 2) {
 145                char *a, *b;
 146                a = argv[i]; b = argv[i+1];
 147                if (!b || strcmp(a, "-p") || get_sha1_hex(b, parent_sha1[parents]))
 148                        usage(commit_tree_usage);
 149                check_valid(parent_sha1[parents], "commit");
 150                parents++;
 151        }
 152        if (!parents)
 153                fprintf(stderr, "Committing initial tree %s\n", argv[1]);
 154        pw = getpwuid(getuid());
 155        if (!pw)
 156                die("You don't exist. Go away!");
 157        realgecos = pw->pw_gecos;
 158        len = strlen(pw->pw_name);
 159        memcpy(realemail, pw->pw_name, len);
 160        realemail[len] = '@';
 161        gethostname(realemail+len+1, sizeof(realemail)-len-1);
 162        if (!strchr(realemail+len+1, '.')) {
 163                strcat(realemail, ".");
 164                getdomainname(realemail+strlen(realemail), sizeof(realemail)-strlen(realemail)-1);
 165        }
 166        time(&now);
 167        tm = localtime(&now);
 168
 169        strftime(realdate, sizeof(realdate), "%s %z", tm);
 170        strcpy(date, realdate);
 171
 172        commitgecos = getenv("COMMIT_AUTHOR_NAME") ? : realgecos;
 173        commitemail = getenv("COMMIT_AUTHOR_EMAIL") ? : realemail;
 174        gecos = getenv("AUTHOR_NAME") ? : realgecos;
 175        email = getenv("AUTHOR_EMAIL") ? : realemail;
 176        audate = getenv("AUTHOR_DATE");
 177        if (audate)
 178                parse_date(audate, &now, date, sizeof(date));
 179
 180        remove_special(gecos); remove_special(realgecos); remove_special(commitgecos);
 181        remove_special(email); remove_special(realemail); remove_special(commitemail);
 182
 183        init_buffer(&buffer, &size);
 184        add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));
 185
 186        /*
 187         * NOTE! This ordering means that the same exact tree merged with a
 188         * different order of parents will be a _different_ changeset even
 189         * if everything else stays the same.
 190         */
 191        for (i = 0; i < parents; i++)
 192                add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i]));
 193
 194        /* Person/date information */
 195        add_buffer(&buffer, &size, "author %s <%s> %s\n", gecos, email, date);
 196        add_buffer(&buffer, &size, "committer %s <%s> %s\n\n", commitgecos, commitemail, realdate);
 197
 198        /* And add the comment */
 199        while (fgets(comment, sizeof(comment), stdin) != NULL)
 200                add_buffer(&buffer, &size, "%s", comment);
 201
 202        write_sha1_file(buffer, size, "commit", commit_sha1);
 203        printf("%s\n", sha1_to_hex(commit_sha1));
 204        return 0;
 205}