[PATCH] Diff-helper update
[git.git] / diff.c
1 /*
2  * Copyright (C) 2005 Junio C Hamano
3  */
4 #include <sys/types.h>
5 #include <sys/wait.h>
6 #include <signal.h>
7 #include <limits.h>
8 #include "cache.h"
9 #include "diff.h"
10
11 static const char *diff_opts = "-pu";
12
13 static const char *external_diff(void)
14 {
15         static const char *external_diff_cmd = NULL;
16         static int done_preparing = 0;
17
18         if (done_preparing)
19                 return external_diff_cmd;
20
21         /*
22          * Default values above are meant to match the
23          * Linux kernel development style.  Examples of
24          * alternative styles you can specify via environment
25          * variables are:
26          *
27          * GIT_DIFF_OPTS="-c";
28          */
29         if (gitenv("GIT_EXTERNAL_DIFF"))
30                 external_diff_cmd = gitenv("GIT_EXTERNAL_DIFF");
31
32         /* In case external diff fails... */
33         diff_opts = gitenv("GIT_DIFF_OPTS") ? : diff_opts;
34
35         done_preparing = 1;
36         return external_diff_cmd;
37 }
38
39 /* Help to copy the thing properly quoted for the shell safety.
40  * any single quote is replaced with '\'', and the caller is
41  * expected to enclose the result within a single quote pair.
42  *
43  * E.g.
44  *  original     sq_expand     result
45  *  name     ==> name      ==> 'name'
46  *  a b      ==> a b       ==> 'a b'
47  *  a'b      ==> a'\''b    ==> 'a'\''b'
48  */
49 static char *sq_expand(const char *src)
50 {
51         static char *buf = NULL;
52         int cnt, c;
53         const char *cp;
54         char *bp;
55
56         /* count bytes needed to store the quoted string. */ 
57         for (cnt = 1, cp = src; *cp; cnt++, cp++)
58                 if (*cp == '\'')
59                         cnt += 3;
60
61         buf = xmalloc(cnt);
62         bp = buf;
63         while ((c = *src++)) {
64                 if (c != '\'')
65                         *bp++ = c;
66                 else {
67                         bp = strcpy(bp, "'\\''");
68                         bp += 4;
69                 }
70         }
71         *bp = 0;
72         return buf;
73 }
74
75 static struct diff_tempfile {
76         const char *name;
77         char hex[41];
78         char mode[10];
79         char tmp_path[50];
80 } diff_temp[2];
81
82 static void builtin_diff(const char *name_a,
83                          const char *name_b,
84                          struct diff_tempfile *temp)
85 {
86         int i, next_at;
87         const char *diff_cmd = "diff -L'%s%s' -L'%s%s'";
88         const char *diff_arg  = "'%s' '%s'||:"; /* "||:" is to return 0 */
89         const char *input_name_sq[2];
90         const char *path0[2];
91         const char *path1[2];
92         const char *name_sq[2];
93         char *cmd;
94
95         name_sq[0] = sq_expand(name_a);
96         name_sq[1] = sq_expand(name_b);
97
98         /* diff_cmd and diff_arg have 6 %s in total which makes
99          * the sum of these strings 12 bytes larger than required.
100          * we use 2 spaces around diff-opts, and we need to count
101          * terminating NUL, so we subtract 9 here.
102          */
103         int cmd_size = (strlen(diff_cmd) + strlen(diff_opts) +
104                         strlen(diff_arg) - 9);
105         for (i = 0; i < 2; i++) {
106                 input_name_sq[i] = sq_expand(temp[i].name);
107                 if (!strcmp(temp[i].name, "/dev/null")) {
108                         path0[i] = "/dev/null";
109                         path1[i] = "";
110                 } else {
111                         path0[i] = i ? "b/" : "a/";
112                         path1[i] = name_sq[i];
113                 }
114                 cmd_size += (strlen(path0[i]) + strlen(path1[i]) +
115                              strlen(input_name_sq[i]));
116         }
117
118         cmd = xmalloc(cmd_size);
119
120         next_at = 0;
121         next_at += snprintf(cmd+next_at, cmd_size-next_at,
122                             diff_cmd,
123                             path0[0], path1[0], path0[1], path1[1]);
124         next_at += snprintf(cmd+next_at, cmd_size-next_at,
125                             " %s ", diff_opts);
126         next_at += snprintf(cmd+next_at, cmd_size-next_at,
127                             diff_arg, input_name_sq[0], input_name_sq[1]);
128
129         printf("diff --git a/%s b/%s\n", name_a, name_b);
130         if (!path1[0][0])
131                 printf("new file mode %s\n", temp[1].mode);
132         else if (!path1[1][0])
133                 printf("deleted file mode %s\n", temp[0].mode);
134         else {
135                 if (strcmp(temp[0].mode, temp[1].mode)) {
136                         printf("old mode %s\n", temp[0].mode);
137                         printf("new mode %s\n", temp[1].mode);
138                 }
139                 if (strcmp(name_a, name_b)) {
140                         printf("rename old %s\n", name_a);
141                         printf("rename new %s\n", name_b);
142                 }
143                 if (strncmp(temp[0].mode, temp[1].mode, 3))
144                         /* we do not run diff between different kind
145                          * of objects.
146                          */
147                         exit(0);
148         }
149         fflush(NULL);
150         execlp("/bin/sh","sh", "-c", cmd, NULL);
151 }
152
153 /*
154  * Given a name and sha1 pair, if the dircache tells us the file in
155  * the work tree has that object contents, return true, so that
156  * prepare_temp_file() does not have to inflate and extract.
157  */
158 static int work_tree_matches(const char *name, const unsigned char *sha1)
159 {
160         struct cache_entry *ce;
161         struct stat st;
162         int pos, len;
163         
164         /* We do not read the cache ourselves here, because the
165          * benchmark with my previous version that always reads cache
166          * shows that it makes things worse for diff-tree comparing
167          * two linux-2.6 kernel trees in an already checked out work
168          * tree.  This is because most diff-tree comparisons deal with
169          * only a small number of files, while reading the cache is
170          * expensive for a large project, and its cost outweighs the
171          * savings we get by not inflating the object to a temporary
172          * file.  Practically, this code only helps when we are used
173          * by diff-cache --cached, which does read the cache before
174          * calling us.
175          */ 
176         if (!active_cache)
177                 return 0;
178
179         len = strlen(name);
180         pos = cache_name_pos(name, len);
181         if (pos < 0)
182                 return 0;
183         ce = active_cache[pos];
184         if ((lstat(name, &st) < 0) ||
185             !S_ISREG(st.st_mode) ||
186             ce_match_stat(ce, &st) ||
187             memcmp(sha1, ce->sha1, 20))
188                 return 0;
189         return 1;
190 }
191
192 static void prep_temp_blob(struct diff_tempfile *temp,
193                            void *blob,
194                            unsigned long size,
195                            unsigned char *sha1,
196                            int mode)
197 {
198         int fd;
199
200         strcpy(temp->tmp_path, ".diff_XXXXXX");
201         fd = mkstemp(temp->tmp_path);
202         if (fd < 0)
203                 die("unable to create temp-file");
204         if (write(fd, blob, size) != size)
205                 die("unable to write temp-file");
206         close(fd);
207         temp->name = temp->tmp_path;
208         strcpy(temp->hex, sha1_to_hex(sha1));
209         temp->hex[40] = 0;
210         sprintf(temp->mode, "%06o", mode);
211 }
212
213 static void prepare_temp_file(const char *name,
214                               struct diff_tempfile *temp,
215                               struct diff_spec *one)
216 {
217         static unsigned char null_sha1[20] = { 0, };
218         int use_work_tree = 0;
219
220         if (!one->file_valid) {
221         not_a_valid_file:
222                 /* A '-' entry produces this for file-2, and
223                  * a '+' entry produces this for file-1.
224                  */
225                 temp->name = "/dev/null";
226                 strcpy(temp->hex, ".");
227                 strcpy(temp->mode, ".");
228                 return;
229         }
230
231         if (one->sha1_valid &&
232             (!memcmp(one->blob_sha1, null_sha1, sizeof(null_sha1)) ||
233              work_tree_matches(name, one->blob_sha1)))
234                 use_work_tree = 1;
235
236         if (!one->sha1_valid || use_work_tree) {
237                 struct stat st;
238                 temp->name = name;
239                 if (lstat(temp->name, &st) < 0) {
240                         if (errno == ENOENT)
241                                 goto not_a_valid_file;
242                         die("stat(%s): %s", temp->name, strerror(errno));
243                 }
244                 if (S_ISLNK(st.st_mode)) {
245                         int ret;
246                         char *buf, buf_[1024];
247                         buf = ((sizeof(buf_) < st.st_size) ?
248                                xmalloc(st.st_size) : buf_);
249                         ret = readlink(name, buf, st.st_size);
250                         if (ret < 0)
251                                 die("readlink(%s)", name);
252                         prep_temp_blob(temp, buf, st.st_size,
253                                        (one->sha1_valid ?
254                                         one->blob_sha1 : null_sha1),
255                                        (one->sha1_valid ?
256                                         one->mode : S_IFLNK));
257                 }
258                 else {
259                         if (!one->sha1_valid)
260                                 strcpy(temp->hex, sha1_to_hex(null_sha1));
261                         else
262                                 strcpy(temp->hex, sha1_to_hex(one->blob_sha1));
263                         sprintf(temp->mode, "%06o",
264                                 S_IFREG |ce_permissions(st.st_mode));
265                 }
266                 return;
267         }
268         else {
269                 void *blob;
270                 char type[20];
271                 unsigned long size;
272
273                 blob = read_sha1_file(one->blob_sha1, type, &size);
274                 if (!blob || strcmp(type, "blob"))
275                         die("unable to read blob object for %s (%s)",
276                             name, sha1_to_hex(one->blob_sha1));
277                 prep_temp_blob(temp, blob, size, one->blob_sha1, one->mode);
278                 free(blob);
279         }
280 }
281
282 static void remove_tempfile(void)
283 {
284         int i;
285
286         for (i = 0; i < 2; i++)
287                 if (diff_temp[i].name == diff_temp[i].tmp_path) {
288                         unlink(diff_temp[i].name);
289                         diff_temp[i].name = NULL;
290                 }
291 }
292
293 static void remove_tempfile_on_signal(int signo)
294 {
295         remove_tempfile();
296 }
297
298 /* An external diff command takes:
299  *
300  * diff-cmd name infile1 infile1-sha1 infile1-mode \
301  *               infile2 infile2-sha1 infile2-mode.
302  *
303  */
304 void run_external_diff(const char *name,
305                        const char *other,
306                        struct diff_spec *one,
307                        struct diff_spec *two)
308 {
309         struct diff_tempfile *temp = diff_temp;
310         pid_t pid;
311         int status;
312         static int atexit_asked = 0;
313
314         if (one && two) {
315                 prepare_temp_file(name, &temp[0], one);
316                 prepare_temp_file(other ? : name, &temp[1], two);
317                 if (! atexit_asked &&
318                     (temp[0].name == temp[0].tmp_path ||
319                      temp[1].name == temp[1].tmp_path)) {
320                         atexit_asked = 1;
321                         atexit(remove_tempfile);
322                 }
323                 signal(SIGINT, remove_tempfile_on_signal);
324         }
325
326         fflush(NULL);
327         pid = fork();
328         if (pid < 0)
329                 die("unable to fork");
330         if (!pid) {
331                 const char *pgm = external_diff();
332                 /* not passing rename patch to external ones */
333                 if (!other && pgm) {
334                         if (one && two)
335                                 execlp(pgm, pgm,
336                                        name,
337                                        temp[0].name, temp[0].hex, temp[0].mode,
338                                        temp[1].name, temp[1].hex, temp[1].mode,
339                                        NULL);
340                         else
341                                 execlp(pgm, pgm, name, NULL);
342                 }
343                 /*
344                  * otherwise we use the built-in one.
345                  */
346                 if (one && two)
347                         builtin_diff(name, other ? : name, temp);
348                 else
349                         printf("* Unmerged path %s\n", name);
350                 exit(0);
351         }
352         if (waitpid(pid, &status, 0) < 0 ||
353             !WIFEXITED(status) || WEXITSTATUS(status)) {
354                 /* Earlier we did not check the exit status because
355                  * diff exits non-zero if files are different, and
356                  * we are not interested in knowing that.  It was a
357                  * mistake which made it harder to quit a diff-*
358                  * session that uses the git-apply-patch-script as
359                  * the GIT_EXTERNAL_DIFF.  A custom GIT_EXTERNAL_DIFF
360                  * should also exit non-zero only when it wants to
361                  * abort the entire diff-* session.
362                  */
363                 remove_tempfile();
364                 fprintf(stderr, "external diff died, stopping at %s.\n", name);
365                 exit(1);
366         }
367         remove_tempfile();
368 }
369
370 void diff_addremove(int addremove, unsigned mode,
371                     const unsigned char *sha1,
372                     const char *base, const char *path)
373 {
374         char concatpath[PATH_MAX];
375         struct diff_spec spec[2], *one, *two;
376
377         memcpy(spec[0].blob_sha1, sha1, 20);
378         spec[0].mode = mode;
379         spec[0].sha1_valid = spec[0].file_valid = 1;
380         spec[1].file_valid = 0;
381
382         if (addremove == '+') {
383                 one = spec + 1; two = spec;
384         } else {
385                 one = spec; two = one + 1;
386         }
387         
388         if (path) {
389                 strcpy(concatpath, base);
390                 strcat(concatpath, path);
391         }
392         run_external_diff(path ? concatpath : base, NULL, one, two);
393 }
394
395 void diff_change(unsigned old_mode, unsigned new_mode,
396                  const unsigned char *old_sha1,
397                  const unsigned char *new_sha1,
398                  const char *base, const char *path) {
399         char concatpath[PATH_MAX];
400         struct diff_spec spec[2];
401
402         memcpy(spec[0].blob_sha1, old_sha1, 20);
403         spec[0].mode = old_mode;
404         memcpy(spec[1].blob_sha1, new_sha1, 20);
405         spec[1].mode = new_mode;
406         spec[0].sha1_valid = spec[0].file_valid = 1;
407         spec[1].sha1_valid = spec[1].file_valid = 1;
408
409         if (path) {
410                 strcpy(concatpath, base);
411                 strcat(concatpath, path);
412         }
413         run_external_diff(path ? concatpath : base, NULL, &spec[0], &spec[1]);
414 }
415
416 void diff_unmerge(const char *path)
417 {
418         run_external_diff(path, NULL, NULL, NULL);
419 }