Getting the “current” Git branch, even if you’re in a detached HEAD state

(Note: This post is originally from 2017, and is copied to this new site for historical reasons. Opinions, network infrastructure, and other things may have changed since this was first written.)

Yeah, yeah, I know, remote branches aren’t technically my current branch because blah blah Git internals. Terminology purists can send me hate mail all they want. The point is, shell prompts commonly have a display for your current branch (like mine), but Google results at the time of writing all fail to work if you happen to be in a “detached HEAD state”, which you are when you’ve checked out a remote branch. So here’s a solution that works in that case.

#!/bin/sh

git rev-parse --git-dir &> /dev/null || return 0 # do nothing if not a git repo

commithash=$(git log --pretty=format:"%h" -n 1)
fullbranchname=$(git name-rev --name-only ${commithash})
branchtype=$(echo ${fullbranchname} | cut -d"/" -f1)

if [[ "${branchtype}" == "refs" ]]; then
    branchname=$(echo ${fullbranchname} | cut -d"/" -f3)
elif [[ "${branchtype}" == "remotes" ]]; then
    branchname=$(echo ${commithash} | cut -d"/" -f2-)
elif [[ "${branchtype}" == "${fullbranchname}" ]]; then
    # git did it for us
    branchname=${fullbranchname}
else
    branchname=${fullbranchname}
    # output a warning to stderr, convenience for maintenance
    echo "warning: unrecognized branch type ${branchtype}" >&2
fi

echo ${branchname}

So, what’s happening in this file?

  • First, the script makes sure the current working directory is within a Git repo in the first place. If not, the script exits immediately.
  • Then, the first useful thing it does is save the current commit hash and full name of the ref you’ve checked out as provided by git name-rev.
  • The “full branch name” is then cut using / as a separator, and the first “field” is compared.
  • If it’s refs, the last item is the branch name (refs/heads/master becomes master).
  • If the cut command returns the same thing it put in (no / characters), then that is assumed to be the branch name. I don’t know, the first time I used git name-rev, I got refs/heads/lavacano, and then I put it in my prompt, and then when I checked out a local branch later git name-rev
    started returning just lavacano. Maybe I did it weird the first time.
  • If the first field is remotes, then we’re on a remote branch, and the last two fields are used (remote/origin/master becomes origin/master).
  • If none of the above are true, the “branch name” variable is filled with the whole git name-rev result and a warning is printed to stderr.
  • Finally, the result is printed to stdout.

This script has only been tested with remote branches and local branches. I don’t know what it will do if you’ve detached your HEAD in some other fashion.

  • October 21, 2022