branch: introduce --show-current display option
PATCH v5 1/1 branch: introduce --show-current display option
When called with --show-current, git branch will print the current branch name and terminate. Only the actual name gets printed, without refs/heads. In detached HEAD state, nothing is output.
Intended both for scripting and interactive/informative use. Unlike git branch --list, no filtering is needed to just get the branch name.
---
Submitting v5 now that a week has passed since latest maintainer comments.
This is basically v4 but with small fixes to the test, as proposed by Junio on pu, and additionally replacing a subshell with { .. } since Dscho and Eric discovered the negative performance effects of subshell invocations.
Documentation/git-branch.txt | 6 ++++- builtin/branch.c | 25 ++++++++++++++++++-- t/t3203-branch-output.sh | 44 ++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-)
Documentation/git-branch.txt +5 -1
@@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git branch' [--color[=<when>] | --no-color] [-r | -a] - [--list] [-v [--abbrev=<length> | --no-abbrev]] + [--list] [--show-current] [-v [--abbrev=<length> | --no-abbrev]] [--column[=<options>] | --no-column] [--sort=<key>] [(--merged | --no-merged) [<commit>]] [--contains [<commit]] [--no-contains [<commit>]]
@@ -160,6 +160,10 @@ This option is only applicable in non-verbose mode. branch --list 'maint-*'`, list only the branches that match the pattern(s). +--show-current:: + Print the name of the current branch. In detached HEAD state, + nothing is printed. + -v:: -vv:: --verbose::
builtin/branch.c +23 -2
@@ -443,6 +443,21 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortinfree (to_free); } +static void print_current_branch_name (void ) +{ +int flags; +const char *refname =resolve_ref_unsafe ("HEAD" ,0 ,NULL , &flags); +const char *shortname; +if (!refname) +die (_ ("could not resolve HEAD" )); +else if (!(flags &REF_ISSYMREF )) +return ; +else if (skip_prefix (refname,"refs/heads/" , &shortname)) +puts (shortname); +else +die (_ ("HEAD (%s) points outside of refs/heads/" ), refname); +} +static void reject_rebase_or_bisect_branch (const char *target) {struct worktree **worktrees =get_worktrees (0 );
@@ -581,6 +596,7 @@ static int edit_branch_description(const char *branch_name)int cmd_branch (int argc,const char **argv,const char *prefix) {int delete =0 , rename =0 , copy =0 , force =0 , list =0 ; +int show_current =0 ;int reflog =0 , edit_description =0 ;int quiet =0 , unset_upstream =0 ;const char *new_upstream =NULL ;
@@ -620,6 +636,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)OPT_BIT ('c' ,"copy" , ©,N_ ("copy a branch and its reflog" ),1 ),OPT_BIT ('C' ,NULL , ©,N_ ("copy a branch, even if target exists" ),2 ),OPT_BOOL ('l' ,"list" , &list,N_ ("list branch names" )), +OPT_BOOL (0 ,"show-current" , &show_current,N_ ("show current branch name" )),OPT_BOOL (0 ,"create-reflog" , &reflog,N_ ("create the branch's reflog" )),OPT_BOOL (0 ,"edit-description" , &edit_description, N_("edit the description for the branch")),
@@ -662,14 +679,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix) argc =parse_options (argc, argv, prefix, options, builtin_branch_usage,0 ); -if (!delete && !rename && !copy && !edit_description && !new_upstream && !unset_upstream && argc ==0 ) +if (!delete && !rename && !copy && !edit_description && !new_upstream && + !show_current && !unset_upstream && argc ==0 ) list =1 ;if (filter.with_commit || filter.merge !=REF_FILTER_MERGED_NONE || filter.points_at.nr || filter.no_commit) list =1 ; -if (!!delete + !!rename + !!copy + !!new_upstream + +if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current + list + unset_upstream >1 )usage_with_options (builtin_branch_usage, options);
@@ -697,6 +715,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)if (!argc)die (_ ("branch name required" ));return delete_branches (argc, argv, delete >1 , filter.kind, quiet); + }else if (show_current) { +print_current_branch_name (); +return 0 ; }else if (list) {/* git branch --local also shows HEAD when it is detached */ if ((filter.kind &FILTER_REFS_BRANCHES ) && filter.detached)
t/t3203-branch-output.sh +44 -0
50 lines not on this page · raw message
Signature · 1 line
2.19.1.329.gad8739a7f.dirty
Quoted 9 lines · Daniels Umanovskis
When called with --show-current, git branch will print the current branch name and terminate. Only the actual name gets printed, without refs/heads. In detached HEAD state, nothing is output.
Signed-off-by Daniels Umanovskis <[email protected]>---
t/t3203-branch-output.sh +1
-0@@ -100,6 +100,50 @@ test_expect_success 'git branch -v pattern does not show branch summaries' ' +test_expect_success 'git branch `--show-current` works properly when tag exists' '
t/t3203-branch-output.sh test_expect_success 'git branch -v pattern does not show branch summaries' ' · line 110
Quoted 10 lines
t/t3203-branch-output.sh +10
-0+ cat >expect <<-\EOF && + branch-and-tag-name + EOF + test_when_finished " + git checkout branch-one + git branch -D branch-and-tag-name + " && + git checkout -b branch-and-tag-name && + test_when_finished "git tag -d branch-and-tag-name" && + git tag branch-and-tag-name &&
If git-tag crashes before actually creating the new tag, then "git tag -d", passed to test_when_finished(), will error out too, which is probably undesirable since "cleanup code" isn't expected to error out. You could fix it this way:
test_when_finished "git tag -d branch-and-tag-name || :" &&
git tag branch-and-tag-name &&or, even better, just swap the two lines:
git tag branch-and-tag-name &&
test_when_finished "git tag -d branch-and-tag-name" &&However, do you even need to clean up the tag? Are there tests following this one which expect a certain set of tags and fail if this new one is present? If not, a simpler approach might be just to leave the tag alone (and the branch too if that doesn't need to be cleaned up).
Quoted 3 lines
t/t3203-branch-output.sh +3
-0+ git branch --show-current >actual && + test_cmp expect actual +'
Quoted 6 lines · Eric Sunshine
t/t3203-branch-output.sh test_expect_success 'git branch -v pattern does not show branch summaries' ' · line 128
Quoted 2 lines
t/t3203-branch-output.sh +2
-0+ test_when_finished "git tag -d branch-and-tag-name" && + git tag branch-and-tag-name &&If git-tag crashes before actually creating the new tag, then "git tag -d", passed to
test_when_finished(), will error out too, which is probably undesirable since "cleanup code" isn't expected to error out.
Ah, I somehow thought that clean-up actions set up via when_finished are allowed to fail without affecting the outcome, but apparently I was mistaken.
This however can be argued both ways---if you create a tag first and try to set up the clean-up action, during which you may get in trouble and end up leaving the tag behind. So rather than swapping the two lines, explicitly preparing for the case the clean-up action fails, i.e. the first alternative below, would be a good fix.
Also it is a good question if the tag need to be even cleaned up.
Quoted 7 lines
You could fix it this way:
test_when_finished "git tag -d branch-and-tag-name || :" && git tag branch-and-tag-name &&or, even better, just swap the two lines:
Quoted 12 lines
git tag branch-and-tag-name && test_when_finished "git tag -d branch-and-tag-name" &&However, do you even need to clean up the tag? Are there tests following this one which expect a certain set of tags and fail if this new one is present? If not, a simpler approach might be just to leave the tag alone (and the branch too if that doesn't need to be cleaned up).
Quoted 3 lines
t/t3203-branch-output.sh +3
-0+ git branch --show-current >actual && + test_cmp expect actual +'
A bigger question we may want to ask ourselves is if we want to detect failures from these clean-up actions in the first place. There are many hits from "git grep 'when_finished .*|| :' t/", which may be a sign that the when_finished mechanism was misdesigned and we should simply ignore the exit status from the clean-up actions instead.
I haven't gone through the list of when_finished clean-up actions that do not end with "|| :"; I suspect some of them are simply being sloppy and would want to have "|| :", but what I want to find out out of such an audit is if there is a legitimate case where it helps to catch failures in the clean-up actions. If there is none, then
...
Quoted 5 lines · Junio C Hamano
Quoted 3 lines · Eric Sunshine
Quoted 2 lines
t/t3203-branch-output.sh +2
-0+ test_when_finished "git tag -d branch-and-tag-name" && + git tag branch-and-tag-name &&
Quoted 7 lines
Quoted 3 lines
If git-tag crashes before actually creating the new tag, then "git tag -d", passed to
test_when_finished(), will error out too, which is probably undesirable since "cleanup code" isn't expected to error out.Ah, I somehow thought that clean-up actions set up via
when_finishedare allowed to fail without affecting the outcome, but apparently I was mistaken.
If a when_finished block fails, we consider that a test failure. But if we failed to create the tag, the test is failing anyway. Do we actually care at that point?
We would still want to make sure we run the rest of the cleanup, but looking at the definition of test_when_finished(), I think we do.
Quoted 6 lines
I haven't gone through the list of
when_finishedclean-up actions that do not end with "|| :"; I suspect some of them are simply being sloppy and would want to have "|| :", but what I want to find out out of such an audit is if there is a legitimate case where it helps to catch failures in the clean-up actions. If there is none, then
...
I think in the success case it is legitimately helpful. If that "tag -d" failed above (after the tag creation and the rest of the test succeeded), it would certainly be unexpected and we would want to know that it happened. So I think "|| :" in this case is not just unnecessary, but actively bad.
-Peff
Quoted 7 lines · Eric Sunshine
t/t3203-branch-output.sh test_expect_success 'git branch -v pattern does not show branch summaries' ' · line 128
Quoted 7 lines
t/t3203-branch-output.sh +7
-0+ test_when_finished " + git checkout branch-one + git branch -D branch-and-tag-name + " && + git checkout -b branch-and-tag-name && + test_when_finished "git tag -d branch-and-tag-name" && + git tag branch-and-tag-name &&
We've discussed about the exit status from clean-up code already, but another thing worth noticing is that it probably is easier to see what is going on if we use a single when-finished to clear both branch and the tag with the same name. Something like
test_when_finished "
git checkout branch-one
git branch -D branch-and-tag-name
git tag -d branch-and-tag-name
:
" &&
upfront before doing anything else. "checkout" may break if the test that follows when-finished accidentally removes branch-one and that would cascade to a failure to remove branch-and-tag-name branch (because we fail to move away from it), but because there is no && in between, we'd clean as much as we could in such a case, which may or may not be a good thing. And then we hide the exit code by having a ":" at the end.
PATCH 1/1 branch: make --show-current use already resolved HEAD
In reply to Daniels Umanovskisprint_current_branch_name() tries to resolve HEAD and die() when it doesn't resolve it successfully. But the conditions being tested are always unreachable because early in branch:cmd_branch() the same logic is performed.
Eliminate the duplicate and unreachable code, and update the current logic to the more reliable check for the detached head.
---
This patch is meant to be either applied or squashed on top of the current series.
I am basing the claims of it being more reliable of what Junio suggested on a previous iteration of this series:
https://public-inbox.org/git/[email protected]/
But the main goal of this patch is to just bring some attention to this, as I mentioned it in a previous thread but it got lost. After asking on #git-devel, the suggestion was to send it as an incremental patch. So here it is. :)
I still think the mention about scripting should be removed from the original commit message, leaving it open to being taught other tricks like --verbose that aren't necessarily script-friendly.
Cheers
builtin/branch.c | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-)
builtin/branch.c +5 -18
@@ -38,6 +38,7 @@ static const char * const builtin_branch_usage[] = {static const char *head;static struct object_id head_oid; +static int head_flags =0 ;static int branch_use_color =-1 ;static char branch_colors[][COLOR_MAXLEN ] = {
@@ -443,21 +444,6 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortinfree (to_free); } -static void print_current_branch_name (void ) -{ -int flags; -const char *refname =resolve_ref_unsafe ("HEAD" ,0 ,NULL , &flags); -const char *shortname; -if (!refname) -die (_ ("could not resolve HEAD" )); -else if (!(flags &REF_ISSYMREF )) -return ; -else if (skip_prefix (refname,"refs/heads/" , &shortname)) -puts (shortname); -else -die (_ ("HEAD (%s) points outside of refs/heads/" ), refname); -} -static void reject_rebase_or_bisect_branch (const char *target) {struct worktree **worktrees =get_worktrees (0 );
@@ -668,10 +654,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) track = git_branch_track; - head =resolve_refdup ("HEAD" ,0 , &head_oid,NULL ); + head =resolve_refdup ("HEAD" ,0 , &head_oid, &head_flags);if (!head)die (_ ("Failed to resolve HEAD as a valid ref." )); -if (!strcmp (head,"HEAD" )) +if (!(head_flags &REF_ISSYMREF )) filter.detached =1 ;else if (!skip_prefix (head,"refs/heads/" , &head))die (_ ("HEAD not found below refs/heads!" ));
@@ -716,7 +702,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)die (_ ("branch name required" ));return delete_branches (argc, argv, delete >1 , filter.kind, quiet); }else if (show_current) { -print_current_branch_name (); +if (!filter.detached) +puts (head);return 0 ; }else if (list) {/* git branch --local also shows HEAD when it is detached */
Signature · 1 line
2.19.1
Re: [PATCH] branch: make --show-current use already resolved HEAD
Quoted 7 lines · Rafael Ascensão
print_current_branch_name()tries to resolve HEAD anddie()when it doesn't resolve it successfully. But the conditions being tested are always unreachable because early in branch:cmd_branch() the same logic is performed.Eliminate the duplicate and unreachable code, and update the current logic to the more reliable check for the detached head.
Nice.
Quoted 3 lines
I still think the mention about scripting should be removed from the original commit message, leaving it open to being taught other tricks like --verbose that aren't necessarily script-friendly.
I'd prefer to see scriptors avoid using "git branch", too.
Unlike end-user facing documentation where we promise "we do X and will continue to do so because of Y" to the readers, the log message is primarily for recording the original motivation of the change, so that we can later learn "we did X back then because we thought Y". When we want to revise X, we revisit if the reason Y is still valid.
So in that sense, the door to "break" the scriptability is still open.
Quoted 2 lines
But the main goal of this patch is to just bring some attention to this, as I mentioned it in a previous thread but it got lost.
This idea of yours seems to lead to a better implementation, and indeed "got lost" is a good way to describe what happened---I do not recall seeing it, for example. Thanks for bringing it back.
builtin/branch.c static const char * const builtin_branch_usage[] = { · line 41
Quoted 9 lines
builtin/branch.c +1
-0@@ -38,6 +38,7 @@ static const char * const builtin_branch_usage[] = {static const char *head;static struct object_id head_oid; +static int head_flags =0 ;
You've eliminated the "now unnecessary" helper and do everything inside cmd_branch(), so perhaps this can be made function local, no?
builtin/branch.c int cmd_branch(int argc, const char **argv, const char *prefix) · line 661
Quoted 11 lines
builtin/branch.c +2
-2@@ -668,10 +654,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) track = git_branch_track; - head =resolve_refdup ("HEAD" ,0 , &head_oid,NULL ); + head =resolve_refdup ("HEAD" ,0 , &head_oid, &head_flags);if (!head)die (_ ("Failed to resolve HEAD as a valid ref." )); -if (!strcmp (head,"HEAD" )) +if (!(head_flags &REF_ISSYMREF )) filter.detached =1 ;
Nice to see we can reuse the resolve_refdup() we already have.
builtin/branch.c int cmd_branch(int argc, const char **argv, const char *prefix) · line 706
Quoted 9 lines
builtin/branch.c +2
-1else if (!skip_prefix (head,"refs/heads/" , &head))die (_ ("HEAD not found below refs/heads!" ));@@ -716,7 +702,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)die (_ ("branch name required" ));return delete_branches (argc, argv, delete >1 , filter.kind, quiet); }else if (show_current) { -print_current_branch_name (); +if (!filter.detached) +puts (head);
Ah, I wondered why we do not have to skip-prefix, but it is already done for us when we validated that an attached HEAD points at a local branch. Good.
Quoted 3 lines
builtin/branch.c +0
-0return 0 ; }else if (list) {/* git branch --local also shows HEAD when it is detached */
Re: [PATCH] branch: make --show-current use already resolved HEAD
I did something that resulted in the mailing list not being cc'd. Apologies to Junio and Daniels for the double send. :(
Quoted 11 lines · Junio C Hamano
I'd prefer to see scriptors avoid using "git branch", too.
Unlike end-user facing documentation where we promise "we do X and will continue to do so because of Y" to the readers, the log message is primarily for recording the original motivation of the change, so that we can later learn "we did X back then because we thought Y". When we want to revise X, we revisit if the reason Y is still valid.
So in that sense, the door to "break" the scriptability is still open.
Over at #git, commit messages are sometimes consulted to disambiguate or clarify certain details. Often the documentation is correct but people dispute over interpretations.
If someone came asking if git branch is parsable, I would advise against and direct them to the plumbing or format alternative. But if someone came over with a link to this commit asking the same question, I suspect the answer would be: it's probably safe to parse the output of this specific option because the commit says so. Thanks for clarifying this is wrong.
Quoted 8 lines
Quoted 4 lines
static const char *head;
static structobject_idhead_oid;
+static inthead_flags= 0;You've eliminated the "now unnecessary" helper and do everything inside
cmd_branch(), so perhaps this can be made function local, no?
I was not sure if these 3 lines were global intentionally or if it was just an artifact from the past. Since it looks like the latter, I'll make them local.
--
Rafael Ascensão