A Git branch names the tip of a line of history. Creating another branch normally adds a reference to an existing commit, not another copy of every tracked file. Two names can point to the same commit and later advance independently. This makes branches inexpensive to create, but it does not mean their names contain the history or that deleting every reference to a commit is harmless.
A reference, not a storage format
A local branch lives in Git’s reference namespace under refs/heads/. Its representation is an implementation detail: references can be loose, packed, or stored by another supported backend. Do not edit a presumed file in .git/refs/heads to manipulate a branch. Git’s reference commands handle the repository’s actual storage and update rules.
Commits refer to trees and parent commits. Following those parent links gives the history reachable from a branch tip. Two branches can therefore share most of their history while disagreeing at their tips. The branch name is not part of a commit’s identity, and creating or renaming a branch does not rewrite the referenced commit.
Creating a branch is different from checking it out. git branch topic creates the name at the current commit but leaves the current branch unchanged. git switch topic selects that branch and updates the working tree as needed. git switch -c topic combines creation and selection. Check the active branch before recording work, rather than assuming a creation command also switched it.
HEAD explains which name moves
In normal attached operation, HEAD symbolically refers to the current branch. Creating a commit updates that branch to the new commit; other branches sharing the old tip stay where they were. The new commit’s parents preserve its ancestry. A branch can also move through merge or other explicit reference-changing operations, not only through ordinary commits.
Checking out a commit directly can detach HEAD. New commits then advance HEAD without advancing a local branch. Those commits are real, but switching away can leave them without a durable branch name. Create a branch at the desired commit before leaving, or use a known object ID to name it afterward. Reflogs can help recovery, but retention and garbage collection make them an unsuitable permanent storage promise.
Remote-tracking references such as origin/main are local observations of a remote, normally updated by fetch. They are not a live connection or the same reference as local main. An upstream configuration describes a relationship used by commands; it does not automatically synchronize the two tips whenever either side commits.
When it breaks
Uncommitted changes belong to the working tree and index, not to a saved branch snapshot. A switch can carry compatible changes along or refuse when it would overwrite them. Inspect git status and preserve outstanding work deliberately. Creating a branch name alone does not save edits that were never committed.
Deleting a branch removes a reference, not an instruction to immediately erase every object it reached. Other references may keep those commits reachable. However, commits with no remaining retention path can eventually be pruned. Git’s branch -d checks whether the branch is merged into its configured upstream, or HEAD when no upstream is configured. This guard is not a test that the resulting application behaves correctly.
A merge is also more than adding a label. A fast-forward moves an existing reference; a merge of diverged histories normally creates a commit containing a resulting tree and multiple parents. Parent reachability preserves historical objects, not a guarantee that every contribution survives correctly in the merged files. Review content as well as the graph.
Check it quickly
Run this with Python 3 and Git 2.28 or newer. It creates only empty commits in a private temporary repository. Personal Git configuration and inherited Git routing variables are excluded, hooks and signing are disabled, and no remote is configured. The deletion attempt concerns only the disposable demonstration branch.
import os
import subprocess
import tempfile
env = {key: value for key, value in os.environ.items() if not key.startswith("GIT_")}
env.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_GLOBAL=os.devnull)
with tempfile.TemporaryDirectory(prefix="git-branch-demo-") as directory:
def git(*args, check=True):
return subprocess.run(
["git", "-c", "user.name=Branch Example",
"-c", "user.email=branch@example.invalid",
"-c", "commit.gpgsign=false", "-c", "core.hooksPath=/dev/null", *args],
cwd=directory, env=env, text=True, capture_output=True, check=check,
)
git("init", "-q", "-b", "main")
git("commit", "--allow-empty", "-qm", "base")
base = git("rev-parse", "HEAD").stdout.strip()
git("branch", "topic")
print("created-without-switch:",
git("symbolic-ref", "--short", "HEAD").stdout.strip() == "main")
print("same-tip:", git("rev-parse", "topic").stdout.strip() == base)
git("switch", "-q", "topic")
git("commit", "--allow-empty", "-qm", "topic work")
topic = git("rev-parse", "HEAD").stdout.strip()
print("only-topic-moved:", topic != base
and git("rev-parse", "main").stdout.strip() == base)
git("switch", "-q", "--detach", "HEAD")
git("commit", "--allow-empty", "-qm", "detached work")
detached = git("rev-parse", "HEAD").stdout.strip()
print("detached-without-moving-topic:",
git("symbolic-ref", "-q", "HEAD", check=False).returncode == 1
and git("rev-parse", "topic").stdout.strip() == topic)
git("branch", "rescue", detached)
git("switch", "-q", "main")
print("unmerged-delete-refused:",
git("branch", "-d", "topic", check=False).returncode != 0)
print("rescued-commit:", git("rev-parse", "rescue").stdout.strip() == detached)
All six checks should print True. Creating topic leaves main selected; committing on topic moves only that name. A detached commit leaves topic unchanged, and rescue then names the detached result. Deleting topic from unmerged main is refused, despite another reference retaining the history. These observations separate current-branch selection, commit reachability, and deletion policy without changing the surrounding project.
Sources
- Pro Git: branches in a nutshell, branch tips, HEAD, and shared ancestry, checked 2026-09-07.
- Git branch reference, branch creation, upstream relationships, and deletion checks, checked 2026-09-07.