git scripting: How to list all git branches containing a commit
Asked Answered
B

2

6

I can list all branches containing a certain commit using git branch --list --contains just fine. But as explained in the related question on how to list all branches, this is a porcelain command that should not be used in scripts.

The latter question suggests to use the plumbing command git for-each-ref, but that does not support --contains.

What is the correct plumbing interface to list all branches that contain a certain commit.

Brockbrocken answered 6/8, 2015 at 10:43 Comment(1)
Note: with git 2.7 (Q4 2015), git for-each-ref --contains will be a reality. See my answer belowCopalite
W
2

One possible solution using the plumbing commands git-for-each-ref and git merge-base (the latter suggested by Joachim himself):

#!/bin/sh

# git-branchesthatcontain.sh
#
# List the local branches that contain a specific revision
#
# Usage: git branchthatcontain <rev>
#
# To make a Git alias called 'branchesthatcontain' out of this script,
# put the latter on your search path, and run
#
#   git config --global alias.branchesthatcontain \
#       '!sh git-branchesthatcontain.sh'

if [ $# -ne 1 ]; then
    printf "%s\n\n" "usage: git branchesthatcontain <rev>"
    exit 1
fi

rev=$1

git for-each-ref --format='%(refname:short)' refs/heads | \
    while read ref; do
        if git merge-base --is-ancestor "$rev" "$ref"; then
            printf "%s\n" "$ref"
        fi;
    done

exit $?

The script is available at jub0bs/git-aliases on GitHub.

(Edit: thanks to coredump for showing me how to get rid of that nasty eval.)

Wondrous answered 6/8, 2015 at 11:49 Comment(5)
looks similar to what I ended up doing, but I used git merge-base --is-ancestor instead of [ -n "$(git rev-list $rev^..$ref)" ].Brockbrocken
@JoachimBreitner Good call, using git merge-base --is-ancestor. Less hacky, more robust. I didn't even know about it (thanks!). Post your own answer, or let me know if I should just edit mine.Wondrous
Mine is not using fancy eval stuff (personal distrust), so do update yours if you think it is an improvement.Brockbrocken
Any reason to use the eval variable, and not just eval "$(git for-each-ref ... )"?Brockbrocken
@JoachimBreitner No good reasons, no. The eval variable is a holdover from the example in the git-for-each-ref man page.Wondrous
C
5

Update 18 months later (April 2017): with Git 2.13 (Q2 2017), git for-each-ref --no-contains <SHA1> is finally supported!

See commit 7505769, commit 783b829, commit ac3f5a3, commit 1e0c3b6, commit 6a33814, commit c485b24, commit eab98ee, commit bf74804 (24 Mar 2017), commit 7ac04f1, commit 682b29f, commit 4612edc, commit b643827 (23 Mar 2017), and commit 17d6c74, commit 8881d35, commit b084060, commit 0488792 (21 Mar 2017) by Ævar Arnfjörð Bjarmason (avar).
(Merged by Junio C Hamano -- gitster -- in commit d1d3d46, 11 Apr 2017)


Original answer

Starting git 2.7 (Q4 2015) you will get a more complete version of git for-each-ref, which now support the --contains

git for-each-ref --contains <SHA1>

With the doc:

--contains [<object>]:

Only list tags which contain the specified commit (HEAD if not specified).


See commit 4a71109, commit ee2bd06, commit f266c91, commit 9d306b5, commit 7c32834, commit 35257aa, commit 5afcb90, ..., commit b2172fd (07 Jul 2015), and commit af83baf (09 Jul 2015) by Karthik Nayak (KarthikNayak).
(Merged by Junio C Hamano -- gitster -- in commit 9958dd8, 05 Oct 2015)

Some features from "git tag -l" and "git branch -l" have been made available to "git for-each-ref" so that eventually the unified implementation can be shared across all three, in a follow-up series or two.

* kn/for-each-tag-branch:
  for-each-ref: add '--contains' option
  ref-filter: implement '--contains' option
  parse-options.h: add macros for '--contains' option
  parse-option: rename parse_opt_with_commit()
  for-each-ref: add '--merged' and '--no-merged' options
  ref-filter: implement '--merged' and '--no-merged' options
  ref-filter: add parse_opt_merge_filter()
  for-each-ref: add '--points-at' option
  ref-filter: implement '--points-at' option  
Copalite answered 7/10, 2015 at 9:12 Comment(0)
W
2

One possible solution using the plumbing commands git-for-each-ref and git merge-base (the latter suggested by Joachim himself):

#!/bin/sh

# git-branchesthatcontain.sh
#
# List the local branches that contain a specific revision
#
# Usage: git branchthatcontain <rev>
#
# To make a Git alias called 'branchesthatcontain' out of this script,
# put the latter on your search path, and run
#
#   git config --global alias.branchesthatcontain \
#       '!sh git-branchesthatcontain.sh'

if [ $# -ne 1 ]; then
    printf "%s\n\n" "usage: git branchesthatcontain <rev>"
    exit 1
fi

rev=$1

git for-each-ref --format='%(refname:short)' refs/heads | \
    while read ref; do
        if git merge-base --is-ancestor "$rev" "$ref"; then
            printf "%s\n" "$ref"
        fi;
    done

exit $?

The script is available at jub0bs/git-aliases on GitHub.

(Edit: thanks to coredump for showing me how to get rid of that nasty eval.)

Wondrous answered 6/8, 2015 at 11:49 Comment(5)
looks similar to what I ended up doing, but I used git merge-base --is-ancestor instead of [ -n "$(git rev-list $rev^..$ref)" ].Brockbrocken
@JoachimBreitner Good call, using git merge-base --is-ancestor. Less hacky, more robust. I didn't even know about it (thanks!). Post your own answer, or let me know if I should just edit mine.Wondrous
Mine is not using fancy eval stuff (personal distrust), so do update yours if you think it is an improvement.Brockbrocken
Any reason to use the eval variable, and not just eval "$(git for-each-ref ... )"?Brockbrocken
@JoachimBreitner No good reasons, no. The eval variable is a holdover from the example in the git-for-each-ref man page.Wondrous

© 2022 - 2024 — McMap. All rights reserved.