Merge branch 'bc/log-decoration'
[git] / builtin / merge-base.c
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
9 static 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
28 static 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
37 static 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
51 static 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
78 static 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
92 struct rev_collect {
93         struct commit **commit;
94         int nr;
95         int alloc;
96         unsigned int initial : 1;
97 };
98
99 static 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
117 static 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
131 static 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
184 cleanup_return:
185         free_commit_list(bases);
186         return ret;
187 }
188
189 int 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 }