builtin / merge-base.con commit Clean up outstanding object_id transforms. (fb4e352)
   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        struct object_id revkey;
  40        struct commit *r;
  41
  42        if (get_oid(arg, &revkey))
  43                die("Not a valid object name %s", arg);
  44        r = lookup_commit_reference(revkey.hash);
  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(struct object_id *oid, struct rev_collect *revs)
 117{
 118        struct commit *commit;
 119
 120        if (is_null_oid(oid))
 121                return;
 122
 123        commit = lookup_commit(oid->hash);
 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(struct object_id *ooid, struct object_id *noid,
 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(ooid, revs);
 143        }
 144        add_one_commit(noid, revs);
 145        return 0;
 146}
 147
 148static int handle_fork_point(int argc, const char **argv)
 149{
 150        struct object_id oid;
 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]), oid.hash, &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_oid(commitname, &oid))
 169                die("Not a valid object name: '%s'", commitname);
 170
 171        derived = lookup_commit_reference(oid.hash);
 172        memset(&revs, 0, sizeof(revs));
 173        revs.initial = 1;
 174        for_each_reflog_ent(refname, collect_one_reflog_ent, &revs);
 175
 176        if (!revs.nr && !get_oid(refname, &oid))
 177                add_one_commit(&oid, &revs);
 178
 179        for (i = 0; i < revs.nr; i++)
 180                revs.commit[i]->object.flags &= ~TMP_MARK;
 181
 182        bases = get_merge_bases_many_dirty(derived, revs.nr, revs.commit);
 183
 184        /*
 185         * There should be one and only one merge base, when we found
 186         * a common ancestor among reflog entries.
 187         */
 188        if (!bases || bases->next) {
 189                ret = 1;
 190                goto cleanup_return;
 191        }
 192
 193        /* And the found one must be one of the reflog entries */
 194        for (i = 0; i < revs.nr; i++)
 195                if (&bases->item->object == &revs.commit[i]->object)
 196                        break; /* found */
 197        if (revs.nr <= i) {
 198                ret = 1; /* not found */
 199                goto cleanup_return;
 200        }
 201
 202        printf("%s\n", oid_to_hex(&bases->item->object.oid));
 203
 204cleanup_return:
 205        free_commit_list(bases);
 206        return ret;
 207}
 208
 209int cmd_merge_base(int argc, const char **argv, const char *prefix)
 210{
 211        struct commit **rev;
 212        int rev_nr = 0;
 213        int show_all = 0;
 214        int cmdmode = 0;
 215
 216        struct option options[] = {
 217                OPT_BOOL('a', "all", &show_all, N_("output all common ancestors")),
 218                OPT_CMDMODE(0, "octopus", &cmdmode,
 219                            N_("find ancestors for a single n-way merge"), 'o'),
 220                OPT_CMDMODE(0, "independent", &cmdmode,
 221                            N_("list revs not reachable from others"), 'r'),
 222                OPT_CMDMODE(0, "is-ancestor", &cmdmode,
 223                            N_("is the first one ancestor of the other?"), 'a'),
 224                OPT_CMDMODE(0, "fork-point", &cmdmode,
 225                            N_("find where <commit> forked from reflog of <ref>"), 'f'),
 226                OPT_END()
 227        };
 228
 229        git_config(git_default_config, NULL);
 230        argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0);
 231
 232        if (cmdmode == 'a') {
 233                if (argc < 2)
 234                        usage_with_options(merge_base_usage, options);
 235                if (show_all)
 236                        die("--is-ancestor cannot be used with --all");
 237                return handle_is_ancestor(argc, argv);
 238        }
 239
 240        if (cmdmode == 'r' && show_all)
 241                die("--independent cannot be used with --all");
 242
 243        if (cmdmode == 'o')
 244                return handle_octopus(argc, argv, show_all);
 245
 246        if (cmdmode == 'r')
 247                return handle_independent(argc, argv);
 248
 249        if (cmdmode == 'f') {
 250                if (argc < 1 || 2 < argc)
 251                        usage_with_options(merge_base_usage, options);
 252                return handle_fork_point(argc, argv);
 253        }
 254
 255        if (argc < 2)
 256                usage_with_options(merge_base_usage, options);
 257
 258        ALLOC_ARRAY(rev, argc);
 259        while (argc-- > 0)
 260                rev[rev_nr++] = get_commit_reference(*argv++);
 261        return show_merge_base(rev, rev_nr, show_all);
 262}