1#include "builtin.h"2#include "cache.h"3#include "config.h"4#include "commit.h"5#include "refs.h"6#include "diff.h"7#include "revision.h"8#include "parse-options.h"9#include "repository.h"1011static int show_merge_base(struct commit **rev, int rev_nr, int show_all)12{13struct commit_list *result, *r;1415result = get_merge_bases_many_dirty(rev[0], rev_nr - 1, rev + 1);1617if (!result)18return 1;1920for (r = result; r; r = r->next) {21printf("%s\n", oid_to_hex(&r->item->object.oid));22if (!show_all)23break;24}2526free_commit_list(result);27return 0;28}2930static const char * const merge_base_usage[] = {31N_("git merge-base [-a | --all] <commit> <commit>..."),32N_("git merge-base [-a | --all] --octopus <commit>..."),33N_("git merge-base --independent <commit>..."),34N_("git merge-base --is-ancestor <commit> <commit>"),35N_("git merge-base --fork-point <ref> [<commit>]"),36NULL37};3839static struct commit *get_commit_reference(const char *arg)40{41struct object_id revkey;42struct commit *r;4344if (get_oid(arg, &revkey))45die("Not a valid object name %s", arg);46r = lookup_commit_reference(the_repository, &revkey);47if (!r)48die("Not a valid commit name %s", arg);4950return r;51}5253static int handle_independent(int count, const char **args)54{55struct commit_list *revs = NULL, *rev;56int i;5758for (i = count - 1; i >= 0; i--)59commit_list_insert(get_commit_reference(args[i]), &revs);6061reduce_heads_replace(&revs);6263if (!revs)64return 1;6566for (rev = revs; rev; rev = rev->next)67printf("%s\n", oid_to_hex(&rev->item->object.oid));6869free_commit_list(revs);70return 0;71}7273static int handle_octopus(int count, const char **args, int show_all)74{75struct commit_list *revs = NULL;76struct commit_list *result, *rev;77int i;7879for (i = count - 1; i >= 0; i--)80commit_list_insert(get_commit_reference(args[i]), &revs);8182result = get_octopus_merge_bases(revs);83free_commit_list(revs);84reduce_heads_replace(&result);8586if (!result)87return 1;8889for (rev = result; rev; rev = rev->next) {90printf("%s\n", oid_to_hex(&rev->item->object.oid));91if (!show_all)92break;93}9495free_commit_list(result);96return 0;97}9899static int handle_is_ancestor(int argc, const char **argv)100{101struct commit *one, *two;102103if (argc != 2)104die("--is-ancestor takes exactly two commits");105one = get_commit_reference(argv[0]);106two = get_commit_reference(argv[1]);107if (in_merge_bases(one, two))108return 0;109else110return 1;111}112113struct rev_collect {114struct commit **commit;115int nr;116int alloc;117unsigned int initial : 1;118};119120static void add_one_commit(struct object_id *oid, struct rev_collect *revs)121{122struct commit *commit;123124if (is_null_oid(oid))125return;126127commit = lookup_commit(oid);128if (!commit ||129(commit->object.flags & TMP_MARK) ||130parse_commit(commit))131return;132133ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc);134revs->commit[revs->nr++] = commit;135commit->object.flags |= TMP_MARK;136}137138static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid,139const char *ident, timestamp_t timestamp,140int tz, const char *message, void *cbdata)141{142struct rev_collect *revs = cbdata;143144if (revs->initial) {145revs->initial = 0;146add_one_commit(ooid, revs);147}148add_one_commit(noid, revs);149return 0;150}151152static int handle_fork_point(int argc, const char **argv)153{154struct object_id oid;155char *refname;156const char *commitname;157struct rev_collect revs;158struct commit *derived;159struct commit_list *bases;160int i, ret = 0;161162switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) {163case 0:164die("No such ref: '%s'", argv[0]);165case 1:166break; /* good */167default:168die("Ambiguous refname: '%s'", argv[0]);169}170171commitname = (argc == 2) ? argv[1] : "HEAD";172if (get_oid(commitname, &oid))173die("Not a valid object name: '%s'", commitname);174175derived = lookup_commit_reference(the_repository, &oid);176memset(&revs, 0, sizeof(revs));177revs.initial = 1;178for_each_reflog_ent(refname, collect_one_reflog_ent, &revs);179180if (!revs.nr && !get_oid(refname, &oid))181add_one_commit(&oid, &revs);182183for (i = 0; i < revs.nr; i++)184revs.commit[i]->object.flags &= ~TMP_MARK;185186bases = get_merge_bases_many_dirty(derived, revs.nr, revs.commit);187188/*189* There should be one and only one merge base, when we found190* a common ancestor among reflog entries.191*/192if (!bases || bases->next) {193ret = 1;194goto cleanup_return;195}196197/* And the found one must be one of the reflog entries */198for (i = 0; i < revs.nr; i++)199if (&bases->item->object == &revs.commit[i]->object)200break; /* found */201if (revs.nr <= i) {202ret = 1; /* not found */203goto cleanup_return;204}205206printf("%s\n", oid_to_hex(&bases->item->object.oid));207208cleanup_return:209free_commit_list(bases);210return ret;211}212213int cmd_merge_base(int argc, const char **argv, const char *prefix)214{215struct commit **rev;216int rev_nr = 0;217int show_all = 0;218int cmdmode = 0;219220struct option options[] = {221OPT_BOOL('a', "all", &show_all, N_("output all common ancestors")),222OPT_CMDMODE(0, "octopus", &cmdmode,223N_("find ancestors for a single n-way merge"), 'o'),224OPT_CMDMODE(0, "independent", &cmdmode,225N_("list revs not reachable from others"), 'r'),226OPT_CMDMODE(0, "is-ancestor", &cmdmode,227N_("is the first one ancestor of the other?"), 'a'),228OPT_CMDMODE(0, "fork-point", &cmdmode,229N_("find where <commit> forked from reflog of <ref>"), 'f'),230OPT_END()231};232233git_config(git_default_config, NULL);234argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0);235236if (cmdmode == 'a') {237if (argc < 2)238usage_with_options(merge_base_usage, options);239if (show_all)240die("--is-ancestor cannot be used with --all");241return handle_is_ancestor(argc, argv);242}243244if (cmdmode == 'r' && show_all)245die("--independent cannot be used with --all");246247if (cmdmode == 'o')248return handle_octopus(argc, argv, show_all);249250if (cmdmode == 'r')251return handle_independent(argc, argv);252253if (cmdmode == 'f') {254if (argc < 1 || 2 < argc)255usage_with_options(merge_base_usage, options);256return handle_fork_point(argc, argv);257}258259if (argc < 2)260usage_with_options(merge_base_usage, options);261262ALLOC_ARRAY(rev, argc);263while (argc-- > 0)264rev[rev_nr++] = get_commit_reference(*argv++);265return show_merge_base(rev, rev_nr, show_all);266}