countermeasure
countermeasure

Reputation: 522

How do I list all remote branches which don't have a local branch?

I'd like to list all the remote Git branches which don't have a corresponding local branch.

For example, if the output of git branch --all is:

  remotes/origin/alpha
* beta
  main
  remotes/origin/beta
  remotes/origin/main

What I'd like to end up with is:

remotes/origin/alpha

What's the best way to do that?

Upvotes: 2

Views: 1107

Answers (1)

Gino Mempin
Gino Mempin

Reputation: 29636

As far as I know and at the time of posting this answer, there is no direct Git command or option for this.

But, inspired by the answers from grep using output from another command, you can use grep to get a list of local branch refname's that are missing from the list of remote branch refname's:

$ grep -v -F -f <(git for-each-ref --format='%(refname:lstrip=2)' refs/heads) <(git for-each-ref --format='%(refname:lstrip=3)' refs/remotes/origin) | grep -vF HEAD

Example:

$ git branch
  feature/C
  feature/D           # local-only branch, not pushed to remote
* master

$ git branch -r
  origin/HEAD -> origin/master
  origin/develop      # no corresponding branch on local
  origin/feature/A    # no corresponding branch on local
  origin/feature/B    # no corresponding branch on local
  origin/feature/C    # is already on local
  origin/master       # is already on local

$ grep -v -F -f <(git for-each-ref --format='%(refname:lstrip=2)' refs/heads) <(git for-each-ref --format='%(refname:lstrip=3)' refs/remotes/origin) | grep -vF HEAD
develop
feature/A
feature/B

The structure of the command is:

$ grep -v -F -f <(command 2) <(command 1)
  • command 2 lists all the local branches
    $ git for-each-ref --format='%(refname:lstrip=2)' refs/heads
    feature/C
    feature/D
    master
    
  • command 1 lists all fetched remote branches
    $ git for-each-ref --format='%(refname:lstrip=3)' refs/remotes/origin
    HEAD
    develop
    feature/A
    feature/B
    feature/C
    master
    

The -f <(command 2) tells grep to search for the patterns from the output of command 2 (the local branch patterns) from the output of command 1 (the remote branch patterns). That should have returned remote branches that already have corresponding local branches, then the -v option inverts the result. The piped git -vF HEAD is just for excluding HEAD.

You can also use git branch but I prefer using git for-each-ref for scripting and automation. See related post on the difference between Git's "plumbing" vs "porcelain" commands.

This may not always work because:

  • This only compares branches by name, specifically refname's, and assumes that you always track a remote branch with a similarly named local branch, which may not always be the case.
  • The command uses Bash 5.x and it uses process substition (<(...)) to allow the command's output to be referred to as a filename to be used with grep's -f option. This makes it a bit non-portable and is quite problematic to be put into a git alias.
  • The target of for-each-ref for remote branches is refs/heads/remotes/origin, where origin is the commonly assumed remote name. Change it as necessary if origin is not your remote name.

For convenience, I turned it into a regular shell alias with a git fetch call:

$ alias git-new-branches="git fetch; grep -v -F -f <(git for-each-ref --format='%(refname:lstrip=2)' refs/heads) <(git for-each-ref --format='%(refname:lstrip=3)' refs/remotes/origin) | grep -vF HEAD"

$ git-new-branches 
develop
feature/A
feature/B

You might prefer turning that into a shell script instead, and embed that into a Git alias.

Upvotes: 2

Related Questions