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