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