Merge branch 'mv/merge-recursive'
authorShawn O. Pearce <spearce@spearce.org>
Thu, 25 Sep 2008 16:49:19 +0000 (09:49 -0700)
committerShawn O. Pearce <spearce@spearce.org>
Thu, 25 Sep 2008 16:49:19 +0000 (09:49 -0700)
* mv/merge-recursive:
builtin-merge: release the lockfile in try_merge_strategy()
merge-recursive: get rid of virtual_id
merge-recursive: move current_{file,directory}_set to struct merge_options
merge-recursive: move the global obuf to struct merge_options
merge-recursive: get rid of the index_only global variable
merge-recursive: move call_depth to struct merge_options
cherry-pick/revert: make direct internal call to merge_tree()
builtin-merge: avoid run_command_v_opt() for recursive and subtree
merge-recursive: introduce merge_options
merge-recursive.c: Add more generic merge_recursive_generic()
Split out merge_recursive() to merge-recursive.c

1  2 
Makefile
builtin-checkout.c
builtin-merge.c
t/t6026-merge-attr.sh
diff --combined Makefile
index 48547a21139e7c22ebc96fc19ee7cd1f6b3116bb,f69761838465b848337f62eb90ab9fe94777fa9f..e0c03c3eecc26d995f8829429092e887df17489f
+++ b/Makefile
@@@ -294,8 -294,8 +294,8 @@@ PROGRAMS += git-mktag$
  PROGRAMS += git-mktree$X
  PROGRAMS += git-pack-redundant$X
  PROGRAMS += git-patch-id$X
 -PROGRAMS += git-receive-pack$X
  PROGRAMS += git-send-pack$X
 +PROGRAMS += git-shell$X
  PROGRAMS += git-show-index$X
  PROGRAMS += git-unpack-file$X
  PROGRAMS += git-update-server-info$X
@@@ -358,12 -358,10 +358,12 @@@ LIB_H += graph.
  LIB_H += grep.h
  LIB_H += hash.h
  LIB_H += help.h
 +LIB_H += levenshtein.h
  LIB_H += list-objects.h
  LIB_H += ll-merge.h
  LIB_H += log-tree.h
  LIB_H += mailmap.h
 +LIB_H += merge-recursive.h
  LIB_H += object.h
  LIB_H += pack.h
  LIB_H += pack-refs.h
@@@ -435,7 -433,6 +435,7 @@@ LIB_OBJS += hash.
  LIB_OBJS += help.o
  LIB_OBJS += ident.o
  LIB_OBJS += interpolate.o
 +LIB_OBJS += levenshtein.o
  LIB_OBJS += list-objects.o
  LIB_OBJS += ll-merge.o
  LIB_OBJS += lockfile.o
@@@ -443,6 -440,7 +443,7 @@@ LIB_OBJS += log-tree.
  LIB_OBJS += mailmap.o
  LIB_OBJS += match-trees.o
  LIB_OBJS += merge-file.o
+ LIB_OBJS += merge-recursive.o
  LIB_OBJS += name-hash.o
  LIB_OBJS += object.o
  LIB_OBJS += pack-check.o
@@@ -545,7 -543,6 +546,7 @@@ BUILTIN_OBJS += builtin-prune-packed.
  BUILTIN_OBJS += builtin-prune.o
  BUILTIN_OBJS += builtin-push.o
  BUILTIN_OBJS += builtin-read-tree.o
 +BUILTIN_OBJS += builtin-receive-pack.o
  BUILTIN_OBJS += builtin-reflog.o
  BUILTIN_OBJS += builtin-remote.o
  BUILTIN_OBJS += builtin-rerere.o
@@@ -636,8 -633,6 +637,8 @@@ ifeq ($(uname_S),Darwin
        endif
        NO_STRLCPY = YesPlease
        NO_MEMMEM = YesPlease
 +      COMPAT_CFLAGS += -Icompat/regex
 +      COMPAT_OBJS += compat/regex/regex.o
  endif
  ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
@@@ -688,8 -683,6 +689,8 @@@ ifeq ($(uname_S),FreeBSD
        BASIC_LDFLAGS += -L/usr/local/lib
        DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
        THREADED_DELTA_SEARCH = YesPlease
 +      COMPAT_CFLAGS += -Icompat/regex
 +      COMPAT_OBJS += compat/regex/regex.o
  endif
  ifeq ($(uname_S),OpenBSD)
        NO_STRCASESTR = YesPlease
@@@ -704,7 -697,8 +705,7 @@@ ifeq ($(uname_S),NetBSD
                NEEDS_LIBICONV = YesPlease
        endif
        BASIC_CFLAGS += -I/usr/pkg/include
 -      BASIC_LDFLAGS += -L/usr/pkg/lib
 -      ALL_LDFLAGS += -Wl,-rpath,/usr/pkg/lib
 +      BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib
        THREADED_DELTA_SEARCH = YesPlease
  endif
  ifeq ($(uname_S),AIX)
        INTERNAL_QSORT = UnfortunatelyYes
        NEEDS_LIBICONV=YesPlease
        BASIC_CFLAGS += -D_LARGE_FILES
 +      COMPAT_CFLAGS += -Icompat/regex
 +      COMPAT_OBJS += compat/regex/regex.o
  endif
  ifeq ($(uname_S),GNU)
        # GNU/Hurd
@@@ -769,10 -761,10 +770,10 @@@ ifneq (,$(findstring MINGW,$(uname_S))
        NO_PERL_MAKEMAKER = YesPlease
        NO_POSIX_ONLY_PROGRAMS = YesPlease
        NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
 -      COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat
 +      COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/regex -Icompat/fnmatch
        COMPAT_CFLAGS += -DSNPRINTF_SIZE_CORR=1
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
 -      COMPAT_OBJS += compat/mingw.o compat/fnmatch.o compat/regex.o compat/winansi.o
 +      COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/regex/regex.o compat/winansi.o
        EXTLIBS += -lws2_32
        X = .exe
        gitexecdir = ../libexec/git-core
@@@ -801,14 -793,12 +802,14 @@@ ifeq ($(uname_S),Darwin
        endif
  endif
  
 -ifdef NO_R_TO_GCC_LINKER
 -      # Some gcc does not accept and pass -R to the linker to specify
 -      # the runtime dynamic library path.
 -      CC_LD_DYNPATH = -Wl,-rpath=
 -else
 -      CC_LD_DYNPATH = -R
 +ifndef CC_LD_DYNPATH
 +      ifdef NO_R_TO_GCC_LINKER
 +              # Some gcc does not accept and pass -R to the linker to specify
 +              # the runtime dynamic library path.
 +              CC_LD_DYNPATH = -Wl,-rpath,
 +      else
 +              CC_LD_DYNPATH = -R
 +      endif
  endif
  
  ifdef NO_CURL
@@@ -844,6 -834,7 +845,6 @@@ EXTLIBS += -l
  ifndef NO_POSIX_ONLY_PROGRAMS
        PROGRAMS += git-daemon$X
        PROGRAMS += git-imap-send$X
 -      PROGRAMS += git-shell$X
  endif
  ifndef NO_OPENSSL
        OPENSSL_LIBSSL = -lssl
@@@ -1271,12 -1262,6 +1272,12 @@@ $(XDIFF_LIB): $(XDIFF_OBJS
  doc:
        $(MAKE) -C Documentation all
  
 +man:
 +      $(MAKE) -C Documentation man
 +
 +html:
 +      $(MAKE) -C Documentation html
 +
  info:
        $(MAKE) -C Documentation info
  
@@@ -1380,7 -1365,7 +1381,7 @@@ install: al
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)'
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
        $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 -      $(INSTALL) git$X git-upload-pack$X git-receive-pack$X git-upload-archive$X git-shell$X '$(DESTDIR_SQ)$(bindir_SQ)'
 +      $(INSTALL) git$X git-upload-pack$X git-receive-pack$X git-upload-archive$X git-shell$X git-cvsserver '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
        $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
  ifndef NO_TCLTK
@@@ -1413,9 -1398,6 +1414,9 @@@ install-info
  quick-install-doc:
        $(MAKE) -C Documentation quick-install
  
 +quick-install-html:
 +      $(MAKE) -C Documentation quick-install-html
 +
  
  
  ### Maintainer's dist rules
diff --combined builtin-checkout.c
index 9a0c173c142ab552910b71dfad41f90682271d8d,f3c6b0fad263c1885fd177b2e3f375c9c07c94e0..075667c9cc8aabe4157900635b2aabc4f55f448c
@@@ -76,15 -76,6 +76,15 @@@ static int read_tree_some(struct tree *
        return 0;
  }
  
 +static int skip_same_name(struct cache_entry *ce, int pos)
 +{
 +      while (++pos < active_nr &&
 +             !strcmp(active_cache[pos]->name, ce->name))
 +              ; /* skip */
 +      return pos;
 +}
 +
 +
  static int checkout_paths(struct tree *source_tree, const char **pathspec)
  {
        int pos;
        if (report_path_error(ps_matched, pathspec, 0))
                return 1;
  
 +      /* Any unmerged paths? */
 +      for (pos = 0; pos < active_nr; pos++) {
 +              struct cache_entry *ce = active_cache[pos];
 +              if (pathspec_match(pathspec, NULL, ce->name, 0)) {
 +                      if (!ce_stage(ce))
 +                              continue;
 +                      errs = 1;
 +                      error("path '%s' is unmerged", ce->name);
 +                      pos = skip_same_name(ce, pos) - 1;
 +              }
 +      }
 +      if (errs)
 +              return 1;
 +
        /* Now we are committed to check them out */
        memset(&state, 0, sizeof(state));
        state.force = 1;
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
                if (pathspec_match(pathspec, NULL, ce->name, 0)) {
 -                      errs |= checkout_entry(ce, &state, NULL);
 +                      if (!ce_stage(ce)) {
 +                              errs |= checkout_entry(ce, &state, NULL);
 +                              continue;
 +                      }
 +                      pos = skip_same_name(ce, pos) - 1;
                }
        }
  
@@@ -269,8 -242,6 +269,8 @@@ static int merge_working_tree(struct ch
                }
  
                /* 2-way merge to the new branch */
 +              topts.initial_checkout = (!active_nr &&
 +                                        (old->commit == new->commit));
                topts.update = 1;
                topts.merge = 1;
                topts.gently = opts->merge;
                         */
                        struct tree *result;
                        struct tree *work;
+                       struct merge_options o;
                        if (!opts->merge)
                                return 1;
                        parse_commit(old->commit);
                         */
  
                        add_files_to_cache(NULL, NULL, 0);
-                       work = write_tree_from_memory();
+                       init_merge_options(&o);
+                       o.verbosity = 0;
+                       work = write_tree_from_memory(&o);
  
                        ret = reset_tree(new->commit->tree, opts, 1);
                        if (ret)
                                return ret;
-                       merge_trees(new->commit->tree, work, old->commit->tree,
-                                   new->name, "local", &result);
+                       o.branch1 = new->name;
+                       o.branch2 = "local";
+                       merge_trees(&o, new->commit->tree, work,
+                               old->commit->tree, &result);
                        ret = reset_tree(new->commit->tree, opts, 0);
                        if (ret)
                                return ret;
            commit_locked_index(lock_file))
                die("unable to write new index file");
  
 -      if (!opts->force)
 +      if (!opts->force && !opts->quiet)
                show_local_changes(&new->commit->object);
  
        return 0;
@@@ -415,11 -391,13 +420,11 @@@ static int switch_branches(struct check
        }
  
        /*
 -       * If the new thing isn't a branch and isn't HEAD and we're
 -       * not starting a new branch, and we want messages, and we
 -       * weren't on a branch, and we're moving to a new commit,
 -       * describe the old commit.
 +       * If we were on a detached HEAD, but we are now moving to
 +       * a new commit, we want to mention the old commit once more
 +       * to remind the user that it might be lost.
         */
 -      if (!new->path && strcmp(new->name, "HEAD") && !opts->new_branch &&
 -          !opts->quiet && !old.path && new->commit != old.commit)
 +      if (!opts->quiet && !old.path && new->commit != old.commit)
                describe_detached_head("Previous HEAD position was", old.commit);
  
        if (!old.commit) {
@@@ -580,18 -558,6 +585,18 @@@ no_reference
                return checkout_paths(source_tree, pathspec);
        }
  
 +      if (opts.new_branch) {
 +              struct strbuf buf;
 +              strbuf_init(&buf, 0);
 +              strbuf_addstr(&buf, "refs/heads/");
 +              strbuf_addstr(&buf, opts.new_branch);
 +              if (!get_sha1(buf.buf, rev))
 +                      die("git checkout: branch %s already exists", opts.new_branch);
 +              if (check_ref_format(buf.buf))
 +                      die("git checkout: we do not like '%s' as a branch name.", opts.new_branch);
 +              strbuf_release(&buf);
 +      }
 +
        if (new.name && !new.commit) {
                die("Cannot switch branch to a non-commit.");
        }
diff --combined builtin-merge.c
index 1942e72aaf20b10bd8069ef3a736fcc3c4732753,bb09e6fb34f8552b959fa5dca5258ec69bc48c12..5c65a5869900ad1a1014fb3aad88c874a5410bf7
@@@ -23,6 -23,7 +23,7 @@@
  #include "color.h"
  #include "rerere.h"
  #include "help.h"
+ #include "merge-recursive.h"
  
  #define DEFAULT_TWOHEAD (1<<0)
  #define DEFAULT_OCTOPUS (1<<1)
@@@ -476,8 -477,6 +477,8 @@@ static int git_merge_config(const char 
  
                buf = xstrdup(v);
                argc = split_cmdline(buf, &argv);
 +              if (argc < 0)
 +                      die("Bad branch.%s.mergeoptions string", branch);
                argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
                memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
                argc++;
@@@ -547,28 -546,65 +548,65 @@@ static int try_merge_strategy(const cha
        struct commit_list *j;
        struct strbuf buf;
  
-       args = xmalloc((4 + commit_list_count(common) +
-                       commit_list_count(remoteheads)) * sizeof(char *));
-       strbuf_init(&buf, 0);
-       strbuf_addf(&buf, "merge-%s", strategy);
-       args[i++] = buf.buf;
-       for (j = common; j; j = j->next)
-               args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
-       args[i++] = "--";
-       args[i++] = head_arg;
-       for (j = remoteheads; j; j = j->next)
-               args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
-       args[i] = NULL;
-       ret = run_command_v_opt(args, RUN_GIT_CMD);
-       strbuf_release(&buf);
-       i = 1;
-       for (j = common; j; j = j->next)
-               free((void *)args[i++]);
-       i += 2;
-       for (j = remoteheads; j; j = j->next)
-               free((void *)args[i++]);
-       free(args);
-       return -ret;
+       if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) {
+               int clean;
+               struct commit *result;
+               struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+               int index_fd;
+               struct commit_list *reversed = NULL;
+               struct merge_options o;
+               if (remoteheads->next) {
+                       error("Not handling anything other than two heads merge.");
+                       return 2;
+               }
+               init_merge_options(&o);
+               if (!strcmp(strategy, "subtree"))
+                       o.subtree_merge = 1;
+               o.branch1 = head_arg;
+               o.branch2 = remoteheads->item->util;
+               for (j = common; j; j = j->next)
+                       commit_list_insert(j->item, &reversed);
+               index_fd = hold_locked_index(lock, 1);
+               clean = merge_recursive(&o, lookup_commit(head),
+                               remoteheads->item, reversed, &result);
+               if (active_cache_changed &&
+                               (write_cache(index_fd, active_cache, active_nr) ||
+                                commit_locked_index(lock)))
+                       die ("unable to write %s", get_index_file());
+               rollback_lock_file(lock);
+               return clean ? 0 : 1;
+       } else {
+               args = xmalloc((4 + commit_list_count(common) +
+                                       commit_list_count(remoteheads)) * sizeof(char *));
+               strbuf_init(&buf, 0);
+               strbuf_addf(&buf, "merge-%s", strategy);
+               args[i++] = buf.buf;
+               for (j = common; j; j = j->next)
+                       args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+               args[i++] = "--";
+               args[i++] = head_arg;
+               for (j = remoteheads; j; j = j->next)
+                       args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+               args[i] = NULL;
+               ret = run_command_v_opt(args, RUN_GIT_CMD);
+               strbuf_release(&buf);
+               i = 1;
+               for (j = common; j; j = j->next)
+                       free((void *)args[i++]);
+               i += 2;
+               for (j = remoteheads; j; j = j->next)
+                       free((void *)args[i++]);
+               free(args);
+               discard_cache();
+               if (read_cache() < 0)
+                       die("failed to read the cache");
+               return -ret;
+       }
  }
  
  static void count_diff_files(struct diff_queue_struct *q,
@@@ -693,7 -729,7 +731,7 @@@ static int merge_trivial(void
        parent->next = xmalloc(sizeof(struct commit_list *));
        parent->next->item = remoteheads->item;
        parent->next->next = NULL;
 -      commit_tree(merge_msg.buf, result_tree, parent, result_commit);
 +      commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
        finish(result_commit, "In-index merge");
        drop_save();
        return 0;
@@@ -722,7 -758,7 +760,7 @@@ static int finish_automerge(struct comm
        }
        free_commit_list(remoteheads);
        strbuf_addch(&merge_msg, '\n');
 -      commit_tree(merge_msg.buf, result_tree, parents, result_commit);
 +      commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
        strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
        finish(result_commit, buf.buf);
        strbuf_release(&buf);
@@@ -779,10 -815,6 +817,6 @@@ static int evaluate_result(void
        int cnt = 0;
        struct rev_info rev;
  
-       discard_cache();
-       if (read_cache() < 0)
-               die("failed to read the cache");
        /* Check how many files differ. */
        init_revisions(&rev, "");
        setup_revisions(0, NULL, &rev, NULL);
@@@ -916,12 -948,14 +950,14 @@@ int cmd_merge(int argc, const char **ar
  
        for (i = 0; i < argc; i++) {
                struct object *o;
+               struct commit *commit;
  
                o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
                if (!o)
                        die("%s - not something we can merge", argv[i]);
-               remotes = &commit_list_insert(lookup_commit(o->sha1),
-                       remotes)->next;
+               commit = lookup_commit(o->sha1);
+               commit->util = (void *)argv[i];
+               remotes = &commit_list_insert(commit, remotes)->next;
  
                strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
                setenv(buf.buf, argv[i], 1);
                }
  
                /* Automerge succeeded. */
-               discard_cache();
                write_tree_trivial(result_tree);
                automerge_was_ok = 1;
                break;
diff --combined t/t6026-merge-attr.sh
index 4b423e937dbd259e5b2311051907a54309e0edb9,86f47ca595c2d71e132c8acdbf7d8edadc1e0ecf..1ba0a252230a283a6bb2463d98537aab1eaf4fb8
@@@ -106,9 -106,9 +106,9 @@@ test_expect_success 'custom merge backe
  
        cmp binary union &&
        sed -e 1,3d text >check-1 &&
 -      o=$(git-unpack-file master^:text) &&
 -      a=$(git-unpack-file side^:text) &&
 -      b=$(git-unpack-file master:text) &&
 +      o=$(git unpack-file master^:text) &&
 +      a=$(git unpack-file side^:text) &&
 +      b=$(git unpack-file master:text) &&
        sh -c "./custom-merge $o $a $b 0" &&
        sed -e 1,3d $a >check-2 &&
        cmp check-1 check-2 &&
@@@ -133,13 -133,35 +133,35 @@@ test_expect_success 'custom merge backe
  
        cmp binary union &&
        sed -e 1,3d text >check-1 &&
 -      o=$(git-unpack-file master^:text) &&
 -      a=$(git-unpack-file anchor:text) &&
 -      b=$(git-unpack-file master:text) &&
 +      o=$(git unpack-file master^:text) &&
 +      a=$(git unpack-file anchor:text) &&
 +      b=$(git unpack-file master:text) &&
        sh -c "./custom-merge $o $a $b 0" &&
        sed -e 1,3d $a >check-2 &&
        cmp check-1 check-2 &&
        rm -f $o $a $b
  '
  
+ test_expect_success 'up-to-date merge without common ancestor' '
+       test_create_repo repo1 &&
+       test_create_repo repo2 &&
+       test_tick &&
+       (
+               cd repo1 &&
+               >a &&
+               git add a &&
+               git commit -m initial
+       ) &&
+       test_tick &&
+       (
+               cd repo2 &&
+               git commit --allow-empty -m initial
+       ) &&
+       test_tick &&
+       (
+               cd repo1 &&
+               git pull ../repo2 master
+       )
+ '
  test_done