Coverage Report

Created: 2024-09-08 06:23

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