Merge branch 'bm/merge-base-octopus-dedup'
authorJunio C Hamano <gitster@pobox.com>
Fri, 10 Jan 2014 18:33:32 +0000 (10:33 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 10 Jan 2014 18:33:33 +0000 (10:33 -0800)
"git merge-base --octopus" used to leave cleaning up suboptimal
result to the caller, but now it does the clean-up itself.

* bm/merge-base-octopus-dedup:
merge-base --octopus: reduce the result from get_octopus_merge_bases()
merge-base: separate "--independent" codepath into its own helper

1  2 
builtin/merge-base.c
t/t6010-merge-base.sh
diff --combined builtin/merge-base.c
index a2923235e1024e554b0dc937c8222282d1855065,87f4dbccaeae39e7bd52e89f368357d58bc20037..0ecde8da30615996f54563014e4774b67812c531
@@@ -1,9 -1,6 +1,9 @@@
  #include "builtin.h"
  #include "cache.h"
  #include "commit.h"
 +#include "refs.h"
 +#include "diff.h"
 +#include "revision.h"
  #include "parse-options.h"
  
  static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
@@@ -30,7 -27,6 +30,7 @@@ static const char * const merge_base_us
        N_("git merge-base [-a|--all] --octopus <commit>..."),
        N_("git merge-base --independent <commit>..."),
        N_("git merge-base --is-ancestor <commit> <commit>"),
 +      N_("git merge-base --fork-point <ref> [<commit>]"),
        NULL
  };
  
@@@ -48,19 -44,36 +48,36 @@@ static struct commit *get_commit_refere
        return r;
  }
  
- static int handle_octopus(int count, const char **args, int reduce, int show_all)
+ static int handle_independent(int count, const char **args)
  {
        struct commit_list *revs = NULL;
        struct commit_list *result;
        int i;
  
-       if (reduce)
-               show_all = 1;
+       for (i = count - 1; i >= 0; i--)
+               commit_list_insert(get_commit_reference(args[i]), &revs);
+       result = reduce_heads(revs);
+       if (!result)
+               return 1;
+       while (result) {
+               printf("%s\n", sha1_to_hex(result->item->object.sha1));
+               result = result->next;
+       }
+       return 0;
+ }
+ static int handle_octopus(int count, const char **args, int show_all)
+ {
+       struct commit_list *revs = NULL;
+       struct commit_list *result;
+       int i;
  
        for (i = count - 1; i >= 0; i--)
                commit_list_insert(get_commit_reference(args[i]), &revs);
  
-       result = reduce ? reduce_heads(revs) : get_octopus_merge_bases(revs);
+       result = reduce_heads(get_octopus_merge_bases(revs));
  
        if (!result)
                return 1;
@@@ -89,149 -102,40 +106,152 @@@ static int handle_is_ancestor(int argc
                return 1;
  }
  
 +struct rev_collect {
 +      struct commit **commit;
 +      int nr;
 +      int alloc;
 +      unsigned int initial : 1;
 +};
 +
 +static void add_one_commit(unsigned char *sha1, struct rev_collect *revs)
 +{
 +      struct commit *commit;
 +
 +      if (is_null_sha1(sha1))
 +              return;
 +
 +      commit = lookup_commit(sha1);
 +      if (!commit ||
 +          (commit->object.flags & TMP_MARK) ||
 +          parse_commit(commit))
 +              return;
 +
 +      ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc);
 +      revs->commit[revs->nr++] = commit;
 +      commit->object.flags |= TMP_MARK;
 +}
 +
 +static int collect_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
 +                                const char *ident, unsigned long timestamp,
 +                                int tz, const char *message, void *cbdata)
 +{
 +      struct rev_collect *revs = cbdata;
 +
 +      if (revs->initial) {
 +              revs->initial = 0;
 +              add_one_commit(osha1, revs);
 +      }
 +      add_one_commit(nsha1, revs);
 +      return 0;
 +}
 +
 +static int handle_fork_point(int argc, const char **argv)
 +{
 +      unsigned char sha1[20];
 +      char *refname;
 +      const char *commitname;
 +      struct rev_collect revs;
 +      struct commit *derived;
 +      struct commit_list *bases;
 +      int i, ret = 0;
 +
 +      switch (dwim_ref(argv[0], strlen(argv[0]), sha1, &refname)) {
 +      case 0:
 +              die("No such ref: '%s'", argv[0]);
 +      case 1:
 +              break; /* good */
 +      default:
 +              die("Ambiguous refname: '%s'", argv[0]);
 +      }
 +
 +      commitname = (argc == 2) ? argv[1] : "HEAD";
 +      if (get_sha1(commitname, sha1))
 +              die("Not a valid object name: '%s'", commitname);
 +
 +      derived = lookup_commit_reference(sha1);
 +      memset(&revs, 0, sizeof(revs));
 +      revs.initial = 1;
 +      for_each_reflog_ent(refname, collect_one_reflog_ent, &revs);
 +
 +      for (i = 0; i < revs.nr; i++)
 +              revs.commit[i]->object.flags &= ~TMP_MARK;
 +
 +      bases = get_merge_bases_many(derived, revs.nr, revs.commit, 0);
 +
 +      /*
 +       * There should be one and only one merge base, when we found
 +       * a common ancestor among reflog entries.
 +       */
 +      if (!bases || bases->next) {
 +              ret = 1;
 +              goto cleanup_return;
 +      }
 +
 +      /* And the found one must be one of the reflog entries */
 +      for (i = 0; i < revs.nr; i++)
 +              if (&bases->item->object == &revs.commit[i]->object)
 +                      break; /* found */
 +      if (revs.nr <= i) {
 +              ret = 1; /* not found */
 +              goto cleanup_return;
 +      }
 +
 +      printf("%s\n", sha1_to_hex(bases->item->object.sha1));
 +
 +cleanup_return:
 +      free_commit_list(bases);
 +      return ret;
 +}
 +
  int cmd_merge_base(int argc, const char **argv, const char *prefix)
  {
        struct commit **rev;
        int rev_nr = 0;
        int show_all = 0;
 -      int octopus = 0;
 -      int reduce = 0;
 -      int is_ancestor = 0;
 +      int cmdmode = 0;
  
        struct option options[] = {
 -              OPT_BOOLEAN('a', "all", &show_all, N_("output all common ancestors")),
 -              OPT_BOOLEAN(0, "octopus", &octopus, N_("find ancestors for a single n-way merge")),
 -              OPT_BOOLEAN(0, "independent", &reduce, N_("list revs not reachable from others")),
 -              OPT_BOOLEAN(0, "is-ancestor", &is_ancestor,
 -                          N_("is the first one ancestor of the other?")),
 +              OPT_BOOL('a', "all", &show_all, N_("output all common ancestors")),
 +              OPT_CMDMODE(0, "octopus", &cmdmode,
 +                          N_("find ancestors for a single n-way merge"), 'o'),
 +              OPT_CMDMODE(0, "independent", &cmdmode,
 +                          N_("list revs not reachable from others"), 'r'),
 +              OPT_CMDMODE(0, "is-ancestor", &cmdmode,
 +                          N_("is the first one ancestor of the other?"), 'a'),
 +              OPT_CMDMODE(0, "fork-point", &cmdmode,
 +                          N_("find where <commit> forked from reflog of <ref>"), 'f'),
                OPT_END()
        };
  
        git_config(git_default_config, NULL);
        argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0);
 -      if (!octopus && !reduce && argc < 2)
 -              usage_with_options(merge_base_usage, options);
 -      if (is_ancestor && (show_all || octopus || reduce))
 -              die("--is-ancestor cannot be used with other options");
 -      if (is_ancestor)
 +
 +      if (cmdmode == 'a') {
 +              if (argc < 2)
 +                      usage_with_options(merge_base_usage, options);
 +              if (show_all)
 +                      die("--is-ancestor cannot be used with --all");
                return handle_is_ancestor(argc, argv);
 -      if (reduce && (show_all || octopus))
 -              die("--independent cannot be used with other options");
 +      }
  
 -      if (octopus)
 +      if (cmdmode == 'r' && show_all)
 +              die("--independent cannot be used with --all");
 +
-       if (cmdmode == 'r' || cmdmode == 'o')
-               return handle_octopus(argc, argv, cmdmode == 'r', show_all);
++      if (cmdmode == 'o')
+               return handle_octopus(argc, argv, show_all);
 -      else if (reduce)
++
++      if (cmdmode == 'r')
+               return handle_independent(argc, argv);
  
 +      if (cmdmode == 'f') {
 +              if (argc < 1 || 2 < argc)
 +                      usage_with_options(merge_base_usage, options);
 +              return handle_fork_point(argc, argv);
 +      }
 +
 +      if (argc < 2)
 +              usage_with_options(merge_base_usage, options);
 +
        rev = xmalloc(argc * sizeof(*rev));
        while (argc-- > 0)
                rev[rev_nr++] = get_commit_reference(*argv++);
diff --combined t/t6010-merge-base.sh
index 30a68335b3a69a8189648213606ddeb32b97d99e,abb5728b34b5eaba80d62f0e7d2b2c872dad4118..39b3238da211957eeec36a9c7b911cd5942de6dd
@@@ -230,32 -230,43 +230,71 @@@ test_expect_success 'criss-cross merge-
        test_cmp expected.sorted actual.sorted
  '
  
 +test_expect_success 'using reflog to find the fork point' '
 +      git reset --hard &&
 +      git checkout -b base $E &&
 +
 +      (
 +              for count in 1 2 3
 +              do
 +                      git commit --allow-empty -m "Base commit #$count" &&
 +                      git rev-parse HEAD >expect$count &&
 +                      git checkout -B derived &&
 +                      git commit --allow-empty -m "Derived #$count" &&
 +                      git rev-parse HEAD >derived$count &&
 +                      git checkout -B base $E || exit 1
 +              done
 +
 +              for count in 1 2 3
 +              do
 +                      git merge-base --fork-point base $(cat derived$count) >actual &&
 +                      test_cmp expect$count actual || exit 1
 +              done
 +
 +      ) &&
 +      # check that we correctly default to HEAD
 +      git checkout derived &&
 +      git merge-base --fork-point base >actual &&
 +      test_cmp expect3 actual
 +'
 +
+ test_expect_success 'merge-base --octopus --all for complex tree' '
+       # Best common ancestor for JE, JAA and JDD is JC
+       #             JE
+       #            / |
+       #           /  |
+       #          /   |
+       #  JAA    /    |
+       #   |\   /     |
+       #   | \  | JDD |
+       #   |  \ |/ |  |
+       #   |   JC JD  |
+       #   |    | /|  |
+       #   |    |/ |  |
+       #  JA    |  |  |
+       #   |\  /|  |  |
+       #   X JB |  X  X
+       #   \  \ | /   /
+       #    \__\|/___/
+       #        J
+       test_commit J &&
+       test_commit JB &&
+       git reset --hard J &&
+       test_commit JC &&
+       git reset --hard J &&
+       test_commit JTEMP1 &&
+       test_merge JA JB &&
+       test_merge JAA JC &&
+       git reset --hard J &&
+       test_commit JTEMP2 &&
+       test_merge JD JB &&
+       test_merge JDD JC &&
+       git reset --hard J &&
+       test_commit JTEMP3 &&
+       test_merge JE JC &&
+       git rev-parse JC >expected &&
+       git merge-base --all --octopus JAA JDD JE >actual &&
+       test_cmp expected actual
+ '
  test_done