Code

Merge branch 'ks/recursive-rename-add-identical'
authorJunio C Hamano <gitster@pobox.com>
Wed, 15 Sep 2010 19:39:12 +0000 (12:39 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 15 Sep 2010 19:39:12 +0000 (12:39 -0700)
* ks/recursive-rename-add-identical:
  RE: [PATCH] Avoid rename/add conflict when contents are identical

1  2 
merge-recursive.c
t/t3030-merge-recursive.sh

diff --combined merge-recursive.c
index 20e1779428ee553a940592c09bbabe9ca9800a39,a2fba84d9d3d1aab5deb10ff1074b065b2a54047..c5746988196b8139ca3234f8b30d03af56d33324
@@@ -20,7 -20,6 +20,7 @@@
  #include "attr.h"
  #include "merge-recursive.h"
  #include "dir.h"
 +#include "submodule.h"
  
  static struct tree *shift_tree_object(struct tree *one, struct tree *two,
                                      const char *subtree_shift)
@@@ -137,10 -136,16 +137,10 @@@ static void output_commit_title(struct 
                if (parse_commit(commit) != 0)
                        printf("(bad commit)\n");
                else {
 -                      const char *s;
 -                      int len;
 -                      for (s = commit->buffer; *s; s++)
 -                              if (*s == '\n' && s[1] == '\n') {
 -                                      s += 2;
 -                                      break;
 -                              }
 -                      for (len = 0; s[len] && '\n' != s[len]; len++)
 -                              ; /* do nothing */
 -                      printf("%.*s\n", len, s);
 +                      const char *title;
 +                      int len = find_commit_subject(commit->buffer, &title);
 +                      if (len)
 +                              printf("%.*s\n", len, title);
                }
        }
  }
@@@ -180,7 -185,7 +180,7 @@@ static int git_merge_trees(int index_on
        opts.fn = threeway_merge;
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
 -      opts.msgs = get_porcelain_error_msgs();
 +      setup_unpack_trees_porcelain(&opts, "merge");
  
        init_tree_desc_from_tree(t+0, common);
        init_tree_desc_from_tree(t+1, head);
@@@ -520,15 -525,13 +520,15 @@@ static void update_file_flags(struct me
                void *buf;
                unsigned long size;
  
 -              if (S_ISGITLINK(mode))
 +              if (S_ISGITLINK(mode)) {
                        /*
                         * We may later decide to recursively descend into
                         * the submodule directory and update its index
                         * and/or work tree, but we do not do that now.
                         */
 +                      update_wd = 0;
                        goto update_index;
 +              }
  
                buf = read_sha1_file(sha, &type, &size);
                if (!buf)
@@@ -644,9 -647,7 +644,9 @@@ static int merge_3way(struct merge_opti
  
        merge_status = ll_merge(result_buf, a->path, &orig, base_name,
                                &src1, name1, &src2, name2,
 -                              (!!o->call_depth) | (favor << 1));
 +                              ((o->call_depth ? LL_OPT_VIRTUAL_ANCESTOR : 0) |
 +                               (o->renormalize ? LL_OPT_RENORMALIZE : 0) |
 +                               create_ll_flag(favor)));
  
        free(name1);
        free(name2);
@@@ -715,8 -716,8 +715,8 @@@ static struct merge_file_info merge_fil
                        free(result_buf.ptr);
                        result.clean = (merge_status == 0);
                } else if (S_ISGITLINK(a->mode)) {
 -                      result.clean = 0;
 -                      hashcpy(result.sha, a->sha1);
 +                      result.clean = merge_submodule(result.sha, one->path, one->sha1,
 +                                                     a->sha1, b->sha1);
                } else if (S_ISLNK(a->mode)) {
                        hashcpy(result.sha, a->sha1);
  
@@@ -805,8 -806,7 +805,8 @@@ static int process_renames(struct merge
                           struct string_list *b_renames)
  {
        int clean_merge = 1, i, j;
 -      struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0};
 +      struct string_list a_by_dst = STRING_LIST_INIT_NODUP;
 +      struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
        const struct rename *sre;
  
        for (i = 0; i < a_renames->nr; i++) {
                                                        ren1->pair->two : NULL,
                                                        branch1 == o->branch1 ?
                                                        NULL : ren1->pair->two, 1);
+                       } else if ((dst_other.mode == ren1->pair->two->mode) &&
+                                  sha_eq(dst_other.sha1, ren1->pair->two->sha1)) {
+                               /* Added file on the other side
+                                  identical to the file being
+                                  renamed: clean merge */
+                               update_file(o, 1, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
                        } else if (!sha_eq(dst_other.sha1, null_sha1)) {
                                const char *new_path;
                                clean_merge = 0;
  
                                if (mfi.clean &&
                                    sha_eq(mfi.sha, ren1->pair->two->sha1) &&
 -                                  mfi.mode == ren1->pair->two->mode)
 +                                  mfi.mode == ren1->pair->two->mode) {
                                        /*
 -                                       * This messaged is part of
 +                                       * This message is part of
                                         * t6022 test. If you change
                                         * it update the test too.
                                         */
                                        output(o, 3, "Skipped %s (merged same as existing)", ren1_dst);
 -                              else {
 +
 +                                      /* There may be higher stage entries left
 +                                       * in the index (e.g. due to a D/F
 +                                       * conflict) that need to be resolved.
 +                                       */
 +                                      if (!ren1->dst_entry->stages[2].mode !=
 +                                          !ren1->dst_entry->stages[3].mode)
 +                                              ren1->dst_entry->processed = 0;
 +                              } else {
                                        if (mfi.merge || !mfi.clean)
                                                output(o, 1, "Renaming %s => %s", ren1_src, ren1_dst);
                                        if (mfi.merge)
@@@ -1064,53 -1062,6 +1070,53 @@@ static unsigned char *stage_sha(const u
        return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
  }
  
 +static int read_sha1_strbuf(const unsigned char *sha1, struct strbuf *dst)
 +{
 +      void *buf;
 +      enum object_type type;
 +      unsigned long size;
 +      buf = read_sha1_file(sha1, &type, &size);
 +      if (!buf)
 +              return error("cannot read object %s", sha1_to_hex(sha1));
 +      if (type != OBJ_BLOB) {
 +              free(buf);
 +              return error("object %s is not a blob", sha1_to_hex(sha1));
 +      }
 +      strbuf_attach(dst, buf, size, size + 1);
 +      return 0;
 +}
 +
 +static int blob_unchanged(const unsigned char *o_sha,
 +                        const unsigned char *a_sha,
 +                        int renormalize, const char *path)
 +{
 +      struct strbuf o = STRBUF_INIT;
 +      struct strbuf a = STRBUF_INIT;
 +      int ret = 0; /* assume changed for safety */
 +
 +      if (sha_eq(o_sha, a_sha))
 +              return 1;
 +      if (!renormalize)
 +              return 0;
 +
 +      assert(o_sha && a_sha);
 +      if (read_sha1_strbuf(o_sha, &o) || read_sha1_strbuf(a_sha, &a))
 +              goto error_return;
 +      /*
 +       * Note: binary | is used so that both renormalizations are
 +       * performed.  Comparison can be skipped if both files are
 +       * unchanged since their sha1s have already been compared.
 +       */
 +      if (renormalize_buffer(path, o.buf, o.len, &o) |
 +          renormalize_buffer(path, a.buf, o.len, &a))
 +              ret = (o.len == a.len && !memcmp(o.buf, a.buf, o.len));
 +
 +error_return:
 +      strbuf_release(&o);
 +      strbuf_release(&a);
 +      return ret;
 +}
 +
  /* Per entry merge function */
  static int process_entry(struct merge_options *o,
                         const char *path, struct stage_data *entry)
        print_index_entry("\tpath: ", entry);
        */
        int clean_merge = 1;
 +      int normalize = o->renormalize;
        unsigned o_mode = entry->stages[1].mode;
        unsigned a_mode = entry->stages[2].mode;
        unsigned b_mode = entry->stages[3].mode;
        unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
        unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
  
 +      entry->processed = 1;
        if (o_sha && (!a_sha || !b_sha)) {
                /* Case A: Deleted in one */
                if ((!a_sha && !b_sha) ||
 -                  (sha_eq(a_sha, o_sha) && !b_sha) ||
 -                  (!a_sha && sha_eq(b_sha, o_sha))) {
 +                  (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||
 +                  (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) {
                        /* Deleted in both or deleted in one and
                         * unchanged in the other */
                        if (a_sha)
        } else if ((!o_sha && a_sha && !b_sha) ||
                   (!o_sha && !a_sha && b_sha)) {
                /* Case B: Added in one. */
 -              const char *add_branch;
 -              const char *other_branch;
                unsigned mode;
                const unsigned char *sha;
 -              const char *conf;
  
                if (a_sha) {
 -                      add_branch = o->branch1;
 -                      other_branch = o->branch2;
                        mode = a_mode;
                        sha = a_sha;
 -                      conf = "file/directory";
                } else {
 -                      add_branch = o->branch2;
 -                      other_branch = o->branch1;
                        mode = b_mode;
                        sha = b_sha;
 -                      conf = "directory/file";
                }
                if (string_list_has_string(&o->current_directory_set, path)) {
 -                      const char *new_path = unique_path(o, path, add_branch);
 -                      clean_merge = 0;
 -                      output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
 -                             "Adding %s as %s",
 -                             conf, path, other_branch, path, new_path);
 -                      remove_file(o, 0, path, 0);
 -                      update_file(o, 0, sha, mode, new_path);
 +                      /* Handle D->F conflicts after all subfiles */
 +                      entry->processed = 0;
 +                      /* But get any file out of the way now, so conflicted
 +                       * entries below the directory of the same name can
 +                       * be put in the working directory.
 +                       */
 +                      if (a_sha)
 +                              output(o, 2, "Removing %s", path);
 +                      /* do not touch working file if it did not exist */
 +                      remove_file(o, 0, path, !a_sha);
 +                      return 1; /* Assume clean till processed */
                } else {
                        output(o, 2, "Adding %s", path);
                        update_file(o, 1, sha, mode, path);
        return clean_merge;
  }
  
 -struct unpack_trees_error_msgs get_porcelain_error_msgs(void)
 +/*
 + * Per entry merge function for D/F conflicts, to be called only after
 + * all files below dir have been processed.  We do this because in the
 + * cases we can cleanly resolve D/F conflicts, process_entry() can clean
 + * out all the files below the directory for us.
 + */
 +static int process_df_entry(struct merge_options *o,
 +                       const char *path, struct stage_data *entry)
  {
 -      struct unpack_trees_error_msgs msgs = {
 -              /* would_overwrite */
 -              "Your local changes to '%s' would be overwritten by merge.  Aborting.",
 -              /* not_uptodate_file */
 -              "Your local changes to '%s' would be overwritten by merge.  Aborting.",
 -              /* not_uptodate_dir */
 -              "Updating '%s' would lose untracked files in it.  Aborting.",
 -              /* would_lose_untracked */
 -              "Untracked working tree file '%s' would be %s by merge.  Aborting",
 -              /* bind_overlap -- will not happen here */
 -              NULL,
 -      };
 -      if (advice_commit_before_merge) {
 -              msgs.would_overwrite = msgs.not_uptodate_file =
 -                      "Your local changes to '%s' would be overwritten by merge.  Aborting.\n"
 -                      "Please, commit your changes or stash them before you can merge.";
 +      int clean_merge = 1;
 +      unsigned o_mode = entry->stages[1].mode;
 +      unsigned a_mode = entry->stages[2].mode;
 +      unsigned b_mode = entry->stages[3].mode;
 +      unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
 +      unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
 +      unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
 +      const char *add_branch;
 +      const char *other_branch;
 +      unsigned mode;
 +      const unsigned char *sha;
 +      const char *conf;
 +      struct stat st;
 +
 +      /* We currently only handle D->F cases */
 +      assert((!o_sha && a_sha && !b_sha) ||
 +             (!o_sha && !a_sha && b_sha));
 +
 +      entry->processed = 1;
 +
 +      if (a_sha) {
 +              add_branch = o->branch1;
 +              other_branch = o->branch2;
 +              mode = a_mode;
 +              sha = a_sha;
 +              conf = "file/directory";
 +      } else {
 +              add_branch = o->branch2;
 +              other_branch = o->branch1;
 +              mode = b_mode;
 +              sha = b_sha;
 +              conf = "directory/file";
        }
 -      return msgs;
 +      if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
 +              const char *new_path = unique_path(o, path, add_branch);
 +              clean_merge = 0;
 +              output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
 +                     "Adding %s as %s",
 +                     conf, path, other_branch, path, new_path);
 +              remove_file(o, 0, path, 0);
 +              update_file(o, 0, sha, mode, new_path);
 +      } else {
 +              output(o, 2, "Adding %s", path);
 +              update_file(o, 1, sha, mode, path);
 +      }
 +
 +      return clean_merge;
  }
  
  int merge_trees(struct merge_options *o,
                                && !process_entry(o, path, e))
                                clean = 0;
                }
 +              for (i = 0; i < entries->nr; i++) {
 +                      const char *path = entries->items[i].string;
 +                      struct stage_data *e = entries->items[i].util;
 +                      if (!e->processed
 +                              && !process_df_entry(o, path, e))
 +                              clean = 0;
 +              }
  
                string_list_clear(re_merge, 0);
                string_list_clear(re_head, 0);
@@@ -1531,7 -1442,6 +1537,7 @@@ void init_merge_options(struct merge_op
        o->buffer_output = 1;
        o->diff_rename_limit = -1;
        o->merge_rename_limit = -1;
 +      o->renormalize = 0;
        git_config(merge_recursive_config, o);
        if (getenv("GIT_MERGE_VERBOSITY"))
                o->verbosity =
index efe2900a372601848248de33188bda292185ba40,df234f144a5f72c85f95ec6d3c218f70eebecfc8..e66e550b2449e76435a0bf16c6a9889c0794f858
@@@ -23,6 -23,8 +23,8 @@@ test_expect_success 'setup 1' 
        git branch df-3 &&
        git branch remove &&
        git branch submod &&
+       git branch copy &&
+       git branch rename &&
  
        echo hello >>a &&
        cp a d/e &&
@@@ -248,6 -250,22 +250,22 @@@ test_expect_success 'setup 7' 
        git commit -m "make d/ a submodule"
  '
  
+ test_expect_success 'setup 8' '
+       git checkout rename &&
+       git mv a e &&
+       git add e &&
+       test_tick &&
+       git commit -m "rename a->e"
+ '
+ test_expect_success 'setup 9' '
+       git checkout copy &&
+       cp a e &&
+       git add e &&
+       test_tick &&
+       git commit -m "copy a->e"
+ '
  test_expect_success 'merge-recursive simple' '
  
        rm -fr [abcd] &&
@@@ -294,7 -312,7 +312,7 @@@ test_expect_success 'fail if the index 
        grep "You have not concluded your merge" out &&
        rm -f .git/MERGE_HEAD &&
        test_must_fail git merge "$c5" 2> out &&
 -      grep "Your local changes to .* would be overwritten by merge." out
 +      grep "Your local changes to the following files would be overwritten by merge:" out
  '
  
  test_expect_success 'merge-recursive remove conflict' '
@@@ -580,4 -598,21 +598,21 @@@ test_expect_failure 'merge-recursive si
        test_cmp expected actual
  '
  
+ test_expect_success 'merge-recursive copy vs. rename' '
+       git checkout -f copy &&
+       git merge rename &&
+       ( git ls-tree -r HEAD && git ls-files -s ) >actual &&
+       (
+               echo "100644 blob $o0   b"
+               echo "100644 blob $o0   c"
+               echo "100644 blob $o0   d/e"
+               echo "100644 blob $o0   e"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o0 0      d/e"
+               echo "100644 $o0 0      e"
+       ) >expected &&
+       test_cmp expected actual
+ '
  test_done