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.
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 itEvery 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
| Task | Command |
|---|---|
| Create a branch and switch to it | git switch -c name |
| The same, on any Git version | git checkout -b name |
| Create without switching | git branch name |
| Create from another branch | git switch -c name main |
| Create from a tag | git switch -c hotfix/1.0.1 v1.0 |
| Create from a commit | git switch -c rescue 478ed10 |
| Check out a branch that exists only on the remote | git switch feature/login |
| Push a new branch and set its upstream | git push -u origin name |
| Make plain git push do that (Git 2.37+) | git config --global push.autoSetupRemote true |
| List local, all, or with upstreams | git branch, git branch -a, git branch -vv |
| Switch to an existing branch | git switch name |
| Go back to the previous branch | git switch - |
| Delete a merged branch | git branch -d name |
| Delete a branch that is not merged | git branch -D name |
| Delete it on the remote | git push origin --delete name |
| Start a branch with no history | git switch --orphan name |
| Check a name before using it | git 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:
git switch -c feature/login
git checkout -b feature/signup main
git branch new-idea
git branch -vSwitched 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 commitThe * 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:
git switch -c wip
git status -sSwitched to a new branch 'wip'
M README.mdThat 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:
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.
AbortingCommit 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:
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 reflogA tag cannot be switched to directly, because git switch only moves between branches:
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:
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:
git fetch
git branch -a
git switch feature/login* 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:
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'.git push -u origin feature/loginTo ../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:
git config --global push.autoSetupRemote trueList branches and switch between them
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* feature/login cfe314a [origin/feature/login] Add login form
feature/signup 478ed10 Initial commit
main 478ed10 [origin/main] Initial commit
new-idea 478ed10 Initial commitBranches 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:
error: The branch 'experiment' is not fully merged.
If you are sure you want to delete it, run '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:
git push origin --delete feature/loginTo ../app.git
- [deleted] feature/loginOther 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; HEADas 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:
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:
sudo apt update
sudo apt install -y git
git --version| Release | Git from apt | Source |
|---|---|---|
| Debian 12 (bookworm) | 2.39.5 | apt-cache policy git, 21 October 2026 |
| Debian 13 (trixie) | 2.47.x | Debian's package archive |
| Ubuntu 22.04 LTS | 2.34.1 | Ubuntu's package archive |
| Ubuntu 24.04 LTS | 2.43.0 | Ubuntu's package archive |
| Any other release | apt policy git | Run 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:
sudo add-apt-repository ppa:git-core/ppa
sudo apt update
sudo apt install -y gitThen 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:
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
| Message | Cause | Fix |
|---|---|---|
fatal: a branch named 'review' already exists | The name is taken | git switch review to use it, or pick another name |
fatal: invalid reference: nosuch | git switch to a branch that exists neither locally nor on a remote | git 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 yet | Commit first, or use git switch -c, which works there |
fatal: a branch is expected, got tag 'v1.0' | git switch to a tag | git switch -c name v1.0 |
fatal: The current branch X has no upstream branch. | First push of a new branch | git push -u origin X |
error: src refspec X does not match any | Pushing a branch name that does not exist locally (typo), or a repository with no commits | git branch to check the name; commit first |
fatal: 'orgin' does not appear to be a git repository | Typo in the remote name | git remote -v lists the real names |
fatal: cannot lock ref 'refs/heads/dev/x': 'refs/heads/dev' exists | A 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 checkout | Switching to an existing branch with uncommitted edits | Commit 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.