1/*2* GIT - The information manager from hell3*4* Copyright (C) Linus Torvalds, 20055*/6#include "cache.h"78#include <pwd.h>9#include <time.h>10#include <string.h>11#include <ctype.h>12#include <time.h>1314#define BLOCKING (1ul << 14)15#define ORIG_OFFSET (40)1617/*18* Leave space at the beginning to insert the tag19* 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{25char *buf = malloc(BLOCKING);26memset(buf, 0, ORIG_OFFSET);27*sizep = ORIG_OFFSET;28*bufp = buf;29}3031static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)32{33char one_line[2048];34va_list args;35int len;36unsigned long alloc, size, newsize;37char *buf;3839va_start(args, fmt);40len = vsnprintf(one_line, sizeof(one_line), fmt, args);41va_end(args);42size = *sizep;43newsize = size + len;44alloc = (size + 32767) & ~32767;45buf = *bufp;46if (newsize > alloc) {47alloc = (newsize + 32767) & ~32767;48buf = realloc(buf, alloc);49*bufp = buf;50}51*sizep = newsize;52memcpy(buf + size, one_line, len);53}5455static int prepend_integer(char *buffer, unsigned val, int i)56{57buffer[--i] = '\0';58do {59buffer[--i] = '0' + (val % 10);60val /= 10;61} while (val);62return i;63}6465static void finish_buffer(char *tag, char **bufp, unsigned int *sizep)66{67int taglen;68int offset;69char *buf = *bufp;70unsigned int size = *sizep;7172offset = prepend_integer(buf, size - ORIG_OFFSET, ORIG_OFFSET);73taglen = strlen(tag);74offset -= taglen;75buf += offset;76size -= offset;77memcpy(buf, tag, taglen);7879*bufp = buf;80*sizep = size;81}8283static void remove_special(char *p)84{85char c;86char *dst = p;8788for (;;) {89c = *p;90p++;91switch(c) {92case '\n': case '<': case '>':93continue;94}95*dst++ = c;96if (!c)97break;98}99}100101static const char *month_names[] = {102"Jan", "Feb", "Mar", "Apr", "May", "Jun",103"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"104};105106static const char *weekday_names[] = {107"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"108};109110111static char *skipfws(char *str)112{113while (isspace(*str))114str++;115return str;116}117118119/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822120(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{123struct tm tm;124char *p;125int i, offset;126time_t then;127128memset(&tm, 0, sizeof(tm));129130/* Skip day-name */131p = skipfws(date);132if (!isdigit(*p)) {133for (i=0; i<7; i++) {134if (!strncmp(p,weekday_names[i],3) && p[3] == ',') {135p = skipfws(p+4);136goto day;137}138}139return;140}141142/* day */143day:144tm.tm_mday = strtoul(p, &p, 10);145146if (tm.tm_mday < 1 || tm.tm_mday > 31)147return;148149if (!isspace(*p))150return;151152p = skipfws(p);153154/* month */155156for (i=0; i<12; i++) {157if (!strncmp(p, month_names[i], 3) && isspace(p[3])) {158tm.tm_mon = i;159p = skipfws(p+strlen(month_names[i]));160goto year;161}162}163return; /* Error -- bad month */164165/* year */166year:167tm.tm_year = strtoul(p, &p, 10);168169if (!tm.tm_year && !isspace(*p))170return;171172if (tm.tm_year > 1900)173tm.tm_year -= 1900;174175p=skipfws(p);176177/* hour */178if (!isdigit(*p))179return;180tm.tm_hour = strtoul(p, &p, 10);181182if (!tm.tm_hour > 23)183return;184185if (*p != ':')186return; /* Error -- bad time */187p++;188189/* minute */190if (!isdigit(*p))191return;192tm.tm_min = strtoul(p, &p, 10);193194if (!tm.tm_min > 59)195return;196197if (isspace(*p))198goto zone;199200if (*p != ':')201return; /* Error -- bad time */202p++;203204/* second */205if (!isdigit(*p))206return;207tm.tm_sec = strtoul(p, &p, 10);208209if (!tm.tm_sec > 59)210return;211212if (!isspace(*p))213return;214215zone:216p = skipfws(p);217218if (*p == '-')219offset = -60;220else if (*p == '+')221offset = 60;222else223return;224225if (!isdigit(p[1]) || !isdigit(p[2]) || !isdigit(p[3]) || !isdigit(p[4]))226return;227228i = strtoul(p+1, NULL, 10);229offset *= ((i % 100) + ((i / 100) * 60));230231if (*(skipfws(p + 5)))232return;233234then = mktime(&tm); /* mktime appears to ignore the GMT offset, stupidly */235if (then == -1)236return;237238then -= offset;239240snprintf(result, maxlen, "%lu %5.5s", then, p);241}242243/*244* Having more than two parents may be strange, but hey, there's245* no conceptual reason why the file format couldn't accept multi-way246* 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 make249* it clear that _conceptually_ it's ok..250*/251#define MAXPARENT (16)252253int main(int argc, char **argv)254{255int i, len;256int parents = 0;257unsigned char tree_sha1[20];258unsigned char parent_sha1[MAXPARENT][20];259unsigned char commit_sha1[20];260char *gecos, *realgecos;261char *email, realemail[1000];262char date[20], realdate[20];263char *audate;264char comment[1000];265struct passwd *pw;266time_t now;267struct tm *tm;268char *buffer;269unsigned int size;270271if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)272usage("commit-tree <sha1> [-p <sha1>]* < changelog");273274for (i = 2; i < argc; i += 2) {275char *a, *b;276a = argv[i]; b = argv[i+1];277if (!b || strcmp(a, "-p") || get_sha1_hex(b, parent_sha1[parents]))278usage("commit-tree <sha1> [-p <sha1>]* < changelog");279parents++;280}281if (!parents)282fprintf(stderr, "Committing initial tree %s\n", argv[1]);283pw = getpwuid(getuid());284if (!pw)285die("You don't exist. Go away!");286realgecos = pw->pw_gecos;287len = strlen(pw->pw_name);288memcpy(realemail, pw->pw_name, len);289realemail[len] = '@';290gethostname(realemail+len+1, sizeof(realemail)-len-1);291time(&now);292tm = localtime(&now);293294strftime(realdate, sizeof(realdate), "%s %z", tm);295strcpy(date, realdate);296297gecos = getenv("AUTHOR_NAME") ? : realgecos;298email = getenv("AUTHOR_EMAIL") ? : realemail;299audate = getenv("AUTHOR_DATE");300if (audate)301parse_rfc2822_date(audate, date, sizeof(date));302303remove_special(gecos); remove_special(realgecos);304remove_special(email); remove_special(realemail);305306init_buffer(&buffer, &size);307add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));308309/*310* NOTE! This ordering means that the same exact tree merged with a311* different order of parents will be a _different_ changeset even312* if everything else stays the same.313*/314for (i = 0; i < parents; i++)315add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i]));316317/* Person/date information */318add_buffer(&buffer, &size, "author %s <%s> %s\n", gecos, email, date);319add_buffer(&buffer, &size, "committer %s <%s> %s\n\n", realgecos, realemail, realdate);320321/* And add the comment */322while (fgets(comment, sizeof(comment), stdin) != NULL)323add_buffer(&buffer, &size, "%s", comment);324325finish_buffer("commit ", &buffer, &size);326327write_sha1_file(buffer, size, commit_sha1);328printf("%s\n", sha1_to_hex(commit_sha1));329return 0;330}