Merge branch 'nd/attr-pathspec-in-tree-walk'
authorJunio C Hamano <gitster@pobox.com>
Mon, 14 Jan 2019 23:29:28 +0000 (15:29 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 14 Jan 2019 23:29:28 +0000 (15:29 -0800)
The traversal over tree objects has learned to honor
":(attr:label)" pathspec match, which has been implemented only for
enumerating paths on the filesystem.

* nd/attr-pathspec-in-tree-walk:
tree-walk: support :(attr) matching
dir.c: move, rename and export match_attrs()
pathspec.h: clean up "extern" in function declarations
tree-walk.c: make tree_entry_interesting() take an index
tree.c: make read_tree*() take 'struct repository *'

1  2 
archive.c
builtin/checkout.c
builtin/grep.c
builtin/log.c
builtin/ls-tree.c
list-objects.c
merge-recursive.c
revision.c
unpack-trees.c
diff --combined archive.c
index 180d97cf77fbe3e4ed3272852bf2ef62ccbb983d,bfa9cc20c95d41f133e9d21da5760fdc4bcc4b0d..be324af675d0444a5b6e11ebbccddbe407e9898a
+++ b/archive.c
@@@ -285,7 -285,8 +285,8 @@@ int write_archive_entries(struct archiv
                git_attr_set_direction(GIT_ATTR_INDEX);
        }
  
-       err = read_tree_recursive(args->tree, "", 0, 0, &args->pathspec,
+       err = read_tree_recursive(args->repo, args->tree, "",
+                                 0, 0, &args->pathspec,
                                  queue_or_write_archive_entry,
                                  &context);
        if (err == READ_TREE_RECURSIVE)
@@@ -346,7 -347,8 +347,8 @@@ static int path_exists(struct archiver_
        ctx.args = args;
        parse_pathspec(&ctx.pathspec, 0, 0, "", paths);
        ctx.pathspec.recursive = 1;
-       ret = read_tree_recursive(args->tree, "", 0, 0, &ctx.pathspec,
+       ret = read_tree_recursive(args->repo, args->tree, "",
+                                 0, 0, &ctx.pathspec,
                                  reject_entry, &ctx);
        clear_pathspec(&ctx.pathspec);
        return ret != 0;
@@@ -391,12 -393,12 +393,12 @@@ static void parse_treeish_arg(const cha
                int refnamelen = colon - name;
  
                if (!dwim_ref(name, refnamelen, &oid, &ref))
 -                      die("no such ref: %.*s", refnamelen, name);
 +                      die(_("no such ref: %.*s"), refnamelen, name);
                free(ref);
        }
  
        if (get_oid(name, &oid))
 -              die("Not a valid object name");
 +              die(_("not a valid object name: %s"), name);
  
        commit = lookup_commit_reference_gently(ar_args->repo, &oid, 1);
        if (commit) {
  
        tree = parse_tree_indirect(&oid);
        if (tree == NULL)
 -              die("not a tree object");
 +              die(_("not a tree object: %s"), oid_to_hex(&oid));
  
        if (prefix) {
                struct object_id tree_oid;
                err = get_tree_entry(&tree->object.oid, prefix, &tree_oid,
                                     &mode);
                if (err || !S_ISDIR(mode))
 -                      die("current working directory is untracked");
 +                      die(_("current working directory is untracked"));
  
                tree = parse_tree_indirect(&tree_oid);
        }
diff --combined builtin/checkout.c
index 08b0ac48f30fd952fe57376a8e8b0d61f8bc1ad6,c9dda8e82e8ed2a88b93a80c729a5971b9e6a139..d338d96fe9a7518852e5461a913ba4dbdb19dc4d
@@@ -115,7 -115,8 +115,8 @@@ static int update_some(const struct obj
  
  static int read_tree_some(struct tree *tree, const struct pathspec *pathspec)
  {
-       read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL);
+       read_tree_recursive(the_repository, tree, "", 0, 0,
+                           pathspec, update_some, NULL);
  
        /* update the index with the given tree's info
         * for all args, expanding wildcards, and exit
@@@ -753,8 -754,7 +754,8 @@@ static void update_refs_for_switch(cons
                        free(refname);
                }
                else
 -                      create_branch(opts->new_branch, new_branch_info->name,
 +                      create_branch(the_repository,
 +                                    opts->new_branch, new_branch_info->name,
                                      opts->new_branch_force ? 1 : 0,
                                      opts->new_branch_force ? 1 : 0,
                                      opts->new_branch_log,
                                delete_reflog(old_branch_info->path);
                }
        }
 -      remove_branch_state();
 +      remove_branch_state(the_repository);
        strbuf_release(&msg);
        if (!opts->quiet &&
            (new_branch_info->path || (!opts->force_detach && !strcmp(new_branch_info->name, "HEAD"))))
@@@ -1080,12 -1080,9 +1081,12 @@@ static int parse_branchname_arg(int arg
                 */
                int recover_with_dwim = dwim_new_local_branch_ok;
  
 -              if (!has_dash_dash &&
 -                  (check_filename(opts->prefix, arg) || !no_wildcard(arg)))
 +              int could_be_checkout_paths = !has_dash_dash &&
 +                      check_filename(opts->prefix, arg);
 +
 +              if (!has_dash_dash && !no_wildcard(arg))
                        recover_with_dwim = 0;
 +
                /*
                 * Accept "git checkout foo" and "git checkout foo --"
                 * as candidates for dwim.
                        const char *remote = unique_tracking_name(arg, rev,
                                                                  dwim_remotes_matched);
                        if (remote) {
 +                              if (could_be_checkout_paths)
 +                                      die(_("'%s' could be both a local file and a tracking branch.\n"
 +                                            "Please use -- (and optionally --no-guess) to disambiguate"),
 +                                          arg);
                                *new_branch = arg;
                                arg = remote;
                                /* DWIMmed to create local branch, case (3).(b) */
@@@ -1236,7 -1229,7 +1237,7 @@@ int cmd_checkout(int argc, const char *
        struct checkout_opts opts;
        struct branch_info new_branch_info;
        char *conflict_style = NULL;
 -      int dwim_new_local_branch = 1;
 +      int dwim_new_local_branch, no_dwim_new_local_branch = 0;
        int dwim_remotes_matched = 0;
        struct option options[] = {
                OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
                OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
                OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
                         N_("do not limit pathspecs to sparse entries only")),
 -              OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
 -                              N_("second guess 'git checkout <no-such-branch>'")),
 +              OPT_BOOL(0, "no-guess", &no_dwim_new_local_branch,
 +                       N_("do not second guess 'git checkout <no-such-branch>'")),
                OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
                         N_("do not check if another worktree is holding the given ref")),
                { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
        argc = parse_options(argc, argv, prefix, options, checkout_usage,
                             PARSE_OPT_KEEP_DASHDASH);
  
 +      dwim_new_local_branch = !no_dwim_new_local_branch;
        if (opts.show_progress < 0) {
                if (opts.quiet)
                        opts.show_progress = 0;
diff --combined builtin/grep.c
index bad9c0a3d5134a69d9388e169793b03c9952d9bf,64b9167a593caac977ef353fe8082942c64c383d..4748195ae173786924f75dab9e865ce05b434930
@@@ -437,7 -437,7 +437,7 @@@ static int grep_submodule(struct grep_o
         * store is no longer global and instead is a member of the repository
         * object.
         */
 -      add_to_alternates_memory(submodule.objects->objectdir);
 +      add_to_alternates_memory(submodule.objects->odb->path);
        grep_read_unlock();
  
        if (oid) {
@@@ -553,7 -553,8 +553,8 @@@ static int grep_tree(struct grep_opt *o
  
                if (match != all_entries_interesting) {
                        strbuf_addstr(&name, base->buf + tn_len);
-                       match = tree_entry_interesting(&entry, &name,
+                       match = tree_entry_interesting(repo->index,
+                                                      &entry, &name,
                                                       0, pathspec);
                        strbuf_setlen(&name, name_base_len);
  
diff --combined builtin/log.c
index e8e51068bd903c01e12207637d59d80ea940bf2d,7709f2b1d4f15f6ab2631a4317cbe1e590b3c733..3e145fe5023638bc50c05f9d9dedfd6d11ae536d
@@@ -641,8 -641,9 +641,9 @@@ int cmd_show(int argc, const char **arg
                                        diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
                                        name,
                                        diff_get_color_opt(&rev.diffopt, DIFF_RESET));
-                       read_tree_recursive((struct tree *)o, "", 0, 0, &match_all,
-                                       show_tree_object, rev.diffopt.file);
+                       read_tree_recursive(the_repository, (struct tree *)o, "",
+                                           0, 0, &match_all, show_tree_object,
+                                           rev.diffopt.file);
                        rev.shown_one = 1;
                        break;
                case OBJ_COMMIT:
@@@ -1011,6 -1012,8 +1012,6 @@@ static void show_diffstat(struct rev_in
  
        memcpy(&opts, &rev->diffopt, sizeof(opts));
        opts.output_format = DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
 -      opts.stat_width = MAIL_DEFAULT_WRAP;
 -
        diff_setup_done(&opts);
  
        diff_tree_oid(get_commit_tree_oid(origin),
@@@ -1094,18 -1097,9 +1095,18 @@@ static void make_cover_letter(struct re
        }
  
        if (rev->rdiff1) {
 +              /*
 +               * Pass minimum required diff-options to range-diff; others
 +               * can be added later if deemed desirable.
 +               */
 +              struct diff_options opts;
 +              diff_setup(&opts);
 +              opts.file = rev->diffopt.file;
 +              opts.use_color = rev->diffopt.use_color;
 +              diff_setup_done(&opts);
                fprintf_ln(rev->diffopt.file, "%s", rev->rdiff_title);
                show_range_diff(rev->rdiff1, rev->rdiff2,
 -                              rev->creation_factor, 1, &rev->diffopt);
 +                              rev->creation_factor, 1, &opts);
        }
  }
  
diff --combined builtin/ls-tree.c
index 7d581d6463dc534606d27b1998c27991a800d6f1,6855f7fea56cb9cbceaad93afcfea3a8faf5da7e..7cad3f24ebd084fe653061f4f5899816853fc4f0
@@@ -100,7 -100,7 +100,7 @@@ static int show_tree(const struct objec
                                                  "BAD");
                                else
                                        xsnprintf(size_text, sizeof(size_text),
 -                                                "%lu", size);
 +                                                "%"PRIuMAX, (uintmax_t)size);
                        } else
                                xsnprintf(size_text, sizeof(size_text), "-");
                        printf("%06o %s %s %7s\t", mode, type,
@@@ -185,5 -185,6 +185,6 @@@ int cmd_ls_tree(int argc, const char **
        tree = parse_tree_indirect(&oid);
        if (!tree)
                die("not a tree object");
-       return !!read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL);
+       return !!read_tree_recursive(the_repository, tree, "", 0, 0,
+                                    &pathspec, show_tree, NULL);
  }
diff --combined list-objects.c
index cf7f25bed352ac89625d311a1f5bdcd0c7a37878,63c395d9c20ef0aed5dee610da3ca3eb120dd6a9..4e2789768d21ccb47a8fe2d5de62b6be58ea9bb7
@@@ -55,8 -55,7 +55,8 @@@ static void process_blob(struct travers
        pathlen = path->len;
        strbuf_addstr(path, name);
        if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn)
 -              r = ctx->filter_fn(LOFS_BLOB, obj,
 +              r = ctx->filter_fn(ctx->revs->repo,
 +                                 LOFS_BLOB, obj,
                                   path->buf, &path->buf[pathlen],
                                   ctx->filter_data);
        if (r & LOFR_MARK_SEEN)
@@@ -114,7 -113,8 +114,8 @@@ static void process_tree_contents(struc
  
        while (tree_entry(&desc, &entry)) {
                if (match != all_entries_interesting) {
-                       match = tree_entry_interesting(&entry, base, 0,
+                       match = tree_entry_interesting(ctx->revs->repo->index,
+                                                      &entry, base, 0,
                                                       &ctx->revs->diffopt.pathspec);
                        if (match == all_entries_not_interesting)
                                break;
                }
  
                if (S_ISDIR(entry.mode)) {
 -                      struct tree *t = lookup_tree(the_repository, entry.oid);
 +                      struct tree *t = lookup_tree(ctx->revs->repo, entry.oid);
                        t->object.flags |= NOT_USER_GIVEN;
                        process_tree(ctx, t, base, entry.path);
                }
                        process_gitlink(ctx, entry.oid->hash,
                                        base, entry.path);
                else {
 -                      struct blob *b = lookup_blob(the_repository, entry.oid);
 +                      struct blob *b = lookup_blob(ctx->revs->repo, entry.oid);
                        b->object.flags |= NOT_USER_GIVEN;
                        process_blob(ctx, b, base, entry.path);
                }
@@@ -176,8 -176,7 +177,8 @@@ static void process_tree(struct travers
  
        strbuf_addstr(base, name);
        if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn)
 -              r = ctx->filter_fn(LOFS_BEGIN_TREE, obj,
 +              r = ctx->filter_fn(ctx->revs->repo,
 +                                 LOFS_BEGIN_TREE, obj,
                                   base->buf, &base->buf[baselen],
                                   ctx->filter_data);
        if (r & LOFR_MARK_SEEN)
                process_tree_contents(ctx, tree, base);
  
        if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn) {
 -              r = ctx->filter_fn(LOFS_END_TREE, obj,
 +              r = ctx->filter_fn(ctx->revs->repo,
 +                                 LOFS_END_TREE, obj,
                                   base->buf, &base->buf[baselen],
                                   ctx->filter_data);
                if (r & LOFR_MARK_SEEN)
diff --combined merge-recursive.c
index ecf8db0b716ff20305425937c26ab6b8e69d33ed,b9467f5ecf2aa6188220987eaaf7f002b181ad8a..59ba4b4a1a083b07050c5e6eba8306cb63736a3e
@@@ -186,7 -186,6 +186,7 @@@ static int oid_eq(const struct object_i
  enum rename_type {
        RENAME_NORMAL = 0,
        RENAME_VIA_DIR,
 +      RENAME_ADD,
        RENAME_DELETE,
        RENAME_ONE_FILE_TO_ONE,
        RENAME_ONE_FILE_TO_TWO,
@@@ -229,7 -228,6 +229,7 @@@ static inline void setup_rename_conflic
                                              struct stage_data *src_entry1,
                                              struct stage_data *src_entry2)
  {
 +      int ostage1 = 0, ostage2;
        struct rename_conflict_info *ci;
  
        /*
                dst_entry2->rename_conflict_info = ci;
        }
  
 -      if (rename_type == RENAME_TWO_FILES_TO_ONE) {
 -              /*
 -               * For each rename, there could have been
 -               * modifications on the side of history where that
 -               * file was not renamed.
 -               */
 -              int ostage1 = o->branch1 == branch1 ? 3 : 2;
 -              int ostage2 = ostage1 ^ 1;
 +      /*
 +       * For each rename, there could have been
 +       * modifications on the side of history where that
 +       * file was not renamed.
 +       */
 +      if (rename_type == RENAME_ADD ||
 +          rename_type == RENAME_TWO_FILES_TO_ONE) {
 +              ostage1 = o->branch1 == branch1 ? 3 : 2;
  
                ci->ren1_other.path = pair1->one->path;
                oidcpy(&ci->ren1_other.oid, &src_entry1->stages[ostage1].oid);
                ci->ren1_other.mode = src_entry1->stages[ostage1].mode;
 +      }
 +
 +      if (rename_type == RENAME_TWO_FILES_TO_ONE) {
 +              ostage2 = ostage1 ^ 1;
  
                ci->ren2_other.path = pair2->one->path;
                oidcpy(&ci->ren2_other.oid, &src_entry2->stages[ostage2].oid);
@@@ -469,7 -463,8 +469,8 @@@ static void get_files_dirs(struct merge
  {
        struct pathspec match_all;
        memset(&match_all, 0, sizeof(match_all));
-       read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o);
+       read_tree_recursive(the_repository, tree, "", 0, 0,
+                           &match_all, save_files_dirs, o);
  }
  
  static int get_tree_entry_if_blob(const struct object_id *tree,
@@@ -696,6 -691,27 +697,6 @@@ static int update_stages(struct merge_o
        return 0;
  }
  
 -static int update_stages_for_stage_data(struct merge_options *opt,
 -                                      const char *path,
 -                                      const struct stage_data *stage_data)
 -{
 -      struct diff_filespec o, a, b;
 -
 -      o.mode = stage_data->stages[1].mode;
 -      oidcpy(&o.oid, &stage_data->stages[1].oid);
 -
 -      a.mode = stage_data->stages[2].mode;
 -      oidcpy(&a.oid, &stage_data->stages[2].oid);
 -
 -      b.mode = stage_data->stages[3].mode;
 -      oidcpy(&b.oid, &stage_data->stages[3].oid);
 -
 -      return update_stages(opt, path,
 -                           is_null_oid(&o.oid) ? NULL : &o,
 -                           is_null_oid(&a.oid) ? NULL : &a,
 -                           is_null_oid(&b.oid) ? NULL : &b);
 -}
 -
  static void update_entry(struct stage_data *entry,
                         struct diff_filespec *o,
                         struct diff_filespec *a,
@@@ -1043,8 -1059,7 +1044,8 @@@ static int merge_3way(struct merge_opti
                      const struct diff_filespec *a,
                      const struct diff_filespec *b,
                      const char *branch1,
 -                    const char *branch2)
 +                    const char *branch2,
 +                    const int extra_marker_size)
  {
        mmfile_t orig, src1, src2;
        struct ll_merge_options ll_opts = {0};
        int merge_status;
  
        ll_opts.renormalize = o->renormalize;
 +      ll_opts.extra_marker_size = extra_marker_size;
        ll_opts.xdl_opts = o->xdl_opts;
  
        if (o->call_depth) {
@@@ -1288,7 -1302,6 +1289,7 @@@ static int merge_mode_and_contents(stru
                                   const char *filename,
                                   const char *branch1,
                                   const char *branch2,
 +                                 const int extra_marker_size,
                                   struct merge_file_info *result)
  {
        if (o->branch1 != branch1) {
                 */
                return merge_mode_and_contents(o, one, b, a,
                                               filename,
 -                                             branch2, branch1, result);
 +                                             branch2, branch1,
 +                                             extra_marker_size, result);
        }
  
        result->merge = 0;
                        int ret = 0, merge_status;
  
                        merge_status = merge_3way(o, &result_buf, one, a, b,
 -                                                branch1, branch2);
 +                                                branch1, branch2,
 +                                                extra_marker_size);
  
                        if ((merge_status < 0) || !result_buf.ptr)
                                ret = err(o, _("Failed to execute internal merge"));
@@@ -1545,203 -1556,80 +1546,203 @@@ static struct diff_filespec *filespec_f
        return target;
  }
  
 -static int handle_file(struct merge_options *o,
 -                      struct diff_filespec *rename,
 -                      int stage,
 -                      struct rename_conflict_info *ci)
 +static int handle_file_collision(struct merge_options *o,
 +                               const char *collide_path,
 +                               const char *prev_path1,
 +                               const char *prev_path2,
 +                               const char *branch1, const char *branch2,
 +                               const struct object_id *a_oid,
 +                               unsigned int a_mode,
 +                               const struct object_id *b_oid,
 +                               unsigned int b_mode)
  {
 -      char *dst_name = rename->path;
 -      struct stage_data *dst_entry;
 -      const char *cur_branch, *other_branch;
 -      struct diff_filespec other;
 -      struct diff_filespec *add;
 -      int ret;
 +      struct merge_file_info mfi;
 +      struct diff_filespec null, a, b;
 +      char *alt_path = NULL;
 +      const char *update_path = collide_path;
  
 -      if (stage == 2) {
 -              dst_entry = ci->dst_entry1;
 -              cur_branch = ci->branch1;
 -              other_branch = ci->branch2;
 -      } else {
 -              dst_entry = ci->dst_entry2;
 -              cur_branch = ci->branch2;
 -              other_branch = ci->branch1;
 +      /*
 +       * It's easiest to get the correct things into stage 2 and 3, and
 +       * to make sure that the content merge puts HEAD before the other
 +       * branch if we just ensure that branch1 == o->branch1.  So, simply
 +       * flip arguments around if we don't have that.
 +       */
 +      if (branch1 != o->branch1) {
 +              return handle_file_collision(o, collide_path,
 +                                           prev_path2, prev_path1,
 +                                           branch2, branch1,
 +                                           b_oid, b_mode,
 +                                           a_oid, a_mode);
        }
  
 -      add = filespec_from_entry(&other, dst_entry, stage ^ 1);
 -      if (add) {
 -              int ren_src_was_dirty = was_dirty(o, rename->path);
 -              char *add_name = unique_path(o, rename->path, other_branch);
 -              if (update_file(o, 0, &add->oid, add->mode, add_name))
 -                      return -1;
 +      /*
 +       * In the recursive case, we just opt to undo renames
 +       */
 +      if (o->call_depth && (prev_path1 || prev_path2)) {
 +              /* Put first file (a_oid, a_mode) in its original spot */
 +              if (prev_path1) {
 +                      if (update_file(o, 1, a_oid, a_mode, prev_path1))
 +                              return -1;
 +              } else {
 +                      if (update_file(o, 1, a_oid, a_mode, collide_path))
 +                              return -1;
 +              }
  
 -              if (ren_src_was_dirty) {
 -                      output(o, 1, _("Refusing to lose dirty file at %s"),
 -                             rename->path);
 +              /* Put second file (b_oid, b_mode) in its original spot */
 +              if (prev_path2) {
 +                      if (update_file(o, 1, b_oid, b_mode, prev_path2))
 +                              return -1;
 +              } else {
 +                      if (update_file(o, 1, b_oid, b_mode, collide_path))
 +                              return -1;
                }
 +
 +              /* Don't leave something at collision path if unrenaming both */
 +              if (prev_path1 && prev_path2)
 +                      remove_file(o, 1, collide_path, 0);
 +
 +              return 0;
 +      }
 +
 +      /* Remove rename sources if rename/add or rename/rename(2to1) */
 +      if (prev_path1)
 +              remove_file(o, 1, prev_path1,
 +                          o->call_depth || would_lose_untracked(prev_path1));
 +      if (prev_path2)
 +              remove_file(o, 1, prev_path2,
 +                          o->call_depth || would_lose_untracked(prev_path2));
 +
 +      /*
 +       * Remove the collision path, if it wouldn't cause dirty contents
 +       * or an untracked file to get lost.  We'll either overwrite with
 +       * merged contents, or just write out to differently named files.
 +       */
 +      if (was_dirty(o, collide_path)) {
 +              output(o, 1, _("Refusing to lose dirty file at %s"),
 +                     collide_path);
 +              update_path = alt_path = unique_path(o, collide_path, "merged");
 +      } else if (would_lose_untracked(collide_path)) {
                /*
 -               * Because the double negatives somehow keep confusing me...
 -               *    1) update_wd iff !ren_src_was_dirty.
 -               *    2) no_wd iff !update_wd
 -               *    3) so, no_wd == !!ren_src_was_dirty == ren_src_was_dirty
 +               * Only way we get here is if both renames were from
 +               * a directory rename AND user had an untracked file
 +               * at the location where both files end up after the
 +               * two directory renames.  See testcase 10d of t6043.
                 */
 -              remove_file(o, 0, rename->path, ren_src_was_dirty);
 -              dst_name = unique_path(o, rename->path, cur_branch);
 +              output(o, 1, _("Refusing to lose untracked file at "
 +                             "%s, even though it's in the way."),
 +                     collide_path);
 +              update_path = alt_path = unique_path(o, collide_path, "merged");
        } else {
 -              if (dir_in_way(rename->path, !o->call_depth, 0)) {
 -                      dst_name = unique_path(o, rename->path, cur_branch);
 -                      output(o, 1, _("%s is a directory in %s adding as %s instead"),
 -                             rename->path, other_branch, dst_name);
 -              } else if (!o->call_depth &&
 -                         would_lose_untracked(rename->path)) {
 -                      dst_name = unique_path(o, rename->path, cur_branch);
 -                      output(o, 1, _("Refusing to lose untracked file at %s; "
 -                                     "adding as %s instead"),
 -                             rename->path, dst_name);
 -              }
 +              /*
 +               * FIXME: It's possible that the two files are identical
 +               * and that the current working copy happens to match, in
 +               * which case we are unnecessarily touching the working
 +               * tree file.  It's not a likely enough scenario that I
 +               * want to code up the checks for it and a better fix is
 +               * available if we restructure how unpack_trees() and
 +               * merge-recursive interoperate anyway, so punting for
 +               * now...
 +               */
 +              remove_file(o, 0, collide_path, 0);
        }
 -      if ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))
 -              ; /* fall through, do allow dst_name to be released */
 -      else if (stage == 2)
 -              ret = update_stages(o, rename->path, NULL, rename, add);
 -      else
 -              ret = update_stages(o, rename->path, NULL, add, rename);
  
 -      if (dst_name != rename->path)
 -              free(dst_name);
 +      /* Store things in diff_filespecs for functions that need it */
 +      memset(&a, 0, sizeof(struct diff_filespec));
 +      memset(&b, 0, sizeof(struct diff_filespec));
 +      null.path = a.path = b.path = (char *)collide_path;
 +      oidcpy(&null.oid, &null_oid);
 +      null.mode = 0;
 +      oidcpy(&a.oid, a_oid);
 +      a.mode = a_mode;
 +      a.oid_valid = 1;
 +      oidcpy(&b.oid, b_oid);
 +      b.mode = b_mode;
 +      b.oid_valid = 1;
  
 -      return ret;
 +      if (merge_mode_and_contents(o, &null, &a, &b, collide_path,
 +                                  branch1, branch2, o->call_depth * 2, &mfi))
 +              return -1;
 +      mfi.clean &= !alt_path;
 +      if (update_file(o, mfi.clean, &mfi.oid, mfi.mode, update_path))
 +              return -1;
 +      if (!mfi.clean && !o->call_depth &&
 +          update_stages(o, collide_path, NULL, &a, &b))
 +              return -1;
 +      free(alt_path);
 +      /*
 +       * FIXME: If both a & b both started with conflicts (only possible
 +       * if they came from a rename/rename(2to1)), but had IDENTICAL
 +       * contents including those conflicts, then in the next line we claim
 +       * it was clean.  If someone cares about this case, we should have the
 +       * caller notify us if we started with conflicts.
 +       */
 +      return mfi.clean;
 +}
 +
 +static int handle_rename_add(struct merge_options *o,
 +                           struct rename_conflict_info *ci)
 +{
 +      /* a was renamed to c, and a separate c was added. */
 +      struct diff_filespec *a = ci->pair1->one;
 +      struct diff_filespec *c = ci->pair1->two;
 +      char *path = c->path;
 +      char *prev_path_desc;
 +      struct merge_file_info mfi;
 +
 +      int other_stage = (ci->branch1 == o->branch1 ? 3 : 2);
 +
 +      output(o, 1, _("CONFLICT (rename/add): "
 +             "Rename %s->%s in %s.  Added %s in %s"),
 +             a->path, c->path, ci->branch1,
 +             c->path, ci->branch2);
 +
 +      prev_path_desc = xstrfmt("version of %s from %s", path, a->path);
 +      if (merge_mode_and_contents(o, a, c, &ci->ren1_other, prev_path_desc,
 +                                  o->branch1, o->branch2,
 +                                  1 + o->call_depth * 2, &mfi))
 +              return -1;
 +      free(prev_path_desc);
 +
 +      return handle_file_collision(o,
 +                                   c->path, a->path, NULL,
 +                                   ci->branch1, ci->branch2,
 +                                   &mfi.oid, mfi.mode,
 +                                   &ci->dst_entry1->stages[other_stage].oid,
 +                                   ci->dst_entry1->stages[other_stage].mode);
 +}
 +
 +static char *find_path_for_conflict(struct merge_options *o,
 +                                  const char *path,
 +                                  const char *branch1,
 +                                  const char *branch2)
 +{
 +      char *new_path = NULL;
 +      if (dir_in_way(path, !o->call_depth, 0)) {
 +              new_path = unique_path(o, path, branch1);
 +              output(o, 1, _("%s is a directory in %s adding "
 +                             "as %s instead"),
 +                     path, branch2, new_path);
 +      } else if (would_lose_untracked(path)) {
 +              new_path = unique_path(o, path, branch1);
 +              output(o, 1, _("Refusing to lose untracked file"
 +                             " at %s; adding as %s instead"),
 +                     path, new_path);
 +      }
 +
 +      return new_path;
  }
  
  static int handle_rename_rename_1to2(struct merge_options *o,
                                     struct rename_conflict_info *ci)
  {
        /* One file was renamed in both branches, but to different names. */
 +      struct merge_file_info mfi;
 +      struct diff_filespec other;
 +      struct diff_filespec *add;
        struct diff_filespec *one = ci->pair1->one;
        struct diff_filespec *a = ci->pair1->two;
        struct diff_filespec *b = ci->pair2->two;
 +      char *path_desc;
  
        output(o, 1, _("CONFLICT (rename/rename): "
               "Rename \"%s\"->\"%s\" in branch \"%s\" "
               one->path, a->path, ci->branch1,
               one->path, b->path, ci->branch2,
               o->call_depth ? _(" (left unresolved)") : "");
 -      if (o->call_depth) {
 -              struct merge_file_info mfi;
 -              struct diff_filespec other;
 -              struct diff_filespec *add;
 -              if (merge_mode_and_contents(o, one, a, b, one->path,
 -                                          ci->branch1, ci->branch2, &mfi))
 -                      return -1;
  
 +      path_desc = xstrfmt("%s and %s, both renamed from %s",
 +                          a->path, b->path, one->path);
 +      if (merge_mode_and_contents(o, one, a, b, path_desc,
 +                                  ci->branch1, ci->branch2,
 +                                  o->call_depth * 2, &mfi))
 +              return -1;
 +      free(path_desc);
 +
 +      if (o->call_depth) {
                /*
                 * FIXME: For rename/add-source conflicts (if we could detect
                 * such), this is wrong.  We should instead find a unique
                }
                else
                        remove_file_from_cache(b->path);
 -      } else if (handle_file(o, a, 2, ci) || handle_file(o, b, 3, ci))
 -              return -1;
 +      } else {
 +              /*
 +               * For each destination path, we need to see if there is a
 +               * rename/add collision.  If not, we can write the file out
 +               * to the specified location.
 +               */
 +              add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
 +              if (add) {
 +                      if (handle_file_collision(o, a->path,
 +                                                NULL, NULL,
 +                                                ci->branch1, ci->branch2,
 +                                                &mfi.oid, mfi.mode,
 +                                                &add->oid, add->mode) < 0)
 +                              return -1;
 +              } else {
 +                      char *new_path = find_path_for_conflict(o, a->path,
 +                                                              ci->branch1,
 +                                                              ci->branch2);
 +                      if (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : a->path))
 +                              return -1;
 +                      free(new_path);
 +                      if (update_stages(o, a->path, NULL, a, NULL))
 +                              return -1;
 +              }
 +
 +              add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
 +              if (add) {
 +                      if (handle_file_collision(o, b->path,
 +                                                NULL, NULL,
 +                                                ci->branch1, ci->branch2,
 +                                                &add->oid, add->mode,
 +                                                &mfi.oid, mfi.mode) < 0)
 +                              return -1;
 +              } else {
 +                      char *new_path = find_path_for_conflict(o, b->path,
 +                                                              ci->branch2,
 +                                                              ci->branch1);
 +                      if (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : b->path))
 +                              return -1;
 +                      free(new_path);
 +                      if (update_stages(o, b->path, NULL, NULL, b))
 +                              return -1;
 +              }
 +      }
  
        return 0;
  }
@@@ -1851,6 -1695,7 +1852,6 @@@ static int handle_rename_rename_2to1(st
        char *path_side_2_desc;
        struct merge_file_info mfi_c1;
        struct merge_file_info mfi_c2;
 -      int ret;
  
        output(o, 1, _("CONFLICT (rename/rename): "
               "Rename %s->%s in %s. "
               a->path, c1->path, ci->branch1,
               b->path, c2->path, ci->branch2);
  
 -      remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
 -      remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
 -
        path_side_1_desc = xstrfmt("version of %s from %s", path, a->path);
        path_side_2_desc = xstrfmt("version of %s from %s", path, b->path);
        if (merge_mode_and_contents(o, a, c1, &ci->ren1_other, path_side_1_desc,
 -                                  o->branch1, o->branch2, &mfi_c1) ||
 +                                  o->branch1, o->branch2,
 +                                  1 + o->call_depth * 2, &mfi_c1) ||
            merge_mode_and_contents(o, b, &ci->ren2_other, c2, path_side_2_desc,
 -                                  o->branch1, o->branch2, &mfi_c2))
 +                                  o->branch1, o->branch2,
 +                                  1 + o->call_depth * 2, &mfi_c2))
                return -1;
        free(path_side_1_desc);
        free(path_side_2_desc);
  
 -      if (o->call_depth) {
 -              /*
 -               * If mfi_c1.clean && mfi_c2.clean, then it might make
 -               * sense to do a two-way merge of those results.  But, I
 -               * think in all cases, it makes sense to have the virtual
 -               * merge base just undo the renames; they can be detected
 -               * again later for the non-recursive merge.
 -               */
 -              remove_file(o, 0, path, 0);
 -              ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);
 -              if (!ret)
 -                      ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
 -                                        b->path);
 -      } else {
 -              char *new_path1 = unique_path(o, path, ci->branch1);
 -              char *new_path2 = unique_path(o, path, ci->branch2);
 -              output(o, 1, _("Renaming %s to %s and %s to %s instead"),
 -                     a->path, new_path1, b->path, new_path2);
 -              if (was_dirty(o, path))
 -                      output(o, 1, _("Refusing to lose dirty file at %s"),
 -                             path);
 -              else if (would_lose_untracked(path))
 -                      /*
 -                       * Only way we get here is if both renames were from
 -                       * a directory rename AND user had an untracked file
 -                       * at the location where both files end up after the
 -                       * two directory renames.  See testcase 10d of t6043.
 -                       */
 -                      output(o, 1, _("Refusing to lose untracked file at "
 -                                     "%s, even though it's in the way."),
 -                             path);
 -              else
 -                      remove_file(o, 0, path, 0);
 -              ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
 -              if (!ret)
 -                      ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
 -                                        new_path2);
 -              /*
 -               * unpack_trees() actually populates the index for us for
 -               * "normal" rename/rename(2to1) situtations so that the
 -               * correct entries are at the higher stages, which would
 -               * make the call below to update_stages_for_stage_data
 -               * unnecessary.  However, if either of the renames came
 -               * from a directory rename, then unpack_trees() will not
 -               * have gotten the right data loaded into the index, so we
 -               * need to do so now.  (While it'd be tempting to move this
 -               * call to update_stages_for_stage_data() to
 -               * apply_directory_rename_modifications(), that would break
 -               * our intermediate calls to would_lose_untracked() since
 -               * those rely on the current in-memory index.  See also the
 -               * big "NOTE" in update_stages()).
 -               */
 -              if (update_stages_for_stage_data(o, path, ci->dst_entry1))
 -                      ret = -1;
 -
 -              free(new_path2);
 -              free(new_path1);
 -      }
 -
 -      return ret;
 +      return handle_file_collision(o, path, a->path, b->path,
 +                                   ci->branch1, ci->branch2,
 +                                   &mfi_c1.oid, mfi_c1.mode,
 +                                   &mfi_c2.oid, mfi_c2.mode);
  }
  
  /*
@@@ -2831,23 -2733,47 +2832,23 @@@ static int process_renames(struct merge
                                                      0  /* update_wd    */))
                                        clean_merge = -1;
                        } else if (!oid_eq(&dst_other.oid, &null_oid)) {
 -                              clean_merge = 0;
 -                              try_merge = 1;
 -                              output(o, 1, _("CONFLICT (rename/add): Rename %s->%s in %s. "
 -                                     "%s added in %s"),
 -                                     ren1_src, ren1_dst, branch1,
 -                                     ren1_dst, branch2);
 -                              if (o->call_depth) {
 -                                      struct merge_file_info mfi;
 -                                      struct diff_filespec one, a, b;
 -
 -                                      oidcpy(&one.oid, &null_oid);
 -                                      one.mode = 0;
 -                                      one.path = ren1->pair->two->path;
 -
 -                                      oidcpy(&a.oid, &ren1->pair->two->oid);
 -                                      a.mode = ren1->pair->two->mode;
 -                                      a.path = one.path;
 -
 -                                      oidcpy(&b.oid, &dst_other.oid);
 -                                      b.mode = dst_other.mode;
 -                                      b.path = one.path;
 -
 -                                      if (merge_mode_and_contents(o, &one, &a, &b, ren1_dst,
 -                                                                  branch1, branch2,
 -                                                                  &mfi)) {
 -                                              clean_merge = -1;
 -                                              goto cleanup_and_return;
 -                                      }
 -                                      output(o, 1, _("Adding merged %s"), ren1_dst);
 -                                      if (update_file(o, 0, &mfi.oid,
 -                                                      mfi.mode, ren1_dst))
 -                                              clean_merge = -1;
 -                                      try_merge = 0;
 -                              } else {
 -                                      char *new_path = unique_path(o, ren1_dst, branch2);
 -                                      output(o, 1, _("Adding as %s instead"), new_path);
 -                                      if (update_file(o, 0, &dst_other.oid,
 -                                                      dst_other.mode, new_path))
 -                                              clean_merge = -1;
 -                                      free(new_path);
 -                              }
 +                              /*
 +                               * Probably not a clean merge, but it's
 +                               * premature to set clean_merge to 0 here,
 +                               * because if the rename merges cleanly and
 +                               * the merge exactly matches the newly added
 +                               * file, then the merge will be clean.
 +                               */
 +                              setup_rename_conflict_info(RENAME_ADD,
 +                                                         ren1->pair,
 +                                                         NULL,
 +                                                         branch1,
 +                                                         branch2,
 +                                                         ren1->dst_entry,
 +                                                         NULL,
 +                                                         o,
 +                                                         ren1->src_entry,
 +                                                         NULL);
                        } else
                                try_merge = 1;
  
@@@ -3128,8 -3054,7 +3129,8 @@@ static int handle_content_merge(struct 
                        df_conflict_remains = 1;
        }
        if (merge_mode_and_contents(o, &one, &a, &b, path,
 -                                  o->branch1, o->branch2, &mfi))
 +                                  o->branch1, o->branch2,
 +                                  o->call_depth * 2, &mfi))
                return -1;
  
        /*
@@@ -3259,15 -3184,6 +3260,15 @@@ static int process_entry(struct merge_o
                                                  conflict_info->branch2))
                                clean_merge = -1;
                        break;
 +              case RENAME_ADD:
 +                      /*
 +                       * Probably unclean merge, but if the renamed file
 +                       * merges cleanly and the result can then be
 +                       * two-way merged cleanly with the added file, I
 +                       * guess it's a clean merge?
 +                       */
 +                      clean_merge = handle_rename_add(o, conflict_info);
 +                      break;
                case RENAME_DELETE:
                        clean_merge = 0;
                        if (handle_rename_delete(o,
                                clean_merge = -1;
                        break;
                case RENAME_TWO_FILES_TO_ONE:
 -                      clean_merge = 0;
 -                      if (handle_rename_rename_2to1(o, conflict_info))
 -                              clean_merge = -1;
 +                      /*
 +                       * Probably unclean merge, but if the two renamed
 +                       * files merge cleanly and the two resulting files
 +                       * can then be two-way merged cleanly, I guess it's
 +                       * a clean merge?
 +                       */
 +                      clean_merge = handle_rename_rename_2to1(o,
 +                                                              conflict_info);
                        break;
                default:
                        entry->processed = 0;
                                clean_merge = -1;
                }
        } else if (a_oid && b_oid) {
 -              /* Case C: Added in both (check for same permissions) and */
 -              /* case D: Modified in both, but differently. */
 -              int is_dirty = 0; /* unpack_trees would have bailed if dirty */
 -              clean_merge = handle_content_merge(o, path, is_dirty,
 -                                                 o_oid, o_mode,
 -                                                 a_oid, a_mode,
 -                                                 b_oid, b_mode,
 -                                                 NULL);
 +              if (!o_oid) {
 +                      /* Case C: Added in both (check for same permissions) */
 +                      output(o, 1,
 +                             _("CONFLICT (add/add): Merge conflict in %s"),
 +                             path);
 +                      clean_merge = handle_file_collision(o,
 +                                                          path, NULL, NULL,
 +                                                          o->branch1,
 +                                                          o->branch2,
 +                                                          a_oid, a_mode,
 +                                                          b_oid, b_mode);
 +              } else {
 +                      /* case D: Modified in both, but differently. */
 +                      int is_dirty = 0; /* unpack_trees would have bailed if dirty */
 +                      clean_merge = handle_content_merge(o, path,
 +                                                         is_dirty,
 +                                                         o_oid, o_mode,
 +                                                         a_oid, a_mode,
 +                                                         b_oid, b_mode,
 +                                                         NULL);
 +              }
        } else if (!o_oid && !a_oid && !b_oid) {
                /*
                 * this entry was deleted altogether. a_mode == 0 means
diff --combined revision.c
index 293303b67d0d876b24e678e34afb1c56160bc128,06708337effb0b2676bf3902d1ed48d82cd82709..f1e6929d4193ad539bda98af2de978abc04ed8bb
@@@ -1463,6 -1463,7 +1463,7 @@@ void repo_init_revisions(struct reposit
        revs->abbrev = DEFAULT_ABBREV;
        revs->ignore_merges = 1;
        revs->simplify_history = 1;
+       revs->pruning.repo = r;
        revs->pruning.flags.recursive = 1;
        revs->pruning.flags.quick = 1;
        revs->pruning.add_remove = file_add_remove;
@@@ -1729,8 -1730,6 +1730,8 @@@ int handle_revision_arg(const char *arg
        if (!cant_be_filename)
                verify_non_filename(revs->prefix, arg);
        object = get_reference(revs, arg, &oid, flags ^ local_flags);
 +      if (!object)
 +              return revs->ignore_missing ? 0 : -1;
        add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
        add_pending_object_with_path(revs, object, arg, oc.mode, oc.path);
        free(oc.path);
diff --combined unpack-trees.c
index 6d53cbfc865096ea41028c0601aa12d6eeb8a145,7f9548e741c9f3d8dda13c32f4ec92fb03294b5f..c70e9926e40a530b3da982fc6d10655fc949af03
@@@ -794,6 -794,7 +794,7 @@@ static int traverse_trees_recursive(in
                                    struct name_entry *names,
                                    struct traverse_info *info)
  {
+       struct unpack_trees_options *o = info->data;
        int i, ret, bottom;
        int nr_buf = 0;
        struct tree_desc t[MAX_UNPACK_TREES];
  
        nr_entries = all_trees_same_as_cache_tree(n, dirmask, names, info);
        if (nr_entries > 0) {
-               struct unpack_trees_options *o = info->data;
                int pos = index_pos_by_traverse_info(names, info);
  
                if (!o->merge || df_conflicts)
        }
  
        bottom = switch_cache_bottom(&newinfo);
-       ret = traverse_trees(n, t, &newinfo);
+       ret = traverse_trees(o->src_index, n, t, &newinfo);
        restore_cache_bottom(&newinfo, bottom);
  
        for (i = 0; i < nr_buf; i++)
@@@ -1550,7 -1550,7 +1550,7 @@@ int unpack_trees(unsigned len, struct t
                }
  
                trace_performance_enter();
-               ret = traverse_trees(len, t, &info);
+               ret = traverse_trees(o->src_index, len, t, &info);
                trace_performance_leave("traverse_trees");
                if (ret < 0)
                        goto return_failed;
                move_index_extensions(&o->result, o->src_index);
                if (!ret) {
                        if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
 -                              cache_tree_verify(&o->result);
 +                              cache_tree_verify(the_repository, &o->result);
                        if (!o->result.cache_tree)
                                o->result.cache_tree = cache_tree();
                        if (!cache_tree_fully_valid(o->result.cache_tree))