Be much more liberal about the file mode bits.
[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 #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  */
23 static 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
31 static 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
55 static 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
65 static 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
83 static 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
101 static const char *month_names[] = {
102         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
103         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
104 };
105
106 static const char *weekday_names[] = {
107         "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
108 };
109
110
111 static 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. */
121 static 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
253 int 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 }