How to find out which of 'master' or 'main' branches exists in a repository?

Viewed 1091

I'm working on a Bash script that does some Git repository maintenance. Some of repositories use 'master' as the main branch while others use 'main'. What Git command can I use to return the first branch of these which exists?

P.S. I want to check local branches of a repo, since there will always be a local 'master' or 'main' branch.

6 Answers

To find out which of the two local branches exists, you can use git branch with the -l/--list argument:

git branch -l master main  # outputs 'master' or 'main', provided only one exists

Git also marks the current branch with an asterisk, so you could use sed to strip it:

git branch -l master main | sed 's/^* //'

What Git command can I use to return the first branch of these which exists?

git branch -r returns a list of remotes.
grep origin/ma will match main and master.
If you need to be pickier, use grep -E 'origin/(main|master)'

Assigning the list returned to an array should work, but be wary of names with spaces &c because the array elements are delimited by whitespace and the whole thing can blow up.

b=( $(git branch -r | grep origin/ma ) ) # all matches
git checkout "${b[0]##*/}"               # first hit

If you want to be a little more careful,

for n in main master; do 
  b="$(git branch -r -l "origin/$n")"
  if [[ -n "$b" ]]; then
    git checkout "${b[0]##*/}"
    break # keep the first success
  fi 
done 
   

To find out what some other Git repository's HEAD is, use git ls-remote:

$ git ls-remote --symref origin HEAD
ref: refs/heads/master  HEAD
670b81a890388c60b7032a4f5b879f2ece8c4558    HEAD

This assumes both your Git, and the remote's Git, are not so old that they do not support --symref (it must be supported on both sides). Note that this command can be run outside any repository by using a URL directly, instead of a remote name like origin.

To find out what branch names exist in some other Git repository, either clone it and inspect the resulting remote-tracking names, or use git ls-remote. Note that you can specify just refs/heads to limit the output to branch names only, or omit it entirely to get everything (all branch and tag names and any other names they choose to expose).

Well, "default branch" has no meaning locally; it only makes sense if your repo is a remote to someone else, or in terms of a remote that someone else hosts. (Also, in terms of "what branch do I pull by default when I pull from this local branch, but if you knew that, you wouldn't be asking this.)

git fetch
git branch -r --list 'origin/HEAD' | grep '>'

I wanted a script that worked with multiple remotes (origin fork or canonical upstream) and repositories that used main or master. Based on Paul Hodges answer I ended up with:

#Update local/fork default to match remote. Can also delete current local branch using -D (to clean up a finished feature branch)
gum(){
  REMOTES=( $(git branch -r | grep -Eo "  (origin|upstream)/(master|main)") )
  RNAME=${REMOTES[-1]%%/*}    # Prefix of last one in the list (prefer fetching from upstream over origin)
  RBRANCH=${REMOTES[-1]##*/}  # Suffix of last matching remote
  LBRANCH="$(git rev-parse --abbrev-ref HEAD)"  # Name of current checked out branch

  if [[ "$LBRANCH" != "$RBRANCH" && "$1" != "-D" ]]; then
    echo "error current branch is '$LBRANCH' not $RBRANCH and -D not specified to delete current branch"
  elif [[ ! -z $(git status -s) ]]; then
    echo "error pending changes on local branch"
  else
    # Switch to local default, delete current if requested
    if [[ "$LBRANCH" != "$RBRANCH" && "$1" == "-D" ]]; then
      git checkout $RBRANCH
      git branch -D $LBRANCH
    fi
    # fetch all and prune, then update local default
    git fetch --all -p && git merge $RNAME/$RBRANCH && if [[ "$RNAME" != "origin" ]]; then
      git push ${REMOTES[0]%%/*} $RBRANCH # also update default branch on fork
    fi
  fi
}

I want to check local branches of a repo, since there will always be a local master or main branch

Taking that as a reliable description of your intended use-case environment, since in general use it's often enough not true,

What Git command can I use to return the first branch of these which exists?

A concise way of doing it is

if git cat-file -e refs/heads/master 2>&-; then echo master
elif git cat-file -e refs/heads/main 2>&-; then echo main
fi

but for anything even a little more elaborate you're likely to want for-each-ref,

{ git for-each-ref --format='%(refname)' refs/{heads,remotes/*}/master;
  git for-each-ref --format='%(refname)' refs/{heads,remotes/*}/main;
} | sed -E 's,(.*)/,\1 ,' | sort -usk1,1

which gives the answer for local branches and all tracked remotes without any foofaraw.

In my git repo this reports refs/remotes/origin master, for instance, and the Git project keeps both branch names in lockstep. But as someone else pointed out, some projects keep master around as a record of where it was when they switched away, and as you might have noticed I don't have a master or main branch. My make branch tracks origin/next, that's the one I run my deployments from, and there's a couple others for little projects.

To do just local branches do

{ git for-each-ref --format='%(refname:short)' refs/heads/master;
  git for-each-ref --format='%(refname:short)' refs/heads/main;
} | head -1

See the for-each-ref examples, it was built for this.

Related