Merge branch 'fixes'
[git] / merge-base.c
1 #include <stdlib.h>
2 #include "cache.h"
3 #include "commit.h"
4
5 #define PARENT1 1
6 #define PARENT2 2
7 #define UNINTERESTING 4
8
9 static struct commit *interesting(struct commit_list *list)
10 {
11         while (list) {
12                 struct commit *commit = list->item;
13                 list = list->next;
14                 if (commit->object.flags & UNINTERESTING)
15                         continue;
16                 return commit;
17         }
18         return NULL;
19 }
20
21 /*
22  * A pathological example of how this thing works.
23  *
24  * Suppose we had this commit graph, where chronologically
25  * the timestamp on the commit are A <= B <= C <= D <= E <= F
26  * and we are trying to figure out the merge base for E and F
27  * commits.
28  *
29  *                  F
30  *                 / \
31  *            E   A   D
32  *             \ /   /  
33  *              B   /
34  *               \ /
35  *                C
36  *
37  * First we push E and F to list to be processed.  E gets bit 1
38  * and F gets bit 2.  The list becomes:
39  *
40  *     list=F(2) E(1), result=empty
41  *
42  * Then we pop F, the newest commit, from the list.  Its flag is 2.
43  * We scan its parents, mark them reachable from the side that F is
44  * reachable from, and push them to the list:
45  *
46  *     list=E(1) D(2) A(2), result=empty
47  *
48  * Next pop E and do the same.
49  *
50  *     list=D(2) B(1) A(2), result=empty
51  *
52  * Next pop D and do the same.
53  *
54  *     list=C(2) B(1) A(2), result=empty
55  *
56  * Next pop C and do the same.
57  *
58  *     list=B(1) A(2), result=empty
59  *
60  * Now it is B's turn.  We mark its parent, C, reachable from B's side,
61  * and push it to the list:
62  *
63  *     list=C(3) A(2), result=empty
64  *
65  * Now pop C and notice it has flags==3.  It is placed on the result list,
66  * and the list now contains:
67  *
68  *     list=A(2), result=C(3)
69  *
70  * We pop A and do the same.
71  * 
72  *     list=B(3), result=C(3)
73  *
74  * Next, we pop B and something very interesting happens.  It has flags==3
75  * so it is also placed on the result list, and its parents are marked
76  * uninteresting, retroactively, and placed back on the list:
77  *
78  *    list=C(7), result=C(7) B(3)
79  * 
80  * Now, list does not have any interesting commit.  So we find the newest
81  * commit from the result list that is not marked uninteresting.  Which is
82  * commit B.
83  */
84
85 static int show_all = 0;
86
87 static int merge_base(struct commit *rev1, struct commit *rev2)
88 {
89         struct commit_list *list = NULL;
90         struct commit_list *result = NULL;
91
92         if (rev1 == rev2) {
93                 printf("%s\n", sha1_to_hex(rev1->object.sha1));
94                 return 0;
95         }
96
97         parse_commit(rev1);
98         parse_commit(rev2);
99
100         rev1->object.flags |= 1;
101         rev2->object.flags |= 2;
102         insert_by_date(rev1, &list);
103         insert_by_date(rev2, &list);
104
105         while (interesting(list)) {
106                 struct commit *commit = list->item;
107                 struct commit_list *tmp = list, *parents;
108                 int flags = commit->object.flags & 7;
109
110                 list = list->next;
111                 free(tmp);
112                 if (flags == 3) {
113                         insert_by_date(commit, &result);
114
115                         /* Mark parents of a found merge uninteresting */
116                         flags |= UNINTERESTING;
117                 }
118                 parents = commit->parents;
119                 while (parents) {
120                         struct commit *p = parents->item;
121                         parents = parents->next;
122                         if ((p->object.flags & flags) == flags)
123                                 continue;
124                         parse_commit(p);
125                         p->object.flags |= flags;
126                         insert_by_date(p, &list);
127                 }
128         }
129
130         if (!result)
131                 return 1;
132
133         while (result) {
134                 struct commit *commit = result->item;
135                 result = result->next;
136                 if (commit->object.flags & UNINTERESTING)
137                         continue;
138                 printf("%s\n", sha1_to_hex(commit->object.sha1));
139                 if (!show_all)
140                         return 0;
141                 commit->object.flags |= UNINTERESTING;
142         }
143         return 0;
144 }
145
146 static const char merge_base_usage[] =
147 "git-merge-base [--all] <commit-id> <commit-id>";
148
149 int main(int argc, char **argv)
150 {
151         struct commit *rev1, *rev2;
152         unsigned char rev1key[20], rev2key[20];
153
154         while (1 < argc && argv[1][0] == '-') {
155                 char *arg = argv[1];
156                 if (!strcmp(arg, "-a") || !strcmp(arg, "--all"))
157                         show_all = 1;
158                 else
159                         usage(merge_base_usage);
160                 argc--; argv++;
161         }
162         if (argc != 3 ||
163             get_sha1(argv[1], rev1key) ||
164             get_sha1(argv[2], rev2key))
165                 usage(merge_base_usage);
166         rev1 = lookup_commit_reference(rev1key);
167         rev2 = lookup_commit_reference(rev2key);
168         if (!rev1 || !rev2)
169                 return 1;
170         return merge_base(rev1, rev2);
171 }