git list branches covers two related but different questions: which branches
exist in your repository, and which one you're standing on right now. The five-second answer
is git branch for local branches, git branch -a to see all branches —
local and remote-tracking together — and git branch --show-current to check your
current branch on any git from 2019 onward. This guide covers every command that can list
branches, the four ways to check current branch (and the one gotcha that makes two of them
disagree in detached HEAD), how to filter and sort a long list of branches, why git branch
-a keeps showing branches someone deleted on the server weeks ago, and how to list
branches on GitHub itself through the web UI or gh. If you just
cloned a remote git repository and are
trying to get your bearings, this is the page. It ends with the part most branch-listing guides
skip: once you know which branches exist, how to actually see what's different between them.
The Four Commands That List Branches
There are four git commands that answer "how to get branch names," each showing a slightly different slice of the same underlying refs. There's no one git command to list all branches everywhere at once — which of the four you reach for depends on whether you mean branches in your own clone, branches your clone last heard about from the server, or branches that exist on the server right this second.
# Local branches only
git branch
# Remote-tracking branches only
git branch -r
# Local + remote-tracking together — the full picture
git branch -a
# Not built for listing, but shows the current branch as a side effect
git status
Plain git branch is the simplest form of the branch list: one line per local
branch, an asterisk and green text marking whichever one HEAD points to. It covers
the common case — branches you created or checked out locally — with no server round-trip
required. It's the default, no-flags-needed view of a repository's own branches, and it's what
most people mean day to day when they just want to see what's here.
$ git branch
develop
* main
feature/csv-export git branch -r lists remote-tracking branches — local bookmarks like
origin/main that record where each branch pointed the last time you fetched, not
live data pulled from the server right now. git branch -a unions both lists, giving
you local and remote-tracking branches together in one shot:
$ git branch -a
develop
* main
feature/csv-export
remotes/origin/HEAD -> origin/main
remotes/origin/develop
remotes/origin/main git status isn't a branch-listing command at all, but its first line of output ("On
branch main") is often the fastest way to check current branch when you already have a terminal
open for another reason — no flags to remember, and it reports staged and unstaged changes plus
how far ahead or behind the upstream you are in the same breath.
How to Check Your Current Branch in Git
Checking your current branch comes up in a few different flavors depending on what you're going to do with the answer — print it for a human, or capture it in a script. All four commands below return the same name in the common case; they only disagree once you're in detached HEAD, covered in full further down.
# Requires git 2.22+ (June 2019) — the modern, direct answer
git branch --show-current
# Works on any git version — prints literally "HEAD" in detached HEAD
git rev-parse --abbrev-ref HEAD
# Fails with an error in detached HEAD instead of printing a fallback
git symbolic-ref --short HEAD
# Not dedicated to the job, but always available and human-readable
git status git branch --show-current is the most direct way to check your current branch on
anything from 2019 onward: it prints just the branch name, nothing else, which makes it the one
worth reaching for in a script or an alias. Before that flag existed,
git rev-parse --abbrev-ref HEAD was the standard trick for getting the current
branch name in a script, and it's still the safest choice when a script has to run on an older
git or an unknown environment, since --abbrev-ref has shipped since Git 1.6.3.
git symbolic-ref
--short HEAD does the same job through a different mechanism — it reads the symbolic ref
HEAD points to directly — and its exit code matters more than its output: it's the
version that fails loudly when there's no branch to report, which the detached-HEAD
section further down builds on.
For a one-off check while working, git status is the fastest way to confirm your
current branch without leaving whatever else you're doing — the obvious choice for anyone who
already has a terminal open. Its first line reads "On branch main" in the normal case, or "HEAD
detached at a1b2c3d" when you're not on any branch — a distinction the other commands like
--show-current handle silently, but git status spells out in words.
Any of the four will tell you your current branch; which one to reach for comes down to whether
a human or a script is going to read the output.
Verbose Output: What -v and -vv Actually Show
Plain git branch only prints names. Add -v and each line grows a
commit hash and subject line; add a second -v (-vv) and git also
reports the upstream branch and how far the two have diverged:
$ git branch -vv
develop 3f2a1b9 [origin/develop] add pagination to results
* main f7a8b9c [origin/main: ahead 2, behind 1] fix retry timeout
feature/csv 9c4d2e1 add CSV export button [origin/main: ahead 2, behind 1] reads relative to the upstream: your local
main has 2 commits the upstream doesn't have yet, and the upstream has 1 commit you
don't have locally. That number comes from your local remote-tracking ref, not a live call to
the server — it reflects the state as of your last fetch, so a branch that shows
"up to date" can still be behind the real origin/main if nobody has fetched
recently. A branch with no upstream configured at all just omits the bracket entirely, which is
itself useful signal: there's nothing to compare against.
One operational detail worth knowing: a long branch list pipes through your pager by default,
same as git log. There's no git branch --no-pager flag — the pager is
a git-wide setting, so the correct command is git --no-pager branch -vv. In
practice this rarely needs manual handling: git automatically disables the pager whenever
standard output isn't a terminal, which is exactly the case inside scripts, CI logs, and command
substitution like $(git branch --show-current).
Filtering and Sorting the Branch List
On a repository with dozens of branches, an unfiltered branch listing is close to useless.
--list accepts a glob pattern to narrow it down:
# Every branch under feature/
git branch --list "feature/*"
# Case-insensitive match
git branch --list "*csv*" -i --merged and --no-merged filter by ancestry instead of name — which
branches have every one of their commits already reachable from a given point, and which don't.
Left without an argument, both compare against HEAD by default, which is easy to
get wrong on a checkout that isn't main:
# Branches already fully merged into HEAD — usually safe to delete
git branch --merged
# Branches with commits HEAD doesn't have yet
git branch --no-merged
# Same check against a specific branch instead of HEAD
git branch --merged main --merged is the standard pre-cleanup check before a
local branch delete — a branch git lists as
merged has nothing left in it that isn't already on the target branch, so deleting it destroys
no reachable work.
--sort changes the order instead of the membership, and takes any of the same field
names for-each-ref uses — prefix with - to reverse:
# Most recently committed branch first
git branch --sort=-committerdate
# Alphabetical (the default)
git branch --sort=refname
For scripting, git for-each-ref is the tool actually built for parsing, not
git branch — no asterisk, no color codes, no current-branch marker to strip out,
just whatever format string you ask for:
git for-each-ref --format='%(refname:short)' refs/heads/
That one-liner is the script-safe way to list local branches whenever the output feeds into
another command instead of a human's terminal — git branch's formatting is meant to
be read, not parsed.
Remote Branches and the Stale-Ref Problem
A git remote branches list is not live data — git branch -r and
git branch -a both read local remote-tracking refs, snapshots written the last time
you ran git fetch (or a command that fetches implicitly, like git
pull). Delete a branch on the server and your local git branch -a keeps
happily listing it until something refreshes those refs — git never automatically notices a
branch disappeared upstream.
# Remove local remote-tracking refs for branches that no longer exist on the remote
git fetch --prune
# Equivalent, without also fetching new commits
git remote prune origin
# Make every future fetch prune automatically
git config --global fetch.prune true When the question is really "what branches exist on the remote server right this second," skip local refs entirely and ask the server directly:
git ls-remote --heads origin git ls-remote --heads origin is the one honest answer to that question — it doesn't
touch your local remote-tracking refs at all, so it can't be stale the way git branch
-a can. For a fuller picture that also flags branches likely safe to prune,
git remote show origin annotates each remote-tracking branch with whether it's
tracked, gone, or ahead/behind:
git remote show origin
That command is a good way to audit stale tracking branches before a cleanup pass — it lists
each remote-tracking branch and explicitly marks entries "(stale)" once the corresponding branch
is gone on the server, which is more informative than the plain output of git branch
-a alone. Working with a fork or a repository that's had its
remote URL changed makes this check even more
worth running before trusting remote-tracking refs that look stale.
Listing Branches on GitHub
Everything so far reads your local clone. Listing branches on GitHub itself is a different question — what does the remote repository think exists — and GitHub gives you a few ways to ask it.
The web UI has a dedicated branches page at github.com/<owner>/<repo>/branches,
split into "Active" and "Stale" tabs, each branch showing its ahead/behind count against the
default branch and a last-commit timestamp. It's the fastest way to eyeball what branches exist
when you don't want a terminal open.
From a terminal, the GitHub CLI queries the same data as JSON, which is what you want the moment listing GitHub's branches needs to feed into a script instead of a human's eyes:
# List every branch on the repo via GitHub's REST API
gh api repos/{owner}/{repo}/branches --jq '.[].name'
# Open the repository's branches page in your default browser
gh browse -- /branches
There's no gh branch command that mirrors git branch --show-current on
the GitHub side — GitHub's web UI has no "checked out" concept, only a default branch and
whatever branch a pull request or URL happens to point at. The closest equivalent is asking your
local clone, since that's the only place "current branch" is a meaningful state; gh repo
view --json defaultBranchRef answers the adjacent question of which branch GitHub treats
as default.
Getting the Branch Name Into a Script or CI Pipeline
Capturing the current branch for a script is command substitution around one of the commands
from earlier, almost always --show-current:
BRANCH=$(git branch --show-current)
echo "Building ${BRANCH}..."
Quote the expansion everywhere it's used — "${BRANCH}", not bare
$BRANCH — since a branch name is allowed to contain slashes and, technically,
anything a filesystem path allows, and an unquoted variable gets word-split by the shell. The
other failure mode worth handling explicitly: running this outside any git repository, where
--show-current exits non-zero and prints an error to stderr instead of a branch
name, so a script that doesn't check the exit code can silently continue with an empty
$BRANCH.
if ! BRANCH=$(git branch --show-current 2>/dev/null) || [ -z "${BRANCH}" ]; then
echo "Not on a branch — aborting" >&2
exit 1
fi
CI systems usually hand you the branch name directly, no git command required, and their
variable is often more reliable than a local git call inside a shallow CI checkout. GitHub Actions exposes GITHUB_REF_NAME; GitLab CI exposes
CI_COMMIT_REF_NAME; both are set before any checkout step even runs, which matters
on jobs that use a detached-HEAD checkout for the build itself — git branch
--show-current would print nothing in that exact state, covered in full next.
"Git Check Branch Version": Branches vs Tags vs Commits
"Git check branch version" is a search that covers at least three different real questions, and picking the wrong one wastes time. A branch itself has no version number — it's a movable pointer to a commit, nothing more — so "checking its version" means one of the following, depending on what you're actually trying to find out.
If the project tags releases, the version lives on the tag, not the branch:
# List every tag, which usually IS the version number in projects that tag releases
git tag --list
# The nearest tag reachable from the current branch, plus how many commits past it
git describe --tags git describe --tags is the most direct git check branch version command for a
tagged project — its output, something like v2.4.1-6-g9c4d2e1, reads as "6 commits
past the v2.4.1 tag, currently at commit 9c4d2e1," which pins down exactly where the branch sits
relative to the last labeled release.
If the project doesn't tag releases and "version" really means "which exact commit," the honest answer is a full commit hash, not a branch name — a branch name is a moving target that points somewhere different after every commit:
git log -1 --format=%H
And git branch -v, from earlier, already answers a lighter version of the same
question inline — each branch's line ends with the short hash and subject of its tip commit,
enough to eyeball which commit a branch is currently sitting on without a separate command.
The other common intent behind "git check branch version" has nothing to do with commits at
all — checking which version of git itself is installed, which is git --version,
unrelated to branches, tags, or commits entirely.
Detached HEAD: When These Commands Return Nothing
Every current-branch command from earlier assumes HEAD points to a branch.
Checking out a commit hash, a tag, or a remote branch directly — instead of a local branch —
puts you in detached HEAD, where that assumption breaks, and the four commands stop agreeing
with each other:
| Command | Behavior in detached HEAD |
|---|---|
git branch --show-current | Prints nothing — empty string, exit code 0, no error |
git rev-parse --abbrev-ref HEAD | Prints the literal string HEAD |
git symbolic-ref --short HEAD | Fails: fatal: ref HEAD is not a symbolic ref |
git status | Reports "HEAD detached at a1b2c3d" instead of "On branch main" |
That divergence is exactly why --show-current and --abbrev-ref HEAD
aren't interchangeable in a script: an empty string and the literal word "HEAD" are both truthy
as far as most shells are concerned, so a script built on --abbrev-ref without an
explicit check for the literal string "HEAD" can walk straight into detached-HEAD logic bugs
that a script built on --show-current's honest empty output would catch
immediately.
Getting back onto a branch from detached HEAD is one command, and it's non-destructive as long as you haven't made new commits you want to keep:
git checkout main
# or: git switch main
If you did make commits while detached and don't want to lose them, create a branch at the
current position before switching away — git checkout -b rescue-branch — since a
detached-HEAD commit with nothing pointing at it becomes unreachable and eventually
garbage-collected the moment you check out something else. The mechanics of getting back to a
known-good state from there overlap heavily with
resetting a branch to a specific commit.
After You List Branches: Comparing What's Actually In Them
Every git list branches command above answers "what exists." The next question is almost always what's actually different between two of them, and that's where the two-dot/three-dot distinction trips people up:
# Two-dot: every commit reachable from B but not A — good for "what did B add since A"
git log main..feature/csv-export
# Three-dot: commits reachable from either side but not both — symmetric difference
git log main...feature/csv-export # Two-dot diff: compares the tips of A and B directly
git diff main..feature/csv-export
# Three-dot diff: compares feature/csv-export against the merge base of the two — usually what you want before a PR
git diff main...feature/csv-export
Two-dot git diff main..feature/csv-export compares the current tips of both
branches, full stop — every difference between them, including changes that landed on
main after the two branches split. Three-dot git diff
main...feature/csv-export instead compares feature/csv-export against the
merge base — the commit where the two branches actually diverged — so it shows only what the
feature branch itself changed, ignoring anything main picked up separately since.
For "what will this pull request actually change," three-dot is almost always the right one;
two-dot quietly includes noise from main's own unrelated progress. git diff
--stat main...feature/csv-export compresses that same three-dot comparison down to a
per-file line-count summary, useful as a first pass before reading the full diff.
For a visual read instead of scrolling raw +/- lines in a terminal,
grab each side out of git and paste both into Diff Checker, a free Chrome extension. Pull one file's
content off each branch — git show main:src/api/retry.ts > /tmp/a.txt and
git show feature/csv-export:src/api/retry.ts > /tmp/b.txt, or just open both
checkouts side by side — then drop both versions into its two panes. Its Monaco-based editor
lines them up with syntax highlighting across 17 languages, a "Show Diff Only" mode that
collapses unchanged regions so a long file doesn't bury the actual change, and a stats bar
reporting lines added, removed, and modified plus a similarity percentage — all computed
locally, nothing uploaded. It doesn't read your repository or know what a branch is; it's a
two-pane text comparison tool, which is exactly enough once you already have both versions of a
file in hand from the commands above. The same side-by-side approach is covered in more depth in
the git diff between two files guide, and for
anyone doing this comparison inside an editor instead of a browser tab,
comparing two files in VS Code covers the
built-in diff viewer.
The same trick works mid-cherry-pick, too — pasting the original commit's diff next to a cherry-picked one confirms it landed exactly right, and after a stash pop the same two-pane read confirms nothing drifted that shouldn't have.
Quick Reference: Every Branch-Listing Command
This is the quick reference for every branch-listing and current-branch command from this
guide, side by side, so you can grab the right one without rereading a whole section. Need the
branch name for a single script line, or just want a quick check for a human to read? Go
straight to the --show-current row.
| Command | What it shows |
|---|---|
git branch | Local branches, current one marked with * |
git branch -r | Remote-tracking branches only |
git branch -a | Local + remote-tracking branches together |
git branch --show-current | Current branch name only (git 2.22+); empty in detached HEAD |
git rev-parse --abbrev-ref HEAD | Current branch name; prints "HEAD" in detached HEAD |
git symbolic-ref --short HEAD | Current branch name; fails with an error in detached HEAD |
git status | Human-readable current branch plus working tree state |
git branch -v | Branches with each one's tip commit hash and subject |
git branch -vv | Adds upstream tracking and ahead/behind counts |
git branch --list "pattern" | Branches matching a glob pattern |
git branch --merged [commit] | Branches fully merged into HEAD (or a given commit) |
git branch --no-merged [commit] | Branches not yet merged into HEAD (or a given commit) |
git branch --sort=<field> | Branches sorted by a chosen field |
git for-each-ref refs/heads/ | Script-safe branch listing, no decoration |
git ls-remote --heads origin | Live branch list straight from the remote server |
git remote show origin | Remote-tracking branches annotated with stale/tracked status |
git fetch --prune | Removes local refs for branches deleted on the remote |
gh api repos/{owner}/{repo}/branches | Branch list from GitHub's REST API |
git tag --list | Every tag — often the real "version" of a release |
git describe --tags | Nearest tag plus commits since it |
For the git-scm project's own reference on every flag these commands accept, see the official git-branch documentation and the git-for-each-ref documentation for the full list of format fields.
Frequently Asked Questions
How do I see all branches in git, including remote ones?
Run git branch -a to list local and remote-tracking branches together in one
output. That list is a snapshot from your last git fetch, not a live read of
the server, so a branch someone deleted on the remote can still appear until you refresh
with git fetch --prune. For a genuinely live list straight from the server,
git ls-remote --heads origin bypasses local refs entirely and queries the
remote directly.
How do I check which branch I'm on in git?
git branch --show-current prints just the branch name and works on any git
2.22 or newer, released June 2019. git status works on every git version and
shows the same information on its first line — "On branch main" — along with your working
tree state. git rev-parse --abbrev-ref HEAD is the pre-2.22 equivalent, with
one difference worth knowing: it prints the literal word "HEAD" instead of a branch name
whenever you're in detached HEAD, where --show-current instead prints nothing
at all.
How do I list branches on GitHub without cloning the repository?
Open the repository's /branches page in the GitHub web UI to see every branch
with its ahead/behind count against the default branch, or run gh api
repos/{owner}/{repo}/branches with the GitHub CLI to get the same
list as JSON for scripting. Neither requires a local clone; both read directly from GitHub's
servers.
Why does git branch -a still show a branch that was deleted on GitHub?
git branch -a reads local remote-tracking refs, which only update when you
fetch — git never automatically notices that a branch disappeared on the remote. Run
git fetch --prune (or git remote prune origin) to clear out
remote-tracking refs for branches that no longer exist upstream, or set git config
--global fetch.prune true so every future fetch does it automatically.
How do I get the current branch name in a script?
The standard idiom for getting the current branch name in a script is command substitution
around git branch --show-current, assigned to a variable. Quote every expansion of
it — "${BRANCH}", never bare $BRANCH — because branch
names are allowed to contain slashes and an unquoted variable gets word-split by the
shell. Check both failure modes as well: outside a git repository the command exits
non-zero, and in detached HEAD it exits 0 with empty output, so a script that tests
neither carries on silently with an empty branch name. Inside CI, prefer the platform's
own variable — GITHUB_REF_NAME on GitHub Actions,
CI_COMMIT_REF_NAME on GitLab CI — since both are set before any checkout step
and stay correct even on jobs that check out in detached HEAD.