Merge branch 'kn/ref-filter-branch-list'
authorJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2017 21:57:13 +0000 (13:57 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2017 21:57:13 +0000 (13:57 -0800)
The code to list branches in "git branch" has been consolidated
with the more generic ref-filter API.

* kn/ref-filter-branch-list: (21 commits)
ref-filter: resurrect "strip" as a synonym to "lstrip"
branch: implement '--format' option
branch: use ref-filter printing APIs
branch, tag: use porcelain output
ref-filter: allow porcelain to translate messages in the output
ref-filter: add an 'rstrip=<N>' option to atoms which deal with refnames
ref-filter: modify the 'lstrip=<N>' option to work with negative '<N>'
ref-filter: Do not abruptly die when using the 'lstrip=<N>' option
ref-filter: rename the 'strip' option to 'lstrip'
ref-filter: make remote_ref_atom_parser() use refname_atom_parser_internal()
ref-filter: introduce refname_atom_parser()
ref-filter: introduce refname_atom_parser_internal()
ref-filter: make "%(symref)" atom work with the ':short' modifier
ref-filter: add support for %(upstream:track,nobracket)
ref-filter: make %(upstream:track) prints "[gone]" for invalid upstreams
ref-filter: introduce format_ref_array_item()
ref-filter: move get_head_description() from branch.c
ref-filter: modify "%(objectname:short)" to take length
ref-filter: implement %(if:equals=<string>) and %(if:notequals=<string>)
ref-filter: include reference to 'used_atom' within 'atom_value'
...

1  2 
Documentation/git-branch.txt
builtin/tag.c
ref-filter.c
ref-filter.h
index 28d46cc03b217c156769f6123c7b3e3351eabce4,1fae4eeee3b255178f075b5ad4fa848d8cd8f73f..092f1bcf9f89f124a2d3c80eadeab5b4a63db941
@@@ -12,7 -12,7 +12,7 @@@ SYNOPSI
        [--list] [-v [--abbrev=<length> | --no-abbrev]]
        [--column[=<options>] | --no-column]
        [(--merged | --no-merged | --contains) [<commit>]] [--sort=<key>]
-       [--points-at <object>] [<pattern>...]
+       [--points-at <object>] [--format=<format>] [<pattern>...]
  'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
  'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
  'git branch' --unset-upstream [<branchname>]
@@@ -91,9 -91,6 +91,9 @@@ OPTION
        based sha1 expressions such as "<branchname>@\{yesterday}".
        Note that in non-bare repositories, reflogs are usually
        enabled by default by the `core.logallrefupdates` config option.
 +      The negated form `--no-create-reflog` only overrides an earlier
 +      `--create-reflog`, but currently does not negate the setting of
 +      `core.logallrefupdates`.
  
  -f::
  --force::
@@@ -253,6 -250,11 +253,11 @@@ start-point is either a local or remote
  --points-at <object>::
        Only list branches of the given object.
  
+ --format <format>::
+       A string that interpolates `%(fieldname)` from the object
+       pointed at by a ref being shown.  The format is the same as
+       that of linkgit:git-for-each-ref[1].
  Examples
  --------
  
diff --combined builtin/tag.c
index e40c4a96763a371b104a5bf47e6d839ed93e8ef5,8a1a476db711820802cfb09d87d4203f0b0d0305..e6c1cf7006896482c37b52981c7f8b8904efafda
@@@ -24,7 -24,7 +24,7 @@@ static const char * const git_tag_usage
        N_("git tag -d <tagname>..."),
        N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
                "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
 -      N_("git tag -v <tagname>..."),
 +      N_("git tag -v [--format=<format>] <tagname>..."),
        NULL
  };
  
@@@ -45,11 -45,11 +45,11 @@@ static int list_tags(struct ref_filter 
        if (!format) {
                if (filter->lines) {
                        to_free = xstrfmt("%s %%(contents:lines=%d)",
-                                         "%(align:15)%(refname:strip=2)%(end)",
+                                         "%(align:15)%(refname:lstrip=2)%(end)",
                                          filter->lines);
                        format = to_free;
                } else
-                       format = "%(refname:strip=2)";
+                       format = "%(refname:lstrip=2)";
        }
  
        verify_ref_format(format);
  }
  
  typedef int (*each_tag_name_fn)(const char *name, const char *ref,
 -                              const unsigned char *sha1);
 +                              const unsigned char *sha1, const void *cb_data);
  
 -static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
 +static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
 +                           const void *cb_data)
  {
        const char **p;
        char ref[PATH_MAX];
                        had_error = 1;
                        continue;
                }
 -              if (fn(*p, ref, sha1))
 +              if (fn(*p, ref, sha1, cb_data))
                        had_error = 1;
        }
        return had_error;
  }
  
  static int delete_tag(const char *name, const char *ref,
 -                              const unsigned char *sha1)
 +                    const unsigned char *sha1, const void *cb_data)
  {
        if (delete_ref(ref, sha1, 0))
                return 1;
  }
  
  static int verify_tag(const char *name, const char *ref,
 -                              const unsigned char *sha1)
 +                    const unsigned char *sha1, const void *cb_data)
  {
 -      return gpg_verify_tag(sha1, name, GPG_VERIFY_VERBOSE);
 +      int flags;
 +      const char *fmt_pretty = cb_data;
 +      flags = GPG_VERIFY_VERBOSE;
 +
 +      if (fmt_pretty)
 +              flags = GPG_VERIFY_OMIT_STATUS;
 +
 +      if (gpg_verify_tag(sha1, name, flags))
 +              return -1;
 +
 +      if (fmt_pretty)
 +              pretty_print_ref(name, sha1, fmt_pretty);
 +
 +      return 0;
  }
  
  static int do_sign(struct strbuf *buffer)
@@@ -389,6 -375,8 +389,8 @@@ int cmd_tag(int argc, const char **argv
                OPT_END()
        };
  
+       setup_ref_filter_porcelain_msg();
        git_config(git_tag_config, sorting_tail);
  
        memset(&opt, 0, sizeof(opt));
        if (filter.merge_commit)
                die(_("--merged and --no-merged option are only allowed with -l"));
        if (cmdmode == 'd')
 -              return for_each_tag_name(argv, delete_tag);
 -      if (cmdmode == 'v')
 -              return for_each_tag_name(argv, verify_tag);
 +              return for_each_tag_name(argv, delete_tag, NULL);
 +      if (cmdmode == 'v') {
 +              if (format)
 +                      verify_ref_format(format);
 +              return for_each_tag_name(argv, verify_tag, format);
 +      }
  
        if (msg.given || msgfile) {
                if (msg.given && msgfile)
diff --combined ref-filter.c
index 3820b21cc75f5e7fd4b1b1851be0aaa9e3268d7d,2a94d6da98a210577850cc832f64fed346df89c6..1ec0fb8391ba6be716e76c688a0f9e2f0208adc2
  #include "git-compat-util.h"
  #include "version.h"
  #include "trailer.h"
+ #include "wt-status.h"
+ static struct ref_msg {
+       const char *gone;
+       const char *ahead;
+       const char *behind;
+       const char *ahead_behind;
+ } msgs = {
+        /* Untranslated plumbing messages: */
+       "gone",
+       "ahead %d",
+       "behind %d",
+       "ahead %d, behind %d"
+ };
+ void setup_ref_filter_porcelain_msg(void)
+ {
+       msgs.gone = _("gone");
+       msgs.ahead = _("ahead %d");
+       msgs.behind = _("behind %d");
+       msgs.ahead_behind = _("ahead %d, behind %d");
+ }
  
  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().
@@@ -38,13 -74,24 +74,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 { 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;
@@@ -58,18 -105,58 +105,58 @@@ static void color_atom_parser(struct us
                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) ||
+                skip_prefix(arg, "strip=", &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)
@@@ -116,13 -203,25 +203,25 @@@ static void contents_atom_parser(struc
  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"))
@@@ -173,12 -272,27 +272,27 @@@ static void align_atom_parser(struct us
        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 },
        { "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 }
  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;
  };
  
@@@ -232,11 -349,9 +349,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;
  };
  
  /*
@@@ -293,7 -408,7 +408,7 @@@ int parse_ref_filter_atom(const char *a
                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;
  }
@@@ -354,13 -469,14 +469,14 @@@ static void pop_stack_element(struct re
        *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);
  }
  
@@@ -371,7 -487,115 +487,115 @@@ static void align_atom_handler(struct a
        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)
  
        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);
        }
@@@ -465,12 -692,15 +692,15 @@@ static int grab_objectname(const char *
                           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");
        }
@@@ -887,50 -1117,108 +1117,108 @@@ static inline char *copy_advance(char *
        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;
+               /* 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) {
+       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(msgs.gone);
+               } else if (!num_ours && !num_theirs)
                        *s = "";
                else if (!num_ours)
-                       *s = xstrfmt("[behind %d]", num_theirs);
+                       *s = xstrfmt(msgs.behind, num_theirs);
                else if (!num_theirs)
-                       *s = xstrfmt("[ahead %d]", num_ours);
+                       *s = xstrfmt(msgs.ahead, num_ours);
                else
-                       *s = xstrfmt("[ahead %d, behind %d]",
+                       *s = xstrfmt(msgs.ahead_behind,
                                     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;
                        *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)
++                      /* TRANSLATORS: make sure this matches
++                         "HEAD detached at " in wt-status.c */
+                       strbuf_addf(&desc, _("(HEAD detached at %s)"),
+                               state.detached_from);
+               else
++                      /* TRANSLATORS: make sure this matches
++                         "HEAD detached from " in wt-status.c */
+                       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);
  }
  
  /*
@@@ -975,10 -1309,10 +1311,10 @@@ static void populate_value(struct ref_a
                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;
                }
  
                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 */
                                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
@@@ -1361,7 -1692,7 +1694,7 @@@ static struct ref_array_item *new_ref_a
        return ref;
  }
  
 -static int filter_ref_kind(struct ref_filter *filter, const char *refname)
 +static int ref_kind_from_refname(const char *refname)
  {
        unsigned int i;
  
                { "refs/tags/", FILTER_REFS_TAGS}
        };
  
 -      if (filter->kind == FILTER_REFS_BRANCHES ||
 -          filter->kind == FILTER_REFS_REMOTES ||
 -          filter->kind == FILTER_REFS_TAGS)
 -              return filter->kind;
 -      else if (!strcmp(refname, "HEAD"))
 +      if (!strcmp(refname, "HEAD"))
                return FILTER_REFS_DETACHED_HEAD;
  
        for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
        return FILTER_REFS_OTHERS;
  }
  
 +static int filter_ref_kind(struct ref_filter *filter, const char *refname)
 +{
 +      if (filter->kind == FILTER_REFS_BRANCHES ||
 +          filter->kind == FILTER_REFS_REMOTES ||
 +          filter->kind == FILTER_REFS_TAGS)
 +              return filter->kind;
 +      return ref_kind_from_refname(refname);
 +}
 +
  /*
   * A call-back given to for_each_ref().  Filter refs and keep them for
   * later object processing.
@@@ -1594,7 -1920,8 +1927,7 @@@ static int cmp_ref_sorting(struct ref_s
        return (s->reverse) ? -cmp : cmp;
  }
  
 -static struct ref_sorting *ref_sorting;
 -static int compare_refs(const void *a_, const void *b_)
 +static int compare_refs(const void *a_, const void *b_, void *ref_sorting)
  {
        struct ref_array_item *a = *((struct ref_array_item **)a_);
        struct ref_array_item *b = *((struct ref_array_item **)b_);
  
  void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
  {
 -      ref_sorting = sorting;
 -      QSORT(array->items, array->nr, compare_refs);
 +      QSORT_S(array->items, array->nr, compare_refs, sorting);
  }
  
  static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state)
        }
  }
  
- 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;
        }
        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');
  }
  
 +void pretty_print_ref(const char *name, const unsigned char *sha1,
 +              const char *format)
 +{
 +      struct ref_array_item *ref_item;
 +      ref_item = new_ref_array_item(name, sha1, 0);
 +      ref_item->kind = ref_kind_from_refname(name);
 +      show_ref_array_item(ref_item, format, 0);
 +      free_array_item(ref_item);
 +}
 +
  /*  If no sorting option is given, use refname to sort as default */
  struct ref_sorting *ref_default_sorting(void)
  {
diff --combined ref-filter.h
index 7b05592baf00661a08f973d0a218f7e0afd99453,44b36eded0f8f0071b5b77353008eaa7c679c468..154e24c405fc9526ab1279b1c515271f0d2cfd7b
@@@ -100,6 -100,9 +100,9 @@@ int parse_ref_filter_atom(const char *a
  int verify_ref_format(const char *format);
  /*  Sort the given ref_array as per the ref_sorting provided */
  void ref_array_sort(struct ref_sorting *sort, struct ref_array *array);
+ /*  Based on the given format and quote_style, fill the strbuf */
+ void format_ref_array_item(struct ref_array_item *info, const char *format,
+                          int quote_style, struct strbuf *final_buf);
  /*  Print the ref using the given format and quote_style */
  void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style);
  /*  Callback function for parsing the sort option */
@@@ -108,12 -111,9 +111,16 @@@ int parse_opt_ref_sorting(const struct 
  struct ref_sorting *ref_default_sorting(void);
  /*  Function to parse --merged and --no-merged options */
  int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset);
+ /*  Get the current HEAD's description */
+ char *get_head_description(void);
+ /*  Set up translated strings in the output. */
+ void setup_ref_filter_porcelain_msg(void);
  
 +/*
 + * Print a single ref, outside of any ref-filter. Note that the
 + * name must be a fully qualified refname.
 + */
 +void pretty_print_ref(const char *name, const unsigned char *sha1,
 +              const char *format);
 +
  #endif /*  REF_FILTER_H  */