Git tracks the history of a project as a series of snapshots, and almost every
command you will run is either recording a new snapshot, moving between existing
ones, or comparing two of them. The reference below is grouped by what you are
trying to achieve rather than alphabetically, and the filter box searches all of
it at once — type rebase and every rebase command on the page comes to you.
Anything in angle brackets is a placeholder you replace. A few commands need
Git 2.23 or newer, and those are called out where they appear — check with
git --version.
The three places your changes live
Most confusing Git behaviour makes sense once this is clear. A change moves left to right, and nearly every command below is really just moving something between these.
| Where | Also called | What it holds | How things get there |
|---|---|---|---|
| Working tree | Your files on disk | Edits you have made and not recorded | You edit a file |
| Staging area | The index, the cache | The exact contents of your next commit | git add |
| Repository | History, the object database | Every commit ever recorded | git commit |
HEAD is a fourth thing worth naming: it is a pointer to the commit you currently
have checked out. HEAD~1 is its parent, HEAD~2 the one before that. That is why
the undo commands are written relative to HEAD.
Searches the task, the command and the third column. Press / from anywhere on the page.
93 commands
The everyday loop
Nine commands out of ten, this is all you need.
| Task | Command |
|---|---|
| See what has changed | git status |
| See the actual edits, unstaged | git diff |
| See the edits you have staged | git diff --staged |
| Stage one file | git add <file> |
| Stage everything, including deletions | git add -A |
| Stage only parts of a file, interactively | git add -p |
| Commit what is staged | git commit -m "<message>" |
| Stage every tracked file and commit in one go | git commit -am "<message>" |
| Download new commits without touching your files | git fetch |
| Download and integrate them | git pull |
| Send your commits to the remote | git push |
| Push a new branch and set it to track | git push -u origin <branch> |
git add -p is the one most people never learn and then use daily. It walks through each block of changes and asks whether to stage it, which is how you keep a tidy history without keeping a tidy working tree.
Start a repository
| Task | Command |
|---|---|
| Create a repository in the current folder | git init |
| Create one with a named default branch | git init -b main |
| Copy an existing repository | git clone <url> |
| Clone into a specific folder | git clone <url> <folder> |
| Clone only the latest commit | git clone --depth 1 <url> |
| Clone one branch only | git clone --single-branch --branch <branch> <url> |
A shallow clone with --depth 1 is much faster on a large repository and is the usual choice in CI, where the history is not needed. It does limit what you can do afterwards: no git log beyond that commit, and no diffing against old versions until you run git fetch --unshallow.
Branching and switching
git switch and git restore arrived in Git 2.23 to split the two unrelated jobs git checkout used to do. Both forms still work, but the newer ones are harder to get wrong: git checkout followed by a name either changes branch or destroys your changes, depending on whether that name happens to be a branch.
| Task | Command | Older equivalent |
|---|---|---|
| List local branches | git branch | |
| List every branch, remotes included | git branch -a | |
| Create a branch and switch to it | git switch -c <branch> | git checkout -b <branch> |
| Switch to an existing branch | git switch <branch> | git checkout <branch> |
| Go back to the previous branch | git switch - | git checkout - |
| Create a branch from a specific commit | git switch -c <branch> <commit> | git checkout -b <branch> <commit> |
| Rename the branch you are on | git branch -m <new-name> | |
| Delete a merged branch | git branch -d <branch> | |
| Delete a branch regardless | git branch -D <branch> | |
| Delete a branch on the remote | git push origin --delete <branch> | |
| See which branches are merged into this one | git branch --merged |
The lowercase -d refuses to delete a branch holding commits that are not merged anywhere, which is a useful safety net. -D skips the check, so reach for it when you mean to abandon the work.
Merging and rebasing
| Task | Command |
|---|---|
| Merge another branch into yours | git merge <branch> |
| Merge but always create a merge commit | git merge --no-ff <branch> |
| Combine a branch into one set of staged changes | git merge --squash <branch> |
| Abandon a merge that hit conflicts | git merge --abort |
| Replay your commits on top of another branch | git rebase <branch> |
| Edit, squash or reorder your last three commits | git rebase -i HEAD~3 |
| Continue after resolving a conflict | git rebase --continue |
| Abandon a rebase | git rebase --abort |
| Pull, rebasing instead of merging | git pull --rebase |
| Copy a single commit onto the current branch | git cherry-pick <commit> |
The rule that keeps you out of trouble: rebase your own unpushed commits as much as you like, and merge anything anyone else might already have. There is a fuller comparison further down the page.
Undoing things
The block people come back for. The right command depends on two things: whether the change is committed yet, and whether you have pushed it.
| I want to | Command | Loses work? |
|---|---|---|
| Unstage a file, keep the edits | git restore --staged <file> | No |
| Throw away uncommitted edits to a file | git restore <file> | Yes, permanently |
| Throw away all uncommitted edits | git restore . | Yes, permanently |
| Delete untracked files and folders | git clean -fd | Yes, permanently |
| Fix the last commit message | git commit --amend | No |
| Add a forgotten file to the last commit | git commit --amend --no-edit | No |
| Undo the last commit, keep changes staged | git reset --soft HEAD~1 | No |
| Undo the last commit, keep changes unstaged | git reset HEAD~1 | No |
| Undo the last commit and its changes | git reset --hard HEAD~1 | Yes |
| Undo a commit that is already pushed | git revert <commit> | No |
| Undo a merge that is already pushed | git revert -m 1 <merge-commit> | No |
| Stop tracking a file but keep it on disk | git rm --cached <file> | No |
Two things worth internalising. git restore and git clean are the only genuinely dangerous commands here, because uncommitted work exists nowhere else, whereas everything else can be walked back through the reflog. And revert is the answer for anything public: it adds a commit rather than rewriting one, so nobody else's clone breaks. Note also that git reset with no flag is --mixed, so git reset HEAD~1 and git reset --mixed HEAD~1 are the same command.
Inspecting history
| Task | Command |
|---|---|
| Compact log, one line per commit | git log --oneline |
| The whole graph, all branches | git log --oneline --graph --decorate --all |
| Last five commits | git log -5 |
| Commits by one person | git log --author="<name>" |
| Commits touching one file | git log --follow -- <file> |
| Commits whose diff mentions some text | git log -S"<text>" |
| Show one commit in full | git show <commit> |
| Who last changed each line | git blame <file> |
| Compare two branches | git diff <branch-a>..<branch-b> |
| What is on your branch and not on main | git log main..HEAD --oneline |
| Search the working tree | git grep "<text>" |
| Commit count per contributor | git shortlog -sn |
git log -S is the pickaxe. It finds commits that changed the number of occurrences of a string, which is how you find where a function was deleted rather than only where it is mentioned.
Working with remotes
| Task | Command |
|---|---|
| List remotes and their URLs | git remote -v |
| Add a remote | git remote add <name> <url> |
| Change a remote's URL | git remote set-url origin <url> |
| Fetch and delete stale remote-tracking branches | git fetch --prune |
| Fetch every remote | git fetch --all |
| Push a rewritten branch, safely | git push --force-with-lease |
| See what tracks what | git branch -vv |
| Set the upstream for the current branch | git push -u origin <branch> |
Prefer --force-with-lease to --force, always. Plain --force overwrites whatever is on the remote, while --force-with-lease first checks the remote is where you last saw it, so a colleague's push turns into an error instead of a deletion.
Stashing
For when you need a clean tree right now and are not ready to commit.
| Task | Command |
|---|---|
| Stash tracked changes | git stash push -m "<message>" |
| Include untracked files | git stash push -u -m "<message>" |
| List what you have stashed | git stash list |
| Apply the most recent and remove it | git stash pop |
| Apply it and keep it in the list | git stash apply |
| Apply a specific one | git stash apply stash@{2} |
| See what is in a stash | git stash show -p stash@{0} |
| Delete one | git stash drop stash@{0} |
| Delete all of them | git stash clear |
git stash push replaced git stash save, which still works but is deprecated. Note that a plain stash leaves untracked files behind, which is a common way to lose a new file. The -u flag includes them.
Tags and releases
| Task | Command |
|---|---|
| List tags | git tag |
| Create an annotated tag | git tag -a v1.0.0 -m "<message>" |
| Tag an older commit | git tag -a v1.0.0 <commit> -m "<message>" |
| Push one tag | git push origin v1.0.0 |
| Push every tag | git push origin --tags |
| Delete a tag locally | git tag -d v1.0.0 |
| Delete a tag on the remote | git push origin --delete v1.0.0 |
Use annotated tags, the -a flag, for anything you release. They carry an author, a date and a message, and unlike lightweight tags they are real objects in the repository, which is what release tooling expects.
When it has gone properly wrong
| Situation | Way out |
|---|---|
| Reset away a commit you needed | git reflog, then git reset --hard <hash> |
| Lost a branch you deleted | git reflog, then git switch -c <branch> <hash> |
| Need to find which commit broke something | git bisect start, then git bisect bad and git bisect good <commit> |
| Finished bisecting | git bisect reset |
| Detached HEAD and made commits | git switch -c <branch> |
| Committed to the wrong branch | git cherry-pick <commit> onto the right one, then reset this one |
git reflog is the single most useful recovery command in Git. It records every move HEAD has made, including the ones that left commits unreachable, and those commits stay on disk for 90 days by default. If you committed it, you can almost certainly get it back. The one thing this does not rescue is a committed secret: rotate the credential first and assume it is compromised, then rewrite the history.
Merge or rebase
Both bring another branch's work into yours, and the choice only really matters for commits you have already shared.
| Merge | Rebase | |
|---|---|---|
| History shape | Branches visibly join | One straight line |
| Commit hashes | Unchanged | Rewritten |
| Records what really happened | Yes | No, it is tidied |
| Safe on shared branches | Yes | No |
| Conflicts to resolve | Once | Potentially once per commit |
| Good for | Bringing a finished feature into main | Cleaning up your own work before a pull request |
Resolving a conflict
git statuslists the conflicted files.- Open each one and edit it. Git marks the two versions with
<<<<<<<,=======and>>>>>>>; delete the markers and leave the content you want. git add <file>on each resolved file to mark it done.git committo finish a merge, orgit rebase --continueto finish a rebase.
If it gets away from you, git merge --abort or git rebase --abort puts
everything back exactly as it was.
Config worth setting once
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
git config --global pull.rebase false
git config --global push.autoSetupRemote true
git config --global core.editor "code --wait"
git config --global diff.colorMoved zebra
push.autoSetupRemote (Git 2.37+) means a plain git push works on a new branch
without -u origin <branch>. pull.rebase has no default, and Git nags on every
pull until you pick one — false merges, true rebases.
To see where a setting came from:
git config --list --show-origin
Common questions
What is the difference between git fetch and git pull?
git fetch downloads new commits from the remote and updates your remote-tracking branches, but leaves your working branch alone. git pull does a fetch and then immediately merges or rebases those commits into your current branch. Fetch is always safe; pull changes your files.
How do I undo the last commit in Git?
If it is not pushed yet, git reset --soft HEAD~1 removes the commit and leaves your changes staged, ready to recommit. Use --mixed, the default, to leave them unstaged, or --hard to throw the changes away entirely. If the commit is already pushed, use git revert instead: it records a new commit that undoes the old one, so nobody else's history breaks.
What is the difference between git merge and git rebase?
Both bring another branch's commits into yours. Merge creates one new commit joining the two histories, so the record of what happened is preserved exactly. Rebase replays your commits on top of the other branch, producing a straight line but new commit hashes. Merge for anything already shared; rebase to tidy your own local work before sharing it.
How do I discard local changes to a file?
git restore followed by the filename throws away uncommitted changes in the working tree, and it cannot be undone because that work was never recorded anywhere. To unstage a file but keep the edits, use git restore --staged instead. On Git older than 2.23 the equivalents are git checkout -- and git reset HEAD.
How do I recover a commit I deleted with git reset --hard?
Run git reflog, which lists every position HEAD has been in for the last 90 days, find the hash you want, then git reset --hard onto it. Commits are not deleted the moment they become unreachable, so a reset you regret is almost always recoverable. This does not help with uncommitted changes, which were never recorded in the first place.
Why should I use --force-with-lease instead of --force?
git push --force overwrites the remote branch regardless of what is on it, so it silently destroys commits a colleague pushed while you were rebasing. --force-with-lease refuses the push if the remote has moved since you last fetched, which turns a data-loss bug into an error message.
How do I change the message of a commit I already pushed?
git commit --amend rewrites the message, but that produces a different commit hash, so the pushed version and your local version have diverged. You then need git push --force-with-lease. That is fine on a branch only you work on and disruptive on a shared one, because everybody else has to reset to the new history.
What does the staging area actually do?
It lets you choose what goes into the next commit, rather than committing everything you have touched. That is what makes it possible to fix a typo and add a feature in the same working session and still record them as two clean, separate commits, using git add -p to stage them one block at a time.