difftool.c 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  1. /*
  2. * "git difftool" builtin command
  3. *
  4. * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
  5. * git-difftool--helper script.
  6. *
  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.
  9. *
  10. * Any arguments that are unknown to this script are forwarded to 'git diff'.
  11. *
  12. * Copyright (C) 2016 Johannes Schindelin
  13. */
  14. #define USE_THE_INDEX_COMPATIBILITY_MACROS
  15. #include "cache.h"
  16. #include "config.h"
  17. #include "builtin.h"
  18. #include "run-command.h"
  19. #include "exec-cmd.h"
  20. #include "parse-options.h"
  21. #include "strvec.h"
  22. #include "strbuf.h"
  23. #include "lockfile.h"
  24. #include "object-store.h"
  25. #include "dir.h"
  26. static int trust_exit_code;
  27. static const char *const builtin_difftool_usage[] = {
  28. N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"),
  29. NULL
  30. };
  31. static int difftool_config(const char *var, const char *value, void *cb)
  32. {
  33. if (!strcmp(var, "difftool.trustexitcode")) {
  34. trust_exit_code = git_config_bool(var, value);
  35. return 0;
  36. }
  37. return git_default_config(var, value, cb);
  38. }
  39. static int print_tool_help(void)
  40. {
  41. const char *argv[] = { "mergetool", "--tool-help=diff", NULL };
  42. return run_command_v_opt(argv, RUN_GIT_CMD);
  43. }
  44. static int parse_index_info(char *p, int *mode1, int *mode2,
  45. struct object_id *oid1, struct object_id *oid2,
  46. char *status)
  47. {
  48. if (*p != ':')
  49. return error("expected ':', got '%c'", *p);
  50. *mode1 = (int)strtol(p + 1, &p, 8);
  51. if (*p != ' ')
  52. return error("expected ' ', got '%c'", *p);
  53. *mode2 = (int)strtol(p + 1, &p, 8);
  54. if (*p != ' ')
  55. return error("expected ' ', got '%c'", *p);
  56. if (parse_oid_hex(++p, oid1, (const char **)&p))
  57. return error("expected object ID, got '%s'", p);
  58. if (*p != ' ')
  59. return error("expected ' ', got '%c'", *p);
  60. if (parse_oid_hex(++p, oid2, (const char **)&p))
  61. return error("expected object ID, got '%s'", p);
  62. if (*p != ' ')
  63. return error("expected ' ', got '%c'", *p);
  64. *status = *++p;
  65. if (!*status)
  66. return error("missing status");
  67. if (p[1] && !isdigit(p[1]))
  68. return error("unexpected trailer: '%s'", p + 1);
  69. return 0;
  70. }
  71. /*
  72. * Remove any trailing slash from $workdir
  73. * before starting to avoid double slashes in symlink targets.
  74. */
  75. static void add_path(struct strbuf *buf, size_t base_len, const char *path)
  76. {
  77. strbuf_setlen(buf, base_len);
  78. if (buf->len && buf->buf[buf->len - 1] != '/')
  79. strbuf_addch(buf, '/');
  80. strbuf_addstr(buf, path);
  81. }
  82. /*
  83. * Determine whether we can simply reuse the file in the worktree.
  84. */
  85. static int use_wt_file(const char *workdir, const char *name,
  86. struct object_id *oid)
  87. {
  88. struct strbuf buf = STRBUF_INIT;
  89. struct stat st;
  90. int use = 0;
  91. strbuf_addstr(&buf, workdir);
  92. add_path(&buf, buf.len, name);
  93. if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {
  94. struct object_id wt_oid;
  95. int fd = open(buf.buf, O_RDONLY);
  96. if (fd >= 0 &&
  97. !index_fd(&the_index, &wt_oid, fd, &st, OBJ_BLOB, name, 0)) {
  98. if (is_null_oid(oid)) {
  99. oidcpy(oid, &wt_oid);
  100. use = 1;
  101. } else if (oideq(oid, &wt_oid))
  102. use = 1;
  103. }
  104. }
  105. strbuf_release(&buf);
  106. return use;
  107. }
  108. struct working_tree_entry {
  109. struct hashmap_entry entry;
  110. char path[FLEX_ARRAY];
  111. };
  112. static int working_tree_entry_cmp(const void *unused_cmp_data,
  113. const struct hashmap_entry *eptr,
  114. const struct hashmap_entry *entry_or_key,
  115. const void *unused_keydata)
  116. {
  117. const struct working_tree_entry *a, *b;
  118. a = container_of(eptr, const struct working_tree_entry, entry);
  119. b = container_of(entry_or_key, const struct working_tree_entry, entry);
  120. return strcmp(a->path, b->path);
  121. }
  122. /*
  123. * The `left` and `right` entries hold paths for the symlinks hashmap,
  124. * and a SHA-1 surrounded by brief text for submodules.
  125. */
  126. struct pair_entry {
  127. struct hashmap_entry entry;
  128. char left[PATH_MAX], right[PATH_MAX];
  129. const char path[FLEX_ARRAY];
  130. };
  131. static int pair_cmp(const void *unused_cmp_data,
  132. const struct hashmap_entry *eptr,
  133. const struct hashmap_entry *entry_or_key,
  134. const void *unused_keydata)
  135. {
  136. const struct pair_entry *a, *b;
  137. a = container_of(eptr, const struct pair_entry, entry);
  138. b = container_of(entry_or_key, const struct pair_entry, entry);
  139. return strcmp(a->path, b->path);
  140. }
  141. static void add_left_or_right(struct hashmap *map, const char *path,
  142. const char *content, int is_right)
  143. {
  144. struct pair_entry *e, *existing;
  145. FLEX_ALLOC_STR(e, path, path);
  146. hashmap_entry_init(&e->entry, strhash(path));
  147. existing = hashmap_get_entry(map, e, entry, NULL);
  148. if (existing) {
  149. free(e);
  150. e = existing;
  151. } else {
  152. e->left[0] = e->right[0] = '\0';
  153. hashmap_add(map, &e->entry);
  154. }
  155. strlcpy(is_right ? e->right : e->left, content, PATH_MAX);
  156. }
  157. struct path_entry {
  158. struct hashmap_entry entry;
  159. char path[FLEX_ARRAY];
  160. };
  161. static int path_entry_cmp(const void *unused_cmp_data,
  162. const struct hashmap_entry *eptr,
  163. const struct hashmap_entry *entry_or_key,
  164. const void *key)
  165. {
  166. const struct path_entry *a, *b;
  167. a = container_of(eptr, const struct path_entry, entry);
  168. b = container_of(entry_or_key, const struct path_entry, entry);
  169. return strcmp(a->path, key ? key : b->path);
  170. }
  171. static void changed_files(struct hashmap *result, const char *index_path,
  172. const char *workdir)
  173. {
  174. struct child_process update_index = CHILD_PROCESS_INIT;
  175. struct child_process diff_files = CHILD_PROCESS_INIT;
  176. struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;
  177. const char *git_dir = absolute_path(get_git_dir()), *env[] = {
  178. NULL, NULL
  179. };
  180. FILE *fp;
  181. strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path);
  182. env[0] = index_env.buf;
  183. strvec_pushl(&update_index.args,
  184. "--git-dir", git_dir, "--work-tree", workdir,
  185. "update-index", "--really-refresh", "-q",
  186. "--unmerged", NULL);
  187. update_index.no_stdin = 1;
  188. update_index.no_stdout = 1;
  189. update_index.no_stderr = 1;
  190. update_index.git_cmd = 1;
  191. update_index.use_shell = 0;
  192. update_index.clean_on_exit = 1;
  193. update_index.dir = workdir;
  194. update_index.env = env;
  195. /* Ignore any errors of update-index */
  196. run_command(&update_index);
  197. strvec_pushl(&diff_files.args,
  198. "--git-dir", git_dir, "--work-tree", workdir,
  199. "diff-files", "--name-only", "-z", NULL);
  200. diff_files.no_stdin = 1;
  201. diff_files.git_cmd = 1;
  202. diff_files.use_shell = 0;
  203. diff_files.clean_on_exit = 1;
  204. diff_files.out = -1;
  205. diff_files.dir = workdir;
  206. diff_files.env = env;
  207. if (start_command(&diff_files))
  208. die("could not obtain raw diff");
  209. fp = xfdopen(diff_files.out, "r");
  210. while (!strbuf_getline_nul(&buf, fp)) {
  211. struct path_entry *entry;
  212. FLEX_ALLOC_STR(entry, path, buf.buf);
  213. hashmap_entry_init(&entry->entry, strhash(buf.buf));
  214. hashmap_add(result, &entry->entry);
  215. }
  216. fclose(fp);
  217. if (finish_command(&diff_files))
  218. die("diff-files did not exit properly");
  219. strbuf_release(&index_env);
  220. strbuf_release(&buf);
  221. }
  222. static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)
  223. {
  224. struct strbuf buf = STRBUF_INIT;
  225. strbuf_addstr(&buf, tmpdir);
  226. remove_dir_recursively(&buf, 0);
  227. if (exit_code)
  228. warning(_("failed: %d"), exit_code);
  229. exit(exit_code);
  230. }
  231. static int ensure_leading_directories(char *path)
  232. {
  233. switch (safe_create_leading_directories(path)) {
  234. case SCLD_OK:
  235. case SCLD_EXISTS:
  236. return 0;
  237. default:
  238. return error(_("could not create leading directories "
  239. "of '%s'"), path);
  240. }
  241. }
  242. /*
  243. * Unconditional writing of a plain regular file is what
  244. * "git difftool --dir-diff" wants to do for symlinks. We are preparing two
  245. * temporary directories to be fed to a Git-unaware tool that knows how to
  246. * show a diff of two directories (e.g. "diff -r A B").
  247. *
  248. * Because the tool is Git-unaware, if a symbolic link appears in either of
  249. * these temporary directories, it will try to dereference and show the
  250. * difference of the target of the symbolic link, which is not what we want,
  251. * as the goal of the dir-diff mode is to produce an output that is logically
  252. * equivalent to what "git diff" produces.
  253. *
  254. * Most importantly, we want to get textual comparison of the result of the
  255. * readlink(2). get_symlink() provides that---it returns the contents of
  256. * the symlink that gets written to a regular file to force the external tool
  257. * to compare the readlink(2) result as text, even on a filesystem that is
  258. * capable of doing a symbolic link.
  259. */
  260. static char *get_symlink(const struct object_id *oid, const char *path)
  261. {
  262. char *data;
  263. if (is_null_oid(oid)) {
  264. /* The symlink is unknown to Git so read from the filesystem */
  265. struct strbuf link = STRBUF_INIT;
  266. if (has_symlinks) {
  267. if (strbuf_readlink(&link, path, strlen(path)))
  268. die(_("could not read symlink %s"), path);
  269. } else if (strbuf_read_file(&link, path, 128))
  270. die(_("could not read symlink file %s"), path);
  271. data = strbuf_detach(&link, NULL);
  272. } else {
  273. enum object_type type;
  274. unsigned long size;
  275. data = read_object_file(oid, &type, &size);
  276. if (!data)
  277. die(_("could not read object %s for symlink %s"),
  278. oid_to_hex(oid), path);
  279. }
  280. return data;
  281. }
  282. static int checkout_path(unsigned mode, struct object_id *oid,
  283. const char *path, const struct checkout *state)
  284. {
  285. struct cache_entry *ce;
  286. int ret;
  287. ce = make_transient_cache_entry(mode, oid, path, 0);
  288. ret = checkout_entry(ce, state, NULL, NULL);
  289. discard_cache_entry(ce);
  290. return ret;
  291. }
  292. static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
  293. int argc, const char **argv)
  294. {
  295. char tmpdir[PATH_MAX];
  296. struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
  297. struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
  298. struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
  299. struct strbuf wtdir = STRBUF_INIT;
  300. char *lbase_dir, *rbase_dir;
  301. size_t ldir_len, rdir_len, wtdir_len;
  302. const char *workdir, *tmp;
  303. int ret = 0, i;
  304. FILE *fp;
  305. struct hashmap working_tree_dups, submodules, symlinks2;
  306. struct hashmap_iter iter;
  307. struct pair_entry *entry;
  308. struct index_state wtindex;
  309. struct checkout lstate, rstate;
  310. int rc, flags = RUN_GIT_CMD, err = 0;
  311. struct child_process child = CHILD_PROCESS_INIT;
  312. const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
  313. struct hashmap wt_modified, tmp_modified;
  314. int indices_loaded = 0;
  315. workdir = get_git_work_tree();
  316. /* Setup temp directories */
  317. tmp = getenv("TMPDIR");
  318. xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
  319. if (!mkdtemp(tmpdir))
  320. return error("could not create '%s'", tmpdir);
  321. strbuf_addf(&ldir, "%s/left/", tmpdir);
  322. strbuf_addf(&rdir, "%s/right/", tmpdir);
  323. strbuf_addstr(&wtdir, workdir);
  324. if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
  325. strbuf_addch(&wtdir, '/');
  326. mkdir(ldir.buf, 0700);
  327. mkdir(rdir.buf, 0700);
  328. memset(&wtindex, 0, sizeof(wtindex));
  329. memset(&lstate, 0, sizeof(lstate));
  330. lstate.base_dir = lbase_dir = xstrdup(ldir.buf);
  331. lstate.base_dir_len = ldir.len;
  332. lstate.force = 1;
  333. memset(&rstate, 0, sizeof(rstate));
  334. rstate.base_dir = rbase_dir = xstrdup(rdir.buf);
  335. rstate.base_dir_len = rdir.len;
  336. rstate.force = 1;
  337. ldir_len = ldir.len;
  338. rdir_len = rdir.len;
  339. wtdir_len = wtdir.len;
  340. hashmap_init(&working_tree_dups, working_tree_entry_cmp, NULL, 0);
  341. hashmap_init(&submodules, pair_cmp, NULL, 0);
  342. hashmap_init(&symlinks2, pair_cmp, NULL, 0);
  343. child.no_stdin = 1;
  344. child.git_cmd = 1;
  345. child.use_shell = 0;
  346. child.clean_on_exit = 1;
  347. child.dir = prefix;
  348. child.out = -1;
  349. strvec_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
  350. NULL);
  351. for (i = 0; i < argc; i++)
  352. strvec_push(&child.args, argv[i]);
  353. if (start_command(&child))
  354. die("could not obtain raw diff");
  355. fp = xfdopen(child.out, "r");
  356. /* Build index info for left and right sides of the diff */
  357. i = 0;
  358. while (!strbuf_getline_nul(&info, fp)) {
  359. int lmode, rmode;
  360. struct object_id loid, roid;
  361. char status;
  362. const char *src_path, *dst_path;
  363. if (starts_with(info.buf, "::"))
  364. die(N_("combined diff formats('-c' and '--cc') are "
  365. "not supported in\n"
  366. "directory diff mode('-d' and '--dir-diff')."));
  367. if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,
  368. &status))
  369. break;
  370. if (strbuf_getline_nul(&lpath, fp))
  371. break;
  372. src_path = lpath.buf;
  373. i++;
  374. if (status != 'C' && status != 'R') {
  375. dst_path = src_path;
  376. } else {
  377. if (strbuf_getline_nul(&rpath, fp))
  378. break;
  379. dst_path = rpath.buf;
  380. }
  381. if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {
  382. strbuf_reset(&buf);
  383. strbuf_addf(&buf, "Subproject commit %s",
  384. oid_to_hex(&loid));
  385. add_left_or_right(&submodules, src_path, buf.buf, 0);
  386. strbuf_reset(&buf);
  387. strbuf_addf(&buf, "Subproject commit %s",
  388. oid_to_hex(&roid));
  389. if (oideq(&loid, &roid))
  390. strbuf_addstr(&buf, "-dirty");
  391. add_left_or_right(&submodules, dst_path, buf.buf, 1);
  392. continue;
  393. }
  394. if (S_ISLNK(lmode)) {
  395. char *content = get_symlink(&loid, src_path);
  396. add_left_or_right(&symlinks2, src_path, content, 0);
  397. free(content);
  398. }
  399. if (S_ISLNK(rmode)) {
  400. char *content = get_symlink(&roid, dst_path);
  401. add_left_or_right(&symlinks2, dst_path, content, 1);
  402. free(content);
  403. }
  404. if (lmode && status != 'C') {
  405. if (checkout_path(lmode, &loid, src_path, &lstate)) {
  406. ret = error("could not write '%s'", src_path);
  407. goto finish;
  408. }
  409. }
  410. if (rmode && !S_ISLNK(rmode)) {
  411. struct working_tree_entry *entry;
  412. /* Avoid duplicate working_tree entries */
  413. FLEX_ALLOC_STR(entry, path, dst_path);
  414. hashmap_entry_init(&entry->entry, strhash(dst_path));
  415. if (hashmap_get(&working_tree_dups, &entry->entry,
  416. NULL)) {
  417. free(entry);
  418. continue;
  419. }
  420. hashmap_add(&working_tree_dups, &entry->entry);
  421. if (!use_wt_file(workdir, dst_path, &roid)) {
  422. if (checkout_path(rmode, &roid, dst_path,
  423. &rstate)) {
  424. ret = error("could not write '%s'",
  425. dst_path);
  426. goto finish;
  427. }
  428. } else if (!is_null_oid(&roid)) {
  429. /*
  430. * Changes in the working tree need special
  431. * treatment since they are not part of the
  432. * index.
  433. */
  434. struct cache_entry *ce2 =
  435. make_cache_entry(&wtindex, rmode, &roid,
  436. dst_path, 0, 0);
  437. add_index_entry(&wtindex, ce2,
  438. ADD_CACHE_JUST_APPEND);
  439. add_path(&rdir, rdir_len, dst_path);
  440. if (ensure_leading_directories(rdir.buf)) {
  441. ret = error("could not create "
  442. "directory for '%s'",
  443. dst_path);
  444. goto finish;
  445. }
  446. add_path(&wtdir, wtdir_len, dst_path);
  447. if (symlinks) {
  448. if (symlink(wtdir.buf, rdir.buf)) {
  449. ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf);
  450. goto finish;
  451. }
  452. } else {
  453. struct stat st;
  454. if (stat(wtdir.buf, &st))
  455. st.st_mode = 0644;
  456. if (copy_file(rdir.buf, wtdir.buf,
  457. st.st_mode)) {
  458. ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf);
  459. goto finish;
  460. }
  461. }
  462. }
  463. }
  464. }
  465. fclose(fp);
  466. fp = NULL;
  467. if (finish_command(&child)) {
  468. ret = error("error occurred running diff --raw");
  469. goto finish;
  470. }
  471. if (!i)
  472. goto finish;
  473. /*
  474. * Changes to submodules require special treatment.This loop writes a
  475. * temporary file to both the left and right directories to show the
  476. * change in the recorded SHA1 for the submodule.
  477. */
  478. hashmap_for_each_entry(&submodules, &iter, entry,
  479. entry /* member name */) {
  480. if (*entry->left) {
  481. add_path(&ldir, ldir_len, entry->path);
  482. ensure_leading_directories(ldir.buf);
  483. write_file(ldir.buf, "%s", entry->left);
  484. }
  485. if (*entry->right) {
  486. add_path(&rdir, rdir_len, entry->path);
  487. ensure_leading_directories(rdir.buf);
  488. write_file(rdir.buf, "%s", entry->right);
  489. }
  490. }
  491. /*
  492. * Symbolic links require special treatment.The standard "git diff"
  493. * shows only the link itself, not the contents of the link target.
  494. * This loop replicates that behavior.
  495. */
  496. hashmap_for_each_entry(&symlinks2, &iter, entry,
  497. entry /* member name */) {
  498. if (*entry->left) {
  499. add_path(&ldir, ldir_len, entry->path);
  500. ensure_leading_directories(ldir.buf);
  501. write_file(ldir.buf, "%s", entry->left);
  502. }
  503. if (*entry->right) {
  504. add_path(&rdir, rdir_len, entry->path);
  505. ensure_leading_directories(rdir.buf);
  506. write_file(rdir.buf, "%s", entry->right);
  507. }
  508. }
  509. strbuf_release(&buf);
  510. strbuf_setlen(&ldir, ldir_len);
  511. helper_argv[1] = ldir.buf;
  512. strbuf_setlen(&rdir, rdir_len);
  513. helper_argv[2] = rdir.buf;
  514. if (extcmd) {
  515. helper_argv[0] = extcmd;
  516. flags = 0;
  517. } else
  518. setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
  519. rc = run_command_v_opt(helper_argv, flags);
  520. /*
  521. * If the diff includes working copy files and those
  522. * files were modified during the diff, then the changes
  523. * should be copied back to the working tree.
  524. * Do not copy back files when symlinks are used and the
  525. * external tool did not replace the original link with a file.
  526. *
  527. * These hashes are loaded lazily since they aren't needed
  528. * in the common case of --symlinks and the difftool updating
  529. * files through the symlink.
  530. */
  531. hashmap_init(&wt_modified, path_entry_cmp, NULL, wtindex.cache_nr);
  532. hashmap_init(&tmp_modified, path_entry_cmp, NULL, wtindex.cache_nr);
  533. for (i = 0; i < wtindex.cache_nr; i++) {
  534. struct hashmap_entry dummy;
  535. const char *name = wtindex.cache[i]->name;
  536. struct stat st;
  537. add_path(&rdir, rdir_len, name);
  538. if (lstat(rdir.buf, &st))
  539. continue;
  540. if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))
  541. continue;
  542. if (!indices_loaded) {
  543. struct lock_file lock = LOCK_INIT;
  544. strbuf_reset(&buf);
  545. strbuf_addf(&buf, "%s/wtindex", tmpdir);
  546. if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
  547. write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
  548. ret = error("could not write %s", buf.buf);
  549. goto finish;
  550. }
  551. changed_files(&wt_modified, buf.buf, workdir);
  552. strbuf_setlen(&rdir, rdir_len);
  553. changed_files(&tmp_modified, buf.buf, rdir.buf);
  554. add_path(&rdir, rdir_len, name);
  555. indices_loaded = 1;
  556. }
  557. hashmap_entry_init(&dummy, strhash(name));
  558. if (hashmap_get(&tmp_modified, &dummy, name)) {
  559. add_path(&wtdir, wtdir_len, name);
  560. if (hashmap_get(&wt_modified, &dummy, name)) {
  561. warning(_("both files modified: '%s' and '%s'."),
  562. wtdir.buf, rdir.buf);
  563. warning(_("working tree file has been left."));
  564. warning("%s", "");
  565. err = 1;
  566. } else if (unlink(wtdir.buf) ||
  567. copy_file(wtdir.buf, rdir.buf, st.st_mode))
  568. warning_errno(_("could not copy '%s' to '%s'"),
  569. rdir.buf, wtdir.buf);
  570. }
  571. }
  572. if (err) {
  573. warning(_("temporary files exist in '%s'."), tmpdir);
  574. warning(_("you may want to cleanup or recover these."));
  575. exit(1);
  576. } else
  577. exit_cleanup(tmpdir, rc);
  578. finish:
  579. if (fp)
  580. fclose(fp);
  581. free(lbase_dir);
  582. free(rbase_dir);
  583. strbuf_release(&ldir);
  584. strbuf_release(&rdir);
  585. strbuf_release(&wtdir);
  586. strbuf_release(&buf);
  587. return ret;
  588. }
  589. static int run_file_diff(int prompt, const char *prefix,
  590. int argc, const char **argv)
  591. {
  592. struct strvec args = STRVEC_INIT;
  593. const char *env[] = {
  594. "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
  595. NULL
  596. };
  597. int ret = 0, i;
  598. if (prompt > 0)
  599. env[2] = "GIT_DIFFTOOL_PROMPT=true";
  600. else if (!prompt)
  601. env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
  602. strvec_push(&args, "diff");
  603. for (i = 0; i < argc; i++)
  604. strvec_push(&args, argv[i]);
  605. ret = run_command_v_opt_cd_env(args.v, RUN_GIT_CMD, prefix, env);
  606. exit(ret);
  607. }
  608. int cmd_difftool(int argc, const char **argv, const char *prefix)
  609. {
  610. int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
  611. tool_help = 0, no_index = 0;
  612. static char *difftool_cmd = NULL, *extcmd = NULL;
  613. struct option builtin_difftool_options[] = {
  614. OPT_BOOL('g', "gui", &use_gui_tool,
  615. N_("use `diff.guitool` instead of `diff.tool`")),
  616. OPT_BOOL('d', "dir-diff", &dir_diff,
  617. N_("perform a full-directory diff")),
  618. OPT_SET_INT_F('y', "no-prompt", &prompt,
  619. N_("do not prompt before launching a diff tool"),
  620. 0, PARSE_OPT_NONEG),
  621. OPT_SET_INT_F(0, "prompt", &prompt, NULL,
  622. 1, PARSE_OPT_NONEG | PARSE_OPT_HIDDEN),
  623. OPT_BOOL(0, "symlinks", &symlinks,
  624. N_("use symlinks in dir-diff mode")),
  625. OPT_STRING('t', "tool", &difftool_cmd, N_("tool"),
  626. N_("use the specified diff tool")),
  627. OPT_BOOL(0, "tool-help", &tool_help,
  628. N_("print a list of diff tools that may be used with "
  629. "`--tool`")),
  630. OPT_BOOL(0, "trust-exit-code", &trust_exit_code,
  631. N_("make 'git-difftool' exit when an invoked diff "
  632. "tool returns a non - zero exit code")),
  633. OPT_STRING('x', "extcmd", &extcmd, N_("command"),
  634. N_("specify a custom command for viewing diffs")),
  635. OPT_ARGUMENT("no-index", &no_index, N_("passed to `diff`")),
  636. OPT_END()
  637. };
  638. git_config(difftool_config, NULL);
  639. symlinks = has_symlinks;
  640. argc = parse_options(argc, argv, prefix, builtin_difftool_options,
  641. builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
  642. PARSE_OPT_KEEP_DASHDASH);
  643. if (tool_help)
  644. return print_tool_help();
  645. if (!no_index && !startup_info->have_repository)
  646. die(_("difftool requires worktree or --no-index"));
  647. if (!no_index){
  648. setup_work_tree();
  649. setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
  650. setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
  651. } else if (dir_diff)
  652. die(_("--dir-diff is incompatible with --no-index"));
  653. if (use_gui_tool + !!difftool_cmd + !!extcmd > 1)
  654. die(_("--gui, --tool and --extcmd are mutually exclusive"));
  655. if (use_gui_tool)
  656. setenv("GIT_MERGETOOL_GUI", "true", 1);
  657. else if (difftool_cmd) {
  658. if (*difftool_cmd)
  659. setenv("GIT_DIFF_TOOL", difftool_cmd, 1);
  660. else
  661. die(_("no <tool> given for --tool=<tool>"));
  662. }
  663. if (extcmd) {
  664. if (*extcmd)
  665. setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1);
  666. else
  667. die(_("no <cmd> given for --extcmd=<cmd>"));
  668. }
  669. setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE",
  670. trust_exit_code ? "true" : "false", 1);
  671. /*
  672. * In directory diff mode, 'git-difftool--helper' is called once
  673. * to compare the a / b directories. In file diff mode, 'git diff'
  674. * will invoke a separate instance of 'git-difftool--helper' for
  675. * each file that changed.
  676. */
  677. if (dir_diff)
  678. return run_dir_diff(extcmd, symlinks, prefix, argc, argv);
  679. return run_file_diff(prompt, prefix, argc, argv);
  680. }