Git Cheat Sheet
Every Git command worth knowing — 82 of them across 15 categories, each with its syntax, the flags you actually use, worked examples and a danger rating so you know what can bite. Includes 36 deep cuts — the plumbing, patch and maintenance commands most cheat sheets leave out.
82 commands
git configSafeRead and write Git settings for one repo, your user, or the whole machine.
git configSafeGit settings live in three layers: system (whole machine), global (your user), and local (this repository). The most specific layer wins, so a local setting overrides your global one. Your name and email are baked into every commit you make, so set them before your first commit or you'll be rewriting authorship later.
Syntax
git config --global <key> <value>
git config --list --show-origin
git config <key>
Common options
--globalApply to your user account (~/.gitconfig) instead of just this repo.--localApply to the current repository only (the default).--listPrint every setting currently in effect.--show-originShow which file each setting came from - the fastest way to debug a surprising value.--unset <key>Remove a setting.--editOpen the config file in your editor.
Examples
Set the identity stamped on every commit you make.
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
Name the first branch main instead of master in new repos.
git config --global init.defaultBranch main
See every active setting and the exact file it came from.
git config --list --show-origin
A conditional include in ~/.gitconfig switches your email based on the folder.
# Use a different identity for work repos automatically
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work
git config --global alias.*SafeCreate your own short Git commands.
git config --global alias.*SafeAn alias maps a short name to any Git command and its flags, so a log incantation you can never remember becomes `git lg`. Prefixing the value with `!` runs an arbitrary shell command instead, which lets you build small tools that live with your config. Aliases are personal - they're never shared through the repository.
Syntax
git config --global alias.<name> '<command>'
git config --get-regexp ^alias
Common options
alias.<name> '<cmd>'Define the alias.'!<shell>'Leading ! runs a shell command instead of a Git subcommand.--get-regexp ^aliasList every alias you've defined.
Examples
A solid starter set - then just run git lg, git st, git unstage <file>.
git config --global alias.lg "log --oneline --graph --all --decorate"
git config --global alias.st "status -sb"
git config --global alias.unstage "restore --staged"
git config --global alias.last "log -1 --stat"
Aliases for the two things you do most when interrupted.
git config --global alias.undo 'reset --soft HEAD~1'
git config --global alias.wip '!git add -A && git commit -m "wip"'
git credential / credential.helperSafeStop Git asking for your username and password on every push.
git credential / credential.helperSafeGit delegates authentication to a credential helper that stores your token in the OS keychain. GitHub removed password authentication for HTTPS in 2021, so the 'password' is now a personal access token - which is exactly why people suddenly get authentication failures on repos that worked for years. SSH keys avoid the whole problem.
Syntax
git config --global credential.helper <helper>
git credential-manager configure
Common options
managerGit Credential Manager - the cross-platform default, uses the OS keychain.osxkeychainmacOS Keychain.libsecretLinux, via the GNOME keyring.cache --timeout=3600Keep credentials in memory for an hour. Never writes to disk.storeWrite to a PLAINTEXT file. Convenient, and a genuine security risk.
Examples
Windows / cross-platform: use Git Credential Manager.
git config --global credential.helper manager
macOS: store credentials in the Keychain.
git config --global credential.helper osxkeychain
Clear a stale token after rotating it.
git credential-manager erase
# or clear one host:
git credential reject
protocol=https
host=github.com
git helpSafeOpen the full manual for any Git command.
git helpSafeEvery Git command ships with a complete manual page. `git help <command>` opens it, and the `-h` short flag prints a quick usage summary in the terminal without leaving it. When you're unsure what a flag does, this is faster and more reliable than searching the web.
Syntax
git help <command>
git <command> -h
git help --all
Common options
-hShort usage summary printed straight to the terminal.--allList every available Git command.-gList the internal guides (git help -g, then git help revisions).
Examples
Open the full manual for git reset.
git help reset
The guide explaining HEAD~3, HEAD^2, @{upstream} and every other way to name a commit.
git help revisions
git initSafeTurn the current folder into a Git repository.
git initSafeCreates a hidden `.git` directory that holds your entire history, configuration and object database. Nothing outside `.git` is touched, so running it on an existing project is safe - your files are simply now trackable. Deleting `.git` deletes all of your history, so never do that casually.
Syntax
git init
git init <directory>
git init --bare <directory>
Common options
--bareCreate a repository with no working files - what a server hosting the repo uses.-b <name>Name the initial branch (e.g. -b main).--template=<dir>Seed .git/hooks and friends from your own template directory.
Examples
Start a new repository with main as the first branch.
git init -b main
Convert an existing project folder into a repo and record everything in it.
git init
git add .
git commit -m "Initial commit"
git cloneSafeCopy a remote repository, its full history and its branches, onto your machine.
git cloneSafeClone downloads the entire object database - every commit, branch and tag - then checks out the default branch for you and wires up a remote named `origin`. Because you get the whole history, most Git operations afterwards are local and instant. Use `--depth` or `--filter` when you only need recent history and the repo is huge.
Syntax
git clone <url>
git clone <url> <directory>
git clone --depth 1 <url>
Common options
--depth <n>Shallow clone: only the last n commits. Much faster on huge repos.--branch <name>Check out a specific branch or tag instead of the default.--single-branchFetch only one branch's history.--recurse-submodulesAlso clone and check out any submodules.--filter=blob:nonePartial clone - fetch file contents lazily. The best option for very large repos.--bare / --mirrorClone without a working tree; --mirror also copies every ref exactly (used for backups and migrations).
Examples
Standard clone into a folder named after the repo.
git clone https://github.com/user/repo.git
Grab only the newest commit on main - fast CI-style checkout.
git clone --depth 1 --branch main https://github.com/user/repo.git
Get all history but download file contents only when you actually touch them.
git clone --filter=blob:none https://github.com/user/huge-repo.git
git sparse-checkoutSafeCheck out only part of a huge repository.
git sparse-checkoutSafeSparse checkout keeps the full history but materialises only the directories you name, so a monorepo with 40 packages can appear on disk as the three you actually work on. Combined with `--filter=blob:none` at clone time it turns an unusable 10 GB checkout into a fast one. Cone mode is the fast, directory-based variant and the one you want.
Syntax
git sparse-checkout init --cone
git sparse-checkout set <dir>...
git sparse-checkout disable
Common options
init --coneEnable sparse checkout in the efficient directory-based mode.set <dirs>Replace the list of directories to materialise.add <dir>Add another directory to the working set.listShow what's currently checked out.disableGo back to a full checkout.
Examples
Clone a monorepo but only put two packages on disk.
git clone --filter=blob:none --no-checkout https://github.com/org/monorepo.git
cd monorepo
git sparse-checkout init --cone
git sparse-checkout set packages/web packages/shared
git checkout main
git statusSafeShow what's changed, what's staged, and what Git is ignoring.
git statusSafeThe command you should run more than any other. It tells you which branch you're on, whether you're ahead of or behind the remote, which files are staged for the next commit, which are modified but unstaged, and which are untracked. When something goes wrong, `git status` almost always names the command that fixes it.
Syntax
git status
git status -s
git status -sb
Common options
-s, --shortCompact one-line-per-file output.-b, --branchShow branch and tracking info even in short mode.--ignoredAlso list files excluded by .gitignore.-uallList individual untracked files instead of collapsing whole directories.
Examples
Compact status with the branch header - the everyday form.
git status -sb
Check whether .gitignore is actually excluding what you think it is.
git status --ignored
git addSafeStage changes so they go into your next commit.
git addSafeStaging copies the current content of a file into the index - a holding area between your folder and history. This is why you can edit a file after staging it and end up committing the older version. Staging also writes the content into Git's object database, which is what makes some otherwise-lost work recoverable later.
Syntax
git add <file>
git add .
git add -p
Common options
-A, --allStage every change in the repo, including deletions.-p, --patchStep through each change and choose hunk by hunk what to stage.-uStage modifications and deletions of already-tracked files, but not new files.-n, --dry-runShow what would be staged without staging it.-fForce-add a file that .gitignore excludes.
Examples
Stage everything under the current directory.
git add .
Interactively pick which hunks of one file to stage - the cleanest way to split a messy edit into focused commits.
git add -p src/app.ts
git commitSafeRecord everything currently staged as a permanent snapshot.
git commitSafeA commit stores a full snapshot of your tracked files plus a pointer to its parent commit, an author, a timestamp and a message. Once created it gets a unique SHA hash and becomes very hard to truly lose - even 'deleted' commits usually survive in the reflog for weeks. Write messages that explain why, not what; the diff already says what.
Syntax
git commit -m "message"
git commit
git commit --amend
Common options
-m <msg>Supply the message inline instead of opening an editor.-aAuto-stage all tracked modified files first (does not include new files).--amendReplace the previous commit instead of adding a new one.--no-verifySkip pre-commit and commit-msg hooks.--allow-emptyCreate a commit with no changes - sometimes needed to trigger CI.--fixup <hash>Mark this as a fixup for an earlier commit, to be squashed by rebase --autosquash.-SGPG-sign the commit.
Examples
Commit the staged changes with a message.
git commit -m "Fix crash when save file is empty"
Rewrite the last commit's message. Only safe if you haven't pushed it.
git commit --amend -m "Better message"
Record a fix against an old commit, then fold it in automatically.
git commit --fixup a1b2c3d
git rebase -i --autosquash a1b2c3d~1
git restoreCautionThrow away working-directory changes or unstage files (the modern, clearer replacement for checkout).
git restoreCautionGit 2.23 split the overloaded `git checkout` into two commands: `git switch` for branches and `git restore` for file contents. `git restore <file>` discards your edits; `git restore --staged <file>` unstages without touching your edits. Restoring a modified file is destructive - those edits were never in Git, so nothing can bring them back.
Syntax
git restore <file>
git restore --staged <file>
git restore --source=<commit> <file>
Common options
--stagedUnstage the file, leaving your working copy untouched.--worktreeRestore the working copy (the default).--source=<commit>Take the content from a specific commit instead of the index.-pChoose hunk by hunk what to discard.
Examples
Unstage a file you added by mistake, keeping your edits.
git restore --staged src/app.ts
Permanently discard your uncommitted edits to that file.
git restore src/app.ts
Bring back the version of a file as it was three commits ago.
git restore --source=HEAD~3 -- src/app.ts
git mvSafeRename or move a file and stage the change in one step.
git mvSafeGit doesn't actually track renames - it infers them by comparing content between commits. `git mv` is therefore just a convenience for moving the file and running `git add` on both paths. It matters most on Windows and macOS, whose case-insensitive filesystems make a rename like `player.cs` to `Player.cs` invisible to a plain `mv`.
Syntax
git mv <source> <destination>
git mv -f <source> <destination>
Common options
-f, --forceOverwrite an existing destination.-kSkip moves that would error instead of failing.-n, --dry-runShow what would happen.
Examples
Rename a file and stage both the deletion and the addition.
git mv src/oldName.ts src/newName.ts
Fix a filename's capitalisation on a case-insensitive filesystem.
git mv --force player.cs Player.cs
git rmCautionDelete a file from the repository - or just stop tracking it.
git rmCautionPlain `git rm` deletes the file from your disk and stages the deletion. `git rm --cached` removes it from Git's index only, leaving your local copy alone, which is the fix for a file that's already tracked and therefore immune to .gitignore. Neither form removes the file from history - earlier commits still contain it.
Syntax
git rm <file>
git rm --cached <file>
git rm -r --cached <dir>
Common options
--cachedUntrack the file but keep it on disk. The .gitignore fix.-rRecurse into directories.-fForce removal when the file has staged changes.-n, --dry-runList what would be removed.
Examples
Untrack a file so .gitignore finally applies to it.
git rm --cached .env
echo ".env" >> .gitignore
git commit -m "Stop tracking local env file"
Untrack a whole directory that should never have been committed.
git rm -r --cached node_modules/
git branchCautionList, create, rename and delete branches.
git branchCautionA branch is nothing more than a movable pointer to a commit, which is why creating one is instant and free no matter how big the repo is. Deleting a branch only removes the pointer - the commits it referenced stay in the object database and stay findable through the reflog, so a deleted branch is usually recoverable.
Syntax
git branch
git branch <name>
git branch -d <name>
git branch -m <old> <new>
Common options
-aList local and remote-tracking branches.-dDelete a branch, refusing if it has unmerged commits.-DForce delete even with unmerged commits.-mRename a branch.-vvShow each branch's upstream and how far ahead/behind it is.--merged / --no-mergedFilter to branches already merged (safe to delete) or not.--sort=-committerdateList by most recently worked on.
Examples
See every local branch with its upstream and ahead/behind counts.
git branch -vv
List branches fully merged into main - the ones that are safe to clean up.
git branch --merged main
Create a branch pointing at a specific commit - how you resurrect deleted work.
git branch feature-recovery a1b2c3d
Your ten most recently touched branches.
git branch --sort=-committerdate | head -10
git switchSafeMove to another branch - the modern, safer half of git checkout.
git switchSafe`git switch` only changes branches, so it can't silently destroy a file the way a mistyped `git checkout` can. It refuses to move if you have uncommitted changes that would be overwritten, which is a feature. `git switch -` jumps back to the previous branch, like `cd -`.
Syntax
git switch <branch>
git switch -c <new-branch>
git switch -
Common options
-c <name>Create the branch and switch to it in one step.-Switch back to the branch you were on before.--detachDeliberately check out a commit without a branch.-fForce the switch, discarding local changes. Destructive.--orphan <name>Start a branch with no history at all.
Examples
Create a new branch off the current commit and move onto it.
git switch -c feature/save-system
Hop back to the branch you were just on.
git switch -
Move to main. Git refuses if uncommitted work would be lost.
git switch main
git checkoutCautionThe old all-in-one: switch branches, restore files, or detach HEAD.
git checkoutCautionCheckout does three unrelated jobs, which is exactly why it caused so many accidents and why Git split it into `switch` and `restore`. The dangerous form is `git checkout -- <file>`, which silently overwrites your uncommitted edits with no confirmation and no way back. You'll still see checkout everywhere in older guides, so it's worth knowing.
Syntax
git checkout <branch>
git checkout -b <new-branch>
git checkout -- <file>
git checkout <commit> -- <file>
Common options
-b <name>Create and switch to a new branch.-- <file>Discard uncommitted changes to a file. Irreversible.<commit> -- <file>Pull one file's content out of another commit.--ours / --theirsDuring a conflict, take one side of the merge wholesale.
Examples
Switch branches, old-style. Prefer git switch main.
git checkout main
Restore one file from an old commit without touching anything else.
git checkout a1b2c3d -- config/settings.json
git mergeCautionCombine another branch's history into the current one.
git mergeCautionMerge finds the common ancestor of the two branches and replays the differences, creating a merge commit with two parents unless it can fast-forward. It preserves the true shape of history, unlike rebase. If both sides changed the same lines, Git stops and asks you to resolve the conflict by hand.
Syntax
git merge <branch>
git merge --abort
git merge --continue
Common options
--no-ffAlways create a merge commit, even when a fast-forward is possible - keeps feature branches visible.--ff-onlyRefuse unless it's a clean fast-forward.--squashBring in the changes as one uncommitted lump instead of a merge.--abortBail out of a conflicted merge and return to how things were.--continueFinish the merge once you've resolved and staged the conflicts.--allow-unrelated-historiesMerge two projects that share no common ancestor.
Examples
Merge a feature branch into main, keeping the branch visible in history.
git switch main
git merge --no-ff feature/save-system
Escape hatch when a merge goes wrong - restores the pre-merge state.
git merge --abort
git rebaseDestructiveReplay your commits on top of another branch to produce a straight, linear history.
git rebaseDestructiveRebase takes each of your commits, sets them aside, moves your branch to the new base, and re-applies them one at a time. Because each replayed commit gets a brand new hash, you are creating new commits and abandoning the old ones - which is why rebasing anything you've already shared causes chaos for everyone else. The golden rule: never rebase public history.
Syntax
git rebase <base>
git rebase -i HEAD~<n>
git rebase --abort
git rebase --continue
Common options
-i, --interactiveOpen an editor to reorder, squash, edit, drop or reword commits.--onto <newbase>Move a range of commits onto a completely different base.--abortCancel the rebase and restore the original branch.--continue / --skipProceed after resolving a conflict, or skip the current commit.--autostashStash uncommitted work before rebasing and reapply it after.-r, --rebase-mergesPreserve the merge structure instead of flattening it.
Examples
Replay your branch's commits on top of the latest main.
git rebase main
Interactively clean up your last four commits before opening a PR.
git rebase -i HEAD~4
Move feature-b's commits off feature-a and onto main.
git rebase --onto main feature-a feature-b
git cherry-pickCautionCopy one specific commit onto your current branch.
git cherry-pickCautionCherry-pick applies the change introduced by a single commit as a new commit on your branch. It's the right tool for backporting a hotfix to a release branch, or for rescuing one good commit out of an abandoned branch. The result is a duplicate change with a different hash, so avoid cherry-picking commits you'll later merge anyway.
Syntax
git cherry-pick <commit>
git cherry-pick <start>..<end>
git cherry-pick --abort
Common options
-n, --no-commitApply the change but leave it staged so you can adjust it first.-xAppend the original commit hash to the message - useful for backports.-m <parent>Required when cherry-picking a merge commit.--continue / --abort / --skipResume after resolving conflicts, cancel entirely, or skip this commit.
Examples
Bring one commit from another branch onto this one.
git cherry-pick a1b2c3d
Copy an inclusive range of commits.
git cherry-pick a1b2c3d^..f4e5d6c
git merge-baseSafeFind the commit two branches diverged from.
git merge-baseSafeThe merge base is the most recent common ancestor of two branches, and it's what every merge and three-dot diff is computed against. Knowing it explicitly is invaluable in scripts and CI, where 'show me only what this pull request changed' means diffing against the merge base rather than the tip of main.
Syntax
git merge-base <a> <b>
git merge-base --is-ancestor <a> <b>
git merge-base --fork-point <branch>
Common options
--is-ancestor <a> <b>Exit 0 if a is an ancestor of b. Perfect for scripts.--fork-pointUse the reflog to work out where a branch actually forked.--allPrint every merge base when there is more than one.
Examples
Print the commit where the feature branch diverged from main.
git merge-base main feature
Show only what your branch changed, ignoring anything main added since.
git diff $(git merge-base main HEAD)..HEAD --stat
git mergetoolSafeOpen a visual three-way diff tool to resolve conflicts.
git mergetoolSafeWhen conflict markers in a text editor get overwhelming, mergetool launches a configured GUI showing your version, their version and the common ancestor side by side. Most editors (VS Code, JetBrains, Meld, Beyond Compare) can register as the merge tool. Configure it once and conflicts become far less intimidating.
Syntax
git mergetool
git config --global merge.tool <tool>
Common options
--tool=<name>Use a specific tool for this run.-ySkip the prompt before launching for each file.merge.conflictStyle=zdiff3Config that adds the common ancestor into the conflict markers. Hugely clarifying.
Examples
Set VS Code as your merge tool, then resolve the current conflict in it.
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'
git mergetool
Show what the code originally looked like inside every conflict block.
git config --global merge.conflictStyle zdiff3
git rerereSafeRemember how you resolved a conflict and replay it automatically next time.
git rerereSafeShort for 'reuse recorded resolution'. Once enabled, Git records the way you settled each conflict and silently reapplies the same fix if the identical conflict shows up again. It pays for itself the moment you rebase a long-lived branch repeatedly and keep hitting the same three conflicts.
Syntax
git config --global rerere.enabled true
git rerere status
git rerere forget <path>
Common options
statusShow which paths have recorded resolutions in play.diffShow what the recorded resolution changes.forget <path>Discard a recorded resolution you got wrong.
Examples
Turn it on once, benefit forever.
git config --global rerere.enabled true
Drop a bad recorded resolution so you can redo it.
git rerere forget src/app.ts
git remoteSafeManage the named URLs your repository syncs with.
git remoteSafeA remote is just a nickname for a URL. `origin` is the conventional name for where you cloned from, and `upstream` is the convention for the original repo when you're working from a fork. Renaming or re-pointing a remote is a local-only operation and affects nothing on the server.
Syntax
git remote -v
git remote add <name> <url>
git remote set-url origin <url>
Common options
-vShow remote names alongside their fetch and push URLs.add <name> <url>Register a new remote.set-url <name> <url>Point an existing remote somewhere else.rename <old> <new>Rename a remote.remove <name>Forget a remote.prune <name>Delete local references to branches that no longer exist on the server.show <name>Full detail: tracked branches, what's stale, what would be pushed.
Examples
Check exactly which server you're pushing to.
git remote -v
Track the original project after forking it.
git remote add upstream https://github.com/original/repo.git
Switch a repo from HTTPS to SSH.
git remote set-url origin git@github.com:user/repo.git
git fetchSafeDownload new commits from the remote without changing any of your files.
git fetchSafeFetch is the completely safe way to see what's happened on the server. It updates your remote-tracking branches (like `origin/main`) but leaves your own branches and working directory untouched, so you can inspect incoming changes before deciding to merge them. Every `git pull` is really a fetch followed by a merge.
Syntax
git fetch
git fetch origin <branch>
git fetch --all --prune
Common options
--allFetch from every configured remote.--pruneRemove tracking refs for branches deleted on the server.--tagsAlso fetch tags.--unshallowConvert a shallow clone into a full one.--dry-runShow what would be fetched without doing it.
Examples
Refresh everything from all remotes and clear out deleted branches.
git fetch --all --prune
See exactly which commits are waiting for you before you pull.
git fetch origin
git log HEAD..origin/main --oneline
git pullCautionFetch from the remote and immediately integrate the changes into your branch.
git pullCautionPull is `git fetch` plus `git merge` (or `git rebase` with `--rebase`). Because it changes your working branch automatically, it's the command most likely to drop you into an unexpected conflict. Many teams set `pull.rebase true` to keep history linear and avoid a merge commit every time someone syncs.
Syntax
git pull
git pull --rebase
git pull origin <branch>
Common options
--rebaseReplay your local commits on top of the fetched ones instead of merging.--ff-onlyRefuse to pull if it would require a merge - a safe default.--no-rebaseForce the merge behaviour for this pull.--autostashStash local changes, pull, then reapply them.
Examples
Bring in the latest main and replay your work on top of it.
git pull --rebase origin main
Set sane defaults once so plain git pull never surprises you.
git config --global pull.rebase true
git config --global pull.ff only
git pushCautionUpload your commits to the remote repository.
git pushCautionPush sends commits the server doesn't have and moves the remote branch pointer forward. Git refuses a push that would discard commits on the server, which is the safety net you should almost never override. When you genuinely must rewrite pushed history, use `--force-with-lease` rather than `--force` - it aborts if someone else pushed in the meantime.
Syntax
git push
git push -u origin <branch>
git push --force-with-lease
Common options
-u, --set-upstreamLink the local branch to the remote one so plain git push works afterwards.--force-with-leaseForce push, but abort if the remote has commits you haven't seen. Always prefer this over --force.--forceOverwrite the remote branch unconditionally. Can destroy a colleague's work.--tags / --follow-tagsPush tags too; --follow-tags sends only annotated tags reachable from what you're pushing.--delete <branch>Delete a branch on the server.--dry-runShow what would be pushed.
Examples
Publish a new branch and set it to track origin.
git push -u origin feature/save-system
Update a rewritten branch safely, refusing if someone else pushed first.
git push --force-with-lease
Delete a remote branch.
git push origin --delete old-feature
git ls-remoteSafeList a remote's branches and tags without cloning it.
git ls-remoteSafeQueries the server directly and prints every ref with its hash, using no local repository at all. It's the quickest way to check whether a tag exists, whether your credentials work, or what a repo's default branch is - and it's a staple of CI scripts that need to test a URL before committing to a full clone.
Syntax
git ls-remote <url>
git ls-remote --heads origin
git ls-remote --tags origin
Common options
--headsBranches only.--tagsTags only.--symrefAlso show what HEAD points to - i.e. the default branch.--exit-codeExit non-zero when nothing matches. Handy in scripts.
Examples
Find a repository's default branch without cloning it.
git ls-remote --symref https://github.com/user/repo.git HEAD
Check which release tags exist on the server.
git ls-remote --tags origin | tail -5
git logSafeBrowse commit history with as much or as little detail as you want.
git logSafeLog is the main window into your project's past, and its filters are what make it powerful: search by author, date range, file path, or even the content of the change itself. `-S` (the 'pickaxe') finds every commit that added or removed a given string, which is how you track down when a line of code appeared or vanished.
Syntax
git log
git log --oneline --graph --all
git log -- <path>
git log -S <string>
Common options
--onelineOne compact line per commit.--graphDraw the branch and merge structure as ASCII art.-pShow the full diff introduced by each commit.-S <string>Pickaxe: find commits where the number of occurrences of a string changed.-G <regex>Like -S but matches the diff text against a regular expression.--follow -- <file>Track a file's history across renames.--all --full-history -- <path>Search every branch, including for files that have since been deleted.--since / --untilLimit by date, e.g. --since='2 weeks ago'.--author=<name>Filter by author.--first-parentFollow only the mainline, hiding commits merged in from branches.--format=<fmt>Custom output, e.g. --format='%h %an %ar %s'.
Examples
The classic visual overview of every branch.
git log --oneline --graph --all --decorate
Find every commit that added or removed a mention of AudioManager.
git log -S "AudioManager" --oneline
Find a file's history even if it was deleted long ago.
git log --all --full-history -- "**/PlayerController.cs"
See only the merges into main - a clean release-level history.
git log --first-parent main --oneline
git diffSafeShow exactly what changed - between your files, the index, commits or branches.
git diffSafeWith no arguments, diff shows unstaged changes; with `--staged` it shows what's about to be committed. The two-dot form compares endpoints, while the three-dot form compares against the common ancestor, which is usually what you want when reviewing a branch. `--word-diff` is invaluable for prose and config files.
Syntax
git diff
git diff --staged
git diff <branch1>..<branch2>
git diff <commit> -- <file>
Common options
--staged, --cachedCompare the index against the last commit - a preview of your next commit.--statSummarise changes per file instead of printing the full diff.--word-diffHighlight changes word by word rather than line by line.main...featureThree-dot form: everything the branch added since it diverged.--name-only / --name-statusJust the changed file names, optionally with their status letters.-wIgnore whitespace changes.--diff-filter=<letters>Limit by change type: A added, M modified, D deleted, R renamed.
Examples
Review precisely what you're about to commit.
git diff --staged
Summarise everything a feature branch adds relative to main.
git diff main...feature/save-system --stat
See how one file changed over the last three commits.
git diff HEAD~3 -- src/app.ts
git showSafeDisplay a single commit, tag or file-at-a-commit in full.
git showSafeShow prints the metadata and complete diff for any object you name. Its most useful trick is the `<commit>:<path>` syntax, which dumps the exact contents of a file as it existed at that commit without checking anything out - perfect for peeking at an old version before deciding to restore it.
Syntax
git show <commit>
git show <commit>:<path>
git show --stat <commit>
Common options
--statShow the summary of changed files rather than the whole diff.--name-onlyList only the affected file names.<commit>:<path>Print a file exactly as it was at that commit.-sSuppress the diff and show only the commit message.
Examples
Print an old version of a file without changing your working directory.
git show HEAD~2:src/config.json
Save a commit's full diff to a file.
git show a1b2c3d > patch.diff
git blameSafeShow which commit and author last touched every line of a file.
git blameSafeBlame annotates each line with the commit, author and date that last modified it, which is how you find the context behind a baffling piece of code. Use `-w` to ignore whitespace-only changes and `-C` to follow lines that moved between files, otherwise a single reformat can make it look like one person wrote everything.
Syntax
git blame <file>
git blame -L <start>,<end> <file>
Common options
-L <start>,<end>Limit to a line range.-wIgnore whitespace changes.-CDetect lines moved or copied from other files.--ignore-rev <rev>Skip a known noisy commit, like a project-wide reformat.--ignore-revs-file <file>Skip every commit listed in a file (conventionally .git-blame-ignore-revs).
Examples
Find who really wrote lines 40-60, ignoring reformatting noise.
git blame -w -C -L 40,60 src/PlayerController.cs
Permanently hide a formatting commit from every future blame.
echo "a1b2c3d4e5f6 # repo-wide prettier run" >> .git-blame-ignore-revs
git config blame.ignoreRevsFile .git-blame-ignore-revs
git bisectSafeBinary-search your history to find the exact commit that introduced a bug.
git bisectSafeYou tell Git one commit where things work and one where they don't; it checks out the midpoint and asks you to test. Each answer halves the search space, so even a thousand commits are narrowed down in about ten steps. With `git bisect run` you hand it a script and it finds the culprit completely unattended.
Syntax
git bisect start
git bisect bad
git bisect good <commit>
git bisect reset
Common options
startBegin a bisect session.bad / goodMark the current checkout as broken or working.run <cmd>Automate: a non-zero exit means bad, zero means good.resetEnd the session and return to where you were.skipSkip a commit that can't be tested (e.g. it doesn't build).log / replaySave and restore a bisect session.
Examples
Manually narrow down which commit between v1.2.0 and now broke things.
git bisect start
git bisect bad
git bisect good v1.2.0
# test, then mark each step:
git bisect good # or: git bisect bad
git bisect reset
Fully automated hunt - Git runs your test suite at each step and names the guilty commit.
git bisect start HEAD v1.2.0
git bisect run npm test
git grepSafeSearch the contents of tracked files - at any point in history.
git grepSafeMuch faster than a normal recursive grep because it only looks at tracked files and uses Git's index. Its real superpower is searching an arbitrary commit or tag, letting you ask 'was this function present in v1.0?' without checking anything out.
Syntax
git grep <pattern>
git grep <pattern> <commit>
git grep -n <pattern>
Common options
-nShow line numbers.-iCase-insensitive.-lList only the matching file names.-C <n>Show n lines of context.--all-matchRequire all specified patterns to match.
Examples
Find every TODO in tracked files with line numbers.
git grep -n "TODO"
Check whether a function existed at a given tag.
git grep "loadSave" v1.2.0
Search every commit in the entire history for a leaked key.
git grep -n "apiKey" $(git rev-list --all)
git shortlogSafeSummarise commits grouped by author - the built-in contribution report.
git shortlogSafeGroups the log by author and prints their commit subjects, which makes it the fastest way to generate release notes or see who worked on what. `-sn` collapses it to a sorted count per person, which is the form almost everyone actually uses.
Syntax
git shortlog
git shortlog -sn
git shortlog -sn <range>
Common options
-sSummary: just the commit count per author.-nSort by number of commits.-eShow email addresses.--no-mergesExclude merge commits.
Examples
Contributor leaderboard with emails, ignoring merges.
git shortlog -sne --no-merges
Draft release notes grouped by author, between two tags.
git shortlog v1.0.0..v1.1.0
git difftoolSafeView diffs in a graphical diff viewer instead of the terminal.
git difftoolSafeRuns the same comparisons as `git diff` but opens each file in a configured GUI. Once set up it's the fastest way to review a large change, and unlike `git diff` it handles a side-by-side view of long files comfortably.
Syntax
git difftool
git difftool <commit>..<commit>
git difftool --dir-diff
Common options
--dir-diff, -dOpen the whole changeset at once instead of file by file. The killer feature.-yDon't prompt before launching.--tool=<name>Pick a tool for this run.
Examples
Review an entire branch in VS Code's diff viewer in one go.
git config --global diff.tool vscode
git config --global difftool.vscode.cmd 'code --wait --diff $LOCAL $REMOTE'
git difftool -d main...HEAD
git range-diffSafeDiff two versions of a branch - what changed between rebases.
git range-diffSafeCompares two ranges of commits and shows how each commit differs from its counterpart, which is exactly what you need when a reviewer asks 'what did you change since my last review?' after you rebased. Without it you're stuck comparing hashes that all changed for unrelated reasons.
Syntax
git range-diff <base>..<old> <base>..<new>
git range-diff <old>...<new>
Common options
--creation-factor=<n>Tune how eagerly it pairs up commits.--no-dual-colorSimpler colouring.
Examples
See exactly how a branch changed across a force-push.
git range-diff main@{1}...main
Compare two revisions of the same feature branch, commit by commit.
git range-diff main..feature-v1 main..feature-v2
git cherrySafeFind which of your commits haven't been applied upstream yet.
git cherrySafeCompares two branches by the *content* of their changes rather than by hash, so a commit that was cherry-picked or rebased upstream is correctly recognised as already applied. It marks each commit with `+` (not upstream) or `-` (already there), which is the honest answer to 'has my patch landed?'.
Syntax
git cherry <upstream>
git cherry -v <upstream> <head>
Common options
-vInclude the commit subject line.<upstream> <head>Compare an explicit pair rather than the current branch.
Examples
List your branch's commits and whether each already exists on main.
git cherry -v main
Show only the work that genuinely hasn't landed upstream.
git cherry -v origin/main | grep '^+'
git resetDestructiveMove the current branch pointer, optionally rewriting the index and your files.
git resetDestructiveReset has three modes and understanding them is most of understanding Git. `--soft` moves only the branch pointer, leaving everything staged. `--mixed` (the default) also clears the index but keeps your files. `--hard` additionally overwrites your working directory, permanently destroying uncommitted work - this is the single most dangerous everyday Git command.
Syntax
git reset --soft HEAD~1
git reset HEAD~1
git reset --hard HEAD~1
git reset <file>
Common options
--softMove the branch only. Changes stay staged, ready to recommit.--mixedDefault. Move the branch and unstage, but keep your file edits.--hardMove the branch and wipe the index and working directory. Destroys uncommitted work.--keepLike --hard but refuses if it would overwrite local changes. A far safer default.--mergeReset but try to preserve uncommitted changes that don't conflict.<file>With a path, unstages that file rather than moving any branch.
Examples
Undo the last commit but keep every change staged - the safest way to redo a commit.
git reset --soft HEAD~1
Undo the last commit and unstage its changes, leaving your files as they are.
git reset HEAD~1
Like --hard, but aborts instead of silently destroying uncommitted work.
git reset --keep HEAD~1
git revertSafeCreate a new commit that undoes an earlier one, leaving history intact.
git revertSafeRevert is the safe way to undo something you've already pushed, because it adds history rather than rewriting it - nobody else has to do anything special. To revert a merge you must say which parent to treat as the mainline with `-m 1`. Use revert on shared branches and reset on private ones.
Syntax
git revert <commit>
git revert -m 1 <merge-commit>
git revert --no-commit <a>..<b>
Common options
-m <parent>Required for merge commits: which parent is the mainline (usually 1).--no-commit, -nStage the reversal without committing, so you can batch several.--continue / --abort / --skipResume, cancel, or skip after a conflict.
Examples
Safely undo a pushed commit by adding an inverse commit.
git revert a1b2c3d
Undo a merge commit, keeping the first parent as the mainline.
git revert -m 1 a1b2c3d
Undo a range of commits as one clean revert.
git revert --no-commit HEAD~3..HEAD
git commit -m "Revert the last three commits"
git cleanDestructiveDelete untracked files and directories from your working tree.
git cleanDestructiveClean removes files Git isn't tracking - build output, stray downloads, generated assets. Because those files were never in Git, deleting them is completely irreversible: no reflog, no object database, nothing. Always run it with `-n` first to see the list, and remember `-x` also removes ignored files like your `.env`.
Syntax
git clean -n
git clean -fd
git clean -fdx
Common options
-n, --dry-runList what would be deleted without deleting it. Always start here.-f, --forceActually delete. Required, because Git refuses otherwise.-dAlso remove untracked directories.-xAlso remove ignored files - including .env files and local configs.-XRemove ONLY ignored files, keeping other untracked ones.-iInteractive mode: choose what to delete.
Examples
Preview exactly which untracked files and folders would be removed.
git clean -nd
Delete untracked files and directories, keeping ignored ones.
git clean -fd
Wipe build output and other ignored files while keeping new source files.
git clean -fX
git stashCautionPark uncommitted work temporarily so you can do something else.
git stashCautionStashing saves your modified tracked files onto a stack and returns your working directory to a clean state. Crucially, stashes are stored as real commit objects, which is why a stash you dropped by accident can usually still be dug out with `git fsck`. By default untracked files are left behind - use `-u` to include them.
Syntax
git stash
git stash push -m "message"
git stash pop
git stash list
Common options
push -m <msg>Stash with a description so you can tell entries apart later.-u, --include-untrackedAlso stash untracked files.-a, --allAlso stash ignored files.-k, --keep-indexLeave already-staged changes in place.popApply the newest stash and remove it from the stack.applyApply a stash but keep it on the stack.drop / clearDelete one stash, or all of them.show -p stash@{0}View a stash's full diff before applying it.branch <name>Create a branch from a stash - the clean fix for a stash that won't apply.
Examples
Stash everything including new files, with a label.
git stash push -u -m "half-done inventory UI"
Inspect a specific stash before restoring it.
git stash list
git stash show -p stash@{1}
git stash pop stash@{1}
Turn a stash that conflicts with your current branch into its own branch.
git stash branch recovered-work stash@{0}
git commit --amendCautionReplace the most recent commit instead of stacking another one on top.
git commit --amendCautionAmend combines whatever is currently staged with the previous commit and rewrites it, producing a new hash. It's the standard fix for a typo in a message or a file you forgot to include. Because the hash changes, amending something already pushed means you'll need `--force-with-lease` and a heads-up to your team.
Syntax
git commit --amend -m "new message"
git commit --amend --no-edit
git commit --amend --author="Name <email>"
Common options
--no-editKeep the existing message - perfect for slipping in a forgotten file.-m <msg>Replace the message.--author=<author>Fix a wrong author on the last commit.--reset-authorSet author to you and refresh the timestamp.--date=<date>Override the author date.
Examples
Add a forgotten file to the last commit without changing its message.
git add forgotten-file.ts
git commit --amend --no-edit
Rewrite a bad commit message.
git commit --amend -m "Fix: clamp health to zero"
git rebase -iDestructiveReorder, squash, split, reword or drop your recent commits.
git rebase -iDestructiveInteractive rebase opens an editor listing your commits oldest-first, each prefixed with an action you can change: `pick`, `reword`, `edit`, `squash`, `fixup` or `drop`. Reordering the lines reorders the commits. It's the tool that turns a messy sequence of 'wip' commits into a clean, reviewable story before you open a pull request.
Syntax
git rebase -i HEAD~<n>
git rebase -i <commit>
git rebase -i --root
Common options
pickKeep the commit as-is.rewordKeep the changes, edit the message.editStop at this commit so you can amend or split it.squashMerge into the previous commit and combine messages.fixupMerge into the previous commit and discard this message.dropDelete the commit entirely.exec <cmd>Run a command after that commit - e.g. run tests at every step.--autosquashAutomatically order commits created with git commit --fixup.--rootInclude the very first commit in the rebase.
Examples
Clean up the last five commits before pushing.
git rebase -i HEAD~5
Replay five commits, running the test suite after each one to find where it broke.
git rebase -i --exec 'npm test' HEAD~5
git filter-repoDestructiveRewrite an entire repository's history - the supported way to purge a leaked secret or a huge file.
git filter-repoDestructiveAn external tool (install with `pip install git-filter-repo`) that officially replaces the slow and error-prone `git filter-branch`. It can strip a path from every commit that ever contained it, rewrite emails, or shrink a bloated repo. It rewrites every affected commit hash, so everyone must re-clone afterwards - and if you removed a secret, you must still rotate it, because the old commits may already be cached on the server.
Syntax
git filter-repo --path <file> --invert-paths
git filter-repo --replace-text <file>
Common options
--path <p> --invert-pathsRemove that path from all of history.--path <p>Keep ONLY that path - how you split a subdirectory into its own repo.--replace-text <file>Replace matching strings everywhere, e.g. redact an API key.--strip-blobs-bigger-than <size>Drop every blob over a size threshold, e.g. 50M.--mailmap <file>Rewrite author and committer identities.--forceRun against a repo that isn't a fresh clone.
Examples
Erase a committed .env from every commit, then republish. Rotate the leaked keys regardless.
pip install git-filter-repo
git filter-repo --path .env --invert-paths
git push --force --all
Extract one subdirectory into a standalone repository, keeping its history.
git filter-repo --path packages/ui/ --path-rename packages/ui/:
git replaceCautionSwap one object for another without rewriting a single hash.
git replaceCautionCreates a ref telling Git to substitute object B whenever object A is requested, so you can graft histories together or correct an ancient commit without changing any hashes. Nothing is rewritten and existing clones stay valid - but replacements are local until you explicitly push refs/replace, which makes them a specialist tool rather than an everyday one.
Syntax
git replace <object> <replacement>
git replace -l
git replace -d <object>
Common options
-l, --listList active replacements.-d, --deleteRemove a replacement.--graft <commit> <parent>Rewrite a commit's parents - how you stitch a truncated history back on.--no-replace-objectsGlobal flag to ignore replacements for one command.
Examples
Reattach an archived history to a repo that was truncated by a shallow clone.
git replace --graft <oldest-commit> <tip-of-archived-history>
git filter-repo --force # make it permanent
git reflogSafeThe undo history for everything - the single most important recovery command.
git reflogSafeThe reflog records every position HEAD and your branches have pointed to, including states no branch references any more. That means a commit destroyed by `reset --hard`, a rebase that mangled your work, or a branch you deleted are all still listed here for about 90 days by default. If you have lost commits, start here before trying anything else.
Syntax
git reflog
git reflog show <branch>
git reset --hard HEAD@{n}
Common options
show <ref>Show the reflog for a specific branch or remote-tracking ref.--date=isoPrint real timestamps rather than relative ones.HEAD@{n}Refer to where HEAD was n moves ago.HEAD@{2.days.ago}Refer to where HEAD was at a point in time.expirePrune old entries (this is what eventually makes recovery impossible).
Examples
List recent HEAD positions - find the hash from just before the disaster.
git reflog
Undo a bad reset by jumping back to the previous HEAD position.
git reflog
git reset --hard HEAD@{1}
See what the remote branch pointed at before someone force-pushed.
git reflog show origin/main
git fsckSafeScan the object database for orphaned commits and blobs the reflog no longer lists.
git fsckSafeWhen even the reflog can't help - a dropped stash, a file that was staged but never committed, work lost in a botched rebase - fsck checks every object in `.git/objects` and reports the ones nothing points to. Dangling blobs are file contents, dangling commits are whole snapshots. This is the deepest recovery layer Git has, and it also detects genuine repository corruption.
Syntax
git fsck --lost-found
git fsck --unreachable
git fsck --dangling --no-reflogs
Common options
--lost-foundWrite recoverable objects into .git/lost-found for easy browsing.--unreachableList objects no reference can reach.--danglingList objects nothing at all points to.--no-reflogsTreat reflog entries as not counting, exposing more candidates.--fullCheck packed objects and alternates too.
Examples
Recover orphaned objects into .git/lost-found/ so you can inspect them.
git fsck --lost-found
List orphaned commits - how you find a stash you dropped.
git fsck --unreachable | grep commit
git cat-fileSafePrint the raw contents or type of any object by its hash.
git cat-fileSafeOnce fsck hands you a list of dangling hashes, cat-file is how you look inside them to work out which one you actually want. `-t` tells you whether a hash is a blob, tree, commit or tag; `-p` pretty-prints its contents. Redirect the output to a file and your lost work is back.
Syntax
git cat-file -t <hash>
git cat-file -p <hash>
git cat-file -p <hash> > file.txt
Common options
-tPrint the object's type.-pPretty-print the object's contents.-sPrint the object's size in bytes.--batch-checkRead many hashes on stdin and report type and size - the basis of every 'find big files' script.
Examples
Dump the contents of a recovered blob to the terminal.
git cat-file -p a1b2c3d
Write a recovered blob straight back to disk.
git cat-file -p a1b2c3d > recovered-file.ts
git tagSafeMark a specific commit as a release.
git tagSafeTags are permanent labels on a commit, unlike branches which move. Prefer annotated tags (`-a`), which are real objects carrying an author, date and message, over lightweight ones. Tags are not pushed by default - you have to send them explicitly, which is a common source of 'why isn't my release showing up'.
Syntax
git tag -a v1.0.0 -m "Release 1.0.0"
git tag
git push origin v1.0.0
Common options
-a <name> -m <msg>Create an annotated tag with a message.-l <pattern>List tags matching a pattern.-d <name>Delete a local tag.-fMove an existing tag to a new commit.-sCreate a GPG-signed tag.--sort=-v:refnameList tags in proper version order, newest first.
Examples
Create an annotated release tag and publish it.
git tag -a v1.0.0 -m "First public release"
git push origin v1.0.0
Show the five most recent version tags.
git tag --sort=-v:refname | head -5
Remove a tag locally and on the server.
git tag -d v1.0.0
git push origin --delete v1.0.0
git describeSafeGenerate a human-readable version string from the nearest tag.
git describeSafeProduces something like `v1.2.0-14-ga1b2c3d`, meaning fourteen commits after tag v1.2.0 at commit a1b2c3d. It's the standard way to stamp a build with a meaningful version number automatically, and it's used constantly in CI pipelines and game build numbers.
Syntax
git describe
git describe --tags --always --dirty
Common options
--tagsConsider lightweight tags too, not just annotated ones.--alwaysFall back to a bare commit hash if no tag exists.--dirtyAppend -dirty if the working tree has uncommitted changes.--abbrev=<n>Control how many hash characters are shown.
Examples
Get a build-stamp version string that always produces something.
git describe --tags --always --dirty
git notesSafeAttach extra information to a commit without changing its hash.
git notesSafeNotes let you annotate a commit after the fact - review links, CI results, release metadata - and the commit's hash never changes, so nothing is rewritten. The catch is that notes live in their own ref and aren't fetched or pushed by default, which is why they're powerful in automation and almost invisible in day-to-day use.
Syntax
git notes add -m "note" <commit>
git notes show <commit>
git log --show-notes
Common options
add -m <msg>Attach a note.append -m <msg>Add to an existing note.show <commit>Print a commit's note.remove <commit>Delete a note.--ref <name>Use a separate namespace, e.g. --ref=ci for build results.
Examples
Annotate a commit and see the note in the log.
git notes add -m "Reviewed by: Alex. Ship blocker resolved." a1b2c3d
git log --show-notes
Notes need explicit push and fetch refspecs to be shared.
git push origin refs/notes/*
git fetch origin 'refs/notes/*:refs/notes/*'
git format-patchSafeExport commits as .patch files you can email or archive.
git format-patchSafeTurns each commit in a range into a standalone file containing the diff plus the full author, date and message. It's how the Linux kernel and most mailing-list projects still accept contributions, and it's the cleanest way to move work between two repositories that share no remote.
Syntax
git format-patch <base>
git format-patch -1 <commit>
git format-patch main..feature
Common options
-<n>Export the last n commits, e.g. -3.--stdoutWrite one combined mbox to stdout instead of numbered files.-o <dir>Write the files into a directory.--cover-letterGenerate a summary letter for the series.-v2Mark this as version 2 of a patch series.
Examples
Export your last three commits as numbered .patch files.
git format-patch -3 -o /tmp/patches
Bundle a whole branch into one file to send elsewhere.
git format-patch main..feature --stdout > feature.mbox
git amCautionApply .patch files as real commits, preserving author and message.
git amCautionThe counterpart to format-patch: it replays each patch as a commit with its original author, date and message intact. This is the difference between `git am` and `git apply` - apply only changes files, while am reconstructs history. If a patch doesn't apply cleanly you get a normal conflict to resolve.
Syntax
git am <patch-file>
git am --abort
git am --continue
Common options
-3Fall back to a three-way merge when the patch doesn't apply cleanly. Almost always worth adding.--abortCancel and restore the original branch.--continue / --skipProceed after resolving a conflict, or skip this patch.--signoffAdd a Signed-off-by line.
Examples
Apply a whole patch series as commits, with three-way fallback.
git am -3 /tmp/patches/*.patch
Back out of a patch series that won't apply.
git am --abort
git applyCautionApply a diff to your working tree without creating a commit.
git applyCautionApplies patch text as plain file changes, leaving you to stage and commit however you like. Because it makes no commit and records no authorship, it's the right choice for applying a diff someone pasted at you, or for testing whether a patch still applies with `--check`.
Syntax
git apply <patch>
git apply --check <patch>
git apply --3way <patch>
Common options
--checkTest whether the patch would apply, changing nothing.--3way, -3Use a three-way merge on conflict instead of failing outright.--reverse, -RUn-apply a patch.--statSummarise what the patch would change.--exclude=<path>Skip parts of the patch.
Examples
Verify a patch applies, then apply it.
git apply --check fix.patch && git apply fix.patch
Park work in a file instead of a stash, then bring it back.
git diff > /tmp/wip.patch
git checkout .
# ... later ...
git apply /tmp/wip.patch
git send-emailSafeEmail a patch series directly from Git.
git send-emailSafeSends the output of format-patch as properly threaded plain-text email, which is still how the kernel, Git itself and many long-running open-source projects accept contributions. It handles the threading and In-Reply-To headers that make a patch series readable in a mail client.
Syntax
git send-email <patch-dir>
git send-email --to=<addr> HEAD~3..HEAD
Common options
--to / --ccRecipients.--annotateOpen each message in your editor before sending.--dry-runShow what would be sent.--composeWrite a cover letter interactively.
Examples
Send a reviewed patch series to a maintainer.
git send-email --to=maintainer@project.org --annotate /tmp/patches/*.patch
git request-pullSafeGenerate a pull request summary for a maintainer, without a hosting platform.
git request-pullSafeProduces the plain-text summary that GitHub's pull request UI replaced: what changed, by whom, and where to fetch it from. It predates and outlives any particular forge, and it's still the standard way to propose a merge on a mailing list.
Syntax
git request-pull <start> <url> [<end>]
Common options
<start>The commit the maintainer already has.<url>Where they should fetch from.<end>Your branch or tag; defaults to HEAD.
Examples
Produce a summary asking a maintainer to pull your branch.
git request-pull v1.2.0 https://github.com/you/repo.git feature/save-system
git bundleSafePack a repository (or part of it) into one file you can clone from.
git bundleSafeCreates a single file containing real Git objects that you can clone or fetch from exactly like a remote. It's the answer to moving a repository across an air gap, onto a USB stick, or through a system that only accepts file attachments - and unlike a zip of the folder, it preserves everything Git needs.
Syntax
git bundle create <file> <refs>
git clone <file> <dir>
git bundle verify <file>
Common options
create <file> --allBundle every ref.create <file> <since>..<until>Bundle only a range - an incremental update.verify <file>Check a bundle is valid and see what it requires.list-heads <file>Show which refs a bundle contains.
Examples
Move a full repository, history included, as one file.
git bundle create repo.bundle --all
# copy repo.bundle to the other machine
git clone repo.bundle myrepo
Create a small incremental bundle with just the last ten commits.
git bundle create update.bundle main~10..main
git archiveSafeExport a clean zip or tarball of your project at any commit.
git archiveSafeProduces an archive of the tracked files at a given commit, with no `.git` directory and none of your untracked junk. It's the correct way to build a release artifact or hand someone a snapshot, and it respects `export-ignore` rules in `.gitattributes` so you can exclude test fixtures from shipped archives.
Syntax
git archive --format=zip -o <file> <commit>
git archive HEAD | tar -x -C <dir>
Common options
--format=zip|tarOutput format.-o <file>Write to a file.--prefix=<dir>/Nest everything under a directory inside the archive.<commit>:<path>Archive only a subdirectory.
Examples
Build a clean release zip from a tag.
git archive --format=zip --prefix=myapp-1.0/ -o myapp-1.0.zip v1.0.0
Extract just one directory at HEAD somewhere else.
git archive HEAD:src | tar -x -C /tmp/src-snapshot
git rev-parseSafeTurn any revision expression into a concrete commit hash.
git rev-parseSafeThe translator between how humans name commits (`HEAD~3`, `main@{yesterday}`, `v1.0^{commit}`) and the 40-character hashes Git actually stores. It's the single most-used command inside shell scripts and hooks, because it also answers structural questions like 'where is the repo root?' and 'am I even inside a repository?'.
Syntax
git rev-parse HEAD
git rev-parse --short HEAD
git rev-parse --show-toplevel
Common options
--short[=n]Abbreviated hash.--abbrev-ref HEADThe current branch name.--show-toplevelAbsolute path to the repository root.--git-dirPath to the .git directory.--is-inside-work-treePrints true/false - the standard 'is this a repo?' check.--verifyFail if the revision doesn't resolve.
Examples
Print the current branch name - the basis of every shell prompt.
git rev-parse --abbrev-ref HEAD
Jump to the repository root from any subdirectory.
cd "$(git rev-parse --show-toplevel)"
Get a short hash to stamp into a build.
git rev-parse --short HEAD
git rev-listSafeList commit hashes matching a range or filter - the engine behind git log.
git rev-listSafeWhere `git log` formats commits for humans, rev-list emits raw hashes for scripts. It powers most repository analysis: counting commits, walking every object in history, or finding what one branch has that another doesn't. Almost every 'find the biggest files in my repo' recipe starts here.
Syntax
git rev-list HEAD
git rev-list --count HEAD
git rev-list --all --objects
Common options
--countJust the number of commits.--allStart from every ref.--objectsList every object, not just commits - blobs and trees included.--left-right <a>...<b>Show which side of a divergence each commit is on.--no-mergesExclude merge commits.
Examples
Total number of commits - a handy monotonic build number.
git rev-list --count HEAD
How many commits each side is ahead by.
git rev-list --left-right --count main...feature
The canonical recipe for finding the ten largest files in your entire history.
git rev-list --objects --all |
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' |
awk '/^blob/ {print $3, $4}' | sort -rn | head -10
git ls-filesSafeList files in the index, with precise filters for state.
git ls-filesSafeReports exactly which paths Git is tracking, and can filter by state - modified, deleted, ignored, untracked, or in conflict. It's far more reliable than parsing `git status` in a script, and `--unmerged` is the cleanest way to enumerate conflicted files during a merge.
Syntax
git ls-files
git ls-files --others --ignored --exclude-standard
git ls-files -u
Common options
-c, --cachedTracked files (the default).-o, --othersUntracked files.-i --exclude-standardCombined with -o, lists ignored files.-u, --unmergedFiles currently in conflict.-d, --deletedTracked files missing from disk.-s, --stageShow mode, hash and stage number.
Examples
List every file .gitignore is actually excluding.
git ls-files --others --ignored --exclude-standard
Enumerate conflicted files during a merge.
git ls-files -u | cut -f2 | sort -u
Count tracked files.
git ls-files | wc -l
git ls-treeSafeList the contents of a tree object at any commit.
git ls-treeSafeShows what files and directories existed at a given commit, along with their modes and blob hashes, without checking anything out. Use it to inspect an old directory listing, to spot a file-mode change, or to pull the hash of a specific historical file so you can extract it with cat-file.
Syntax
git ls-tree <commit>
git ls-tree -r <commit>
git ls-tree <commit> <path>
Common options
-rRecurse into subdirectories.--name-onlyJust the paths.-lInclude object sizes.-dShow only directories.
Examples
List every file that existed at the v1.0.0 tag.
git ls-tree -r --name-only v1.0.0
Find the five biggest files in the current commit.
git ls-tree -r -l HEAD | sort -k4 -n | tail -5
git hash-objectSafeCompute the Git hash of a file - and optionally store it as an object.
git hash-objectSafeShows you exactly how Git identifies content: hash a file and you get the same 40-character id Git would use. With `-w` it writes the object into the database, which is the low-level building block behind `git add`. In practice its best use is forensic - hashing a local file to check whether that exact content already exists somewhere in your history.
Syntax
git hash-object <file>
git hash-object -w <file>
git hash-object --stdin
Common options
-wWrite the object into .git/objects, not just compute the hash.--stdinRead the content from standard input.-t <type>Treat the input as blob, tree, commit or tag.
Examples
Compute the object id of a file's current contents.
git hash-object src/config.json
Check whether a local file's exact content already exists as an object.
H=$(git hash-object old-copy.cs)
git cat-file -t "$H" 2>/dev/null && echo "this exact content IS in the repo"
git for-each-refSafeQuery and format every branch, tag and ref in the repository.
git for-each-refSafeA scriptable, precisely formattable listing of every reference, with sorting and filtering that `git branch` and `git tag` only partly expose. It's how you build reports like 'every branch, its author and how stale it is' in a single command.
Syntax
git for-each-ref
git for-each-ref --format='<fmt>' <pattern>
Common options
--sort=<field>Sort by committerdate, refname, etc. Prefix with - to reverse.--format=<fmt>Fields like %(refname:short), %(authorname), %(committerdate:relative).--count=<n>Limit the number of results.--merged / --no-mergedFilter by merge state.
Examples
Every local branch with its age and author - the stale-branch audit.
git for-each-ref --sort=-committerdate refs/heads/ \
--format='%(committerdate:relative)%09%(authorname)%09%(refname:short)'
The five most recently created tags.
git for-each-ref --count=5 --sort=-creatordate refs/tags
git update-indexCautionManipulate the index directly - including telling Git to ignore changes to a tracked file.
git update-indexCautionMostly plumbing, but two flags get reached for constantly: `--skip-worktree`, which makes Git ignore local modifications to a tracked file (the correct way to keep a local config override), and `--assume-unchanged`, a performance hint that people misuse for the same purpose. Both hide real changes, so a forgotten flag looks exactly like a Git bug months later.
Syntax
git update-index --skip-worktree <file>
git update-index --no-skip-worktree <file>
Common options
--skip-worktreeIgnore local changes to a tracked file. Survives most operations - use this one.--no-skip-worktreeUndo it.--assume-unchangedA performance promise that you won't change the file. Not an ignore mechanism.--chmod=+xSet the executable bit on a tracked file, including on Windows.--refreshRe-stat files to clear phantom modifications.
Examples
Keep your own version of a tracked config file without ever committing it.
git update-index --skip-worktree config/local.json
Find every file you've marked skip-worktree - run this before blaming Git.
git ls-files -v | grep '^S'
Mark a script executable in the repo from Windows.
git update-index --chmod=+x scripts/build.sh
git symbolic-refCautionRead or set what a symbolic reference like HEAD points to.
git symbolic-refCautionHEAD is not a commit - it's a pointer to a branch, and this is the command that reads and writes that pointer. Its practical use is renaming a repository's default branch, particularly on a bare server repo where there's no working tree to check anything out into.
Syntax
git symbolic-ref HEAD
git symbolic-ref HEAD refs/heads/main
Common options
--shortPrint just the branch name.-dDelete the symbolic ref.
Examples
The current branch name, without the refs/heads/ prefix.
git symbolic-ref --short HEAD
Change the default branch of a bare repository from master to main.
git symbolic-ref HEAD refs/heads/main
git diff-treeSafeList what changed in a commit, in a script-friendly form.
git diff-treeSafeThe plumbing behind `git show`'s file list. Because its output is stable and easy to parse, it's what hooks and CI scripts use to answer 'which files did this push touch?' - typically to decide whether to run a particular test suite.
Syntax
git diff-tree --no-commit-id --name-only -r <commit>
Common options
-rRecurse into subdirectories.--no-commit-idOmit the commit hash line.--name-only / --name-statusPaths, optionally with status letters.-mAlso show diffs for merge commits.
Examples
List the files changed by the latest commit - the standard hook one-liner.
git diff-tree --no-commit-id --name-only -r HEAD
Run tests only when source files changed.
git diff-tree --no-commit-id --name-only -r HEAD | grep -q '^src/' && npm test
git gcDestructiveCompress the repository and delete unreachable objects.
git gcDestructivePacks loose objects together, prunes ones nothing references, and expires old reflog entries. Git runs it automatically now and then, which is exactly why recovery has a deadline - once gc prunes an unreachable commit, no amount of fsck will find it. Never run `--prune=now` while you're trying to recover something.
Syntax
git gc
git gc --aggressive
git gc --prune=now
Common options
--aggressiveRepack much more thoroughly. Slow; worth it occasionally after a big rewrite.--prune=<date>Delete unreachable objects older than a date. --prune=now deletes them all immediately.--autoOnly run if Git thinks it's needed. What the automatic invocation uses.--no-pruneRepack but keep unreachable objects.
Examples
Routine housekeeping - safe, keeps recent unreachable objects.
git gc
Actually shrink the repo after a history rewrite. Destroys all recovery options first.
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git pruneDestructiveDelete unreachable objects from the object database.
git pruneDestructiveThe narrow tool that `git gc` calls to remove objects nothing points to. You rarely run it directly, and doing so while a recovery is in progress is how a recoverable situation becomes permanent - `--dry-run` first, always.
Syntax
git prune --dry-run
git prune --expire <date>
Common options
-n, --dry-runReport what would be deleted.--expire <date>Only prune objects older than this.-vVerbose.
Examples
See which unreachable objects would be removed - and check none of them are yours.
git prune --dry-run -v
git repackCautionRebuild the pack files that store your objects efficiently.
git repackCautionGit stores objects either loose (one file each) or packed (many, delta-compressed, in one file). Repacking is what turns thousands of loose objects into a compact pack, and after a large history rewrite it's what actually reclaims the disk space.
Syntax
git repack -a -d
git repack -a -d --depth=250 --window=250
Common options
-aRepack everything into a single pack.-dDelete the now-redundant packs.--window / --depthTrade CPU time for a smaller result.-fRecompute deltas from scratch.
Examples
Maximum-effort repack - noticeably smaller, and slow on a big repo.
git repack -a -d --depth=250 --window=250
git count-objectsSafeReport how much disk space the repository is using.
git count-objectsSafeTells you how many loose and packed objects exist and how much space they occupy, in human-readable units with `-vH`. It's the first command to run when a clone feels absurdly slow or `.git` has quietly grown to several gigabytes.
Syntax
git count-objects -vH
Common options
-vVerbose breakdown.-HHuman-readable sizes.
Examples
See size-pack and count - the quickest diagnosis of repository bloat.
git count-objects -vH
git verify-packSafeInspect a pack file to find the largest objects inside it.
git verify-packSafeLists every object in a pack with its size, which is the fastest route to identifying what is actually making your repository huge. Sort its output by size, take the top few hashes, and `git rev-list --objects` will tell you their filenames.
Syntax
git verify-pack -v .git/objects/pack/*.idx
Common options
-vList every object with type and size.-sSummary only.
Examples
Find the ten biggest objects in the repository and print their filenames.
git verify-pack -v .git/objects/pack/*.idx |
sort -k 3 -n | tail -10 |
awk '{print $1}' |
while read h; do git rev-list --objects --all | grep "$h"; done
git maintenanceSafeSchedule background upkeep so big repositories stay fast.
git maintenanceSafeThe modern replacement for hoping `git gc --auto` runs at a convenient moment. It registers scheduled background tasks - prefetching, commit-graph writing, incremental repacking - that keep large repositories responsive without ever blocking a command you're waiting on.
Syntax
git maintenance start
git maintenance run --task=<task>
git maintenance stop
Common options
start / stopRegister or remove the background schedule.registerAdd this repo to the maintenance list without scheduling.run --task=commit-graph|prefetch|gc|incremental-repackRun one task now.
Examples
Turn on background maintenance for a large repository.
git maintenance start
Speed up log and blame immediately by writing the commit graph.
git maintenance run --task=commit-graph
git commit-graphSafeBuild an index that makes history traversal dramatically faster.
git commit-graphSafePrecomputes commit relationships into a single file so operations that walk history - `git log --graph`, `git merge-base`, branch ahead/behind counts - stop re-parsing thousands of commit objects. On a large repository it can turn a multi-second log into an instant one.
Syntax
git commit-graph write --reachable
git commit-graph verify
Common options
write --reachableBuild the graph from all refs.--changed-pathsAlso index changed paths, which speeds up file-scoped log enormously.verifyCheck the graph file is consistent.
Examples
Build the full graph, including the path index that accelerates git log -- <file>.
git commit-graph write --reachable --changed-paths
git fast-export / fast-importCautionStream an entire history out and back in - the basis of repo surgery.
git fast-export / fast-importCautionfast-export serialises history into a plain-text stream and fast-import reads it back, so piping one into the other through a filter lets you rewrite anything about a repository. It's how migration tools move projects between version control systems, and the mechanism `git filter-repo` is built on.
Syntax
git fast-export --all > repo.stream
git fast-import < repo.stream
Common options
--allExport every ref.-M -CDetect renames and copies.--signed-tags=stripDrop signatures that would be invalidated.
Examples
Clone a repository through a stream - the base of any custom rewrite.
git fast-export --all | git -C ../new-repo fast-import
git worktreeSafeCheck out several branches into separate folders from one repository.
git worktreeSafeA worktree gives you an additional working directory backed by the same object database, so you can have main and a feature branch open side by side without cloning twice or stashing constantly. It's ideal when a build takes a long time, or when you need to review a colleague's branch while your own work stays untouched.
Syntax
git worktree add <path> <branch>
git worktree list
git worktree remove <path>
Common options
add <path> <branch>Create a new working directory for an existing branch.add -b <new> <path>Create a new branch and a worktree for it at once.listShow all worktrees.remove <path>Delete a worktree cleanly.pruneClean up records for worktrees whose folders were deleted.
Examples
Open the hotfix branch in a sibling folder while you keep working in the main one.
git worktree add ../repo-hotfix hotfix/crash
Inspect an arbitrary commit in its own folder without disturbing anything.
git worktree add --detach ../repo-review a1b2c3d
git submoduleCautionEmbed another Git repository inside yours at a pinned commit.
git submoduleCautionA submodule records a specific commit of an external repo rather than copying its files into your history. Cloning a project with submodules gives you empty folders until you initialise them, which is the number one submodule confusion. Many teams prefer a package manager or subtree instead, because submodules add real day-to-day friction.
Syntax
git submodule add <url> <path>
git submodule update --init --recursive
git clone --recurse-submodules <url>
Common options
add <url> <path>Attach an external repo at a path.update --init --recursiveFetch and check out all submodules, including nested ones.update --remoteAdvance submodules to their latest upstream commit.foreach <cmd>Run a command in every submodule.deinit <path>Unregister a submodule.statusShow each submodule's checked-out commit.
Examples
Fix the classic 'my submodule folders are empty after cloning'.
git submodule update --init --recursive
Bring every submodule up to date on its main branch.
git submodule foreach 'git switch main && git pull'
git subtreeCautionVendor another repository into a subdirectory - without submodules' friction.
git subtreeCautionSubtree merges an external project's files directly into a folder of your repo, so anyone who clones you gets everything with no extra initialisation step. You can still pull upstream updates and even push changes back. The trade-off is a fatter history and slightly awkward commands, but for consumers it is dramatically simpler than a submodule.
Syntax
git subtree add --prefix=<dir> <repo> <ref> --squash
git subtree pull --prefix=<dir> <repo> <ref> --squash
Common options
add --prefix=<dir>Import a repository into a subdirectory.pull --prefix=<dir>Update it from upstream.push --prefix=<dir>Send your changes back upstream.--squashCollapse the imported history into a single commit. Usually what you want.split --prefix=<dir>Extract a subdirectory's history into its own branch.
Examples
Vendor a dependency in so clones just work, no submodule init required.
git subtree add --prefix=vendor/lib https://github.com/other/lib.git main --squash
Split a subdirectory out into its own branch, ready to become a separate repo.
git subtree split --prefix=packages/ui -b ui-only
git lfsCautionStore large binary files outside history and keep the repo small.
git lfsCautionGit stores a complete copy of every version of every file, which is fine for text and catastrophic for 200 MB textures, audio and 3D models. Git LFS replaces those files with small pointer files and keeps the real data on a separate server. Set it up before committing large assets - retrofitting LFS means rewriting history.
Syntax
git lfs install
git lfs track "*.psd"
git lfs ls-files
Common options
installSet up the LFS hooks in this repo (once per machine per repo).track "<pattern>"Route matching files through LFS and record it in .gitattributes.ls-filesList files currently managed by LFS.migrate import --include="*.psd"Move existing large files in history into LFS - rewrites history.pruneDelete local LFS files that are no longer needed.
Examples
Set up LFS for a game project's art assets before committing them.
git lfs install
git lfs track "*.psd"
git lfs track "*.fbx"
git add .gitattributes
git commit -m "Track binary assets with LFS"
Retrofit LFS onto a repo that already committed big binaries. Rewrites history.
git lfs migrate import --include="*.psd,*.fbx" --everything
.gitattributesSafePer-path rules for line endings, diffing, merging and export.
.gitattributesSafeA tracked file that tells Git how to treat particular paths - which is why, unlike your local config, its rules apply identically for everyone who clones. It's the correct fix for CRLF/LF churn, for marking binary files as unmergeable, and for teaching Git to produce readable diffs of unusual formats.
Syntax
# in .gitattributes
<pattern> <attribute>...
Common options
* text=autoNormalise line endings in the repository. The standard first line.*.png binaryShorthand for -text -diff: never try to merge or diff it.*.unity merge=unityyamlmergeRoute Unity scenes through a smart merge tool.*.md diff=markdownUse a language-aware diff driver for better hunk headers.tests/ export-ignoreExclude a path from git archive output.*.psd filter=lfs diff=lfs merge=lfs -textWhat git lfs track writes for you.
Examples
Kill line-ending noise across a mixed Windows/macOS team.
# .gitattributes
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
*.png binary
*.fbx binary
Apply new line-ending rules to files already in the repo.
git add .gitattributes
git commit -m "Normalise line endings"
git add --renormalize .
git commit -m "Renormalise existing files"
git hooksSafeRun your own scripts automatically at points in the Git lifecycle.
git hooksSafeHooks are executable scripts in `.git/hooks` that fire on events like `pre-commit`, `commit-msg` and `pre-push`. They're how projects enforce linting, formatting and commit-message conventions locally. They aren't cloned with the repo, so teams use a tool like Husky or set `core.hooksPath` to share them.
Syntax
git config core.hooksPath .githooks
chmod +x .githooks/pre-commit
git commit --no-verify
Common options
pre-commitRuns before a commit is created - the usual place for linting.commit-msgValidate or rewrite the commit message.pre-pushLast chance to block a push - run the test suite here.post-checkout / post-mergeReact to a branch change, e.g. reinstall dependencies.--no-verifySkip hooks for one command when you genuinely need to.
Examples
Share hooks with your team by committing them to a tracked folder.
git config core.hooksPath .githooks
A pre-commit hook that catches credentials before they enter history.
#!/bin/sh
# .githooks/pre-commit - block accidental secrets
if git diff --cached | grep -qE "(api[_-]?key|secret|password)\s*="; then
echo "Possible secret in staged changes. Use --no-verify to override."
exit 1
fi
git bugreportSafeCollect your Git version and environment into a ready-to-file report.
git bugreportSafeGenerates a text file with your Git version, platform details and configuration, prefilled with the questions maintainers always ask. Even if you never file a bug, it's a fast way to capture the environment details that explain why a command behaves differently on your machine than a colleague's.
Syntax
git bugreport
git bugreport -o <dir>
Common options
-o <dir>Where to write the report.-s <suffix>Custom filename suffix.
Examples
Produce a full environment report before asking for help.
git bugreport -o /tmp
Git commands FAQ
How many Git commands are there?
Git ships with well over 150 commands, but they split into 'porcelain' (the friendly ones you type daily) and 'plumbing' (low-level internals scripts are built from). In practice about a dozen cover most work, and this reference documents the 80-odd that are genuinely worth knowing.
Which Git commands should I learn first?
status, add, commit, push, pull, switch, branch, merge, log and diff will carry you through almost every normal day. Add restore and reset for undoing things, then reflog and fsck — not because you'll use them often, but because they're what get your work back when something goes wrong.
Which Git commands are dangerous?
The ones that can destroy uncommitted work: git reset --hard, git clean -fdx, git checkout -- <file> and git restore <file>. Rewriting commands like rebase, commit --amend and filter-repo are dangerous in a different way — they change history others may already have. Every command here carries a danger rating for exactly this reason.
What is the difference between porcelain and plumbing commands?
Porcelain commands (log, commit, merge, status) are the human-facing interface, and their output can change between versions. Plumbing commands (rev-parse, cat-file, ls-tree, rev-list) are stable, script-friendly internals — the porcelain is built on top of them, which is why they're what you reach for in hooks and CI.
What is git reflog and why does it matter so much?
It's a local journal of every position HEAD and each branch has held, kept for about 90 days — including states no branch points at any more. That makes it the single most valuable recovery command in Git: a bad reset, a botched rebase or a deleted branch are all still listed there and can be pointed at again.
What does git fsck do?
It walks every object in .git/objects and reports the ones nothing references. Dangling blobs are file contents from work that was staged but never committed; dangling commits come from deleted branches and dropped stashes. It's the deepest recovery layer Git has, below even the reflog.
What is the difference between git switch and git checkout?
git checkout does three unrelated jobs — change branches, restore files and detach HEAD — which is why a typo could silently destroy uncommitted work. Git 2.23 split it into git switch (branches only) and git restore (file contents only). Prefer the new pair; checkout still works and appears throughout older tutorials.
Can I create shortcuts for long Git commands?
Yes — git config --global alias.<name> '<command>'. For example alias.lg for a graph log, or alias.unstage for restore --staged. Prefix the value with ! to run an arbitrary shell command instead. Aliases are personal and never shared through the repository.
Do I need to memorise all of these?
No. Learn the dozen you use daily and bookmark the rest. The value of a reference like this is recognising that a command exists — knowing git bisect can find a bad commit automatically, or that git range-diff compares two versions of a branch, matters far more than remembering the exact flags.
Something actually broken?
Knowing the command is one thing — knowing which one to run at 2am is another. There are 56 step-by-step rescue guides for the situations these commands are meant to fix.
Browse the fix-it guides →