Guides

How to create a new branch in Git: git switch -c, git checkout -b and git push -u

How to create a new branch in Git with git switch -c or checkout -b, from a tag or remote branch, push it with -u, delete it, and install Git on Ubuntu.

Published ~11 min read
On this page

Short answer: git switch -c new-branch creates a branch at the commit you are on and switches to it. git checkout -b new-branch does exactly the same and works on every Git version, and git branch new-branch creates it without switching. To publish it, run git push -u origin new-branch, which also sets it to track the remote branch.

any system · create, commit, publish
git switch -c feature/login          # new branch from where you are, and switch to it
# ... edit, git add, git commit ...
git push -u origin feature/login     # publish it and track it

Every command and every output below comes from Git 2.39.5, the version Debian 12 ships, run on 21 October 2026 in a throwaway repository with a bare repository beside it as the remote. That is why push lines read To ../app.git; yours show your Git host’s address.

Cheat sheet: creating and managing branches

TaskCommand
Create a branch and switch to itgit switch -c name
The same, on any Git versiongit checkout -b name
Create without switchinggit branch name
Create from another branchgit switch -c name main
Create from a taggit switch -c hotfix/1.0.1 v1.0
Create from a commitgit switch -c rescue 478ed10
Check out a branch that exists only on the remotegit switch feature/login
Push a new branch and set its upstreamgit push -u origin name
Make plain git push do that (Git 2.37+)git config --global push.autoSetupRemote true
List local, all, or with upstreamsgit branch, git branch -a, git branch -vv
Switch to an existing branchgit switch name
Go back to the previous branchgit switch -
Delete a merged branchgit branch -d name
Delete a branch that is not mergedgit branch -D name
Delete it on the remotegit push origin --delete name
Start a branch with no historygit switch --orphan name
Check a name before using itgit check-ref-format --branch "name"

git switch -c, git checkout -b and git branch

A branch in Git is a movable pointer to a commit, nothing more. Creating one writes a 41-byte file (the commit hash and a newline) and copies nothing, which is why it is instant in any repository. All three commands create that pointer at the commit you have checked out (HEAD); they differ only in whether you end up on the new branch:

any system
git switch -c feature/login
git checkout -b feature/signup main
git branch new-idea
git branch -v
Output
Switched to a new branch 'feature/login'
Switched to a new branch 'feature/signup'
  feature/login  478ed10 Initial commit
* feature/signup 478ed10 Initial commit
  main           478ed10 Initial commit
  new-idea       478ed10 Initial commit

The * marks the branch you are on. Note that git branch new-idea left you on feature/signup: the classic mistake is to create a branch that way, keep working, and commit to the old branch. git switch arrived in Git 2.23 (August 2019) to take branch switching out of git checkout, which also restores files; the man page of Git 2.39 still labels it experimental, but Git’s own hints suggest it. Both spellings stay valid, and scripts that must run on old Git should use git checkout -b.

What happens to uncommitted changes

They come along. A new branch starts at the same commit, so nothing in the working tree has to change, and your edits are simply still there on the new branch:

any system · README.md edited but not committed
git switch -c wip
git status -s
Output
Switched to a new branch 'wip'
 M README.md

That makes git switch -c the fix for “I started this on main by mistake”: create the branch, then commit there. Switching to an existing branch is different, because Git would have to overwrite files that differ between the two:

Output · git switch main with an edited file main does not have
error: Your local changes to the following files would be overwritten by checkout:
	login.txt
Please commit your changes or stash them before you switch branches.
Aborting

Commit the change, or put it aside with git stash and bring it back with git stash pop on the branch where it belongs.

Create a branch from another branch, a tag or a commit

Give a start point after the name, and the branch starts there instead of at HEAD. The start point can be any branch, tag or commit hash:

any system
git switch -c feature/signup main       # from main, wherever you are
git switch -c hotfix/1.0.1 v1.0         # from a release tag
git switch -c rescue 478ed10            # from a commit, e.g. one found in git log or git reflog

A tag cannot be switched to directly, because git switch only moves between branches:

Output · git switch v1.0
fatal: a branch is expected, got tag 'v1.0'
hint: If you want to detach HEAD at the commit, try again with the --detach option.

git checkout v1.0 does go there, in “detached HEAD” state: you are on the commit, not on a branch, and commits you make there belong to no branch. Git says so and shows the way out, which is the same command as above:

Output · git checkout v1.0 (shortened)
Note: switching to 'v1.0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Check out a branch that exists only on the remote

A colleague pushed feature/login, and you want it locally. Fetch, look, and switch to it by its plain name. When exactly one remote has a branch of that name, Git creates the local branch and sets it to track the remote one:

any system
git fetch
git branch -a
git switch feature/login
Output
* main
  remotes/origin/HEAD -> origin/main
  remotes/origin/feature/login
  remotes/origin/main
branch 'feature/login' set up to track 'origin/feature/login'.
Switched to a new branch 'feature/login'

To give the local branch a different name, name the remote branch as the start point: git switch -c review origin/feature/login (or git checkout -b review origin/feature/login) prints the same tracking line. If two remotes have a branch of that name, Git cannot guess; say which one with git switch --track origin/feature/login. Add --no-track when you want a local branch that starts there but does not follow it.

Push a new branch and set the upstream

A new branch exists only in your clone until you push it, and the first plain git push refuses because the branch has nowhere to go yet:

Output · git push on a new branch
fatal: The current branch feature/login has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin feature/login

To have this happen automatically for branches without a tracking
upstream, see 'push.autoSetupRemote' in 'git help config'.
any system
git push -u origin feature/login
Output
To ../app.git
 * [new branch]      feature/login -> feature/login
branch 'feature/login' set up to track 'origin/feature/login'.

-u is short for --set-upstream. From now on, plain git push and git pull on this branch know where to go, and git status tells you how far ahead or behind it is. To skip the -u step for every new branch, set this once (Git 2.37 or newer); the next plain git push on a new branch then creates it on the remote and prints the same tracking line:

any system
git config --global push.autoSetupRemote true

List branches and switch between them

any system
git branch                     # local branches, * = current
git branch -a                  # local and remote-tracking
git branch -vv                 # with last commit and upstream
git branch --show-current      # just the name, for scripts and prompts
git branch --merged            # merged into the current branch: safe to delete
git branch --no-merged         # work that is not merged yet
git branch --sort=-committerdate   # most recently used first
Output of git branch -vv
* feature/login  cfe314a [origin/feature/login] Add login form
  feature/signup 478ed10 Initial commit
  main           478ed10 [origin/main] Initial commit
  new-idea       478ed10 Initial commit

Branches without brackets have no upstream yet: they were never pushed, or were pushed without -u. Switch with git switch name, and jump back to the branch you came from with git switch -, like cd - in the shell.

Delete a branch, locally and on the remote

git branch -d deletes a branch only if its commits are merged; otherwise it stops and tells you how to insist:

Output · git branch -d experiment
error: The branch 'experiment' is not fully merged.
If you are sure you want to delete it, run 'git branch -D experiment'.
Output · git branch -D experiment
Deleted branch experiment (was 28c2a84).

Keep that hash: if the delete was a mistake, git branch experiment 28c2a84 brings the branch back. You cannot delete the branch you are on (error: Cannot delete branch 'feature/login' checked out at '...'), so switch away first. Deleting locally does not touch the remote; that is a push:

any system
git push origin --delete feature/login
Output
To ../app.git
 - [deleted]         feature/login

Other clones keep their copy of origin/feature/login until they fetch with --prune; their local branch then shows the upstream as [origin/feature/login: gone]. Renaming a branch is a related job with a few traps of its own, covered in renaming a Git branch, locally and on the remote.

Branch naming rules

Git accepts slashes, so feature/login and fix/123-timeout are fine and many tools group branches by the part before the slash. What it refuses:

  • spaces and the characters ~ ^ : ? * [ \, and control characters;
  • two dots in a row (fix..typo) and the sequence @{;
  • a name that starts with -, a part that starts with ., and a name that ends in /, . or .lock;
  • HEAD as a branch name.

git check-ref-format --branch tests a name without creating anything; it exits non-zero for an invalid one. Run against a set of names on the test box, it accepted feature/login-form, Feature/Login and bug#12, and rejected my branch, fix..typo, -fix, release.lock, feat/, v1.0^, fix:typo, a/.b and HEAD. Creating one of those fails with fatal: 'my branch' is not a valid branch name.

One rule is not about characters. Branch names are stored like paths, so dev and dev/new-api cannot both exist:

Output · git switch -c dev/new-api while a branch dev exists
fatal: cannot lock ref 'refs/heads/dev/new-api': 'refs/heads/dev' exists; cannot create 'refs/heads/dev/new-api'

Rename or delete dev, or pick a prefix that is not also a branch. Case is another trap: on Linux Feature/Login and feature/login are two branches, while the case-insensitive file systems of macOS and Windows can mix them up, so a team on mixed systems should stick to lowercase.

A branch with no history

git switch --orphan gh-pages creates a branch with no commits and an empty working tree (Git prints No commits yet in git status), for content that shares nothing with the main code, such as a generated website or documentation.

Install Git on Ubuntu or Debian first

Git is not part of a minimal server image. One package fixes that on both distributions:

Debian · Ubuntu
sudo apt update
sudo apt install -y git
git --version
ReleaseGit from aptSource
Debian 12 (bookworm)2.39.5apt-cache policy git, 21 October 2026
Debian 13 (trixie)2.47.xDebian's package archive
Ubuntu 22.04 LTS2.34.1Ubuntu's package archive
Ubuntu 24.04 LTS2.43.0Ubuntu's package archive
Any other releaseapt policy gitRun it on the machine

Every command in this guide works on all of them; push.autoSetupRemote needs 2.37, so on Ubuntu 22.04 use -u. For a newer Git on Ubuntu, the Ubuntu Git Maintainers team publishes one in a PPA:

Ubuntu · optional, newer Git
sudo add-apt-repository ppa:git-core/ppa
sudo apt update
sudo apt install -y git

Then tell Git who you are. Every commit is stamped with this name and email; without them, git commit stops with Author identity unknown or commits under a name guessed from your user and host name. Set the default name for new repositories too, so git init starts on main:

any system
git config --global user.name "YOUR NAME"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
git config --global --list

--global writes to ~/.gitconfig for your user. Inside one repository, the same commands without --global override it there, for example a work email in a work repository. If the shell says git: command not found after the install, or Windows says 'git' is not recognized, see git: command not found. To clone and push over SSH instead of HTTPS, you need a key on the Git host: generating an SSH key covers it.

Errors when creating branches

MessageCauseFix
fatal: a branch named 'review' already existsThe name is takengit switch review to use it, or pick another name
fatal: invalid reference: nosuchgit switch to a branch that exists neither locally nor on a remotegit fetch, then check git branch -a for the exact name
fatal: not a valid object name: 'main'git branch in a repository with no commits yetCommit first, or use git switch -c, which works there
fatal: a branch is expected, got tag 'v1.0'git switch to a taggit switch -c name v1.0
fatal: The current branch X has no upstream branch.First push of a new branchgit push -u origin X
error: src refspec X does not match anyPushing a branch name that does not exist locally (typo), or a repository with no commitsgit branch to check the name; commit first
fatal: 'orgin' does not appear to be a git repositoryTypo in the remote namegit remote -v lists the real names
fatal: cannot lock ref 'refs/heads/dev/x': 'refs/heads/dev' existsA branch dev blocks the prefix dev/Rename or delete dev, or use another prefix
Your local changes to the following files would be overwritten by checkoutSwitching to an existing branch with uncommitted editsCommit or git stash first

Branches on a server

On a server that deploys from Git, the checkout is just another clone: clone once, then git switch release (or whichever branch you deploy) and git pull on each release, as the user that owns the files rather than as root. Keep the deploy branch long-lived and do the work on short feature branches that you merge into it. If you host the remote yourself, a bare repository on a Linux VPS reached over SSH is all a small team needs: git init --bare app.git on the server and git remote add origin user@server:app.git on each clone, and every branch command above works against it unchanged.

Frequently asked

How do I create a new branch in Git?

Run git switch -c branch-name, which creates the branch at your current commit and switches to it. git checkout -b branch-name does the same on any Git version, and git branch branch-name creates it without switching. Uncommitted changes come along to the new branch.

How do I push a new branch to the remote?

Run git push -u origin branch-name. The -u (--set-upstream) flag records origin/branch-name as the branch's upstream, so a plain git push or git pull works from then on. With git config --global push.autoSetupRemote true (Git 2.37 or newer), a plain git push does this for every new branch.

How do I create a branch from a tag or a commit?

Give it as the start point: git switch -c hotfix v1.0, or git switch -c rescue followed by the commit hash. git switch v1.0 alone fails with 'a branch is expected, got tag', and git checkout v1.0 leaves you in detached HEAD state, where git switch -c name turns that position into a branch.

How do I install Git on Ubuntu?

Run sudo apt update, then sudo apt install git, then git --version. Ubuntu 24.04 installs Git 2.43.0 and Ubuntu 22.04 Git 2.34.1; the git-core PPA has newer releases. Set your identity once with git config --global user.name and git config --global user.email before the first commit.

Related articles

All guides