[PATCH] Introduce diff-tree-helper.
[git/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 }