Delete local branches that are merged into HEAD and deleted on remote

TIL how to delete local branches that are merged into HEAD and deleted on remote.

I stumbled upon the issue that I had a lot of local branches, which I had already merged into HEAD and deleted on remote. I went to Google and found the answer on Stack Overflow. The answer says that the following line—which requires bash or Z shell—should do the trick:

git branch -r | awk '{print $1}' | egrep -v -f /dev/fd/0 <(git branch -vv | grep origin) | awk '{print $1}' | xargs git branch -d

Before I am actually going to use it, let's see what every command does.


git branch -r

With git branch you can list, create or delete branches. Option -r causes the remote-tracking branches to be listed. If you run this command in your git repository, the output will be similar to:

$ git branch -r
  origin/project-version-1.0.1
  origin/HEAD -> origin/master
  origin/dev-issue-123
  origin/dev-issue-124

|

Pipes (|) let you use the output of a command as the input of another command.


awk '{print $1}'

awk searches files for text containing a pattern. {print $1} prints the first item from each line of the output from git branch -r. Combined with the previous command, the output is:

$ git branch -r | awk '{print $1}'
origin/project-version-1.0.1
origin/HEAD -> origin/master
origin/dev-issue-123
origin/dev-issue-124

Notice that first the command git branch -r is executed, and it's output, instead of beining printed, is sent (piped) to the awk command, which in turn, prints what is has to.


egrep -v -f /dev/fd/0 <(git branch -vv | grep origin)

egrep searches for a pattern using extended regular expressions—esentially the same as running grep with the -E option. Option -v inverts the sense of matching, to select non-matching lines. Option -f obtains patterns from FILE, one per line.

git branch is already explained. Option -vv shows sha1 and commit subject line for each head, along with relationship to upstream branch—if any and prints the name of the upstream branch.

grep filters the output of git branch -vv for those containing origin.

Because git branch -vv | grep origin are basically two commands, we can not use pipe to pass the output to egrep. We have to place the two commands inside <() to be able to pass the output to egrep. It is important to note that before the output is passed to egrep, the output is redirected and gets a virtual file discriptor inside /dev/fd/0 assigned. This acts like a temporary file, which contains the output. This output is eventually passed to egrep.

Combining these command with the previous commands, the output would be:

$ git branch -r | awk '{print $1}' | egrep -v -f /dev/fd/0 <(git branch -vv | grep origin)
  dev-issue-90     da39a3ee [origin/dev-issue-90: gone] Fix #90 - Implemented new feature
  dev-issue-91     3255bfef [origin/dev-issue-91: gone] Closed #91 - Solved issue with login
  dev-issue-92     5e6b4b0d [origin/dev-issue-92: gone] Fix #93 - Implemented new feature
  dev-issue-93     95bd1890 [origin/dev-issue-93: gone] Fixed #93 - Refactored classes
  dev-issue-94     afd80709 [origin/dev-issue-94: gone] Solved #94 - Implemented new feature
  dev-issue-95     5e6b4b0c [origin/dev-issue-95: gone] Fix #95 - Do not use deprecated method
  dev-issue-96     12abcd34 [origin/dev-issue-96: gone] Fix #96 - Corrected typo in comments

awk '{print $1}'

Already explained awk. Notice that it now prints the first item from each line of the output of the previous commands combined:

$ git branch -r | awk '{print $1}' | egrep -v -f /dev/fd/0 <(git branch -vv | grep origin) | awk '{print $1}'
  dev-issue-90
  dev-issue-91
  dev-issue-92
  dev-issue-93
  dev-issue-94
  dev-issue-95
  dev-issue-96

xargs git branch -d

xargs typically contains the skeleton, or template, of another command. In this case the previous command—which lacks the argument to delete the listed branches. xargs adds the argument git branch -d from standard input to complete the command, then runs the resulting command.

git branch -d on its turn deletes a branch. Notice that this branch must be fully merged in its upstream branch, or in HEAD if no upstream was set with --track or --set-upstream.

If we now run the whole line, we have successfully deleted all local branches that are merged into HEAD and deleted on remote.


So that is what all of the commands do. It may or may not be the best way to delete local branches that are merged into HEAD and deleted on remote. Nevertheless, the main intention of this blogposts was to investigate the used commands. 🤓

How do you handle local branches vs remote branches? Let me know!


SHARE THIS ARTICLE