How to create a new Git branch and switch to it (with naming, tracking, and protection)

· Category: Git

Short answer

Since Git 2.23 (Aug 2019), the canonical command is:

git switch -c feature/user-login

This creates feature/user-login from your current HEAD and checks it out in one step. The legacy git checkout -b feature/user-login still works and produces an identical result — git switch was introduced specifically to split branch switching off from file restoration, which git checkout had been overloaded with for years. See git-switch(1) and the Git 2.23 release notes.

If you want the branch to start from somewhere other than HEAD:

git switch -c hotfix/login-csrf origin/main      # branch off remote main
git switch -c experiment/cache  abc1234           # branch off a specific commit
git switch -c rerun-tests       v2.4.0            # branch off a tag

Steps (the safe sequence)

The most common mistake when creating a branch is starting it from a stale local main that's days behind the remote. Pull first.

# 1. Move to your base branch and pick up the latest commits
git switch main
git pull --ff-only origin main

# 2. Create and switch to your new branch in one step
git switch -c feature/user-login

# 3. (optional) push immediately so the branch is visible to teammates
#     and tracked against origin
git push -u origin feature/user-login

Notes on each step:

  • git pull --ff-only aborts if the remote has diverged from local. This is what you want — a regular git pull would silently merge, polluting your branch's history before you've made a single commit.
  • git push -u (--set-upstream) is what makes future git push and git pull on this branch work without arguments. Without -u, every push needs the full git push origin feature/user-login.

Branch naming that survives code review

Most teams converge on a <type>/<scope> pattern. Examples that scale:

Prefix Use for
feature/ New user-facing functionality
fix/ or bugfix/ Non-urgent bug fix from the backlog
hotfix/ Production fix that bypasses the release cadence
chore/ Tooling, deps, refactors with no behavior change
release/ Release-prep branches (mostly relevant in Git Flow)
revert/ A revert that needs review before merging

Two rules from experience:

  • Avoid uppercase, spaces, and .. — they're either invalid (per git check-ref-format) or break shell completion.
  • Don't put ticket IDs at the front (PROJ-1234-fix-login). Put them at the end or in the commit message — front-loaded IDs sort poorly in git branch listings and make tab-completion useless.
git check-ref-format --branch "feature/User Login"
# fatal: 'feature/User Login' is not a valid branch name

Tracking, upstream, and git push -u explained

A "tracking" relationship is a pointer in .git/config saying "this local branch corresponds to that remote branch." Without it:

git push
# fatal: The current branch feature/user-login has no upstream branch.
# To push the current branch and set the remote as upstream, use:
#     git push --set-upstream origin feature/user-login

Three ways to set upstream:

git push -u origin feature/user-login           # push and set upstream in one shot
git branch --set-upstream-to=origin/feature/user-login   # set without pushing
git config --global push.autoSetupRemote true   # set upstream automatically on first push

The push.autoSetupRemote setting was added in Git 2.37 (June 2022) and is the modern default — turn it on once and forget about -u.

Confirm what's tracked:

git branch -vv
# * feature/user-login 1a2b3c4 [origin/feature/user-login] WIP login form
#   main              5d6e7f8 [origin/main]                 ...

The [origin/...] annotation is the upstream pointer. Missing brackets means no upstream is set.

Branching from a remote without a local copy first

You don't need to git fetch then git switch. Modern Git auto-creates a local tracking branch when you switch to a remote branch name that has exactly one match:

git switch coworkers-feature
# Branch 'coworkers-feature' set up to track remote branch 'coworkers-feature' from 'origin'.
# Switched to a new branch 'coworkers-feature'

If multiple remotes have the same branch name, this is ambiguous and Git refuses; in that case use git switch -c local-name --track origin/coworkers-feature.

Avoiding accidental detached HEAD

These all check out something that is not a branch:

git switch --detach v2.4.0           # explicit, intentional
git checkout v2.4.0                  # legacy: same effect, no warning in older Git
git switch --detach abc1234          # branch off a commit hash

In detached HEAD mode any commits you make are not on a branch — they're reachable only from HEAD and will be garbage-collected once HEAD moves. The fix is to give them a branch name before moving on:

git switch -c experiment/cache       # save the work as a real branch

If you've already moved away and lost the commits, git reflog can recover them. See how to use git reflog to recover lost commits.

Common issues

fatal: A branch named 'feature/x' already exists. You created this branch before. Either switch to the existing one (git switch feature/x), force-replace it with the current HEAD (git switch -C feature/x — uppercase C; destructive), or pick a different name. Never use git branch -D to delete then recreate without checking that the old branch wasn't pushed somewhere.

error: pathspec 'main' did not match any file(s) known to git. You're in a fresh clone and don't have a local main yet. Run git fetch origin first, or use the remote name directly: git switch -c feature/x origin/main.

My new branch has commits I didn't make. You branched off the wrong base. git log --oneline main..HEAD shows the commits unique to your branch — if there are extras you don't recognize, you forgot the git pull --ff-only origin main step and branched off a stale local main. Easiest fix: git rebase --onto origin/main old-base feature/x (assumes old-base is where you actually started).

Auto-completion suggests a branch I deleted long ago. Local Git keeps a list of remote-tracking refs that don't get cleaned up automatically. git fetch --prune (or git config --global fetch.prune true once and forget) drops refs the remote no longer has.

GitHub branch protection blocks my push. Protected branches like main and release/* reject direct pushes from non-admins. The intended flow is to push to a separate branch and open a PR — see the GitHub branch protection docs. The error message names the rule that blocked you.

Tips

  • git config --global init.defaultBranch main sets main (not master) as the initial branch in new repos. Match what your hosting provider uses.
  • git switch - jumps back to the previous branch — analogous to cd - in shells. Useful when bouncing between feature work and main for a quick PR review.
  • For trunk-based teams that don't run long-lived branches, see how to implement trunk-based development.
  • For the Git Flow approach with develop, release/*, and hotfix/* branches, the same git switch -c mechanics apply — the difference is policy, not commands.