Merge branch 'js/for-each-ref-remote-name-and-ref'
authorJunio C Hamano <gitster@pobox.com>
Wed, 15 Nov 2017 03:14:32 +0000 (12:14 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 15 Nov 2017 03:14:32 +0000 (12:14 +0900)
The "--format=..." option "git for-each-ref" takes learned to show
the name of the 'remote' repository and the ref at the remote side
that is affected for 'upstream' and 'push' via "%(push:remotename)"
and friends.

* js/for-each-ref-remote-name-and-ref:
for-each-ref: test :remotename and :remoteref
for-each-ref: let upstream/push report the remote ref name
for-each-ref: let upstream/push optionally report the remote name

1  2 
Documentation/git-for-each-ref.txt
ref-filter.c
remote.c
t/t6300-for-each-ref.sh
index 1d420e4cde8230de00aae583a296128fdd59140f,174af2c0ec205c8cab1f03b723b87812f3340ae0..dffa14a7950e074bbff73ec79defdbbdcc9702be
@@@ -57,11 -57,6 +57,11 @@@ OPTION
        `xx`; for example `%00` interpolates to `\0` (NUL),
        `%09` to `\t` (TAB) and `%0a` to `\n` (LF).
  
 +--color[=<when>]:
 +      Respect any colors specified in the `--format` option. The
 +      `<when>` field must be one of `always`, `never`, or `auto` (if
 +      `<when>` is absent, behave as if `always` was given).
 +
  --shell::
  --perl::
  --python::
@@@ -145,18 -140,25 +145,25 @@@ upstream:
        (behind), "<>" (ahead and behind), or "=" (in sync). `:track`
        also prints "[gone]" whenever unknown upstream ref is
        encountered. Append `:track,nobracket` to show tracking
-       information without brackets (i.e "ahead N, behind M").  Has
-       no effect if the ref does not have tracking information
-       associated with it.  All the options apart from `nobracket`
-       are mutually exclusive, but if used together the last option
-       is selected.
+       information without brackets (i.e "ahead N, behind M").
+ +
+ For any remote-tracking branch `%(upstream)`, `%(upstream:remotename)`
+ and `%(upstream:remoteref)` refer to the name of the remote and the
+ name of the tracked remote ref, respectively. In other words, the
+ remote-tracking branch can be updated explicitly and individually by
+ using the refspec `%(upstream:remoteref):%(upstream)` to fetch from
+ `%(upstream:remotename)`.
+ +
+ Has no effect if the ref does not have tracking information associated
+ with it.  All the options apart from `nobracket` are mutually exclusive,
+ but if used together the last option is selected.
  
  push::
        The name of a local ref which represents the `@{push}`
        location for the displayed ref. Respects `:short`, `:lstrip`,
-       `:rstrip`, `:track`, and `:trackshort` options as `upstream`
-       does. Produces an empty string if no `@{push}` ref is
-       configured.
+       `:rstrip`, `:track`, `:trackshort`, `:remotename`, and `:remoteref`
+       options as `upstream` does. Produces an empty string if no `@{push}`
+       ref is configured.
  
  HEAD::
        '*' if HEAD matches current ref (the checked out branch), ' '
@@@ -218,15 -220,11 +225,15 @@@ and `date` to extract the named compone
  The complete message in a commit and tag object is `contents`.
  Its first line is `contents:subject`, where subject is the concatenation
  of all lines of the commit message up to the first blank line.  The next
 -line is 'contents:body', where body is all of the lines after the first
 +line is `contents:body`, where body is all of the lines after the first
  blank line.  The optional GPG signature is `contents:signature`.  The
  first `N` lines of the message is obtained using `contents:lines=N`.
  Additionally, the trailers as interpreted by linkgit:git-interpret-trailers[1]
 -are obtained as 'contents:trailers'.
 +are obtained as `trailers` (or by using the historical alias
 +`contents:trailers`).  Non-trailer lines from the trailer block can be omitted
 +with `trailers:only`. Whitespace-continuations can be removed from trailers so
 +that each trailer appears on a line by itself with its full content with
 +`trailers:unfold`. Both can be used together as `trailers:unfold,only`.
  
  For sorting purposes, fields with numeric values sort in numeric order
  (`objectsize`, `authordate`, `committerdate`, `creatordate`, `taggerdate`).
diff --combined ref-filter.c
index e728b15b3aeec8e8a9154d4ee4a4284cf06b5931,2259563e732d27b91c1e8515958db2452ba27032..3f9161707e66be86eabaf7347da23a2450ffdd9f
@@@ -76,13 -76,14 +76,15 @@@ static struct used_atom 
                char color[COLOR_MAXLEN];
                struct align align;
                struct {
-                       enum { RR_REF, RR_TRACK, RR_TRACKSHORT } option;
+                       enum {
+                               RR_REF, RR_TRACK, RR_TRACKSHORT, RR_REMOTE_NAME, RR_REMOTE_REF
+                       } option;
                        struct refname_atom refname;
-                       unsigned int nobracket : 1;
+                       unsigned int nobracket : 1, push : 1, push_remote : 1;
                } remote_ref;
                struct {
                        enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB, C_TRAILERS } option;
 +                      struct process_trailer_options trailer_opts;
                        unsigned int nlines;
                } contents;
                struct {
@@@ -138,6 -139,9 +140,9 @@@ static void remote_ref_atom_parser(cons
        struct string_list params = STRING_LIST_INIT_DUP;
        int i;
  
+       if (!strcmp(atom->name, "push") || starts_with(atom->name, "push:"))
+               atom->u.remote_ref.push = 1;
        if (!arg) {
                atom->u.remote_ref.option = RR_REF;
                refname_atom_parser_internal(&atom->u.remote_ref.refname,
                        atom->u.remote_ref.option = RR_TRACKSHORT;
                else if (!strcmp(s, "nobracket"))
                        atom->u.remote_ref.nobracket = 1;
-               else {
+               else if (!strcmp(s, "remotename")) {
+                       atom->u.remote_ref.option = RR_REMOTE_NAME;
+                       atom->u.remote_ref.push_remote = 1;
+               } else if (!strcmp(s, "remoteref")) {
+                       atom->u.remote_ref.option = RR_REMOTE_REF;
+                       atom->u.remote_ref.push_remote = 1;
+               } else {
                        atom->u.remote_ref.option = RR_REF;
                        refname_atom_parser_internal(&atom->u.remote_ref.refname,
                                                     arg, atom->name);
@@@ -183,23 -193,9 +194,23 @@@ static void subject_atom_parser(const s
  
  static void trailers_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
  {
 -      if (arg)
 -              die(_("%%(trailers) does not take arguments"));
 +      struct string_list params = STRING_LIST_INIT_DUP;
 +      int i;
 +
 +      if (arg) {
 +              string_list_split(&params, arg, ',', -1);
 +              for (i = 0; i < params.nr; i++) {
 +                      const char *s = params.items[i].string;
 +                      if (!strcmp(s, "unfold"))
 +                              atom->u.contents.trailer_opts.unfold = 1;
 +                      else if (!strcmp(s, "only"))
 +                              atom->u.contents.trailer_opts.only_trailers = 1;
 +                      else
 +                              die(_("unknown %%(trailers) argument: %s"), s);
 +              }
 +      }
        atom->u.contents.option = C_TRAILERS;
 +      string_list_clear(&params, 0);
  }
  
  static void contents_atom_parser(const struct ref_format *format, 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)) {
 +      else if (skip_prefix(arg, "trailers", &arg)) {
 +              skip_prefix(arg, ":", &arg);
 +              trailers_atom_parser(format, atom, *arg ? arg : NULL);
 +      } else if (skip_prefix(arg, "lines=", &arg)) {
                atom->u.contents.option = C_LINES;
                if (strtoul_ui(arg, 10, &atom->u.contents.nlines))
                        die(_("positive value expected contents:lines=%s"), arg);
@@@ -429,16 -424,8 +440,16 @@@ static int parse_ref_filter_atom(const 
        REALLOC_ARRAY(used_atom, used_atom_cnt);
        used_atom[at].name = xmemdupz(atom, ep - atom);
        used_atom[at].type = valid_atom[i].cmp_type;
 -      if (arg)
 +      if (arg) {
                arg = used_atom[at].name + (arg - atom) + 1;
 +              if (!*arg) {
 +                      /*
 +                       * Treat empty sub-arguments list as NULL (i.e.,
 +                       * "%(atom:)" is equivalent to "%(atom)").
 +                       */
 +                      arg = NULL;
 +              }
 +      }
        memset(&used_atom[at].u, 0, sizeof(used_atom[at].u));
        if (valid_atom[i].parser)
                valid_atom[i].parser(format, &used_atom[at], arg);
@@@ -1064,7 -1051,7 +1075,7 @@@ static void grab_sub_body_contents(stru
                        name++;
                if (strcmp(name, "subject") &&
                    strcmp(name, "body") &&
 -                  strcmp(name, "trailers") &&
 +                  !starts_with(name, "trailers") &&
                    !starts_with(name, "contents"))
                        continue;
                if (!subpos)
                        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;
 +                      struct strbuf s = STRBUF_INIT;
 +
 +                      /* Format the trailer info according to the trailer_opts given */
 +                      format_trailers_from_commit(&s, subpos, &atom->u.contents.trailer_opts);
  
 -                      /* 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);
 +                      v->s = strbuf_detach(&s, NULL);
                } else if (atom->u.contents.option == C_BARE)
                        v->s = xstrdup(subpos);
        }
@@@ -1268,6 -1256,25 +1279,25 @@@ static void fill_remote_ref_details(str
                        *s = ">";
                else
                        *s = "<>";
+       } else if (atom->u.remote_ref.option == RR_REMOTE_NAME) {
+               int explicit;
+               const char *remote = atom->u.remote_ref.push ?
+                       pushremote_for_branch(branch, &explicit) :
+                       remote_for_branch(branch, &explicit);
+               if (explicit)
+                       *s = xstrdup(remote);
+               else
+                       *s = "";
+       } else if (atom->u.remote_ref.option == RR_REMOTE_REF) {
+               int explicit;
+               const char *merge;
+               merge = remote_ref_for_branch(branch, atom->u.remote_ref.push,
+                                             &explicit);
+               if (explicit)
+                       *s = xstrdup(merge);
+               else
+                       *s = "";
        } else
                die("BUG: unhandled RR_* enum");
  }
@@@ -1377,16 -1384,20 +1407,20 @@@ static void populate_value(struct ref_a
                        if (refname)
                                fill_remote_ref_details(atom, refname, branch, &v->s);
                        continue;
-               } else if (starts_with(name, "push")) {
+               } else if (atom->u.remote_ref.push) {
                        const char *branch_name;
                        if (!skip_prefix(ref->refname, "refs/heads/",
                                         &branch_name))
                                continue;
                        branch = branch_get(branch_name);
  
-                       refname = branch_get_push(branch, NULL);
-                       if (!refname)
-                               continue;
+                       if (atom->u.remote_ref.push_remote)
+                               refname = NULL;
+                       else {
+                               refname = branch_get_push(branch, NULL);
+                               if (!refname)
+                                       continue;
+                       }
                        fill_remote_ref_details(atom, refname, branch, &v->s);
                        continue;
                } else if (starts_with(name, "color:")) {
diff --combined remote.c
index 685e776a65ed9c971ab6952ebfd886f78b451181,2bdcfc280cd430a59c9cf9c6b300d18909c98fc9..4e93753e1988afd4a01559951f96142c6dc2e73d
+++ b/remote.c
@@@ -675,6 -675,36 +675,36 @@@ const char *pushremote_for_branch(struc
        return remote_for_branch(branch, explicit);
  }
  
+ const char *remote_ref_for_branch(struct branch *branch, int for_push,
+                                 int *explicit)
+ {
+       if (branch) {
+               if (!for_push) {
+                       if (branch->merge_nr) {
+                               if (explicit)
+                                       *explicit = 1;
+                               return branch->merge_name[0];
+                       }
+               } else {
+                       const char *dst, *remote_name =
+                               pushremote_for_branch(branch, NULL);
+                       struct remote *remote = remote_get(remote_name);
+                       if (remote && remote->push_refspec_nr &&
+                           (dst = apply_refspecs(remote->push,
+                                                 remote->push_refspec_nr,
+                                                 branch->refname))) {
+                               if (explicit)
+                                       *explicit = 1;
+                               return dst;
+                       }
+               }
+       }
+       if (explicit)
+               *explicit = 0;
+       return "";
+ }
  static struct remote *remote_get_1(const char *name,
                                   const char *(*get_default)(struct branch *, int *))
  {
@@@ -1629,7 -1659,7 +1659,7 @@@ static void set_merge(struct branch *re
                    strcmp(ret->remote_name, "."))
                        continue;
                if (dwim_ref(ret->merge_name[i], strlen(ret->merge_name[i]),
 -                           oid.hash, &ref) == 1)
 +                           &oid, &ref) == 1)
                        ret->merge[i]->dst = ref;
                else
                        ret->merge[i]->dst = xstrdup(ret->merge_name[i]);
@@@ -2002,13 -2032,13 +2032,13 @@@ int stat_tracking_info(struct branch *b
                return -1;
  
        /* Cannot stat if what we used to build on no longer exists */
 -      if (read_ref(base, oid.hash))
 +      if (read_ref(base, &oid))
                return -1;
        theirs = lookup_commit_reference(&oid);
        if (!theirs)
                return -1;
  
 -      if (read_ref(branch->refname, oid.hash))
 +      if (read_ref(branch->refname, &oid))
                return -1;
        ours = lookup_commit_reference(&oid);
        if (!ours)
@@@ -2327,7 -2357,7 +2357,7 @@@ static int remote_tracking(struct remot
        dst = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname);
        if (!dst)
                return -1; /* no tracking ref for refname at remote */
 -      if (read_ref(dst, oid->hash))
 +      if (read_ref(dst, oid))
                return -1; /* we know what the tracking ref is but we cannot read it */
        return 0;
  }
diff --combined t/t6300-for-each-ref.sh
index 3aa534933e08f85ea1c215e5ef6cc6c7f4a7cd31,d9eb2be0256e3988e8aeff4c8812cd80a5ff7a75..c128dfc5790790de9edf1b4d2cfa8b028c1036bc
@@@ -51,7 -51,6 +51,7 @@@ test_atom() 
  }
  
  test_atom head refname refs/heads/master
 +test_atom head refname: refs/heads/master
  test_atom head refname:short master
  test_atom head refname:lstrip=1 heads/master
  test_atom head refname:lstrip=2 master
@@@ -426,7 -425,8 +426,7 @@@ test_expect_success 'set up color tests
  '
  
  test_expect_success TTY '%(color) shows color with a tty' '
 -      test_terminal env TERM=vt100 \
 -              git for-each-ref --format="$color_format" >actual.raw &&
 +      test_terminal git for-each-ref --format="$color_format" >actual.raw &&
        test_decode_color <actual.raw >actual &&
        test_cmp expected.color actual
  '
@@@ -436,17 -436,12 +436,17 @@@ test_expect_success '%(color) does not 
        test_cmp expected.bare actual
  '
  
 -test_expect_success 'color.ui=always can override tty check' '
 -      git -c color.ui=always for-each-ref --format="$color_format" >actual.raw &&
 +test_expect_success '--color can override tty check' '
 +      git for-each-ref --color --format="$color_format" >actual.raw &&
        test_decode_color <actual.raw >actual &&
        test_cmp expected.color actual
  '
  
 +test_expect_success 'color.ui=always does not override tty check' '
 +      git -c color.ui=always for-each-ref --format="$color_format" >actual &&
 +      test_cmp expected.bare actual
 +'
 +
  cat >expected <<\EOF
  heads/master
  tags/master
@@@ -610,104 -605,18 +610,104 @@@ test_expect_success 'do not dereferenc
  cat >trailers <<EOF
  Reviewed-by: A U Thor <author@example.com>
  Signed-off-by: A U Thor <author@example.com>
 +[ v2 updated patch description ]
 +Acked-by: A U Thor
 +  <author@example.com>
  EOF
  
 -test_expect_success 'basic atom: head contents:trailers' '
 +unfold () {
 +      perl -0pe 's/\n\s+/ /g'
 +}
 +
 +test_expect_success 'set up trailers for next test' '
        echo "Some contents" > two &&
        git add two &&
 -      git commit -F - <<-EOF &&
 +      git commit -F - <<-EOF
        trailers: this commit message has trailers
  
        Some message contents
  
        $(cat trailers)
        EOF
 +'
 +
 +test_expect_success '%(trailers:unfold) unfolds trailers' '
 +      git for-each-ref --format="%(trailers:unfold)" refs/heads/master >actual &&
 +      {
 +              unfold <trailers
 +              echo
 +      } >expect &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success '%(trailers:only) shows only "key: value" trailers' '
 +      git for-each-ref --format="%(trailers:only)" refs/heads/master >actual &&
 +      {
 +              grep -v patch.description <trailers &&
 +              echo
 +      } >expect &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success '%(trailers:only) and %(trailers:unfold) work together' '
 +      git for-each-ref --format="%(trailers:only,unfold)" refs/heads/master >actual &&
 +      git for-each-ref --format="%(trailers:unfold,only)" refs/heads/master >reverse &&
 +      test_cmp actual reverse &&
 +      {
 +              grep -v patch.description <trailers | unfold &&
 +              echo
 +      } >expect &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success '%(contents:trailers:unfold) unfolds trailers' '
 +      git for-each-ref --format="%(contents:trailers:unfold)" refs/heads/master >actual &&
 +      {
 +              unfold <trailers
 +              echo
 +      } >expect &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success '%(contents:trailers:only) shows only "key: value" trailers' '
 +      git for-each-ref --format="%(contents:trailers:only)" refs/heads/master >actual &&
 +      {
 +              grep -v patch.description <trailers &&
 +              echo
 +      } >expect &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success '%(contents:trailers:only) and %(contents:trailers:unfold) work together' '
 +      git for-each-ref --format="%(contents:trailers:only,unfold)" refs/heads/master >actual &&
 +      git for-each-ref --format="%(contents:trailers:unfold,only)" refs/heads/master >reverse &&
 +      test_cmp actual reverse &&
 +      {
 +              grep -v patch.description <trailers | unfold &&
 +              echo
 +      } >expect &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success '%(trailers) rejects unknown trailers arguments' '
 +      # error message cannot be checked under i18n
 +      cat >expect <<-EOF &&
 +      fatal: unknown %(trailers) argument: unsupported
 +      EOF
 +      test_must_fail git for-each-ref --format="%(trailers:unsupported)" 2>actual &&
 +      test_i18ncmp expect actual
 +'
 +
 +test_expect_success '%(contents:trailers) rejects unknown trailers arguments' '
 +      # error message cannot be checked under i18n
 +      cat >expect <<-EOF &&
 +      fatal: unknown %(trailers) argument: unsupported
 +      EOF
 +      test_must_fail git for-each-ref --format="%(contents:trailers:unsupported)" 2>actual &&
 +      test_i18ncmp expect actual
 +'
 +
 +test_expect_success 'basic atom: head contents:trailers' '
        git for-each-ref --format="%(contents:trailers)" refs/heads/master >actual &&
        sanitize_pgp <actual >actual.clean &&
        # git for-each-ref ends with a blank line
@@@ -766,4 -675,36 +766,36 @@@ test_expect_success 'Verify usage of %(
        test_cmp expected actual
  '
  
+ test_expect_success ':remotename and :remoteref' '
+       git init remote-tests &&
+       (
+               cd remote-tests &&
+               test_commit initial &&
+               git remote add from fifth.coffee:blub &&
+               git config branch.master.remote from &&
+               git config branch.master.merge refs/heads/stable &&
+               git remote add to southridge.audio:repo &&
+               git config remote.to.push "refs/heads/*:refs/heads/pushed/*" &&
+               git config branch.master.pushRemote to &&
+               for pair in "%(upstream)=refs/remotes/from/stable" \
+                       "%(upstream:remotename)=from" \
+                       "%(upstream:remoteref)=refs/heads/stable" \
+                       "%(push)=refs/remotes/to/pushed/master" \
+                       "%(push:remotename)=to" \
+                       "%(push:remoteref)=refs/heads/pushed/master"
+               do
+                       echo "${pair#*=}" >expect &&
+                       git for-each-ref --format="${pair%=*}" \
+                               refs/heads/master >actual &&
+                       test_cmp expect actual
+               done &&
+               git branch push-simple &&
+               git config branch.push-simple.pushRemote from &&
+               actual="$(git for-each-ref \
+                       --format="%(push:remotename),%(push:remoteref)" \
+                       refs/heads/push-simple)" &&
+               test from, = "$actual"
+       )
+ '
  test_done