Clean-up output from "git show-branch" and document it.
[git.git] / convert-cache.c
1 #define _XOPEN_SOURCE /* glibc2 needs this */
2 #include <time.h>
3 #include <ctype.h>
4 #include "cache.h"
5
6 struct entry {
7         unsigned char old_sha1[20];
8         unsigned char new_sha1[20];
9         int converted;
10 };
11
12 #define MAXOBJECTS (1000000)
13
14 static struct entry *convert[MAXOBJECTS];
15 static int nr_convert;
16
17 static struct entry * convert_entry(unsigned char *sha1);
18
19 static struct entry *insert_new(unsigned char *sha1, int pos)
20 {
21         struct entry *new = xmalloc(sizeof(struct entry));
22         memset(new, 0, sizeof(*new));
23         memcpy(new->old_sha1, sha1, 20);
24         memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *));
25         convert[pos] = new;
26         nr_convert++;
27         if (nr_convert == MAXOBJECTS)
28                 die("you're kidding me - hit maximum object limit");
29         return new;
30 }
31
32 static struct entry *lookup_entry(unsigned char *sha1)
33 {
34         int low = 0, high = nr_convert;
35
36         while (low < high) {
37                 int next = (low + high) / 2;
38                 struct entry *n = convert[next];
39                 int cmp = memcmp(sha1, n->old_sha1, 20);
40                 if (!cmp)
41                         return n;
42                 if (cmp < 0) {
43                         high = next;
44                         continue;
45                 }
46                 low = next+1;
47         }
48         return insert_new(sha1, low);
49 }
50
51 static void convert_binary_sha1(void *buffer)
52 {
53         struct entry *entry = convert_entry(buffer);
54         memcpy(buffer, entry->new_sha1, 20);
55 }
56
57 static void convert_ascii_sha1(void *buffer)
58 {
59         unsigned char sha1[20];
60         struct entry *entry;
61
62         if (get_sha1_hex(buffer, sha1))
63                 die("expected sha1, got '%s'", (char*) buffer);
64         entry = convert_entry(sha1);
65         memcpy(buffer, sha1_to_hex(entry->new_sha1), 40);
66 }
67
68 static unsigned int convert_mode(unsigned int mode)
69 {
70         unsigned int newmode;
71
72         newmode = mode & S_IFMT;
73         if (S_ISREG(mode))
74                 newmode |= (mode & 0100) ? 0755 : 0644;
75         return newmode;
76 }
77
78 static int write_subdirectory(void *buffer, unsigned long size, const char *base, int baselen, unsigned char *result_sha1)
79 {
80         char *new = xmalloc(size);
81         unsigned long newlen = 0;
82         unsigned long used;
83
84         used = 0;
85         while (size) {
86                 int len = 21 + strlen(buffer);
87                 char *path = strchr(buffer, ' ');
88                 unsigned char *sha1;
89                 unsigned int mode;
90                 char *slash, *origpath;
91
92                 if (!path || sscanf(buffer, "%o", &mode) != 1)
93                         die("bad tree conversion");
94                 mode = convert_mode(mode);
95                 path++;
96                 if (memcmp(path, base, baselen))
97                         break;
98                 origpath = path;
99                 path += baselen;
100                 slash = strchr(path, '/');
101                 if (!slash) {
102                         newlen += sprintf(new + newlen, "%o %s", mode, path);
103                         new[newlen++] = '\0';
104                         memcpy(new + newlen, buffer + len - 20, 20);
105                         newlen += 20;
106
107                         used += len;
108                         size -= len;
109                         buffer += len;
110                         continue;
111                 }
112
113                 newlen += sprintf(new + newlen, "%o %.*s", S_IFDIR, (int)(slash - path), path);
114                 new[newlen++] = 0;
115                 sha1 = (unsigned char *)(new + newlen);
116                 newlen += 20;
117
118                 len = write_subdirectory(buffer, size, origpath, slash-origpath+1, sha1);
119
120                 used += len;
121                 size -= len;
122                 buffer += len;
123         }
124
125         write_sha1_file(new, newlen, "tree", result_sha1);
126         free(new);
127         return used;
128 }
129
130 static void convert_tree(void *buffer, unsigned long size, unsigned char *result_sha1)
131 {
132         void *orig_buffer = buffer;
133         unsigned long orig_size = size;
134
135         while (size) {
136                 int len = 1+strlen(buffer);
137
138                 convert_binary_sha1(buffer + len);
139
140                 len += 20;
141                 if (len > size)
142                         die("corrupt tree object");
143                 size -= len;
144                 buffer += len;
145         }
146
147         write_subdirectory(orig_buffer, orig_size, "", 0, result_sha1);
148 }
149
150 static unsigned long parse_oldstyle_date(const char *buf)
151 {
152         char c, *p;
153         char buffer[100];
154         struct tm tm;
155         const char *formats[] = {
156                 "%c",
157                 "%a %b %d %T",
158                 "%Z",
159                 "%Y",
160                 " %Y",
161                 NULL
162         };
163         /* We only ever did two timezones in the bad old format .. */
164         const char *timezones[] = {
165                 "PDT", "PST", "CEST", NULL
166         };
167         const char **fmt = formats;
168
169         p = buffer;
170         while (isspace(c = *buf))
171                 buf++;
172         while ((c = *buf++) != '\n')
173                 *p++ = c;
174         *p++ = 0;
175         buf = buffer;
176         memset(&tm, 0, sizeof(tm));
177         do {
178                 const char *next = strptime(buf, *fmt, &tm);
179                 if (next) {
180                         if (!*next)
181                                 return mktime(&tm);
182                         buf = next;
183                 } else {
184                         const char **p = timezones;
185                         while (isspace(*buf))
186                                 buf++;
187                         while (*p) {
188                                 if (!memcmp(buf, *p, strlen(*p))) {
189                                         buf += strlen(*p);
190                                         break;
191                                 }
192                                 p++;
193                         }
194                 }
195                 fmt++;
196         } while (*buf && *fmt);
197         printf("left: %s\n", buf);
198         return mktime(&tm);                             
199 }
200
201 static int convert_date_line(char *dst, void **buf, unsigned long *sp)
202 {
203         unsigned long size = *sp;
204         char *line = *buf;
205         char *next = strchr(line, '\n');
206         char *date = strchr(line, '>');
207         int len;
208
209         if (!next || !date)
210                 die("missing or bad author/committer line %s", line);
211         next++; date += 2;
212
213         *buf = next;
214         *sp = size - (next - line);
215
216         len = date - line;
217         memcpy(dst, line, len);
218         dst += len;
219
220         /* Is it already in new format? */
221         if (isdigit(*date)) {
222                 int datelen = next - date;
223                 memcpy(dst, date, datelen);
224                 return len + datelen;
225         }
226
227         /*
228          * Hacky hacky: one of the sparse old-style commits does not have
229          * any date at all, but we can fake it by using the committer date.
230          */
231         if (*date == '\n' && strchr(next, '>'))
232                 date = strchr(next, '>')+2;
233
234         return len + sprintf(dst, "%lu -0700\n", parse_oldstyle_date(date));
235 }
236
237 static void convert_date(void *buffer, unsigned long size, unsigned char *result_sha1)
238 {
239         char *new = xmalloc(size + 100);
240         unsigned long newlen = 0;
241         
242         // "tree <sha1>\n"
243         memcpy(new + newlen, buffer, 46);
244         newlen += 46;
245         buffer += 46;
246         size -= 46;
247
248         // "parent <sha1>\n"
249         while (!memcmp(buffer, "parent ", 7)) {
250                 memcpy(new + newlen, buffer, 48);
251                 newlen += 48;
252                 buffer += 48;
253                 size -= 48;
254         }
255
256         // "author xyz <xyz> date"
257         newlen += convert_date_line(new + newlen, &buffer, &size);
258         // "committer xyz <xyz> date"
259         newlen += convert_date_line(new + newlen, &buffer, &size);
260
261         // Rest
262         memcpy(new + newlen, buffer, size);
263         newlen += size;
264
265         write_sha1_file(new, newlen, "commit", result_sha1);
266         free(new);      
267 }
268
269 static void convert_commit(void *buffer, unsigned long size, unsigned char *result_sha1)
270 {
271         void *orig_buffer = buffer;
272         unsigned long orig_size = size;
273
274         if (memcmp(buffer, "tree ", 5))
275                 die("Bad commit '%s'", (char*) buffer);
276         convert_ascii_sha1(buffer+5);
277         buffer += 46;    /* "tree " + "hex sha1" + "\n" */
278         while (!memcmp(buffer, "parent ", 7)) {
279                 convert_ascii_sha1(buffer+7);
280                 buffer += 48;
281         }
282         convert_date(orig_buffer, orig_size, result_sha1);
283 }
284
285 static struct entry * convert_entry(unsigned char *sha1)
286 {
287         struct entry *entry = lookup_entry(sha1);
288         char type[20];
289         void *buffer, *data;
290         unsigned long size;
291
292         if (entry->converted)
293                 return entry;
294         data = read_sha1_file(sha1, type, &size);
295         if (!data)
296                 die("unable to read object %s", sha1_to_hex(sha1));
297
298         buffer = xmalloc(size);
299         memcpy(buffer, data, size);
300         
301         if (!strcmp(type, "blob")) {
302                 write_sha1_file(buffer, size, "blob", entry->new_sha1);
303         } else if (!strcmp(type, "tree"))
304                 convert_tree(buffer, size, entry->new_sha1);
305         else if (!strcmp(type, "commit"))
306                 convert_commit(buffer, size, entry->new_sha1);
307         else
308                 die("unknown object type '%s' in %s", type, sha1_to_hex(sha1));
309         entry->converted = 1;
310         free(buffer);
311         free(data);
312         return entry;
313 }
314
315 int main(int argc, char **argv)
316 {
317         unsigned char sha1[20];
318         struct entry *entry;
319
320         if (argc != 2 || get_sha1(argv[1], sha1))
321                 usage("git-convert-cache <sha1>");
322
323         entry = convert_entry(sha1);
324         printf("new sha1: %s\n", sha1_to_hex(entry->new_sha1));
325         return 0;
326 }