{"thread":{"id":"43887","subject":"[PATCH] for-each-ref: add %(upstream:gone) to mark missing refs","startedAt":"2016-08-19T21:50:50Z","lastAt":"2016-08-24T18:06:22Z","messageCount":2,"participants":["Øystein Walle","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"299761","messageId":"20160819215023.10770-1-oystwa@gmail.com","threadId":"43887","inReplyTo":null,"subject":"[PATCH] for-each-ref: add %(upstream:gone) to mark missing refs","fromName":"Øystein Walle","fromEmail":"oystwa@gmail.com","sentAt":"2016-08-19T21:50:23Z","receivedAt":"2016-08-19T21:50:50Z","isPatch":true,"sender":{"key":"oystwa@gmail.com","avatar":"https://avatars.githubusercontent.com/u/794585?v=4"},"body":"git branch -vv will show \"gone\" next to a remote tracking branch if it\ndoes not exist. for-each-ref is suitable for parsing but had no way of\nshowing this information.\n\nThis introduces \"%(upstream:gone)\" to display \"gone\" in the formatted\noutput if the ref does not exist or an empty string otherwise, analogous\nto git branch -vv.\n\nSigned-off-by: Øystein Walle <oystwa@gmail.com>\n---\n Documentation/git-for-each-ref.txt |  5 +++--\n ref-filter.c                       | 10 +++++++++-\n t/t6300-for-each-ref.sh            | 12 ++++++++++++\n 3 files changed, 24 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex f57e69b..039a86b 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -114,8 +114,9 @@ upstream::\n \t`refname` above.  Additionally respects `:track` to show\n \t\"[ahead N, behind M]\" and `:trackshort` to show the terse\n \tversion: \">\" (ahead), \"<\" (behind), \"<>\" (ahead and behind),\n-\tor \"=\" (in sync).  Has no effect if the ref does not have\n-\ttracking information associated with it.\n+\tor \"=\" (in sync) and `:gone` to show \"gone\" if the remote ref\n+\tdoes not exist, or an empty string if it does. Has no effect if\n+\tthe ref does not have tracking information associated with it.\n \n push::\n \tThe name of a local ref which represents the `@{push}` location\ndiff --git a/ref-filter.c b/ref-filter.c\nindex bc551a7..5402052 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -37,7 +37,7 @@ static struct used_atom {\n \tunion {\n \t\tchar color[COLOR_MAXLEN];\n \t\tstruct align align;\n-\t\tenum { RR_NORMAL, RR_SHORTEN, RR_TRACK, RR_TRACKSHORT }\n+\t\tenum { RR_NORMAL, RR_SHORTEN, RR_TRACK, RR_TRACKSHORT, RR_GONE }\n \t\t\tremote_ref;\n \t\tstruct {\n \t\t\tenum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB } option;\n@@ -67,6 +67,8 @@ static void remote_ref_atom_parser(struct used_atom *atom, const char *arg)\n \t\tatom->u.remote_ref = RR_TRACK;\n \telse if (!strcmp(arg, \"trackshort\"))\n \t\tatom->u.remote_ref = RR_TRACKSHORT;\n+\telse if (!strcmp(arg, \"gone\"))\n+\t\tatom->u.remote_ref = RR_GONE;\n \telse\n \t\tdie(_(\"unrecognized format: %%(%s)\"), atom->name);\n }\n@@ -923,6 +925,12 @@ static void fill_remote_ref_details(struct used_atom *atom, const char *refname,\n \t\t\t*s = \">\";\n \t\telse\n \t\t\t*s = \"<>\";\n+\t} else if (atom->u.remote_ref == RR_GONE) {\n+\t\tconst char *upstream;\n+\t\tif (stat_tracking_info(branch, &num_ours, &num_theirs, &upstream) < 0)\n+\t\t\t*s = \"gone\";\n+\t\telse\n+\t\t\t*s = \"\";\n \t} else /* RR_NORMAL */\n \t\t*s = refname;\n }\ndiff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh\nindex 19a2823..1fc5d1d 100755\n--- a/t/t6300-for-each-ref.sh\n+++ b/t/t6300-for-each-ref.sh\n@@ -383,6 +383,18 @@ test_expect_success 'Check that :track[short] works when upstream is invalid' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'Check that :gone produces expected results' '\n+\tcat >expected <<-\\EOF &&\n+gone\n+\tEOF\n+\ttest_when_finished \"git config branch.master.merge refs/heads/master\" &&\n+\tgit config branch.master.merge refs/heads/does-not-exist &&\n+\tgit for-each-ref \\\n+\t\t--format=\"%(upstream:gone)\" \\\n+\t\trefs/heads >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'Check for invalid refname format' '\n \ttest_must_fail git for-each-ref --format=\"%(refname:INVALID)\"\n '\n-- \n2.9.2\n\n"},{"id":"300093","messageId":"20160824180541.jrliwgkrqu2siywd@sigill.intra.peff.net","threadId":"43887","inReplyTo":"20160819215023.10770-1-oystwa@gmail.com","subject":"Re: [PATCH] for-each-ref: add %(upstream:gone) to mark missing refs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-08-24T18:05:41Z","receivedAt":"2016-08-24T18:06:22Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 19, 2016 at 11:50:23PM +0200, Øystein Walle wrote:\n\n> git branch -vv will show \"gone\" next to a remote tracking branch if it\n> does not exist. for-each-ref is suitable for parsing but had no way of\n> showing this information.\n> \n> This introduces \"%(upstream:gone)\" to display \"gone\" in the formatted\n> output if the ref does not exist or an empty string otherwise, analogous\n> to git branch -vv.\n\nHmm. So this sounds like the minimum thing to implement \"branch -vv\"\noutput, but it feels sort of weirdly non-orthogonal. I guess you'd say:\n\n  %(upstream:track)%(upstream:gone)\n\nand assume that only one of them will show any data.\n\nI almost wonder if %(upstream:track) should just output some specific\ntoken for the \"gone\" case, which matches what \"branch -vv\" does.\n\n(I'm also not sure we can match \"branch -vv\" exactly here anyway, as it\nlooks like \"[%(upstream): %(upstream:track)\"], but our\n\"%(upstream:track)\" uses its own brackets.)\n\nI suppose that is a backwards-incompatible change for\n\"%(upstream:track)\", but I'm not sure how much we have promised about\nits format. The error cases seem totally undocumented.\n\n> ---\n>  Documentation/git-for-each-ref.txt |  5 +++--\n>  ref-filter.c                       | 10 +++++++++-\n>  t/t6300-for-each-ref.sh            | 12 ++++++++++++\n>  3 files changed, 24 insertions(+), 3 deletions(-)\n\nThe code itself looks OK to me (assuming we want upstream:gone as the\ninterface), with a few minor nits in the test:\n\n> +test_expect_success 'Check that :gone produces expected results' '\n> +\tcat >expected <<-\\EOF &&\n> +gone\n> +\tEOF\n\nThe \"<<-\" will strip leading tabs, meaning you can indent \"gone\" here to\nmatch the rest of the test.\n\n> +\ttest_when_finished \"git config branch.master.merge refs/heads/master\" &&\n> +\tgit config branch.master.merge refs/heads/does-not-exist &&\n\nI thought this could be test_config, but it is not clever enough to\nrestore an old value, only to remove a value we just added. So this is\nprobably the best solution.\n\n> +\tgit for-each-ref \\\n> +\t\t--format=\"%(upstream:gone)\" \\\n> +\t\trefs/heads >actual &&\n> +\ttest_cmp expected actual\n> +'\n\nThis tests only that we show \"gone\"; do you want to add a second branch\nand make sure that it shows nothing? Maybe you could even use a new\nbranch name, which makes test_config applicable again:\n\n  cat >expected <<-\\EOF &&\n  master \n  no-upstream gone\n  EOF\n  git branch refs/heads/no-upstream &&\n  test_config branch.no-upstream.remote . &&\n  test_config branch.no-upstream.merge refs/heads/does-not-exist &&\n  git for-each-ref --format=\"%(refname:short) %(upstream:gone)\" >actual &&\n  test_cmp expected actual\n\n-Peff\n"}]}