/src/git/builtin/show-branch.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "builtin.h" |
2 | | #include "config.h" |
3 | | #include "environment.h" |
4 | | #include "gettext.h" |
5 | | #include "hash.h" |
6 | | #include "hex.h" |
7 | | #include "pretty.h" |
8 | | #include "refs.h" |
9 | | #include "color.h" |
10 | | #include "strvec.h" |
11 | | #include "object-name.h" |
12 | | #include "parse-options.h" |
13 | | #include "repository.h" |
14 | | #include "dir.h" |
15 | | #include "commit-slab.h" |
16 | | #include "date.h" |
17 | | #include "wildmatch.h" |
18 | | |
19 | | static const char* show_branch_usage[] = { |
20 | | N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n" |
21 | | " [--current] [--color[=<when>] | --no-color] [--sparse]\n" |
22 | | " [--more=<n> | --list | --independent | --merge-base]\n" |
23 | | " [--no-name | --sha1-name] [--topics]\n" |
24 | | " [(<rev> | <glob>)...]"), |
25 | | N_("git show-branch (-g | --reflog)[=<n>[,<base>]] [--list] [<ref>]"), |
26 | | NULL |
27 | | }; |
28 | | |
29 | | static int showbranch_use_color = -1; |
30 | | |
31 | | static struct strvec default_args = STRVEC_INIT; |
32 | | |
33 | | /* |
34 | | * TODO: convert this use of commit->object.flags to commit-slab |
35 | | * instead to store a pointer to ref name directly. Then use the same |
36 | | * UNINTERESTING definition from revision.h here. |
37 | | */ |
38 | 0 | #define UNINTERESTING 01 |
39 | | |
40 | 0 | #define REV_SHIFT 2 |
41 | 0 | #define MAX_REVS (FLAG_BITS - REV_SHIFT) /* should not exceed bits_per_int - REV_SHIFT */ |
42 | | |
43 | 0 | #define DEFAULT_REFLOG 4 |
44 | | |
45 | | static const char *get_color_code(int idx) |
46 | 0 | { |
47 | 0 | if (want_color(showbranch_use_color)) |
48 | 0 | return column_colors_ansi[idx % column_colors_ansi_max]; |
49 | 0 | return ""; |
50 | 0 | } |
51 | | |
52 | | static const char *get_color_reset_code(void) |
53 | 0 | { |
54 | 0 | if (want_color(showbranch_use_color)) |
55 | 0 | return GIT_COLOR_RESET; |
56 | 0 | return ""; |
57 | 0 | } |
58 | | |
59 | | static struct commit *interesting(struct commit_list *list) |
60 | 0 | { |
61 | 0 | while (list) { |
62 | 0 | struct commit *commit = list->item; |
63 | 0 | list = list->next; |
64 | 0 | if (commit->object.flags & UNINTERESTING) |
65 | 0 | continue; |
66 | 0 | return commit; |
67 | 0 | } |
68 | 0 | return NULL; |
69 | 0 | } |
70 | | |
71 | | struct commit_name { |
72 | | const char *head_name; /* which head's ancestor? */ |
73 | | int generation; /* how many parents away from head_name */ |
74 | | }; |
75 | | |
76 | | define_commit_slab(commit_name_slab, struct commit_name *); |
77 | | static struct commit_name_slab name_slab; |
78 | | |
79 | | static struct commit_name *commit_to_name(struct commit *commit) |
80 | 0 | { |
81 | 0 | return *commit_name_slab_at(&name_slab, commit); |
82 | 0 | } |
83 | | |
84 | | |
85 | | /* Name the commit as nth generation ancestor of head_name; |
86 | | * we count only the first-parent relationship for naming purposes. |
87 | | */ |
88 | | static void name_commit(struct commit *commit, const char *head_name, int nth) |
89 | 0 | { |
90 | 0 | struct commit_name *name; |
91 | |
|
92 | 0 | name = *commit_name_slab_at(&name_slab, commit); |
93 | 0 | if (!name) { |
94 | 0 | name = xmalloc(sizeof(*name)); |
95 | 0 | *commit_name_slab_at(&name_slab, commit) = name; |
96 | 0 | } |
97 | 0 | name->head_name = head_name; |
98 | 0 | name->generation = nth; |
99 | 0 | } |
100 | | |
101 | | /* Parent is the first parent of the commit. We may name it |
102 | | * as (n+1)th generation ancestor of the same head_name as |
103 | | * commit is nth generation ancestor of, if that generation |
104 | | * number is better than the name it already has. |
105 | | */ |
106 | | static void name_parent(struct commit *commit, struct commit *parent) |
107 | 0 | { |
108 | 0 | struct commit_name *commit_name = commit_to_name(commit); |
109 | 0 | struct commit_name *parent_name = commit_to_name(parent); |
110 | 0 | if (!commit_name) |
111 | 0 | return; |
112 | 0 | if (!parent_name || |
113 | 0 | commit_name->generation + 1 < parent_name->generation) |
114 | 0 | name_commit(parent, commit_name->head_name, |
115 | 0 | commit_name->generation + 1); |
116 | 0 | } |
117 | | |
118 | | static int name_first_parent_chain(struct commit *c) |
119 | 0 | { |
120 | 0 | int i = 0; |
121 | 0 | while (c) { |
122 | 0 | struct commit *p; |
123 | 0 | if (!commit_to_name(c)) |
124 | 0 | break; |
125 | 0 | if (!c->parents) |
126 | 0 | break; |
127 | 0 | p = c->parents->item; |
128 | 0 | if (!commit_to_name(p)) { |
129 | 0 | name_parent(c, p); |
130 | 0 | i++; |
131 | 0 | } |
132 | 0 | else |
133 | 0 | break; |
134 | 0 | c = p; |
135 | 0 | } |
136 | 0 | return i; |
137 | 0 | } |
138 | | |
139 | | static void name_commits(struct commit_list *list, |
140 | | struct commit **rev, |
141 | | char **ref_name, |
142 | | int num_rev) |
143 | 0 | { |
144 | 0 | struct commit_list *cl; |
145 | 0 | struct commit *c; |
146 | 0 | int i; |
147 | | |
148 | | /* First give names to the given heads */ |
149 | 0 | for (cl = list; cl; cl = cl->next) { |
150 | 0 | c = cl->item; |
151 | 0 | if (commit_to_name(c)) |
152 | 0 | continue; |
153 | 0 | for (i = 0; i < num_rev; i++) { |
154 | 0 | if (rev[i] == c) { |
155 | 0 | name_commit(c, ref_name[i], 0); |
156 | 0 | break; |
157 | 0 | } |
158 | 0 | } |
159 | 0 | } |
160 | | |
161 | | /* Then commits on the first parent ancestry chain */ |
162 | 0 | do { |
163 | 0 | i = 0; |
164 | 0 | for (cl = list; cl; cl = cl->next) { |
165 | 0 | i += name_first_parent_chain(cl->item); |
166 | 0 | } |
167 | 0 | } while (i); |
168 | | |
169 | | /* Finally, any unnamed commits */ |
170 | 0 | do { |
171 | 0 | i = 0; |
172 | 0 | for (cl = list; cl; cl = cl->next) { |
173 | 0 | struct commit_list *parents; |
174 | 0 | struct commit_name *n; |
175 | 0 | int nth; |
176 | 0 | c = cl->item; |
177 | 0 | if (!commit_to_name(c)) |
178 | 0 | continue; |
179 | 0 | n = commit_to_name(c); |
180 | 0 | parents = c->parents; |
181 | 0 | nth = 0; |
182 | 0 | while (parents) { |
183 | 0 | struct commit *p = parents->item; |
184 | 0 | struct strbuf newname = STRBUF_INIT; |
185 | 0 | parents = parents->next; |
186 | 0 | nth++; |
187 | 0 | if (commit_to_name(p)) |
188 | 0 | continue; |
189 | 0 | switch (n->generation) { |
190 | 0 | case 0: |
191 | 0 | strbuf_addstr(&newname, n->head_name); |
192 | 0 | break; |
193 | 0 | case 1: |
194 | 0 | strbuf_addf(&newname, "%s^", n->head_name); |
195 | 0 | break; |
196 | 0 | default: |
197 | 0 | strbuf_addf(&newname, "%s~%d", |
198 | 0 | n->head_name, n->generation); |
199 | 0 | break; |
200 | 0 | } |
201 | 0 | if (nth == 1) |
202 | 0 | strbuf_addch(&newname, '^'); |
203 | 0 | else |
204 | 0 | strbuf_addf(&newname, "^%d", nth); |
205 | 0 | name_commit(p, strbuf_detach(&newname, NULL), 0); |
206 | 0 | i++; |
207 | 0 | name_first_parent_chain(p); |
208 | 0 | } |
209 | 0 | } |
210 | 0 | } while (i); |
211 | 0 | } |
212 | | |
213 | | static int mark_seen(struct commit *commit, struct commit_list **seen_p) |
214 | 0 | { |
215 | 0 | if (!commit->object.flags) { |
216 | 0 | commit_list_insert(commit, seen_p); |
217 | 0 | return 1; |
218 | 0 | } |
219 | 0 | return 0; |
220 | 0 | } |
221 | | |
222 | | static void join_revs(struct commit_list **list_p, |
223 | | struct commit_list **seen_p, |
224 | | int num_rev, int extra) |
225 | 0 | { |
226 | 0 | int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); |
227 | 0 | int all_revs = all_mask & ~((1u << REV_SHIFT) - 1); |
228 | |
|
229 | 0 | while (*list_p) { |
230 | 0 | struct commit_list *parents; |
231 | 0 | int still_interesting = !!interesting(*list_p); |
232 | 0 | struct commit *commit = pop_commit(list_p); |
233 | 0 | int flags = commit->object.flags & all_mask; |
234 | |
|
235 | 0 | if (!still_interesting && extra <= 0) |
236 | 0 | break; |
237 | | |
238 | 0 | mark_seen(commit, seen_p); |
239 | 0 | if ((flags & all_revs) == all_revs) |
240 | 0 | flags |= UNINTERESTING; |
241 | 0 | parents = commit->parents; |
242 | |
|
243 | 0 | while (parents) { |
244 | 0 | struct commit *p = parents->item; |
245 | 0 | int this_flag = p->object.flags; |
246 | 0 | parents = parents->next; |
247 | 0 | if ((this_flag & flags) == flags) |
248 | 0 | continue; |
249 | 0 | repo_parse_commit(the_repository, p); |
250 | 0 | if (mark_seen(p, seen_p) && !still_interesting) |
251 | 0 | extra--; |
252 | 0 | p->object.flags |= flags; |
253 | 0 | commit_list_insert_by_date(p, list_p); |
254 | 0 | } |
255 | 0 | } |
256 | | |
257 | | /* |
258 | | * Postprocess to complete well-poisoning. |
259 | | * |
260 | | * At this point we have all the commits we have seen in |
261 | | * seen_p list. Mark anything that can be reached from |
262 | | * uninteresting commits not interesting. |
263 | | */ |
264 | 0 | for (;;) { |
265 | 0 | int changed = 0; |
266 | 0 | struct commit_list *s; |
267 | 0 | for (s = *seen_p; s; s = s->next) { |
268 | 0 | struct commit *c = s->item; |
269 | 0 | struct commit_list *parents; |
270 | |
|
271 | 0 | if (((c->object.flags & all_revs) != all_revs) && |
272 | 0 | !(c->object.flags & UNINTERESTING)) |
273 | 0 | continue; |
274 | | |
275 | | /* The current commit is either a merge base or |
276 | | * already uninteresting one. Mark its parents |
277 | | * as uninteresting commits _only_ if they are |
278 | | * already parsed. No reason to find new ones |
279 | | * here. |
280 | | */ |
281 | 0 | parents = c->parents; |
282 | 0 | while (parents) { |
283 | 0 | struct commit *p = parents->item; |
284 | 0 | parents = parents->next; |
285 | 0 | if (!(p->object.flags & UNINTERESTING)) { |
286 | 0 | p->object.flags |= UNINTERESTING; |
287 | 0 | changed = 1; |
288 | 0 | } |
289 | 0 | } |
290 | 0 | } |
291 | 0 | if (!changed) |
292 | 0 | break; |
293 | 0 | } |
294 | 0 | } |
295 | | |
296 | | static void show_one_commit(struct commit *commit, int no_name) |
297 | 0 | { |
298 | 0 | struct strbuf pretty = STRBUF_INIT; |
299 | 0 | const char *pretty_str = "(unavailable)"; |
300 | 0 | struct commit_name *name = commit_to_name(commit); |
301 | |
|
302 | 0 | if (commit->object.parsed) { |
303 | 0 | pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty); |
304 | 0 | pretty_str = pretty.buf; |
305 | 0 | } |
306 | 0 | skip_prefix(pretty_str, "[PATCH] ", &pretty_str); |
307 | |
|
308 | 0 | if (!no_name) { |
309 | 0 | if (name && name->head_name) { |
310 | 0 | printf("[%s", name->head_name); |
311 | 0 | if (name->generation) { |
312 | 0 | if (name->generation == 1) |
313 | 0 | printf("^"); |
314 | 0 | else |
315 | 0 | printf("~%d", name->generation); |
316 | 0 | } |
317 | 0 | printf("] "); |
318 | 0 | } |
319 | 0 | else |
320 | 0 | printf("[%s] ", |
321 | 0 | repo_find_unique_abbrev(the_repository, &commit->object.oid, |
322 | 0 | DEFAULT_ABBREV)); |
323 | 0 | } |
324 | 0 | puts(pretty_str); |
325 | 0 | strbuf_release(&pretty); |
326 | 0 | } |
327 | | |
328 | | static char *ref_name[MAX_REVS + 1]; |
329 | | static int ref_name_cnt; |
330 | | |
331 | | static const char *find_digit_prefix(const char *s, int *v) |
332 | 0 | { |
333 | 0 | const char *p; |
334 | 0 | int ver; |
335 | 0 | char ch; |
336 | |
|
337 | 0 | for (p = s, ver = 0; |
338 | 0 | '0' <= (ch = *p) && ch <= '9'; |
339 | 0 | p++) |
340 | 0 | ver = ver * 10 + ch - '0'; |
341 | 0 | *v = ver; |
342 | 0 | return p; |
343 | 0 | } |
344 | | |
345 | | |
346 | | static int version_cmp(const char *a, const char *b) |
347 | 0 | { |
348 | 0 | while (1) { |
349 | 0 | int va, vb; |
350 | |
|
351 | 0 | a = find_digit_prefix(a, &va); |
352 | 0 | b = find_digit_prefix(b, &vb); |
353 | 0 | if (va != vb) |
354 | 0 | return va - vb; |
355 | | |
356 | 0 | while (1) { |
357 | 0 | int ca = *a; |
358 | 0 | int cb = *b; |
359 | 0 | if ('0' <= ca && ca <= '9') |
360 | 0 | ca = 0; |
361 | 0 | if ('0' <= cb && cb <= '9') |
362 | 0 | cb = 0; |
363 | 0 | if (ca != cb) |
364 | 0 | return ca - cb; |
365 | 0 | if (!ca) |
366 | 0 | break; |
367 | 0 | a++; |
368 | 0 | b++; |
369 | 0 | } |
370 | 0 | if (!*a && !*b) |
371 | 0 | return 0; |
372 | 0 | } |
373 | 0 | } |
374 | | |
375 | | static int compare_ref_name(const void *a_, const void *b_) |
376 | 0 | { |
377 | 0 | const char * const*a = a_, * const*b = b_; |
378 | 0 | return version_cmp(*a, *b); |
379 | 0 | } |
380 | | |
381 | | static void sort_ref_range(int bottom, int top) |
382 | 0 | { |
383 | 0 | QSORT(ref_name + bottom, top - bottom, compare_ref_name); |
384 | 0 | } |
385 | | |
386 | | static int append_ref(const char *refname, const struct object_id *oid, |
387 | | int allow_dups) |
388 | 0 | { |
389 | 0 | struct commit *commit = lookup_commit_reference_gently(the_repository, |
390 | 0 | oid, 1); |
391 | 0 | int i; |
392 | |
|
393 | 0 | if (!commit) |
394 | 0 | return 0; |
395 | | |
396 | 0 | if (!allow_dups) { |
397 | | /* Avoid adding the same thing twice */ |
398 | 0 | for (i = 0; i < ref_name_cnt; i++) |
399 | 0 | if (!strcmp(refname, ref_name[i])) |
400 | 0 | return 0; |
401 | 0 | } |
402 | 0 | if (MAX_REVS <= ref_name_cnt) { |
403 | 0 | warning(Q_("ignoring %s; cannot handle more than %d ref", |
404 | 0 | "ignoring %s; cannot handle more than %d refs", |
405 | 0 | MAX_REVS), refname, MAX_REVS); |
406 | 0 | return 0; |
407 | 0 | } |
408 | 0 | ref_name[ref_name_cnt++] = xstrdup(refname); |
409 | 0 | ref_name[ref_name_cnt] = NULL; |
410 | 0 | return 0; |
411 | 0 | } |
412 | | |
413 | | static int append_head_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, |
414 | | int flag UNUSED, void *cb_data UNUSED) |
415 | 0 | { |
416 | 0 | struct object_id tmp; |
417 | 0 | int ofs = 11; |
418 | 0 | if (!starts_with(refname, "refs/heads/")) |
419 | 0 | return 0; |
420 | | /* If both heads/foo and tags/foo exists, get_sha1 would |
421 | | * get confused. |
422 | | */ |
423 | 0 | if (repo_get_oid(the_repository, refname + ofs, &tmp) || !oideq(&tmp, oid)) |
424 | 0 | ofs = 5; |
425 | 0 | return append_ref(refname + ofs, oid, 0); |
426 | 0 | } |
427 | | |
428 | | static int append_remote_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, |
429 | | int flag UNUSED, void *cb_data UNUSED) |
430 | 0 | { |
431 | 0 | struct object_id tmp; |
432 | 0 | int ofs = 13; |
433 | 0 | if (!starts_with(refname, "refs/remotes/")) |
434 | 0 | return 0; |
435 | | /* If both heads/foo and tags/foo exists, get_sha1 would |
436 | | * get confused. |
437 | | */ |
438 | 0 | if (repo_get_oid(the_repository, refname + ofs, &tmp) || !oideq(&tmp, oid)) |
439 | 0 | ofs = 5; |
440 | 0 | return append_ref(refname + ofs, oid, 0); |
441 | 0 | } |
442 | | |
443 | | static int append_tag_ref(const char *refname, const struct object_id *oid, |
444 | | int flag UNUSED, void *cb_data UNUSED) |
445 | 0 | { |
446 | 0 | if (!starts_with(refname, "refs/tags/")) |
447 | 0 | return 0; |
448 | 0 | return append_ref(refname + 5, oid, 0); |
449 | 0 | } |
450 | | |
451 | | static const char *match_ref_pattern = NULL; |
452 | | static int match_ref_slash = 0; |
453 | | |
454 | | static int append_matching_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, |
455 | | int flag, void *cb_data) |
456 | 0 | { |
457 | | /* we want to allow pattern hold/<asterisk> to show all |
458 | | * branches under refs/heads/hold/, and v0.99.9? to show |
459 | | * refs/tags/v0.99.9a and friends. |
460 | | */ |
461 | 0 | const char *tail; |
462 | 0 | int slash = count_slashes(refname); |
463 | 0 | for (tail = refname; *tail && match_ref_slash < slash; ) |
464 | 0 | if (*tail++ == '/') |
465 | 0 | slash--; |
466 | 0 | if (!*tail) |
467 | 0 | return 0; |
468 | 0 | if (wildmatch(match_ref_pattern, tail, 0)) |
469 | 0 | return 0; |
470 | 0 | if (starts_with(refname, "refs/heads/")) |
471 | 0 | return append_head_ref(refname, NULL, oid, flag, cb_data); |
472 | 0 | if (starts_with(refname, "refs/tags/")) |
473 | 0 | return append_tag_ref(refname, oid, flag, cb_data); |
474 | 0 | return append_ref(refname, oid, 0); |
475 | 0 | } |
476 | | |
477 | | static void snarf_refs(int head, int remotes) |
478 | 0 | { |
479 | 0 | if (head) { |
480 | 0 | int orig_cnt = ref_name_cnt; |
481 | |
|
482 | 0 | refs_for_each_ref(get_main_ref_store(the_repository), |
483 | 0 | append_head_ref, NULL); |
484 | 0 | sort_ref_range(orig_cnt, ref_name_cnt); |
485 | 0 | } |
486 | 0 | if (remotes) { |
487 | 0 | int orig_cnt = ref_name_cnt; |
488 | |
|
489 | 0 | refs_for_each_ref(get_main_ref_store(the_repository), |
490 | 0 | append_remote_ref, NULL); |
491 | 0 | sort_ref_range(orig_cnt, ref_name_cnt); |
492 | 0 | } |
493 | 0 | } |
494 | | |
495 | | static int rev_is_head(const char *head, const char *name) |
496 | 0 | { |
497 | 0 | if (!head) |
498 | 0 | return 0; |
499 | 0 | skip_prefix(head, "refs/heads/", &head); |
500 | 0 | if (!skip_prefix(name, "refs/heads/", &name)) |
501 | 0 | skip_prefix(name, "heads/", &name); |
502 | 0 | return !strcmp(head, name); |
503 | 0 | } |
504 | | |
505 | | static int show_merge_base(const struct commit_list *seen, int num_rev) |
506 | 0 | { |
507 | 0 | int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); |
508 | 0 | int all_revs = all_mask & ~((1u << REV_SHIFT) - 1); |
509 | 0 | int exit_status = 1; |
510 | |
|
511 | 0 | for (const struct commit_list *s = seen; s; s = s->next) { |
512 | 0 | struct commit *commit = s->item; |
513 | 0 | int flags = commit->object.flags & all_mask; |
514 | 0 | if (!(flags & UNINTERESTING) && |
515 | 0 | ((flags & all_revs) == all_revs)) { |
516 | 0 | puts(oid_to_hex(&commit->object.oid)); |
517 | 0 | exit_status = 0; |
518 | 0 | commit->object.flags |= UNINTERESTING; |
519 | 0 | } |
520 | 0 | } |
521 | 0 | return exit_status; |
522 | 0 | } |
523 | | |
524 | | static int show_independent(struct commit **rev, |
525 | | int num_rev, |
526 | | unsigned int *rev_mask) |
527 | 0 | { |
528 | 0 | int i; |
529 | |
|
530 | 0 | for (i = 0; i < num_rev; i++) { |
531 | 0 | struct commit *commit = rev[i]; |
532 | 0 | unsigned int flag = rev_mask[i]; |
533 | |
|
534 | 0 | if (commit->object.flags == flag) |
535 | 0 | puts(oid_to_hex(&commit->object.oid)); |
536 | 0 | commit->object.flags |= UNINTERESTING; |
537 | 0 | } |
538 | 0 | return 0; |
539 | 0 | } |
540 | | |
541 | | static void append_one_rev(const char *av) |
542 | 0 | { |
543 | 0 | struct object_id revkey; |
544 | 0 | if (!repo_get_oid(the_repository, av, &revkey)) { |
545 | 0 | append_ref(av, &revkey, 0); |
546 | 0 | return; |
547 | 0 | } |
548 | 0 | if (strpbrk(av, "*?[")) { |
549 | | /* glob style match */ |
550 | 0 | int saved_matches = ref_name_cnt; |
551 | |
|
552 | 0 | match_ref_pattern = av; |
553 | 0 | match_ref_slash = count_slashes(av); |
554 | 0 | refs_for_each_ref(get_main_ref_store(the_repository), |
555 | 0 | append_matching_ref, NULL); |
556 | 0 | if (saved_matches == ref_name_cnt && |
557 | 0 | ref_name_cnt < MAX_REVS) |
558 | 0 | error(_("no matching refs with %s"), av); |
559 | 0 | sort_ref_range(saved_matches, ref_name_cnt); |
560 | 0 | return; |
561 | 0 | } |
562 | 0 | die("bad sha1 reference %s", av); |
563 | 0 | } |
564 | | |
565 | | static int git_show_branch_config(const char *var, const char *value, |
566 | | const struct config_context *ctx, void *cb) |
567 | 0 | { |
568 | 0 | if (!strcmp(var, "showbranch.default")) { |
569 | 0 | if (!value) |
570 | 0 | return config_error_nonbool(var); |
571 | | /* |
572 | | * default_arg is now passed to parse_options(), so we need to |
573 | | * mimic the real argv a bit better. |
574 | | */ |
575 | 0 | if (!default_args.nr) |
576 | 0 | strvec_push(&default_args, "show-branch"); |
577 | 0 | strvec_push(&default_args, value); |
578 | 0 | return 0; |
579 | 0 | } |
580 | | |
581 | 0 | if (!strcmp(var, "color.showbranch")) { |
582 | 0 | showbranch_use_color = git_config_colorbool(var, value); |
583 | 0 | return 0; |
584 | 0 | } |
585 | | |
586 | 0 | if (git_color_config(var, value, cb) < 0) |
587 | 0 | return -1; |
588 | | |
589 | 0 | return git_default_config(var, value, ctx, cb); |
590 | 0 | } |
591 | | |
592 | | static int omit_in_dense(struct commit *commit, struct commit **rev, int n) |
593 | 0 | { |
594 | | /* If the commit is tip of the named branches, do not |
595 | | * omit it. |
596 | | * Otherwise, if it is a merge that is reachable from only one |
597 | | * tip, it is not that interesting. |
598 | | */ |
599 | 0 | int i, flag, count; |
600 | 0 | for (i = 0; i < n; i++) |
601 | 0 | if (rev[i] == commit) |
602 | 0 | return 0; |
603 | 0 | flag = commit->object.flags; |
604 | 0 | for (i = count = 0; i < n; i++) { |
605 | 0 | if (flag & (1u << (i + REV_SHIFT))) |
606 | 0 | count++; |
607 | 0 | } |
608 | 0 | if (count == 1) |
609 | 0 | return 1; |
610 | 0 | return 0; |
611 | 0 | } |
612 | | |
613 | | static int reflog = 0; |
614 | | |
615 | | static int parse_reflog_param(const struct option *opt, const char *arg, |
616 | | int unset) |
617 | 0 | { |
618 | 0 | char *ep; |
619 | 0 | const char **base = (const char **)opt->value; |
620 | 0 | BUG_ON_OPT_NEG(unset); |
621 | 0 | if (!arg) |
622 | 0 | arg = ""; |
623 | 0 | reflog = strtoul(arg, &ep, 10); |
624 | 0 | if (*ep == ',') |
625 | 0 | *base = ep + 1; |
626 | 0 | else if (*ep) |
627 | 0 | return error("unrecognized reflog param '%s'", arg); |
628 | 0 | else |
629 | 0 | *base = NULL; |
630 | 0 | if (reflog <= 0) |
631 | 0 | reflog = DEFAULT_REFLOG; |
632 | 0 | return 0; |
633 | 0 | } |
634 | | |
635 | | int cmd_show_branch(int ac, const char **av, const char *prefix) |
636 | 0 | { |
637 | 0 | struct commit *rev[MAX_REVS], *commit; |
638 | 0 | char *reflog_msg[MAX_REVS] = {0}; |
639 | 0 | struct commit_list *list = NULL, *seen = NULL; |
640 | 0 | unsigned int rev_mask[MAX_REVS]; |
641 | 0 | int num_rev, i, extra = 0; |
642 | 0 | int all_heads = 0, all_remotes = 0; |
643 | 0 | int all_mask, all_revs; |
644 | 0 | enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER; |
645 | 0 | char *head; |
646 | 0 | struct object_id head_oid; |
647 | 0 | int merge_base = 0; |
648 | 0 | int independent = 0; |
649 | 0 | int no_name = 0; |
650 | 0 | int sha1_name = 0; |
651 | 0 | int shown_merge_point = 0; |
652 | 0 | int with_current_branch = 0; |
653 | 0 | int head_at = -1; |
654 | 0 | int topics = 0; |
655 | 0 | int sparse = 0; |
656 | 0 | const char *reflog_base = NULL; |
657 | 0 | struct option builtin_show_branch_options[] = { |
658 | 0 | OPT_BOOL('a', "all", &all_heads, |
659 | 0 | N_("show remote-tracking and local branches")), |
660 | 0 | OPT_BOOL('r', "remotes", &all_remotes, |
661 | 0 | N_("show remote-tracking branches")), |
662 | 0 | OPT__COLOR(&showbranch_use_color, |
663 | 0 | N_("color '*!+-' corresponding to the branch")), |
664 | 0 | { OPTION_INTEGER, 0, "more", &extra, N_("n"), |
665 | 0 | N_("show <n> more commits after the common ancestor"), |
666 | 0 | PARSE_OPT_OPTARG, NULL, (intptr_t)1 }, |
667 | 0 | OPT_SET_INT(0, "list", &extra, N_("synonym to more=-1"), -1), |
668 | 0 | OPT_BOOL(0, "no-name", &no_name, N_("suppress naming strings")), |
669 | 0 | OPT_BOOL(0, "current", &with_current_branch, |
670 | 0 | N_("include the current branch")), |
671 | 0 | OPT_BOOL(0, "sha1-name", &sha1_name, |
672 | 0 | N_("name commits with their object names")), |
673 | 0 | OPT_BOOL(0, "merge-base", &merge_base, |
674 | 0 | N_("show possible merge bases")), |
675 | 0 | OPT_BOOL(0, "independent", &independent, |
676 | 0 | N_("show refs unreachable from any other ref")), |
677 | 0 | OPT_SET_INT_F(0, "topo-order", &sort_order, |
678 | 0 | N_("show commits in topological order"), |
679 | 0 | REV_SORT_IN_GRAPH_ORDER, PARSE_OPT_NONEG), |
680 | 0 | OPT_BOOL(0, "topics", &topics, |
681 | 0 | N_("show only commits not on the first branch")), |
682 | 0 | OPT_SET_INT(0, "sparse", &sparse, |
683 | 0 | N_("show merges reachable from only one tip"), 1), |
684 | 0 | OPT_SET_INT_F(0, "date-order", &sort_order, |
685 | 0 | N_("topologically sort, maintaining date order " |
686 | 0 | "where possible"), |
687 | 0 | REV_SORT_BY_COMMIT_DATE, PARSE_OPT_NONEG), |
688 | 0 | OPT_CALLBACK_F('g', "reflog", &reflog_base, N_("<n>[,<base>]"), |
689 | 0 | N_("show <n> most recent ref-log entries starting at " |
690 | 0 | "base"), |
691 | 0 | PARSE_OPT_OPTARG | PARSE_OPT_NONEG, |
692 | 0 | parse_reflog_param), |
693 | 0 | OPT_END() |
694 | 0 | }; |
695 | 0 | const char **args_copy = NULL; |
696 | 0 | int ret; |
697 | |
|
698 | 0 | init_commit_name_slab(&name_slab); |
699 | |
|
700 | 0 | git_config(git_show_branch_config, NULL); |
701 | | |
702 | | /* If nothing is specified, try the default first */ |
703 | 0 | if (ac == 1 && default_args.nr) { |
704 | 0 | DUP_ARRAY(args_copy, default_args.v, default_args.nr); |
705 | 0 | ac = default_args.nr; |
706 | 0 | av = args_copy; |
707 | 0 | } |
708 | |
|
709 | 0 | ac = parse_options(ac, av, prefix, builtin_show_branch_options, |
710 | 0 | show_branch_usage, PARSE_OPT_STOP_AT_NON_OPTION); |
711 | 0 | if (all_heads) |
712 | 0 | all_remotes = 1; |
713 | |
|
714 | 0 | if (extra || reflog) { |
715 | | /* "listing" mode is incompatible with |
716 | | * independent nor merge-base modes. |
717 | | */ |
718 | 0 | if (independent || merge_base) |
719 | 0 | usage_with_options(show_branch_usage, |
720 | 0 | builtin_show_branch_options); |
721 | 0 | if (reflog && ((0 < extra) || all_heads || all_remotes)) |
722 | | /* |
723 | | * Asking for --more in reflog mode does not |
724 | | * make sense. --list is Ok. |
725 | | * |
726 | | * Also --all and --remotes do not make sense either. |
727 | | */ |
728 | 0 | die(_("options '%s' and '%s' cannot be used together"), "--reflog", |
729 | 0 | "--all/--remotes/--independent/--merge-base"); |
730 | 0 | } |
731 | | |
732 | 0 | if (with_current_branch && reflog) |
733 | 0 | die(_("options '%s' and '%s' cannot be used together"), |
734 | 0 | "--reflog", "--current"); |
735 | | |
736 | | /* If nothing is specified, show all branches by default */ |
737 | 0 | if (ac <= topics && all_heads + all_remotes == 0) |
738 | 0 | all_heads = 1; |
739 | |
|
740 | 0 | if (reflog) { |
741 | 0 | struct object_id oid; |
742 | 0 | char *ref; |
743 | 0 | int base = 0; |
744 | 0 | unsigned int flags = 0; |
745 | |
|
746 | 0 | if (ac == 0) { |
747 | 0 | static const char *fake_av[2]; |
748 | |
|
749 | 0 | fake_av[0] = refs_resolve_refdup(get_main_ref_store(the_repository), |
750 | 0 | "HEAD", |
751 | 0 | RESOLVE_REF_READING, |
752 | 0 | &oid, |
753 | 0 | NULL); |
754 | 0 | fake_av[1] = NULL; |
755 | 0 | av = fake_av; |
756 | 0 | ac = 1; |
757 | 0 | if (!*av) |
758 | 0 | die(_("no branches given, and HEAD is not valid")); |
759 | 0 | } |
760 | 0 | if (ac != 1) |
761 | 0 | die(_("--reflog option needs one branch name")); |
762 | | |
763 | 0 | if (MAX_REVS < reflog) |
764 | 0 | die(Q_("only %d entry can be shown at one time.", |
765 | 0 | "only %d entries can be shown at one time.", |
766 | 0 | MAX_REVS), MAX_REVS); |
767 | 0 | if (!repo_dwim_ref(the_repository, *av, strlen(*av), &oid, |
768 | 0 | &ref, 0)) |
769 | 0 | die(_("no such ref %s"), *av); |
770 | | |
771 | | /* Has the base been specified? */ |
772 | 0 | if (reflog_base) { |
773 | 0 | char *ep; |
774 | 0 | base = strtoul(reflog_base, &ep, 10); |
775 | 0 | if (*ep) { |
776 | | /* Ah, that is a date spec... */ |
777 | 0 | timestamp_t at; |
778 | 0 | at = approxidate(reflog_base); |
779 | 0 | read_ref_at(get_main_ref_store(the_repository), |
780 | 0 | ref, flags, at, -1, &oid, NULL, |
781 | 0 | NULL, NULL, &base); |
782 | 0 | } |
783 | 0 | } |
784 | |
|
785 | 0 | for (i = 0; i < reflog; i++) { |
786 | 0 | char *logmsg = NULL; |
787 | 0 | char *nth_desc; |
788 | 0 | const char *msg; |
789 | 0 | char *end; |
790 | 0 | timestamp_t timestamp; |
791 | 0 | int tz; |
792 | |
|
793 | 0 | if (read_ref_at(get_main_ref_store(the_repository), |
794 | 0 | ref, flags, 0, base + i, &oid, &logmsg, |
795 | 0 | ×tamp, &tz, NULL)) { |
796 | 0 | free(logmsg); |
797 | 0 | reflog = i; |
798 | 0 | break; |
799 | 0 | } |
800 | | |
801 | 0 | end = strchr(logmsg, '\n'); |
802 | 0 | if (end) |
803 | 0 | *end = '\0'; |
804 | |
|
805 | 0 | msg = (*logmsg == '\0') ? "(none)" : logmsg; |
806 | 0 | reflog_msg[i] = xstrfmt("(%s) %s", |
807 | 0 | show_date(timestamp, tz, |
808 | 0 | DATE_MODE(RELATIVE)), |
809 | 0 | msg); |
810 | 0 | free(logmsg); |
811 | |
|
812 | 0 | nth_desc = xstrfmt("%s@{%d}", *av, base+i); |
813 | 0 | append_ref(nth_desc, &oid, 1); |
814 | 0 | free(nth_desc); |
815 | 0 | } |
816 | 0 | free(ref); |
817 | 0 | } |
818 | 0 | else { |
819 | 0 | while (0 < ac) { |
820 | 0 | append_one_rev(*av); |
821 | 0 | ac--; av++; |
822 | 0 | } |
823 | 0 | if (all_heads + all_remotes) |
824 | 0 | snarf_refs(all_heads, all_remotes); |
825 | 0 | } |
826 | | |
827 | 0 | head = refs_resolve_refdup(get_main_ref_store(the_repository), "HEAD", |
828 | 0 | RESOLVE_REF_READING, |
829 | 0 | &head_oid, NULL); |
830 | |
|
831 | 0 | if (with_current_branch && head) { |
832 | 0 | int has_head = 0; |
833 | 0 | for (i = 0; !has_head && i < ref_name_cnt; i++) { |
834 | | /* We are only interested in adding the branch |
835 | | * HEAD points at. |
836 | | */ |
837 | 0 | if (rev_is_head(head, ref_name[i])) |
838 | 0 | has_head++; |
839 | 0 | } |
840 | 0 | if (!has_head) { |
841 | 0 | const char *name = head; |
842 | 0 | skip_prefix(name, "refs/heads/", &name); |
843 | 0 | append_one_rev(name); |
844 | 0 | } |
845 | 0 | } |
846 | |
|
847 | 0 | if (!ref_name_cnt) { |
848 | 0 | fprintf(stderr, "No revs to be shown.\n"); |
849 | 0 | ret = 0; |
850 | 0 | goto out; |
851 | 0 | } |
852 | | |
853 | 0 | for (num_rev = 0; ref_name[num_rev]; num_rev++) { |
854 | 0 | struct object_id revkey; |
855 | 0 | unsigned int flag = 1u << (num_rev + REV_SHIFT); |
856 | |
|
857 | 0 | if (MAX_REVS <= num_rev) |
858 | 0 | die(Q_("cannot handle more than %d rev.", |
859 | 0 | "cannot handle more than %d revs.", |
860 | 0 | MAX_REVS), MAX_REVS); |
861 | 0 | if (repo_get_oid(the_repository, ref_name[num_rev], &revkey)) |
862 | 0 | die(_("'%s' is not a valid ref."), ref_name[num_rev]); |
863 | 0 | commit = lookup_commit_reference(the_repository, &revkey); |
864 | 0 | if (!commit) |
865 | 0 | die(_("cannot find commit %s (%s)"), |
866 | 0 | ref_name[num_rev], oid_to_hex(&revkey)); |
867 | 0 | repo_parse_commit(the_repository, commit); |
868 | 0 | mark_seen(commit, &seen); |
869 | | |
870 | | /* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1, |
871 | | * and so on. REV_SHIFT bits from bit 0 are used for |
872 | | * internal bookkeeping. |
873 | | */ |
874 | 0 | commit->object.flags |= flag; |
875 | 0 | if (commit->object.flags == flag) |
876 | 0 | commit_list_insert_by_date(commit, &list); |
877 | 0 | rev[num_rev] = commit; |
878 | 0 | } |
879 | 0 | for (i = 0; i < num_rev; i++) |
880 | 0 | rev_mask[i] = rev[i]->object.flags; |
881 | |
|
882 | 0 | if (0 <= extra) |
883 | 0 | join_revs(&list, &seen, num_rev, extra); |
884 | |
|
885 | 0 | commit_list_sort_by_date(&seen); |
886 | |
|
887 | 0 | if (merge_base) { |
888 | 0 | ret = show_merge_base(seen, num_rev); |
889 | 0 | goto out; |
890 | 0 | } |
891 | | |
892 | 0 | if (independent) { |
893 | 0 | ret = show_independent(rev, num_rev, rev_mask); |
894 | 0 | goto out; |
895 | 0 | } |
896 | | |
897 | | /* Show list; --more=-1 means list-only */ |
898 | 0 | if (1 < num_rev || extra < 0) { |
899 | 0 | for (i = 0; i < num_rev; i++) { |
900 | 0 | int j; |
901 | 0 | int is_head = rev_is_head(head, ref_name[i]) && |
902 | 0 | oideq(&head_oid, &rev[i]->object.oid); |
903 | 0 | if (extra < 0) |
904 | 0 | printf("%c [%s] ", |
905 | 0 | is_head ? '*' : ' ', ref_name[i]); |
906 | 0 | else { |
907 | 0 | for (j = 0; j < i; j++) |
908 | 0 | putchar(' '); |
909 | 0 | printf("%s%c%s [%s] ", |
910 | 0 | get_color_code(i), |
911 | 0 | is_head ? '*' : '!', |
912 | 0 | get_color_reset_code(), ref_name[i]); |
913 | 0 | } |
914 | |
|
915 | 0 | if (!reflog) { |
916 | | /* header lines never need name */ |
917 | 0 | show_one_commit(rev[i], 1); |
918 | 0 | } |
919 | 0 | else |
920 | 0 | puts(reflog_msg[i]); |
921 | |
|
922 | 0 | if (is_head) |
923 | 0 | head_at = i; |
924 | 0 | } |
925 | 0 | if (0 <= extra) { |
926 | 0 | for (i = 0; i < num_rev; i++) |
927 | 0 | putchar('-'); |
928 | 0 | putchar('\n'); |
929 | 0 | } |
930 | 0 | } |
931 | 0 | if (extra < 0) { |
932 | 0 | ret = 0; |
933 | 0 | goto out; |
934 | 0 | } |
935 | | |
936 | | /* Sort topologically */ |
937 | 0 | sort_in_topological_order(&seen, sort_order); |
938 | | |
939 | | /* Give names to commits */ |
940 | 0 | if (!sha1_name && !no_name) |
941 | 0 | name_commits(seen, rev, ref_name, num_rev); |
942 | |
|
943 | 0 | all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); |
944 | 0 | all_revs = all_mask & ~((1u << REV_SHIFT) - 1); |
945 | |
|
946 | 0 | for (struct commit_list *l = seen; l; l = l->next) { |
947 | 0 | struct commit *commit = l->item; |
948 | 0 | int this_flag = commit->object.flags; |
949 | 0 | int is_merge_point = ((this_flag & all_revs) == all_revs); |
950 | |
|
951 | 0 | shown_merge_point |= is_merge_point; |
952 | |
|
953 | 0 | if (1 < num_rev) { |
954 | 0 | int is_merge = !!(commit->parents && |
955 | 0 | commit->parents->next); |
956 | 0 | if (topics && |
957 | 0 | !is_merge_point && |
958 | 0 | (this_flag & (1u << REV_SHIFT))) |
959 | 0 | continue; |
960 | 0 | if (!sparse && is_merge && |
961 | 0 | omit_in_dense(commit, rev, num_rev)) |
962 | 0 | continue; |
963 | 0 | for (i = 0; i < num_rev; i++) { |
964 | 0 | int mark; |
965 | 0 | if (!(this_flag & (1u << (i + REV_SHIFT)))) |
966 | 0 | mark = ' '; |
967 | 0 | else if (is_merge) |
968 | 0 | mark = '-'; |
969 | 0 | else if (i == head_at) |
970 | 0 | mark = '*'; |
971 | 0 | else |
972 | 0 | mark = '+'; |
973 | 0 | if (mark == ' ') |
974 | 0 | putchar(mark); |
975 | 0 | else |
976 | 0 | printf("%s%c%s", |
977 | 0 | get_color_code(i), |
978 | 0 | mark, get_color_reset_code()); |
979 | 0 | } |
980 | 0 | putchar(' '); |
981 | 0 | } |
982 | 0 | show_one_commit(commit, no_name); |
983 | |
|
984 | 0 | if (shown_merge_point && --extra < 0) |
985 | 0 | break; |
986 | 0 | } |
987 | |
|
988 | 0 | ret = 0; |
989 | |
|
990 | 0 | out: |
991 | 0 | for (size_t i = 0; i < ARRAY_SIZE(reflog_msg); i++) |
992 | 0 | free(reflog_msg[i]); |
993 | 0 | free_commit_list(seen); |
994 | 0 | free_commit_list(list); |
995 | 0 | free(args_copy); |
996 | 0 | free(head); |
997 | 0 | return ret; |
998 | 0 | } |