Skip to content
HARBAN AI LABS

Git & GitHub Mastery Β· Chapter 33

Cheat Sheets

Git 2.55.0. 🟒 reads or only adds Β· 🟑 moves refs, recoverable through the reflog Β· πŸ”΄ can destroy work or the safety net. Every command was run in this book's labs. Details: disaster-recovery playbook.

41 min read Β· Volume 4 of 4: Scale, professional practice, production, and reference

Source file: cheatsheets/git-cheat-sheet.md

Git cheat sheet

Baseline. Git 2.55.0. 🟒 SAFE reads state or only adds objects Β· 🟑 CAUTION moves refs or rewrites local history, recoverable through the reflog Β· πŸ”΄ DANGEROUS can destroy uncommitted work, remote history, or the safety net itself (Chapter 1, section 1.8). Every command is taught in the textbook; the number in brackets is the section. Full forms: Git command reference. The five answers for every 🟑 and πŸ”΄ command: command safety. When something is already lost: emergency recovery.

Before anything else: look

CommandPurposeExampleRiskCommon mistakeRecovery
git status -sbBranch, upstream, ahead and behind, changed paths (4.4)git status -sb🟒Reading "clean" as "equal to the commit": ignored files are not showngit status --ignored
git log --oneline --graph --decorate --allEvery ref as a graph (14A.9)git log --oneline --graph --decorate --all -20🟒Forgetting --all: the walk starts at HEAD onlyNot needed
git diff, git diff --cachedIndex against working tree; HEAD against index (5.4)git diff --cached🟒Committing without reading the staged diffNot needed
git reflogEvery commit HEAD was on, and the command that moved it (13.3)git reflog -10🟒Expecting one in a fresh clone or on CIThe clone where the work was done
git remote -v, git branch -vvWhere pushes go; which upstream each branch has (12.2, 12.5)git branch -vv🟒Trusting origin/<branch> without fetching: it is a cachegit fetch

Daily use

CommandPurposeExampleRiskCommon mistakeRecovery
git initCreates .git (1.9)git init shop🟒Running it in the wrong directoryRemove the stray .git if it holds nothing
git cloneNew repository with origin, remote-tracking refs and one checked-out branch (12.3)git clone <url>🟒--depth 1 where history is neededgit fetch --unshallow
git add <path>Stores content as a blob and points the index entry at it (5.3)git add src/router.py🟒Editing after git add: the commit lacks the editgit add again
git add -pStages hunk by hunk (5.5)git add -p🟒Following it with git commit -agit reset HEAD~1 🟑 while private, stage again
git commit -mNew commit from the index; moves the current branch (6.3)git commit -m "Add rate limits"🟒A missing file: it was never stagedA further commit
git commit --amendReplaces the tip commit with a new one (6.7)git commit --amend --no-edit🟑Amending a commit that is already pushedgit reset --soft 'HEAD@{1}'
git restore --staged <path>Unstages: index entry copied from HEAD (5.9)git restore --staged notes.txt🟑Using git rm --cached, which stages a deletiongit add <path>
git restore <path>Overwrites the file from the index (4.7)git restore -p src/router.pyπŸ”΄Not reading git diff -- <path> firstNone for content never staged
git rm --cached <path>Stops tracking; the file stays on disk (4.6)git rm --cached .env🟑Forgetting that the commit deletes the file for everyone who pullsgit restore --staged <path> before committing
git mv <a> <b>Renames the file and its index entry (4.8)git mv old.py new.py🟒Renaming and editing in one commit: history of the file stops at the renameRename and edit in separate commits
git stash push -uParks index and working tree as commits under refs/stash (11.11)git stash push -u -m "wip retry"🟑Leaving long-lived work in the stashgit stash pop --index
git stash popApplies the newest entry and drops it (11.11)git stash pop --index🟑Popping onto local edits: conflict, entry keptgit reset --merge, then git stash branch <name>
git switch <branch>Moves HEAD, index and working tree; refuses to lose edits (7.6)git switch -c feature/streaming🟒Committing on a detached HEAD and leavinggit branch <name> <id> from git reflog
git config get, listReads configuration with its origin (14B.3; Git 2.46 or later)git config list --show-origin --show-scope🟒Looking in one scope onlyNot needed
git config setWrites one configuration file (14B.3; Git 2.46 or later)git config set --global pull.ff only🟑A mistyped key is stored and ignoredgit config unset

History

CommandPurposeExampleRiskCommon mistakeRecovery
git log <A>..<B>Commits reachable from B and not from A (14A.8)git log --oneline origin/main..HEAD🟒Confusing it with git diff A..B, which compares two tipsNot needed
git log <A>...<B> --left-rightCommits on either side and not both (14A.14)git log --oneline --left-right main...feature/x🟒Reading three dots in log like three dots in diffNot needed
git diff <A>...<B>Merge base against B: what a branch adds (14A.2)git diff --stat main...feature/x🟒Two dots for reviewNot needed
git show <commit>One commit with its diff; a file at a revision (3.5)git show HEAD~1:config/limits.yaml🟒Reading a merge's combined diff as the whole mergegit show --remerge-diff <merge>
git log -S<string>Commits that changed the number of occurrences (14A.11)git log -S'PASS_MARK' --oneline🟒Taking the hit for the cause without reading the patchCheck with -G and git show
git log -LHistory of a range of lines or a function (14A.12)git log -L :score:scoring/metrics.py🟒Anchoring on line numbersA /regex/ or :funcname range
git log --follow -- <path>History of one file across renames (14A.10)git log --follow --oneline -- src/app.py🟒Expecting it to survive a rename plus rewriteRead on under the old path
git blameThe commit that last changed each line (14A.16)git blame -w -C -L 10,30 <file>🟒Stopping at a formatting commit--ignore-rev, --ignore-revs-file
git shortlog -sn <rev>Commit counts per author (14A.15)git shortlog -sn --no-merges HEAD🟒Omitting the revision in a script: it reads standard inputPass the revision
git describeVersion string from the nearest tag (14B.12)git describe --tags --dirty🟒Shallow clone without tagsgit fetch --unshallow --tags
git range-diffCompares two versions of a commit series (9.14)git range-diff main ORIG_HEAD HEAD🟒Pushing a rebase without itNot needed

Branches and integration

CommandPurposeExampleRiskCommon mistakeRecovery
git branch <name> [<start>]Adds one ref (7.5)git branch rescue/before-rebase🟒Branching on an unborn branchMake the first commit
git branch -dDeletes a merged branch and its reflog (7.5)git branch -d feature/x🟑Reading the refusal after a squash merge as a buggit branch <name> <id> from the was <id> line
git branch -DDeletes with no merge check (7.5)git branch -D feature/xπŸ”΄Deleting the only name of unmerged commitsHEAD reflog, while the objects exist
git merge <other>Fast-forward or three-way merge into the current branch (8.3, 8.4)git merge --no-ff feature/x🟑Whole-file --ours or --theirs in a conflictgit reset --merge ORIG_HEAD; pushed: git revert -m 1
git merge --abortBack to the state before the merge (8.10)git merge --abortπŸ”΄Losing resolutions and edits staged during the mergegit fsck for staged content
git merge-tree --write-treeA test merge without touching anything (8.17)git merge-tree --write-tree --name-only HEAD feature/x🟒The deprecated three-argument formNot needed
git rebase <upstream>Re-creates your commits on top of the upstream; new IDs (9.2)git rebase origin/main🟑Rebasing a branch others push togit reset --hard <branch>@{1} πŸ”΄
git rebase -iReorder, squash, edit, drop commits (9.6)git rebase -i --autosquash origin/main🟑Deleting a line: the commit is dropped silentlySame reset; rebase.missingCommitsCheck=error
git rebase --onto <new> <old>Replays only the commits after <old> (9.5)git rebase --onto main feature/base🟑The wrong boundary: conflicts in files you never changedgit rebase --abort
git rebase --continue, --abortGo on after resolving; or give up (9.11)git rebase --continue🟑--amend at a conflict stop; forgetting that "ours" is the new baseReflog of the branch
git cherry-pick -x <commit>Copies one change as a new commit (10.2)git cherry-pick -x <id>🟑No -x on a backportgit cherry-pick --abort; pushed: git revert
git tag -a <name>Annotated tag: a tag object plus a ref (14B.8)git tag -a v1.0.0 -m "1.0.0"🟒Moving it after publishinggit tag -d <name> while unpublished
git worktree addA second working tree on the same repository (25.2)git worktree add ../hotfix release/1.2🟒Removing it with rm -rfgit worktree prune

Remotes

CommandPurposeExampleRiskCommon mistakeRecovery
git fetchUpdates remote-tracking refs; never your branches (12.4)git fetch origin🟒Believing it changed your branchNot needed
git fetch --pruneAlso deletes stale remote-tracking refs and their reflogs (12.11)git fetch --prune🟑Pruning the last name of commits you still needgit fsck --lost-found, then git branch <name> <id>
git pull --ff-onlyFetch, then fast-forward or stop (12.6)git pull --ff-only🟑Plain git pull on diverged branches without a setting: fatalgit reset --hard ORIG_HEAD πŸ”΄, at once
git pull --rebaseFetch, then re-create your unpushed commits (9.13)git pull --rebase🟑After someone's forced push it can drop commitsgit reset --hard <branch>@{1} πŸ”΄
git push -u origin <branch>Publishes the branch and sets its upstream (12.5)git push -u origin feature/x🟑Everything up-to-date while the commit is on another branchPush the branch that holds it
git push --dry-runShows <source> -> <destination> without sending (12.7)git push --dry-run🟒Skipping it before a push that mattersNot needed
git push --force-with-lease --force-if-includesReplaces the server's branch only if it is what you last integrated (12.8)git push --force-with-lease --force-if-includesπŸ”΄Using it on a branch others push toThe old tip from your reflog, pushed back
git push --forceReplaces the server's branch unconditionally (12.8)none: use the guarded formπŸ”΄Answering "non-fast-forward" with itA clone that still has the old tip
git push origin --delete <branch>Deletes a ref on the server (12.7)git push origin --delete feature/xπŸ”΄Deleting a branch with unmerged commitsgit push origin <id>:refs/heads/<name>
git push origin <tag>Publishes one tag (14B.10)git push origin v1.0.0🟑--tags, which publishes every local tagNone that is clean
git ls-remoteWhat the server has now (12.4)git ls-remote --tags origin🟒Deciding from stale remote-tracking refsNot needed
git remote set-urlChanges where a remote points (12.2)git remote set-url origin <url>🟑A token inside the URLSet the previous URL; revoke the token
git submodule update --init --recursiveChecks out the recorded submodule commits (23.3)git submodule update --init --recursive🟑Work on the detached HEAD vanishes at the next updategit -C <path> branch <name> <id>

Undo

SituationCommandExampleRiskCommon mistakeRecovery
Unstage a filegit restore --staged <path> (5.9)git restore --staged notes.txt🟑git reset --hard insteadgit add <path>
Discard edits to a filegit restore <path> (4.7)git restore -p config.yamlπŸ”΄No git diff firstNone unless staged once
Get one file from another commitgit restore --source=<commit> <path> (11.3)git restore --source=HEAD~2 -- config.yamlπŸ”΄A . as the path: deletes files the commit lacksgit restore . for the index versions
Fix the last commit, unpushedgit commit --amend (11.7)git commit --amend🟑Unrelated staged changes are swallowedgit reset --soft 'HEAD@{1}'
Take back commits, keep the changesgit reset --soft <commit> or git reset <commit> (11.4)git reset --soft HEAD~1🟑Doing it to pushed commitsgit reset --soft ORIG_HEAD, at once
Move the branch, keep local edits safegit reset --keep <commit> (11.6)git reset --keep origin/main🟑Reaching for --hardORIG_HEAD, the reflog
Throw away commits and editsgit reset --hard <commit> (11.5)git reset --hard origin/mainπŸ”΄Uncommitted work in the treeCommits: reflog. Staged: git fsck --lost-found. Unstaged: none
Undo a pushed commitgit revert <commit> (11.8)git revert --no-edit <id>🟑Resetting and force-pushing insteadRevert the revert
Undo a pushed mergegit revert -m 1 <merge> (11.9)git revert -m 1 <id>🟑Merging the same branch again later: its changes are missingRevert the revert, then merge
Remove untracked filesgit clean -n, then git clean -f (4.14)git clean -n -dπŸ”΄-x in a repository with ignored dataNone through Git
Leave a merge, rebase or pick--abort (29.6)git rebase --abort🟑 (merge: πŸ”΄)Aborting before anchoring new commitsHEAD reflog

Recovery

SituationCommandExampleRiskCommon mistakeRecovery
Name a state before touching anythinggit branch rescue/<what> <id> (13.7)git branch rescue/before-fix🟒A second fix before the anchorgit branch -D when done
Find where a branch wasgit reflog show <branch> (13.3)git reflog show main🟒Using ORIG_HEAD later than the next commandThe branch reflog
Commits lost by reset, amend or rebasegit reset --keep <id> or git cherry-pick <range> (13.8)git reset --keep 'main@{1}'🟑--hard during recoveryORIG_HEAD
A deleted branchgit branch <name> <id> (13.8)git branch feature/x <id>🟒Looking for the deleted branch's reflogHEAD reflog; git fsck --no-reflogs
Staged content that was overwrittengit fsck --lost-found (13.9)git fsck --lost-found🟒Running git gc --prune=now firstgit cat-file -p <id> > <path>
A dropped stashgit stash store -m <message> <id> (13.9)git fsck --unreachable to find the ID🟒git stash clearWhile the objects exist
A teammate force-pushedgit reflog show origin/<branch> (13.10)git branch rescue/server-before origin/main@{1}🟒Pulling firstPush the old tip back, with a lease
A backup that can travelgit bundle create <file> --all (13.14)git bundle verify repo.bundle🟒Expecting reflogs or uncommitted files in itcp -Rp of the repository for those
The point of no returngit reflog expire --expire=now --all, git gc --prune=now (13.13)none: only at the end of a secret clean-upπŸ”΄Running them as "maintenance"None

Debugging

CommandPurposeExampleRiskCommon mistakeRecovery
git bisect start, good, badBinary search for the commit that changed a property (14A.20)git bisect start HEAD v1.2.0🟑A dirty tree; a verdict on a commit that does not buildgit bisect reset
git bisect run <command>Lets a script give the verdicts; exit 125 skips (14A.21)git bisect run ./test.sh🟑A script that fails for other reasons than the buggit bisect log, edit, git bisect replay
git check-ignore -vThe pattern that ignores a path (4.5)git check-ignore -v build/out.log🟒Asking about a tracked file--no-index
git ls-files --stageThe index, entry by entry; stages 1 to 3 in a conflict (5.11)git ls-files -u🟒Missing the lower-case tag of -v for assume-unchangedgit update-index --no-assume-unchanged
git rev-parseA name as an object ID; facts about the repository (3.6)git rev-parse --abbrev-ref HEAD🟒Reading .git files in scriptsNot needed
git cat-file -p, -tContent and type of any object (3.5)git cat-file -p HEAD🟒Assuming 40-digit IDsNot needed
git config list --show-origin --show-scopeEvery setting and the file it comes from (14B.2)git config get --show-origin user.email🟒Forgetting includes and environment variablesgit var GIT_AUTHOR_IDENT
GIT_TRACE=1 git <command>What Git actually ran (14B.7)GIT_TRACE=1 git st🟒Pasting a trace that holds credentialsNot needed
git diff --checkConflict markers and whitespace errors (14A.6)git diff --cached --check🟒Committing a merge without itA new commit that removes the markers
git fsckIntegrity of the object database (3.8)git fsck🟒Reading dangling as damageNot needed

Advanced

CommandPurposeExampleRiskCommon mistakeRecovery
git commit --fixup=<commit> with git rebase --autosquashA correction that finds its target at the next rebase (9.7)git commit --fixup=<id>🟒, then 🟑Forgetting the rebase before mergingFold it in, or drop it
git rebase --update-refsMoves every local branch in the range: stacks stay stacked (9.9; Git 2.38 or later)git rebase --update-refs main🟑It moves branches you did not nameEach branch from its reflog
git rerereReplays recorded conflict resolutions (14C.3)git rerere diff🟒, 🟑 for forgetA wrong resolution is replayed toogit rerere forget <path>, git restore --merge <path> πŸ”΄
git add --renormalize .Restages every file under the current attributes (14C.5)git add --renormalize .🟑Mixing it with other changesgit restore --staged .
git hook list <event>Which hooks would run, and from which scope (14C.11)git hook list --show-scope pre-commit🟒Treating hooks as enforcementEnforce on the server
git sparse-checkout setLimits the working tree to a cone of directories (24.4)git sparse-checkout set services/gateway🟑Ignored files in directories that leave the cone are deletedNone for those files
git clone --filter=blob:noneA blobless partial clone: full history, blobs on demand (26.12)git clone --filter=blob:none <url>🟒Going offline without git backfillReconnect
git maintenance runPacks, commit-graph, multi-pack-index (26.3)git maintenance run --task=commit-graph🟑Running git gc beside itNone for expired reflog entries
git gc --prune=nowDeletes all unreachable objects at once (13.13)none: see RecoveryπŸ”΄"It fixes a slow repository"None in this repository
git worktree removeDeletes a clean working tree and its HEAD reflog (25.6)git worktree remove ../hotfix🟑 (πŸ”΄ with --force)Detached commits made there lose their namegit fsck --lost-found
git lfs track, git lfs migrate infoWhich paths become pointers; what a migration would rewrite (22.4, 22.9)git lfs migrate info --above=1mb🟒Tracking after the file is in historygit lfs migrate import on unpushed commits 🟑
git history rewordRewrites one commit and moves descendant branches (14D.6; experimental, Git 2.54 or later)git history reword --dry-run HEAD~2🟑Using it on published commitsEach branch from its reflog
git update-ref <ref> <new> [<old>]Writes one ref and nothing else (7.2)git update-ref refs/backup/main <id>🟑 (πŸ”΄ with -d)Moving the current branch with itgit update-ref <ref> "<ref>@{1}"

Emergency recovery, one page

Source file: cheatsheets/emergency-recovery-one-page.md

Emergency recovery: one page

Git 2.55.0. 🟒 reads or only adds Β· 🟑 moves refs, recoverable through the reflog Β· πŸ”΄ can destroy work or the safety net. Every command was run in this book's labs. Details: disaster-recovery playbook.

Stop. No git gc, git prune, git clean, git stash clear, git fetch --prune, re-clone or second fix until the lost state has a name.

Look (always first)

CommandShows
git status -sb🟒branch, upstream, ahead and behind, operation in progress
git reflog show <branch>🟒every value the branch had, and the command that set it
git reflog🟒every commit HEAD was on (survives branch deletion)
git reflog show origin/<branch>🟒the server's values as this clone saw them; @{1} is the one before the last change
git ls-remote origin🟒what the server holds now
git fsck --lost-found🟒dangling commits and blobs; copies in .git/lost-found/

Anchor (before anything moves)

Command
git branch rescue/<what> <ID>🟒a named commit cannot expire

Recover

DisasterCommand
Reset, amend or rebase took commits awaygit reset --keep rescue/<what>🟑
The same, but new commits were made sincegit cherry-pick <branch>..rescue/<what>🟑
Branch deleted (Deleted branch x (was <ID>))git branch <name> <ID>🟒
Rebase in progress, going wronggit rebase --abort🟑
Merge in progress, going wronggit merge --abort (discards the resolutions made so far)πŸ”΄
Bad merge committed, not pushedgit reset --keep ORIG_HEAD🟑
Bad commit on a shared branchgit revert --no-edit <ID>🟑
Bad merge on a shared branchgit revert -m 1 <merge> (a later re-merge then brings nothing)🟑
Staged file overwrittengit cat-file -p <blob ID> > <path>🟑
Stash droppedgit stash store -m 'recovered' <ID>🟒
Server branch force-pushed: keep the newcomersgit push origin origin/<branch>:refs/heads/<new-name>🟒
Server branch force-pushed: restoregit push --force-with-lease=<branch>:<bad ID> origin rescue/<what>:<branch>πŸ”΄
Server branch deletedgit push origin <name>🟒
My clone after the server was restoredgit cherry -v origin/<branch> <branch>, then git reset --keep origin/<branch>🟒 🟑
Corrupt indexmv .git/index .git/index.corrupt && git reset🟑
Missing or corrupt objectsgit fetch --refetch origin🟑
Secret committedRotate the credential first. Then the playbook, page 11

Verify

CommandProves
git range-diff <base> <old> <new>🟒= on every line: the same commits came back
git diff --stat <old> <new>🟒no output: identical content
git merge-base --is-ancestor <ID> <branch>🟒exit 0: the commit is on the branch
git ls-remote origin <branch>🟒the server has the ID you intended

Never in an emergency

CommandWhy
git reset --hardπŸ”΄destroys uncommitted changes; use --keep
git push --forceπŸ”΄no condition; use --force-with-lease=<branch>:<ID>
git reflog expire --expire=now --all, git gc --prune=nowπŸ”΄deletes the safety net itself
git clean -f -dπŸ”΄deletes untracked files; no undo

Command safety

Source file: reference/command-safety.md

Command safety: every 🟑 and πŸ”΄ command of the book

Baseline. Git 2.55.0 on macOS; GitHub CLI 2.88.1; GitHub facts as of 1 October 2026. This file is compiled from the "Command safety" tables and the inline risk labels of Chapters 1 to 30. It adds nothing to them. The section number in the last column is where the textbook answers the questions in full.

1. How to read this file

The three labels are defined in Chapter 1, section 1.8, and they describe the worst case:

LabelMeaningExamples from 1.8
🟒 SAFEReads state or only adds objects. Nothing is lostgit status, git log, git fetch, git reflog, git commit
🟑 CAUTIONMoves refs or rewrites local history. Recoverable through the reflog if you know howgit reset --soft, git rebase, git commit --amend
πŸ”΄ DANGEROUSCan destroy uncommitted work, remote history, or the safety net itselfgit reset --hard, git clean -fd, git push --force and its conditional forms --force-with-lease and --force-if-includes, git reflog expire --expire=now --all, git gc --prune=now

Before any πŸ”΄ command the book answers five questions: what it changes, what it can destroy, how to preview it, how to recover, when it is appropriate. The tables below give those five answers for every πŸ”΄ and every 🟑 command. 🟒 commands are not listed here; they are in the Git command reference.

Three rules follow from the tables and are worth knowing before you read them:

  1. The preview is always a 🟒 command. Run it, read every line, then decide.
  2. "Reflog" in the Recover column means a local reflog. A fresh clone and a CI runner have none (Chapter 13, section 13.16), tags and remote-tracking refs have none by default (Chapter 7, section 7.11), and the lab configuration switches reflog expiry off while real repositories expire entries after 90 days, or 30 days when unreachable (Chapter 13, section 13.4).
  3. Content that was never staged and never committed is in no object. No reflog and no git fsck returns it (Chapter 13, section 13.12). That is the common reason for a πŸ”΄ label.

Earlier drafts of the chapters labelled a few commands in two ways. One label per command has since been chosen, and section 12 records every decision and its reason.

2. Working tree and index

CommandLabelWhat it changesWhat it can destroyPreviewRecoverWhen appropriateSection
git restore <path>, git restore -p <path>, git checkout -- <path>πŸ”΄Working tree files, from the indexEvery unstaged change in the named paths, without confirmationgit diff -- <path>None for content that was never staged; git fsck for content that wasAfter you have read the diff and decided the changes are worthless; -p to discard hunk by hunk4.7, 11.3
git restore --source=<commit> [--staged] [--worktree] <path>πŸ”΄Files (and index entries) from a commit; deletes tracked paths the commit lacksUnstaged edits in those pathsgit status --short -- <path>; git diff <commit> -- <path>None for unstaged editsBringing back a deleted file, where no file of that name exists; name the paths you mean4.7, 11.3, 14A.13
git restore <path> on a file with the assume-unchanged bitπŸ”΄Overwrites the hidden edit without warningThe edit the bit was hidingClear the bit, then git diffNoneNever as a way to keep local edits: the bits are not an ignore mechanism5.12
git restore <notebook>, git switch across a notebook change, with a clean filter that strips outputsπŸ”΄ for local outputsRewrites the file from the stripped blobNotebook outputs, which were never in Gitgit status; copy the file firstNone; rerun the notebookWhen the working copy holds nothing that exists only there28.18
git clean -f [-d] [-X or -x]πŸ”΄Deletes untracked files; with -d untracked directories; with -x or -X ignored filesFiles Git has no copy of: .env, downloaded datasets, model checkpoints, virtual environmentsThe same options with -n in place of -fNone through GitTo prove a build from a pristine tree or reset a CI workspace; prefer -X to -x, pass a pathspec, protect files with -e4.14, 11.10
git switch --discard-changes, git checkout -fπŸ”΄Index and working tree, to the targetUncommitted edits in the files it touchesgit status, git diffNone for content that was never stagedOnly when you have decided to throw the edits away7.15
git switch <branch> when an ignored file is tracked on that branchπŸ”΄Overwrites the ignored fileThe ignored file's contentgit ls-tree -r --name-only <branch> compared with git status --ignoredNone; prevent with --no-overwrite-ignoreNot a choice: know the case before switching4.17
git rm -f <path>πŸ”΄As git rm, without the checkUncommitted changes in the filegit diff HEAD -- <path>None for unstaged contentOnly after reading that diff4.8
git rm <path>🟑Deletes the file and its index entry; refuses if the content is not in HEADNothing: the content is in HEADgit rm -n <path>git restore --staged --worktree <path>Removing a tracked file from the project4.8
git rm --cached <path>🟑Removes the index entry; the file on disk staysThe next commit deletes the path for everyone who pullsgit rm --cached -n <path>; git statusgit restore --staged <path> before committingA file that was tracked by mistake; announce the commit with the restore command4.6, 5.9, 21B.10, 28.9
git restore --staged <path>, git reset [--] <path>🟑Index entries, copied from HEADThe name of a staged version that differed from the filegit diff --cached -- <path>git add <path>; git fsck --lost-found for a staged version that differed from the fileUnstaging5.9, 11.3
git update-index --add --cacheinfo <mode>,<id>,<path>🟑One index entryNothinggit ls-files --stagegit restore --staged <path>Building a commit by hand to learn the plumbing2.6
git update-index --assume-unchanged, --skip-worktree🟑A flag bit that hides later edits from git statusNothing at once; sets up the πŸ”΄ row abovegit ls-files -vThe --no- formsNot as an ignore mechanism5.12
git add --renormalize .🟑Stages every tracked file whose stored form changes under the current attributes and filtersNothinggit ls-files --eol; git status before committinggit restore --staged . before committingOne renormalizing commit when attributes or filters change14C.5, 20B.18, 28.18
git apply <patch>🟑Working tree files (and the index with --index)Nothing that the reverse patch does not restoregit apply --check, git apply --statgit apply -RApplying a diff that is not a commit14D.11, 28.18
rm .git/index.lock🟑Removes a lock; unsafe while a Git process is writingA write in progresspgrep -fl gitNone needed for a stale lockOnly when no Git process is running5.15
rm .git/index, then git resetπŸ”΄Discards staging state, flag bits and conflict stages; outside Git's lockingWhat was staged and not committed loses its index entrygit diff --cached; move the file aside instead of deleting itgit fsck for blobs that were staged; move the file back"index file corrupt", "index file smaller than expected"5.15, 13.18

3. Stash

CommandLabelWhat it changesWhat it can destroyPreviewRecoverWhen appropriateSection
git stash push [-u]🟑Records index and working tree as commits that only refs/stash reaches, then resets both to HEAD; with -u also removes untracked filesNothing while the entry existsgit status -sgit stash pop --indexParking work for minutes; a branch is better for work that must survive11.11, 14C.2
git stash apply, git stash pop, git stash branch <name>🟑Merges an entry into the files; can conflictNothing: the entry is kept after a conflictgit stash show -pgit reset --merge backs out; then git stash branch <name>Pop soon; clear local edits before popping11.11
git stash drop, git stash clearπŸ”΄Deletes one line, or all lines, of the log of refs/stashThe stashed changes, once the unreachable commits are prunedgit stash show -p 'stash@{n}'git stash store <id> while the objects exist; without the ID, the git fsck --unreachable recipe of Lab 8.6drop after git stash show -p; clear is almost never appropriate11.11, 14C.15

4. Commits, branches and refs

CommandLabelWhat it changesWhat it can destroyPreviewRecoverWhen appropriateSection
git commit --amend (with or without --no-edit, --only, --reset-author)🟑Replaces the tip commit with a new one that has a new IDNothing locally; a published commit that others have is not replaced for themgit diff --cached, git log -1git reset --soft 'HEAD@{1}'Unpublished commits only6.7, 11.7
git commit --no-verify🟑As git commit, without the pre-commit and commit-msg hooksNothing; the checks did not runRun the hook's checks by handAmend, or a further commit; the server may still refuseWhen a hook is wrong and you have run its checks yourself6.15, 14C.11, 28.18
git branch -m <name>🟑Renames the ref, its reflog and its configuration sectionNothinggit branch -vvRename it backBefore the first push7.5
git branch -f <name> <commit>, git switch -C🟑Moves a ref; the old value stays in the reflogNothing while the reflog entry livesgit branch -vv, git rev-parse <ref>git branch -f <ref> <ref>@{1}Putting a branch where you have decided it belongs7.5
git branch -d <name>🟑Deletes the ref and its reflog after a merge check against the upstream or HEADThe branch refloggit branch --merged, git branch -vvgit branch <name> <id> from the was <id> output or from git reflogMerged branches7.5, 27.21
git branch -D <name>πŸ”΄Deletes the ref and its reflog with no merge checkThe only name of unmerged commitsgit log --oneline main..<branch>; git diff main <branch>git branch <name> <id> while the objects exist; the HEAD reflog if the commits were checked outAfter the diff against the target is empty (a squash-merged branch)7.5, 13.8, 17.9, 30.23
git tag -f <name>, git tag -d <name>🟑Moves or deletes a local tag; tags have no reflog by defaultThe only record of where the tag pointedgit rev-parse <tag>, and write the ID downgit tag <name> <old-id> from the was <id> output; git fsck lists a dangling tag objectUnpublished tags7.11, 14B.21
git update-ref <ref> <new> [<old>]🟑One ref and its reflog, and nothing else: not the index, not the working treeNothing while the reflog entry livesgit rev-parse <ref>; pass <old> so that a surprise stops the commandgit update-ref <ref> "<ref>@{1}"Scripts; full names that begin with refs/2.6, 3.17, 7.2
git update-ref -d <ref>πŸ”΄Deletes the ref and its reflog, even for the current branchThe only name that leads to those commitsgit rev-parse <ref>; write the ID downgit update-ref <ref> <id>; without the ID, git fsck --no-reflogs lists the commits as dangling while they existScripts that remove refs they created themselves2.14
git symbolic-ref HEAD <ref>🟑Which branch is current, without touching the index or the working treeNothinggit symbolic-ref HEADgit symbolic-ref HEAD <previous>; git reflogRepairs and demonstrations; porcelain otherwise3.17, 7.3
Writing a file under .git/refs/ by handπŸ”΄A ref without validation, locking or reflogA typo produces a broken ref that git log --all refuses to readUse git update-ref insteadgit update-ref after deleting the broken file (Lab 4.1)Never7.15
rm of a file under .git/refsπŸ”΄Removes a ref outside Git's lockingThe ref's valueMove the file aside instead of deleting itMove it backAn empty or unreadable ref file that git update-ref -d cannot delete13.11, 13.18
Deleting or editing files under .git/objectsπŸ”΄The object databaseOne object can be part of every snapshot in the historyNoneWrite the same content back, or copy the object from another clone (Lab 1.2)Never2.14
rm -rf .gitπŸ”΄Deletes the repository and leaves the working tree filesEvery commit that was never pushed, every local branch, stash and refloggit log --oneline --all, git branch -vvNone through Git; another clone or a disk backupA repository created by mistake that holds no commit you need1.14

5. Reset

CommandLabelWhat it changesWhat it can destroyPreviewRecoverWhen appropriateSection
git reset --soft <commit>🟑The branch refNothinggit log --oneline <commit>..HEADThe same mode with ORIG_HEAD, or the reflogRegrouping private commits; git reset --soft @{u} after a rejected push of an amend11.4, 21B.23
git reset [--mixed] <commit>🟑The branch ref and the indexNothing in files; staged versions lose their index entryAs above, and git diff --cachedAs aboveWhile the commits are private11.4
git reset --keep <commit>🟑The branch ref, the index, files that differ between the two commits; refuses when it would overwrite a local changeNothinggit log --oneline <commit>..HEADORIG_HEAD, the branch reflogThe reset to use during recovery, in place of --hard11.6, 13.7, 30.23
git reset --merge [<commit>]πŸ”΄As --keep for unstaged changes; discards staged changesStaged changesgit diff --cachedCommits: reflog. Staged content: git fsck --lost-foundBacking out of a merge, a pop or a pick that left the index unmerged8.21, 11.6
git reset --hard <commit>, git reset --hard ORIG_HEADπŸ”΄The branch ref, the index, and every tracked file that differs from the targetEvery uncommitted change to tracked files, and untracked files that are in the target's waygit status -s, git diff HEAD --stat, git log --oneline <commit>..HEADCommits: reflog. Staged content: git fsck --lost-found. Unstaged: noneWhen the working tree is known to be disposable; git stash push -u first when unsure; ORIG_HEAD only as the very next command9.16, 11.5, 13.7

6. Merge, rebase, cherry-pick, revert, bisect

CommandLabelWhat it changesWhat it can destroyPreviewRecoverWhen appropriateSection
git merge <other> (fast-forward or true merge), --no-ff, --ff-only🟑Moves the current branch; rewrites index and working treeNothing permanentlygit merge-tree --write-tree --name-only HEAD <other>; git diff HEAD...<other>; git log HEAD..<other>Before pushing: git reset --merge ORIG_HEAD or git reset --keep ORIG_HEAD (check it first, or take the commit from the reflog). After pushing: git revert -m 1From a clean tree8.3, 8.4, 8.12, 27.21
git merge -X ours, -X theirs, -X ignore-space-change🟑As above, and silently drops one side of every conflicting hunkThe dropped side's changes, in the resultMerge without the option firstAs above; audit with --remerge-diffOnly when one side's hunks are the decision8.6
git merge -s ours <other>🟑Records <other> as merged and takes none of its contentNothing in objects; the branch counts as merged while its changes are missinggit diff HEAD...<other> shows what is discardedAs for git mergeDeliberately superseding a branch8.6
git merge --squash <other>, git merge --no-ff --no-commit <other>🟑Index and working tree; no commitNothingAs for git mergegit reset --mergeReviewing or editing the result before committing8.12
git merge --continue, git commit during a merge🟑Creates the merge commit and moves the branchNothinggit diff --cached --check; run the testsgit reset --merge ORIG_HEAD before pushingAfter every conflict is resolved and checked8.10
git merge --quit🟑Deletes the merge state onlyNothing in files; no command resumes the mergegit statusgit reset --mergeRarely8.10
git merge --abort, git reset --mergeπŸ”΄Resets index and working tree to HEAD; deletes the merge stateEvery resolution made so far, and any uncommitted edit that was staged during the mergegit status, git diff --cached --statStaged content survives as unreferenced objects that git fsck lists (Lab 6.7); unstaged resolution edits are goneThe merge was a mistake or needs a different approach8.10, 8.21
git restore --ours|--theirs|--merge <path>, and the git checkout formsπŸ”΄Overwrites the working tree file with one side, or with a fresh marker versionYour hand edits to that file, which exist nowhere else until you git add themgit diff --ours, git diff --theirs, git diff <path>git restore --merge <path> brings back the starting point, not your editsThe whole file from one side is the decision; a resolution, yours or rerere's, is wrong8.10, 8.21, 14C.15
git rebase <upstream>, --onto, -i, --autosquash, --keep-base, --rebase-merges🟑New commits; the branch ref, at the endNothing permanentlygit log --oneline <upstream>..HEAD lists what will be replayedgit reset --hard <branch>@{1}, or git reset --keep ORIG_HEAD; a backup refYour own unpublished commits; ask before rebasing a branch others push to9.2, 9.5, 9.6
git rebase --update-refs🟑Also every local branch inside the rangeNothing permanentlyWith -i, the update-ref linesEach branch from its own reflogStacked branches9.9
git rebase --root🟑Every commit of the branchNothing locallygit log --onelineReflog; for a published repository there is no clean recoveryUnpublished repositories9.23
git rebase --continue🟑Commits the index as the copy, runs onNothinggit status, git diff --cachedReflog, after the rebaseAfter git add of the resolution; never --amend at a conflict stop9.11
git rebase --skip🟑Omits the stopped commitNothing: the original is in the old historygit rebase --show-current-patchRedoA commit that is already upstream9.11
git rebase --abort🟑Index and working tree back to the old tipResolution work in progress; commits made inside the rebase leave the branchgit status, git diff, git log <branch>..HEADCommits: the HEAD reflog or a rescue ref. Uncommitted edits: noneAfter anchoring new commits with a branch9.11, 29.3
git rebase --quit🟑Removes the state directory; HEAD stays detachedNothinggit statusgit switch <branch>Rarely; leave with --abort9.11
git pull --rebase🟑Fetches; re-creates your unpushed commits on the fetched upstreamNothing permanently; after someone's forced push it can drop commitsgit fetch, then git log --oneline @{u}..HEADgit reset --hard <branch>@{1}Unpushed local commits; look before integrating after a forced update9.13, 12.6
git pull --ff-only, git pull --no-rebase🟑Fetch, then moves the branch forward or creates a merge commit; working treeNothing permanentlygit fetch; git log ..@{u}; git log --oneline --graph HEAD @{u}git reset --hard ORIG_HEAD (itself πŸ”΄); the reflogNot in automation: a job that pulls can create merge commits nobody reviewed12.6, 12.16
git cherry-pick <commit>, with or without -x, -e, -m🟑Adds commits to the current branch; updates index and working tree; refuses to overwrite local changesNothinggit show <commit>; git merge-tree --write-tree --merge-base=<commit>^ HEAD <commit>; git cherry -vgit reset --keep ORIG_HEAD or the branch reflog; git revert if already pushedBackports (-x always), and recovery of single commits10.2, 10.4, 13.7
git cherry-pick -n <commit>🟑Index and working tree; no commit, no in-progress stateNothinggit show <commit>git restore --staged --worktree <paths>Combining several changes into one commit10.4
git cherry-pick --skip, --abort, --quit🟑--skip discards the stopped pick; --abort returns branch, index and working tree to the state before the sequence; --quit removes the sequencer state onlyResolution work in progressgit statusCompleted copies are in the reflog; resolution work is goneAn interrupted sequence10.6
git revert <commit>, git revert -m 1 <merge>🟑Adds one commit that applies the inverse changeNothing now; after -m 1 a later merge of the same branch is silently emptygit show <commit>; git show --stat <merge>Revert the revert; before pushing, git reset --keep HEAD~1The undo for published history; -m 1 only when the merged branch will never be merged again as it is, or with the re-merge procedure in the message10.11, 11.8, 11.9, 30.23
git revert -n <commit>🟑Index and filesNothinggit show <commit>git revert --abortSeveral reverts in one commit11.16
git merge --abort, git rebase --abort, git cherry-pick --abort, git revert --abort, git bisect reset as "leave" commands🟑 (merge: πŸ”΄, above)End the operation; reset index and working tree to the starting commitUncommitted edits made during the operationgit status, git diff, git log <branch>..HEADCommits: the HEAD reflog or a rescue ref. Uncommitted edits: noneAfter the PRESERVE step of the troubleshooting method29.6, 29.14
git bisect start, good, bad, skip, run, replay🟑Detaches HEAD and checks out other commits; writes refs/bisect/* and BISECT_*Nothinggit status must be clean; git rev-list --count <good>..<bad>git bisect resetFinding the commit that changed a property14A.20
git bisect reset🟑Checks out the starting branch; deletes the bisect stateThe session, unless you saved itgit bisect log > file to keep the sessiongit bisect replay fileAt the end of a session14A.22
git history reword, fixup, split (experimental; reword and split since Git 2.54, fixup since 2.55)🟑New commits; every descendant local branch movesNothing locally--dry-run; git branch -r --contains <commit> firstEach branch's reflogLocal cleanup only14D.6
git replay --onto, --advance, --revert (experimental; since Git 2.44, updates refs itself since 2.53, --revert since 2.54)🟑New commits; refs move in one transactionNothing where a reflog exists--ref-action=printReflog, where one exists; none by default in a bare repositoryRebase and cherry-pick without a working tree14D.7
git am🟑New commits on the current branchNothinggit apply --checkgit am --abort while it is stopped; afterwards the reflog of the branchThe patch-based workflow14D.11

7. Remotes, pushes and tags

Git, not GitHub. The rows in this section describe what Git does against any server. On GitHub, rulesets and branch protection can reject a push that Git would perform (Chapter 18), and a default bare Git server keeps no reflog (Chapter 12, section 12.8).

CommandLabelWhat it changesWhat it can destroyPreviewRecoverWhen appropriateSection
git push, git push -u, git push --follow-tags🟑Refs on the server (fast-forward or new), your remote-tracking refs; starts whatever listens for pushesNothing; published commits are not taken back quietly once others have fetchedgit push --dry-run; git log @{u}..A revert commit and another push; moving the ref back needs a forceAfter reading <source> -> <destination> in the dry run1.12, 12.7
git push origin <tag>, git push --tags🟑Creates tags on the server; on GitHub starts every workflow with a matching tag filterNothing in Git; a published name cannot be taken back cleanlygit push --dry-run; git ls-remote --tags originNone that is clean: others may have fetched; deleting the tag does not unpublish what a workflow builtPush single tags or --follow-tags, not --tags14B.10, 20A.18
git push <remote> --delete <ref>, git push <remote> :<ref>πŸ”΄Deletes a ref on the server, and your remote-tracking refThe only name the branch's commits have there; a bare server keeps no record of the deletiongit ls-remote <remote>; git log --oneline origin/<branch> --not origin/mainFrom any clone that has the commit: git push <remote> <id>:refs/heads/<name>Branches that are merged, or abandoned by agreement12.7, 27.21
git push --force, git push -f, git push <remote> +<ref>πŸ”΄Sets the server's ref with no check, and your remote-tracking refEvery commit on the server's branch that is not in your history, including commits you have never seengit fetch, then git log --oneline HEAD..origin/<branch>; git push --dry-run --forceThe old tip from any clone that had it (a teammate's branch, the reflog of anybody's remote-tracking ref), or the server's unreachable object until it is collected; push it backNone for the plain form: use a guarded form. Not in an incident9.17, 12.8, 30.23
git push --force-with-lease aloneπŸ”΄The server's ref, if it equals your remote-tracking ref; the check passes wrongly after a background fetchAs --force, when the remote-tracking ref was updated without your looking--dry-run; git log HEAD..origin/<branch>Only from a clone that has the commitsNone: add --force-if-includes or an explicit value9.17, 9.23
git push --force-with-lease --force-if-includes, git push --force-with-lease=<ref>:<expect>πŸ”΄The server's ref, only if it still has the value you rebased from or namedCommits on the server that only the server and stale clones have; with these forms everything removed is something your clone has--dry-run; git log <local>..<expect> lists what will leave the branchThe old tip is in your own reflogs; a clone's reflog of origin/<ref>; on GitHub the Activity view (Chapter 13, section 13.15)A branch that only you use, such as your own pull request branch after a rebase; restoring a known good value in an incident; tell teammates before the push9.17, 12.8, 27.3, 30.23
git push --mirror, git push --prune, git push --force --mirror originπŸ”΄Creates, forces and deletes server refs to match yours; from an ordinary clone --mirror would also publish your remote-tracking refsBranches and tags that exist only on the remotegit push --dry-run --force --mirror originFrom other clones, ref by ref; on GitHub, the instruments of Chapter 13Once, after a freeze, at the end of a verified rewrite, from a --mirror clone12.16, 21B.17
git push --force origin <tag>πŸ”΄Replaces a published tagThe meaning of a release name for everyone who fetches later; a workflow that names the tag runs different codegit ls-remote --tags originForce the old tag object back from a clone that has itPutting a tag back at its published position; a name that is meant to move should be a branch14B.11, 21A.7
git push origin --delete <tag>πŸ”΄Deletes a published tagThe only published name of a release commit, and the tag under a GitHub releasegit ls-remote --tags originPush it again from a clone that kept itA tag pushed by mistake minutes ago, announced14B.10
git fetch --prune, git remote prune <remote>🟑Deletes stale remote-tracking refs and their reflogsThe reflog that recorded the server's earlier valuesgit remote prune --dry-run <remote>git fsck --lost-found, then git branch <name> <id>, before garbage collectionRoutine; not while a lost remote state still has no name7.13, 12.11, 13.18
git fetch --tags --force🟑Replaces local tags with the server'sYour local value of a moved taggit ls-remote --tags; git fetch --tags --dry-run; note git rev-parse <tag> firstRe-create from that ID; the old tag objects stay, dangling, until prunedAfter confirming the server is right12.4, 14B.11
git fetch --prune --prune-tagsπŸ”΄Deletes every local tag the remote lacksPrivate lightweight tags, which have no reflog and no object to find them byAdd --dry-runAnnotated tags: git fsck; lightweight: only the IDs in the command's outputA mirror or a CI cache that must equal the server14B.10
git remote set-url, set-head, set-branches, rename; git branch -u; git config set push.default🟑Where later fetches, pulls and pushes goNothinggit remote -v; git config list --localSet the previous valueDeliberate changes of topology12.2, 12.5, 12.10
git remote remove <name>🟑The remote, its remote-tracking refs and reflogs, upstream settingsThe reflogs of the remote-tracking refsgit branch -r, git branch -vvAdd and fetch again; the reflogs are lostA remote you no longer use12.10

8. Reflogs, the object database and maintenance

CommandLabelWhat it changesWhat it can destroyPreviewRecoverWhen appropriateSection
git gc, git maintenance run --task=gc🟑Packs objects and refs; expires reflog entries by the configured periods; deletes unreachable objects older than two weeksWhat was past the retention periodsgit fsck --unreachable; git reflog expire --dry-run --all; git count-objects -v; git prune -nWithin the periods nothing is lost; beyond them, another clone or a backupRarely by hand: maintenance is automatic (geometric strategy by default since Git 2.54)3.17, 13.13, 26.3
git maintenance run🟑Packs, packed-refs, commit-graph, multi-pack-index; expires reflog entries by the configured periodsExpired reflog entriesgit maintenance is-needed (Git 2.53 or later); GIT_TRACE=1None for expired reflog entriesHundreds of packs or thousands of loose objects26.3
git maintenance start, git maintenance register🟑Global configuration and the system schedulerNothinggit help maintenancegit maintenance stop, git maintenance unregisterA deliberate choice on your own machine; never in the labs26.3
git gc --prune=now, git pruneπŸ”΄Deletes unreachable objects at once, with no grace periodEverything that only the grace period was keeping: dropped stashes, staged blobs, commits whose reflog entries are gonegit fsck --unreachable; git prune -nNone in this repository; another clone or a backupThe purge after a deliberate history rewrite, with no other process using the repository3.17, 13.13, 26.5
git repack -a -dπŸ”΄Rewrites packs; drops the unreachable objects of the packs it replacesUnreachable objects in those packsgit fsck --unreachable; git count-objects -vAnother clone or a backup onlyAfter a deliberate history rewrite in a repository you have copied first3.17
git repack --geometric=2 -d🟑Rewrites packsNothing reachablegit count-objects -vNot needed for reachable objectsWhat automatic maintenance does for you26.4
git reflog expire, git reflog delete <ref>@{<n>}, git reflog drop <ref> (drop: Git 2.50 or later)πŸ”΄Remove reflog entriesThe record of where a ref used to point--dry-run --verbosegit fsck, until a pruneAlmost never by hand13.4, 13.18
git reflog expire --expire=now --all, usually followed by git gc --prune=nowπŸ”΄Empties every reflog, the stash list included; then deletes every unreachable objectEvery recovery path in that clone: every commit, stash and staged blob that only a reflog, or nothing at all, was keepinggit reflog expire --dry-run --expire=now --all; git fsck --unreachable --no-reflogs lists what would goNone locallyThe final step of a secret clean-up, in a cleanup clone, after rotation; in a stale clone after its owner has saved their work3.17, 13.13, 21B.17, 30.23
git refs migrate --ref-format=<format> (Git 2.46 or later)🟑The ref storage format; values are keptNothing--dry-runMigrate back, or restore the copy of .git you made firstA clone that hits a case-insensitive ref conflict; throwaway repositories3.13, 14D.5
git filter-repo ..., git filter-branch ...πŸ”΄Every affected commit and all descendants, on every rewritten refSignatures; with a wrong path argument, files you meant to keepgit-filter-repo --dry-run; run in a fresh cloneThe untouched server and other clones, until you pushData that stays harmful after rotation; for an unpushed mistake on a private branch a rebase is the smaller tool21B.17

9. Configuration, hooks and recorded resolutions

CommandLabelWhat it changesWhat it can destroyPreviewRecoverWhen appropriateSection
git config set, unset, edit (Git 2.46 or later), in any scope🟑One configuration file, which has no historyThe previous valuegit config get --show-origin <key>; git config list --show-origin --show-scopeSet the old value again; keep the global file under version controlDeliberate settings; the scope that should own the key14B.3
git config set --global init.default*, git config set --global safe.bareRepository explicit🟑The behavior of later commands: new repositories; Git commands inside bare repositories then need --git-dir or GIT_DIRNothinggit config list --show-originUnset the keysinit.defaultBranch and safe.bareRepository now; the object and ref formats per repository, not globally14D.4, 21B.23
git config set --global --append safe.directory <dir>🟑Makes Git trust that directory's configuration and hooksNothing; removes a protectionRead <dir>/.git/config and <dir>/.git/hooks firstgit config unset --global --value=<dir> safe.directoryA directory whose contents you have read21B.23
git config set core.hooksPath, hook.<name>.*, filter.*, diff.*, merge.*🟑Which programs Git runs on your machineNothinggit hook list --show-scope <event>; git config list --show-originUnset the keysPrograms you have read14C.11, 14C.12, 28.18
git hook run <event>; --no-verify🟑Runs the local checks; skips themNothinggit hook list <event>The server may still refuseTesting a hook; a hook that is wrong14C.11
git rerere forget <path>, git rerere clear, git rerere gc🟑Deletes recorded resolutionsThe recorded resolutiongit rerere diff; ls .git/rr-cacheResolve againA recorded resolution that is wrong14C.3
git lfs install without --local🟑The filter keys go into your global configuration; hooks in the current repositoryNothinggit config list --show-origingit lfs uninstallIn this book it is always run with --local (🟒)22.3

10. Submodules, subtrees, LFS, sparse-checkout, worktrees

CommandLabelWhat it changesWhat it can destroyPreviewRecoverWhen appropriateSection
git submodule update (default checkout)🟑Moves the submodule's HEAD (detached) and its files; refuses to overwrite local edits to files it must changeNothing; commits made on the detached HEAD lose their namegit submodule status; git -C <path> statusThe submodule's reflog: git -C <path> reflog; git -C <path> branch <name> <id>After every pull that moved a gitlink23.3, 23.5
git submodule update --remote🟑As above, to the tip of the remote-tracking branch; the superproject then shows a modified gitlinkNothinggit -C <path> fetch && git -C <path> log --oneline HEAD..origin/HEADgit submodule update returns to the recorded commitMoving the pointer on purpose, in a dedicated branch23.6
git submodule update --force, git submodule deinit --forceπŸ”΄Overwrites modified tracked files in the submoduleUncommitted changes inside the submodulegit submodule foreach 'git status --short'None for uncommitted editsTo get back to exactly the recorded state23.9, 23.16
git submodule deinit <path>🟑Empties the working directory; removes the section from .git/config; refuses if there are local modifications unless --forceNothinggit -C <path> statusgit submodule update --initA submodule you do not need locally23.11
git rm <submodule path>🟑Removes the gitlink, the .gitmodules section and the working directoryNothinggit statusgit restore --staged --worktree .gitmodules <path>, then git submodule update --initRemoving the submodule from the project23.11
Deleting .git/modules/<name>πŸ”΄Deletes a repositoryCommits in the submodule that were never pushedgit -C <path> status; git -C <path> reflogNoneRemoving a submodule completely from your clone, after checking for unpushed commits23.11, 23.16
git push --recurse-submodules=on-demand🟑Pushes in the submodules, then the superprojectAs for any pushgit submodule foreach 'git status --short --branch'As for any pushWhen the superproject commit needs submodule commits that are not published23.8
git subtree add, git subtree pull🟑New commits on the current branch (a merge); needs a clean working treeNothinggit ls-remote <repository> <ref>git reset --hard ORIG_HEAD if not pushed (πŸ”΄ for uncommitted work); git revert -m 1 if pushedVendoring a dependency's files into your own history23.13
git subtree push🟑Creates or updates a branch in the library's repositoryNothing in yoursgit subtree split first and inspect the resultDelete or reset that branch in the library's repositorySending changes back upstream23.13
git lfs push, and git push with the pre-push hook🟑Uploads LFS objects; on GitHub they are billed and cannot be deleted by youNothinggit lfs push --dry-run <remote> <ref>; git lfs statusNone on GitHub short of deleting the repository (22.11)After deciding what belongs in LFS22.6
git lfs prune🟑Deletes local LFS objects that are pushed and not neededNothing while the remote has the objects--dry-run --verbose; --verify-remotegit lfs fetch, if the remote has themReclaiming disk space22.8
git lfs migrate import, export (default scope or named branches)🟑Rewrites unpushed commits; the working tree is left with pointers after importNothing locallygit lfs migrate info with the same optionsBranch reflogs: git reset --hard <branch>@{1}Unpushed commits that contain large files22.9
git lfs migrate import --everything, then a forced pushπŸ”΄Rewrites published historyNothing locally; breaks every other clonegit lfs migrate info --everything; rehearse in a fresh cloneBefore the push: reflogs. After: the old history from another cloneA coordinated rewrite22.9, 22.13
git sparse-checkout set, add, init --cone (init is deprecated: set does everything)🟑Working tree, skip-worktree bits, pattern file, config.worktree; deletes ignored files in directories that leave the coneIgnored files in directories that leave the cone, such as a local .env or a downloaded evaluation setgit status --short --ignoredset again or disable; ignored files are goneAfter checking for ignored files you cannot regenerate24.4, 24.5
git sparse-checkout reapply, disable🟑Removes, or writes, tracked filesNothing trackedgit ls-files -tset or addAfter conflicts outside the cone24.4
git sparse-checkout set --sparse-index🟑Rewrites the index in a form some tools cannot readNothingTest the tools first--no-sparse-indexVery large repositories24.6
git sparse-checkout clean -f (Git 2.52 or later)πŸ”΄Deletes directories outside the cone that hold untracked filesUntracked files there, which were never in Git--dry-run, --verboseNoneAfter reading the preview and moving away what you need24.5
scalar clone🟑A clone, and also global configuration, a scheduler unless --no-maintenance, a monitor daemonNothinggit help scalarscalar unregister or scalar delete; git fsmonitor--daemon stopA deliberate choice on your own machine24.7
git worktree move🟑The directory and both pointersNothinggit worktree listMove it backReorganising directories25.6
git worktree remove🟑Deletes a clean working tree and its entry, including its HEAD reflogThe HEAD reflog of that worktree; the name of detached commits made theregit -C <path> status --short; git -C <path> log --oneline -3git worktree add again; detached commits via git fsck --lost-foundA finished task; create a branch before removing25.6
git worktree remove --forceπŸ”΄As above, and deletes uncommitted and untracked filesFiles that were never committedgit -C <path> status --short --ignoredNone for files that were never committedOnly after looking25.6, 25.9
git worktree prune🟑Deletes entries whose working tree is missing, including their HEAD reflogsThose HEAD reflogsgit worktree prune --dry-run --verbosegit worktree add again; a moved tree: git worktree repair before pruningAfter a working tree was deleted with rm -rf25.6
git worktree add --force for a branch that is already checked outπŸ”΄Two indexes on one refThe consistency of the stale worktree: staged changes nobody madeNone; do not usegit reset --hard in the stale worktree after saving its workNever25.4, 25.9
python3 tools/dataref.py checkout (and dvc checkout)πŸ”΄ for unrecorded data editsOverwrites data files with the versions the pointers nameUnrecorded edits to data filespython3 tools/dataref.py verifyNone for unrecorded edits; record them first with addAfter verify reports "ok"28.18

11. GitHub: the GitHub CLI, authentication, rules and Actions

GitHub, not Git. Every πŸ”΄ command here changes GitHub objects that no clone contains (Chapter 15, section 15.22). The preview is always a read with the same tool. Flags were checked with gh <command> --help of version 2.88.1; nothing was run against GitHub. The full command tables are in the GitHub CLI cheat sheet.

CommandLabelWhat it changesWhat it can destroyPreviewRecoverWhen appropriateSection
gh repo create --source=. --push🟑A repository on GitHub, a remote, a pushNothing; what reached a public repository is publishedgit log, git status; check --public or --privategh repo deleteAfter reading what the push will publish15.22
gh issue create, gh pr create, gh label create, gh release create --verify-tag --draft🟑GitHub objects that notify people; gh pr create may push the branchNothing; notifications are not recalledgh pr create --dry-run; --draftClose or delete; gh pr closeOrdinary collaboration15.22, 17.21
gh repo edit (features, merge methods), gh repo fork, gh repo sync🟑Settings; a new repository; a fast-forwardNothinggh repo view --json; git log HEAD..upstream/mainSet the previous value; delete the forkDeliberate changes15.22
gh repo sync --forceπŸ”΄Hard reset of the destination branchCommits on the destination branch that the source lacksgit log upstream/main..origin/main after a fetch; git rev-list --left-right --count upstream/main...origin/mainPush the old commits back from a clone that still has themA fork branch that holds nothing of its own15.7, 17.21, 27.21
gh api with POST, PATCH, PUT, DELETEπŸ”΄Whatever the endpoint says, with all your permissionsDepends on the endpointThe GET firstDepends on the endpoint; often noneAfter the GET, with the saved JSON at hand15.22
gh release create TAG without --verify-tagπŸ”΄May create a tag on the default branch and start tag workflowsNothing existing; publishes a version namegit ls-remote --tags origin TAGDelete release and tag; consumers may have fetched itUse --verify-tag15.22
gh release delete --cleanup-tag, git push origin --delete TAGπŸ”΄A published version's nameThe release and its taggh release view TAGRe-create from the recorded commit IDA release published by mistake15.22
gh repo edit --visibility, gh repo delete, gh repo rename, gh repo archiveπŸ”΄Visibility with its side effects; the repository, its URL, its writabilityThe side effects of a visibility change cannot be undoneSection 15.5; ask who depends on the URLVisibility can be changed back, its side effects cannot; restore within 90 days unless the fork network is not emptyPlanned, announced changes15.5, 15.22
gh pr update-branch, gh pr edit --base, gh pr merge🟑Move the head branch, change the base, or write to the base branch; can dismiss approvalsNothinggh pr view, gh pr checks --requiredChange the base back; gh pr revertThe normal end of a reviewed pull request17.21
gh pr merge --admin, a bypass mergeπŸ”΄Writes to a protected branch without the required reviews or checksNothing in Git; the controlgh pr checks --requiredgh pr revert; record whyA planned migration or a declared emergency, by a named person, with the reason written down where the audit will find it17.16, 18.21
gh api --method POST repos/ORG/REPO/rulesets --input file.jsonπŸ”΄Creates a ruleset; if Active, it binds everyone at once, you includedNothingCreate it with "enforcement": "disabled", then gh ruleset checkSet enforcement to disabled, or delete the rulesetRules as reviewed JSON18.21
gh api --method PUT .../rulesets/IDπŸ”΄Replaces settings of a rulesetThe previous settingsgh api .../rulesets/ID first and keep the JSONPUT the saved JSON backChanging a rule18.21
gh api --method DELETE .../rulesets/IDπŸ”΄Removes the protection for every ref it targeted, immediatelyNothing directly; it makes force pushes and deletions possibleSave the JSON firstRe-create from the saved JSON; ruleset history exists only on EnterpriseA planned migration or a declared emergency18.21
git push --force to a shared base branch, or to a branch whose protection was just removedπŸ”΄Removes commits from the remote branch; corrupts open pull requestsCommits others pushedgit log <branch>..origin/<branch> and the reverseChapter 13; Chapter 30Not appropriate17.21, 18.21
Editing CODEOWNERS in a pull request🟑Who is requested, and under a rule who can block, from the next merge onNothingThe errors endpoint with refRevert the commitWith review by the current owners19.14
Removing the owner of /.github/πŸ”΄Lets any writer change reviewers and workflows without owner reviewNo data; removes a controlReview the diff of the fileRestore the line; audit what merged in betweenNever without a replacement owner19.14
gh workflow run <file> --ref <branch>🟑Starts a run, which uses minutes and may deploy if the workflow deploysNothing in GitRead the workflow file at that ref: gh workflow view --yamlgh run cancel <run-id>; deploy the previous commitA workflow_dispatch workflow you have read20A.18, 20B.18
gh run rerun <run-id>, --failed🟑Starts a new attempt on the original commitNothinggh run view RUN_ID --json headSha and compare with mainCancel the attempt; start a new run from the current commitA flaky failure, not a stale commit20A.18, 20B.18
gh run cancel🟑Stops a run, possibly mid-deploymentThe consistency of a half-finished deploymentgh run viewRe-run; check the target's state by handA run that should not finish20B.18
gh secret set, gh variable set🟑Overwrites the stored valueThe old secret value, which cannot be read backgh secret list --env NAMESet the previous value again from your secret storeRotation20B.18
gh api -X PUT .../environments/NAMEπŸ”΄Replaces the environment's protection settings with the body sentThe previous settingsgh api .../environments/NAMESend the previous configuration againEnvironment rules as code20B.18
gh cache delete --all🟑Removes every cache of the repositoryNothing permanentgh cache listCaches are rebuilt by the next runs, slowlyA poisoned or oversized cache20B.18
git tag -f -a v1, then git push --force origin v1🟑, then πŸ”΄Moves a local tag; replaces a published tag that workflows may nameWhat uses: ...@v1 meant yesterdaygit rev-parse 'v1^{commit}'; git ls-remote --tags origin v1Re-create the tag at the old commit; push the old tag object back, if you still have itThe reason to pin actions by full commit ID: see the action pins21A.7
Pushing a change under .github/workflows/🟑Git: an ordinary commit. GitHub Actions: the next matching event runs the new file with the repository's token and secretsNothing in GitReview the diff with section 21A.19Revert the commit; rotate any secret the changed workflow could readAfter review by the owner of /.github/21A.22
ssh-keygen -t ed25519 -f NEW🟑Creates two files; overwrites an existing key only after askingAn overwritten private keyls ~/.sshNone for an overwritten private keyA new key under a new file name16.23
ssh-add, ssh-add --apple-use-keychain🟑The agent's key list; a keychain entryNothingssh-add -lssh-add -dLoading the key you mean16.23
ssh-keygen -R HOST🟑Removes a host's lines from known_hosts, keeping a .old copyNothingssh-keygen -F HOSTThe .old fileAfter comparing the published fingerprint16.11, 16.23
StrictHostKeyChecking no, or deleting known_hosts wholesaleπŸ”΄Removes the host key checkThe protection against a wrong hostNoneInstall the published lines insteadNever; on a CI runner install the published lines16.11
git config set --global credential..., gh auth setup-git, gh auth login, gh auth switch🟑Which credential answers, for every repositoryNothinggit config get --show-origin --all credential.helperSet the previous value; git config unsetDeliberate changes of identity16.23
git credential reject, git credential-osxkeychain erase🟑Deletes a stored credentialThe stored credentialgit credential fill shows what is storedLog in againA stale or wrong stored credential16.23
gh auth token, gh auth status --show-tokenπŸ”΄Prints a live token to the terminal and its scrollbackThe secrecy of the tokenAsk whether you need the value at allRevoke the tokenRarely; never in a shared or recorded terminal16.23
gh ssh-key delete, deleting a token or a deploy key in the web interfaceπŸ”΄Every machine and job that used it stopsAccess for those machines and jobsList where it is usedCreate and distribute a new oneRotation, with the list of users at hand16.23
Writing a token into a URL, a file or a command lineπŸ”΄Publishes the secret to logs, history and backupsThe secrecy of the tokenNoneRevoke it; cleaning is not enoughNever16.23

For the response to a leaked secret, the order of the steps matters more than any single command: see the security cheat sheet and Chapter 21B. For recovery after any πŸ”΄ command, see the emergency recovery page and the disaster-recovery playbook.

12. Where chapters label the same command differently

Earlier drafts of the chapters labelled fifteen commands in two ways, because each chapter judged the worst case in its own context. The editorial pass of 6 October 2026 chose one label per command from the definitions in section 1, and the chapters, this file, the command reference, the cheat sheets and the playbooks now agree. The table records each decision. Git behavior named in the last column was checked on Git 2.55.0; the gh rows were checked against gh <command> --help of 2.88.1 and not run against GitHub.

One test settled most rows: a command is 🟒 only if no ref moves and nothing is lost. A command that adds commits to the current branch through the merge machinery moves that branch and rewrites index and working tree, which is the reason git merge is 🟑 in 8.21; a command that overwrites a setting loses the previous value, because a configuration file keeps no history.

CommandLabel nowSections changed (label before)Reason
git cherry-pick <commit>, git cherry-pick --continue🟑2.10, 2.15, 10.2, 10.14, 27.9, 27.21 (🟒)It moves the current branch and rewrites index and working tree, as git merge does; it adds commits and removes none
git revert <commit>, git revert -m 1 <merge>🟑10.11, 10.14, 11.8, 11.16, 27.15, 27.21 (🟒)As above; and after -m 1 a later merge of the same branch is silently empty
git merge --abortπŸ”΄29.14, the two playbooks, the emergency page (🟑)It discards every resolution made so far and every edit staged during the merge, and the manual warns that uncommitted changes present when the merge started cannot always be reconstructed. git rebase --abort, git cherry-pick --abort and git revert --abort stay 🟑 in their own rows
git push --force-with-lease --force-if-includesπŸ”΄1.8 (🟑, as an example)Every forced push replaces history on a remote, where you have no reflog (9.17, 12.8). The conditions make it the right form of a πŸ”΄ command, not a 🟑 one
git push --force-with-leaseπŸ”΄17.21 (🟑)As above; alone, the check passes wrongly after a background fetch
git update-ref -d <ref>πŸ”΄3.17 (🟑)It deletes the ref together with its reflog, which is the safety net for that ref
git repack -a -dπŸ”΄26.18 (🟑)It drops the unreachable objects of the packs it replaces, at once and without the two-week grace period. git repack --geometric=2 -d stays 🟑
rm .git/index, then git resetπŸ”΄5.18 (🟑)It removes the index outside Git's locking and discards the record of what was staged, the flag bits and the conflict stages; no reflog holds them. Moving the file aside first (mv .git/index .git/index.corrupt, 13.11) is 🟑, because the file can be moved back
git config set <key>, unset, edit, in any scope🟑14A.17, 14A.27 (blame.ignoreRevsFile, diff.algorithm, blame.markIgnoredLines), 14D.4 (init.default*), 21B.23 (safe.bareRepository) (🟒)It rewrites a file that keeps no history, and it changes the behavior of later commands. This is the label of 1.15 and 14B.21
git lfs checkout🟒22.7 (🟑)It replaces unmodified pointer files with their content from the local store and never overwrites a modified file: no ref moves and nothing is lost. git lfs pull is fetch plus checkout and carries the same label (22.14). The row this file had for the command is removed, because 🟒 commands are not listed here
git subtree add🟑23.13 (🟒)It creates a merge on the current branch
gh pr create🟑17.2, 17.21 (🟒)It may push the branch, and it creates a GitHub object that notifies people
gh api with POST, PATCH, PUT or DELETE, including the ruleset calls of 18.21 and the environment call of 20B.18πŸ”΄18.21 (POST and PUT on rulesets), 20B.18 (PUT on an environment) (🟑)The call does whatever the endpoint and the body say, with all your permissions (15.22). A PUT replaces settings that exist nowhere else unless you saved the JSON first; the POST that creates a ruleset destroys nothing and keeps the label of the command, with that stated in its row

Three labels of the GitHub CLI cheat sheet that no chapter table carried were confirmed in the same pass:

CommandLabelReason
gh ssh-key add🟑It attaches a credential to your account that can act as you until it is deleted; gh ssh-key delete undoes it (16.8)
gh repo set-default <repository>🟑It records a setting in the clone that decides which repository later gh commands act on; --view is 🟒 and --unset undoes it (15.16)
gh api graphql🟒 with a query, πŸ”΄ with a mutationA GraphQL query only reads, although the request is sent as a POST; a mutation writes, and falls under the rule of 15.22

13. Chapter files

The section numbers in the tables point into these files.

SectionsFile
1.xChapter 1: Fundamentals
2.xChapter 2: The Mental Model
3.xChapter 3: Git Internals
4.xChapter 4: The Working Tree
5.xChapter 5: The Index
6.xChapter 6: Commits
7.xChapter 7: Branches
8.xChapter 8: Merge
9.xChapter 9: Rebase
10.xChapter 10: Cherry-pick
11.xChapter 11: Reset, Revert, Restore
12.xChapter 12: Remote Operations
13.xChapter 13: Recovery
14A.xChapter 14A: History investigation
14B.xChapter 14B: Configuration, Aliases, Tags and Signing
14C.xChapter 14C: Stash Internals, Rerere, Attributes, Hooks
14D.xChapter 14D: The Frontier
15.xChapter 15: GitHub
16.xChapter 16: Authentication
17.xChapter 17: Pull Requests
18.xChapter 18: Branch Protection and Rulesets
19.xChapter 19: CODEOWNERS
20A.xChapter 20A: GitHub Actions Fundamentals
20B.xChapter 20B: GitHub Actions: delivery, runners, cost and debugging
21A.xChapter 21A: GitHub Actions security
21B.xChapter 21B: Repository security, identity, the Git client, and secret-leak response
22.xChapter 22: Git LFS
23.xChapter 23: Submodules and Subtrees
24.xChapter 24: Monorepos
25.xChapter 25: Worktrees
26.xChapter 26: Performance
27.xChapter 27: Open source and team workflows
28.xChapter 28: AI/ML workflows
29.xChapter 29: Production Troubleshooting
30.xChapter 30: Incident Response

Written against Git 2.55.0 on macOS; GitHub facts as of 1 October 2026. Git transcripts are output from the book’s lab scripts; GitHub behaviour is described from GitHub’s documentation. Found a mistake? Tell us through the enquiry form.

Model registry/models
Benchmarks/benchmarks
Paper breakdowns/research
AI tools directory/tools
Work with us/work-with-us