Using git branch, git switch, and git restore — the way Git was meant to work
For years, git checkout did two completely different things depending on what arguments you gave it. It could switch branches or discard changes to a file — two operations that have nothing to do with each other. This confused beginners and experts alike.
Git 2.23 (released August 2019) introduced two focused commands to replace checkout:
| What you want to do | Old (confusing) | New (clear) |
|---|---|---|
| Switch to a branch | git checkout main | git switch main |
| Create and switch to new branch | git checkout -b feature | git switch --create feature |
| Discard changes to a file | git checkout -- hello.c | git restore hello.c |
| Unstage a file | git reset HEAD hello.c | git restore --staged hello.c |
One command, one job. git switch only ever touches branches. git restore only ever touches file content. You can reason about them without juggling mental state.
A branch is just a lightweight pointer to a commit. Creating one takes milliseconds and costs almost nothing. This matters because it means you should be using branches constantly — not just for big features.
Create branches freely. Make one to try a risky refactor. Make one to test a different approach. Make one just to experiment with an idea you're not sure about. Local branches are free. If the experiment fails, delete it with one command and nothing is lost. If it succeeds, merge it in.
The only branch you need to protect is main. Everything else is fair game.
Here's what a healthy branching workflow looks like visually:
# See all local branches git branch # Create a branch (does NOT switch to it) git branch feature/user-login # Create AND switch in one step (preferred) git switch --create feature/user-login # Delete a branch you're done with git branch --delete feature/user-login
Use a short, descriptive lowercase name with hyphens. Prefixes like feature/, fix/, or experiment/ help organize many branches at a glance. Examples: feature/add-dark-mode, fix/login-redirect, experiment/new-parser.
Here's a full cycle from starting work to merging it back:
# Make sure main is up to date git switch main git pull
git switch --create feature/better-search # You are now on the new branch
# Stage specific files (preferred over staging everything) git add search.js search.test.js # Or stage everything in current directory git add --all # Commit with a proper message (see Section 4) git commit --message "Add fuzzy search to reduce zero-result queries"
# Discard unstaged changes to a file git restore search.js # Unstage a file you accidentally staged git restore --staged search.js # Restore a file to its state 3 commits ago git restore --source HEAD~3 search.js
# Switch back to main git switch main # Merge your branch in git merge feature/better-search # Clean up — delete the branch git branch --delete feature/better-search
A commit message is a letter to your future self (and your teammates). A bad message like fix stuff is useless in six months. A good message tells you what changed and — more importantly — why.
Three rules, always:
fix bug · update · wip · changes · asdfgh
These tell you nothing. When you run git log to find when something broke, these messages are useless.
Write as if the commit is a command: "Add fuzzy search", not "Added fuzzy search". This matches how Git itself describes things ("Merge branch 'feature'") and reads naturally as a log: each commit does something.
# Opens your editor — write the full message there git commit # For a one-liner (when the subject line is enough) git commit --message "Remove deprecated payment gateway"
Set your editor to something you like: git config --global core.editor "nano". Then git commit without --message opens it and gives you space to write a proper multi-line message.
git branch # list branches git branch my-branch # create (don't switch) git branch --delete my-branch # delete branch git branch --move old-name new-name # rename branch
git switch main # switch to existing branch git switch --create feature/thing # create + switch git switch --detach HEAD~3 # inspect old commit (read-only)
git restore file.js # discard unstaged changes git restore --staged file.js # unstage file git restore --source HEAD~2 file.js # restore from 2 commits ago
git status # what changed? git log --oneline # compact commit history git log --oneline --graph # history with branch diagram git diff # unstaged changes git diff --staged # staged changes
Local branches are free. Experiment without fear. The worst outcome is running git branch --delete and losing nothing but some keystrokes. Branch early, branch often.