]>
Commit | Line | Data |
---|---|---|
f76412ed JH |
1 | #include <stdlib.h> |
2 | #include "cache.h" | |
3 | #include "commit.h" | |
4 | #include "refs.h" | |
5 | ||
6 | static const char show_branch_usage[] = | |
7 | "git-show-branch [--all] [--heads] [--tags] [--more=count] [<refs>...]"; | |
8 | ||
9 | #define UNINTERESTING 01 | |
10 | ||
11 | #define REV_SHIFT 2 | |
12 | #define MAX_REVS 29 /* should not exceed bits_per_int - REV_SHIFT */ | |
13 | ||
14 | static struct commit *interesting(struct commit_list *list) | |
15 | { | |
16 | while (list) { | |
17 | struct commit *commit = list->item; | |
18 | list = list->next; | |
19 | if (commit->object.flags & UNINTERESTING) | |
20 | continue; | |
21 | return commit; | |
22 | } | |
23 | return NULL; | |
24 | } | |
25 | ||
26 | static struct commit *pop_one_commit(struct commit_list **list_p) | |
27 | { | |
28 | struct commit *commit; | |
29 | struct commit_list *list; | |
30 | list = *list_p; | |
31 | commit = list->item; | |
32 | *list_p = list->next; | |
33 | free(list); | |
34 | return commit; | |
35 | } | |
36 | ||
37 | struct commit_name { | |
38 | int head_rev; /* which head's ancestor? */ | |
39 | int generation; /* how many parents away from head_rev */ | |
40 | }; | |
41 | ||
42 | /* Name the commit as nth generation ancestor of head_rev; | |
43 | * we count only the first-parent relationship for naming purposes. | |
44 | */ | |
45 | static void name_commit(struct commit *commit, int head_rev, int nth) | |
46 | { | |
47 | struct commit_name *name; | |
48 | if (!commit->object.util) | |
49 | commit->object.util = xmalloc(sizeof(struct commit_name)); | |
50 | name = commit->object.util; | |
51 | name->head_rev = head_rev; | |
52 | name->generation = nth; | |
53 | } | |
54 | ||
55 | /* Parent is the first parent of the commit. We may name it | |
56 | * as (n+1)th generation ancestor of the same head_rev as | |
57 | * commit is nth generation ancestore of, if that generation | |
58 | * number is better than the name it already has. | |
59 | */ | |
60 | static void name_parent(struct commit *commit, struct commit *parent) | |
61 | { | |
62 | struct commit_name *commit_name = commit->object.util; | |
63 | struct commit_name *parent_name = parent->object.util; | |
64 | if (!commit_name) | |
65 | return; | |
66 | if (!parent_name || | |
67 | commit_name->generation + 1 < parent_name->generation) | |
68 | name_commit(parent, commit_name->head_rev, | |
69 | commit_name->generation + 1); | |
70 | } | |
71 | ||
72 | static int mark_seen(struct commit *commit, struct commit_list **seen_p) | |
73 | { | |
74 | if (!commit->object.flags) { | |
75 | insert_by_date(commit, seen_p); | |
76 | return 1; | |
77 | } | |
78 | return 0; | |
79 | } | |
80 | ||
81 | static void join_revs(struct commit_list **list_p, | |
82 | struct commit_list **seen_p, | |
83 | int num_rev, int extra) | |
84 | { | |
85 | int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); | |
86 | int all_revs = all_mask & ~((1u << REV_SHIFT) - 1); | |
87 | ||
88 | while (*list_p) { | |
89 | struct commit_list *parents; | |
90 | struct commit *commit = pop_one_commit(list_p); | |
91 | int flags = commit->object.flags & all_mask; | |
92 | int nth_parent = 0; | |
93 | int still_interesting = !!interesting(*list_p); | |
94 | ||
95 | if (!still_interesting && extra < 0) | |
96 | break; | |
97 | ||
98 | mark_seen(commit, seen_p); | |
99 | if ((flags & all_revs) == all_revs) | |
100 | flags |= UNINTERESTING; | |
101 | parents = commit->parents; | |
102 | ||
103 | while (parents) { | |
104 | struct commit *p = parents->item; | |
105 | int this_flag = p->object.flags; | |
106 | parents = parents->next; | |
107 | nth_parent++; | |
108 | if (nth_parent == 1) | |
109 | name_parent(commit, p); | |
110 | ||
111 | if ((this_flag & flags) == flags) | |
112 | continue; | |
113 | parse_commit(p); | |
114 | if (mark_seen(p, seen_p) && !still_interesting) | |
115 | extra--; | |
116 | p->object.flags |= flags; | |
117 | insert_by_date(p, list_p); | |
118 | } | |
119 | } | |
120 | } | |
121 | ||
122 | static void show_one_commit(struct commit *commit, char **head_name) | |
123 | { | |
124 | char pretty[128], *cp; | |
125 | struct commit_name *name = commit->object.util; | |
126 | pretty_print_commit(CMIT_FMT_ONELINE, commit->buffer, ~0, | |
127 | pretty, sizeof(pretty)); | |
128 | if (!strncmp(pretty, "[PATCH] ", 8)) | |
129 | cp = pretty + 8; | |
130 | else | |
131 | cp = pretty; | |
132 | if (name && head_name) { | |
133 | printf("[%s", head_name[name->head_rev]); | |
134 | if (name->generation) | |
135 | printf("~%d", name->generation); | |
136 | printf("] "); | |
137 | } | |
138 | puts(cp); | |
139 | } | |
140 | ||
141 | static char *ref_name[MAX_REVS + 1]; | |
142 | static int ref_name_cnt; | |
143 | ||
144 | static int append_ref(const char *refname, const unsigned char *sha1) | |
145 | { | |
146 | struct commit *commit = lookup_commit_reference_gently(sha1, 1); | |
147 | if (!commit) | |
148 | return 0; | |
149 | if (MAX_REVS < ref_name_cnt) { | |
150 | fprintf(stderr, "warning: ignoring %s; " | |
151 | "cannot handle more than %d refs", | |
152 | refname, MAX_REVS); | |
153 | return 0; | |
154 | } | |
155 | ref_name[ref_name_cnt++] = strdup(refname); | |
156 | ref_name[ref_name_cnt] = NULL; | |
157 | return 0; | |
158 | } | |
159 | ||
160 | static int append_head_ref(const char *refname, const unsigned char *sha1) | |
161 | { | |
162 | if (strncmp(refname, "refs/heads/", 11)) | |
163 | return 0; | |
164 | return append_ref(refname + 5, sha1); | |
165 | } | |
166 | ||
167 | static int append_tag_ref(const char *refname, const unsigned char *sha1) | |
168 | { | |
169 | if (strncmp(refname, "refs/tags/", 10)) | |
170 | return 0; | |
171 | return append_ref(refname + 5, sha1); | |
172 | } | |
173 | ||
174 | static void snarf_refs(int head, int tag) | |
175 | { | |
176 | if (head) | |
177 | for_each_ref(append_head_ref); | |
178 | if (tag) | |
179 | for_each_ref(append_tag_ref); | |
180 | } | |
181 | ||
182 | static int rev_is_head(char *head_path, int headlen, | |
183 | char *name, | |
184 | unsigned char *head_sha1, unsigned char *sha1) | |
185 | { | |
186 | int namelen; | |
187 | if ((!head_path[0]) || memcmp(head_sha1, sha1, 20)) | |
188 | return 0; | |
189 | namelen = strlen(name); | |
190 | if ((headlen < namelen) || | |
191 | memcmp(head_path + headlen - namelen, name, namelen)) | |
192 | return 0; | |
193 | if (headlen == namelen || | |
194 | head_path[headlen - namelen - 1] == '/') | |
195 | return 1; | |
196 | return 0; | |
197 | } | |
198 | ||
199 | static int show_merge_base(struct commit_list *seen, int num_rev) | |
200 | { | |
201 | int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); | |
202 | int all_revs = all_mask & ~((1u << REV_SHIFT) - 1); | |
203 | ||
204 | while (seen) { | |
205 | struct commit *commit = pop_one_commit(&seen); | |
206 | int flags = commit->object.flags & all_mask; | |
207 | if (!(flags & UNINTERESTING) && | |
208 | ((flags & all_revs) == all_revs)) { | |
209 | puts(sha1_to_hex(commit->object.sha1)); | |
210 | return 0; | |
211 | } | |
212 | } | |
213 | return 1; | |
214 | } | |
215 | ||
216 | int main(int ac, char **av) | |
217 | { | |
218 | struct commit *rev[MAX_REVS], *commit; | |
219 | struct commit_list *list = NULL, *seen = NULL; | |
220 | int num_rev, i, extra = 0; | |
221 | int all_heads = 0, all_tags = 0; | |
222 | char head_path[128]; | |
223 | int head_path_len; | |
224 | unsigned char head_sha1[20]; | |
225 | int merge_base = 0; | |
f5e375c9 | 226 | char **label; |
f76412ed JH |
227 | |
228 | while (1 < ac && av[1][0] == '-') { | |
229 | char *arg = av[1]; | |
230 | if (!strcmp(arg, "--all")) | |
231 | all_heads = all_tags = 1; | |
232 | else if (!strcmp(arg, "--heads")) | |
233 | all_heads = 1; | |
234 | else if (!strcmp(arg, "--tags")) | |
235 | all_tags = 1; | |
236 | else if (!strcmp(arg, "--more")) | |
237 | extra = 1; | |
238 | else if (!strncmp(arg, "--more=", 7)) { | |
239 | extra = atoi(arg + 7); | |
240 | if (extra < 0) | |
241 | usage(show_branch_usage); | |
242 | } | |
243 | else if (!strcmp(arg, "--merge-base")) | |
244 | merge_base = 1; | |
245 | else | |
246 | usage(show_branch_usage); | |
247 | ac--; av++; | |
248 | } | |
249 | ac--; av++; | |
250 | ||
251 | if (all_heads + all_tags) | |
252 | snarf_refs(all_heads, all_tags); | |
253 | ||
254 | while (0 < ac) { | |
255 | unsigned char revkey[20]; | |
256 | if (get_sha1(*av, revkey)) | |
257 | die("bad sha1 reference %s", *av); | |
258 | append_ref(*av, revkey); | |
259 | ac--; av++; | |
260 | } | |
261 | ||
262 | /* If still no revs, then add heads */ | |
263 | if (!ref_name_cnt) | |
264 | snarf_refs(1, 0); | |
265 | ||
266 | for (num_rev = 0; ref_name[num_rev]; num_rev++) { | |
267 | unsigned char revkey[20]; | |
268 | ||
269 | if (MAX_REVS <= num_rev) | |
270 | die("cannot handle more than %d revs.", MAX_REVS); | |
271 | if (get_sha1(ref_name[num_rev], revkey)) | |
272 | usage(show_branch_usage); | |
273 | commit = lookup_commit_reference(revkey); | |
274 | if (!commit) | |
275 | die("cannot find commit %s (%s)", | |
276 | ref_name[num_rev], revkey); | |
277 | parse_commit(commit); | |
278 | if (!commit->object.util) | |
279 | name_commit(commit, num_rev, 0); | |
280 | mark_seen(commit, &seen); | |
281 | ||
282 | /* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1, | |
283 | * and so on. REV_SHIFT bits from bit 0 are used for | |
284 | * internal bookkeeping. | |
285 | */ | |
286 | commit->object.flags |= 1u << (num_rev + REV_SHIFT); | |
287 | insert_by_date(commit, &list); | |
288 | rev[num_rev] = commit; | |
289 | } | |
290 | join_revs(&list, &seen, num_rev, extra); | |
291 | ||
292 | head_path_len = readlink(".git/HEAD", head_path, sizeof(head_path)-1); | |
293 | if ((head_path_len < 0) || get_sha1("HEAD", head_sha1)) | |
294 | head_path[0] = 0; | |
295 | else | |
296 | head_path[head_path_len] = 0; | |
297 | ||
298 | if (merge_base) | |
299 | return show_merge_base(seen, num_rev); | |
300 | ||
f5e375c9 JH |
301 | /* Show list */ |
302 | if (1 < num_rev) { | |
f76412ed JH |
303 | for (i = 0; i < num_rev; i++) { |
304 | int j; | |
305 | int is_head = rev_is_head(head_path, | |
306 | head_path_len, | |
307 | ref_name[i], | |
308 | head_sha1, | |
309 | rev[i]->object.sha1); | |
310 | for (j = 0; j < i; j++) | |
311 | putchar(' '); | |
312 | printf("%c [%s] ", is_head ? '*' : '!', ref_name[i]); | |
313 | show_one_commit(rev[i], NULL); | |
314 | } | |
f5e375c9 JH |
315 | for (i = 0; i < num_rev; i++) |
316 | putchar('-'); | |
317 | putchar('\n'); | |
318 | } | |
319 | ||
320 | label = ref_name; | |
f76412ed JH |
321 | while (seen) { |
322 | struct commit *commit = pop_one_commit(&seen); | |
323 | int this_flag = commit->object.flags; | |
f5e375c9 JH |
324 | static char *obvious[] = { "" }; |
325 | ||
f76412ed JH |
326 | if ((this_flag & UNINTERESTING) && (--extra < 0)) |
327 | break; | |
f5e375c9 JH |
328 | if (1 < num_rev) { |
329 | for (i = 0; i < num_rev; i++) | |
330 | putchar((this_flag & (1u << (i + REV_SHIFT))) | |
331 | ? '+' : ' '); | |
332 | putchar(' '); | |
333 | } | |
334 | show_one_commit(commit, label); | |
335 | if (num_rev == 1) | |
336 | label = obvious; | |
f76412ed JH |
337 | } |
338 | return 0; | |
339 | } |