Branches
A branch is a movable named pointer to a commit. It lets you develop a feature in isolation from main and merge the result back when it is ready, without your half-finished work ever touching the shared line of history. Because the pointer is the entire branch, creating one is nearly instant.
If branching feels heavyweight to you, that intuition comes from older systems where a branch was a directory copy on a server. In Git it is a 41-byte file. The cost of having ten branches open is ten small files; the cost of merging them later is the only thing worth thinking about.
What a Branch Actually Is
A branch is a file under .git/refs/heads/ containing one commit hash. That is the whole data structure. HEAD is a second pointer that names which branch you are currently on — usually it holds ref: refs/heads/main rather than a hash directly. When you commit, Git writes the new commit and then advances the current branch's hash to point at it; HEAD follows along because it points at the branch, not the commit.
This is why "switching branches" changes the files in your working tree but copies nothing: Git reads the snapshot the target branch points at and lays it down. The branch did not store those files — the commit it points to did.
Creating and Switching
Use git switch -c <name> to create a branch and move onto it in one step, and bare git switch <name> to move to an existing one. The older git checkout -b <name> does the same thing, but checkout is overloaded — it also discards file changes — and that overload is a frequent source of accidental data loss. New code should prefer switch for branches and restore for files, which split those two unrelated jobs apart.
A new branch starts at whatever commit HEAD is on when you create it. Branch from a stale local main and your feature starts behind the remote before you have written a line.
Tracking and Remote Branches
A local branch and its remote-tracking counterpart are different refs. main is your local branch; origin/main is a read-only local snapshot of where the remote's main was at your last fetch. Setting upstream tracking with git push -u origin <branch> links the two so a bare git push and git pull know where to go. Deleting a local branch does nothing to the remote — that takes git push origin --delete <branch>.
Listing and Deleting
git branch lists local branches with the current one marked. git branch -d <name> deletes a branch but refuses if its commits are not merged anywhere, which is the safety you want; git branch -D forces the delete regardless. The lowercase form is the one to reach for by default — the uppercase form throws away commits that only git reflog can recover.
Branching Strategies
Short-lived feature branches that live hours to a few days are the low-conflict path: they diverge from main for a small window, so the merge back is small. Trunk-based development pushes this further, integrating to main behind feature flags many times a day. The opposite — a branch that lives for weeks while main moves on — turns the eventual merge into a conflict-heavy ordeal that frequent integration would have avoided entirely.
- Letting a feature branch live for weeks while
mainmoves on, so the eventual merge is a conflict-heavy ordeal that a daily merge or rebase would have spread out into trivial ones. - Deleting a branch with
git branch -Dbefore its work is merged anywhere, discarding commits that onlygit reflogcan recover. - Confusing a local branch with its remote-tracking
origin/<branch>and pushing to the wrong place, or assuming a local delete also removed the branch on the remote. - Working directly on
mainand pushing, bypassing review and turning any rollback into a shared-history rewrite. - Creating a branch from a stale local
maininstead of fetching first, so the branch starts behind the remote and merges dirty.
- Use
git switch -c <name>to create feature branches and keep them short-lived, on the order of hours to a few days. - Fetch and update
mainbefore branching so the new branch starts from current state. - Use
git branch -d(lowercase) for deletes so Git refuses to drop unmerged work. - Set upstream tracking with
git push -u origin <branch>so plaingit pushandgit pullresolve the remote automatically. - Integrate
maininto a long-running branch regularly rather than saving one giant merge for the end.
branches/ pathPerforce uses streams or branch specsFossil branches are tags on the graph, first-class but never deletedKnowledge Check
What is a Git branch, physically?
- A small ref file under
.git/refs/heads/holding one commit hash - A full copy of the project's files at the branch point
- A server-side directory created by copying the tree under a
branches/path - A stored diff against
mainthat Git recomputes on every new commit
What is the difference between main and origin/main?
mainis your local branch;origin/mainis a local snapshot of the remote as of your last fetch- They are two interchangeable aliases for one underlying ref and always resolve to the exact same commit hash
origin/mainis the editable branch you commit on directly, whilemainis a read-only mirror that updates on fetchorigin/mainlives only on the server and is queried fresh over the network each time
Why is git branch -d safer than git branch -D?
-drefuses a branch whose commits are unmerged, while-Dforces the delete and can discard work-ddeletes only the remote copy onoriginover the network, leaving the local branch and its reflog fully intact-Derases the underlying commit objects while-donly hides the branch from listings- There is no practical difference between them; the two flags are interchangeable aliases for the same delete operation
Why does a long-lived feature branch cost more than several short ones?
- It diverges from
mainfor a long window, so the eventual merge accumulates many overlapping changes to resolve at once - Long-lived branches consume proportionally more disk over time because Git keeps a separate full copy of the working files for each one
- Git enforces a hard ceiling on how many commits a single branch may accumulate, and a long-lived branch eventually hits that limit
- Remote-tracking refs expire automatically after a week of inactivity, breaking any branch that has lived longer than that window
You got correct