[PATCH] Simplify date handling and make it more reliable
[git/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 #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 }