Make 'fsck' able to take an arbitrary number of parents on the
[git.git] / fsck-cache.c
1 #include "cache.h"
2
3 #include <sys/types.h>
4 #include <dirent.h>
5
6 /*
7  * The low 16 bits of the "flags" field shows whether
8  * a commit is part of the path to the root for that
9  * parent.
10  *
11  * Bit 16 is an internal flag that we've seen the
12  * definition for this rev, and not just seen it as
13  * a parent target.
14  */
15 #define MAX_COMMITS (16)
16 #define marked(rev)     ((rev)->flags & 0xffff)
17 #define SEEN 0x10000
18 #define USED 0x20000
19 #define REACHABLE 0x40000
20
21 static int show_unreachable = 0;
22 static unsigned char head_sha1[20];
23
24 struct parent {
25         struct revision *parent;
26         struct parent *next;
27 };
28
29 struct revision {
30         unsigned int flags;
31         unsigned char sha1[20];
32         unsigned long date;
33         struct parent *parent;
34 };
35
36 static struct revision **revs;
37 static int nr_revs, rev_allocs;
38
39 static int find_rev(unsigned char *sha1)
40 {
41         int first = 0, last = nr_revs;
42
43         while (first < last) {
44                 int next = (first + last) / 2;
45                 struct revision *rev = revs[next];
46                 int cmp;
47
48                 cmp = memcmp(sha1, rev->sha1, 20);
49                 if (!cmp)
50                         return next;
51                 if (cmp < 0) {
52                         last = next;
53                         continue;
54                 }
55                 first = next+1;
56         }
57         return -first-1;
58 }
59
60 static struct revision *lookup_rev(unsigned char *sha1)
61 {
62         int pos = find_rev(sha1);
63         struct revision *n;
64
65         if (pos >= 0)
66                 return revs[pos];
67         
68         pos = -pos-1;
69
70         if (rev_allocs == nr_revs) {
71                 rev_allocs = alloc_nr(rev_allocs);
72                 revs = realloc(revs, rev_allocs * sizeof(struct revision *));
73         }
74         n = malloc(sizeof(struct revision));
75
76         n->flags = 0;
77         memcpy(n->sha1, sha1, 20);
78         n->parent = NULL;
79
80         /* Insert it into the right place */
81         memmove(revs + pos + 1, revs + pos, (nr_revs - pos) * sizeof(struct revision *));
82         revs[pos] = n;
83         nr_revs++;
84
85         return n;
86 }
87
88 static struct revision *add_relationship(struct revision *rev, unsigned char *needs)
89 {
90         struct revision *parent_rev = lookup_rev(needs);
91         struct parent **pp = &rev->parent, *p;
92
93         while ((p = *pp) != NULL) {
94                 if (p->parent == parent_rev)
95                         return parent_rev;
96                 pp = &p->next;
97         }
98
99         p = malloc(sizeof(*p));
100         p->parent = parent_rev;
101         p->next = NULL;
102         *pp = p;
103         return parent_rev;
104 }
105
106 static void mark_reachable(struct revision *rev)
107 {
108         struct parent *p = rev->parent;
109
110         /* If we've been here already, don't bother */
111         if (rev->flags & REACHABLE)
112                 return;
113         rev->flags |= REACHABLE | USED;
114         while (p) {
115                 mark_reachable(p->parent);
116                 p = p->next;
117         }
118 }
119
120 static void check_connectivity(void)
121 {
122         int i;
123
124         /* Look up all the requirements, warn about missing objects.. */
125         for (i = 0; i < nr_revs; i++) {
126                 struct revision *rev = revs[i];
127
128                 if (show_unreachable && !(rev->flags & REACHABLE)) {
129                         printf("unreachable %s\n", sha1_to_hex(rev->sha1));
130                         continue;
131                 }
132
133                 switch (rev->flags & (SEEN | USED)) {
134                 case 0:
135                         printf("bad %s\n", sha1_to_hex(rev->sha1));
136                         break;
137                 case USED:
138                         printf("missing %s\n", sha1_to_hex(rev->sha1));
139                         break;
140                 case SEEN:
141                         printf("dangling %s\n", sha1_to_hex(rev->sha1));
142                         break;
143                 }
144         }
145 }
146
147 static void mark_needs_sha1(unsigned char *parent, const char * tag, unsigned char *child)
148 {
149         struct revision * child_rev = add_relationship(lookup_rev(parent), child);
150         child_rev->flags |= USED;
151 }
152
153 static int mark_sha1_seen(unsigned char *sha1, char *tag)
154 {
155         struct revision *rev = lookup_rev(sha1);
156
157         rev->flags |= SEEN;
158         return 0;
159 }
160
161 static int fsck_tree(unsigned char *sha1, void *data, unsigned long size)
162 {
163         int warn_old_tree = 1;
164
165         while (size) {
166                 int len = 1+strlen(data);
167                 unsigned char *file_sha1 = data + len;
168                 char *path = strchr(data, ' ');
169                 unsigned int mode;
170                 if (size < len + 20 || !path || sscanf(data, "%o", &mode) != 1)
171                         return -1;
172
173                 /* Warn about trees that don't do the recursive thing.. */
174                 if (warn_old_tree && strchr(path, '/')) {
175                         fprintf(stderr, "warning: fsck-cache: tree %s has full pathnames in it\n", sha1_to_hex(sha1));
176                         warn_old_tree = 0;
177                 }
178
179                 data += len + 20;
180                 size -= len + 20;
181                 mark_needs_sha1(sha1, S_ISDIR(mode) ? "tree" : "blob", file_sha1);
182         }
183         return 0;
184 }
185
186 static int fsck_commit(unsigned char *sha1, void *data, unsigned long size)
187 {
188         int parents;
189         unsigned char tree_sha1[20];
190         unsigned char parent_sha1[20];
191
192         if (memcmp(data, "tree ", 5))
193                 return -1;
194         if (get_sha1_hex(data + 5, tree_sha1) < 0)
195                 return -1;
196         mark_needs_sha1(sha1, "tree", tree_sha1);
197         data += 5 + 40 + 1;     /* "tree " + <hex sha1> + '\n' */
198         parents = 0;
199         while (!memcmp(data, "parent ", 7)) {
200                 if (get_sha1_hex(data + 7, parent_sha1) < 0)
201                         return -1;
202                 mark_needs_sha1(sha1, "commit", parent_sha1);
203                 data += 7 + 40 + 1;     /* "parent " + <hex sha1> + '\n' */
204                 parents++;
205         }
206         if (!parents)
207                 printf("root %s\n", sha1_to_hex(sha1));
208         return 0;
209 }
210
211 static int fsck_entry(unsigned char *sha1, char *tag, void *data, unsigned long size)
212 {
213         if (!strcmp(tag, "blob")) {
214                 /* Nothing to check */;
215         } else if (!strcmp(tag, "tree")) {
216                 if (fsck_tree(sha1, data, size) < 0)
217                         return -1;
218         } else if (!strcmp(tag, "commit")) {
219                 if (fsck_commit(sha1, data, size) < 0)
220                         return -1;
221         } else
222                 return -1;
223         return mark_sha1_seen(sha1, tag);
224 }
225
226 static int fsck_name(char *hex)
227 {
228         unsigned char sha1[20];
229         if (!get_sha1_hex(hex, sha1)) {
230                 unsigned long mapsize;
231                 void *map = map_sha1_file(sha1, &mapsize);
232                 if (map) {
233                         char type[100];
234                         unsigned long size;
235                         void *buffer = NULL;
236                         if (!check_sha1_signature(sha1, map, mapsize))
237                                 buffer = unpack_sha1_file(map, mapsize, type, &size);
238                         munmap(map, mapsize);
239                         if (buffer && !fsck_entry(sha1, type, buffer, size))
240                                 return 0;
241                 }
242         }
243         return -1;
244 }
245
246 static int fsck_dir(int i, char *path)
247 {
248         DIR *dir = opendir(path);
249         struct dirent *de;
250
251         if (!dir) {
252                 return error("missing sha1 directory '%s'", path);
253         }
254
255         while ((de = readdir(dir)) != NULL) {
256                 char name[100];
257                 int len = strlen(de->d_name);
258
259                 switch (len) {
260                 case 2:
261                         if (de->d_name[1] != '.')
262                                 break;
263                 case 1:
264                         if (de->d_name[0] != '.')
265                                 break;
266                         continue;
267                 case 38:
268                         sprintf(name, "%02x", i);
269                         memcpy(name+2, de->d_name, len+1);
270                         if (!fsck_name(name))
271                                 continue;
272                 }
273                 fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
274         }
275         closedir(dir);
276         return 0;
277 }
278
279 int main(int argc, char **argv)
280 {
281         int i, heads;
282         char *sha1_dir;
283
284         sha1_dir = getenv(DB_ENVIRONMENT) ? : DEFAULT_DB_ENVIRONMENT;
285         for (i = 0; i < 256; i++) {
286                 static char dir[4096];
287                 sprintf(dir, "%s/%02x", sha1_dir, i);
288                 fsck_dir(i, dir);
289         }
290
291         heads = 0;
292         for (i = 1; i < argc; i++) {
293                 if (!strcmp(argv[i], "--unreachable")) {
294                         show_unreachable = 1;
295                         continue;
296                 }
297                 if (!get_sha1_hex(argv[i], head_sha1)) {
298                         mark_reachable(lookup_rev(head_sha1));
299                         heads++;
300                         continue;
301                 }
302                 error("fsck-cache [[--unreachable] <head-sha1>*]");
303         }
304
305         if (!heads) {
306                 if (show_unreachable) {
307                         fprintf(stderr, "unable to do reachability without a head\n");
308                         show_unreachable = 0; 
309                 }
310                 fprintf(stderr, "expect dangling commits - potential heads - due to lack of head information\n");
311         }
312
313         check_connectivity();
314         return 0;
315 }