Reputation: 41418
Suppose I have a .git/config
as follows:
[remote "origin"]
url = [email protected]:some/repo.git
fetch = +refs/heads/main:refs/remotes/origin/main
fetch = +refs/heads/staging*:refs/remotes/origin/staging*
fetch = +refs/heads/jakub/*:refs/remotes/origin/jakub/*
I want a git command that will filter the git branch
output and only print the branches which match the refspecs from .git/config, i.e. the branches whose corresponding remote tracking branches are actively being updated when I run parameterless git fetch
.
Is there such a git command?
E.g. I want it to print:
main
staging-1
staging-2
jakub/foo
jakub/bar
I want it to filter out all non-matching branches like:
bob/foo
bob/bar
other
Conversely, I also may want to only get non-matching ones.
Update: The ultimate goal is to delete non-matching branches from local clone, and only keep the matching ones. I downloaded a reference mirror clone which has all branches (though they don't have a git tracking relationship), I updated my .git/config
to minimize the subset of branches that are fetched, and I want to get rid of the other branches.
Upvotes: 2
Views: 94
Reputation: 41418
.git/config
does not have negative refspecs (starting with ^
)The following command seems to do what I want:
git config --get-all remote.origin.fetch | sed 's|^+||' | cut -d':' -f1 | sed 's|refs/heads/||' | xargs git branch --list | cut -c3- | sort | uniq
Explanation:
git config --get-all remote.origin.fetch
reads the fetch specssed
strips the leading +
if encounteredcut
splits on :
and takes left hand side (src)sed
removes leading refs/heads/
git branch --list
on each line of input, to find matching branches for every refspeccut
removes first two columns from git branch
outputsort | uniq
is to remove duplicates (unlikely but possible)To get the inverse, i.e. only the non-matching branches, one could do:
the-command-above > /tmp/matching.txt
git branch | cut -c3- > /tmp/all.txt
comm -23 all.txt matching.txt > /tmp/not-matching.txt
We make sure that both files are sorted (git branch
is sorted by default), and then, use comm
which is the unix utility which compares two sorted files.
To delete the non-matching branches, a naive approach would be to try:
cat /tmp/not-matching.txt | xargs git branch -D
This is fast and mostly works, except if there are branches which differ by case only, because git can't create two lock files with "same" name but differing by case only, at the same time.
A workaround for this would be:
cat /tmp/not-matching.txt | xargs -n1 git branch -D
...to only delete one branch at a time, however this is very slow when number of branches is huge.
Something that has best of both: first delete one-by-one the conflicting-case branches; and then in parallel, the others.
cat /tmp/not-matching.txt | tr '[:upper:]' '[:lower:]' | uniq -d > /tmp/dupe-branches-with-conflicting-case.txt
cat /tmp/dupe-branches-with-conflicting-case.txt | xargs -I {} grep -i {} /tmp/not-matching.txt | xargs -n1 git branch -D
cat /tmp/not-matching.txt | xargs git branch -D
Explanation:
uniq -d
to detect which branch names are duplicated. Say we have FooBar
and FOOBAR
, they will get lowercased to foobar
and uniq -d
will find `foobar.foobar
), we grep -i
(case insenstive) the file with branches to delete, to find back FooBar
and FOOBAR
, and we delete them one by one (xargs -n1
) so that git doesn't crash.xargs
without -n1
) which is very fast. Note: This will result in error: branch FOOBAR not found
since we deleted them in previous step. You may want to add error handling for that case, or suppress errors in the output like > /dev/null 2>&1
.Upvotes: 4
Reputation: 22057
Is there such a git command?
Yes, there is.
git branch
, the "porcelain" branch listing command, contrary to what I've written in first answer, does allow this, even without switching to its plumbing counterpart git for-each-ref
. The useful option they share being conditional formats. Thanks to @LeGEC for pointing that out.
Try this format:
--format="%(if)%(upstream:short)%(then)%(refname:short)%(end)"
The full command in alias would be
# alias "tb" for Tracked Branches for instance
git config --global alias.tb '!git branch --format="%(if)%(upstream:short)%(then)%(refname:short)%(end)" | sort -u'
(%(if)%(upstream:short)
tests if the branch has a remote set.)
Edit: and since you @jakub suggested how to get the inverse...
# alias "ntb" for Non Tracked Branches
git config --global alias.ntb '!git branch --format="%(if)%(upstream:short)%(then)%(else)%(refname:short)%(end)" | sort -u'
Upvotes: 5