Merge branch 'en/abort-df-conflict-fixes'
authorJunio C Hamano <gitster@pobox.com>
Fri, 17 Aug 2018 20:09:57 +0000 (13:09 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 17 Aug 2018 20:09:57 +0000 (13:09 -0700)
"git merge --abort" etc. did not clean things up properly when
there were conflicted entries in the index in certain order that
are involved in D/F conflicts. This has been corrected.

* en/abort-df-conflict-fixes:
read-cache: fix directory/file conflict handling in read_index_unmerged()
t1015: demonstrate directory/file conflict recovery failures

1  2 
read-cache.c
t/t6042-merge-rename-corner-cases.sh
diff --combined read-cache.c
index 880849fc8ad8454a756ac0849dff288ad8f38eaa,666d295a5adbd0a23c5e4954570fb2b5e58cec2b..c5fabc844aad68f60a30d0ff7498ff7e5d692a7a
@@@ -6,14 -6,11 +6,14 @@@
  #define NO_THE_INDEX_COMPATIBILITY_MACROS
  #include "cache.h"
  #include "config.h"
 +#include "diff.h"
 +#include "diffcore.h"
  #include "tempfile.h"
  #include "lockfile.h"
  #include "cache-tree.h"
  #include "refs.h"
  #include "dir.h"
 +#include "object-store.h"
  #include "tree.h"
  #include "commit.h"
  #include "blob.h"
                 CE_ENTRY_ADDED | CE_ENTRY_REMOVED | CE_ENTRY_CHANGED | \
                 SPLIT_INDEX_ORDERED | UNTRACKED_CHANGED | FSMONITOR_CHANGED)
  
 +
 +/*
 + * This is an estimate of the pathname length in the index.  We use
 + * this for V4 index files to guess the un-deltafied size of the index
 + * in memory because of pathname deltafication.  This is not required
 + * for V2/V3 index formats because their pathnames are not compressed.
 + * If the initial amount of memory set aside is not sufficient, the
 + * mem pool will allocate extra memory.
 + */
 +#define CACHE_ENTRY_PATH_LENGTH 80
 +
 +static inline struct cache_entry *mem_pool__ce_alloc(struct mem_pool *mem_pool, size_t len)
 +{
 +      struct cache_entry *ce;
 +      ce = mem_pool_alloc(mem_pool, cache_entry_size(len));
 +      ce->mem_pool_allocated = 1;
 +      return ce;
 +}
 +
 +static inline struct cache_entry *mem_pool__ce_calloc(struct mem_pool *mem_pool, size_t len)
 +{
 +      struct cache_entry * ce;
 +      ce = mem_pool_calloc(mem_pool, 1, cache_entry_size(len));
 +      ce->mem_pool_allocated = 1;
 +      return ce;
 +}
 +
 +static struct mem_pool *find_mem_pool(struct index_state *istate)
 +{
 +      struct mem_pool **pool_ptr;
 +
 +      if (istate->split_index && istate->split_index->base)
 +              pool_ptr = &istate->split_index->base->ce_mem_pool;
 +      else
 +              pool_ptr = &istate->ce_mem_pool;
 +
 +      if (!*pool_ptr)
 +              mem_pool_init(pool_ptr, 0);
 +
 +      return *pool_ptr;
 +}
 +
  struct index_state the_index;
  static const char *alternate_index_output;
  
@@@ -106,7 -61,7 +106,7 @@@ static void replace_index_entry(struct 
  
        replace_index_entry_in_base(istate, old, ce);
        remove_name_hash(istate, old);
 -      free(old);
 +      discard_cache_entry(old);
        ce->ce_flags &= ~CE_HASHED;
        set_index_entry(istate, nr, ce);
        ce->ce_flags |= CE_UPDATE_IN_BASE;
@@@ -119,7 -74,7 +119,7 @@@ void rename_index_entry_at(struct index
        struct cache_entry *old_entry = istate->cache[nr], *new_entry;
        int namelen = strlen(new_name);
  
 -      new_entry = xmalloc(cache_entry_size(namelen));
 +      new_entry = make_empty_cache_entry(istate, namelen);
        copy_cache_entry(new_entry, old_entry);
        new_entry->ce_flags &= ~CE_HASHED;
        new_entry->ce_namelen = namelen;
@@@ -668,7 -623,7 +668,7 @@@ static struct cache_entry *create_alias
  
        /* Ok, create the new entry using the name of the existing alias */
        len = ce_namelen(alias);
 -      new_entry = xcalloc(1, cache_entry_size(len));
 +      new_entry = make_empty_cache_entry(istate, len);
        memcpy(new_entry->name, alias->name, len);
        copy_cache_entry(new_entry, ce);
        save_or_free_index_entry(istate, ce);
@@@ -685,7 -640,7 +685,7 @@@ void set_object_name_for_intent_to_add_
  
  int add_to_index(struct index_state *istate, const char *path, struct stat *st, int flags)
  {
 -      int size, namelen, was_same;
 +      int namelen, was_same;
        mode_t st_mode = st->st_mode;
        struct cache_entry *ce, *alias = NULL;
        unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE|CE_MATCH_RACY_IS_DIRTY;
                while (namelen && path[namelen-1] == '/')
                        namelen--;
        }
 -      size = cache_entry_size(namelen);
 -      ce = xcalloc(1, size);
 +      ce = make_empty_cache_entry(istate, namelen);
        memcpy(ce->name, path, namelen);
        ce->ce_namelen = namelen;
        if (!intent_only)
                                ce_mark_uptodate(alias);
                        alias->ce_flags |= CE_ADDED;
  
 -                      free(ce);
 +                      discard_cache_entry(ce);
                        return 0;
                }
        }
        if (!intent_only) {
                if (index_path(&ce->oid, path, st, newflags)) {
 -                      free(ce);
 +                      discard_cache_entry(ce);
                        return error("unable to index file %s", path);
                }
        } else
                    ce->ce_mode == alias->ce_mode);
  
        if (pretend)
 -              free(ce);
 +              discard_cache_entry(ce);
        else if (add_index_entry(istate, ce, add_option)) {
 -              free(ce);
 +              discard_cache_entry(ce);
                return error("unable to add %s to index", path);
        }
        if (verbose && !was_same)
@@@ -789,25 -745,12 +789,25 @@@ int add_file_to_index(struct index_stat
        return add_to_index(istate, path, &st, flags);
  }
  
 -struct cache_entry *make_cache_entry(unsigned int mode,
 -              const unsigned char *sha1, const char *path, int stage,
 -              unsigned int refresh_options)
 +struct cache_entry *make_empty_cache_entry(struct index_state *istate, size_t len)
 +{
 +      return mem_pool__ce_calloc(find_mem_pool(istate), len);
 +}
 +
 +struct cache_entry *make_empty_transient_cache_entry(size_t len)
 +{
 +      return xcalloc(1, cache_entry_size(len));
 +}
 +
 +struct cache_entry *make_cache_entry(struct index_state *istate,
 +                                   unsigned int mode,
 +                                   const struct object_id *oid,
 +                                   const char *path,
 +                                   int stage,
 +                                   unsigned int refresh_options)
  {
 -      int size, len;
        struct cache_entry *ce, *ret;
 +      int len;
  
        if (!verify_path(path, mode)) {
                error("Invalid path '%s'", path);
        }
  
        len = strlen(path);
 -      size = cache_entry_size(len);
 -      ce = xcalloc(1, size);
 +      ce = make_empty_cache_entry(istate, len);
  
 -      hashcpy(ce->oid.hash, sha1);
 +      oidcpy(&ce->oid, oid);
        memcpy(ce->name, path, len);
        ce->ce_flags = create_ce_flags(stage);
        ce->ce_namelen = len;
        ce->ce_mode = create_ce_mode(mode);
  
 -      ret = refresh_cache_entry(ce, refresh_options);
 +      ret = refresh_cache_entry(&the_index, ce, refresh_options);
        if (ret != ce)
 -              free(ce);
 +              discard_cache_entry(ce);
        return ret;
  }
  
 +struct cache_entry *make_transient_cache_entry(unsigned int mode, const struct object_id *oid,
 +                                             const char *path, int stage)
 +{
 +      struct cache_entry *ce;
 +      int len;
 +
 +      if (!verify_path(path, mode)) {
 +              error("Invalid path '%s'", path);
 +              return NULL;
 +      }
 +
 +      len = strlen(path);
 +      ce = make_empty_transient_cache_entry(len);
 +
 +      oidcpy(&ce->oid, oid);
 +      memcpy(ce->name, path, len);
 +      ce->ce_flags = create_ce_flags(stage);
 +      ce->ce_namelen = len;
 +      ce->ce_mode = create_ce_mode(mode);
 +
 +      return ce;
 +}
 +
  /*
   * Chmod an index entry with either +x or -x.
   *
@@@ -1347,7 -1268,7 +1347,7 @@@ static struct cache_entry *refresh_cach
  {
        struct stat st;
        struct cache_entry *updated;
 -      int changed, size;
 +      int changed;
        int refresh = options & CE_MATCH_REFRESH;
        int ignore_valid = options & CE_MATCH_IGNORE_VALID;
        int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
                return NULL;
        }
  
 -      size = ce_size(ce);
 -      updated = xmalloc(size);
 +      updated = make_empty_cache_entry(istate, ce_namelen(ce));
        copy_cache_entry(updated, ce);
        memcpy(updated->name, ce->name, ce->ce_namelen + 1);
        fill_stat_cache_info(updated, &st);
@@@ -1551,11 -1473,10 +1551,11 @@@ int refresh_index(struct index_state *i
        return has_errors;
  }
  
 -struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
 -                                             unsigned int options)
 +struct cache_entry *refresh_cache_entry(struct index_state *istate,
 +                                      struct cache_entry *ce,
 +                                      unsigned int options)
  {
 -      return refresh_cache_ent(&the_index, ce, options, NULL, NULL);
 +      return refresh_cache_ent(istate, ce, options, NULL, NULL);
  }
  
  
@@@ -1713,13 -1634,12 +1713,13 @@@ int read_index(struct index_state *ista
        return read_index_from(istate, get_index_file(), get_git_dir());
  }
  
 -static struct cache_entry *cache_entry_from_ondisk(struct ondisk_cache_entry *ondisk,
 +static struct cache_entry *cache_entry_from_ondisk(struct mem_pool *mem_pool,
 +                                                 struct ondisk_cache_entry *ondisk,
                                                   unsigned int flags,
                                                   const char *name,
                                                   size_t len)
  {
 -      struct cache_entry *ce = xmalloc(cache_entry_size(len));
 +      struct cache_entry *ce = mem_pool__ce_alloc(mem_pool, len);
  
        ce->ce_stat_data.sd_ctime.sec = get_be32(&ondisk->ctime.sec);
        ce->ce_stat_data.sd_mtime.sec = get_be32(&ondisk->mtime.sec);
@@@ -1761,8 -1681,7 +1761,8 @@@ static unsigned long expand_name_field(
        return (const char *)ep + 1 - cp_;
  }
  
 -static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk,
 +static struct cache_entry *create_from_disk(struct mem_pool *mem_pool,
 +                                          struct ondisk_cache_entry *ondisk,
                                            unsigned long *ent_size,
                                            struct strbuf *previous_name)
  {
                /* v3 and earlier */
                if (len == CE_NAMEMASK)
                        len = strlen(name);
 -              ce = cache_entry_from_ondisk(ondisk, flags, name, len);
 +              ce = cache_entry_from_ondisk(mem_pool, ondisk, flags, name, len);
  
                *ent_size = ondisk_ce_size(ce);
        } else {
                unsigned long consumed;
                consumed = expand_name_field(previous_name, name);
 -              ce = cache_entry_from_ondisk(ondisk, flags,
 +              ce = cache_entry_from_ondisk(mem_pool, ondisk, flags,
                                             previous_name->buf,
                                             previous_name->len);
  
@@@ -1873,22 -1792,6 +1873,22 @@@ static void post_read_index_from(struc
        tweak_fsmonitor(istate);
  }
  
 +static size_t estimate_cache_size_from_compressed(unsigned int entries)
 +{
 +      return entries * (sizeof(struct cache_entry) + CACHE_ENTRY_PATH_LENGTH);
 +}
 +
 +static size_t estimate_cache_size(size_t ondisk_size, unsigned int entries)
 +{
 +      long per_entry = sizeof(struct cache_entry) - sizeof(struct ondisk_cache_entry);
 +
 +      /*
 +       * Account for potential alignment differences.
 +       */
 +      per_entry += align_padding_size(sizeof(struct cache_entry), -sizeof(struct ondisk_cache_entry));
 +      return ondisk_size + entries * per_entry;
 +}
 +
  /* remember to discard_cache() before reading a different cache! */
  int do_read_index(struct index_state *istate, const char *path, int must_exist)
  {
        istate->cache = xcalloc(istate->cache_alloc, sizeof(*istate->cache));
        istate->initialized = 1;
  
 -      if (istate->version == 4)
 +      if (istate->version == 4) {
                previous_name = &previous_name_buf;
 -      else
 +              mem_pool_init(&istate->ce_mem_pool,
 +                            estimate_cache_size_from_compressed(istate->cache_nr));
 +      } else {
                previous_name = NULL;
 +              mem_pool_init(&istate->ce_mem_pool,
 +                            estimate_cache_size(mmap_size, istate->cache_nr));
 +      }
  
        src_offset = sizeof(*hdr);
        for (i = 0; i < istate->cache_nr; i++) {
                unsigned long consumed;
  
                disk_ce = (struct ondisk_cache_entry *)((char *)mmap + src_offset);
 -              ce = create_from_disk(disk_ce, &consumed, previous_name);
 +              ce = create_from_disk(istate->ce_mem_pool, disk_ce, &consumed, previous_name);
                set_index_entry(istate, i, ce);
  
                src_offset += consumed;
@@@ -2049,15 -1947,17 +2049,15 @@@ int is_index_unborn(struct index_state 
  
  int discard_index(struct index_state *istate)
  {
 -      int i;
 +      /*
 +       * Cache entries in istate->cache[] should have been allocated
 +       * from the memory pool associated with this index, or from an
 +       * associated split_index. There is no need to free individual
 +       * cache entries. validate_cache_entries can detect when this
 +       * assertion does not hold.
 +       */
 +      validate_cache_entries(istate);
  
 -      for (i = 0; i < istate->cache_nr; i++) {
 -              if (istate->cache[i]->index &&
 -                  istate->split_index &&
 -                  istate->split_index->base &&
 -                  istate->cache[i]->index <= istate->split_index->base->cache_nr &&
 -                  istate->cache[i] == istate->split_index->base->cache[istate->cache[i]->index - 1])
 -                      continue;
 -              free(istate->cache[i]);
 -      }
        resolve_undo_clear_index(istate);
        istate->cache_nr = 0;
        istate->cache_changed = 0;
        discard_split_index(istate);
        free_untracked_cache(istate->untracked);
        istate->untracked = NULL;
 +
 +      if (istate->ce_mem_pool) {
 +              mem_pool_discard(istate->ce_mem_pool, should_validate_cache_entries());
 +              istate->ce_mem_pool = NULL;
 +      }
 +
        return 0;
  }
  
 +/*
 + * Validate the cache entries of this index.
 + * All cache entries associated with this index
 + * should have been allocated by the memory pool
 + * associated with this index, or by a referenced
 + * split index.
 + */
 +void validate_cache_entries(const struct index_state *istate)
 +{
 +      int i;
 +
 +      if (!should_validate_cache_entries() ||!istate || !istate->initialized)
 +              return;
 +
 +      for (i = 0; i < istate->cache_nr; i++) {
 +              if (!istate) {
 +                      die("internal error: cache entry is not allocated from expected memory pool");
 +              } else if (!istate->ce_mem_pool ||
 +                      !mem_pool_contains(istate->ce_mem_pool, istate->cache[i])) {
 +                      if (!istate->split_index ||
 +                              !istate->split_index->base ||
 +                              !istate->split_index->base->ce_mem_pool ||
 +                              !mem_pool_contains(istate->split_index->base->ce_mem_pool, istate->cache[i])) {
 +                              die("internal error: cache entry is not allocated from expected memory pool");
 +                      }
 +              }
 +      }
 +
 +      if (istate->split_index)
 +              validate_cache_entries(istate->split_index->base);
 +}
 +
  int unmerged_index(const struct index_state *istate)
  {
        int i;
        return 0;
  }
  
 +int index_has_changes(const struct index_state *istate,
 +                    struct tree *tree,
 +                    struct strbuf *sb)
 +{
 +      struct object_id cmp;
 +      int i;
 +
 +      if (istate != &the_index) {
 +              BUG("index_has_changes cannot yet accept istate != &the_index; do_diff_cache needs updating first.");
 +      }
 +      if (tree)
 +              cmp = tree->object.oid;
 +      if (tree || !get_oid_tree("HEAD", &cmp)) {
 +              struct diff_options opt;
 +
 +              diff_setup(&opt);
 +              opt.flags.exit_with_status = 1;
 +              if (!sb)
 +                      opt.flags.quick = 1;
 +              do_diff_cache(&cmp, &opt);
 +              diffcore_std(&opt);
 +              for (i = 0; sb && i < diff_queued_diff.nr; i++) {
 +                      if (i)
 +                              strbuf_addch(sb, ' ');
 +                      strbuf_addstr(sb, diff_queued_diff.queue[i]->two->path);
 +              }
 +              diff_flush(&opt);
 +              return opt.flags.has_changes != 0;
 +      } else {
 +              for (i = 0; sb && i < istate->cache_nr; i++) {
 +                      if (i)
 +                              strbuf_addch(sb, ' ');
 +                      strbuf_addstr(sb, istate->cache[i]->name);
 +              }
 +              return !!istate->cache_nr;
 +      }
 +}
 +
  #define WRITE_BUFFER_SIZE 8192
  static unsigned char write_buffer[WRITE_BUFFER_SIZE];
  static unsigned long write_buffer_len;
  
  /*
   * Read the index file that is potentially unmerged into given
-  * index_state, dropping any unmerged entries.  Returns true if
-  * the index is unmerged.  Callers who want to refuse to work
-  * from an unmerged state can call this and check its return value,
-  * instead of calling read_cache().
+  * index_state, dropping any unmerged entries to stage #0 (potentially
+  * resulting in a path appearing as both a file and a directory in the
+  * index; the caller is responsible to clear out the extra entries
+  * before writing the index to a tree).  Returns true if the index is
+  * unmerged.  Callers who want to refuse to work from an unmerged
+  * state can call this and check its return value, instead of calling
+  * read_cache().
   */
  int read_index_unmerged(struct index_state *istate)
  {
        for (i = 0; i < istate->cache_nr; i++) {
                struct cache_entry *ce = istate->cache[i];
                struct cache_entry *new_ce;
 -              int size, len;
 +              int len;
  
                if (!ce_stage(ce))
                        continue;
                unmerged = 1;
                len = ce_namelen(ce);
 -              size = cache_entry_size(len);
 -              new_ce = xcalloc(1, size);
 +              new_ce = make_empty_cache_entry(istate, len);
                memcpy(new_ce->name, ce->name, len);
                new_ce->ce_flags = create_ce_flags(0) | CE_CONFLICTED;
                new_ce->ce_namelen = len;
                new_ce->ce_mode = ce->ce_mode;
-               if (add_index_entry(istate, new_ce, 0))
+               if (add_index_entry(istate, new_ce, ADD_CACHE_SKIP_DFCHECK))
                        return error("%s: cannot drop to stage #0",
                                     new_ce->name);
        }
@@@ -2937,41 -2765,3 +2940,41 @@@ void move_index_extensions(struct index
        dst->untracked = src->untracked;
        src->untracked = NULL;
  }
 +
 +struct cache_entry *dup_cache_entry(const struct cache_entry *ce,
 +                                  struct index_state *istate)
 +{
 +      unsigned int size = ce_size(ce);
 +      int mem_pool_allocated;
 +      struct cache_entry *new_entry = make_empty_cache_entry(istate, ce_namelen(ce));
 +      mem_pool_allocated = new_entry->mem_pool_allocated;
 +
 +      memcpy(new_entry, ce, size);
 +      new_entry->mem_pool_allocated = mem_pool_allocated;
 +      return new_entry;
 +}
 +
 +void discard_cache_entry(struct cache_entry *ce)
 +{
 +      if (ce && should_validate_cache_entries())
 +              memset(ce, 0xCD, cache_entry_size(ce->ce_namelen));
 +
 +      if (ce && ce->mem_pool_allocated)
 +              return;
 +
 +      free(ce);
 +}
 +
 +int should_validate_cache_entries(void)
 +{
 +      static int validate_index_cache_entries = -1;
 +
 +      if (validate_index_cache_entries < 0) {
 +              if (getenv("GIT_TEST_VALIDATE_INDEX_CACHE_ENTRIES"))
 +                      validate_index_cache_entries = 1;
 +              else
 +                      validate_index_cache_entries = 0;
 +      }
 +
 +      return validate_index_cache_entries;
 +}
index 07dd09d985fad600c6f21d335ccfb30e3626db13,de77bfaf4fa99715977b5f4b8bec7d374791dfa8..b97aca7fa263f79520013b14d90fa0b49031484b
@@@ -6,40 -6,31 +6,40 @@@ test_description="recursive merge corne
  . ./test-lib.sh
  
  test_expect_success 'setup rename/delete + untracked file' '
 -      echo "A pretty inscription" >ring &&
 -      git add ring &&
 -      test_tick &&
 -      git commit -m beginning &&
 -
 -      git branch people &&
 -      git checkout -b rename-the-ring &&
 -      git mv ring one-ring-to-rule-them-all &&
 -      test_tick &&
 -      git commit -m fullname &&
 -
 -      git checkout people &&
 -      git rm ring &&
 -      echo gollum >owner &&
 -      git add owner &&
 -      test_tick &&
 -      git commit -m track-people-instead-of-objects &&
 -      echo "Myyy PRECIOUSSS" >ring
 +      test_create_repo rename-delete-untracked &&
 +      (
 +              cd rename-delete-untracked &&
 +
 +              echo "A pretty inscription" >ring &&
 +              git add ring &&
 +              test_tick &&
 +              git commit -m beginning &&
 +
 +              git branch people &&
 +              git checkout -b rename-the-ring &&
 +              git mv ring one-ring-to-rule-them-all &&
 +              test_tick &&
 +              git commit -m fullname &&
 +
 +              git checkout people &&
 +              git rm ring &&
 +              echo gollum >owner &&
 +              git add owner &&
 +              test_tick &&
 +              git commit -m track-people-instead-of-objects &&
 +              echo "Myyy PRECIOUSSS" >ring
 +      )
  '
  
  test_expect_success "Does git preserve Gollum's precious artifact?" '
 -      test_must_fail git merge -s recursive rename-the-ring &&
 +      (
 +              cd rename-delete-untracked &&
  
 -      # Make sure git did not delete an untracked file
 -      test -f ring
 +              test_must_fail git merge -s recursive rename-the-ring &&
 +
 +              # Make sure git did not delete an untracked file
 +              test_path_is_file ring
 +      )
  '
  
  # Testcase setup for rename/modify/add-source:
  # We should be able to merge B & C cleanly
  
  test_expect_success 'setup rename/modify/add-source conflict' '
 -      git rm -rf . &&
 -      git clean -fdqx &&
 -      rm -rf .git &&
 -      git init &&
 -
 -      printf "1\n2\n3\n4\n5\n6\n7\n" >a &&
 -      git add a &&
 -      git commit -m A &&
 -      git tag A &&
 -
 -      git checkout -b B A &&
 -      echo 8 >>a &&
 -      git add a &&
 -      git commit -m B &&
 -
 -      git checkout -b C A &&
 -      git mv a b &&
 -      echo something completely different >a &&
 -      git add a &&
 -      git commit -m C
 +      test_create_repo rename-modify-add-source &&
 +      (
 +              cd rename-modify-add-source &&
 +
 +              printf "1\n2\n3\n4\n5\n6\n7\n" >a &&
 +              git add a &&
 +              git commit -m A &&
 +              git tag A &&
 +
 +              git checkout -b B A &&
 +              echo 8 >>a &&
 +              git add a &&
 +              git commit -m B &&
 +
 +              git checkout -b C A &&
 +              git mv a b &&
 +              echo something completely different >a &&
 +              git add a &&
 +              git commit -m C
 +      )
  '
  
  test_expect_failure 'rename/modify/add-source conflict resolvable' '
 -      git checkout B^0 &&
 +      (
 +              cd rename-modify-add-source &&
  
 -      git merge -s recursive C^0 &&
 +              git checkout B^0 &&
  
 -      test $(git rev-parse B:a) = $(git rev-parse b) &&
 -      test $(git rev-parse C:a) = $(git rev-parse a)
 +              git merge -s recursive C^0 &&
 +
 +              git rev-parse >expect \
 +                      B:a   C:a     &&
 +              git rev-parse >actual \
 +                      b     c       &&
 +              test_cmp expect actual
 +      )
  '
  
  test_expect_success 'setup resolvable conflict missed if rename missed' '
 -      git rm -rf . &&
 -      git clean -fdqx &&
 -      rm -rf .git &&
 -      git init &&
 -
 -      printf "1\n2\n3\n4\n5\n" >a &&
 -      echo foo >b &&
 -      git add a b &&
 -      git commit -m A &&
 -      git tag A &&
 -
 -      git checkout -b B A &&
 -      git mv a c &&
 -      echo "Completely different content" >a &&
 -      git add a &&
 -      git commit -m B &&
 -
 -      git checkout -b C A &&
 -      echo 6 >>a &&
 -      git add a &&
 -      git commit -m C
 +      test_create_repo break-detection-1 &&
 +      (
 +              cd break-detection-1 &&
 +
 +              printf "1\n2\n3\n4\n5\n" >a &&
 +              echo foo >b &&
 +              git add a b &&
 +              git commit -m A &&
 +              git tag A &&
 +
 +              git checkout -b B A &&
 +              git mv a c &&
 +              echo "Completely different content" >a &&
 +              git add a &&
 +              git commit -m B &&
 +
 +              git checkout -b C A &&
 +              echo 6 >>a &&
 +              git add a &&
 +              git commit -m C
 +      )
  '
  
  test_expect_failure 'conflict caused if rename not detected' '
 -      git checkout -q C^0 &&
 -      git merge -s recursive B^0 &&
 -
 -      test 3 -eq $(git ls-files -s | wc -l) &&
 -      test 0 -eq $(git ls-files -u | wc -l) &&
 -      test 0 -eq $(git ls-files -o | wc -l) &&
 -
 -      test_line_count = 6 c &&
 -      test $(git rev-parse HEAD:a) = $(git rev-parse B:a) &&
 -      test $(git rev-parse HEAD:b) = $(git rev-parse A:b)
 +      (
 +              cd break-detection-1 &&
 +
 +              git checkout -q C^0 &&
 +              git merge -s recursive B^0 &&
 +
 +              git ls-files -s >out &&
 +              test_line_count = 3 out &&
 +              git ls-files -u >out &&
 +              test_line_count = 0 out &&
 +              git ls-files -o >out &&
 +              test_line_count = 1 out &&
 +
 +              test_line_count = 6 c &&
 +              git rev-parse >expect \
 +                      B:a   A:b     &&
 +              git rev-parse >actual \
 +                      :0:a  :0:b    &&
 +              test_cmp expect actual
 +      )
  '
  
  test_expect_success 'setup conflict resolved wrong if rename missed' '
 -      git reset --hard &&
 -      git clean -f &&
 -
 -      git checkout -b D A &&
 -      echo 7 >>a &&
 -      git add a &&
 -      git mv a c &&
 -      echo "Completely different content" >a &&
 -      git add a &&
 -      git commit -m D &&
 -
 -      git checkout -b E A &&
 -      git rm a &&
 -      echo "Completely different content" >>a &&
 -      git add a &&
 -      git commit -m E
 +      test_create_repo break-detection-2 &&
 +      (
 +              cd break-detection-2 &&
 +
 +              printf "1\n2\n3\n4\n5\n" >a &&
 +              echo foo >b &&
 +              git add a b &&
 +              git commit -m A &&
 +              git tag A &&
 +
 +              git checkout -b D A &&
 +              echo 7 >>a &&
 +              git add a &&
 +              git mv a c &&
 +              echo "Completely different content" >a &&
 +              git add a &&
 +              git commit -m D &&
 +
 +              git checkout -b E A &&
 +              git rm a &&
 +              echo "Completely different content" >>a &&
 +              git add a &&
 +              git commit -m E
 +      )
  '
  
  test_expect_failure 'missed conflict if rename not detected' '
 -      git checkout -q E^0 &&
 -      test_must_fail git merge -s recursive D^0
 +      (
 +              cd break-detection-2 &&
 +
 +              git checkout -q E^0 &&
 +              test_must_fail git merge -s recursive D^0
 +      )
  '
  
  # Tests for undetected rename/add-source causing a file to erroneously be
  #   Commit C: rename a->b, add unrelated a
  
  test_expect_success 'setup undetected rename/add-source causes data loss' '
 -      git rm -rf . &&
 -      git clean -fdqx &&
 -      rm -rf .git &&
 -      git init &&
 -
 -      printf "1\n2\n3\n4\n5\n" >a &&
 -      git add a &&
 -      git commit -m A &&
 -      git tag A &&
 -
 -      git checkout -b B A &&
 -      git mv a b &&
 -      git commit -m B &&
 -
 -      git checkout -b C A &&
 -      git mv a b &&
 -      echo foobar >a &&
 -      git add a &&
 -      git commit -m C
 +      test_create_repo break-detection-3 &&
 +      (
 +              cd break-detection-3 &&
 +
 +              printf "1\n2\n3\n4\n5\n" >a &&
 +              git add a &&
 +              git commit -m A &&
 +              git tag A &&
 +
 +              git checkout -b B A &&
 +              git mv a b &&
 +              git commit -m B &&
 +
 +              git checkout -b C A &&
 +              git mv a b &&
 +              echo foobar >a &&
 +              git add a &&
 +              git commit -m C
 +      )
  '
  
  test_expect_failure 'detect rename/add-source and preserve all data' '
 -      git checkout B^0 &&
 +      (
 +              cd break-detection-3 &&
  
 -      git merge -s recursive C^0 &&
 +              git checkout B^0 &&
  
 -      test 2 -eq $(git ls-files -s | wc -l) &&
 -      test 2 -eq $(git ls-files -u | wc -l) &&
 -      test 0 -eq $(git ls-files -o | wc -l) &&
 +              git merge -s recursive C^0 &&
  
 -      test -f a &&
 -      test -f b &&
 +              git ls-files -s >out &&
 +              test_line_count = 2 out &&
 +              git ls-files -u >out &&
 +              test_line_count = 2 out &&
 +              git ls-files -o >out &&
 +              test_line_count = 1 out &&
  
 -      test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
 -      test $(git rev-parse HEAD:a) = $(git rev-parse C:a)
 +              test_path_is_file a &&
 +              test_path_is_file b &&
 +
 +              git rev-parse >expect \
 +                      A:a   C:a     &&
 +              git rev-parse >actual \
 +                      :0:b  :0:a    &&
 +              test_cmp expect actual
 +      )
  '
  
  test_expect_failure 'detect rename/add-source and preserve all data, merge other way' '
 -      git checkout C^0 &&
 +      (
 +              cd break-detection-3 &&
 +
 +              git checkout C^0 &&
  
 -      git merge -s recursive B^0 &&
 +              git merge -s recursive B^0 &&
  
 -      test 2 -eq $(git ls-files -s | wc -l) &&
 -      test 2 -eq $(git ls-files -u | wc -l) &&
 -      test 0 -eq $(git ls-files -o | wc -l) &&
 +              git ls-files -s >out &&
 +              test_line_count = 2 out &&
 +              git ls-files -u >out &&
 +              test_line_count = 2 out &&
 +              git ls-files -o >out &&
 +              test_line_count = 1 out &&
  
 -      test -f a &&
 -      test -f b &&
 +              test_path_is_file a &&
 +              test_path_is_file b &&
  
 -      test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
 -      test $(git rev-parse HEAD:a) = $(git rev-parse C:a)
 +              git rev-parse >expect \
 +                      A:a   C:a     &&
 +              git rev-parse >actual \
 +                      :0:b  :0:a    &&
 +              test_cmp expect actual
 +      )
  '
  
  test_expect_success 'setup content merge + rename/directory conflict' '
 -      git rm -rf . &&
 -      git clean -fdqx &&
 -      rm -rf .git &&
 -      git init &&
 -
 -      printf "1\n2\n3\n4\n5\n6\n" >file &&
 -      git add file &&
 -      test_tick &&
 -      git commit -m base &&
 -      git tag base &&
 -
 -      git checkout -b right &&
 -      echo 7 >>file &&
 -      mkdir newfile &&
 -      echo junk >newfile/realfile &&
 -      git add file newfile/realfile &&
 -      test_tick &&
 -      git commit -m right &&
 -
 -      git checkout -b left-conflict base &&
 -      echo 8 >>file &&
 -      git add file &&
 -      git mv file newfile &&
 -      test_tick &&
 -      git commit -m left &&
 -
 -      git checkout -b left-clean base &&
 -      echo 0 >newfile &&
 -      cat file >>newfile &&
 -      git add newfile &&
 -      git rm file &&
 -      test_tick &&
 -      git commit -m left
 +      test_create_repo rename-directory-1 &&
 +      (
 +              cd rename-directory-1 &&
 +
 +              printf "1\n2\n3\n4\n5\n6\n" >file &&
 +              git add file &&
 +              test_tick &&
 +              git commit -m base &&
 +              git tag base &&
 +
 +              git checkout -b right &&
 +              echo 7 >>file &&
 +              mkdir newfile &&
 +              echo junk >newfile/realfile &&
 +              git add file newfile/realfile &&
 +              test_tick &&
 +              git commit -m right &&
 +
 +              git checkout -b left-conflict base &&
 +              echo 8 >>file &&
 +              git add file &&
 +              git mv file newfile &&
 +              test_tick &&
 +              git commit -m left &&
 +
 +              git checkout -b left-clean base &&
 +              echo 0 >newfile &&
 +              cat file >>newfile &&
 +              git add newfile &&
 +              git rm file &&
 +              test_tick &&
 +              git commit -m left
 +      )
  '
  
  test_expect_success 'rename/directory conflict + clean content merge' '
 -      git reset --hard &&
 -      git clean -fdqx &&
 +      (
 +              cd rename-directory-1 &&
  
 -      git checkout left-clean^0 &&
 +              git checkout left-clean^0 &&
  
 -      test_must_fail git merge -s recursive right^0 &&
 +              test_must_fail git merge -s recursive right^0 &&
  
 -      test 2 -eq $(git ls-files -s | wc -l) &&
 -      test 1 -eq $(git ls-files -u | wc -l) &&
 -      test 1 -eq $(git ls-files -o | wc -l) &&
 +              git ls-files -s >out &&
 +              test_line_count = 2 out &&
 +              git ls-files -u >out &&
 +              test_line_count = 1 out &&
 +              git ls-files -o >out &&
 +              test_line_count = 2 out &&
  
 -      echo 0 >expect &&
 -      git cat-file -p base:file >>expect &&
 -      echo 7 >>expect &&
 -      test_cmp expect newfile~HEAD &&
 +              echo 0 >expect &&
 +              git cat-file -p base:file >>expect &&
 +              echo 7 >>expect &&
 +              test_cmp expect newfile~HEAD &&
  
 -      test $(git rev-parse :2:newfile) = $(git hash-object expect) &&
 +              test $(git rev-parse :2:newfile) = $(git hash-object expect) &&
  
 -      test -f newfile/realfile &&
 -      test -f newfile~HEAD
 +              test_path_is_file newfile/realfile &&
 +              test_path_is_file newfile~HEAD
 +      )
  '
  
  test_expect_success 'rename/directory conflict + content merge conflict' '
 -      git reset --hard &&
 -      git clean -fdqx &&
 -
 -      git checkout left-conflict^0 &&
 -
 -      test_must_fail git merge -s recursive right^0 &&
 -
 -      test 4 -eq $(git ls-files -s | wc -l) &&
 -      test 3 -eq $(git ls-files -u | wc -l) &&
 -      test 1 -eq $(git ls-files -o | wc -l) &&
 -
 -      git cat-file -p left-conflict:newfile >left &&
 -      git cat-file -p base:file    >base &&
 -      git cat-file -p right:file   >right &&
 -      test_must_fail git merge-file \
 -              -L "HEAD:newfile" \
 -              -L "" \
 -              -L "right^0:file" \
 -              left base right &&
 -      test_cmp left newfile~HEAD &&
 -
 -      test $(git rev-parse :1:newfile) = $(git rev-parse base:file) &&
 -      test $(git rev-parse :2:newfile) = $(git rev-parse left-conflict:newfile) &&
 -      test $(git rev-parse :3:newfile) = $(git rev-parse right:file) &&
 -
 -      test -f newfile/realfile &&
 -      test -f newfile~HEAD
 +      (
 +              cd rename-directory-1 &&
 +
-               git reset --hard &&
 +              git reset --hard &&
 +              git clean -fdqx &&
 +
 +              git checkout left-conflict^0 &&
 +
 +              test_must_fail git merge -s recursive right^0 &&
 +
 +              git ls-files -s >out &&
 +              test_line_count = 4 out &&
 +              git ls-files -u >out &&
 +              test_line_count = 3 out &&
 +              git ls-files -o >out &&
 +              test_line_count = 2 out &&
 +
 +              git cat-file -p left-conflict:newfile >left &&
 +              git cat-file -p base:file    >base &&
 +              git cat-file -p right:file   >right &&
 +              test_must_fail git merge-file \
 +                      -L "HEAD:newfile" \
 +                      -L "" \
 +                      -L "right^0:file" \
 +                      left base right &&
 +              test_cmp left newfile~HEAD &&
 +
 +              git rev-parse >expect                                 \
 +                      base:file   left-conflict:newfile  right:file &&
 +              git rev-parse >actual                                 \
 +                      :1:newfile  :2:newfile             :3:newfile &&
 +              test_cmp expect actual &&
 +
 +              test_path_is_file newfile/realfile &&
 +              test_path_is_file newfile~HEAD
 +      )
  '
  
  test_expect_success 'setup content merge + rename/directory conflict w/ disappearing dir' '
 -      git reset --hard &&
 -      git rm -rf . &&
 -      git clean -fdqx &&
 -      rm -rf .git &&
 -      git init &&
 -
 -      mkdir sub &&
 -      printf "1\n2\n3\n4\n5\n6\n" >sub/file &&
 -      git add sub/file &&
 -      test_tick &&
 -      git commit -m base &&
 -      git tag base &&
 -
 -      git checkout -b right &&
 -      echo 7 >>sub/file &&
 -      git add sub/file &&
 -      test_tick &&
 -      git commit -m right &&
 -
 -      git checkout -b left base &&
 -      echo 0 >newfile &&
 -      cat sub/file >>newfile &&
 -      git rm sub/file &&
 -      mv newfile sub &&
 -      git add sub &&
 -      test_tick &&
 -      git commit -m left
 +      test_create_repo rename-directory-2 &&
 +      (
 +              cd rename-directory-2 &&
 +
 +              mkdir sub &&
 +              printf "1\n2\n3\n4\n5\n6\n" >sub/file &&
 +              git add sub/file &&
 +              test_tick &&
 +              git commit -m base &&
 +              git tag base &&
 +
 +              git checkout -b right &&
 +              echo 7 >>sub/file &&
 +              git add sub/file &&
 +              test_tick &&
 +              git commit -m right &&
 +
 +              git checkout -b left base &&
 +              echo 0 >newfile &&
 +              cat sub/file >>newfile &&
 +              git rm sub/file &&
 +              mv newfile sub &&
 +              git add sub &&
 +              test_tick &&
 +              git commit -m left
 +      )
  '
  
  test_expect_success 'disappearing dir in rename/directory conflict handled' '
 -      git reset --hard &&
 -      git clean -fdqx &&
 +      (
 +              cd rename-directory-2 &&
  
 -      git checkout left^0 &&
 +              git checkout left^0 &&
  
 -      git merge -s recursive right^0 &&
 +              git merge -s recursive right^0 &&
  
 -      test 1 -eq $(git ls-files -s | wc -l) &&
 -      test 0 -eq $(git ls-files -u | wc -l) &&
 -      test 0 -eq $(git ls-files -o | wc -l) &&
 +              git ls-files -s >out &&
 +              test_line_count = 1 out &&
 +              git ls-files -u >out &&
 +              test_line_count = 0 out &&
 +              git ls-files -o >out &&
 +              test_line_count = 1 out &&
  
 -      echo 0 >expect &&
 -      git cat-file -p base:sub/file >>expect &&
 -      echo 7 >>expect &&
 -      test_cmp expect sub &&
 +              echo 0 >expect &&
 +              git cat-file -p base:sub/file >>expect &&
 +              echo 7 >>expect &&
 +              test_cmp expect sub &&
  
 -      test -f sub
 +              test_path_is_file sub
 +      )
  '
  
  # Test for all kinds of things that can go wrong with rename/rename (2to1):
  #   * Nothing else should be present.  Is anything?
  
  test_expect_success 'setup rename/rename (2to1) + modify/modify' '
 -      git rm -rf . &&
 -      git clean -fdqx &&
 -      rm -rf .git &&
 -      git init &&
 -
 -      printf "1\n2\n3\n4\n5\n" >a &&
 -      printf "5\n4\n3\n2\n1\n" >b &&
 -      git add a b &&
 -      git commit -m A &&
 -      git tag A &&
 -
 -      git checkout -b B A &&
 -      git mv a c &&
 -      echo 0 >>b &&
 -      git add b &&
 -      git commit -m B &&
 -
 -      git checkout -b C A &&
 -      git mv b c &&
 -      echo 6 >>a &&
 -      git add a &&
 -      git commit -m C
 +      test_create_repo rename-rename-2to1 &&
 +      (
 +              cd rename-rename-2to1 &&
 +
 +              printf "1\n2\n3\n4\n5\n" >a &&
 +              printf "5\n4\n3\n2\n1\n" >b &&
 +              git add a b &&
 +              git commit -m A &&
 +              git tag A &&
 +
 +              git checkout -b B A &&
 +              git mv a c &&
 +              echo 0 >>b &&
 +              git add b &&
 +              git commit -m B &&
 +
 +              git checkout -b C A &&
 +              git mv b c &&
 +              echo 6 >>a &&
 +              git add a &&
 +              git commit -m C
 +      )
  '
  
  test_expect_success 'handle rename/rename (2to1) conflict correctly' '
 -      git checkout B^0 &&
 -
 -      test_must_fail git merge -s recursive C^0 >out &&
 -      test_i18ngrep "CONFLICT (rename/rename)" out &&
 -
 -      test 2 -eq $(git ls-files -s | wc -l) &&
 -      test 2 -eq $(git ls-files -u | wc -l) &&
 -      test 2 -eq $(git ls-files -u c | wc -l) &&
 -      test 3 -eq $(git ls-files -o | wc -l) &&
 -
 -      test ! -f a &&
 -      test ! -f b &&
 -      test -f c~HEAD &&
 -      test -f c~C^0 &&
 -
 -      test $(git hash-object c~HEAD) = $(git rev-parse C:a) &&
 -      test $(git hash-object c~C^0) = $(git rev-parse B:b)
 +      (
 +              cd rename-rename-2to1 &&
 +
 +              git checkout B^0 &&
 +
 +              test_must_fail git merge -s recursive C^0 >out &&
 +              test_i18ngrep "CONFLICT (rename/rename)" out &&
 +
 +              git ls-files -s >out &&
 +              test_line_count = 2 out &&
 +              git ls-files -u >out &&
 +              test_line_count = 2 out &&
 +              git ls-files -u c >out &&
 +              test_line_count = 2 out &&
 +              git ls-files -o >out &&
 +              test_line_count = 3 out &&
 +
 +              test_path_is_missing a &&
 +              test_path_is_missing b &&
 +              test_path_is_file c~HEAD &&
 +              test_path_is_file c~C^0 &&
 +
 +              git rev-parse >expect   \
 +                      C:a     B:b     &&
 +              git hash-object >actual \
 +                      c~HEAD  c~C^0   &&
 +              test_cmp expect actual
 +      )
  '
  
  # Testcase setup for simple rename/rename (1to2) conflict:
  #   Commit B: rename a->b
  #   Commit C: rename a->c
  test_expect_success 'setup simple rename/rename (1to2) conflict' '
 -      git rm -rf . &&
 -      git clean -fdqx &&
 -      rm -rf .git &&
 -      git init &&
 -
 -      echo stuff >a &&
 -      git add a &&
 -      test_tick &&
 -      git commit -m A &&
 -      git tag A &&
 -
 -      git checkout -b B A &&
 -      git mv a b &&
 -      test_tick &&
 -      git commit -m B &&
 -
 -      git checkout -b C A &&
 -      git mv a c &&
 -      test_tick &&
 -      git commit -m C
 +      test_create_repo rename-rename-1to2 &&
 +      (
 +              cd rename-rename-1to2 &&
 +
 +              echo stuff >a &&
 +              git add a &&
 +              test_tick &&
 +              git commit -m A &&
 +              git tag A &&
 +
 +              git checkout -b B A &&
 +              git mv a b &&
 +              test_tick &&
 +              git commit -m B &&
 +
 +              git checkout -b C A &&
 +              git mv a c &&
 +              test_tick &&
 +              git commit -m C
 +      )
  '
  
  test_expect_success 'merge has correct working tree contents' '
 -      git checkout C^0 &&
 -
 -      test_must_fail git merge -s recursive B^0 &&
 -
 -      test 3 -eq $(git ls-files -s | wc -l) &&
 -      test 3 -eq $(git ls-files -u | wc -l) &&
 -      test 0 -eq $(git ls-files -o | wc -l) &&
 -
 -      test $(git rev-parse :1:a) = $(git rev-parse A:a) &&
 -      test $(git rev-parse :3:b) = $(git rev-parse A:a) &&
 -      test $(git rev-parse :2:c) = $(git rev-parse A:a) &&
 -
 -      test ! -f a &&
 -      test $(git hash-object b) = $(git rev-parse A:a) &&
 -      test $(git hash-object c) = $(git rev-parse A:a)
 +      (
 +              cd rename-rename-1to2 &&
 +
 +              git checkout C^0 &&
 +
 +              test_must_fail git merge -s recursive B^0 &&
 +
 +              git ls-files -s >out &&
 +              test_line_count = 3 out &&
 +              git ls-files -u >out &&
 +              test_line_count = 3 out &&
 +              git ls-files -o >out &&
 +              test_line_count = 1 out &&
 +
 +              test_path_is_missing a &&
 +              git rev-parse >expect   \
 +                      A:a   A:a   A:a \
 +                      A:a   A:a       &&
 +              git rev-parse >actual    \
 +                      :1:a  :3:b  :2:c &&
 +              git hash-object >>actual \
 +                      b     c          &&
 +              test_cmp expect actual
 +      )
  '
  
  # Testcase setup for rename/rename(1to2)/add-source conflict:
  # Merging of B & C should NOT be clean; there's a rename/rename conflict
  
  test_expect_success 'setup rename/rename(1to2)/add-source conflict' '
 -      git rm -rf . &&
 -      git clean -fdqx &&
 -      rm -rf .git &&
 -      git init &&
 -
 -      printf "1\n2\n3\n4\n5\n6\n7\n" >a &&
 -      git add a &&
 -      git commit -m A &&
 -      git tag A &&
 -
 -      git checkout -b B A &&
 -      git mv a b &&
 -      git commit -m B &&
 -
 -      git checkout -b C A &&
 -      git mv a c &&
 -      echo something completely different >a &&
 -      git add a &&
 -      git commit -m C
 +      test_create_repo rename-rename-1to2-add-source-1 &&
 +      (
 +              cd rename-rename-1to2-add-source-1 &&
 +
 +              printf "1\n2\n3\n4\n5\n6\n7\n" >a &&
 +              git add a &&
 +              git commit -m A &&
 +              git tag A &&
 +
 +              git checkout -b B A &&
 +              git mv a b &&
 +              git commit -m B &&
 +
 +              git checkout -b C A &&
 +              git mv a c &&
 +              echo something completely different >a &&
 +              git add a &&
 +              git commit -m C
 +      )
  '
  
  test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' '
 -      git checkout B^0 &&
 +      (
 +              cd rename-rename-1to2-add-source-1 &&
 +
 +              git checkout B^0 &&
  
 -      test_must_fail git merge -s recursive C^0 &&
 +              test_must_fail git merge -s recursive C^0 &&
  
 -      test 4 -eq $(git ls-files -s | wc -l) &&
 -      test 0 -eq $(git ls-files -o | wc -l) &&
 +              git ls-files -s >out &&
 +              test_line_count = 4 out &&
 +              git ls-files -o >out &&
 +              test_line_count = 1 out &&
  
 -      test $(git rev-parse 3:a) = $(git rev-parse C:a) &&
 -      test $(git rev-parse 1:a) = $(git rev-parse A:a) &&
 -      test $(git rev-parse 2:b) = $(git rev-parse B:b) &&
 -      test $(git rev-parse 3:c) = $(git rev-parse C:c) &&
 +              git rev-parse >expect         \
 +                      C:a   A:a   B:b   C:C &&
 +              git rev-parse >actual          \
 +                      :3:a  :1:a  :2:b  :3:c &&
 +              test_cmp expect actual &&
  
 -      test -f a &&
 -      test -f b &&
 -      test -f c
 +              test_path_is_file a &&
 +              test_path_is_file b &&
 +              test_path_is_file c
 +      )
  '
  
  test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' '
 -      git rm -rf . &&
 -      git clean -fdqx &&
 -      rm -rf .git &&
 -      git init &&
 -
 -      >a &&
 -      git add a &&
 -      test_tick &&
 -      git commit -m base &&
 -      git tag A &&
 -
 -      git checkout -b B A &&
 -      git mv a b &&
 -      test_tick &&
 -      git commit -m one &&
 -
 -      git checkout -b C A &&
 -      git mv a b &&
 -      echo important-info >a &&
 -      git add a &&
 -      test_tick &&
 -      git commit -m two
 +      test_create_repo rename-rename-1to2-add-source-2 &&
 +      (
 +              cd rename-rename-1to2-add-source-2 &&
 +
 +              >a &&
 +              git add a &&
 +              test_tick &&
 +              git commit -m base &&
 +              git tag A &&
 +
 +              git checkout -b B A &&
 +              git mv a b &&
 +              test_tick &&
 +              git commit -m one &&
 +
 +              git checkout -b C A &&
 +              git mv a b &&
 +              echo important-info >a &&
 +              git add a &&
 +              test_tick &&
 +              git commit -m two
 +      )
  '
  
  test_expect_failure 'rename/rename/add-source still tracks new a file' '
 -      git checkout C^0 &&
 -      git merge -s recursive B^0 &&
 -
 -      test 2 -eq $(git ls-files -s | wc -l) &&
 -      test 0 -eq $(git ls-files -o | wc -l) &&
 -
 -      test $(git rev-parse HEAD:a) = $(git rev-parse C:a) &&
 -      test $(git rev-parse HEAD:b) = $(git rev-parse A:a)
 +      (
 +              cd rename-rename-1to2-add-source-2 &&
 +
 +              git checkout C^0 &&
 +              git merge -s recursive B^0 &&
 +
 +              git ls-files -s >out &&
 +              test_line_count = 2 out &&
 +              git ls-files -o >out &&
 +              test_line_count = 1 out &&
 +
 +              git rev-parse >expect \
 +                      C:a   A:a     &&
 +              git rev-parse >actual \
 +                      :0:a  :0:b    &&
 +              test_cmp expect actual
 +      )
  '
  
  test_expect_success 'setup rename/rename(1to2)/add-dest conflict' '
 -      git rm -rf . &&
 -      git clean -fdqx &&
 -      rm -rf .git &&
 -      git init &&
 -
 -      echo stuff >a &&
 -      git add a &&
 -      test_tick &&
 -      git commit -m base &&
 -      git tag A &&
 -
 -      git checkout -b B A &&
 -      git mv a b &&
 -      echo precious-data >c &&
 -      git add c &&
 -      test_tick &&
 -      git commit -m one &&
 -
 -      git checkout -b C A &&
 -      git mv a c &&
 -      echo important-info >b &&
 -      git add b &&
 -      test_tick &&
 -      git commit -m two
 +      test_create_repo rename-rename-1to2-add-dest &&
 +      (
 +              cd rename-rename-1to2-add-dest &&
 +
 +              echo stuff >a &&
 +              git add a &&
 +              test_tick &&
 +              git commit -m base &&
 +              git tag A &&
 +
 +              git checkout -b B A &&
 +              git mv a b &&
 +              echo precious-data >c &&
 +              git add c &&
 +              test_tick &&
 +              git commit -m one &&
 +
 +              git checkout -b C A &&
 +              git mv a c &&
 +              echo important-info >b &&
 +              git add b &&
 +              test_tick &&
 +              git commit -m two
 +      )
  '
  
  test_expect_success 'rename/rename/add-dest merge still knows about conflicting file versions' '
 -      git checkout C^0 &&
 -      test_must_fail git merge -s recursive B^0 &&
 -
 -      test 5 -eq $(git ls-files -s | wc -l) &&
 -      test 2 -eq $(git ls-files -u b | wc -l) &&
 -      test 2 -eq $(git ls-files -u c | wc -l) &&
 -      test 4 -eq $(git ls-files -o | wc -l) &&
 -
 -      test $(git rev-parse :1:a) = $(git rev-parse A:a) &&
 -      test $(git rev-parse :2:b) = $(git rev-parse C:b) &&
 -      test $(git rev-parse :3:b) = $(git rev-parse B:b) &&
 -      test $(git rev-parse :2:c) = $(git rev-parse C:c) &&
 -      test $(git rev-parse :3:c) = $(git rev-parse B:c) &&
 -
 -      test $(git hash-object c~HEAD) = $(git rev-parse C:c) &&
 -      test $(git hash-object c~B\^0) = $(git rev-parse B:c) &&
 -      test $(git hash-object b~HEAD) = $(git rev-parse C:b) &&
 -      test $(git hash-object b~B\^0) = $(git rev-parse B:b) &&
 -
 -      test ! -f b &&
 -      test ! -f c
 +      (
 +              cd rename-rename-1to2-add-dest &&
 +
 +              git checkout C^0 &&
 +              test_must_fail git merge -s recursive B^0 &&
 +
 +              git ls-files -s >out &&
 +              test_line_count = 5 out &&
 +              git ls-files -u b >out &&
 +              test_line_count = 2 out &&
 +              git ls-files -u c >out &&
 +              test_line_count = 2 out &&
 +              git ls-files -o >out &&
 +              test_line_count = 5 out &&
 +
 +              git rev-parse >expect               \
 +                      A:a   C:b   B:b   C:c   B:c &&
 +              git rev-parse >actual                \
 +                      :1:a  :2:b  :3:b  :2:c  :3:c &&
 +              test_cmp expect actual &&
 +
 +              git rev-parse >expect               \
 +                      C:c     B:c     C:b     B:b &&
 +              git hash-object >actual                \
 +                      c~HEAD  c~B\^0  b~HEAD  b~B\^0 &&
 +              test_cmp expect actual &&
 +
 +              test_path_is_missing b &&
 +              test_path_is_missing c
 +      )
 +'
 +
 +# Testcase rad, rename/add/delete
 +#   Commit O: foo
 +#   Commit A: rm foo, add different bar
 +#   Commit B: rename foo->bar
 +#   Expected: CONFLICT (rename/add/delete), two-way merged bar
 +
 +test_expect_success 'rad-setup: rename/add/delete conflict' '
 +      test_create_repo rad &&
 +      (
 +              cd rad &&
 +              echo "original file" >foo &&
 +              git add foo &&
 +              git commit -m "original" &&
 +
 +              git branch O &&
 +              git branch A &&
 +              git branch B &&
 +
 +              git checkout A &&
 +              git rm foo &&
 +              echo "different file" >bar &&
 +              git add bar &&
 +              git commit -m "Remove foo, add bar" &&
 +
 +              git checkout B &&
 +              git mv foo bar &&
 +              git commit -m "rename foo to bar"
 +      )
 +'
 +
 +test_expect_failure 'rad-check: rename/add/delete conflict' '
 +      (
 +              cd rad &&
 +
 +              git checkout B^0 &&
 +              test_must_fail git merge -s recursive A^0 >out 2>err &&
 +
 +              # Not sure whether the output should contain just one
 +              # "CONFLICT (rename/add/delete)" line, or if it should break
 +              # it into a pair of "CONFLICT (rename/delete)" and
 +              # "CONFLICT (rename/add)"; allow for either.
 +              test_i18ngrep "CONFLICT (rename.*add)" out &&
 +              test_i18ngrep "CONFLICT (rename.*delete)" out &&
 +              test_must_be_empty err &&
 +
 +              git ls-files -s >file_count &&
 +              test_line_count = 2 file_count &&
 +              git ls-files -u >file_count &&
 +              test_line_count = 2 file_count &&
 +              git ls-files -o >file_count &&
 +              test_line_count = 2 file_count &&
 +
 +              git rev-parse >actual \
 +                      :2:bar :3:bar &&
 +              git rev-parse >expect \
 +                      B:bar  A:bar  &&
 +
 +              test_cmp file_is_missing foo &&
 +              # bar should have two-way merged contents of the different
 +              # versions of bar; check that content from both sides is
 +              # present.
 +              grep original bar &&
 +              grep different bar
 +      )
 +'
 +
 +# Testcase rrdd, rename/rename(2to1)/delete/delete
 +#   Commit O: foo, bar
 +#   Commit A: rename foo->baz, rm bar
 +#   Commit B: rename bar->baz, rm foo
 +#   Expected: CONFLICT (rename/rename/delete/delete), two-way merged baz
 +
 +test_expect_success 'rrdd-setup: rename/rename(2to1)/delete/delete conflict' '
 +      test_create_repo rrdd &&
 +      (
 +              cd rrdd &&
 +              echo foo >foo &&
 +              echo bar >bar &&
 +              git add foo bar &&
 +              git commit -m O &&
 +
 +              git branch O &&
 +              git branch A &&
 +              git branch B &&
 +
 +              git checkout A &&
 +              git mv foo baz &&
 +              git rm bar &&
 +              git commit -m "Rename foo, remove bar" &&
 +
 +              git checkout B &&
 +              git mv bar baz &&
 +              git rm foo &&
 +              git commit -m "Rename bar, remove foo"
 +      )
 +'
 +
 +test_expect_failure 'rrdd-check: rename/rename(2to1)/delete/delete conflict' '
 +      (
 +              cd rrdd &&
 +
 +              git checkout A^0 &&
 +              test_must_fail git merge -s recursive B^0 >out 2>err &&
 +
 +              # Not sure whether the output should contain just one
 +              # "CONFLICT (rename/rename/delete/delete)" line, or if it
 +              # should break it into three: "CONFLICT (rename/rename)" and
 +              # two "CONFLICT (rename/delete)" lines; allow for either.
 +              test_i18ngrep "CONFLICT (rename/rename)" out &&
 +              test_i18ngrep "CONFLICT (rename.*delete)" out &&
 +              test_must_be_empty err &&
 +
 +              git ls-files -s >file_count &&
 +              test_line_count = 2 file_count &&
 +              git ls-files -u >file_count &&
 +              test_line_count = 2 file_count &&
 +              git ls-files -o >file_count &&
 +              test_line_count = 2 file_count &&
 +
 +              git rev-parse >actual \
 +                      :2:baz :3:baz &&
 +              git rev-parse >expect \
 +                      O:foo  O:bar  &&
 +
 +              test_cmp file_is_missing foo &&
 +              test_cmp file_is_missing bar &&
 +              # baz should have two-way merged contents of the original
 +              # contents of foo and bar; check that content from both sides
 +              # is present.
 +              grep foo baz &&
 +              grep bar baz
 +      )
 +'
 +
 +# Testcase mod6, chains of rename/rename(1to2) and rename/rename(2to1)
 +#   Commit O: one,      three,       five
 +#   Commit A: one->two, three->four, five->six
 +#   Commit B: one->six, three->two,  five->four
 +#   Expected: six CONFLICT(rename/rename) messages, each path in two of the
 +#             multi-way merged contents found in two, four, six
 +
 +test_expect_success 'mod6-setup: chains of rename/rename(1to2) and rename/rename(2to1)' '
 +      test_create_repo mod6 &&
 +      (
 +              cd mod6 &&
 +              test_seq 11 19 >one &&
 +              test_seq 31 39 >three &&
 +              test_seq 51 59 >five &&
 +              git add . &&
 +              test_tick &&
 +              git commit -m "O" &&
 +
 +              git branch O &&
 +              git branch A &&
 +              git branch B &&
 +
 +              git checkout A &&
 +              test_seq 10 19 >one &&
 +              echo 40        >>three &&
 +              git add one three &&
 +              git mv  one   two  &&
 +              git mv  three four &&
 +              git mv  five  six  &&
 +              test_tick &&
 +              git commit -m "A" &&
 +
 +              git checkout B &&
 +              echo 20    >>one       &&
 +              echo forty >>three     &&
 +              echo 60    >>five      &&
 +              git add one three five &&
 +              git mv  one   six  &&
 +              git mv  three two  &&
 +              git mv  five  four &&
 +              test_tick &&
 +              git commit -m "B"
 +      )
 +'
 +
 +test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename(2to1)' '
 +      (
 +              cd mod6 &&
 +
 +              git checkout A^0 &&
 +
 +              test_must_fail git merge -s recursive B^0 >out 2>err &&
 +
 +              test_i18ngrep "CONFLICT (rename/rename)" out &&
 +              test_must_be_empty err &&
 +
 +              git ls-files -s >file_count &&
 +              test_line_count = 6 file_count &&
 +              git ls-files -u >file_count &&
 +              test_line_count = 6 file_count &&
 +              git ls-files -o >file_count &&
 +              test_line_count = 3 file_count &&
 +
 +              test_seq 10 20 >merged-one &&
 +              test_seq 51 60 >merged-five &&
 +              # Determine what the merge of three would give us.
 +              test_seq 30 40 >three-side-A &&
 +              test_seq 31 39 >three-side-B &&
 +              echo forty >three-side-B &&
 +              >empty &&
 +              test_must_fail git merge-file \
 +                      -L "HEAD" \
 +                      -L "" \
 +                      -L "B^0" \
 +                      three-side-A empty three-side-B &&
 +              sed -e "s/^\([<=>]\)/\1\1\1/" three-side-A >merged-three &&
 +
 +              # Verify the index is as expected
 +              git rev-parse >actual         \
 +                      :2:two       :3:two   \
 +                      :2:four      :3:four  \
 +                      :2:six       :3:six   &&
 +              git hash-object >expect           \
 +                      merged-one   merged-three \
 +                      merged-three merged-five  \
 +                      merged-five  merged-one   &&
 +              test_cmp expect actual &&
 +
 +              git cat-file -p :2:two >expect &&
 +              git cat-file -p :3:two >other &&
 +              test_must_fail git merge-file    \
 +                      -L "HEAD"  -L ""  -L "B^0" \
 +                      expect     empty  other &&
 +              test_cmp expect two &&
 +
 +              git cat-file -p :2:four >expect &&
 +              git cat-file -p :3:four >other &&
 +              test_must_fail git merge-file    \
 +                      -L "HEAD"  -L ""  -L "B^0" \
 +                      expect     empty  other &&
 +              test_cmp expect four &&
 +
 +              git cat-file -p :2:six >expect &&
 +              git cat-file -p :3:six >other &&
 +              test_must_fail git merge-file    \
 +                      -L "HEAD"  -L ""  -L "B^0" \
 +                      expect     empty  other &&
 +              test_cmp expect six
 +      )
  '
  
  test_done