List and delete git branches without cloning
Someone with more git knowledge may be able to expand on this, but today I had a similar challenge in that I wanted to remove a branch in multiple repos but I didn't want to clone each one before removing the branch.
It turns out git will let you remove a branch in a different repo as long as you are in some repo. I'm not sure if there are any restrictions here like they have to point to the same GitHub server, etc.
But this worked for me:
cd to a repo dir you do not want to remove
git push git@server:path_to_repo.git :branch_you_want_to_delete
If you think about it, this is the same as:
git push origin :branch_you_want_delete
Except you are replacing the reference origin with the explicit path
You need git ls-remote
:
NAME
git-ls-remote
- List references in a remote repositorySYNOPSIS
git ls-remote [--heads] [--tags] [--refs] [--upload-pack=<exec>] [-q | --quiet] [--exit-code] [--get-url] [--symref] [<repository> [<refs>...]]
DESCRIPTION
Displays references available in a remote repository along with the associated commit IDs.
So it works like:
% git ls-remote origin
af51dfb080728117d898e1d0a10e3fe01ed67063 HEAD
6a60cc68a2953f1a62b0dca641eb29509b5b6e8c refs/heads/expdate-fix
af51dfb080728117d898e1d0a10e3fe01ed67063 refs/heads/master
4c42e43b4ccfd37074d115f6e9a694ddb8b70d55 refs/heads/redux
fd18a67bbc5cbf8aa6cda136afa4e5c20ed2d522 refs/heads/rest
7ad17cdf8b0dcd1a29a1795a363279fb3c76ac66 refs/tags/test.key
be0b2d6881902600fb3d6686c10d0a47f1e6751a refs/tags/test.pub
To get only branches (heads), you need to narrow the refspec down:
% git ls-remote origin 'refs/heads/*'
6a60cc68a2953f1a62b0dca641eb29509b5b6e8c refs/heads/expdate-fix
af51dfb080728117d898e1d0a10e3fe01ed67063 refs/heads/master
4c42e43b4ccfd37074d115f6e9a694ddb8b70d55 refs/heads/redux
fd18a67bbc5cbf8aa6cda136afa4e5c20ed2d522 refs/heads/rest
Now you could script around this output like
git ls-remote origin 'refs/heads/*' | while read sha ref; do
# test if $sha is merged
done
To delete a branch, you need to "push nothing" to it, like in
git push origin :refs/heads/feature-x
(notice an empty string to the left of ":" which defined what to push to what is on the right side).
So we get something like
#!/bin/sh
set -e -u
git ls-remote origin 'refs/heads/*' | while read sha ref; do
# test if $sha is merged
E=`git cat-file -t "$sha" 2>&1`
test $? -ne 0 -a "${E#*git cat-file: *}" = "could not get object info" && continue
git branch --merged "$sha" && printf ':%s\0' "$ref"
done | xargs -0 git push origin
Note that we're using printf
shell builtin to delimit the names of the refs we output with the ASCII NUL character and then pass -0
to xargs
to expect NUL-terminated input.
This way we work around funky ref names (containing spaces etc).
Some explanations:
If
git cat-file -t <object_sha1_name>
fails to locate the object with the indicated SHA1 name in the local repository, it exits with a non-zero exit code and printsfatal: git cat-file: could not get object info
to its stderr.
So to test whether the history a remote ref points at exists in the local repository we run
git cat-file -t
on the SHA1 name of the object it points at, grab the combined output of that command and then test whether it exited with a non-zero exit code ($? -ne 0
) and whether its error message indicates a missing object (the${VAR#PATTERN}
removes the prefix matchingPATTERN
from the contents of the variableVAR
and returns the resulting value).If the history a remote ref points at does not exist in the local repository, it cannot be merged to any of the local refs by definition, so if we detect such a ref, we skip its further testing with
git branch --merged
.