[PATCH] init-db.c: create and use safe_create_dir helper
[git.git] / diff-cache.c
1 #include "cache.h"
2
3 static int cached_only = 0;
4 static int recursive = 0;
5 static int line_termination = '\n';
6
7 static int diff_cache(void *tree, unsigned long size, struct cache_entry **ac, int entries, const char *base);
8
9 static void update_tree_entry(void **bufp, unsigned long *sizep)
10 {
11         void *buf = *bufp;
12         unsigned long size = *sizep;
13         int len = strlen(buf) + 1 + 20;
14
15         if (size < len)
16                 die("corrupt tree file 1 (%s)", size);
17         *bufp = buf + len;
18         *sizep = size - len;
19 }
20
21 static const unsigned char *extract(void *tree, unsigned long size, const char **pathp, unsigned int *modep)
22 {
23         int len = strlen(tree)+1;
24         const unsigned char *sha1 = tree + len;
25         const char *path = strchr(tree, ' ');
26
27         if (!path || size < len + 20 || sscanf(tree, "%o", modep) != 1)
28                 die("corrupt tree file 2 (%d)", size);
29         *pathp = path+1;
30         return sha1;
31 }
32
33 static char *malloc_base(const char *base, const char *path, int pathlen)
34 {
35         int baselen = strlen(base);
36         char *newbase = malloc(baselen + pathlen + 2);
37         memcpy(newbase, base, baselen);
38         memcpy(newbase + baselen, path, pathlen);
39         memcpy(newbase + baselen + pathlen, "/", 2);
40         return newbase;
41 }
42
43 static void show_file(const char *prefix, const char *path, unsigned int mode, const unsigned char *sha1, const char *base);
44
45 /* A whole sub-tree went away or appeared */
46 static void show_tree(const char *prefix, void *tree, unsigned long size, const char *base)
47 {
48         while (size) {
49                 const char *path;
50                 unsigned int mode;
51                 const unsigned char *sha1 = extract(tree, size, &path, &mode);
52                 
53                 show_file(prefix, path, mode, sha1, base);
54                 update_tree_entry(&tree, &size);
55         }
56 }
57
58 /* A file entry went away or appeared */
59 static void show_file(const char *prefix, const char *path, unsigned int mode, const unsigned char *sha1, const char *base)
60 {
61         if (recursive && S_ISDIR(mode)) {
62                 char type[20];
63                 unsigned long size;
64                 char *newbase = malloc_base(base, path, strlen(path));
65                 void *tree;
66
67                 tree = read_sha1_file(sha1, type, &size);
68                 if (!tree || strcmp(type, "tree"))
69                         die("corrupt tree sha %s", sha1_to_hex(sha1));
70
71                 show_tree(prefix, tree, size, newbase);
72                 
73                 free(tree);
74                 free(newbase);
75                 return;
76         }
77
78         printf("%s%o\t%s\t%s\t%s%s%c", prefix, mode,
79                S_ISDIR(mode) ? "tree" : "blob",
80                sha1_to_hex(sha1), base, path,
81                line_termination);
82 }
83
84 static int compare_tree_entry(const char *path1, unsigned int mode1, const unsigned char *sha1,
85                               struct cache_entry **ac, int *entries, const char *base)
86 {
87         int baselen = strlen(base);
88         struct cache_entry *ce = *ac;
89         const char *path2 = ce->name + baselen;
90         unsigned int mode2 = ntohl(ce->ce_mode);
91         const unsigned char *sha2 = ce->sha1;
92         int cmp, pathlen1, pathlen2;
93         char old_sha1_hex[50];
94
95         pathlen1 = strlen(path1);
96         pathlen2 = strlen(path2);
97         cmp = cache_name_compare(path1, pathlen1, path2, pathlen2);
98         if (cmp < 0) {
99                 if (S_ISDIR(mode1)) {
100                         char type[20];
101                         unsigned long size;
102                         void *tree = read_sha1_file(sha1, type, &size);
103                         char *newbase = malloc(baselen + 2 + pathlen1);
104
105                         memcpy(newbase, base, baselen);
106                         memcpy(newbase + baselen, path1, pathlen1);
107                         memcpy(newbase + baselen + pathlen1, "/", 2);
108                         if (!tree || strcmp(type, "tree"))
109                                 die("unable to read tree object %s", sha1_to_hex(sha1));
110                         *entries = diff_cache(tree, size, ac, *entries, newbase);
111                         free(newbase);
112                         free(tree);
113                         return -1;
114                 }
115                 show_file("-", path1, mode1, sha1, base);
116                 return -1;
117         }
118
119         if (!cached_only) {
120                 static unsigned char no_sha1[20];
121                 int fd, changed;
122                 struct stat st;
123                 fd = open(ce->name, O_RDONLY);
124                 if (fd < 0 || fstat(fd, &st) < 0) {
125                         show_file("-", path1, mode1, sha1, base);
126                         return -1;
127                 }
128                 changed = cache_match_stat(ce, &st);
129                 close(fd);
130                 if (changed) {
131                         mode2 = st.st_mode;
132                         sha2 = no_sha1;
133                 }
134         }
135
136         if (cmp > 0) {
137                 show_file("+", path2, mode2, sha2, base);
138                 return 1;
139         }
140         if (!memcmp(sha1, sha2, 20) && mode1 == mode2)
141                 return 0;
142
143         /*
144          * If the filemode has changed to/from a directory from/to a regular
145          * file, we need to consider it a remove and an add.
146          */
147         if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
148                 show_file("-", path1, mode1, sha1, base);
149                 show_file("+", path2, mode2, sha2, base);
150                 return 0;
151         }
152
153         strcpy(old_sha1_hex, sha1_to_hex(sha1));
154         printf("*%o->%o\t%s\t%s->%s\t%s%s%c", mode1, mode2,
155                S_ISDIR(mode1) ? "tree" : "blob",
156                old_sha1_hex, sha1_to_hex(sha2), base, path1,
157                line_termination);
158         return 0;
159 }
160
161 static int diff_cache(void *tree, unsigned long size, struct cache_entry **ac, int entries, const char *base)
162 {
163         int baselen = strlen(base);
164
165         for (;;) {
166                 struct cache_entry *ce;
167                 unsigned int mode;
168                 const char *path;
169                 const unsigned char *sha1;
170                 int left;
171
172                 /*
173                  * No entries in the cache (with this base)?
174                  * Output the tree contents.
175                  */
176                 if (!entries || ce_namelen(ce = *ac) < baselen || memcmp(ce->name, base, baselen)) {
177                         if (!size)
178                                 return entries;
179                         sha1 = extract(tree, size, &path, &mode);
180                         show_file("-", path, mode, sha1, base);
181                         update_tree_entry(&tree, &size);
182                         continue;
183                 }
184
185                 /*
186                  * No entries in the tree? Output the cache contents
187                  */
188                 if (!size) {
189                         show_file("+", ce->name, ntohl(ce->ce_mode), ce->sha1, "");
190                         ac++;
191                         entries--;
192                         continue;
193                 }
194
195                 sha1 = extract(tree, size, &path, &mode);
196                 left = entries;
197                 switch (compare_tree_entry(path, mode, sha1, ac, &left, base)) {
198                 case -1:
199                         update_tree_entry(&tree, &size);
200                         if (left < entries) {
201                                 ac += (entries - left);
202                                 entries = left;
203                         }
204                         continue;
205                 case 0:
206                         update_tree_entry(&tree, &size);
207                         /* Fallthrough */
208                 case 1:
209                         ac++;
210                         entries--;
211                         continue;
212                 }
213                 die("diff-cache: internal error");
214         }
215         return 0;
216 }
217
218 int main(int argc, char **argv)
219 {
220         unsigned char tree_sha1[20];
221         void *tree;
222         unsigned long size;
223         char type[20];
224
225         read_cache();
226         while (argc > 2) {
227                 char *arg = argv[1];
228                 argv++;
229                 argc--;
230                 if (!strcmp(arg, "-r")) {
231                         recursive = 1;
232                         continue;
233                 }
234                 if (!strcmp(arg, "-z")) {
235                         line_termination = '\0';
236                         continue;
237                 }
238                 if (!strcmp(arg, "--cached")) {
239                         cached_only = 1;
240                         continue;
241                 }
242                 usage("diff-cache [-r] [-z] <tree sha1>");
243         }
244
245         if (argc != 2 || get_sha1_hex(argv[1], tree_sha1))
246                 usage("diff-cache [-r] [-z] <tree sha1>");
247
248         tree = read_sha1_file(tree_sha1, type, &size);
249         if (!tree)
250                 die("bad tree object %s", argv[1]);
251
252         /* We allow people to feed us a commit object, just because we're nice */
253         if (!strcmp(type, "commit")) {
254                 /* tree sha1 is always at offset 5 ("tree ") */
255                 if (get_sha1_hex(tree + 5, tree_sha1))
256                         die("bad commit object %s", argv[1]);
257                 free(tree);
258                 tree = read_sha1_file(tree_sha1, type, &size);       
259                 if (!tree)
260                         die("unable to read tree object %s", sha1_to_hex(tree_sha1));
261         }
262
263         if (strcmp(type, "tree"))
264                 die("bad tree object %s (%s)", sha1_to_hex(tree_sha1), type);
265
266         return diff_cache(tree, size, active_cache, active_nr, "");
267 }