From: Junio C Hamano Date: Wed, 15 Nov 2017 03:14:32 +0000 (+0900) Subject: Merge branch 'js/for-each-ref-remote-name-and-ref' X-Git-Tag: v2.16.0-rc0~128 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/093048b22913be23fe45635e8723d3aa3a185ff3?hp=-c Merge branch 'js/for-each-ref-remote-name-and-ref' 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 --- 093048b22913be23fe45635e8723d3aa3a185ff3 diff --combined Documentation/git-for-each-ref.txt index 1d420e4cde,174af2c0ec..dffa14a795 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@@ -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[=]: + Respect any colors specified in the `--format` option. The + `` field must be one of `always`, `never`, or `auto` (if + `` 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 e728b15b3a,2259563e73..3f9161707e --- a/ref-filter.c +++ b/ref-filter.c @@@ -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, @@@ -157,7 -161,13 +162,13 @@@ 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(¶ms, 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(¶ms, 0); } static void contents_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg) @@@ -212,10 -208,9 +223,10 @@@ 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) @@@ -1089,12 -1076,13 +1100,12 @@@ 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 685e776a65,2bdcfc280c..4e93753e19 --- a/remote.c +++ 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 3aa534933e,d9eb2be025..c128dfc579 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@@ -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 && 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 && 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 < Signed-off-by: A U Thor +[ v2 updated patch description ] +Acked-by: A U Thor + 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 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 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 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 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 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 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.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