Merge branch 'rr/for-each-ref-decoration'
authorJunio C Hamano <gitster@pobox.com>
Fri, 6 Dec 2013 19:07:20 +0000 (11:07 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 6 Dec 2013 19:07:21 +0000 (11:07 -0800)
Add a few formatting directives to "git for-each-ref --format=...",
to paint them in color, etc.

* rr/for-each-ref-decoration:
for-each-ref: avoid color leakage
for-each-ref: introduce %(color:...) for color
for-each-ref: introduce %(upstream:track[short])
for-each-ref: introduce %(HEAD) asterisk marker
t6300 (for-each-ref): don't hardcode SHA-1 hexes
t6300 (for-each-ref): clearly demarcate setup

1  2 
builtin/for-each-ref.c
t/t6300-for-each-ref.sh
diff --combined builtin/for-each-ref.c
index d096051b15c14a4f109bc9a9cfcf9749c14258b9,5ff51d1d322c554fc906a78b7b88130d7a8dd60e..875bd813d5ed6d09622fc3473fdb574e9714bf7f
@@@ -9,6 -9,7 +9,7 @@@
  #include "quote.h"
  #include "parse-options.h"
  #include "remote.h"
+ #include "color.h"
  
  /* Quoting styles */
  #define QUOTE_NONE 0
@@@ -75,6 -76,8 +76,8 @@@ static struct 
        { "upstream" },
        { "symref" },
        { "flag" },
+       { "HEAD" },
+       { "color" },
  };
  
  /*
@@@ -90,6 -93,7 +93,7 @@@
  static const char **used_atom;
  static cmp_type *used_atom_type;
  static int used_atom_cnt, sort_atom_limit, need_tagged, need_symref;
+ static int need_color_reset_at_eol;
  
  /*
   * Used to parse format string and sort specifiers
@@@ -176,13 -180,21 +180,21 @@@ static const char *find_next(const cha
  static int verify_format(const char *format)
  {
        const char *cp, *sp;
+       static const char color_reset[] = "color:reset";
+       need_color_reset_at_eol = 0;
        for (cp = format; *cp && (sp = find_next(cp)); ) {
                const char *ep = strchr(sp, ')');
+               int at;
                if (!ep)
                        return error("malformed format string %s", sp);
                /* sp points at "%(" and ep points at the closing ")" */
-               parse_atom(sp + 2, ep);
+               at = parse_atom(sp + 2, ep);
                cp = ep + 1;
+               if (!memcmp(used_atom[at], "color:", 6))
+                       need_color_reset_at_eol = !!strcmp(used_atom[at], color_reset);
        }
        return 0;
  }
@@@ -205,22 -217,6 +217,22 @@@ static void *get_obj(const unsigned cha
        return buf;
  }
  
 +static int grab_objectname(const char *name, const unsigned char *sha1,
 +                          struct atom_value *v)
 +{
 +      if (!strcmp(name, "objectname")) {
 +              char *s = xmalloc(41);
 +              strcpy(s, sha1_to_hex(sha1));
 +              v->s = s;
 +              return 1;
 +      }
 +      if (!strcmp(name, "objectname:short")) {
 +              v->s = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
 +              return 1;
 +      }
 +      return 0;
 +}
 +
  /* See grab_values */
  static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
  {
                        v->ul = sz;
                        v->s = s;
                }
 -              else if (!strcmp(name, "objectname")) {
 -                      char *s = xmalloc(41);
 -                      strcpy(s, sha1_to_hex(obj->sha1));
 -                      v->s = s;
 -              }
 -              else if (!strcmp(name, "objectname:short")) {
 -                      v->s = xstrdup(find_unique_abbrev(obj->sha1,
 -                                                        DEFAULT_ABBREV));
 -              }
 +              else if (deref)
 +                      grab_objectname(name, obj->sha1, v);
        }
  }
  
@@@ -649,6 -652,7 +661,7 @@@ static void populate_value(struct refin
                int deref = 0;
                const char *refname;
                const char *formatp;
+               struct branch *branch = NULL;
  
                if (*name == '*') {
                        deref = 1;
                else if (!prefixcmp(name, "symref"))
                        refname = ref->symref ? ref->symref : "";
                else if (!prefixcmp(name, "upstream")) {
-                       struct branch *branch;
                        /* only local branches may have an upstream */
                        if (prefixcmp(ref->refname, "refs/heads/"))
                                continue;
                            !branch->merge[0]->dst)
                                continue;
                        refname = branch->merge[0]->dst;
-               }
-               else if (!strcmp(name, "flag")) {
+               } else if (!prefixcmp(name, "color:")) {
+                       char color[COLOR_MAXLEN] = "";
+                       color_parse(name + 6, "--format", color);
+                       v->s = xstrdup(color);
+                       continue;
+               } else if (!strcmp(name, "flag")) {
                        char buf[256], *cp = buf;
                        if (ref->flag & REF_ISSYMREF)
                                cp = copy_advance(cp, ",symref");
                                v->s = xstrdup(buf + 1);
                        }
                        continue;
-               }
-               else if (!deref && grab_objectname(name, ref->objectname, v))
++              } else if (!deref && grab_objectname(name, ref->objectname, v)) {
 +                      continue;
-               else
+               } else if (!strcmp(name, "HEAD")) {
+                       const char *head;
+                       unsigned char sha1[20];
+                       head = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
+                       if (!strcmp(ref->refname, head))
+                               v->s = "*";
+                       else
+                               v->s = " ";
+                       continue;
+               } else
                        continue;
  
                formatp = strchr(name, ':');
-               /* look for "short" refname format */
                if (formatp) {
+                       int num_ours, num_theirs;
                        formatp++;
                        if (!strcmp(formatp, "short"))
                                refname = shorten_unambiguous_ref(refname,
                                                      warn_ambiguous_refs);
-                       else
+                       else if (!strcmp(formatp, "track") &&
+                               !prefixcmp(name, "upstream")) {
+                               char buf[40];
+                               stat_tracking_info(branch, &num_ours, &num_theirs);
+                               if (!num_ours && !num_theirs)
+                                       v->s = "";
+                               else if (!num_ours) {
+                                       sprintf(buf, "[behind %d]", num_theirs);
+                                       v->s = xstrdup(buf);
+                               } else if (!num_theirs) {
+                                       sprintf(buf, "[ahead %d]", num_ours);
+                                       v->s = xstrdup(buf);
+                               } else {
+                                       sprintf(buf, "[ahead %d, behind %d]",
+                                               num_ours, num_theirs);
+                                       v->s = xstrdup(buf);
+                               }
+                               continue;
+                       } else if (!strcmp(formatp, "trackshort") &&
+                               !prefixcmp(name, "upstream")) {
+                               assert(branch);
+                               stat_tracking_info(branch, &num_ours, &num_theirs);
+                               if (!num_ours && !num_theirs)
+                                       v->s = "=";
+                               else if (!num_ours)
+                                       v->s = "<";
+                               else if (!num_theirs)
+                                       v->s = ">";
+                               else
+                                       v->s = "<>";
+                               continue;
+                       } else
                                die("unknown %.*s format %s",
                                    (int)(formatp - name), name, formatp);
                }
@@@ -875,11 -923,9 +934,9 @@@ static void sort_refs(struct ref_sort *
        qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
  }
  
- static void print_value(struct refinfo *ref, int atom, int quote_style)
+ static void print_value(struct atom_value *v, int quote_style)
  {
-       struct atom_value *v;
        struct strbuf sb = STRBUF_INIT;
-       get_value(ref, atom, &v);
        switch (quote_style) {
        case QUOTE_NONE:
                fputs(v->s, stdout);
@@@ -946,15 -992,26 +1003,26 @@@ static void show_ref(struct refinfo *in
        const char *cp, *sp, *ep;
  
        for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
+               struct atom_value *atomv;
                ep = strchr(sp, ')');
                if (cp < sp)
                        emit(cp, sp);
-               print_value(info, parse_atom(sp + 2, ep), quote_style);
+               get_value(info, parse_atom(sp + 2, ep), &atomv);
+               print_value(atomv, quote_style);
        }
        if (*cp) {
                sp = cp + strlen(cp);
                emit(cp, sp);
        }
+       if (need_color_reset_at_eol) {
+               struct atom_value resetv;
+               char color[COLOR_MAXLEN] = "";
+               color_parse("reset", "--format", color);
+               resetv.s = color;
+               print_value(&resetv, quote_style);
+       }
        putchar('\n');
  }
  
diff --combined t/t6300-for-each-ref.sh
index da5fb6c9170b3e7fd818a59fc6e48ff3161d12da,46866bab01f7c9be0515cc02457224aaa3def859..bda354c1c48bce9fc6fdc442c3ec89fb034f20d3
@@@ -18,16 -18,13 +18,13 @@@ setdate_and_increment () 
      export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
  }
  
- test_expect_success 'Create sample commit with known timestamp' '
+ test_expect_success setup '
        setdate_and_increment &&
        echo "Using $datestamp" > one &&
        git add one &&
        git commit -m "Initial" &&
        setdate_and_increment &&
-       git tag -a -m "Tagging at $datestamp" testtag
- '
- test_expect_success 'Create upstream config' '
+       git tag -a -m "Tagging at $datestamp" testtag &&
        git update-ref refs/remotes/origin/master master &&
        git remote add origin nowhere &&
        git config branch.master.remote origin &&
@@@ -52,14 -49,12 +49,14 @@@ test_atom head refname refs/heads/maste
  test_atom head upstream refs/remotes/origin/master
  test_atom head objecttype commit
  test_atom head objectsize 171
- test_atom head objectname 67a36f10722846e891fbada1ba48ed035de75581
- test_atom head tree 0e51c00fcb93dffc755546f27593d511e1bdb46f
+ test_atom head objectname $(git rev-parse refs/heads/master)
+ test_atom head tree $(git rev-parse refs/heads/master^{tree})
  test_atom head parent ''
  test_atom head numparent 0
  test_atom head object ''
  test_atom head type ''
 +test_atom head '*objectname' ''
 +test_atom head '*objecttype' ''
  test_atom head author 'A U Thor <author@example.com> 1151939924 +0200'
  test_atom head authorname 'A U Thor'
  test_atom head authoremail '<author@example.com>'
@@@ -82,19 -77,18 +79,20 @@@ test_atom head contents:body '
  test_atom head contents:signature ''
  test_atom head contents 'Initial
  '
+ test_atom head HEAD '*'
  
  test_atom tag refname refs/tags/testtag
  test_atom tag upstream ''
  test_atom tag objecttype tag
  test_atom tag objectsize 154
- test_atom tag objectname 98b46b1d36e5b07909de1b3886224e3e81e87322
+ test_atom tag objectname $(git rev-parse refs/tags/testtag)
  test_atom tag tree ''
  test_atom tag parent ''
  test_atom tag numparent ''
- test_atom tag object '67a36f10722846e891fbada1ba48ed035de75581'
+ test_atom tag object $(git rev-parse refs/tags/testtag^0)
  test_atom tag type 'commit'
 +test_atom tag '*objectname' '67a36f10722846e891fbada1ba48ed035de75581'
 +test_atom tag '*objecttype' 'commit'
  test_atom tag author ''
  test_atom tag authorname ''
  test_atom tag authoremail ''
@@@ -117,6 -111,7 +115,7 @@@ test_atom tag contents:body '
  test_atom tag contents:signature ''
  test_atom tag contents 'Tagging at 1151939927
  '
+ test_atom tag HEAD ' '
  
  test_expect_success 'Check invalid atoms names are errors' '
        test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
@@@ -308,8 -303,35 +307,35 @@@ test_expect_success 'Check short upstre
        test_cmp expected actual
  '
  
+ test_expect_success 'setup for upstream:track[short]' '
+       test_commit two
+ '
+ cat >expected <<EOF
+ [ahead 1]
+ EOF
+ test_expect_success 'Check upstream:track format' '
+       git for-each-ref --format="%(upstream:track)" refs/heads >actual &&
+       test_cmp expected actual
+ '
+ cat >expected <<EOF
+ >
+ EOF
+ test_expect_success 'Check upstream:trackshort format' '
+       git for-each-ref --format="%(upstream:trackshort)" refs/heads >actual &&
+       test_cmp expected actual
+ '
+ test_expect_success 'Check that :track[short] cannot be used with other atoms' '
+       test_must_fail git for-each-ref --format="%(refname:track)" 2>/dev/null &&
+       test_must_fail git for-each-ref --format="%(refname:trackshort)" 2>/dev/null
+ '
  cat >expected <<EOF
- 67a36f1
+ $(git rev-parse --short HEAD)
  EOF
  
  test_expect_success 'Check short objectname format' '
@@@ -321,6 -343,23 +347,23 @@@ test_expect_success 'Check for invalid 
        test_must_fail git for-each-ref --format="%(refname:INVALID)"
  '
  
+ get_color ()
+ {
+       git config --get-color no.such.slot "$1"
+ }
+ cat >expected <<EOF
+ $(git rev-parse --short refs/heads/master) $(get_color green)master$(get_color reset)
+ $(git rev-parse --short refs/remotes/origin/master) $(get_color green)origin/master$(get_color reset)
+ $(git rev-parse --short refs/tags/testtag) $(get_color green)testtag$(get_color reset)
+ $(git rev-parse --short refs/tags/two) $(get_color green)two$(get_color reset)
+ EOF
+ test_expect_success 'Check %(color:...) ' '
+       git for-each-ref --format="%(objectname:short) %(color:green)%(refname:short)" >actual &&
+       test_cmp expected actual
+ '
  cat >expected <<\EOF
  heads/master
  tags/master
@@@ -460,9 -499,9 +503,9 @@@ test_atom refs/tags/signed-long content
  body contents
  $sig"
  
- cat >expected <<\EOF
408fe76d02a785a006c2e9c669b7be5589ede96d <committer@example.com> refs/tags/master
90b5ebede4899eda64893bc2a4c8f1d6fb6dfc40 <committer@example.com> refs/tags/bogo
+ cat >expected <<EOF
$(git rev-parse refs/tags/master) <committer@example.com> refs/tags/master
$(git rev-parse refs/tags/bogo) <committer@example.com> refs/tags/bogo
  EOF
  
  test_expect_success 'Verify sort with multiple keys' '