Remove obsolete note about native CPU byte format
[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,
83                          struct diff_tempfile *temp)
84 {
85         int i, next_at;
86         const char *git_prefix = "# mode: ";
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 = sq_expand(name);
93         char *cmd;
94         
95         /* diff_cmd and diff_arg have 6 %s in total which makes
96          * the sum of these strings 12 bytes larger than required.
97          * we use 2 spaces around diff-opts, and we need to count
98          * terminating NUL, so we subtract 9 here.
99          */
100         int cmd_size = (strlen(diff_cmd) + strlen(diff_opts) +
101                         strlen(diff_arg) - 9);
102         for (i = 0; i < 2; i++) {
103                 input_name_sq[i] = sq_expand(temp[i].name);
104                 if (!strcmp(temp[i].name, "/dev/null")) {
105                         path0[i] = "/dev/null";
106                         path1[i] = "";
107                 } else {
108                         path0[i] = i ? "b/" : "a/";
109                         path1[i] = name_sq;
110                 }
111                 cmd_size += (strlen(path0[i]) + strlen(path1[i]) +
112                              strlen(input_name_sq[i]));
113         }
114
115         cmd = xmalloc(cmd_size);
116
117         next_at = 0;
118         next_at += snprintf(cmd+next_at, cmd_size-next_at,
119                             diff_cmd,
120                             path0[0], path1[0], path0[1], path1[1]);
121         next_at += snprintf(cmd+next_at, cmd_size-next_at,
122                             " %s ", diff_opts);
123         next_at += snprintf(cmd+next_at, cmd_size-next_at,
124                             diff_arg, input_name_sq[0], input_name_sq[1]);
125
126         if (!path1[0][0])
127                 printf("%s. %s %s\n", git_prefix, temp[1].mode, name);
128         else if (!path1[1][0])
129                 printf("%s%s . %s\n", git_prefix, temp[0].mode, name);
130         else {
131                 if (strcmp(temp[0].mode, temp[1].mode))
132                         printf("%s%s %s %s\n", git_prefix,
133                                temp[0].mode, temp[1].mode, name);
134
135                 if (strncmp(temp[0].mode, temp[1].mode, 3))
136                         /* we do not run diff between different kind
137                          * of objects.
138                          */
139                         exit(0);
140         }
141         fflush(NULL);
142         execlp("/bin/sh","sh", "-c", cmd, NULL);
143 }
144
145 /*
146  * Given a name and sha1 pair, if the dircache tells us the file in
147  * the work tree has that object contents, return true, so that
148  * prepare_temp_file() does not have to inflate and extract.
149  */
150 static int work_tree_matches(const char *name, const unsigned char *sha1)
151 {
152         struct cache_entry *ce;
153         struct stat st;
154         int pos, len;
155         
156         /* We do not read the cache ourselves here, because the
157          * benchmark with my previous version that always reads cache
158          * shows that it makes things worse for diff-tree comparing
159          * two linux-2.6 kernel trees in an already checked out work
160          * tree.  This is because most diff-tree comparison deals with
161          * only a small number of files, while reading the cache is
162          * expensive for a large project, and its cost outweighs the
163          * savings we get by not inflating the object to a temporary
164          * file.  Practically, this code only helps when we are used
165          * by diff-cache --cached, which does read the cache before
166          * calling us.
167          */ 
168         if (!active_cache)
169                 return 0;
170
171         len = strlen(name);
172         pos = cache_name_pos(name, len);
173         if (pos < 0)
174                 return 0;
175         ce = active_cache[pos];
176         if ((lstat(name, &st) < 0) ||
177             !S_ISREG(st.st_mode) ||
178             ce_match_stat(ce, &st) ||
179             memcmp(sha1, ce->sha1, 20))
180                 return 0;
181         return 1;
182 }
183
184 static void prep_temp_blob(struct diff_tempfile *temp,
185                            void *blob,
186                            unsigned long size,
187                            unsigned char *sha1,
188                            int mode)
189 {
190         int fd;
191
192         strcpy(temp->tmp_path, ".diff_XXXXXX");
193         fd = mkstemp(temp->tmp_path);
194         if (fd < 0)
195                 die("unable to create temp-file");
196         if (write(fd, blob, size) != size)
197                 die("unable to write temp-file");
198         close(fd);
199         temp->name = temp->tmp_path;
200         strcpy(temp->hex, sha1_to_hex(sha1));
201         temp->hex[40] = 0;
202         sprintf(temp->mode, "%06o", mode);
203 }
204
205 static void prepare_temp_file(const char *name,
206                               struct diff_tempfile *temp,
207                               struct diff_spec *one)
208 {
209         static unsigned char null_sha1[20] = { 0, };
210         int use_work_tree = 0;
211
212         if (!one->file_valid) {
213         not_a_valid_file:
214                 /* A '-' entry produces this for file-2, and
215                  * a '+' entry produces this for file-1.
216                  */
217                 temp->name = "/dev/null";
218                 strcpy(temp->hex, ".");
219                 strcpy(temp->mode, ".");
220                 return;
221         }
222
223         if (one->sha1_valid &&
224             (!memcmp(one->blob_sha1, null_sha1, sizeof(null_sha1)) ||
225              work_tree_matches(name, one->blob_sha1)))
226                 use_work_tree = 1;
227
228         if (!one->sha1_valid || use_work_tree) {
229                 struct stat st;
230                 temp->name = name;
231                 if (lstat(temp->name, &st) < 0) {
232                         if (errno == ENOENT)
233                                 goto not_a_valid_file;
234                         die("stat(%s): %s", temp->name, strerror(errno));
235                 }
236                 if (S_ISLNK(st.st_mode)) {
237                         int ret;
238                         char *buf, buf_[1024];
239                         buf = ((sizeof(buf_) < st.st_size) ?
240                                xmalloc(st.st_size) : buf_);
241                         ret = readlink(name, buf, st.st_size);
242                         if (ret < 0)
243                                 die("readlink(%s)", name);
244                         prep_temp_blob(temp, buf, st.st_size,
245                                        (one->sha1_valid ?
246                                         one->blob_sha1 : null_sha1),
247                                        (one->sha1_valid ?
248                                         one->mode : S_IFLNK));
249                 }
250                 else {
251                         if (!one->sha1_valid)
252                                 strcpy(temp->hex, sha1_to_hex(null_sha1));
253                         else
254                                 strcpy(temp->hex, sha1_to_hex(one->blob_sha1));
255                         sprintf(temp->mode, "%06o",
256                                 S_IFREG |ce_permissions(st.st_mode));
257                 }
258                 return;
259         }
260         else {
261                 void *blob;
262                 char type[20];
263                 unsigned long size;
264
265                 blob = read_sha1_file(one->blob_sha1, type, &size);
266                 if (!blob || strcmp(type, "blob"))
267                         die("unable to read blob object for %s (%s)",
268                             name, sha1_to_hex(one->blob_sha1));
269                 prep_temp_blob(temp, blob, size, one->blob_sha1, one->mode);
270                 free(blob);
271         }
272 }
273
274 static void remove_tempfile(void)
275 {
276         int i;
277
278         for (i = 0; i < 2; i++)
279                 if (diff_temp[i].name == diff_temp[i].tmp_path) {
280                         unlink(diff_temp[i].name);
281                         diff_temp[i].name = NULL;
282                 }
283 }
284
285 static void remove_tempfile_on_signal(int signo)
286 {
287         remove_tempfile();
288 }
289
290 /* An external diff command takes:
291  *
292  * diff-cmd name infile1 infile1-sha1 infile1-mode \
293  *               infile2 infile2-sha1 infile2-mode.
294  *
295  */
296 void run_external_diff(const char *name,
297                        struct diff_spec *one,
298                        struct diff_spec *two)
299 {
300         struct diff_tempfile *temp = diff_temp;
301         pid_t pid;
302         int status;
303         static int atexit_asked = 0;
304
305         if (one && two) {
306                 prepare_temp_file(name, &temp[0], one);
307                 prepare_temp_file(name, &temp[1], two);
308                 if (! atexit_asked &&
309                     (temp[0].name == temp[0].tmp_path ||
310                      temp[1].name == temp[1].tmp_path)) {
311                         atexit_asked = 1;
312                         atexit(remove_tempfile);
313                 }
314                 signal(SIGINT, remove_tempfile_on_signal);
315         }
316
317         fflush(NULL);
318         pid = fork();
319         if (pid < 0)
320                 die("unable to fork");
321         if (!pid) {
322                 const char *pgm = external_diff();
323                 if (pgm) {
324                         if (one && two)
325                                 execlp(pgm, pgm,
326                                        name,
327                                        temp[0].name, temp[0].hex, temp[0].mode,
328                                        temp[1].name, temp[1].hex, temp[1].mode,
329                                        NULL);
330                         else
331                                 execlp(pgm, pgm, name, NULL);
332                 }
333                 /*
334                  * otherwise we use the built-in one.
335                  */
336                 if (one && two)
337                         builtin_diff(name, temp);
338                 else
339                         printf("* Unmerged path %s\n", name);
340                 exit(0);
341         }
342         if (waitpid(pid, &status, 0) < 0 ||
343             !WIFEXITED(status) || WEXITSTATUS(status)) {
344                 /* Earlier we did not check the exit status because
345                  * diff exits non-zero if files are different, and
346                  * we are not interested in knowing that.  It was a
347                  * mistake which made it harder to quit a diff-*
348                  * session that uses the git-apply-patch-script as
349                  * the GIT_EXTERNAL_DIFF.  A custom GIT_EXTERNAL_DIFF
350                  * should also exit non-zero only when it wants to
351                  * abort the entire diff-* session.
352                  */
353                 remove_tempfile();
354                 fprintf(stderr, "external diff died, stopping at %s.\n", name);
355                 exit(1);
356         }
357         remove_tempfile();
358 }
359
360 void diff_addremove(int addremove, unsigned mode,
361                     const unsigned char *sha1,
362                     const char *base, const char *path)
363 {
364         char concatpath[PATH_MAX];
365         struct diff_spec spec[2], *one, *two;
366
367         memcpy(spec[0].blob_sha1, sha1, 20);
368         spec[0].mode = mode;
369         spec[0].sha1_valid = spec[0].file_valid = 1;
370         spec[1].file_valid = 0;
371
372         if (addremove == '+') {
373                 one = spec + 1; two = spec;
374         } else {
375                 one = spec; two = one + 1;
376         }
377         
378         if (path) {
379                 strcpy(concatpath, base);
380                 strcat(concatpath, path);
381         }
382         run_external_diff(path ? concatpath : base, one, two);
383 }
384
385 void diff_change(unsigned old_mode, unsigned new_mode,
386                  const unsigned char *old_sha1,
387                  const unsigned char *new_sha1,
388                  const char *base, const char *path) {
389         char concatpath[PATH_MAX];
390         struct diff_spec spec[2];
391
392         memcpy(spec[0].blob_sha1, old_sha1, 20);
393         spec[0].mode = old_mode;
394         memcpy(spec[1].blob_sha1, new_sha1, 20);
395         spec[1].mode = new_mode;
396         spec[0].sha1_valid = spec[0].file_valid = 1;
397         spec[1].sha1_valid = spec[1].file_valid = 1;
398
399         if (path) {
400                 strcpy(concatpath, base);
401                 strcat(concatpath, path);
402         }
403         run_external_diff(path ? concatpath : base, &spec[0], &spec[1]);
404 }
405
406 void diff_unmerge(const char *path)
407 {
408         run_external_diff(path, NULL, NULL);
409 }