banned.h: mark `strtok()` and `strtok_r()` as banned
[git/debian.git] / builtin / difftool.c
blobe010a21bfbc1c4dacc70c9e1866ad4f109af737b
1 /*
2 * "git difftool" builtin command
4 * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
5 * git-difftool--helper script.
7 * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
8 * The GIT_DIFF* variables are exported for use by git-difftool--helper.
10 * Any arguments that are unknown to this script are forwarded to 'git diff'.
12 * Copyright (C) 2016 Johannes Schindelin
14 #define USE_THE_INDEX_VARIABLE
15 #include "cache.h"
16 #include "abspath.h"
17 #include "config.h"
18 #include "builtin.h"
19 #include "run-command.h"
20 #include "environment.h"
21 #include "exec-cmd.h"
22 #include "gettext.h"
23 #include "hex.h"
24 #include "parse-options.h"
25 #include "strvec.h"
26 #include "strbuf.h"
27 #include "lockfile.h"
28 #include "object-store.h"
29 #include "dir.h"
30 #include "entry.h"
31 #include "setup.h"
32 #include "wrapper.h"
34 static int trust_exit_code;
36 static const char *const builtin_difftool_usage[] = {
37 N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"),
38 NULL
41 static int difftool_config(const char *var, const char *value, void *cb)
43 if (!strcmp(var, "difftool.trustexitcode")) {
44 trust_exit_code = git_config_bool(var, value);
45 return 0;
48 return git_default_config(var, value, cb);
51 static int print_tool_help(void)
53 struct child_process cmd = CHILD_PROCESS_INIT;
55 cmd.git_cmd = 1;
56 strvec_pushl(&cmd.args, "mergetool", "--tool-help=diff", NULL);
57 return run_command(&cmd);
60 static int parse_index_info(char *p, int *mode1, int *mode2,
61 struct object_id *oid1, struct object_id *oid2,
62 char *status)
64 if (*p != ':')
65 return error("expected ':', got '%c'", *p);
66 *mode1 = (int)strtol(p + 1, &p, 8);
67 if (*p != ' ')
68 return error("expected ' ', got '%c'", *p);
69 *mode2 = (int)strtol(p + 1, &p, 8);
70 if (*p != ' ')
71 return error("expected ' ', got '%c'", *p);
72 if (parse_oid_hex(++p, oid1, (const char **)&p))
73 return error("expected object ID, got '%s'", p);
74 if (*p != ' ')
75 return error("expected ' ', got '%c'", *p);
76 if (parse_oid_hex(++p, oid2, (const char **)&p))
77 return error("expected object ID, got '%s'", p);
78 if (*p != ' ')
79 return error("expected ' ', got '%c'", *p);
80 *status = *++p;
81 if (!*status)
82 return error("missing status");
83 if (p[1] && !isdigit(p[1]))
84 return error("unexpected trailer: '%s'", p + 1);
85 return 0;
89 * Remove any trailing slash from $workdir
90 * before starting to avoid double slashes in symlink targets.
92 static void add_path(struct strbuf *buf, size_t base_len, const char *path)
94 strbuf_setlen(buf, base_len);
95 if (buf->len && buf->buf[buf->len - 1] != '/')
96 strbuf_addch(buf, '/');
97 strbuf_addstr(buf, path);
101 * Determine whether we can simply reuse the file in the worktree.
103 static int use_wt_file(const char *workdir, const char *name,
104 struct object_id *oid)
106 struct strbuf buf = STRBUF_INIT;
107 struct stat st;
108 int use = 0;
110 strbuf_addstr(&buf, workdir);
111 add_path(&buf, buf.len, name);
113 if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {
114 struct object_id wt_oid;
115 int fd = open(buf.buf, O_RDONLY);
117 if (fd >= 0 &&
118 !index_fd(&the_index, &wt_oid, fd, &st, OBJ_BLOB, name, 0)) {
119 if (is_null_oid(oid)) {
120 oidcpy(oid, &wt_oid);
121 use = 1;
122 } else if (oideq(oid, &wt_oid))
123 use = 1;
127 strbuf_release(&buf);
129 return use;
132 struct working_tree_entry {
133 struct hashmap_entry entry;
134 char path[FLEX_ARRAY];
137 static int working_tree_entry_cmp(const void *cmp_data UNUSED,
138 const struct hashmap_entry *eptr,
139 const struct hashmap_entry *entry_or_key,
140 const void *keydata UNUSED)
142 const struct working_tree_entry *a, *b;
144 a = container_of(eptr, const struct working_tree_entry, entry);
145 b = container_of(entry_or_key, const struct working_tree_entry, entry);
147 return strcmp(a->path, b->path);
151 * The `left` and `right` entries hold paths for the symlinks hashmap,
152 * and a SHA-1 surrounded by brief text for submodules.
154 struct pair_entry {
155 struct hashmap_entry entry;
156 char left[PATH_MAX], right[PATH_MAX];
157 const char path[FLEX_ARRAY];
160 static int pair_cmp(const void *cmp_data UNUSED,
161 const struct hashmap_entry *eptr,
162 const struct hashmap_entry *entry_or_key,
163 const void *keydata UNUSED)
165 const struct pair_entry *a, *b;
167 a = container_of(eptr, const struct pair_entry, entry);
168 b = container_of(entry_or_key, const struct pair_entry, entry);
170 return strcmp(a->path, b->path);
173 static void add_left_or_right(struct hashmap *map, const char *path,
174 const char *content, int is_right)
176 struct pair_entry *e, *existing;
178 FLEX_ALLOC_STR(e, path, path);
179 hashmap_entry_init(&e->entry, strhash(path));
180 existing = hashmap_get_entry(map, e, entry, NULL);
181 if (existing) {
182 free(e);
183 e = existing;
184 } else {
185 e->left[0] = e->right[0] = '\0';
186 hashmap_add(map, &e->entry);
188 strlcpy(is_right ? e->right : e->left, content, PATH_MAX);
191 struct path_entry {
192 struct hashmap_entry entry;
193 char path[FLEX_ARRAY];
196 static int path_entry_cmp(const void *cmp_data UNUSED,
197 const struct hashmap_entry *eptr,
198 const struct hashmap_entry *entry_or_key,
199 const void *key)
201 const struct path_entry *a, *b;
203 a = container_of(eptr, const struct path_entry, entry);
204 b = container_of(entry_or_key, const struct path_entry, entry);
206 return strcmp(a->path, key ? key : b->path);
209 static void changed_files(struct hashmap *result, const char *index_path,
210 const char *workdir)
212 struct child_process update_index = CHILD_PROCESS_INIT;
213 struct child_process diff_files = CHILD_PROCESS_INIT;
214 struct strbuf buf = STRBUF_INIT;
215 const char *git_dir = absolute_path(get_git_dir());
216 FILE *fp;
218 strvec_pushl(&update_index.args,
219 "--git-dir", git_dir, "--work-tree", workdir,
220 "update-index", "--really-refresh", "-q",
221 "--unmerged", NULL);
222 update_index.no_stdin = 1;
223 update_index.no_stdout = 1;
224 update_index.no_stderr = 1;
225 update_index.git_cmd = 1;
226 update_index.use_shell = 0;
227 update_index.clean_on_exit = 1;
228 update_index.dir = workdir;
229 strvec_pushf(&update_index.env, "GIT_INDEX_FILE=%s", index_path);
230 /* Ignore any errors of update-index */
231 run_command(&update_index);
233 strvec_pushl(&diff_files.args,
234 "--git-dir", git_dir, "--work-tree", workdir,
235 "diff-files", "--name-only", "-z", NULL);
236 diff_files.no_stdin = 1;
237 diff_files.git_cmd = 1;
238 diff_files.use_shell = 0;
239 diff_files.clean_on_exit = 1;
240 diff_files.out = -1;
241 diff_files.dir = workdir;
242 strvec_pushf(&diff_files.env, "GIT_INDEX_FILE=%s", index_path);
243 if (start_command(&diff_files))
244 die("could not obtain raw diff");
245 fp = xfdopen(diff_files.out, "r");
246 while (!strbuf_getline_nul(&buf, fp)) {
247 struct path_entry *entry;
248 FLEX_ALLOC_STR(entry, path, buf.buf);
249 hashmap_entry_init(&entry->entry, strhash(buf.buf));
250 hashmap_add(result, &entry->entry);
252 fclose(fp);
253 if (finish_command(&diff_files))
254 die("diff-files did not exit properly");
255 strbuf_release(&buf);
258 static int ensure_leading_directories(char *path)
260 switch (safe_create_leading_directories(path)) {
261 case SCLD_OK:
262 case SCLD_EXISTS:
263 return 0;
264 default:
265 return error(_("could not create leading directories "
266 "of '%s'"), path);
271 * Unconditional writing of a plain regular file is what
272 * "git difftool --dir-diff" wants to do for symlinks. We are preparing two
273 * temporary directories to be fed to a Git-unaware tool that knows how to
274 * show a diff of two directories (e.g. "diff -r A B").
276 * Because the tool is Git-unaware, if a symbolic link appears in either of
277 * these temporary directories, it will try to dereference and show the
278 * difference of the target of the symbolic link, which is not what we want,
279 * as the goal of the dir-diff mode is to produce an output that is logically
280 * equivalent to what "git diff" produces.
282 * Most importantly, we want to get textual comparison of the result of the
283 * readlink(2). get_symlink() provides that---it returns the contents of
284 * the symlink that gets written to a regular file to force the external tool
285 * to compare the readlink(2) result as text, even on a filesystem that is
286 * capable of doing a symbolic link.
288 static char *get_symlink(const struct object_id *oid, const char *path)
290 char *data;
291 if (is_null_oid(oid)) {
292 /* The symlink is unknown to Git so read from the filesystem */
293 struct strbuf link = STRBUF_INIT;
294 if (has_symlinks) {
295 if (strbuf_readlink(&link, path, strlen(path)))
296 die(_("could not read symlink %s"), path);
297 } else if (strbuf_read_file(&link, path, 128))
298 die(_("could not read symlink file %s"), path);
300 data = strbuf_detach(&link, NULL);
301 } else {
302 enum object_type type;
303 unsigned long size;
304 data = repo_read_object_file(the_repository, oid, &type,
305 &size);
306 if (!data)
307 die(_("could not read object %s for symlink %s"),
308 oid_to_hex(oid), path);
311 return data;
314 static int checkout_path(unsigned mode, struct object_id *oid,
315 const char *path, const struct checkout *state)
317 struct cache_entry *ce;
318 int ret;
320 ce = make_transient_cache_entry(mode, oid, path, 0, NULL);
321 ret = checkout_entry(ce, state, NULL, NULL);
323 discard_cache_entry(ce);
324 return ret;
327 static void write_file_in_directory(struct strbuf *dir, size_t dir_len,
328 const char *path, const char *content)
330 add_path(dir, dir_len, path);
331 ensure_leading_directories(dir->buf);
332 unlink(dir->buf);
333 write_file(dir->buf, "%s", content);
336 /* Write the file contents for the left and right sides of the difftool
337 * dir-diff representation for submodules and symlinks. Symlinks and submodules
338 * are written as regular text files so that external diff tools can diff them
339 * as text files, resulting in behavior that is analogous to to what "git diff"
340 * displays for symlink and submodule diffs.
342 static void write_standin_files(struct pair_entry *entry,
343 struct strbuf *ldir, size_t ldir_len,
344 struct strbuf *rdir, size_t rdir_len)
346 if (*entry->left)
347 write_file_in_directory(ldir, ldir_len, entry->path, entry->left);
348 if (*entry->right)
349 write_file_in_directory(rdir, rdir_len, entry->path, entry->right);
352 static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
353 struct child_process *child)
355 struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
356 struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
357 struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
358 struct strbuf wtdir = STRBUF_INIT;
359 struct strbuf tmpdir = STRBUF_INIT;
360 char *lbase_dir = NULL, *rbase_dir = NULL;
361 size_t ldir_len, rdir_len, wtdir_len;
362 const char *workdir, *tmp;
363 int ret = 0, i;
364 FILE *fp = NULL;
365 struct hashmap working_tree_dups = HASHMAP_INIT(working_tree_entry_cmp,
366 NULL);
367 struct hashmap submodules = HASHMAP_INIT(pair_cmp, NULL);
368 struct hashmap symlinks2 = HASHMAP_INIT(pair_cmp, NULL);
369 struct hashmap_iter iter;
370 struct pair_entry *entry;
371 struct index_state wtindex = INDEX_STATE_INIT(the_repository);
372 struct checkout lstate, rstate;
373 int err = 0;
374 struct child_process cmd = CHILD_PROCESS_INIT;
375 struct hashmap wt_modified, tmp_modified;
376 int indices_loaded = 0;
378 workdir = get_git_work_tree();
380 /* Setup temp directories */
381 tmp = getenv("TMPDIR");
382 strbuf_add_absolute_path(&tmpdir, tmp ? tmp : "/tmp");
383 strbuf_trim_trailing_dir_sep(&tmpdir);
384 strbuf_addstr(&tmpdir, "/git-difftool.XXXXXX");
385 if (!mkdtemp(tmpdir.buf)) {
386 ret = error("could not create '%s'", tmpdir.buf);
387 goto finish;
389 strbuf_addf(&ldir, "%s/left/", tmpdir.buf);
390 strbuf_addf(&rdir, "%s/right/", tmpdir.buf);
391 strbuf_addstr(&wtdir, workdir);
392 if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
393 strbuf_addch(&wtdir, '/');
394 mkdir(ldir.buf, 0700);
395 mkdir(rdir.buf, 0700);
397 memset(&lstate, 0, sizeof(lstate));
398 lstate.base_dir = lbase_dir = xstrdup(ldir.buf);
399 lstate.base_dir_len = ldir.len;
400 lstate.force = 1;
401 memset(&rstate, 0, sizeof(rstate));
402 rstate.base_dir = rbase_dir = xstrdup(rdir.buf);
403 rstate.base_dir_len = rdir.len;
404 rstate.force = 1;
406 ldir_len = ldir.len;
407 rdir_len = rdir.len;
408 wtdir_len = wtdir.len;
410 child->no_stdin = 1;
411 child->git_cmd = 1;
412 child->use_shell = 0;
413 child->clean_on_exit = 1;
414 child->dir = prefix;
415 child->out = -1;
416 if (start_command(child))
417 die("could not obtain raw diff");
418 fp = xfdopen(child->out, "r");
420 /* Build index info for left and right sides of the diff */
421 i = 0;
422 while (!strbuf_getline_nul(&info, fp)) {
423 int lmode, rmode;
424 struct object_id loid, roid;
425 char status;
426 const char *src_path, *dst_path;
428 if (starts_with(info.buf, "::"))
429 die(N_("combined diff formats ('-c' and '--cc') are "
430 "not supported in\n"
431 "directory diff mode ('-d' and '--dir-diff')."));
433 if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,
434 &status))
435 break;
436 if (strbuf_getline_nul(&lpath, fp))
437 break;
438 src_path = lpath.buf;
440 i++;
441 if (status != 'C' && status != 'R') {
442 dst_path = src_path;
443 } else {
444 if (strbuf_getline_nul(&rpath, fp))
445 break;
446 dst_path = rpath.buf;
449 if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {
450 strbuf_reset(&buf);
451 strbuf_addf(&buf, "Subproject commit %s",
452 oid_to_hex(&loid));
453 add_left_or_right(&submodules, src_path, buf.buf, 0);
454 strbuf_reset(&buf);
455 strbuf_addf(&buf, "Subproject commit %s",
456 oid_to_hex(&roid));
457 if (oideq(&loid, &roid))
458 strbuf_addstr(&buf, "-dirty");
459 add_left_or_right(&submodules, dst_path, buf.buf, 1);
460 continue;
463 if (S_ISLNK(lmode)) {
464 char *content = get_symlink(&loid, src_path);
465 add_left_or_right(&symlinks2, src_path, content, 0);
466 free(content);
469 if (S_ISLNK(rmode)) {
470 char *content = get_symlink(&roid, dst_path);
471 add_left_or_right(&symlinks2, dst_path, content, 1);
472 free(content);
475 if (lmode && status != 'C') {
476 if (checkout_path(lmode, &loid, src_path, &lstate)) {
477 ret = error("could not write '%s'", src_path);
478 goto finish;
482 if (rmode && !S_ISLNK(rmode)) {
483 struct working_tree_entry *entry;
485 /* Avoid duplicate working_tree entries */
486 FLEX_ALLOC_STR(entry, path, dst_path);
487 hashmap_entry_init(&entry->entry, strhash(dst_path));
488 if (hashmap_get(&working_tree_dups, &entry->entry,
489 NULL)) {
490 free(entry);
491 continue;
493 hashmap_add(&working_tree_dups, &entry->entry);
495 if (!use_wt_file(workdir, dst_path, &roid)) {
496 if (checkout_path(rmode, &roid, dst_path,
497 &rstate)) {
498 ret = error("could not write '%s'",
499 dst_path);
500 goto finish;
502 } else if (!is_null_oid(&roid)) {
504 * Changes in the working tree need special
505 * treatment since they are not part of the
506 * index.
508 struct cache_entry *ce2 =
509 make_cache_entry(&wtindex, rmode, &roid,
510 dst_path, 0, 0);
512 add_index_entry(&wtindex, ce2,
513 ADD_CACHE_JUST_APPEND);
515 add_path(&rdir, rdir_len, dst_path);
516 if (ensure_leading_directories(rdir.buf)) {
517 ret = error("could not create "
518 "directory for '%s'",
519 dst_path);
520 goto finish;
522 add_path(&wtdir, wtdir_len, dst_path);
523 if (symlinks) {
524 if (symlink(wtdir.buf, rdir.buf)) {
525 ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf);
526 goto finish;
528 } else {
529 struct stat st;
530 if (stat(wtdir.buf, &st))
531 st.st_mode = 0644;
532 if (copy_file(rdir.buf, wtdir.buf,
533 st.st_mode)) {
534 ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf);
535 goto finish;
542 fclose(fp);
543 fp = NULL;
544 if (finish_command(child)) {
545 ret = error("error occurred running diff --raw");
546 goto finish;
549 if (!i)
550 goto finish;
553 * Changes to submodules require special treatment.This loop writes a
554 * temporary file to both the left and right directories to show the
555 * change in the recorded SHA1 for the submodule.
557 hashmap_for_each_entry(&submodules, &iter, entry,
558 entry /* member name */) {
559 write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len);
563 * Symbolic links require special treatment. The standard "git diff"
564 * shows only the link itself, not the contents of the link target.
565 * This loop replicates that behavior.
567 hashmap_for_each_entry(&symlinks2, &iter, entry,
568 entry /* member name */) {
570 write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len);
573 strbuf_setlen(&ldir, ldir_len);
574 strbuf_setlen(&rdir, rdir_len);
576 if (extcmd) {
577 strvec_push(&cmd.args, extcmd);
578 } else {
579 strvec_push(&cmd.args, "difftool--helper");
580 cmd.git_cmd = 1;
581 setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
583 strvec_pushl(&cmd.args, ldir.buf, rdir.buf, NULL);
584 ret = run_command(&cmd);
586 /* TODO: audit for interaction with sparse-index. */
587 ensure_full_index(&wtindex);
590 * If the diff includes working copy files and those
591 * files were modified during the diff, then the changes
592 * should be copied back to the working tree.
593 * Do not copy back files when symlinks are used and the
594 * external tool did not replace the original link with a file.
596 * These hashes are loaded lazily since they aren't needed
597 * in the common case of --symlinks and the difftool updating
598 * files through the symlink.
600 hashmap_init(&wt_modified, path_entry_cmp, NULL, wtindex.cache_nr);
601 hashmap_init(&tmp_modified, path_entry_cmp, NULL, wtindex.cache_nr);
603 for (i = 0; i < wtindex.cache_nr; i++) {
604 struct hashmap_entry dummy;
605 const char *name = wtindex.cache[i]->name;
606 struct stat st;
608 add_path(&rdir, rdir_len, name);
609 if (lstat(rdir.buf, &st))
610 continue;
612 if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))
613 continue;
615 if (!indices_loaded) {
616 struct lock_file lock = LOCK_INIT;
617 strbuf_reset(&buf);
618 strbuf_addf(&buf, "%s/wtindex", tmpdir.buf);
619 if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
620 write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
621 ret = error("could not write %s", buf.buf);
622 goto finish;
624 changed_files(&wt_modified, buf.buf, workdir);
625 strbuf_setlen(&rdir, rdir_len);
626 changed_files(&tmp_modified, buf.buf, rdir.buf);
627 add_path(&rdir, rdir_len, name);
628 indices_loaded = 1;
631 hashmap_entry_init(&dummy, strhash(name));
632 if (hashmap_get(&tmp_modified, &dummy, name)) {
633 add_path(&wtdir, wtdir_len, name);
634 if (hashmap_get(&wt_modified, &dummy, name)) {
635 warning(_("both files modified: '%s' and '%s'."),
636 wtdir.buf, rdir.buf);
637 warning(_("working tree file has been left."));
638 warning("%s", "");
639 err = 1;
640 } else if (unlink(wtdir.buf) ||
641 copy_file(wtdir.buf, rdir.buf, st.st_mode))
642 warning_errno(_("could not copy '%s' to '%s'"),
643 rdir.buf, wtdir.buf);
647 if (err) {
648 warning(_("temporary files exist in '%s'."), tmpdir.buf);
649 warning(_("you may want to cleanup or recover these."));
650 ret = 1;
651 } else {
652 remove_dir_recursively(&tmpdir, 0);
653 if (ret)
654 warning(_("failed: %d"), ret);
657 finish:
658 if (fp)
659 fclose(fp);
661 free(lbase_dir);
662 free(rbase_dir);
663 strbuf_release(&ldir);
664 strbuf_release(&rdir);
665 strbuf_release(&wtdir);
666 strbuf_release(&buf);
667 strbuf_release(&tmpdir);
669 return (ret < 0) ? 1 : ret;
672 static int run_file_diff(int prompt, const char *prefix,
673 struct child_process *child)
675 const char *env[] = {
676 "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
677 NULL
680 if (prompt > 0)
681 env[2] = "GIT_DIFFTOOL_PROMPT=true";
682 else if (!prompt)
683 env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
685 child->git_cmd = 1;
686 child->dir = prefix;
687 strvec_pushv(&child->env, env);
689 return run_command(child);
692 int cmd_difftool(int argc, const char **argv, const char *prefix)
694 int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
695 tool_help = 0, no_index = 0;
696 static char *difftool_cmd = NULL, *extcmd = NULL;
697 struct option builtin_difftool_options[] = {
698 OPT_BOOL('g', "gui", &use_gui_tool,
699 N_("use `diff.guitool` instead of `diff.tool`")),
700 OPT_BOOL('d', "dir-diff", &dir_diff,
701 N_("perform a full-directory diff")),
702 OPT_SET_INT_F('y', "no-prompt", &prompt,
703 N_("do not prompt before launching a diff tool"),
704 0, PARSE_OPT_NONEG),
705 OPT_SET_INT_F(0, "prompt", &prompt, NULL,
706 1, PARSE_OPT_NONEG | PARSE_OPT_HIDDEN),
707 OPT_BOOL(0, "symlinks", &symlinks,
708 N_("use symlinks in dir-diff mode")),
709 OPT_STRING('t', "tool", &difftool_cmd, N_("tool"),
710 N_("use the specified diff tool")),
711 OPT_BOOL(0, "tool-help", &tool_help,
712 N_("print a list of diff tools that may be used with "
713 "`--tool`")),
714 OPT_BOOL(0, "trust-exit-code", &trust_exit_code,
715 N_("make 'git-difftool' exit when an invoked diff "
716 "tool returns a non-zero exit code")),
717 OPT_STRING('x', "extcmd", &extcmd, N_("command"),
718 N_("specify a custom command for viewing diffs")),
719 OPT_BOOL(0, "no-index", &no_index, N_("passed to `diff`")),
720 OPT_END()
722 struct child_process child = CHILD_PROCESS_INIT;
724 git_config(difftool_config, NULL);
725 symlinks = has_symlinks;
727 argc = parse_options(argc, argv, prefix, builtin_difftool_options,
728 builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN_OPT |
729 PARSE_OPT_KEEP_DASHDASH);
731 if (tool_help)
732 return print_tool_help();
734 if (!no_index && !startup_info->have_repository)
735 die(_("difftool requires worktree or --no-index"));
737 if (!no_index){
738 setup_work_tree();
739 setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
740 setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
741 } else if (dir_diff)
742 die(_("options '%s' and '%s' cannot be used together"), "--dir-diff", "--no-index");
744 die_for_incompatible_opt3(use_gui_tool, "--gui",
745 !!difftool_cmd, "--tool",
746 !!extcmd, "--extcmd");
748 if (use_gui_tool)
749 setenv("GIT_MERGETOOL_GUI", "true", 1);
750 else if (difftool_cmd) {
751 if (*difftool_cmd)
752 setenv("GIT_DIFF_TOOL", difftool_cmd, 1);
753 else
754 die(_("no <tool> given for --tool=<tool>"));
757 if (extcmd) {
758 if (*extcmd)
759 setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1);
760 else
761 die(_("no <cmd> given for --extcmd=<cmd>"));
764 setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE",
765 trust_exit_code ? "true" : "false", 1);
768 * In directory diff mode, 'git-difftool--helper' is called once
769 * to compare the a / b directories. In file diff mode, 'git diff'
770 * will invoke a separate instance of 'git-difftool--helper' for
771 * each file that changed.
773 strvec_push(&child.args, "diff");
774 if (no_index)
775 strvec_push(&child.args, "--no-index");
776 if (dir_diff)
777 strvec_pushl(&child.args, "--raw", "--no-abbrev", "-z", NULL);
778 strvec_pushv(&child.args, argv);
780 if (dir_diff)
781 return run_dir_diff(extcmd, symlinks, prefix, &child);
782 return run_file_diff(prompt, prefix, &child);