[PATCH] Introduce diff-tree-helper.
[git.git] / diff-tree-helper.c
1 #include "cache.h"
2 #include "strbuf.h"
3 #include "diff.h"
4
5 static int matches_pathspec(const char *name, char **spec, int cnt)
6 {
7         int i;
8         int namelen = strlen(name);
9         for (i = 0; i < cnt; i++) {
10                 int speclen = strlen(spec[i]);
11                 if (! strncmp(spec[i], name, speclen) &&
12                     speclen <= namelen &&
13                     (name[speclen] == 0 ||
14                      name[speclen] == '/'))
15                         return 1;
16         }
17         return 0;
18 }
19
20 static int parse_oneside_change(const char *cp, unsigned char *sha1,
21                                 char *path) {
22         int ch;
23         while ((ch = *cp) && '0' <= ch && ch <= '7')
24                 cp++; /* skip mode bits */
25         if (strncmp(cp, "\tblob\t", 6))
26                 return -1;
27         cp += 6;
28         if (get_sha1_hex(cp, sha1))
29                 return -1;
30         cp += 40;
31         if (*cp++ != '\t')
32                 return -1;
33         strcpy(path, cp);
34         return 0;
35 }
36
37 #define STATUS_CACHED    0 /* cached and sha1 valid */
38 #define STATUS_ABSENT    1 /* diff-tree says old removed or new added */
39 #define STATUS_UNCACHED  2 /* diff-cache output: read from working tree */
40
41 static int parse_diff_tree_output(const char *buf,
42                                   unsigned char *old_sha1,
43                                   int *old_status,
44                                   unsigned char *new_sha1,
45                                   int *new_status,
46                                   char *path) {
47         const char *cp = buf;
48         int ch;
49         static unsigned char null_sha[20] = { 0, };
50
51         switch (*cp++) {
52         case '+':
53                 *old_status = STATUS_ABSENT;
54                 *new_status = (memcmp(new_sha1, null_sha, sizeof(null_sha)) ?
55                                STATUS_CACHED : STATUS_UNCACHED);
56                 return parse_oneside_change(cp, new_sha1, path);
57         case '-':
58                 *new_status = STATUS_ABSENT;
59                 *old_status = (memcmp(old_sha1, null_sha, sizeof(null_sha)) ?
60                                STATUS_CACHED : STATUS_UNCACHED);
61                 return parse_oneside_change(cp, old_sha1, path);
62         case '*':
63                 break;
64         default:
65                 return -1;
66         }
67         
68         /* This is for '*' entries */
69         while ((ch = *cp) && ('0' <= ch && ch <= '7'))
70                 cp++; /* skip mode bits */
71         if (strncmp(cp, "->", 2))
72                 return -1;
73         cp += 2;
74         while ((ch = *cp) && ('0' <= ch && ch <= '7'))
75                 cp++; /* skip mode bits */
76         if (strncmp(cp, "\tblob\t", 6))
77                 return -1;
78         cp += 6;
79         if (get_sha1_hex(cp, old_sha1))
80                 return -1;
81         cp += 40;
82         if (strncmp(cp, "->", 2))
83                 return -1;
84         cp += 2;
85         if (get_sha1_hex(cp, new_sha1))
86                 return -1;
87         cp += 40;
88         if (*cp++ != '\t')
89                 return -1;
90         strcpy(path, cp);
91         *old_status = (memcmp(old_sha1, null_sha, sizeof(null_sha)) ?
92                        STATUS_CACHED : STATUS_UNCACHED);
93         *new_status = (memcmp(new_sha1, null_sha, sizeof(null_sha)) ?
94                        STATUS_CACHED : STATUS_UNCACHED);
95         return 0;
96 }
97
98 static int sha1err(const char *path, const unsigned char *sha1)
99 {
100         return error("diff-tree-helper: unable to read sha1 file of %s (%s)",
101                      path, sha1_to_hex(sha1));
102 }
103
104 static int fserr(const char *path)
105 {
106         return error("diff-tree-helper: unable to read file %s", path);
107 }
108
109 static char *map_whole_file(const char *path, unsigned long *size) {
110         int fd;
111         struct stat st;
112         void *buf;
113
114         if ((fd = open(path, O_RDONLY)) < 0) {
115                 error("diff-tree-helper: unable to read file %s", path);
116                 return 0;
117         }
118         if (fstat(fd, &st) < 0) {
119                 close(fd);
120                 error("diff-tree-helper: unable to stat file %s", path);
121                 return 0;
122         }
123         *size = st.st_size;
124         buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
125         close(fd);
126         return buf;
127 }
128
129 static int show_diff(const unsigned char *old_sha1, int old_status,
130                      const unsigned char *new_sha1, int new_status,
131                      const char *path, int reverse_diff)
132 {
133         char other[PATH_MAX];
134         unsigned long size;
135         char type[20];
136         int fd;
137         int reverse;
138         void *blob = 0;
139         const char *fs = 0;
140         int need_unmap = 0;
141         int need_unlink = 0;
142
143
144         switch (old_status) {
145         case STATUS_CACHED:
146                 blob = read_sha1_file(old_sha1, type, &size);
147                 if (! blob)
148                         return sha1err(path, old_sha1);
149                         
150                 switch (new_status) {
151                 case STATUS_CACHED:
152                         strcpy(other, ".diff_tree_helper_XXXXXX");
153                         fd = mkstemp(other);
154                         if (fd < 0)
155                                 die("unable to create temp-file");
156                         if (write(fd, blob, size) != size)
157                                 die("unable to write temp-file");
158                         close(fd);
159                         free(blob);
160
161                         blob = read_sha1_file(new_sha1, type, &size);
162                         if (! blob)
163                                 return sha1err(path, new_sha1);
164
165                         need_unlink = 1;
166                         /* new = blob, old = fs */
167                         reverse = !reverse_diff;
168                         fs = other;
169                         break;
170
171                 case STATUS_ABSENT:
172                 case STATUS_UNCACHED:
173                         fs = ((new_status == STATUS_ABSENT) ?
174                               "/dev/null" : path);
175                         reverse = reverse_diff;
176                         break;
177
178                 default:
179                         reverse = reverse_diff;
180                 }
181                 break;
182
183         case STATUS_ABSENT:
184                 switch (new_status) {
185                 case STATUS_CACHED:
186                         blob = read_sha1_file(new_sha1, type, &size);
187                         if (! blob)
188                                 return sha1err(path, new_sha1);
189                         /* old = fs, new = blob */
190                         fs = "/dev/null";
191                         reverse = !reverse_diff;
192                         break;
193
194                 case STATUS_ABSENT:
195                         return error("diff-tree-helper: absent from both old and new?");
196                 case STATUS_UNCACHED:
197                         fs = path;
198                         blob = strdup("");
199                         size = 0;
200                         /* old = blob, new = fs */
201                         reverse = reverse_diff;
202                         break;
203                 default:
204                         reverse = reverse_diff;
205                 }
206                 break;
207
208         case STATUS_UNCACHED:
209                 fs = path; /* old = fs, new = blob */
210                 reverse = !reverse_diff;
211
212                 switch (new_status) {
213                 case STATUS_CACHED:
214                         blob = read_sha1_file(new_sha1, type, &size);
215                         if (! blob)
216                                 return sha1err(path, new_sha1);
217                         break;
218
219                 case STATUS_ABSENT:
220                         blob = strdup("");
221                         size = 0;
222                         break;
223
224                 case STATUS_UNCACHED:
225                         /* old = fs */
226                         blob = map_whole_file(path, &size);
227                         if (! blob)
228                                 return fserr(path);
229                         need_unmap = 1;
230                         break;
231                 default:
232                         reverse = reverse_diff;
233                 }
234                 break;
235
236         default:
237                 reverse = reverse_diff;
238         }
239         
240         if (fs)
241                 show_differences(fs,
242                                  path, /* label */
243                                  blob,
244                                  size,
245                                  reverse /* 0: diff blob fs
246                                             1: diff fs blob */);
247
248         if (need_unlink)
249                 unlink(other);
250         if (need_unmap && blob)
251                 munmap(blob, size);
252         else
253                 free(blob);
254         return 0;
255 }
256
257 static const char *diff_tree_helper_usage =
258 "diff-tree-helper [-R] [-z] paths...";
259
260 int main(int ac, char **av) {
261         struct strbuf sb;
262         int reverse_diff = 0;
263         int line_termination = '\n';
264
265         strbuf_init(&sb);
266
267         while (1 < ac && av[1][0] == '-') {
268                 if (av[1][1] == 'R')
269                         reverse_diff = 1;
270                 else if (av[1][1] == 'z')
271                         line_termination = 0;
272                 else
273                         usage(diff_tree_helper_usage);
274                 ac--; av++;
275         }
276         /* the remaining parameters are paths patterns */
277
278         prepare_diff_cmd();
279
280         while (1) {
281                 int old_status, new_status;
282                 unsigned char old_sha1[20], new_sha1[20];
283                 char path[PATH_MAX];
284                 read_line(&sb, stdin, line_termination);
285                 if (sb.eof)
286                         break;
287                 if (parse_diff_tree_output(sb.buf,
288                                            old_sha1, &old_status,
289                                            new_sha1, &new_status,
290                                            path)) {
291                         fprintf(stderr, "cannot parse %s\n", sb.buf);
292                         continue;
293                 }
294                 if (1 < ac && ! matches_pathspec(path, av+1, ac-1))
295                         continue;
296
297                 show_diff(old_sha1, old_status,
298                           new_sha1, new_status,
299                           path, reverse_diff);
300         }
301         return 0;
302 }