rev-parse: make "whatchanged -- git-fetch-script" work again.
[gitweb.git] / show-branch.c
index 70120005be00bb38feb6d56740a9340f2f353373..7a0dcc649edaf6a823e15f60d04c6ce67348dc42 100644 (file)
@@ -1,10 +1,15 @@
 #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 ] [<refs>...]";
+
+static int default_num = 0;
+static int default_alloc = 0;
+static char **default_arg = NULL;
 
 #define UNINTERESTING  01
 
@@ -54,7 +59,7 @@ static void name_commit(struct commit *commit, const char *head_name, int nth)
 
 /* 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)
@@ -181,11 +186,11 @@ static void join_revs(struct commit_list **list_p,
 
        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);
@@ -199,18 +204,58 @@ static void join_revs(struct commit_list **list_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,
@@ -243,10 +288,54 @@ static void show_one_commit(struct commit *commit, int no_name)
 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)
@@ -258,11 +347,18 @@ 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;
+       /* 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;
        }
@@ -273,9 +369,16 @@ static int append_ref(const char *refname, const unsigned char *sha1)
 
 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)
@@ -285,6 +388,39 @@ 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) {
@@ -299,12 +435,12 @@ static void snarf_refs(int head, int tag)
        }
 }
 
-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) ||
@@ -353,6 +489,44 @@ static int show_independent(struct commit **rev,
        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;
@@ -360,7 +534,7 @@ int main(int ac, char **av)
        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;
        char head_path[128];
        const char *head_path_p;
        int head_path_len;
@@ -369,12 +543,27 @@ int main(int ac, char **av)
        int independent = 0;
        int no_name = 0;
        int sha1_name = 0;
+       int shown_merge_point = 0;
+       int topo_order = 0;
+       int with_current_branch = 0;
+       int head_at = -1;
 
+       git_config(git_show_branch_config);
        setup_git_directory();
 
+       /* 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;
@@ -386,6 +575,8 @@ int main(int ac, char **av)
                        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))
@@ -394,6 +585,8 @@ int main(int ac, char **av)
                        merge_base = 1;
                else if (!strcmp(arg, "--independent"))
                        independent = 1;
+               else if (!strcmp(arg, "--topo-order"))
+                       topo_order = 1;
                else
                        usage(show_branch_usage);
                ac--; av++;
@@ -404,20 +597,49 @@ int main(int ac, char **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];
@@ -426,7 +648,7 @@ int main(int ac, char **av)
                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)",
@@ -449,16 +671,6 @@ int main(int ac, char **av)
        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);
 
@@ -485,6 +697,8 @@ int main(int ac, char **av)
                        }
                        /* 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++)
@@ -496,7 +710,8 @@ int main(int ac, char **av)
                exit(0);
 
        /* Sort topologically */
-       sort_in_topological_order(&seen);
+       if (topo_order)
+               sort_in_topological_order(&seen);
 
        /* Give names to commits */
        if (!sha1_name && !no_name)
@@ -504,26 +719,33 @@ int main(int ac, char **av)
 
        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;
 
-               if (is_merge_point)
-                       shown_merge_point = 1;
+               shown_merge_point |= ((this_flag & all_revs) == all_revs);
 
                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);
+                       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;
 }