ref-filter: add an 'rstrip=<N>' option to atoms which deal with refnames
[gitweb.git] / ref-filter.c
index 9adbb8af3eaa138ccf161a91ce5b00e366fae7fa..47e292bc46105544d5342a7e1ebe61b8cfdd36e8 100644 (file)
 #include "utf8.h"
 #include "git-compat-util.h"
 #include "version.h"
+#include "trailer.h"
+#include "wt-status.h"
 
 typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
+typedef enum { COMPARE_EQUAL, COMPARE_UNEQUAL, COMPARE_NONE } cmp_status;
 
 struct align {
        align_type position;
        unsigned int width;
 };
 
+struct if_then_else {
+       cmp_status cmp_status;
+       const char *str;
+       unsigned int then_atom_seen : 1,
+               else_atom_seen : 1,
+               condition_satisfied : 1;
+};
+
+struct refname_atom {
+       enum { R_NORMAL, R_SHORT, R_LSTRIP, R_RSTRIP } option;
+       int lstrip, rstrip;
+};
+
 /*
  * An atom is a valid field atom listed below, possibly prefixed with
  * a "*" to denote deref_tag().
@@ -37,13 +53,24 @@ static struct used_atom {
        union {
                char color[COLOR_MAXLEN];
                struct align align;
-               enum { RR_NORMAL, RR_SHORTEN, RR_TRACK, RR_TRACKSHORT }
-                       remote_ref;
                struct {
-                       enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB } option;
+                       enum { RR_REF, RR_TRACK, RR_TRACKSHORT } option;
+                       struct refname_atom refname;
+                       unsigned int nobracket : 1;
+               } remote_ref;
+               struct {
+                       enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB, C_TRAILERS } option;
                        unsigned int nlines;
                } contents;
-               enum { O_FULL, O_SHORT } objectname;
+               struct {
+                       cmp_status cmp_status;
+                       const char *str;
+               } if_then_else;
+               struct {
+                       enum { O_FULL, O_LENGTH, O_SHORT } option;
+                       unsigned int length;
+               } objectname;
+               struct refname_atom refname;
        } u;
 } *used_atom;
 static int used_atom_cnt, need_tagged, need_symref;
@@ -57,18 +84,57 @@ static void color_atom_parser(struct used_atom *atom, const char *color_value)
                die(_("unrecognized color: %%(color:%s)"), color_value);
 }
 
-static void remote_ref_atom_parser(struct used_atom *atom, const char *arg)
+static void refname_atom_parser_internal(struct refname_atom *atom,
+                                        const char *arg, const char *name)
 {
        if (!arg)
-               atom->u.remote_ref = RR_NORMAL;
+               atom->option = R_NORMAL;
        else if (!strcmp(arg, "short"))
-               atom->u.remote_ref = RR_SHORTEN;
-       else if (!strcmp(arg, "track"))
-               atom->u.remote_ref = RR_TRACK;
-       else if (!strcmp(arg, "trackshort"))
-               atom->u.remote_ref = RR_TRACKSHORT;
-       else
-               die(_("unrecognized format: %%(%s)"), atom->name);
+               atom->option = R_SHORT;
+       else if (skip_prefix(arg, "lstrip=", &arg)) {
+               atom->option = R_LSTRIP;
+               if (strtol_i(arg, 10, &atom->lstrip))
+                       die(_("Integer value expected refname:lstrip=%s"), arg);
+       } else if (skip_prefix(arg, "rstrip=", &arg)) {
+               atom->option = R_RSTRIP;
+               if (strtol_i(arg, 10, &atom->rstrip))
+                       die(_("Integer value expected refname:rstrip=%s"), arg);
+       } else
+               die(_("unrecognized %%(%s) argument: %s"), name, arg);
+}
+
+static void remote_ref_atom_parser(struct used_atom *atom, const char *arg)
+{
+       struct string_list params = STRING_LIST_INIT_DUP;
+       int i;
+
+       if (!arg) {
+               atom->u.remote_ref.option = RR_REF;
+               refname_atom_parser_internal(&atom->u.remote_ref.refname,
+                                            arg, atom->name);
+               return;
+       }
+
+       atom->u.remote_ref.nobracket = 0;
+       string_list_split(&params, arg, ',', -1);
+
+       for (i = 0; i < params.nr; i++) {
+               const char *s = params.items[i].string;
+
+               if (!strcmp(s, "track"))
+                       atom->u.remote_ref.option = RR_TRACK;
+               else if (!strcmp(s, "trackshort"))
+                       atom->u.remote_ref.option = RR_TRACKSHORT;
+               else if (!strcmp(s, "nobracket"))
+                       atom->u.remote_ref.nobracket = 1;
+               else {
+                       atom->u.remote_ref.option = RR_REF;
+                       refname_atom_parser_internal(&atom->u.remote_ref.refname,
+                                                    arg, atom->name);
+               }
+       }
+
+       string_list_clear(&params, 0);
 }
 
 static void body_atom_parser(struct used_atom *atom, const char *arg)
@@ -85,6 +151,13 @@ static void subject_atom_parser(struct used_atom *atom, const char *arg)
        atom->u.contents.option = C_SUB;
 }
 
+static void trailers_atom_parser(struct used_atom *atom, const char *arg)
+{
+       if (arg)
+               die(_("%%(trailers) does not take arguments"));
+       atom->u.contents.option = C_TRAILERS;
+}
+
 static void contents_atom_parser(struct used_atom *atom, const char *arg)
 {
        if (!arg)
@@ -95,6 +168,8 @@ static void contents_atom_parser(struct used_atom *atom, const char *arg)
                atom->u.contents.option = C_SIG;
        else if (!strcmp(arg, "subject"))
                atom->u.contents.option = C_SUB;
+       else if (!strcmp(arg, "trailers"))
+               atom->u.contents.option = C_TRAILERS;
        else if (skip_prefix(arg, "lines=", &arg)) {
                atom->u.contents.option = C_LINES;
                if (strtoul_ui(arg, 10, &atom->u.contents.nlines))
@@ -106,13 +181,25 @@ static void contents_atom_parser(struct used_atom *atom, const char *arg)
 static void objectname_atom_parser(struct used_atom *atom, const char *arg)
 {
        if (!arg)
-               atom->u.objectname = O_FULL;
+               atom->u.objectname.option = O_FULL;
        else if (!strcmp(arg, "short"))
-               atom->u.objectname = O_SHORT;
-       else
+               atom->u.objectname.option = O_SHORT;
+       else if (skip_prefix(arg, "short=", &arg)) {
+               atom->u.objectname.option = O_LENGTH;
+               if (strtoul_ui(arg, 10, &atom->u.objectname.length) ||
+                   atom->u.objectname.length == 0)
+                       die(_("positive value expected objectname:short=%s"), arg);
+               if (atom->u.objectname.length < MINIMUM_ABBREV)
+                       atom->u.objectname.length = MINIMUM_ABBREV;
+       } else
                die(_("unrecognized %%(objectname) argument: %s"), arg);
 }
 
+static void refname_atom_parser(struct used_atom *atom, const char *arg)
+{
+       return refname_atom_parser_internal(&atom->u.refname, arg, atom->name);
+}
+
 static align_type parse_align_position(const char *s)
 {
        if (!strcmp(s, "right"))
@@ -163,12 +250,27 @@ static void align_atom_parser(struct used_atom *atom, const char *arg)
        string_list_clear(&params, 0);
 }
 
+static void if_atom_parser(struct used_atom *atom, const char *arg)
+{
+       if (!arg) {
+               atom->u.if_then_else.cmp_status = COMPARE_NONE;
+               return;
+       } else if (skip_prefix(arg, "equals=", &atom->u.if_then_else.str)) {
+               atom->u.if_then_else.cmp_status = COMPARE_EQUAL;
+       } else if (skip_prefix(arg, "notequals=", &atom->u.if_then_else.str)) {
+               atom->u.if_then_else.cmp_status = COMPARE_UNEQUAL;
+       } else {
+               die(_("unrecognized %%(if) argument: %s"), arg);
+       }
+}
+
+
 static struct {
        const char *name;
        cmp_type cmp_type;
        void (*parser)(struct used_atom *atom, const char *arg);
 } valid_atom[] = {
-       { "refname" },
+       { "refname" , FIELD_STR, refname_atom_parser },
        { "objecttype" },
        { "objectsize", FIELD_ULONG },
        { "objectname", FIELD_STR, objectname_atom_parser },
@@ -194,15 +296,19 @@ static struct {
        { "creatordate", FIELD_TIME },
        { "subject", FIELD_STR, subject_atom_parser },
        { "body", FIELD_STR, body_atom_parser },
+       { "trailers", FIELD_STR, trailers_atom_parser },
        { "contents", FIELD_STR, contents_atom_parser },
        { "upstream", FIELD_STR, remote_ref_atom_parser },
        { "push", FIELD_STR, remote_ref_atom_parser },
-       { "symref" },
+       { "symref", FIELD_STR, refname_atom_parser },
        { "flag" },
        { "HEAD" },
        { "color", FIELD_STR, color_atom_parser },
        { "align", FIELD_STR, align_atom_parser },
        { "end" },
+       { "if", FIELD_STR, if_atom_parser },
+       { "then" },
+       { "else" },
 };
 
 #define REF_FORMATTING_STATE_INIT  { 0, NULL }
@@ -210,7 +316,7 @@ static struct {
 struct ref_formatting_stack {
        struct ref_formatting_stack *prev;
        struct strbuf output;
-       void (*at_end)(struct ref_formatting_stack *stack);
+       void (*at_end)(struct ref_formatting_stack **stack);
        void *at_end_data;
 };
 
@@ -221,11 +327,9 @@ struct ref_formatting_state {
 
 struct atom_value {
        const char *s;
-       union {
-               struct align align;
-       } u;
        void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);
        unsigned long ul; /* used for sorting when not FIELD_STR */
+       struct used_atom *atom;
 };
 
 /*
@@ -235,7 +339,7 @@ int parse_ref_filter_atom(const char *atom, const char *ep)
 {
        const char *sp;
        const char *arg;
-       int i, at;
+       int i, at, atom_len;
 
        sp = atom;
        if (*sp == '*' && sp < ep)
@@ -250,19 +354,19 @@ int parse_ref_filter_atom(const char *atom, const char *ep)
                        return i;
        }
 
+       /*
+        * If the atom name has a colon, strip it and everything after
+        * it off - it specifies the format for this entry, and
+        * shouldn't be used for checking against the valid_atom
+        * table.
+        */
+       arg = memchr(sp, ':', ep - sp);
+       atom_len = (arg ? arg : ep) - sp;
+
        /* Is the atom a valid one? */
        for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
                int len = strlen(valid_atom[i].name);
-
-               /*
-                * If the atom name has a colon, strip it and everything after
-                * it off - it specifies the format for this entry, and
-                * shouldn't be used for checking against the valid_atom
-                * table.
-                */
-               arg = memchr(sp, ':', ep - sp);
-               if (len == (arg ? arg : ep) - sp &&
-                   !memcmp(valid_atom[i].name, sp, len))
+               if (len == atom_len && !memcmp(valid_atom[i].name, sp, len))
                        break;
        }
 
@@ -282,7 +386,7 @@ int parse_ref_filter_atom(const char *atom, const char *ep)
                valid_atom[i].parser(&used_atom[at], arg);
        if (*atom == '*')
                need_tagged = 1;
-       if (!strcmp(used_atom[at].name, "symref"))
+       if (!strcmp(valid_atom[i].name, "symref"))
                need_symref = 1;
        return at;
 }
@@ -343,13 +447,14 @@ static void pop_stack_element(struct ref_formatting_stack **stack)
        *stack = prev;
 }
 
-static void end_align_handler(struct ref_formatting_stack *stack)
+static void end_align_handler(struct ref_formatting_stack **stack)
 {
-       struct align *align = (struct align *)stack->at_end_data;
+       struct ref_formatting_stack *cur = *stack;
+       struct align *align = (struct align *)cur->at_end_data;
        struct strbuf s = STRBUF_INIT;
 
-       strbuf_utf8_align(&s, align->position, align->width, stack->output.buf);
-       strbuf_swap(&stack->output, &s);
+       strbuf_utf8_align(&s, align->position, align->width, cur->output.buf);
+       strbuf_swap(&cur->output, &s);
        strbuf_release(&s);
 }
 
@@ -360,7 +465,115 @@ static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_s
        push_stack_element(&state->stack);
        new = state->stack;
        new->at_end = end_align_handler;
-       new->at_end_data = &atomv->u.align;
+       new->at_end_data = &atomv->atom->u.align;
+}
+
+static void if_then_else_handler(struct ref_formatting_stack **stack)
+{
+       struct ref_formatting_stack *cur = *stack;
+       struct ref_formatting_stack *prev = cur->prev;
+       struct if_then_else *if_then_else = (struct if_then_else *)cur->at_end_data;
+
+       if (!if_then_else->then_atom_seen)
+               die(_("format: %%(if) atom used without a %%(then) atom"));
+
+       if (if_then_else->else_atom_seen) {
+               /*
+                * There is an %(else) atom: we need to drop one state from the
+                * stack, either the %(else) branch if the condition is satisfied, or
+                * the %(then) branch if it isn't.
+                */
+               if (if_then_else->condition_satisfied) {
+                       strbuf_reset(&cur->output);
+                       pop_stack_element(&cur);
+               } else {
+                       strbuf_swap(&cur->output, &prev->output);
+                       strbuf_reset(&cur->output);
+                       pop_stack_element(&cur);
+               }
+       } else if (!if_then_else->condition_satisfied) {
+               /*
+                * No %(else) atom: just drop the %(then) branch if the
+                * condition is not satisfied.
+                */
+               strbuf_reset(&cur->output);
+       }
+
+       *stack = cur;
+       free(if_then_else);
+}
+
+static void if_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
+{
+       struct ref_formatting_stack *new;
+       struct if_then_else *if_then_else = xcalloc(sizeof(struct if_then_else), 1);
+
+       if_then_else->str = atomv->atom->u.if_then_else.str;
+       if_then_else->cmp_status = atomv->atom->u.if_then_else.cmp_status;
+
+       push_stack_element(&state->stack);
+       new = state->stack;
+       new->at_end = if_then_else_handler;
+       new->at_end_data = if_then_else;
+}
+
+static int is_empty(const char *s)
+{
+       while (*s != '\0') {
+               if (!isspace(*s))
+                       return 0;
+               s++;
+       }
+       return 1;
+}
+
+static void then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
+{
+       struct ref_formatting_stack *cur = state->stack;
+       struct if_then_else *if_then_else = NULL;
+
+       if (cur->at_end == if_then_else_handler)
+               if_then_else = (struct if_then_else *)cur->at_end_data;
+       if (!if_then_else)
+               die(_("format: %%(then) atom used without an %%(if) atom"));
+       if (if_then_else->then_atom_seen)
+               die(_("format: %%(then) atom used more than once"));
+       if (if_then_else->else_atom_seen)
+               die(_("format: %%(then) atom used after %%(else)"));
+       if_then_else->then_atom_seen = 1;
+       /*
+        * If the 'equals' or 'notequals' attribute is used then
+        * perform the required comparison. If not, only non-empty
+        * strings satisfy the 'if' condition.
+        */
+       if (if_then_else->cmp_status == COMPARE_EQUAL) {
+               if (!strcmp(if_then_else->str, cur->output.buf))
+                       if_then_else->condition_satisfied = 1;
+       } else if (if_then_else->cmp_status == COMPARE_UNEQUAL) {
+               if (strcmp(if_then_else->str, cur->output.buf))
+                       if_then_else->condition_satisfied = 1;
+       } else if (cur->output.len && !is_empty(cur->output.buf))
+               if_then_else->condition_satisfied = 1;
+       strbuf_reset(&cur->output);
+}
+
+static void else_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
+{
+       struct ref_formatting_stack *prev = state->stack;
+       struct if_then_else *if_then_else = NULL;
+
+       if (prev->at_end == if_then_else_handler)
+               if_then_else = (struct if_then_else *)prev->at_end_data;
+       if (!if_then_else)
+               die(_("format: %%(else) atom used without an %%(if) atom"));
+       if (!if_then_else->then_atom_seen)
+               die(_("format: %%(else) atom used without a %%(then) atom"));
+       if (if_then_else->else_atom_seen)
+               die(_("format: %%(else) atom used more than once"));
+       if_then_else->else_atom_seen = 1;
+       push_stack_element(&state->stack);
+       state->stack->at_end_data = prev->at_end_data;
+       state->stack->at_end = prev->at_end;
 }
 
 static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
@@ -370,14 +583,17 @@ static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_sta
 
        if (!current->at_end)
                die(_("format: %%(end) atom used without corresponding atom"));
-       current->at_end(current);
+       current->at_end(&state->stack);
+
+       /*  Stack may have been popped within at_end(), hence reset the current pointer */
+       current = state->stack;
 
        /*
         * Perform quote formatting when the stack element is that of
         * a supporting atom. If nested then perform quote formatting
         * only on the topmost supporting atom.
         */
-       if (!state->stack->prev->prev) {
+       if (!current->prev->prev) {
                quote_formatting(&s, current->output.buf, state->quote_style);
                strbuf_swap(&current->output, &s);
        }
@@ -454,12 +670,15 @@ static int grab_objectname(const char *name, const unsigned char *sha1,
                           struct atom_value *v, struct used_atom *atom)
 {
        if (starts_with(name, "objectname")) {
-               if (atom->u.objectname == O_SHORT) {
+               if (atom->u.objectname.option == O_SHORT) {
                        v->s = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
                        return 1;
-               } else if (atom->u.objectname == O_FULL) {
+               } else if (atom->u.objectname.option == O_FULL) {
                        v->s = xstrdup(sha1_to_hex(sha1));
                        return 1;
+               } else if (atom->u.objectname.option == O_LENGTH) {
+                       v->s = xstrdup(find_unique_abbrev(sha1, atom->u.objectname.length));
+                       return 1;
                } else
                        die("BUG: unknown %%(objectname) option");
        }
@@ -785,6 +1004,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
                        name++;
                if (strcmp(name, "subject") &&
                    strcmp(name, "body") &&
+                   strcmp(name, "trailers") &&
                    !starts_with(name, "contents"))
                        continue;
                if (!subpos)
@@ -808,6 +1028,14 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
                        /*  Size is the length of the message after removing the signature */
                        append_lines(&s, subpos, contents_end - subpos, atom->u.contents.nlines);
                        v->s = strbuf_detach(&s, NULL);
+               } else if (atom->u.contents.option == C_TRAILERS) {
+                       struct trailer_info info;
+
+                       /* Search for trailer info */
+                       trailer_info_get(&info, subpos);
+                       v->s = xmemdupz(info.trailer_start,
+                                       info.trailer_end - info.trailer_start);
+                       trailer_info_release(&info);
                } else if (atom->u.contents.option == C_BARE)
                        v->s = xstrdup(subpos);
        }
@@ -867,50 +1095,108 @@ static inline char *copy_advance(char *dst, const char *src)
        return dst;
 }
 
-static const char *strip_ref_components(const char *refname, const char *nr_arg)
+static const char *lstrip_ref_components(const char *refname, int len)
 {
-       char *end;
-       long nr = strtol(nr_arg, &end, 10);
-       long remaining = nr;
+       long remaining = len;
        const char *start = refname;
 
-       if (nr < 1 || *end != '\0')
-               die(_(":strip= requires a positive integer argument"));
+       if (len < 0) {
+               int i;
+               const char *p = refname;
 
-       while (remaining) {
+               /* Find total no of '/' separated path-components */
+               for (i = 0; p[i]; p[i] == '/' ? i++ : *p++)
+                       ;
+               /*
+                * The number of components we need to strip is now
+                * the total minus the components to be left (Plus one
+                * because we count the number of '/', but the number
+                * of components is one more than the no of '/').
+                */
+               remaining = i + len + 1;
+       }
+
+       while (remaining > 0) {
                switch (*start++) {
                case '\0':
-                       die(_("ref '%s' does not have %ld components to :strip"),
-                           refname, nr);
+                       return "";
                case '/':
                        remaining--;
                        break;
                }
        }
+
+       return start;
+}
+
+static const char *rstrip_ref_components(const char *refname, int len)
+{
+       long remaining = len;
+       char *start = xstrdup(refname);
+
+       if (len < 0) {
+               int i;
+               const char *p = refname;
+
+               /* Find total no of '/' separated path-components */
+               for (i = 0; p[i]; p[i] == '/' ? i++ : *p++)
+                       ;
+               /*
+                * The number of components we need to strip is now
+                * the total minus the components to be left (Plus one
+                * because we count the number of '/', but the number
+                * of components is one more than the no of '/').
+                */
+               remaining = i + len + 1;
+       }
+
+       while (remaining-- > 0) {
+               char *p = strrchr(start, '/');
+               if (p == NULL)
+                       return "";
+               else
+                       p[0] = '\0';
+       }
        return start;
 }
 
+static const char *show_ref(struct refname_atom *atom, const char *refname)
+{
+       if (atom->option == R_SHORT)
+               return shorten_unambiguous_ref(refname, warn_ambiguous_refs);
+       else if (atom->option == R_LSTRIP)
+               return lstrip_ref_components(refname, atom->lstrip);
+       else if (atom->option == R_RSTRIP)
+               return rstrip_ref_components(refname, atom->rstrip);
+       else
+               return refname;
+}
+
 static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
                                    struct branch *branch, const char **s)
 {
        int num_ours, num_theirs;
-       if (atom->u.remote_ref == RR_SHORTEN)
-               *s = shorten_unambiguous_ref(refname, warn_ambiguous_refs);
-       else if (atom->u.remote_ref == RR_TRACK) {
+       if (atom->u.remote_ref.option == RR_REF)
+               *s = show_ref(&atom->u.remote_ref.refname, refname);
+       else if (atom->u.remote_ref.option == RR_TRACK) {
                if (stat_tracking_info(branch, &num_ours,
-                                      &num_theirs, NULL))
-                       return;
-
-               if (!num_ours && !num_theirs)
+                                      &num_theirs, NULL)) {
+                       *s = xstrdup("gone");
+               } else if (!num_ours && !num_theirs)
                        *s = "";
                else if (!num_ours)
-                       *s = xstrfmt("[behind %d]", num_theirs);
+                       *s = xstrfmt("behind %d", num_theirs);
                else if (!num_theirs)
-                       *s = xstrfmt("[ahead %d]", num_ours);
+                       *s = xstrfmt("ahead %d", num_ours);
                else
-                       *s = xstrfmt("[ahead %d, behind %d]",
+                       *s = xstrfmt("ahead %d, behind %d",
                                     num_ours, num_theirs);
-       } else if (atom->u.remote_ref == RR_TRACKSHORT) {
+               if (!atom->u.remote_ref.nobracket && *s[0]) {
+                       const char *to_free = *s;
+                       *s = xstrfmt("[%s]", *s);
+                       free((void *)to_free);
+               }
+       } else if (atom->u.remote_ref.option == RR_TRACKSHORT) {
                if (stat_tracking_info(branch, &num_ours,
                                       &num_theirs, NULL))
                        return;
@@ -923,8 +1209,54 @@ static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
                        *s = ">";
                else
                        *s = "<>";
-       } else /* RR_NORMAL */
-               *s = refname;
+       } else
+               die("BUG: unhandled RR_* enum");
+}
+
+char *get_head_description(void)
+{
+       struct strbuf desc = STRBUF_INIT;
+       struct wt_status_state state;
+       memset(&state, 0, sizeof(state));
+       wt_status_get_state(&state, 1);
+       if (state.rebase_in_progress ||
+           state.rebase_interactive_in_progress)
+               strbuf_addf(&desc, _("(no branch, rebasing %s)"),
+                           state.branch);
+       else if (state.bisect_in_progress)
+               strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
+                           state.branch);
+       else if (state.detached_from) {
+               /* TRANSLATORS: make sure these match _("HEAD detached at ")
+                  and _("HEAD detached from ") in wt-status.c */
+               if (state.detached_at)
+                       strbuf_addf(&desc, _("(HEAD detached at %s)"),
+                               state.detached_from);
+               else
+                       strbuf_addf(&desc, _("(HEAD detached from %s)"),
+                               state.detached_from);
+       }
+       else
+               strbuf_addstr(&desc, _("(no branch)"));
+       free(state.branch);
+       free(state.onto);
+       free(state.detached_from);
+       return strbuf_detach(&desc, NULL);
+}
+
+static const char *get_symref(struct used_atom *atom, struct ref_array_item *ref)
+{
+       if (!ref->symref)
+               return "";
+       else
+               return show_ref(&atom->u.refname, ref->symref);
+}
+
+static const char *get_refname(struct used_atom *atom, struct ref_array_item *ref)
+{
+       if (ref->kind & FILTER_REFS_DETACHED_HEAD)
+               return get_head_description();
+       return show_ref(&atom->u.refname, ref->refname);
 }
 
 /*
@@ -955,10 +1287,10 @@ static void populate_value(struct ref_array_item *ref)
                struct atom_value *v = &ref->value[i];
                int deref = 0;
                const char *refname;
-               const char *formatp;
                struct branch *branch = NULL;
 
                v->handler = append_atom;
+               v->atom = atom;
 
                if (*name == '*') {
                        deref = 1;
@@ -966,9 +1298,9 @@ static void populate_value(struct ref_array_item *ref)
                }
 
                if (starts_with(name, "refname"))
-                       refname = ref->refname;
+                       refname = get_refname(atom, ref);
                else if (starts_with(name, "symref"))
-                       refname = ref->symref ? ref->symref : "";
+                       refname = get_symref(atom, ref);
                else if (starts_with(name, "upstream")) {
                        const char *branch_name;
                        /* only local branches may have an upstream */
@@ -1017,36 +1349,33 @@ static void populate_value(struct ref_array_item *ref)
 
                        head = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
                                                  sha1, NULL);
-                       if (!strcmp(ref->refname, head))
+                       if (head && !strcmp(ref->refname, head))
                                v->s = "*";
                        else
                                v->s = " ";
                        continue;
                } else if (starts_with(name, "align")) {
-                       v->u.align = atom->u.align;
                        v->handler = align_atom_handler;
                        continue;
                } else if (!strcmp(name, "end")) {
                        v->handler = end_atom_handler;
                        continue;
+               } else if (starts_with(name, "if")) {
+                       const char *s;
+
+                       if (skip_prefix(name, "if:", &s))
+                               v->s = xstrdup(s);
+                       v->handler = if_atom_handler;
+                       continue;
+               } else if (!strcmp(name, "then")) {
+                       v->handler = then_atom_handler;
+                       continue;
+               } else if (!strcmp(name, "else")) {
+                       v->handler = else_atom_handler;
+                       continue;
                } else
                        continue;
 
-               formatp = strchr(name, ':');
-               if (formatp) {
-                       const char *arg;
-
-                       formatp++;
-                       if (!strcmp(formatp, "short"))
-                               refname = shorten_unambiguous_ref(refname,
-                                                     warn_ambiguous_refs);
-                       else if (skip_prefix(formatp, "strip=", &arg))
-                               refname = strip_ref_components(refname, arg);
-                       else
-                               die(_("unknown %.*s format %s"),
-                                   (int)(formatp - name), name, formatp);
-               }
-
                if (!deref)
                        v->s = refname;
                else
@@ -1231,8 +1560,14 @@ static int commit_contains(struct ref_filter *filter, struct commit *commit)
  * matches a pattern "refs/heads/mas") or a wildcard (e.g. the same ref
  * matches "refs/heads/mas*", too).
  */
-static int match_pattern(const char **patterns, const char *refname)
+static int match_pattern(const struct ref_filter *filter, const char *refname)
 {
+       const char **patterns = filter->name_patterns;
+       unsigned flags = 0;
+
+       if (filter->ignore_case)
+               flags |= WM_CASEFOLD;
+
        /*
         * When no '--format' option is given we need to skip the prefix
         * for matching refs of tags and branches.
@@ -1243,7 +1578,7 @@ static int match_pattern(const char **patterns, const char *refname)
               skip_prefix(refname, "refs/", &refname));
 
        for (; *patterns; patterns++) {
-               if (!wildmatch(*patterns, refname, 0, NULL))
+               if (!wildmatch(*patterns, refname, flags, NULL))
                        return 1;
        }
        return 0;
@@ -1255,9 +1590,15 @@ static int match_pattern(const char **patterns, const char *refname)
  * matches a pattern "refs/heads/" but not "refs/heads/m") or a
  * wildcard (e.g. the same ref matches "refs/heads/m*", too).
  */
-static int match_name_as_path(const char **pattern, const char *refname)
+static int match_name_as_path(const struct ref_filter *filter, const char *refname)
 {
+       const char **pattern = filter->name_patterns;
        int namelen = strlen(refname);
+       unsigned flags = WM_PATHNAME;
+
+       if (filter->ignore_case)
+               flags |= WM_CASEFOLD;
+
        for (; *pattern; pattern++) {
                const char *p = *pattern;
                int plen = strlen(p);
@@ -1280,8 +1621,8 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname)
        if (!*filter->name_patterns)
                return 1; /* No pattern always matches */
        if (filter->match_as_path)
-               return match_name_as_path(filter->name_patterns, refname);
-       return match_pattern(filter->name_patterns, refname);
+               return match_name_as_path(filter, refname);
+       return match_pattern(filter, refname);
 }
 
 /*
@@ -1536,18 +1877,20 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru
        struct atom_value *va, *vb;
        int cmp;
        cmp_type cmp_type = used_atom[s->atom].type;
+       int (*cmp_fn)(const char *, const char *);
 
        get_ref_atom_value(a, s->atom, &va);
        get_ref_atom_value(b, s->atom, &vb);
+       cmp_fn = s->ignore_case ? strcasecmp : strcmp;
        if (s->version)
                cmp = versioncmp(va->s, vb->s);
        else if (cmp_type == FIELD_STR)
-               cmp = strcmp(va->s, vb->s);
+               cmp = cmp_fn(va->s, vb->s);
        else {
                if (va->ul < vb->ul)
                        cmp = -1;
                else if (va->ul == vb->ul)
-                       cmp = strcmp(a->refname, b->refname);
+                       cmp = cmp_fn(a->refname, b->refname);
                else
                        cmp = 1;
        }
@@ -1573,7 +1916,7 @@ static int compare_refs(const void *a_, const void *b_)
 void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
 {
        ref_sorting = sorting;
-       qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs);
+       QSORT(array->items, array->nr, compare_refs);
 }
 
 static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state)
@@ -1598,10 +1941,10 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting
        }
 }
 
-void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
+void format_ref_array_item(struct ref_array_item *info, const char *format,
+                          int quote_style, struct strbuf *final_buf)
 {
        const char *cp, *sp, *ep;
-       struct strbuf *final_buf;
        struct ref_formatting_state state = REF_FORMATTING_STATE_INIT;
 
        state.quote_style = quote_style;
@@ -1631,9 +1974,17 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
        }
        if (state.stack->prev)
                die(_("format: %%(end) atom missing"));
-       final_buf = &state.stack->output;
-       fwrite(final_buf->buf, 1, final_buf->len, stdout);
+       strbuf_addbuf(final_buf, &state.stack->output);
        pop_stack_element(&state.stack);
+}
+
+void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
+{
+       struct strbuf final_buf = STRBUF_INIT;
+
+       format_ref_array_item(info, format, quote_style, &final_buf);
+       fwrite(final_buf.buf, 1, final_buf.len, stdout);
+       strbuf_release(&final_buf);
        putchar('\n');
 }