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
Three layers behind "git branch" Local branches refs/heads/ Remote-tracking refs refs/remotes/origin/ origin (server) lives on GitHub, not here * main develop feature/csv-export origin/main origin/develop origin/HEAD main develop hotfix/urgent fetch git branch, git status git branch -r git ls-remote --heads git branch -a unions columns 1 + 2 — a snapshot, not a live read of the server Remote-tracking refs update only on fetch — the server can move without local git noticing.
Local branches (refs/heads/) live only in your clone. Remote-tracking refs (refs/remotes/origin/) are a local snapshot updated by fetch. Branches on the server are a separate, third thing that only git ls-remote reads directly.

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
Choosing a check-current-branch command Who reads the output — human or script? Human, at the terminal git status always works most detail: branch + tree state Script, git 2.22+ (2019+) git branch --show-current empty string, exit 0, in detached HEAD Script, older git or need certainty git rev-parse --abbrev-ref HEAD prints literal word HEAD git symbolic-ref --short HEAD fails: not a symbolic ref Any git version: git status always works. Need 2.22+: --show-current is simplest for scripts.
git status always works for a human glance. For scripts, git branch --show-current is the modern default; on older git, git rev-parse --abbrev-ref HEAD returns a value (the literal word HEAD when detached) while git symbolic-ref --short HEAD fails outright instead.

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
ahead 2, behind 1 — measured against your last fetch [origin/main: ahead 2, behind 1] diverged here behind 1 — origin/main tip you don't have this commit yet ahead 2 — commits only you have main (HEAD) Both counts come from your last fetch, not a live read of the server. No upstream configured? The bracket is omitted entirely.
ahead 2, behind 1 is measured against your last fetch of origin/main, not a live comparison — main has 2 commits origin/main doesn't have, and origin/main has 1 commit main doesn't have yet.

[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
Lifecycle of a remote-tracking ref 1. First fetch origin/feature-x ref created 2. Later fetch ref refreshed stays in sync 3. Deleted on server ref NOT removed still listed (stale) 4. fetch --prune stale ref removed or remote prune origin fetch.prune=true (git config) automates step 4 on every future fetch. Until pruned, git branch -a keeps listing a branch that's already gone remotely.
A remote-tracking ref is created on first fetch, refreshed on every later fetch, then goes stale — still listed by git branch -a — the moment the branch is deleted on the server, until git fetch --prune or git remote prune origin removes it.

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"
Same three commands, normal branch vs detached HEAD On a normal branch (main) git branch --show-current main git rev-parse --abbrev-ref HEAD main git symbolic-ref --short HEAD main In detached HEAD git branch --show-current (empty string, exit 0) git rev-parse --abbrev-ref HEAD HEAD git symbolic-ref --short HEAD fatal: HEAD not a symbolic ref Same three commands, same branch state otherwise — only detached HEAD makes them disagree.
On a normal branch checkout, all three commands agree. In detached HEAD they diverge: --show-current returns an empty string, rev-parse --abbrev-ref HEAD prints the literal word HEAD, and symbolic-ref --short HEAD fails outright.

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 vs three-dot: what actually gets compared merge base main's own commit main (A) feature's own commits feature/csv-export (B) git diff main..feature/csv-export Tips compared directly — includes main's own commit too (noise from main's side) git diff main...feature/csv-export Compares merge base to feature's tip only — isolates just feature's own changes Three-dot is almost always right for reviewing what a pull request will actually change.
Two-dot git diff main..feature/csv-export compares the tips of both branches directly, including main's own unique commit. Three-dot git diff main...feature/csv-export compares the merge base against feature/csv-export's tip only, isolating just that branch's changes.

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.