Branches

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

This page covers Git 2.55.x, GitHub CLI 2.101.x, IntelliJ IDEA 2026.2, current stable VS Code with the GitHub Pull Requests and Issues extension, and github.com as of September 2026. Examples assume the repository’s default branch is named main.

Concept

A branch in Git is not a copy of the project’s files — it is a small, mutable pointer to a single commit. The pointer lives as a plain text file under .git/refs/heads/, and it contains nothing more than the SHA of the commit it currently points at. When a new commit is made on a branch, Git creates the commit object and then simply moves the branch pointer forward to that new commit’s SHA.

A second, special pointer called HEAD tracks which branch is currently checked out. Most of the time HEAD does not point directly at a commit — it points at a branch, which in turn points at a commit, so HEAD moves automatically whenever that branch gains a new commit. Switching branches simply repoints HEAD at a different branch and updates the working tree to match the commit that branch now points to.

Because a branch is only a reference and not a copy of the repository, creating one is nearly instantaneous and costs a few bytes regardless of how large the project’s history is — there is no file-copying involved. This is the property that makes branching in Git cheap enough to use constantly, unlike the heavier branching model of older centralized version-control systems.

The diagram below shows main and a feature branch that diverged from it: both started at the same commit, and each has since gained commits of its own. HEAD is currently on feature.

gitGraph commit id: "c1" commit id: "c2" branch feature checkout main commit id: "c3" checkout feature commit id: "c4" commit id: "c5"

Command line

List the local branches, with an asterisk and highlight marking the one HEAD is on:

git branch

Add -a to also list remote-tracking branches (Git’s local record of what each remote’s branches look like, such as remotes/origin/main):

git branch -a

Create a new branch and switch to it in one step — the modern, preferred way to start work on something new:

git switch -c feature/123-short-description

git switch -c <name> is equivalent to the older two-step git branch <name> followed by git checkout <name>; see Checkout, Switch and Restore for the full comparison between checkout and switch/restore.

Rename the branch that is currently checked out, or any other local branch:

git branch -m new-name
git branch -m old-name new-name

Delete a local branch once it is no longer needed. -d refuses to delete a branch whose commits are not yet merged anywhere Git can see; -D is a shortcut for --delete --force and deletes the branch regardless, which can permanently lose commits that exist only on it:

git branch -d feature/123-short-description
git branch -D feature/123-short-description

A local branch such as main is a pointer this repository controls directly. A remote-tracking branch such as origin/main is a read-only pointer that records where that branch was on the remote the last time Git talked to it — Git updates it only on fetch, pull or push, never on its own. A local branch can have one remote-tracking branch set as its upstream, which is what lets plain git pull and git push (with no arguments) know where to go:

git branch -u origin/main
git branch -vv

The full explanation of remotes, remote-tracking branches and upstreams belongs to Fetch, Pull and Push — this page only needs enough of the idea to talk about branches that exist both locally and on GitHub.

Branching workflows

GitHub flow

GitHub’s own recommended workflow, GitHub flow, is deliberately simple and is the default choice for most projects, especially anything deployed continuously:

  1. Create a short-lived branch off main, named for what it does (increase-test-timeout, feature/123-oauth-login).

  2. Commit to that branch as the work progresses, pushing regularly so collaborators and CI can see it.

  3. Open a pull request as soon as there is something worth discussing, even in draft form.

  4. Address review comments with further commits — they update the same pull request automatically.

  5. Merge the pull request into main once it is approved and its checks pass.

  6. Delete the branch — its history stays in the pull request and in `main’s log, so nothing is lost.

main is always assumed to be deployable, and every change reaches it exclusively through a reviewed, tested pull request. See Pull Requests for the review and merge side of this flow in detail.

A Git-flow-style workflow

Projects that cut discrete, versioned releases — rather than deploying continuously from main — often add a longer-lived integration branch and a small set of naming conventions on top of the same underlying branch mechanics. This is a lighter variant of the classic "Git flow" model, and it happens to match the convention this account’s own repositories and tooling already use:

  • main holds only released code — every commit on it corresponds to a tagged release.

  • develop is the integration branch: finished features land here first, and it is always at least as new as the latest release.

  • feature/<issue> branches fork from develop for new work, named after the tracking issue (for example feature/180), and merge back into develop through a pull request.

  • hotfix/<issue> branches fork from main for urgent fixes that cannot wait for the next release, and merge back into both main and develop once released.

  • release_x.y.z branches fork from develop when a release is being prepared (version bump, changelog, final stabilization) and merge into main, which is then tagged.

gitGraph commit id: "1.3.0" tag: "1.3.0" branch develop checkout develop commit id: "d1" branch feature/180 checkout feature/180 commit id: "f1" commit id: "f2" checkout develop merge feature/180 branch release_1.4.0 checkout release_1.4.0 commit id: "bump version" checkout main merge release_1.4.0 tag: "1.4.0" checkout develop merge release_1.4.0

A few naming tips that apply to either workflow: keep names short and lower-case, use hyphens rather than spaces, put the tracking issue number in the name when there is one (feature/123-oauth-login reads better than oauth-login alone once several branches pile up), and avoid /-prefixes that collide with an existing branch name (Git branches are stored as paths, so a branch named release and a branch named release/1.4.0 cannot coexist).

On GitHub.com

  1. From a repository’s Code tab, open the branch dropdown above the file list, type a name into Find or create a branch…​, and click Create branch: <name> — optionally switch the source branch first if the new branch should not fork from the currently selected one.

  2. From an issue, use the Create a branch button in the right-hand sidebar (under Development); GitHub offers a suggested branch name derived from the issue title and number and lets you pick the source branch and where the new branch should be checked out (locally, or directly on GitHub).

  3. Open the branch dropdown and choose View all branches to see every branch, its last commit, and whether it is ahead or behind the default branch.

  4. From that branches overview, click the trash icon next to a branch to delete it; the default branch cannot be deleted, and a branch with an open pull request must have that pull request merged or closed first.

  5. The repository’s default branch (the one pull requests target and clones check out by default) is set under Settings › General › Default branch.

    • Branch protection rules and rulesets, both under Settings › Branches / Settings › Rules, can require reviews, passing status checks, signed commits or a linear history before a branch may be updated, and can block force-pushes or deletion; a ruleset is the newer, more flexible mechanism (several can target the same branch at once, and they can also apply to tags across many repositories), while a classic branch protection rule is a single rule per branch pattern — see the reference pages below for the full configuration options.

In IntelliJ IDEA

  1. Click the branch-name widget (the VCS widget) in the main window header (or use Git › Branches) to open the branches popup.

  2. Choose New Branch to create a branch from the one currently checked out, or select another branch or a commit in the Git log first and choose New Branch from Selected / New Branch from its context menu; leave Checkout branch enabled to switch to it immediately.

  3. Select a branch in the popup and choose Rename to rename it locally — this only renames the local branch, not any remote-tracking counterpart.

  4. Check out a different branch first, then select the branch to remove and choose Delete. Unmerged branches are deleted without asking (like git branch -D), but a notification offers Restore in case it was a mistake.

  5. Select a branch and choose Compare with Current to see the commits that differ between it and the checked-out branch, or Show Diff with Working Tree to compare it against uncommitted changes.

The branches popup is also reachable with Ctrl+Shift+Backquote on Windows/Linux and Control+Shift+Backquote on macOS.

In VS Code

  1. Click the branch name in the status bar to open the branch picker, which lists local and remote branches and offers to create a new one.

  2. Run Git: Create Branch…​ from the Command Palette (Ctrl+Shift+P / Cmd+Shift+P) to create a branch from the current HEAD, or Git: Create Branch From…​ to pick a different starting point.

  3. Run Git: Delete Branch…​ to remove a local branch, and Git: Rename Branch…​ to rename the current one.

  4. The Source Control Graph view (part of the built-in Git support) shows branches, incoming/outgoing commits and lets you check out or compare a branch from its context menu.

Common mistakes / tips

  • Forgetting to switch back to main (or develop) before starting the next piece of work, so the new branch accidentally forks from the previous feature branch instead. Check the branch widget or run git branch before creating a new branch.

  • Using git branch -D out of habit instead of -d. -D bypasses the "fully merged" safety check and can discard commits permanently if the branch was never merged or pushed anywhere.

  • Assuming git branch -m renames the branch on GitHub too. Renaming is purely local until the renamed branch is pushed (and the old remote branch, if any, is deleted separately).

  • Letting merged branches accumulate. Delete a branch on GitHub (or with git push origin --delete <name>) once its pull request is merged; a long list of stale branches makes the Find or create a branch…​ dropdown much less useful.

  • Confusing a Git-flow-style release_x.y.z branch with a tag: the branch is where release stabilization commits happen, while the tag (covered in Tags and Releases) is the fixed pointer created once that branch is merged and released.