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