diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt new file mode 100644 index 0000000000..71d5ba8fd7 --- /dev/null +++ b/Documentation/git-show-branch.txt @@ -0,0 +1,107 @@ +git-show-branch(1) +================== +v0.99.4, Aug 2005 + +NAME +---- +git-show-branch - Show branches and their commits. + +SYNOPSIS +-------- +'git show-branch [--all] [--heads] [--tags] [--more=] [--merge-base] ...' + +DESCRIPTION +----------- +Shows the head commits from the named (or all refs under +$GIT_DIR/refs/heads), and displays concise list of commit logs +to show their relationship semi-visually. + +OPTIONS +------- +:: + Name of the reference under $GIT_DIR/refs/. + +--all --heads --tags:: + Show all refs under $GIT_DIR/refs, $GIT_DIR/refs/heads, + and $GIT_DIR/refs/tags, respectively. + +--more=:: + Usually the command stops output upon showing the commit + that is the common ancestor of all the branches. This + flag tells the command to go commits beyond that. + +--merge-base:: + Instead of showing the commit list, just act like the + 'git-merge-base' command except that it can accept more + than two heads. + + +OUTPUT +------ +Given N , the first N lines are the one-line +description from their commit message. The branch head that is +pointed at by $GIT_DIR/HEAD is prefixed with an asterisk '*' +character while other heads are prefixed with a '!' character. + +Following these N lines, one-line log for each commit is +displayed, indented N places. If a commit is on the I-th +branch, the I-th indentation character shows a '+' sign; +otherwise it shows a space. Each commit shows a short name that +can be used as an exended SHA1 to name that commit. + +The following example shows three branches, "master", "fixes" +and "mhf": + +------------------------------------------------ +$ git show-branch master fixes mhf +! [master] Add 'git show-branch'. + ! [fixes] Introduce "reset type" flag to "git reset" + ! [mhf] Allow "+remote:local" refspec to cause --force when fetching. +--- + + [mhf] Allow "+remote:local" refspec to cause --force when fetching. + + [mhf~1] Use git-octopus when pulling more than one heads. + + [fixes] Introduce "reset type" flag to "git reset" + + [mhf~2] "git fetch --force". + + [mhf~3] Use .git/remote/origin, not .git/branches/origin. + + [mhf~4] Make "git pull" and "git fetch" default to origin + + [mhf~5] Infamous 'octopus merge' + + [mhf~6] Retire git-parse-remote. + + [mhf~7] Multi-head fetch. + + [mhf~8] Start adding the $GIT_DIR/remotes/ support. ++++ [master] Add 'git show-branch'. +------------------------------------------------ + +These three branches all forked from a common commit, [master], +whose commit message is "Add 'git show-branch'. "fixes" branch +adds one commit 'Introduce "reset type"'. "mhf" branch has many +other commits. + +When only one head is given, the output format changes slightly +to conserve space. The '+' sign to show which commit is +reachable from which head and the first N lines to show the list +of heads being displayed are both meaningless so they are +omitted. Also the label given to each commit does not repeat +the name of the branch because it is obvious. + +------------------------------------------------ +$ git show-branch --more=4 master +[master] Add 'git show-branch'. +[~1] Add a new extended SHA1 syntax ~ +[~2] Fix "git-diff-script A B" +[~3] git-ls-files: generalized pathspecs +[~4] Make "git-ls-files" work in subdirectories +------------------------------------------------ + +Author +------ +Written by Junio C Hamano + + +Documentation +-------------- +Documentation by Junio C Hamano. + + +GIT +--- +Part of the link:git.html[git] suite diff --git a/Documentation/git-show-branches-script.txt b/Documentation/git-show-branches-script.txt deleted file mode 100644 index c7fb02cf7b..0000000000 --- a/Documentation/git-show-branches-script.txt +++ /dev/null @@ -1,71 +0,0 @@ -git-show-branches-script(1) -=========================== -v0.99.4, Aug 2005 - -NAME ----- -git-show-branches-script - Show branches and their commits. - -SYNOPSIS --------- -'git show-branches ...' - -DESCRIPTION ------------ -Shows the head commits from the named (or all refs under -$GIT_DIR/refs/heads), and displays concise list of commit logs -to show their relationship semi-visually. - -OPTIONS -------- -:: - Name of the reference under $GIT_DIR/refs/heads/. - - -OUTPUT ------- -Given N , the first N lines are the one-line -description from their commit message. The branch head that is -pointed at by $GIT_DIR/HEAD is prefixed with an asterisk '*' -character while other heads are prefixed with a '!' character. - -Following these N lines, one-line log for each commit is -displayed, indented N places. If a commit is on the I-th -branch, the I-th indentation character shows a '+' sign; -otherwise it shows a space. - -The following example shows three branches, "pu", "master" and -"rc": - ------------------------------------------------- -* [pu] Add cheap local clone '-s' flag to git-clone-script - ! [master] Documentation updates. - ! [rc] Merge master into rc -+ Add cheap local clone '-s' flag to git-clone-script -+ Alternate object pool mechanism updates. -+ Audit rev-parse users. -++ Documentation updates. - + Merge master into rc -+++ [PATCH] plug memory leak in diff.c::diff_free_filepair() ------------------------------------------------- - -These three branches all forked from a common commit, "[PATCH] -plug memory leak...", and "rc" has one commit ahead of it. The -"master" branch has one different commit that is also shared by -"pu" branch, and "pu" branch has three more commits on top of -"master" branch. - - -Author ------- -Written by Junio C Hamano - - -Documentation --------------- -Documentation by Junio C Hamano. - - -GIT ---- -Part of the link:git.html[git] suite diff --git a/Documentation/git.txt b/Documentation/git.txt index 8d313cba6e..9d4fccdd47 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -208,7 +208,7 @@ link:git-pull-script.html[git-pull-script]:: link:git-commit-script.html[git-commit-script]:: Record changes to the repository. -link:git-show-branches-script.html[git-show-branches-script]:: +link:git-show-branch.html[git-show-branch]:: Show branches and their commits. link:git-repack-script.html[git-repack-script]:: diff --git a/Makefile b/Makefile index c0a84298b5..797c27fb04 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,6 @@ SCRIPTS=git git-apply-patch-script git-merge-one-file-script git-prune-script \ SCRIPTS += git-count-objects-script # SCRIPTS += git-send-email-script SCRIPTS += git-revert-script -SCRIPTS += git-show-branches-script PROG= git-update-cache git-diff-files git-init-db git-write-tree \ git-read-tree git-commit-tree git-cat-file git-fsck-cache \ @@ -84,7 +83,7 @@ PROG= git-update-cache git-diff-files git-init-db git-write-tree \ git-diff-stages git-rev-parse git-patch-id git-pack-objects \ git-unpack-objects git-verify-pack git-receive-pack git-send-pack \ git-prune-packed git-fetch-pack git-upload-pack git-clone-pack \ - git-show-index git-daemon git-var git-peek-remote \ + git-show-index git-daemon git-var git-peek-remote git-show-branch \ git-update-server-info git-show-rev-cache git-build-rev-cache ifndef NO_CURL diff --git a/commit.c b/commit.c index 59e93d4590..e51eda1524 100644 --- a/commit.c +++ b/commit.c @@ -38,23 +38,32 @@ enum cmit_fmt get_commit_format(const char *arg) die("invalid --pretty format"); } -static struct commit *check_commit(struct object *obj, const unsigned char *sha1) +static struct commit *check_commit(struct object *obj, + const unsigned char *sha1, + int quiet) { if (obj->type != commit_type) { - error("Object %s is a %s, not a commit", - sha1_to_hex(sha1), obj->type); + if (!quiet) + error("Object %s is a %s, not a commit", + sha1_to_hex(sha1), obj->type); return NULL; } return (struct commit *) obj; } -struct commit *lookup_commit_reference(const unsigned char *sha1) +struct commit *lookup_commit_reference_gently(const unsigned char *sha1, + int quiet) { struct object *obj = deref_tag(parse_object(sha1)); if (!obj) return NULL; - return check_commit(obj, sha1); + return check_commit(obj, sha1, quiet); +} + +struct commit *lookup_commit_reference(const unsigned char *sha1) +{ + return lookup_commit_reference_gently(sha1, 0); } struct commit *lookup_commit(const unsigned char *sha1) @@ -69,7 +78,7 @@ struct commit *lookup_commit(const unsigned char *sha1) } if (!obj->type) obj->type = commit_type; - return check_commit(obj, sha1); + return check_commit(obj, sha1, 0); } static unsigned long parse_commit_date(const char *buf) diff --git a/commit.h b/commit.h index 5d179d4dc8..9dda2f045f 100644 --- a/commit.h +++ b/commit.h @@ -21,6 +21,8 @@ extern const char *commit_type; struct commit *lookup_commit(const unsigned char *sha1); struct commit *lookup_commit_reference(const unsigned char *sha1); +struct commit *lookup_commit_reference_gently(const unsigned char *sha1, + int quiet); int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size); diff --git a/git-diff-script b/git-diff-script index 926f594be4..99ff2640f1 100755 --- a/git-diff-script +++ b/git-diff-script @@ -8,8 +8,14 @@ case "${#rev[*]}" in 1) git-diff-cache -M -p "$@";; 2) - begin=$(echo "${rev[1]}" | tr -d '^') - end="${rev[0]}" + case "${rev[1]}" in + ^?*) + begin=$(echo "${rev[1]}" | tr -d '^') + end="${rev[0]}" ;; + *) + begin="${rev[0]}" + end="${rev[1]}" ;; + esac git-diff-tree -M -p $flags $begin $end $files;; *) echo "I don't understand" diff --git a/git-show-branches-script b/git-show-branches-script deleted file mode 100755 index 90018a9e88..0000000000 --- a/git-show-branches-script +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/sh -# -# Show refs and their recent commits. -# - -. git-sh-setup-script || die "Not a git repository" - -usage () { - die "usage: $0 ..." -} - -headref=`readlink $GIT_DIR/HEAD` - -case "$(git-rev-parse --no-revs)" in '') ;; *) usage ;; esac -revs=$(git-rev-parse --revs-only --symbolic --no-flags "$@") -flags=$(git-rev-parse --revs-only --flags "$@") -case "$revs" in -'') - revs=$(git-rev-parse --symbolic --all | sed -ne 's|^refs/heads/||p' | - sort) - shift ;; -esac -set x $revs -shift - -hh= in= -for ref -do - case "/$headref" in - */"$ref") H='*' ;; - *) H='!' ;; - esac - h=`git-rev-parse --verify "$ref^0" 2>/dev/null` || continue - l=`git-log-script --max-count=1 --pretty=oneline "$h" | - sed -e 's/^[^ ]* //'` - hh="$hh $h" - echo "$in$H [$ref] $l" - in="$in " -done -set x $hh -shift - -git-rev-list --pretty=oneline $flags $@ | -while read v l -do - in='' - for h - do - b=`git-merge-base $h $v` - case "$b" in - $v) in="$in+" ;; - *) in="$in " ;; - esac - done - - echo "$in $l" - case "$in" in - *' '*) ;; - *) break ;; - esac -done diff --git a/ls-files.c b/ls-files.c index c76e30e990..2c7aada88c 100644 --- a/ls-files.c +++ b/ls-files.c @@ -21,7 +21,7 @@ static int line_terminator = '\n'; static int prefix_len = 0, prefix_offset = 0; static const char *prefix = NULL; -static const char *glob = NULL; +static const char **pathspec = NULL; static const char *tag_cached = ""; static const char *tag_unmerged = ""; @@ -302,6 +302,33 @@ static int cmp_name(const void *p1, const void *p2) e2->name, e2->len); } +/* + * Match a pathspec against a filename. The first "len" characters + * are the common prefix + */ +static int match(const char **spec, const char *filename, int len) +{ + const char *m; + + while ((m = *spec++) != NULL) { + int matchlen = strlen(m + len); + + if (!matchlen) + return 1; + if (!strncmp(m + len, filename + len, matchlen)) { + if (m[len + matchlen - 1] == '/') + return 1; + switch (filename[len + matchlen]) { + case '/': case '\0': + return 1; + } + } + if (!fnmatch(m + len, filename + len, 0)) + return 1; + } + return 0; +} + static void show_dir_entry(const char *tag, struct nond_on_fs *ent) { int len = prefix_len; @@ -310,7 +337,7 @@ static void show_dir_entry(const char *tag, struct nond_on_fs *ent) if (len >= ent->len) die("git-ls-files: internal error - directory entry not superset of prefix"); - if (glob && fnmatch(glob, ent->name + len, 0)) + if (pathspec && !match(pathspec, ent->name, len)) return; printf("%s%s%c", tag, ent->name + offset, line_terminator); @@ -373,7 +400,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) if (len >= ce_namelen(ce)) die("git-ls-files: internal error - cache entry not superset of prefix"); - if (glob && fnmatch(glob, ce->name + len, 0)) + if (pathspec && !match(pathspec, ce->name, len)) return; if (!show_stage) @@ -455,36 +482,44 @@ static void prune_cache(void) active_nr = last; } -/* - * If the glob starts with a subdirectory, append it to - * the prefix instead, for more efficient operation. - * - * But we do not update the "prefix_offset", which tells - * how much of the name to ignore at printout. - */ -static void extend_prefix(void) +static void verify_pathspec(void) { - const char *p, *slash; - char c; - - p = glob; - slash = NULL; - while ((c = *p++) != '\0') { - if (c == '*') - break; - if (c == '/') - slash = p; + const char **p, *n, *prev; + char *real_prefix; + unsigned long max; + + prev = NULL; + max = PATH_MAX; + for (p = pathspec; (n = *p) != NULL; p++) { + int i, len = 0; + for (i = 0; i < max; i++) { + char c = n[i]; + if (prev && prev[i] != c) + break; + if (c == '*' || c == '?') + break; + if (c == '/') + len = i+1; + } + prev = n; + if (len < max) { + max = len; + if (!max) + break; + } } - if (slash) { - int len = slash - glob; - char *newprefix = xmalloc(len + prefix_len + 1); - memcpy(newprefix, prefix, prefix_len); - memcpy(newprefix + prefix_len, glob, len); - prefix_len += len; - newprefix[prefix_len] = 0; - prefix = newprefix; - glob = *slash ? slash : NULL; + + if (prefix_offset > max || memcmp(prev, prefix, prefix_offset)) + die("git-ls-files: cannot generate relative filenames containing '..'"); + + real_prefix = NULL; + prefix_len = max; + if (max) { + real_prefix = xmalloc(max + 1); + memcpy(real_prefix, prev, max); + real_prefix[max] = 0; } + prefix = real_prefix; } static const char ls_files_usage[] = @@ -499,7 +534,7 @@ int main(int argc, char **argv) prefix = setup_git_directory(); if (prefix) - prefix_offset = prefix_len = strlen(prefix); + prefix_offset = strlen(prefix); for (i = 1; i < argc; i++) { char *arg = argv[i]; @@ -577,13 +612,16 @@ int main(int argc, char **argv) prefix_offset = 0; continue; } - if (glob || *arg == '-') + if (*arg == '-') usage(ls_files_usage); - glob = arg; + break; } - if (glob) - extend_prefix(); + pathspec = get_pathspec(prefix, argv + i); + + /* Verify that the pathspec matches the prefix */ + if (pathspec) + verify_pathspec(); if (show_ignored && !exc_given) { fprintf(stderr, "%s: --ignored needs some exclude pattern\n", diff --git a/sha1_name.c b/sha1_name.c index 6744bbbef5..133af3b6c1 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -191,9 +191,29 @@ static int get_parent(const char *name, int len, return -1; } +static int get_nth_ancestor(const char *name, int len, + unsigned char *result, int generation) +{ + unsigned char sha1[20]; + int ret = get_sha1_1(name, len, sha1); + if (ret) + return ret; + + while (generation--) { + struct commit *commit = lookup_commit_reference(sha1); + + if (!commit || parse_commit(commit) || !commit->parents) + return -1; + memcpy(sha1, commit->parents->item->object.sha1, 20); + } + memcpy(result, sha1, 20); + return 0; +} + static int get_sha1_1(const char *name, int len, unsigned char *sha1) { int parent, ret; + const char *cp; /* foo^[0-9] or foo^ (== foo^1); we do not do more than 9 parents. */ if (len > 2 && name[len-2] == '^' && @@ -210,6 +230,27 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1) if (parent >= 0) return get_parent(name, len, sha1, parent); + /* "name~3" is "name^^^", + * "name~12" is "name^^^^^^^^^^^^", and + * "name~" and "name~0" are name -- not "name^0"! + */ + parent = 0; + for (cp = name + len - 1; name <= cp; cp--) { + int ch = *cp; + if ('0' <= ch && ch <= '9') + continue; + if (ch != '~') + parent = -1; + break; + } + if (!parent && *cp == '~') { + int len1 = cp - name; + cp++; + while (cp < name + len) + parent = parent * 10 + *cp++ - '0'; + return get_nth_ancestor(name, len1, sha1, parent); + } + ret = get_sha1_basic(name, len, sha1); if (!ret) return 0; diff --git a/show-branch.c b/show-branch.c new file mode 100644 index 0000000000..fc827ee497 --- /dev/null +++ b/show-branch.c @@ -0,0 +1,339 @@ +#include +#include "cache.h" +#include "commit.h" +#include "refs.h" + +static const char show_branch_usage[] = +"git-show-branch [--all] [--heads] [--tags] [--more=count] [...]"; + +#define UNINTERESTING 01 + +#define REV_SHIFT 2 +#define MAX_REVS 29 /* should not exceed bits_per_int - REV_SHIFT */ + +static struct commit *interesting(struct commit_list *list) +{ + while (list) { + struct commit *commit = list->item; + list = list->next; + if (commit->object.flags & UNINTERESTING) + continue; + return commit; + } + return NULL; +} + +static struct commit *pop_one_commit(struct commit_list **list_p) +{ + struct commit *commit; + struct commit_list *list; + list = *list_p; + commit = list->item; + *list_p = list->next; + free(list); + return commit; +} + +struct commit_name { + int head_rev; /* which head's ancestor? */ + int generation; /* how many parents away from head_rev */ +}; + +/* Name the commit as nth generation ancestor of head_rev; + * we count only the first-parent relationship for naming purposes. + */ +static void name_commit(struct commit *commit, int head_rev, int nth) +{ + struct commit_name *name; + if (!commit->object.util) + commit->object.util = xmalloc(sizeof(struct commit_name)); + name = commit->object.util; + name->head_rev = head_rev; + name->generation = nth; +} + +/* Parent is the first parent of the commit. We may name it + * as (n+1)th generation ancestor of the same head_rev as + * commit is nth generation ancestore of, if that generation + * number is better than the name it already has. + */ +static void name_parent(struct commit *commit, struct commit *parent) +{ + struct commit_name *commit_name = commit->object.util; + struct commit_name *parent_name = parent->object.util; + if (!commit_name) + return; + if (!parent_name || + commit_name->generation + 1 < parent_name->generation) + name_commit(parent, commit_name->head_rev, + commit_name->generation + 1); +} + +static int mark_seen(struct commit *commit, struct commit_list **seen_p) +{ + if (!commit->object.flags) { + insert_by_date(commit, seen_p); + return 1; + } + return 0; +} + +static void join_revs(struct commit_list **list_p, + struct commit_list **seen_p, + int num_rev, int extra) +{ + int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); + int all_revs = all_mask & ~((1u << REV_SHIFT) - 1); + + while (*list_p) { + struct commit_list *parents; + struct commit *commit = pop_one_commit(list_p); + int flags = commit->object.flags & all_mask; + int nth_parent = 0; + int still_interesting = !!interesting(*list_p); + + if (!still_interesting && extra < 0) + break; + + mark_seen(commit, seen_p); + if ((flags & all_revs) == all_revs) + flags |= UNINTERESTING; + parents = commit->parents; + + while (parents) { + struct commit *p = parents->item; + int this_flag = p->object.flags; + parents = parents->next; + nth_parent++; + if (nth_parent == 1) + name_parent(commit, p); + + if ((this_flag & flags) == flags) + continue; + parse_commit(p); + if (mark_seen(p, seen_p) && !still_interesting) + extra--; + p->object.flags |= flags; + insert_by_date(p, list_p); + } + } +} + +static void show_one_commit(struct commit *commit, char **head_name) +{ + char pretty[128], *cp; + struct commit_name *name = commit->object.util; + pretty_print_commit(CMIT_FMT_ONELINE, commit->buffer, ~0, + pretty, sizeof(pretty)); + if (!strncmp(pretty, "[PATCH] ", 8)) + cp = pretty + 8; + else + cp = pretty; + if (name && head_name) { + printf("[%s", head_name[name->head_rev]); + if (name->generation) + printf("~%d", name->generation); + printf("] "); + } + puts(cp); +} + +static char *ref_name[MAX_REVS + 1]; +static int ref_name_cnt; + +static int append_ref(const char *refname, const unsigned char *sha1) +{ + struct commit *commit = lookup_commit_reference_gently(sha1, 1); + if (!commit) + return 0; + if (MAX_REVS < ref_name_cnt) { + fprintf(stderr, "warning: ignoring %s; " + "cannot handle more than %d refs", + refname, MAX_REVS); + return 0; + } + ref_name[ref_name_cnt++] = strdup(refname); + ref_name[ref_name_cnt] = NULL; + return 0; +} + +static int append_head_ref(const char *refname, const unsigned char *sha1) +{ + if (strncmp(refname, "refs/heads/", 11)) + return 0; + return append_ref(refname + 5, sha1); +} + +static int append_tag_ref(const char *refname, const unsigned char *sha1) +{ + if (strncmp(refname, "refs/tags/", 10)) + return 0; + return append_ref(refname + 5, sha1); +} + +static void snarf_refs(int head, int tag) +{ + if (head) + for_each_ref(append_head_ref); + if (tag) + for_each_ref(append_tag_ref); +} + +static int rev_is_head(char *head_path, int headlen, + char *name, + unsigned char *head_sha1, unsigned char *sha1) +{ + int namelen; + if ((!head_path[0]) || memcmp(head_sha1, sha1, 20)) + return 0; + namelen = strlen(name); + if ((headlen < namelen) || + memcmp(head_path + headlen - namelen, name, namelen)) + return 0; + if (headlen == namelen || + head_path[headlen - namelen - 1] == '/') + return 1; + return 0; +} + +static int show_merge_base(struct commit_list *seen, int num_rev) +{ + int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); + int all_revs = all_mask & ~((1u << REV_SHIFT) - 1); + + while (seen) { + struct commit *commit = pop_one_commit(&seen); + int flags = commit->object.flags & all_mask; + if (!(flags & UNINTERESTING) && + ((flags & all_revs) == all_revs)) { + puts(sha1_to_hex(commit->object.sha1)); + return 0; + } + } + return 1; +} + +int main(int ac, char **av) +{ + struct commit *rev[MAX_REVS], *commit; + struct commit_list *list = NULL, *seen = NULL; + int num_rev, i, extra = 0; + int all_heads = 0, all_tags = 0; + char head_path[128]; + int head_path_len; + unsigned char head_sha1[20]; + int merge_base = 0; + char **label; + + while (1 < ac && av[1][0] == '-') { + char *arg = av[1]; + if (!strcmp(arg, "--all")) + all_heads = all_tags = 1; + else if (!strcmp(arg, "--heads")) + all_heads = 1; + else if (!strcmp(arg, "--tags")) + all_tags = 1; + else if (!strcmp(arg, "--more")) + extra = 1; + else if (!strncmp(arg, "--more=", 7)) { + extra = atoi(arg + 7); + if (extra < 0) + usage(show_branch_usage); + } + else if (!strcmp(arg, "--merge-base")) + merge_base = 1; + else + usage(show_branch_usage); + ac--; av++; + } + ac--; av++; + + if (all_heads + all_tags) + snarf_refs(all_heads, all_tags); + + while (0 < ac) { + unsigned char revkey[20]; + if (get_sha1(*av, revkey)) + die("bad sha1 reference %s", *av); + append_ref(*av, revkey); + ac--; av++; + } + + /* If still no revs, then add heads */ + if (!ref_name_cnt) + snarf_refs(1, 0); + + for (num_rev = 0; ref_name[num_rev]; num_rev++) { + unsigned char revkey[20]; + + if (MAX_REVS <= num_rev) + die("cannot handle more than %d revs.", MAX_REVS); + if (get_sha1(ref_name[num_rev], revkey)) + usage(show_branch_usage); + commit = lookup_commit_reference(revkey); + if (!commit) + die("cannot find commit %s (%s)", + ref_name[num_rev], revkey); + parse_commit(commit); + if (!commit->object.util) + name_commit(commit, num_rev, 0); + mark_seen(commit, &seen); + + /* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1, + * and so on. REV_SHIFT bits from bit 0 are used for + * internal bookkeeping. + */ + commit->object.flags |= 1u << (num_rev + REV_SHIFT); + insert_by_date(commit, &list); + rev[num_rev] = commit; + } + join_revs(&list, &seen, num_rev, extra); + + head_path_len = readlink(".git/HEAD", head_path, sizeof(head_path)-1); + if ((head_path_len < 0) || get_sha1("HEAD", head_sha1)) + head_path[0] = 0; + else + head_path[head_path_len] = 0; + + if (merge_base) + return show_merge_base(seen, num_rev); + + /* Show list */ + if (1 < num_rev) { + for (i = 0; i < num_rev; i++) { + int j; + int is_head = rev_is_head(head_path, + head_path_len, + ref_name[i], + head_sha1, + rev[i]->object.sha1); + for (j = 0; j < i; j++) + putchar(' '); + printf("%c [%s] ", is_head ? '*' : '!', ref_name[i]); + show_one_commit(rev[i], NULL); + } + for (i = 0; i < num_rev; i++) + putchar('-'); + putchar('\n'); + } + + label = ref_name; + while (seen) { + struct commit *commit = pop_one_commit(&seen); + int this_flag = commit->object.flags; + static char *obvious[] = { "" }; + + if ((this_flag & UNINTERESTING) && (--extra < 0)) + break; + if (1 < num_rev) { + for (i = 0; i < num_rev; i++) + putchar((this_flag & (1u << (i + REV_SHIFT))) + ? '+' : ' '); + putchar(' '); + } + show_one_commit(commit, label); + if (num_rev == 1) + label = obvious; + } + return 0; +}