|
| 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; |
| 226 | + |
| 227 | + while (1 < ac && av[1][0] == '-') { |
| 228 | + char *arg = av[1]; |
| 229 | + if (!strcmp(arg, "--all")) |
| 230 | + all_heads = all_tags = 1; |
| 231 | + else if (!strcmp(arg, "--heads")) |
| 232 | + all_heads = 1; |
| 233 | + else if (!strcmp(arg, "--tags")) |
| 234 | + all_tags = 1; |
| 235 | + else if (!strcmp(arg, "--more")) |
| 236 | + extra = 1; |
| 237 | + else if (!strncmp(arg, "--more=", 7)) { |
| 238 | + extra = atoi(arg + 7); |
| 239 | + if (extra < 0) |
| 240 | + usage(show_branch_usage); |
| 241 | + } |
| 242 | + else if (!strcmp(arg, "--merge-base")) |
| 243 | + merge_base = 1; |
| 244 | + else |
| 245 | + usage(show_branch_usage); |
| 246 | + ac--; av++; |
| 247 | + } |
| 248 | + ac--; av++; |
| 249 | + |
| 250 | + if (all_heads + all_tags) |
| 251 | + snarf_refs(all_heads, all_tags); |
| 252 | + |
| 253 | + while (0 < ac) { |
| 254 | + unsigned char revkey[20]; |
| 255 | + if (get_sha1(*av, revkey)) |
| 256 | + die("bad sha1 reference %s", *av); |
| 257 | + append_ref(*av, revkey); |
| 258 | + ac--; av++; |
| 259 | + } |
| 260 | + |
| 261 | + /* If still no revs, then add heads */ |
| 262 | + if (!ref_name_cnt) |
| 263 | + snarf_refs(1, 0); |
| 264 | + |
| 265 | + for (num_rev = 0; ref_name[num_rev]; num_rev++) { |
| 266 | + unsigned char revkey[20]; |
| 267 | + |
| 268 | + if (MAX_REVS <= num_rev) |
| 269 | + die("cannot handle more than %d revs.", MAX_REVS); |
| 270 | + if (get_sha1(ref_name[num_rev], revkey)) |
| 271 | + usage(show_branch_usage); |
| 272 | + commit = lookup_commit_reference(revkey); |
| 273 | + if (!commit) |
| 274 | + die("cannot find commit %s (%s)", |
| 275 | + ref_name[num_rev], revkey); |
| 276 | + parse_commit(commit); |
| 277 | + if (!commit->object.util) |
| 278 | + name_commit(commit, num_rev, 0); |
| 279 | + mark_seen(commit, &seen); |
| 280 | + |
| 281 | + /* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1, |
| 282 | + * and so on. REV_SHIFT bits from bit 0 are used for |
| 283 | + * internal bookkeeping. |
| 284 | + */ |
| 285 | + commit->object.flags |= 1u << (num_rev + REV_SHIFT); |
| 286 | + insert_by_date(commit, &list); |
| 287 | + rev[num_rev] = commit; |
| 288 | + } |
| 289 | + join_revs(&list, &seen, num_rev, extra); |
| 290 | + |
| 291 | + head_path_len = readlink(".git/HEAD", head_path, sizeof(head_path)-1); |
| 292 | + if ((head_path_len < 0) || get_sha1("HEAD", head_sha1)) |
| 293 | + head_path[0] = 0; |
| 294 | + else |
| 295 | + head_path[head_path_len] = 0; |
| 296 | + |
| 297 | + if (merge_base) |
| 298 | + return show_merge_base(seen, num_rev); |
| 299 | + |
| 300 | + if (1 < num_rev) |
| 301 | + for (i = 0; i < num_rev; i++) { |
| 302 | + int j; |
| 303 | + int is_head = rev_is_head(head_path, |
| 304 | + head_path_len, |
| 305 | + ref_name[i], |
| 306 | + head_sha1, |
| 307 | + rev[i]->object.sha1); |
| 308 | + for (j = 0; j < i; j++) |
| 309 | + putchar(' '); |
| 310 | + printf("%c [%s] ", is_head ? '*' : '!', ref_name[i]); |
| 311 | + show_one_commit(rev[i], NULL); |
| 312 | + } |
| 313 | + while (seen) { |
| 314 | + struct commit *commit = pop_one_commit(&seen); |
| 315 | + int this_flag = commit->object.flags; |
| 316 | + if ((this_flag & UNINTERESTING) && (--extra < 0)) |
| 317 | + break; |
| 318 | + for (i = 0; i < num_rev; i++) |
| 319 | + putchar((this_flag & (1u << (i + REV_SHIFT))) |
| 320 | + ? '+' : ' '); |
| 321 | + putchar(' '); |
| 322 | + show_one_commit(commit, ref_name); |
| 323 | + } |
| 324 | + return 0; |
| 325 | +} |
0 commit comments