#include <stdlib.h>
+#include <fnmatch.h>
#include "cache.h"
#include "commit.h"
#include "refs.h"
static const char show_branch_usage[] =
-"git-show-branch [--all] [--heads] [--tags] [--more=count | --list | --independent | --merge-base ] [<refs>...]";
+"git-show-branch [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
+
+static int default_num = 0;
+static int default_alloc = 0;
+static char **default_arg = NULL;
#define UNINTERESTING 01
/* Parent is the first parent of the commit. We may name it
* as (n+1)th generation ancestor of the same head_name as
- * commit is nth generation ancestore of, if that generation
+ * commit is nth generation ancestor of, if that generation
* number is better than the name it already has.
*/
static void name_parent(struct commit *commit, struct commit *parent)
while (*list_p) {
struct commit_list *parents;
+ int still_interesting = !!interesting(*list_p);
struct commit *commit = pop_one_commit(list_p);
int flags = commit->object.flags & all_mask;
- int still_interesting = !!interesting(*list_p);
- if (!still_interesting && extra < 0)
+ if (!still_interesting && extra <= 0)
break;
mark_seen(commit, seen_p);
parents = parents->next;
if ((this_flag & flags) == flags)
continue;
- parse_commit(p);
+ if (!p->object.parsed)
+ parse_commit(p);
if (mark_seen(p, seen_p) && !still_interesting)
extra--;
p->object.flags |= flags;
insert_by_date(p, list_p);
}
}
+
+ /*
+ * Postprocess to complete well-poisoning.
+ *
+ * At this point we have all the commits we have seen in
+ * seen_p list (which happens to be sorted chronologically but
+ * it does not really matter). Mark anything that can be
+ * reached from uninteresting commits not interesting.
+ */
+ for (;;) {
+ int changed = 0;
+ struct commit_list *s;
+ for (s = *seen_p; s; s = s->next) {
+ struct commit *c = s->item;
+ struct commit_list *parents;
+
+ if (((c->object.flags & all_revs) != all_revs) &&
+ !(c->object.flags & UNINTERESTING))
+ continue;
+
+ /* The current commit is either a merge base or
+ * already uninteresting one. Mark its parents
+ * as uninteresting commits _only_ if they are
+ * already parsed. No reason to find new ones
+ * here.
+ */
+ parents = c->parents;
+ while (parents) {
+ struct commit *p = parents->item;
+ parents = parents->next;
+ if (!(p->object.flags & UNINTERESTING)) {
+ p->object.flags |= UNINTERESTING;
+ changed = 1;
+ }
+ }
+ }
+ if (!changed)
+ break;
+ }
}
static void show_one_commit(struct commit *commit, int no_name)
{
- char pretty[128], *cp;
+ char pretty[256], *cp;
struct commit_name *name = commit->object.util;
if (commit->object.parsed)
- pretty_print_commit(CMIT_FMT_ONELINE, commit->buffer, ~0,
- pretty, sizeof(pretty));
+ pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
+ pretty, sizeof(pretty), 0);
else
strcpy(pretty, "(unavailable)");
if (!strncmp(pretty, "[PATCH] ", 8))
static char *ref_name[MAX_REVS + 1];
static int ref_name_cnt;
+static const char *find_digit_prefix(const char *s, int *v)
+{
+ const char *p;
+ int ver;
+ char ch;
+
+ for (p = s, ver = 0;
+ '0' <= (ch = *p) && ch <= '9';
+ p++)
+ ver = ver * 10 + ch - '0';
+ *v = ver;
+ return p;
+}
+
+
+static int version_cmp(const char *a, const char *b)
+{
+ while (1) {
+ int va, vb;
+
+ a = find_digit_prefix(a, &va);
+ b = find_digit_prefix(b, &vb);
+ if (va != vb)
+ return va - vb;
+
+ while (1) {
+ int ca = *a;
+ int cb = *b;
+ if ('0' <= ca && ca <= '9')
+ ca = 0;
+ if ('0' <= cb && cb <= '9')
+ cb = 0;
+ if (ca != cb)
+ return ca - cb;
+ if (!ca)
+ break;
+ a++;
+ b++;
+ }
+ if (!*a && !*b)
+ return 0;
+ }
+}
+
static int compare_ref_name(const void *a_, const void *b_)
{
const char * const*a = a_, * const*b = b_;
- return strcmp(*a, *b);
+ return version_cmp(*a, *b);
}
static void sort_ref_range(int bottom, int top)
static int append_ref(const char *refname, const unsigned char *sha1)
{
struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+ int i;
+
if (!commit)
return 0;
- if (MAX_REVS < ref_name_cnt) {
+ /* Avoid adding the same thing twice */
+ for (i = 0; i < ref_name_cnt; i++)
+ if (!strcmp(refname, ref_name[i]))
+ return 0;
+
+ if (MAX_REVS <= ref_name_cnt) {
fprintf(stderr, "warning: ignoring %s; "
- "cannot handle more than %d refs",
+ "cannot handle more than %d refs\n",
refname, MAX_REVS);
return 0;
}
static int append_head_ref(const char *refname, const unsigned char *sha1)
{
- if (strncmp(refname, "refs/heads/", 11))
+ unsigned char tmp[20];
+ int ofs = 11;
+ if (strncmp(refname, "refs/heads/", ofs))
return 0;
- return append_ref(refname + 11, sha1);
+ /* If both heads/foo and tags/foo exists, get_sha1 would
+ * get confused.
+ */
+ if (get_sha1(refname + ofs, tmp) || memcmp(tmp, sha1, 20))
+ ofs = 5;
+ return append_ref(refname + ofs, sha1);
}
static int append_tag_ref(const char *refname, const unsigned char *sha1)
return append_ref(refname + 5, sha1);
}
+static const char *match_ref_pattern = NULL;
+static int match_ref_slash = 0;
+static int count_slash(const char *s)
+{
+ int cnt = 0;
+ while (*s)
+ if (*s++ == '/')
+ cnt++;
+ return cnt;
+}
+
+static int append_matching_ref(const char *refname, const unsigned char *sha1)
+{
+ /* we want to allow pattern hold/<asterisk> to show all
+ * branches under refs/heads/hold/, and v0.99.9? to show
+ * refs/tags/v0.99.9a and friends.
+ */
+ const char *tail;
+ int slash = count_slash(refname);
+ for (tail = refname; *tail && match_ref_slash < slash; )
+ if (*tail++ == '/')
+ slash--;
+ if (!*tail)
+ return 0;
+ if (fnmatch(match_ref_pattern, tail, 0))
+ return 0;
+ if (!strncmp("refs/heads/", refname, 11))
+ return append_head_ref(refname, sha1);
+ if (!strncmp("refs/tags/", refname, 10))
+ return append_tag_ref(refname, sha1);
+ return append_ref(refname, sha1);
+}
+
static void snarf_refs(int head, int tag)
{
if (head) {
}
}
-static int rev_is_head(char *head_path, int headlen,
- char *name,
+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))
+ if ((!head_path[0]) ||
+ (head_sha1 && sha1 && memcmp(head_sha1, sha1, 20)))
return 0;
namelen = strlen(name);
if ((headlen < namelen) ||
return 0;
}
+static void append_one_rev(const char *av)
+{
+ unsigned char revkey[20];
+ if (!get_sha1(av, revkey)) {
+ append_ref(av, revkey);
+ return;
+ }
+ if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
+ /* glob style match */
+ int saved_matches = ref_name_cnt;
+ match_ref_pattern = av;
+ match_ref_slash = count_slash(av);
+ for_each_ref(append_matching_ref);
+ if (saved_matches == ref_name_cnt &&
+ ref_name_cnt < MAX_REVS)
+ error("no matching refs with %s", av);
+ if (saved_matches + 1 < ref_name_cnt)
+ sort_ref_range(saved_matches, ref_name_cnt);
+ return;
+ }
+ die("bad sha1 reference %s", av);
+}
+
+static int git_show_branch_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "showbranch.default")) {
+ if (default_alloc <= default_num + 1) {
+ default_alloc = default_alloc * 3 / 2 + 20;
+ default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc);
+ }
+ default_arg[default_num++] = strdup(value);
+ default_arg[default_num] = NULL;
+ return 0;
+ }
+
+ return git_default_config(var, value);
+}
+
int main(int ac, char **av)
{
struct commit *rev[MAX_REVS], *commit;
unsigned int rev_mask[MAX_REVS];
int num_rev, i, extra = 0;
int all_heads = 0, all_tags = 0;
- int all_mask, all_revs, shown_merge_point;
+ int all_mask, all_revs;
+ int lifo = 1;
char head_path[128];
const char *head_path_p;
int head_path_len;
int independent = 0;
int no_name = 0;
int sha1_name = 0;
+ int shown_merge_point = 0;
+ int with_current_branch = 0;
+ int head_at = -1;
+ int topics = 0;
setup_git_directory();
+ git_config(git_show_branch_config);
+
+ /* If nothing is specified, try the default first */
+ if (ac == 1 && default_num) {
+ ac = default_num + 1;
+ av = default_arg - 1; /* ick; we would not address av[0] */
+ }
while (1 < ac && av[1][0] == '-') {
char *arg = av[1];
- if (!strcmp(arg, "--all"))
+ if (!strcmp(arg, "--")) {
+ ac--; av++;
+ break;
+ }
+ else if (!strcmp(arg, "--all"))
all_heads = all_tags = 1;
else if (!strcmp(arg, "--heads"))
all_heads = 1;
extra = -1;
else if (!strcmp(arg, "--no-name"))
no_name = 1;
+ else if (!strcmp(arg, "--current"))
+ with_current_branch = 1;
else if (!strcmp(arg, "--sha1-name"))
sha1_name = 1;
else if (!strncmp(arg, "--more=", 7))
merge_base = 1;
else if (!strcmp(arg, "--independent"))
independent = 1;
+ else if (!strcmp(arg, "--topo-order"))
+ lifo = 1;
+ else if (!strcmp(arg, "--topics"))
+ topics = 1;
+ else if (!strcmp(arg, "--date-order"))
+ lifo = 0;
else
usage(show_branch_usage);
ac--; av++;
if (1 < independent + merge_base + (extra != 0))
usage(show_branch_usage);
+ /* If nothing is specified, show all branches by default */
+ if (ac + all_heads + all_tags == 0)
+ all_heads = 1;
+
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);
+ append_one_rev(*av);
ac--; av++;
}
- /* If still no revs, then add heads */
- if (!ref_name_cnt)
- snarf_refs(1, 0);
+ head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
+ if (head_path_p) {
+ head_path_len = strlen(head_path_p);
+ memcpy(head_path, head_path_p, head_path_len + 1);
+ }
+ else {
+ head_path_len = 0;
+ head_path[0] = 0;
+ }
+
+ if (with_current_branch && head_path_p) {
+ int has_head = 0;
+ for (i = 0; !has_head && i < ref_name_cnt; i++) {
+ /* We are only interested in adding the branch
+ * HEAD points at.
+ */
+ if (rev_is_head(head_path,
+ head_path_len,
+ ref_name[i],
+ head_sha1, NULL))
+ has_head++;
+ }
+ if (!has_head) {
+ int pfxlen = strlen(git_path("refs/heads/"));
+ append_one_rev(head_path + pfxlen);
+ }
+ }
+
+ if (!ref_name_cnt) {
+ fprintf(stderr, "No revs to be shown.\n");
+ exit(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);
+ die("'%s' is not a valid ref.", ref_name[num_rev]);
commit = lookup_commit_reference(revkey);
if (!commit)
die("cannot find commit %s (%s)",
if (0 <= extra)
join_revs(&list, &seen, num_rev, extra);
- head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
- if (head_path_p) {
- head_path_len = strlen(head_path_p);
- memcpy(head_path, head_path_p, head_path_len + 1);
- }
- else {
- head_path_len = 0;
- head_path[0] = 0;
- }
-
if (merge_base)
return show_merge_base(seen, num_rev);
}
/* header lines never need name */
show_one_commit(rev[i], 1);
+ if (is_head)
+ head_at = i;
}
if (0 <= extra) {
for (i = 0; i < num_rev; i++)
exit(0);
/* Sort topologically */
- sort_in_topological_order(&seen);
+ sort_in_topological_order(&seen, lifo);
/* Give names to commits */
if (!sha1_name && !no_name)
all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
- shown_merge_point = 0;
while (seen) {
struct commit *commit = pop_one_commit(&seen);
int this_flag = commit->object.flags;
- int is_merge_point = (this_flag & all_revs) == all_revs;
+ int is_merge_point = ((this_flag & all_revs) == all_revs);
- if (is_merge_point)
- shown_merge_point = 1;
+ shown_merge_point |= is_merge_point;
if (1 < num_rev) {
- for (i = 0; i < num_rev; i++)
- putchar((this_flag & (1u << (i + REV_SHIFT)))
- ? '+' : ' ');
+ int is_merge = !!(commit->parents && commit->parents->next);
+ if (topics &&
+ !is_merge_point &&
+ (this_flag & (1u << REV_SHIFT)))
+ continue;
+
+ for (i = 0; i < num_rev; i++) {
+ int mark;
+ if (!(this_flag & (1u << (i + REV_SHIFT))))
+ mark = ' ';
+ else if (is_merge)
+ mark = '-';
+ else if (i == head_at)
+ mark = '*';
+ else
+ mark = '+';
+ putchar(mark);
+ }
putchar(' ');
}
show_one_commit(commit, no_name);
- if (shown_merge_point && is_merge_point)
- if (--extra < 0)
- break;
+
+ if (shown_merge_point && --extra < 0)
+ break;
}
return 0;
}