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 repository

SYNOPSIS

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 prints

    fatal: 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 matching PATTERN from the contents of the variable VAR 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.

Tags:

Branch

Git