builtin / merge-base.con commit worktree: fix "add -B" (0ebf4a2)
   1#include "builtin.h"
   2#include "cache.h"
   3#include "commit.h"
   4#include "refs.h"
   5#include "diff.h"
   6#include "revision.h"
   7#include "parse-options.h"
   8
   9static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
  10{
  11        struct commit_list *result;
  12
  13        result = get_merge_bases_many_dirty(rev[0], rev_nr - 1, rev + 1);
  14
  15        if (!result)
  16                return 1;
  17
  18        while (result) {
  19                printf("%s\n", oid_to_hex(&result->item->object.oid));
  20                if (!show_all)
  21                        return 0;
  22                result = result->next;
  23        }
  24
  25        return 0;
  26}
  27
  28static const char * const merge_base_usage[] = {
  29        N_("git merge-base [-a | --all] <commit> <commit>..."),
  30        N_("git merge-base [-a | --all] --octopus <commit>..."),
  31        N_("git merge-base --independent <commit>..."),
  32        N_("git merge-base --is-ancestor <commit> <commit>"),
  33        N_("git merge-base --fork-point <ref> [<commit>]"),
  34        NULL
  35};
  36
  37static struct commit *get_commit_reference(const char *arg)
  38{
  39        unsigned char revkey[20];
  40        struct commit *r;
  41
  42        if (get_sha1(arg, revkey))
  43                die("Not a valid object name %s", arg);
  44        r = lookup_commit_reference(revkey);
  45        if (!r)
  46                die("Not a valid commit name %s", arg);
  47
  48        return r;
  49}
  50
  51static int handle_independent(int count, const char **args)
  52{
  53        struct commit_list *revs = NULL;
  54        struct commit_list *result;
  55        int i;
  56
  57        for (i = count - 1; i >= 0; i--)
  58                commit_list_insert(get_commit_reference(args[i]), &revs);
  59
  60        result = reduce_heads(revs);
  61        if (!result)
  62                return 1;
  63
  64        while (result) {
  65                printf("%s\n", oid_to_hex(&result->item->object.oid));
  66                result = result->next;
  67        }
  68        return 0;
  69}
  70
  71static int handle_octopus(int count, const char **args, int show_all)
  72{
  73        struct commit_list *revs = NULL;
  74        struct commit_list *result;
  75        int i;
  76
  77        for (i = count - 1; i >= 0; i--)
  78                commit_list_insert(get_commit_reference(args[i]), &revs);
  79
  80        result = reduce_heads(get_octopus_merge_bases(revs));
  81
  82        if (!result)
  83                return 1;
  84
  85        while (result) {
  86                printf("%s\n", oid_to_hex(&result->item->object.oid));
  87                if (!show_all)
  88                        return 0;
  89                result = result->next;
  90        }
  91
  92        return 0;
  93}
  94
  95static int handle_is_ancestor(int argc, const char **argv)
  96{
  97        struct commit *one, *two;
  98
  99        if (argc != 2)
 100                die("--is-ancestor takes exactly two commits");
 101        one = get_commit_reference(argv[0]);
 102        two = get_commit_reference(argv[1]);
 103        if (in_merge_bases(one, two))
 104                return 0;
 105        else
 106                return 1;
 107}
 108
 109struct rev_collect {
 110        struct commit **commit;
 111        int nr;
 112        int alloc;
 113        unsigned int initial : 1;
 114};
 115
 116static void add_one_commit(unsigned char *sha1, struct rev_collect *revs)
 117{
 118        struct commit *commit;
 119
 120        if (is_null_sha1(sha1))
 121                return;
 122
 123        commit = lookup_commit(sha1);
 124        if (!commit ||
 125            (commit->object.flags & TMP_MARK) ||
 126            parse_commit(commit))
 127                return;
 128
 129        ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc);
 130        revs->commit[revs->nr++] = commit;
 131        commit->object.flags |= TMP_MARK;
 132}
 133
 134static int collect_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
 135                                  const char *ident, unsigned long timestamp,
 136                                  int tz, const char *message, void *cbdata)
 137{
 138        struct rev_collect *revs = cbdata;
 139
 140        if (revs->initial) {
 141                revs->initial = 0;
 142                add_one_commit(osha1, revs);
 143        }
 144        add_one_commit(nsha1, revs);
 145        return 0;
 146}
 147
 148static int handle_fork_point(int argc, const char **argv)
 149{
 150        unsigned char sha1[20];
 151        char *refname;
 152        const char *commitname;
 153        struct rev_collect revs;
 154        struct commit *derived;
 155        struct commit_list *bases;
 156        int i, ret = 0;
 157
 158        switch (dwim_ref(argv[0], strlen(argv[0]), sha1, &refname)) {
 159        case 0:
 160                die("No such ref: '%s'", argv[0]);
 161        case 1:
 162                break; /* good */
 163        default:
 164                die("Ambiguous refname: '%s'", argv[0]);
 165        }
 166
 167        commitname = (argc == 2) ? argv[1] : "HEAD";
 168        if (get_sha1(commitname, sha1))
 169                die("Not a valid object name: '%s'", commitname);
 170
 171        derived = lookup_commit_reference(sha1);
 172        memset(&revs, 0, sizeof(revs));
 173        revs.initial = 1;
 174        for_each_reflog_ent(refname, collect_one_reflog_ent, &revs);
 175
 176        for (i = 0; i < revs.nr; i++)
 177                revs.commit[i]->object.flags &= ~TMP_MARK;
 178
 179        bases = get_merge_bases_many_dirty(derived, revs.nr, revs.commit);
 180
 181        /*
 182         * There should be one and only one merge base, when we found
 183         * a common ancestor among reflog entries.
 184         */
 185        if (!bases || bases->next) {
 186                ret = 1;
 187                goto cleanup_return;
 188        }
 189
 190        /* And the found one must be one of the reflog entries */
 191        for (i = 0; i < revs.nr; i++)
 192                if (&bases->item->object == &revs.commit[i]->object)
 193                        break; /* found */
 194        if (revs.nr <= i) {
 195                ret = 1; /* not found */
 196                goto cleanup_return;
 197        }
 198
 199        printf("%s\n", oid_to_hex(&bases->item->object.oid));
 200
 201cleanup_return:
 202        free_commit_list(bases);
 203        return ret;
 204}
 205
 206int cmd_merge_base(int argc, const char **argv, const char *prefix)
 207{
 208        struct commit **rev;
 209        int rev_nr = 0;
 210        int show_all = 0;
 211        int cmdmode = 0;
 212
 213        struct option options[] = {
 214                OPT_BOOL('a', "all", &show_all, N_("output all common ancestors")),
 215                OPT_CMDMODE(0, "octopus", &cmdmode,
 216                            N_("find ancestors for a single n-way merge"), 'o'),
 217                OPT_CMDMODE(0, "independent", &cmdmode,
 218                            N_("list revs not reachable from others"), 'r'),
 219                OPT_CMDMODE(0, "is-ancestor", &cmdmode,
 220                            N_("is the first one ancestor of the other?"), 'a'),
 221                OPT_CMDMODE(0, "fork-point", &cmdmode,
 222                            N_("find where <commit> forked from reflog of <ref>"), 'f'),
 223                OPT_END()
 224        };
 225
 226        git_config(git_default_config, NULL);
 227        argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0);
 228
 229        if (cmdmode == 'a') {
 230                if (argc < 2)
 231                        usage_with_options(merge_base_usage, options);
 232                if (show_all)
 233                        die("--is-ancestor cannot be used with --all");
 234                return handle_is_ancestor(argc, argv);
 235        }
 236
 237        if (cmdmode == 'r' && show_all)
 238                die("--independent cannot be used with --all");
 239
 240        if (cmdmode == 'o')
 241                return handle_octopus(argc, argv, show_all);
 242
 243        if (cmdmode == 'r')
 244                return handle_independent(argc, argv);
 245
 246        if (cmdmode == 'f') {
 247                if (argc < 1 || 2 < argc)
 248                        usage_with_options(merge_base_usage, options);
 249                return handle_fork_point(argc, argv);
 250        }
 251
 252        if (argc < 2)
 253                usage_with_options(merge_base_usage, options);
 254
 255        rev = xmalloc(argc * sizeof(*rev));
 256        while (argc-- > 0)
 257                rev[rev_nr++] = get_commit_reference(*argv++);
 258        return show_merge_base(rev, rev_nr, show_all);
 259}