Operating principle: every Git problem is CURRENT STATE → DESIRED STATE → STATE TRANSFORMATION → VERIFICATION.
Audience: Senior / Staff / Principal Engineers, SREs, DevOps, Release Engineers, Infrastructure Engineers, Engineering Managers, and teams operating production repositories.
Validation baseline: command workflows in this artifact were experimentally exercised on Git 2.47.3 in disposable repositories. Authoritative Git/GitHub/GitLab documentation was also reviewed during authorship; where current documentation may be newer than the local test binary, that distinction is explicit.
This is not a command catalog. It is a state-model reference. Start by identifying what Git state actually exists; do not mutate the repository because someone described the situation informally. The supplied specification explicitly requires a current-state/desired-state model and a diagnosis-before-mutation workflow. Source specification: state-first command teaching.
For incident pressure, read only the first layer of the relevant procedure. For learning, follow the cross-references into the state model, DAG, refs, index, reflog, and plumbing sections.
this manual deliberately moves through four levels:
HEAD, branch refs, index, working tree, DAG and
remotes.STOP
↓
INSPECT
↓
IDENTIFY LAST KNOWN GOOD STATE
↓
SAVE CURRENT STATE IF NECESSARY
↓
CHECK REFLOG / OBJECTS
↓
MODEL CURRENT DAG
↓
DEFINE DESIRED DAG
↓
SELECT THE LEAST DESTRUCTIVE SUITABLE TRANSFORMATION
↓
EXECUTE
↓
VERIFY
Never let “least destructive” mean “always preserve every historical shape.” The correct operation is the one that reaches the desired state while satisfying the collaboration, audit, deployment, and repository-policy constraints.
Git stores content in a content-addressed object database and names useful entry points with references. A commit refers to a tree and one or more parent commits. A tree refers to blobs and subtrees. Branch names are refs that typically point to commits. Tags are refs with different semantics, and annotated tags themselves are tag objects that can point to another object.
refs / names
│
▼
┌────────────┐
│ commit │
│ tree + │
│ parents │
└─────┬──────┘
│
▼
┌─────┐
│tree │
└──┬──┘
│
┌───────┴────────┐
▼ ▼
┌──────┐ ┌──────┐
│ blob │ │ tree │
└──────┘ └──────┘
Git does not store a branch as “a folder containing copied files.” A branch is a movable reference. Creating a branch normally creates or updates a ref; it does not duplicate the commit graph.
Use these as three snapshots, not three synonyms:
| View | Conceptual meaning | Typical inspection | Can differ from the others? |
|---|---|---|---|
HEAD |
The commit checked out, or the symbolic branch ref followed by
HEAD |
git rev-parse HEAD,
git symbolic-ref HEAD |
Yes |
| Index | The next snapshot Git will commit; during conflicts it can hold multiple stages | git diff --cached, git ls-files -u |
Yes |
| Working tree | Files currently checked out on disk | git diff, git status |
Yes |
HEAD commit A---B---C
│
│ tree(C)
▼
Index staged snapshot
│
▼
Working tree staged + unstaged filesystem changes
git status
git diff
git diff --cached
git diff HEAD
git rev-parse HEADInterpretation:
git diff: working tree vs index — unstaged
changes.git diff --cached: index vs HEAD — staged
changes that the next normal commit would record.git diff HEAD: working tree vs HEAD —
staged + unstaged changes relative to the checked-out commit.Let:
| Symbol | Meaning |
|---|---|
W |
working tree state |
I |
index state |
H |
commit named by HEAD |
R |
current local branch ref, when HEAD is attached |
G |
commit DAG / reachability graph |
O |
object database |
RT |
local remote-tracking refs such as
refs/remotes/origin/main |
RR |
remote refs on the remote repository |
This is descriptive, not a formal proof calculus. Use it to predict effects before executing commands.
| Current state | Desired state | Primary transformation | Core state that changes | What remains conceptually intact | Reversibility / risk |
|---|---|---|---|---|---|
| untracked file | tracked+staged | git add |
index | working-tree bytes | easy |
| modified tracked file | staged | git add |
index | working tree | easy |
| staged change | unstaged change | git restore --staged /
git reset <path> |
index | working tree | easy |
| staged snapshot | committed | git commit |
object DB + current ref | working tree contents | normal |
| bad local commit, preserve changes | previous commit as tip | git reset --soft / mixed |
ref + maybe index | objects generally retained | local rewrite |
| bad local commit, discard changes | previous commit as tip and content | git reset --hard |
ref + index + working tree | old objects may remain via reflog | high |
| shared bad commit | history preserved | git revert |
new commit | old DAG | low/moderate |
| change exists on A, needs on B | patch replayed on B | git cherry-pick |
new commit + ref | source commit remains | depends on conflicts |
| two divergent lines need combined history | one DAG with both parents | git merge |
new merge commit or fast-forward | topology if true merge | normal if intentional |
| feature should appear as if based on newer base | rewritten feature commits | git rebase |
new commits + ref | old objects temporarily | history rewrite |
| deleted/rewound branch | prior tip recovered | reflog + new ref | ref | underlying object if retained | time-sensitive |
| local refs stale vs remote | local remote-tracking state updated | git fetch |
object DB + refs/remotes/* +
FETCH_HEAD |
working tree normally | safe |
| remote branch needs fast-forward | remote ref advanced | git push |
remote ref | local history | normal |
| remote branch must be rewritten | remote ref moved backward/sideways | force push with policy/lease | remote ref | remote object DB may retain old commits | high |
Rule: the same command can be a very different operational choice depending on whether the affected history is private, private-but-pushed, shared, protected, or release/public history.
Use this before any history rewrite:
HEAD?git status
git branch --show-current
git log --graph --decorate --oneline --all --reflog
git branch -vv
git remote -v
git reflog --all -20Git’s core loose object types are blob, tree, commit, and tag. Object identity is based on the object’s bytes under the repository’s hash algorithm. A repository may use SHA-1 or SHA-256 object formats; the important operational property is that changing object content changes its object ID.
tree <tree-id>
parent <parent-id> # zero or more lines
author ...
committer ...
commit message
Because the parent IDs, tree ID, author/committer metadata, and message contribute to the commit object, changing a parent relationship normally changes the commit ID. This is the mechanical reason a rebase creates different commits.
mode type object-id path
100644 blob <oid> src/main.c
040000 tree <oid> src/lib
Trees describe a directory snapshot. A commit points to exactly one top-level tree.
A blob is file content without the pathname. The pathname is represented by the tree entry that points to the blob.
An annotated tag is a first-class tag object that can carry a tagger identity, message, and optional signature, and points at another object. A lightweight tag is simply a ref directly naming an object.
git cat-file -t <oid>
git cat-file -p <oid>
git ls-tree <commit-or-tree>
git rev-parse <name>
git hash-object <file>
git fsck --fullThe validation harness constructed a tree with
git write-tree, inspected it using
git ls-tree, inspected object types using
git cat-file -t, and constructed a commit with
git commit-tree. It then moved a branch ref with
git update-ref. This verified the conceptual
porcelain→plumbing path described later.
An object can stop being reachable from a visible branch while still existing in the object database. Reflogs, the index, remote-tracking refs and other references can keep objects alive. Git maintenance eventually prunes objects that are no longer protected by the repository’s reachability/retention mechanisms.
Do not equate “no branch points to it” with “object immediately deleted.” This distinction explains why reflog-based recovery often works.
Attached HEAD is normally a symbolic ref:
git symbolic-ref HEAD
# e.g. refs/heads/main
git rev-parse HEAD
# e.g. <commit-id>Detached HEAD instead names a commit directly. That is
why git switch --detach <commit> can create useful
temporary states without moving any branch ref.
| State | HEAD |
Current branch ref moves when you commit? | Operational consequence |
|---|---|---|---|
| attached | ref: refs/heads/main |
yes | normal branch workflow |
| detached | direct commit ID | no | commits are reachable only through some other ref until you create one |
The index is a binary file that records the next tree Git can write.
In the normal state, each path has one stage. During an unmerged
conflict, a path can have multiple stages: stage 1 common ancestor,
stage 2 “ours” (HEAD) and stage 3 “theirs”
(MERGE_HEAD) during a normal merge. Inspect with
git ls-files -u.
git ls-files --stage
git ls-files -uFor merge conflict education, remember: the index is where unresolved structure lives; the working tree is where the human resolves it.
Use the index deliberately when one file contains multiple logical changes.
git add -p
git diff --cached
git diff
git restore --staged path/to/file
git reset -pworking tree
├── security fix
├── debug logging
├── formatting noise
└── unrelated feature
│
├── git add -p → index contains only security fix
└── commit → clean, surgical commit
Use git add --patch to select hunks into the index. Use
git diff --cached as the pre-commit proof that the commit
contains exactly what you think it contains.
A---B---C---D main
\
E---F feature
Each commit points backward to its parent(s). A normal commit has one parent; a root commit has zero; a true merge commit has two or more parents. This makes Git history a directed acyclic graph (DAG).
before
A---B main
\
C feature
after git merge feature
A---B---C main,feature
A fast-forward does not create a merge commit. The receiving branch ref simply moves to the descendant commit.
before
A---B---C main
\
D---E feature
after git merge feature
A---B---C---M main
\\ /
D---E
M has parents C and E
A true merge creates a commit with both branch tips as parents and a tree representing the merged result. The merge operation therefore preserves the fact that two histories were integrated.
git merge-base A B
git merge-base --is-ancestor A B
git rev-list A
git rev-list --ancestry-path A..B
git branch --contains <commit>
git branch --merged
git branch --no-mergedmerge-base gives a common ancestor useful for three-way
reasoning. --is-ancestor A B asks whether A is reachable
from B. This is the primitive behind many fast-forward / divergence
decisions.
| Syntax | Meaning | Key trap |
|---|---|---|
HEAD |
commit named by HEAD | attached vs detached both resolve to a commit |
HEAD~1 |
first-parent ancestor, one generation | follows first parent only |
HEAD~2 |
first-parent ancestor, two generations | not “parent #2” |
HEAD^ |
first parent of commit | on merge commit this is parent 1 |
HEAD^2 |
second parent of a merge commit | invalid for a normal one-parent commit |
A..B |
commits reachable from B but not A | in set terms, not simply textual “between” |
A...B |
symmetric difference of reachability sets; often used to describe commits on either side of a merge base | context depends on consuming command |
A^..B |
includes A itself plus descendants reachable from B but not before A under the usual revision-set semantics | useful for cherry-pick/log ranges |
@{-1} |
previous checkout/switch location | stored in reflog-ish checkout state |
branch@{yesterday} |
branch reflog entry at the specified time | needs reflog data |
HEAD@{1} |
previous HEAD reflog entry | reflog is local state, not remote history |
Authoritative revision syntax is documented by Git’s
gitrevisions manual.
A..B ≠ A...BFor reachability sets:
A..B = Reach(B) \ Reach(A)
A...B = (Reach(A) \ Reach(B)) ∪ (Reach(B) \ Reach(A))
In merge-base terms, A...B commonly describes the
commits on either side of the divergence. Do not treat it as a synonym
for “the diff”. Commands such as git diff A...B have their
own defined semantics: compare the merge base of A and B to B. Always
check the command’s revision interpretation when scripting around
ranges.
| Ref | Meaning | Stored where | Typical role |
|---|---|---|---|
refs/heads/main |
local branch ref | local repo | movable branch pointer |
refs/tags/v1.2.3 |
tag ref | local repo | release name |
refs/remotes/origin/main |
local remote-tracking ref | local repo | last-observed remote branch tip |
HEAD |
symbolic/direct pseudoref | repo metadata | current checkout target |
ORIG_HEAD |
safety pseudoref used by drastic movements | repo metadata | pre-operation pointer |
FETCH_HEAD |
fetched remote refs from most recent fetch | repo metadata | fetch result / scripts |
MERGE_HEAD |
commit(s) being merged while merge is in progress | repo metadata | merge state |
CHERRY_PICK_HEAD |
commit currently being cherry-picked on conflict | repo metadata | sequencer state |
REBASE_HEAD |
commit currently associated with stopped rebase | repo metadata | rebase state |
git branch feature-a main
git show-ref --heads
git rev-parse refs/heads/feature-aThe new branch initially names the same commit as the source. No
commit objects are copied. The next commit made while
feature-a is checked out creates a new commit and advances
only that branch ref.
git show-ref
git for-each-ref
git update-ref refs/heads/experiment <new-oid> <expected-old-oid>git update-ref is the controlled plumbing interface for
changing refs. Its old-OID argument supports compare-and-swap-style
safety: move the ref only if it still equals what you expected. This is
the same class of primitive you want in automation that manipulates
history precisely.
git addHuman intention: put selected working-tree content into the next commit.
State transformation: W → I for
selected paths/hunks. HEAD does not move. The working tree
remains as it was.
git add path/to/file
git add -pVerification: git diff --cached.
git commitHuman intention: make the staged snapshot durable in history.
State transformation: write tree from the index →
create commit object → advance current branch ref → attached
HEAD follows the branch. The working tree is generally not
rewritten by the core commit operation.
git diff --cached
git commit -m "Describe the change"
git rev-parse HEAD
git show --stat --oneline HEADHEAD moves because it follows the branch ref; it does
not move as a separate independent branch pointer.
git restore vs git reset vs
git revert| Command | Primary purpose | Moves branch tip? | Index effect | Working tree effect | History effect |
|---|---|---|---|---|---|
restore |
restore paths from a source snapshot | No | optional | yes | none |
reset (commit form) |
move current branch to a target | Yes | mode-dependent | mode-dependent | rewrites current ref |
revert |
record inverse change as new commit | Yes, by adding a new commit | through commit | through commit | preserves original history |
Use git restore for file-state restoration,
git reset for pointer/index/worktree manipulation, and
git revert when the desired public/shared-history operation
is “make a new commit that undoes the effect of an old commit.” This
distinction is part of Git’s own command overview.
| Mode | Branch/HEAD | Index | Working tree |
|---|---|---|---|
--soft |
moves to target | unchanged | unchanged |
--mixed |
moves to target | reset to target | unchanged |
--hard |
moves to target | reset to target | updated to target, discarding tracked changes that block the reset |
Git also supports --merge, --keep,
path-limited reset and patch mode. Do not flatten these into the three
common modes when working on complex merges.
BEFORE
HEAD -> C
branch main -> C
INDEX = tree(C) + staged S
WORKTREE = tree(C) + staged S + unstaged U
git reset --soft HEAD~1
AFTER
HEAD -> B
branch main -> B
INDEX = tree(C) + staged S
WORKTREE = tree(C) + staged S + unstaged U
git reset --soft HEAD~1
git status
git diff --cached
git diffChanging a branch ref makes the old tip less reachable from that ref. The commit object may still be reachable via reflogs or other refs, and Git’s maintenance rules determine when unreachable data becomes eligible for pruning.
A---B---C---D
│
└── revert C,D → E,F
A---B---C---D---E---F
git revert creates new commits that reverse the selected
patches. This is normally the right conceptual tool when shared history
must remain intact. It does not make the original commits cease to
exist.
git revert <commit>
git log --graph --decorate --oneline -6Because a merge has multiple parents, reverting a merge requires
choosing a mainline parent with -m <parent-number>.
This is not “undo both parents”; it means produce the inverse of the
merge relative to the selected mainline.
Use merge when the desired result is to integrate histories and preserve the fact that they were independently developed. Whether that creates a new merge commit depends on fast-forward rules and flags.
| Mode | Possible topology | New commit? | Use when |
|---|---|---|---|
default --ff |
fast-forward if possible, otherwise true merge | sometimes | normal integration where fast-forward is acceptable |
--no-ff |
always records merge node | yes | topology should record branch integration even if FF possible |
--ff-only |
fast-forward only | no | policy requires no merge commit |
--squash |
applies merge result to index/worktree, no merge commit metadata | not by the merge command itself | want one resulting commit without recording merge ancestry |
git fetch origin
git merge --ff-only origin/mainDuring a true merge conflict, HEAD remains the current
side, MERGE_HEAD names the incoming side, and the index can
carry stage 1/2/3 entries. The working tree contains the attempted
merged content, including conflict markers for textual conflicts.
git status
git ls-files -u
git diff --ours
git diff --theirs
# resolve files
git add <resolved-files>
git merge --continue
# or
git merge --abortgit rebase <upstream> identifies commits reachable
from the current branch but not the selected upstream, then replays them
on the new base (with caveats for merge-preserving modes and special
options). It creates new commit objects for the replayed commits because
their parent relationship and/or metadata differ.
BEFORE
A---B---C main
\
D---E feature
rebase feature onto main
A---B---C main
\
D'---E' feature
D != D'
E != E'
git switch feature
git rebase main
git log --graph --decorate --oneline --allA commit includes parent IDs. Rebasing changes the parent of replayed commits, which changes the commit object’s bytes and therefore its ID. The old commits may remain in the repository for a time if reachable from reflogs or other refs.
git status
git diff
# resolve and stage
git add <paths>
git rebase --continue
# skip the current patch if it is intentionally unnecessary
git rebase --skip
# abandon
git rebase --abortOurs/theirs trap: during rebase, Git’s internal
“ours”/“theirs” perspective is counter-intuitive compared with a normal
merge. Git’s documentation explicitly warns that the labels can appear
swapped because the “current” side is the rebased series being applied
onto the new base. Do not make a policy around
--ours/--theirs without understanding the
active operation.
git rebase --onto: surgical graph surgeryFor git rebase --onto NEWBASE OLDBASE BRANCH, think:
Replay = Reach(BRANCH) \ Reach(OLDBASE)
Destination = NEWBASE
Operation = replay Replay, in ancestry order, onto NEWBASE
Example:
A---B
\
E---F topic
\
G---H feature
Goal: keep G,H but move them directly onto B.
Action:
git rebase --onto B F feature
Result:
A---B------G'---H' feature
\
E---F topic
git rebase --onto B F featureThis is one of the highest-value expert commands because it lets you express “remove this ancestor region and replay what remains” directly.
Interactive rebase is a history-editor, not merely a conflict tool.
| Action | Effect | Typical use |
|---|---|---|
pick |
replay commit | keep |
reword |
replay but change message | clarify |
edit |
stop to modify commit | split, amend, fix |
squash |
combine with previous, keep/edit messages | collapse related work |
fixup |
combine with previous, discard this commit message | fold cleanup |
drop |
omit commit | remove accidental history |
exec |
run shell command during sequence | per-commit tests/formatters |
break |
pause the sequence | deliberate inspection |
Example transformation:
A(feature)---B(typo)---C(debug)---D(feature)---E(logging)
interactive rebase can produce:
A(feature)-------------------B(logically complete)
Before rewriting pushed history, classify the branch as private, private-but-pushed, shared, protected, release/public, or deployed. The mechanics may work even when the collaboration policy should forbid them.
Cherry-pick means: take the patch introduced by an existing commit
and apply it to the current HEAD; normally record the
result as a new commit. It is therefore a
change-transfer operation, not a branch merge.
source history
A---B---X---Y
X = bug fix
target history
A---B---M
\
X' = replayed bug fix
git switch release/1.0
git cherry-pick XUse -x when the environment benefits from recording the
source commit ID in the message. Use --no-commit to apply
the patch to index/worktree without making a commit immediately. During
a conflict, use git cherry-pick --continue,
--skip, or --abort as appropriate.
git cherry-pick A..B selects commits reachable from B
but not A under the revision-set semantics;
git cherry-pick A^..B includes A. This difference is subtle
and central to correct backport ranges. Inspect with
git rev-list --reverse before mutating when the set
matters.
git rev-list --reverse A..B
git rev-list --reverse A^..B-mA merge commit has multiple parents and therefore does not have a
single unambiguous patch to replay.
git cherry-pick -m <parent-number> <merge>
tells Git which parent should be treated as the mainline when computing
the inverse/forward difference.
origin/main is local
stateorigin/main normally resolves to a local
remote-tracking ref, such as
refs/remotes/origin/main. It is not a live network pointer.
A fetch updates that local ref according to the remote’s refspec.
Remote repository
refs/heads/main -> R5
│
│ git fetch
▼
Local repository
refs/remotes/origin/main -> R5
After another developer pushes R6, your origin/main
still names R5 until you fetch. This is why a local lease based on
remote-tracking state can be stale.
Conceptually:
remote refs + required objects
│
▼
local object database
+
refs/remotes/<remote>/*
+
FETCH_HEAD
git fetch origin
git show-ref --heads --remotes
cat .git/FETCH_HEADGit’s fetch documentation states that fetch downloads refs and
objects needed to complete their histories, updates remote-tracking
branches according to refspecs, and writes fetched refs/object names to
.git/FETCH_HEAD by default.
git fetch --prune origin
git remote prune originPruning removes stale local remote-tracking refs; it does not magically delete remote branch data everywhere.
git pull is a convenience workflow combining fetch with
an integration choice.
| Mode | Concept |
|---|---|
| default/configured | fetch, then configured integration behavior |
git pull --rebase |
fetch then rebase local work on fetched upstream |
git pull --no-rebase |
fetch then merge |
git pull --ff-only |
fetch then refuse unless a fast-forward is possible |
For production branches, explicitly selecting the integration policy is usually easier to reason about than relying on user/global configuration.
Conceptually:
local source ref
refs/heads/main -> M
│
│ negotiate/send objects
▼
remote ref
refs/heads/main -> M
git push origin main
git push -u origin feature/payment-refactorWithout force semantics, a remote branch update that would discard commits can be rejected as non-fast-forward. This is a server-side ref policy check layered over the transport and object transfer protocol.
| Option | Concept | Main risk |
|---|---|---|
--force |
override normal non-fast-forward safety checks | can overwrite remote commits unexpectedly |
--force-with-lease |
require a remote-ref expectation (“lease”) before forced update | still unsafe if your expectation is stale/incorrect or policy is inappropriate |
--force-if-includes |
additional protection tied to whether remote-tracking tip was integrated | more robust, but must understand configured refs/reflogs |
Git’s push documentation explicitly describes --force as
capable of losing remote commits and --force-with-lease as
a safety check against an unexpected remote state.
git fetch origin
git log --graph --decorate --oneline --all
# inspect the exact remote-tracking tip
git rev-parse origin/feature-x
# rewrite local feature branch
git rebase ...
# then, if a rewrite is actually intended:
git push --force-with-lease origin feature-xNever assume “lease” means “safe.” It is safer than blind force because it constrains the remote state you are willing to overwrite. It is not a substitute for coordination or branch protection.
A reflog records updates to a ref locally. It is not a second commit
graph and it is not a universal server-side undo log. It is a record of
where refs and HEAD have pointed over time, which
frequently makes previously visible commits recoverable.
git reflog
git reflog show HEAD
git reflog show main
git reflog --allCommon disaster:
A---B---C---D feature
user runs git reset --hard B
visible branch:
A---B
HEAD reflog still records a prior position at D
git reflog
git branch recovery HEAD@{1}
# or use the exact OID
git show <lost-commit>The safe recovery sequence is: inspect the reflog, identify the exact desired OID, create a recovery ref before experimenting further, then decide how to reintegrate it.
ORIG_HEAD
and operation safety netsGit uses pseudorefs such as ORIG_HEAD during operations
that move HEAD drastically. These are useful evidence
during recovery, but reflogs are often more informative because multiple
transitions are recorded.
git show ORIG_HEAD
git rev-parse ORIG_HEADGit maintenance can repack objects, update ancillary indexes, and eventually remove objects no longer protected by repository reachability/retention rules. Current Git documentation describes default expiry values for reflogs and prune grace periods and notes that reachable objects referenced from refs, reflogs, the index, and other namespaces are protected.
| Term | Operational meaning |
|---|---|
| reachable | can be reached from relevant protected references/object roots |
| unreachable | not currently reachable from those roots |
| dangling | a valid object not referenced by another reachable object in the
manner fsck reports |
| pruned | no longer retained in the object database after cleanup rules permit deletion |
git fsck --full --no-reflogs
git gc
git count-objects -vDo not build an operational policy that assumes reflog recovery works forever. Reflog entries can expire, unreachable-object grace periods can elapse, repositories can be repacked/pruned, and alternate copies may or may not exist. Recovery gets progressively more difficult as references and retention evidence disappear.
A conflict is not “Git has two text files and is confused.” It is a state where Git could not automatically produce one index entry for a path. For a normal merge, the index stores base/ours/theirs stages. Your task is to choose the intended resulting content, write that content, and collapse the path to stage 0 by staging it.
git status
git ls-files -u
git diff --ours
git diff --theirs
# edit
git add file
git merge --continue| Operation | “ours” means | “theirs” means | Key nuance |
|---|---|---|---|
| normal merge | current HEAD side |
incoming MERGE_HEAD side |
intuitive case |
| rebase | current rebased branch/upstream side in the implementation’s operation perspective | commit being replayed | labels can feel swapped; follow the rebase docs/state |
| cherry-pick conflict | current HEAD side |
the picked commit’s side | sequencer state uses CHERRY_PICK_HEAD |
git stash stores a working-state snapshot so you can
temporarily restore a clean working tree. The exact stash structure is
richer than “a zip of my changes”; depending on options and Git version
it can represent tracked changes, staged state, and optionally
untracked/ignored files.
git stash push -m "incident checkpoint"
git stash list
git stash show -p stash@{0}
git stash apply stash@{0}
# or apply and remove if successful
git stash popapply re-applies the stash but retains the stash entry.
pop attempts to apply it and then drops it if application
succeeds according to Git’s stash semantics. During incident recovery,
apply is often easier to reason about because it keeps the
original stash as a recovery artifact until you verify the result.
git stash push -u
# includes untracked files
git stash push -a
# includes ignored files as wellBe explicit about what you are protecting. If the thing you need to recover is not in the stash snapshot, it cannot be recreated by popping the stash.
git status
git branch --show-current
git log --graph --decorate --oneline --all --reflog
git branch -vv
git show-ref
git for-each-ref
git rev-parse HEAD
git reflog --all -50
git diff
git diff --cached
git remote -v
git fsck --full| Question | Command family |
|---|---|
| What am I on? | git status, git branch --show-current,
git symbolic-ref HEAD |
| What is the DAG? | git log --graph --decorate --oneline --all --reflog |
| Where does commit X exist? | git branch --contains X,
git tag --contains X,
git rev-list --all --contains X |
| What does X change? | git show X, git diff X^ X |
| What is staged? | git diff --cached |
| What is local vs remote? | git branch -vv, git show-ref,
git ls-remote origin |
| What ref moved? | git reflog show <ref> |
| Does A contain B? | git merge-base --is-ancestor B A |
| What is the common base? | git merge-base A B |
| Are there dangling objects? | git fsck --full |
git rev-list: set algebra over historygit rev-list is one of the best tools for answering
“what commits are in this set?”
git rev-list main
git rev-list origin/main..feature
git rev-list --left-right --count origin/main...feature
git rev-list --ancestry-path A..BUse it to preflight a cherry-pick range, quantify divergence, determine candidate commits for backporting, or prove that one ref contains another.
git range-diffgit range-diff compares two versions of a patch series
and helps reviewers understand whether a rebase/rewrite materially
changed the series.
git range-diff old-base..old-tip new-base..new-tipIt is particularly valuable when a developer force-pushes an amended series and the reviewer needs to compare version N-1 to version N instead of re-reading the entire history.
git blame →
git show → git bisectUse blame as a locator, not as a verdict about responsibility:
symptom
↓
suspicious line
↓
git blame
↓
commit X
↓
git show X
↓
parent / surrounding commits
↓
git bisect if the regression boundary is uncertain
git blame -L <start>,<end> -- path/to/file
git show <commit>
git bisect start
git bisect bad
git bisect good <known-good>
# test each checkout
git bisect resetFor N candidate commits, bisection typically takes O(log2 N) test iterations, assuming a deterministic good/bad predicate over the chosen history.
git worktree lets one repository have multiple working
directories tied to different branches/commits.
git worktree add ../hotfix hotfix/2026.09
git worktree add ../release release/2026.09
git worktree list
git worktree remove ../hotfix
git worktree pruneOperationally, worktrees are useful when an engineer must inspect or patch another line while retaining an in-progress working tree. They avoid the cognitive and state-management cost of repeated stash/switch cycles.
rererererere records conflict resolutions so that recurring
identical conflict shapes can be reused, particularly valuable for
long-running branches rebased repeatedly.
git config rerere.enabled true
git rerere status
git rerere diffGit’s documentation notes that rerere records
preimage/resolution data and can reuse it; enabling rerere does not mean
you should blindly accept every automatic resolution. Inspect the result
and stage it deliberately.
git format-patch serializes commits into mail-oriented
patch files; git am applies such patches as commits. This
remains useful for kernel-style workflows, offline transfer, selective
backporting, and cross-repository change movement.
git format-patch -1 <commit>
git am 0001-some-change.patchPatch transfer differs from cherry-pick in operational emphasis: patch files are portable artifacts, while cherry-pick operates directly on commit objects available in the repository graph.
| Tag type | Representation | Metadata | Typical role |
|---|---|---|---|
| lightweight | ref → target object | none beyond ref name | private/simple labels |
| annotated | ref → tag object → target | tagger/message/signature possible | releases / auditable milestones |
| signed | annotated tag with cryptographic signature | signer evidence | stronger authenticity / release processes |
git tag v1.2.3
git tag -a v1.2.3 -m "Release 1.2.3"
git show v1.2.3
git push origin v1.2.3
# or all tags, deliberately
git push origin --tagsBe careful deleting or moving release tags. Tag names are human-facing release identifiers and are often consumed by CI/CD, package managers, and deployment tooling.
Client-side hooks include pre-commit,
commit-msg, pre-push, post-merge,
and post-checkout. Hooks are powerful for local quality
gates but are not a substitute for server-side or CI enforcement because
local hooks can be absent or bypassed depending on configuration and
workflow.
Good hook responsibilities:
Keep slow, authoritative, or security-critical enforcement in CI/hosting policy as well.
One useful conceptual construction is:
working files
│
├─ git add ────────────────┐
▼ │
index │
│ │
└─ git write-tree ───────► tree OID
│
└─ git commit-tree <tree> -p <parent>
│
▼
commit OID
│
└─ git update-ref refs/heads/main <commit>
git add <files>
TREE=$(git write-tree)
COMMIT=$(printf "message\n" | git commit-tree "$TREE" -p "$(git rev-parse HEAD)")
git update-ref refs/heads/main "$COMMIT"
git rev-parse HEAD
git cat-file -p "$COMMIT"This is not a replacement for git commit. It is a mental
bridge between porcelain commands and the underlying object/ref
machinery.
Never collapse these into one action:
| Problem | Required treatment |
|---|---|
| secret exists in working tree only | remove/rotate as appropriate; do not commit |
| secret exists in latest local commit | revoke/rotate; remove before sharing; rewrite local history if needed |
| secret exists in shared/public history | revoke/rotate first; coordinate history remediation and affected copies |
| secret exists in remote caches/forks/PRs/artifacts | use hosting/security-specific cleanup processes in addition to Git rewrite |
GitHub’s current sensitive-data guidance explicitly says revocation/rotation is the first priority for real secrets and explains that history rewriting has side effects, including changed commit hashes, coordination requirements, and residual copies in clones/forks/caches/PRs. See the source in the bibliography.
For modern GitHub-oriented remediation, git filter-repo
is the recommended external tool; the older
git filter-branch command is explicitly discouraged by its
own documentation.
git filter-repo --sensitive-data-removal --invert-paths --path path/to/secret.txt
# or replace sensitive text patterns
git filter-repo --sensitive-data-removal --replace-text ../replacements.txt1. Revoke/rotate secret
2. Freeze / coordinate affected pushes if necessary
3. Clone a clean mirror / working copy
4. Rewrite history
5. Inspect changed refs and affected commits
6. Force-update the central repository according to the incident plan
7. Coordinate clone cleanup / fork cleanup
8. Clean hosting caches/PR references when supported
9. Verify the secret is absent from relevant refs and artifacts
10. Prevent recurrence
A commit that deletes the file creates a new tree state, but the earlier commit containing the secret remains reachable from history. Anyone who can access that commit can still inspect the old content. Therefore “delete it in the next commit” is not history erasure.
As repositories scale, the constraints shift from “can Git model this?” to “how much data must each developer materialize and how much history must common commands traverse?”
Important mechanisms:
| Technique | Problem it addresses |
|---|---|
| sparse checkout | checkout only selected paths |
| sparse index | reduce index traversal for sparse working trees |
| partial clone / promisor objects | avoid downloading all object content up front |
| shallow clone | limit history depth when full history is unnecessary |
| Git LFS | store large binary payloads outside normal Git blob storage |
| submodules | compose repositories while recording exact submodule commits |
| subtree | vendor/merge another project’s history into one repository |
Do not use shallow history for workflows that require complete ancestry (for example, some bisect/release/security investigations) without a plan to deepen the clone.
Git LFS changes the representation seen in normal Git history: Git tracks pointer files while LFS storage holds the large object payloads separately. Clone/fetch/checkout workflows may therefore involve Git objects and LFS objects as two related but distinct transfer/storage systems.
If someone commits a huge binary directly as a normal Git blob, the blob becomes part of Git history. Deleting it later does not remove the historical object; every clone that fetched that history may already have it. Moving to LFS later often requires a history migration and careful coordination.
A superproject records a gitlink identifying the exact commit of a submodule. This is why the superproject can pin a dependency to a specific submodule commit without containing the submodule’s full file history in its own tree.
git submodule add <url> path/to/submodule
git submodule init
git submodule update
git submodule sync
git submodule statusCommon production failure modes include: forgetting to push the referenced submodule commit, checking out a superproject commit that points to a submodule commit the developer has not fetched, and accidentally modifying a detached submodule checkout without updating the superproject gitlink.
Important repository performance mechanisms include packfiles, delta compression, commit-graph, multi-pack-index, bitmaps, fsmonitor, sparse checkout/index, partial clone, and maintenance tasks.
git count-objects -vH
git maintenance run --auto
git repack -ad
git commit-graph write --reachable
git multi-pack-index write
git statusDo not run expensive repack/aggressive maintenance blindly on a production repository under concurrent load. Git’s maintenance documentation distinguishes automatic maintenance from explicit, potentially expensive optimization steps.
Git defines objects, refs, transport protocols, local history manipulation, and core merge/rebase semantics. Hosting platforms add policy and collaboration layers such as pull/merge requests, protected branches, review requirements, CODEOWNERS integration, merge queues/trains, status checks, and UI/API workflows.
GitHub protected branches can enforce review requirements, status checks, linear history, signed commits, merge queues, deployment checks, force-push restrictions, and deletion restrictions. These are repository-hosting policies, not primitives of local Git itself.
GitLab protected branches similarly control who can push/merge/force-push/delete, and GitLab supports merged-results pipelines and merge trains for validating changes against integration state.
Always name the layer when documenting an operational rule: Git, host policy, CI, deployment controller, or artifact registry.
Keep these four objects conceptually separate:
Git commit/ref
↓
Build artifact (container, binary, package)
↓
Deployment record / environment state
↓
Actual production behavior
A successful Git revert does not necessarily roll back a production environment. A deployment can be built from an immutable artifact that no longer maps 1:1 to a branch tip. Rollbacks should therefore be expressed as artifact/deployment operations when the deployment system is the source of truth, with Git history used as the source of code lineage and desired-state intent.
| Strategy | Typical shape | Strength | Cost / risk |
|---|---|---|---|
| trunk-based | short-lived branches → main | low divergence | demands strong CI/review/release discipline |
| GitHub Flow-like | short-lived feature branches → main | simple mental model | release coordination may need another layer |
| release branches | main + supported release lines | explicit stabilization/backport lane | divergence and cherry-pick management |
| GitFlow-like | develop/release/hotfix branches | explicit long release choreography | more refs and merge complexity |
| monorepo + ownership | shared trunk + path governance | unified dependency changes | scale requires tooling and policy |
Choose topology based on release cadence, compatibility windows, audit requirements, team count, and deployment architecture—not on folklore.
A merge queue addresses the problem that “PR passed against branch state X” does not prove that multiple PRs will compose successfully in the eventual target branch. GitHub and GitLab implement different hosting-level mechanisms, including GitHub merge queue and GitLab merge trains/merged-results pipelines.
From a Git mental-model perspective, the important point is that CI may test a synthetic merge state that is not yet a durable branch ref. Treat the tested commit / merge result as a build input, then separately identify the resulting integrated ref and deployment artifact.
main
│
├── release/2026.09
│ │
│ └── hotfix/2026.09.1
│
└── feature branches
Common release flow:
feature → main
main → release branch
hotfix → release branch
hotfix → main (forward-port)
release tag → artifact → deployment
The exact branch policy varies. The invariant is to make the propagation direction explicit so that a hotfix does not disappear from the forward-moving branch and a backport does not accidentally import unrelated feature history.
main: A---B---C---D
release/1.0: A---B
D = fix
Desired:
release/1.0: A---B---D'
git switch release/1.0
git cherry-pick D
# resolve/verify
git diff HEAD^ HEAD
git show --stat --oneline HEADBefore cherry-picking, inspect the patch’s dependencies. A fix that relies on changes introduced after the release base may not apply cleanly or may compile but be semantically invalid.
main
│
A---B main
\
C---D---E feature/payment-refactor
\
H production hotfix accidentally committed here
Assume C and D are feature work and H is the one-line production fix. The required end state is:
main
│
A---B---H' main / hotfix lineage
\
C---D---E feature
First question: is H’s patch independent of C/D/E? A commit’s diff is relative to its parent, so applying H to B may conflict if its patch context depends on feature changes. The DAG alone does not tell you whether the patch is semantically portable.
git branch hotfix H
git switch feature/payment-refactor
git reset --hard E
git switch main
git cherry-pick hotfixUse this when H is the tip, feature history is private or otherwise safe to rewrite, and you want to retain H exactly as the source commit for a clean transfer. Verify the feature branch ref moved to E and the hotfix branch still points to H.
git switch main
git cherry-pick HUse this when the feature branch is shared or there is no need to remove H from it immediately. If H should not remain on the feature branch, a later cleanup may be considered, but the collaboration impact must be assessed first.
git switch feature/payment-refactor
git rebase -i <base-before-C>
# reorder/drop/edit so feature work and H are separated
# then update main/hotfix appropriatelyUse only when history is private enough to rewrite and the sequence is easier to transform than to cherry-pick. This can be the most surgical approach when H is not the tip or when feature and fix are entangled across adjacent commits.
rebase --ontoWhen the hotfix is separated by an identifiable ancestry boundary,
rebase --onto can rebase the feature suffix while keeping
the hotfix commit referenced elsewhere. Derive the exact old-base by
computing which commits should be replayed, not by copying a memorized
incantation.
git show --format=email --patch H > /tmp/hotfix.patch
git switch main
git apply --index /tmp/hotfix.patch
git diff --cached
git commit -m "Hotfix: ..."Patch extraction is useful when commit identity/topology is less important than preserving exactly the change. If H’s patch contains feature-only context, expect manual adjustment.
| State of feature branch | Operational consequence | Typical posture |
|---|---|---|
| local only | history rewrite is cheap | branch at H, restore feature tip, transfer fix |
| pushed private branch | rewrite may require force-with-lease | coordinate with yourself/automation; preserve backup ref before rewrite |
| shared team branch | history rewrite affects collaborators | prefer add-only change transfer; avoid rewriting the shared ref |
| already merged | downstream graph already contains the fix | decide whether it is logically a duplicate; avoid unnecessary surgery |
| already deployed | Git correction does not equal production rollback | use deployment rollback/forward-fix policy plus Git lineage cleanup |
When the hotfix is still mixed into working-tree state, capture it as a patch or isolate it with partial staging before switching context.
git diff > /tmp/hotfix-working-tree.patch
git status
git switch main
git apply --index /tmp/hotfix-working-tree.patch
git diff --cached
git commit -m "Hotfix: ..."This assumes the patch is contextually applicable. If feature and
hotfix edits are intermixed in the same hunk, use
git add -p, git restore -p, manual separation,
or a temporary worktree to isolate the exact content.
Do not try to “make main look right” with an uncoordinated local reset. A protected/shared branch should usually be repaired through a history-preserving revert or an approved incident-specific rewrite procedure. Hosting policy may reject force pushes regardless of local Git state.
Emergency default: stop mutating refs until you have a backup ref, a known OID, or a saved patch/stash when that is practical.
Branch ref is gone locally.
git reflog --all -50
git reflog show HEAD
git branch recovery <oid>Once you identify the last correct tip, recreate the intended branch
name with git branch <name> <oid>.
Do not run aggressive pruning first.
git reset --hardThe branch now points earlier and working tree/index match the target.
git reflog show HEAD
git show <candidate-oid>
git branch recovery <candidate-oid>Recover via the reflog or another ref before continuing work.
Do not assume the data is gone merely because the branch moved.
Your branch now contains rewritten commit IDs.
git reflog --all
git log --graph --decorate --oneline --all --reflogCreate a recovery ref at the pre-rebase tip; then decide whether to reset back or keep the rewrite.
Do not immediately force-push the rewritten branch.
A change exists under the wrong branch tip.
git show HEAD
git branch --contains HEAD
git log --graph --oneline --allIf local/private, create the correct branch at the commit then move the wrong branch back; otherwise transfer the patch with cherry-pick.
Do not delete the only ref until the commit is safely named elsewhere.
A remote ref changed unexpectedly.
git fetch origin
git show-ref --remote
git reflog --allIf shared, use a preserving correction (often revert) and coordinate. If a private branch was rewritten, use an appropriate lease.
Do not force the intended branch just because the wrong branch was updated.
Remote history changed unexpectedly.
git fetch origin
git reflog --all
git fsck --fullIdentify previous remote tip from another clone, CI checkout, teammate reflog, or server evidence; create a recovery ref before deciding on restoration.
Do not blindly force-push a different guess.
Your local and remote refs diverged unexpectedly.
git fetch origin
git log --graph --decorate --oneline --all --reflogName your local work with a recovery branch, inspect remote history, then coordinate a reconciliation strategy.
Do not delete your local branch or hard reset before creating a recovery ref.
A merge commit or fast-forward incorporated unintended history.
git show --summary HEAD
git log --graph --decorate --oneline --all
git reflogIf private, reset/rebuild the merge. If shared, consider reverting the merge with the correct mainline semantics.
Do not revert a merge without understanding -m.
A credential/token/key is in Git history.
git show --stat HEAD
git log --all -- <path>
# then immediately revoke/rotate the secretRevoke/rotate first; then clean history and hosting copies according to the security incident plan.
Do not rely on a later delete commit as remediation.
Large/generated content entered tracked history.
git show --stat HEAD
git status
git log --oneline -- <generated-path>If unshared, amend/reset and add ignore rules. If shared, decide whether the content should remain and whether history rewrite is worth the coordination cost.
Do not delete history without establishing policy and ownership.
A public branch contains a bad commit.
git show <bad-commit>
git branch -r --contains <bad-commit>
git log --graph --decorate --oneline --allPrefer git revert <bad-commit> for shared/public
history unless an approved rewrite is required.
Do not reset shared history as the default undo mechanism.
The rebase has stopped with many conflicts.
git status
git rebase --show-current-patch
git diff --name-only
git ls-files -uResolve the current patch as a state problem; use
--continue, --skip, or --abort
deliberately.
Do not mix merge-specific assumptions into a rebase without checking the active sequencer state.
The target branch cannot apply the source patch cleanly.
git status
git cherry-pick --show-current-patch
git diff --ours
git diff --theirsResolve, stage, git cherry-pick --continue; skip only
when the patch is intentionally unnecessary.
Do not use --theirs blindly across files.
Commits exist without a branch ref pointing to the tip.
git log --oneline --decorate -10
git reflog -20
git branch rescue <oid>Create a branch at the desired detached tip, then continue normally.
Do not switch away repeatedly before naming the commit if it is important.
A stash was popped/dropped or disappeared.
git reflog --all
git fsck --full --no-reflogsSearch dangling commits/objects and identify stash parent structures; recovery becomes more difficult as objects age out.
Do not immediately run pruning or aggressive maintenance.
A release tag ref was removed.
git reflog --all
git fsck --full
git show <candidate-oid>Recreate the tag at the exact prior target; confirm annotated-tag object vs target commit.
Do not create a new tag at “the current release” without proving equivalence.
Local and upstream refs have diverged.
git fetch origin
git rev-list --left-right --count HEAD...origin/$(git branch --show-current)
git log --graph --decorate --oneline --allChoose merge/rebase/reset based on sharing and intended topology.
Do not use git pull blindly when the branch has an
unclear history policy.
Push or merge attempted a non-descendant update.
git fetch origin
git merge-base --is-ancestor origin/main main; echo $?
git log --graph --decorate --oneline --allDetermine whether remote work should be integrated, or whether a history rewrite is intentional and permitted.
Do not add --force merely to silence the error.
The two tips have no common ancestor in the expected graph.
git merge-base A B; echo $?
git log --graph --decorate --oneline --all --boundaryValidate whether two repositories were accidentally combined, then
choose an explicit migration/import strategy.
--allow-unrelated-histories is a deliberate exception, not
a repair button.
Do not merge unrelated histories just because Git offered an option.
Commands report missing/broken objects or invalid refs.
git fsck --full
git show-ref
git count-objects -vH
git verify-pack -v .git/objects/pack/*.idx 2>/dev/null | head -50Freeze destructive maintenance, capture a filesystem backup, locate alternate clones/object sources, and repair refs/object stores methodically.
Do not run git gc / git prune first in a
repository where recovery evidence may still be needed.
The human-facing release name has incorrect target state.
git show-ref --tags
git rev-parse <tag>
git show <tag>Determine whether the tag is immutable by policy. For a public release, publish a corrected tag/version rather than silently moving the existing name unless policy explicitly permits retagging.
Do not force-move a release tag without understanding downstream consumers.
One commit contains unrelated logical changes.
git show --stat <commit>
git show <commit>
git switch -c split-work <commit>^Use interactive rebase/edit + mixed reset or patch-based staging to construct multiple clean commits.
Do not manipulate the index without verifying each resulting diff.
The desired fix is mixed with unrelated changes in the same commit.
git show <commit>
git diff <commit>^ <commit>
git merge-base HEAD <commit>Use cherry-pick if the whole patch is acceptable; otherwise extract
selected hunks with
git show/git apply/git add -p.
Do not assume a commit is an indivisible unit of useful change.
You inherited or reopened a messy checkout.
git status --short
git diff
git diff --cached
git stash push -u -m "checkpoint"Checkpoint before mutation; classify changes before cleaning or switching branches.
Do not use git clean -fdx as housekeeping without
enumerating what it will remove.
Each case uses the required pattern: Context → Current DAG → Mistake → Diagnosis → Procedure → Intermediate State → Final DAG → Verification → Alternatives → Lessons.
A developer is on a private feature branch and accidentally adds a production fix.
main A---B; feature C---D---H
Create a hotfix ref at H; move feature back to D; transfer H to main.
git branch hotfix H
git reset --hard D
git switch main
git cherry-pick hotfixmain A---B---H' ; feature C---D ; hotfix -> H
git branch -vv; git show H; git show main; git diff D..featureA patch-only extraction is preferable when H depends on feature context.
Private history enables surgical rewrite; once shared, transfer-first is usually easier.
Working tree contains feature edits plus a one-line production fix.
main A---B; feature C---D; W = feature + hotfix mixed
Capture or isolate the hotfix before changing refs.
git diff > /tmp/hotfix.patch
git switch main
git apply --index /tmp/hotfix.patch
git diff --cached
git commit -m "Hotfix"main A---B---H; feature still has original working state to clean up
git status; git diff --cached; git show HEADPartial staging is better than a broad patch when hunks can be separated.
When content is mixed inside one hunk, the real problem is content separation, not branch movement.
A feature branch was reset two commits back.
before A---B---C---D feature; after A---B feature
Use reflog to identify C/D and name the intended tip.
git reflog show feature
git branch rescue <oid>A---B---C---D rescue; feature remains B until chosen
git log --graph --decorate --oneline --all --reflog; git show rescueRecovery may use ORIG_HEAD when available, but reflog is
the broader record.
A ref disappearing does not imply immediate object deletion.
A bad commit is already in main.
A---B---C bad---D main
Create an inverse commit rather than moving main backward.
git revert C
git show --stat HEADA---B---C---D---E main, E reverses C
git diff C^ C; git show E; CI/deployment checksIf the incident requires restoring an exact prior artifact, deploy the prior artifact; Git revert is source-history correction.
Production rollback and source rollback are related but not identical.
Engineer rewrites a private branch while another clone pushes new work.
remote R2; local remote-tracking R2; other clone pushes R3; local rewrite proposes R2→R4
Use fetch/lease semantics so the stale remote state is detected.
git fetch origin
git push --force-with-lease origin featureThe stale lease rejects the overwrite until the engineer reconciles the remote.
Inspect rejection; fetch; compare DAG; replay or rebase; retry with correct lease.Blind force would not provide the same guard.
A lease is only as good as the expected remote state and the operator’s discipline.
A feature branch was rebased onto a new main.
A---B---C main; B---D---E feature
Treat D/E as rewritten copies D’/E’.
git switch feature
git rebase mainA---B---C---D'---E' feature
git log --graph --decorate --oneline --all; compare old/new with `git range-diff`Merge may avoid identity rewriting but preserves the original topology.
Commit identity is a graph/object property, not a stable “change ID”.
Fix exists on main but must be applied to release/1.0.
main A---B---C---D fix; release A---B
Replay D onto the release line.
git switch release/1.0
git cherry-pick Drelease A---B---D'
run targeted tests; `git diff B D'`; verify artifactPatch extraction can be used when the commit contains unrelated change.
Backport correctness is semantic, not just conflict-free application.
Interactive rebase reordered/dropped the wrong commits.
pre-rebase A---B---C---D; post-rebase A---B---C'---D'
Find pre-rebase tip in HEAD reflog, create recovery ref, then decide.
git reflog show HEAD
git branch pre-rebase <oid>Both old and new lines are named; no evidence is destroyed.
git log --graph --all --decorate --reflog; compare with range-diffIf collaboration already occurred, recovery may mean preserving both lines until coordination is complete.
Recovery starts by naming states, not by guessing the reset target.
Main and side edit the same file differently.
A---B main; A---C side
Merge and inspect index stages.
git merge side
git ls-files -uindex stages 1/2/3; resolve → `git add` → merge commit
git status; git ls-files -u should become empty; inspect merge commit parentsAbort the merge if the base is wrong.
The index is the authoritative unresolved merge state.
Engineer checked out a historical commit, built a fix, and committed twice.
A---B---C; detached tip D---E
Create a branch at E.
git branch rescue-detached E
git switch rescue-detachedA---B---C; rescue-detached E
git show rescue-detached; git status; git branch --contains EIf E has to be integrated elsewhere, cherry-pick or rebase based on intent.
Detached does not mean “uncommitted”; it means no branch ref advances automatically.
A vendor commit includes hundreds of unrelated updates and one critical line.
A---V big change
Extract only the desired patch rather than cherry-picking the entire commit.
git show V > /tmp/vendor.patch
# apply selected hunks manually or with git apply
git add -p
git committarget branch gains a small surgical commit
git diff --cached; tests; `git show HEAD`A dedicated upstream patch or filtered cherry-pick can be cleaner if maintainable.
Commit boundaries are useful organizational units, but they do not dictate the unit of transfer.
A credential appears in a public repository.
A---B(secret)---C main
Revoke/rotate first, then coordinate history cleanup.
revoke/rotate secret
# clean a fresh clone with git-filter-repo
# force-update refs only after coordinationnew rewritten graph without the secret in relevant refs
search all refs; inspect changed refs; clean forks/caches/PRs as requiredIf the secret is already revoked and compliance impact is low, full rewrite may be unnecessary depending on policy.
History cleanup cannot retroactively un-expose copies already made.
Many fixes exist on both main and release with different ancestry.
main A-B-C-D-E; release A-B-F-G
Model common base and patch sets before choosing merge vs cherry-pick.
git merge-base main release
git rev-list --left-right --count release...main
git log --graph --allExplicit propagation plan
verify release artifact and forward-port hotfixesA merge may be appropriate for topology; cherry-picks can preserve independent release lines.
Divergence is an organizational state, not merely a Git error.
Remote feature branch was overwritten with unrelated history.
remote old X; remote new Y; local still X
Recover old X from a clone/reflog/CI checkout, name it, then coordinate.
git branch recovered-X X
# compare Y vs X before any remote rewriterecovery ref X preserved
inspect hosting audit/PR refs and all known clonesDo not immediately push X back over Y until all relevant work is captured.
The first goal is evidence preservation.
A developer rebases and force-pushes a 12-commit series.
series v1 and series v2 differ only in the rewrite
Use range-diff to correlate commits.
git range-diff base..oldtip base..newtipreviewer sees patch correspondence and changed hunks
run CI on v2 and verify target refA plain git log comparison may obscure which commits
correspond.
For complex rebases, patch-series comparison is a first-class review operation.
| Layer | Examples | Why experts care |
|---|---|---|
| porcelain | status, add, commit,
switch, restore, merge,
rebase, cherry-pick |
human workflow |
| inspection plumbing | rev-parse, show-ref,
cat-file, ls-tree, rev-list |
precise introspection |
| ref plumbing | update-ref, symbolic-ref |
controlled reference mutation |
| object plumbing | hash-object, write-tree,
commit-tree |
object-model experiments / automation |
rev-parse: resolve names to exact valuesgit rev-parse HEAD
git rev-parse refs/heads/main
git rev-parse --symbolic-full-name @{upstream}
git rev-parse --show-toplevelUse rev-parse when scripts need canonical values and
names without depending on display-oriented command output.
for-each-ref: inspect the ref namespace systematicallygit for-each-ref --format="%(refname:short) %(objectname)" refs/heads refs/remotes refs/tagsThis is superior to parsing human-oriented git branch
output when building tooling.
update-ref: compare-and-swap for refsold=$(git rev-parse refs/heads/main)
new=<new-oid>
git update-ref refs/heads/main "$new" "$old"The expected-old parameter makes the operation fail rather than silently overwrite an unexpected current ref value. This is the right mental primitive for race-aware ref automation.
hash-object: content addressing in the openoid=$(git hash-object path/to/file)
echo "$oid"
git cat-file -t "$oid"With -w, hash-object writes the object into
the repository object database. Without -w, it computes the
identifier without storing the object.
fsck and pack
inspectiongit fsck --full
git count-objects -vH
git verify-pack -v .git/objects/pack/*.idxUse these for repository forensics, not as casual daily commands. Pack internals can reveal where objects physically live and which objects remain even when refs moved.
Advanced repositories may have refs/replace/*, graft
files, alternates, shallow boundaries, and promisor/partial-clone
metadata. These features can change what “history” means for a given
command. Forensic scripts should inspect unusual namespaces before
declaring a commit truly unreachable or a repository truly
self-contained.
CONTENT
↓
blob/tree objects
↓
commit object (tree + parents + metadata)
↓
refs name commits
↓
HEAD selects current branch/ref or detached commit
WORKING TREE ↔ INDEX ↔ HEAD
add commit
REMOTE
↓ fetch
remote-tracking refs + objects + FETCH_HEAD
↑ push
Recovery evidence:
refs + reflogs + other refs + object database
| Goal | Command |
|---|---|
| Inspect state | git status |
| Show topology | git log --graph --decorate --oneline --all --reflog |
| See unstaged | git diff |
| See staged | git diff --cached |
| Stage selected | git add -p |
| Unstage | git restore --staged <path> |
| Discard file changes | git restore <path> |
| Create branch | git switch -c <name> |
| Move branch pointer | git reset --soft\|--mixed\|--hard <target> |
| Public undo | git revert <commit> |
| Integrate history | git merge |
| Rewrite private series | git rebase / git rebase -i |
| Surgical base change | git rebase --onto |
| Transfer one commit | git cherry-pick <commit> |
| Update remote view | git fetch |
| Normal push | git push |
| Guarded rewrite | git push --force-with-lease |
| Recover prior ref | git reflog |
| Find object | git fsck --full |
| Situation | Preserve history? | Typical transformation |
|---|---|---|
| Uncommitted file changes, keep | yes | leave working tree or stash |
| Uncommitted file changes, discard | no | git restore |
| Staged but not committed, unstage | yes | git restore --staged |
| Local commit, keep changes | no history at tip | git reset --soft HEAD~1 |
| Local commit, keep worktree but restage | no history at tip | git reset --mixed HEAD~1 |
| Local commit, discard content | no | git reset --hard with explicit target |
| Shared bad commit | yes | git revert |
| Need change from another branch | not necessarily | git cherry-pick / patch |
| Lost branch/commit | recover | reflog + new ref |
| Task | Typical operation | Shared-history note |
|---|---|---|
| new feature branch | git switch -c feature |
safe |
| update from main | merge or rebase | depends on branch policy |
| backport | cherry-pick / patch | verify dependencies |
| hotfix | branch from release/prod line; forward-port | preserve topology policy |
| clean private feature history | interactive rebase | do not rewrite shared branch |
| recover deleted branch | reflog → branch | name recovery ref first |
1. STOP
2. git status
3. git log --graph --decorate --oneline --all --reflog
4. git reflog --all
5. git branch recovery/<timestamp> <known-good-oid>
6. Save any important work with stash / patch / worktree
7. Model CURRENT vs DESIRED DAG
8. Choose reset / revert / rebase / cherry-pick / merge based on sharing
9. Execute one meaningful mutation at a time
10. Verify refs + DAG + index + worktree + remote state
BUG DISCOVERED
↓
Identify deployed artifact + corresponding Git ref/tag
↓
Branch from the actual release/prod line
↓
Implement smallest corrective change
↓
CI + tests + review
↓
Tag/build/deploy
↓
Forward-port to main
↓
Verify release and main contain the intended lineage
| Command | High-value use |
|---|---|
git merge-base |
common-base / ancestry reasoning |
git rev-list |
history set algebra |
git range-diff |
compare rewritten patch series |
git worktree |
parallel branch work |
git rerere |
reuse recurring conflict resolutions |
git bisect |
regression localization |
git update-ref |
precise ref mutation |
git for-each-ref |
scriptable ref inventory |
git cat-file |
object inspection |
git write-tree / commit-tree |
understand commit construction |
git fsck |
object/repository forensics |
git filter-repo |
history rewrite / sensitive-data remediation (external tool) |
NEED TO CHANGE GIT STATE?
│
├─ Is the problem only file content?
│ ├─ working tree → git restore
│ └─ index → git restore --staged / reset <path>
│
├─ Is the branch tip wrong?
│ ├─ private → reset/rebase as derived from desired DAG
│ └─ shared → preserve history with revert/forward correction
│
├─ Need a change from elsewhere?
│ ├─ whole branch topology → merge
│ ├─ one patch → cherry-pick
│ └─ portable patch artifact → format-patch/am
│
├─ Need newer base?
│ ├─ preserve topology → merge
│ └─ rewrite private series → rebase / rebase --onto
│
└─ Something disappeared?
└─ reflog → identify OID → create recovery ref → proceed
Before mutation:
HEAD recordedgit status capturedAfter mutation:
Semantic difference: reset changes
where the current branch points and may change index/worktree.
revert records a new commit that inverses an earlier
change.
Operational selection: Use reset for appropriate local/private history surgery; revert for shared/public history preservation.
DAG/state implication: Reset can alter the branch DAG view; revert adds a new node.
Semantic difference: restore is
file/index-oriented; checkout is a historical multipurpose
command that also moves HEAD.
Operational selection: Prefer restore for file content and switch for branch movement in new workflows; know checkout because legacy scripts use it.
DAG/state implication: Checkout remains valid and is not “broken”; its role is broader and easier to misuse.
Semantic difference: switch is
branch-oriented and more explicit; checkout is older/multipurpose.
Operational selection: Use switch for branch navigation; checkout for legacy compatibility or operations not covered by switch.
DAG/state implication: Both can participate in detached HEAD workflows.
Semantic difference: Merge integrates histories and can create a merge node; rebase rewrites the copied/replayed side onto another base.
Operational selection: Merge for topology preservation/shared histories; rebase for suitable private-history cleanup.
DAG/state implication: Rebase changes commit IDs; merge can preserve original commits.
Semantic difference: Merge connects branch histories; cherry-pick transfers selected patch effects into a new commit.
Operational selection: Use merge when integration of the histories is desired; cherry-pick for selective transfer.
DAG/state implication: Cherry-picked commit is not the same object as source commit in the normal case.
Semantic difference: Cherry-pick transfers a commit from Git object history; patch workflows serialize changes into portable patch artifacts.
Operational selection: Use patch when repository boundaries or offline/email workflows matter.
DAG/state implication: Patch application may produce a different commit identity and metadata.
Semantic difference: Fetch updates objects and remote-tracking refs; pull fetches and then integrates.
Operational selection: Use fetch for explicit control and diagnosis.
DAG/state implication: Pull has integration side effects; fetch does not merge/rebase the current branch by itself.
Semantic difference: Both attempt to restore stash content; apply retains the stash; pop removes it after successful application under stash semantics.
Operational selection: During recovery, apply often preserves evidence longer.
DAG/state implication: Conflicts can affect whether a pop drops the entry.
Semantic difference: Force bypasses normal ref-update safety; lease requires an expected remote state.
Operational selection: Use lease when a rewrite is intended and permitted.
DAG/state implication: Lease is not a collaboration substitute; stale/incorrect expectations can still produce bad outcomes.
Semantic difference: Branch is normally a movable ref for ongoing work; tag is a named release/milestone ref.
Operational selection: Use branch for evolving work; tag for stable names.
DAG/state implication: Both are refs, but their workflows and expectations differ.
Semantic difference: Annotated tag points to a tag object with metadata; lightweight tag names an object directly.
Operational selection: Annotated tags are usually more appropriate for releases where provenance matters.
DAG/state implication: Do not retarget release tags silently.
Semantic difference: Clone initializes a new repository and obtains history; fetch adds/updates objects and refs in an existing repository.
Operational selection: Use clone for new copies; fetch for refreshing existing copies.
DAG/state implication: Clone also sets up remote/tracking configuration by default.
Semantic difference: All move the current branch tip; they differ in index/worktree synchronization.
Operational selection: Soft for recommit workflows; mixed for unstaging/rebuilding; hard for deliberate content reset.
DAG/state implication: Hard can discard uncommitted tracked work and therefore requires explicit verification.
--force to silence a non-fast-forward
error.--force-with-lease for permitted
rewrites.Repository topology is an organizational system, not merely a Git preference. The useful question is not “Which branching model is best?” but “What failure modes does this topology make easy or hard, and what automation/policy compensates for them?”
A small team can often operate with a single long-lived integration branch plus short-lived feature branches:
feature/a ─┐
feature/b ─┼──> main ───> build ───> deploy
feature/c ─┘
The operational focus is review quality, fast CI, clear ownership, and short branch lifetimes. The main failure modes are usually accidental commits, stale branches, merge conflicts, and ad-hoc release procedures.
Useful controls:
git branch -vv
git log --graph --decorate --oneline --all
git fetch --prune
git merge --ff-onlyAt roughly this scale, the branch itself is no longer a sufficient coordination mechanism. Teams need explicit ownership and integration policy:
developer branch
│
▼
code review + ownership checks
│
▼
CI / integration validation
│
▼
merge queue / controlled integration
│
▼
main
Typical additions include path ownership, required checks, CODEOWNERS, protected branches/rulesets, merge queues, release automation, and observability around failed integrations. The Git objects and refs remain the same; the hosting/CI system constrains which ref transitions are permitted.
At larger scale, contention is a first-class capacity problem. Common architectural responses include:
┌── ownership / policy
├── merge queues or trains
├── incremental / targeted CI
main ────────────────► ├── release branches where needed
├── monorepo scale tooling
└── reproducible artifacts
Questions become operational:
The repository design should follow these constraints. Git itself does not provide a universal “large organization mode.”
These labels describe workflows layered over Git’s common primitives. They are not distinct Git implementations.
| Model | Characteristic topology | Typical operational concern |
|---|---|---|
| trunk-based | short-lived branches converge quickly | CI/review throughput |
| GitHub Flow-like | feature branch → pull request → main | release sequencing may need extra automation |
| GitLab Flow-like | branch/release/environment relationships vary by policy | multiple integration/release lines |
| GitFlow-like | long-lived develop/release/hotfix branches | merge and backport complexity |
| release-branch model | main plus supported release lines | propagation of fixes across versions |
Use the state model to reason about any of them. A
release/* branch is still a ref. A pull request is still a
host-level object referencing commits/branches. A merge queue is still a
policy/control mechanism around integration; its synthetic test state
may not be a durable branch ref.
git notes
— attach metadata without changing the commit IDA note is separate Git data associated with an object. Adding a note does not alter the commit object and therefore does not create a new commit ID merely because metadata was attached.
git notes add -m 'incident=INC-1234' <commit>
git notes show <commit>
git notes listConceptually:
commit X ────────────────► unchanged commit object
│
└────────► refs/notes/commits ─► note object/tree
Notes are useful for review metadata, incident annotations, test evidence, migration markers, and other information that should not become part of the commit payload. Because notes use refs, they need explicit transport policy when shared across repositories; ordinary commit fetch/push should not be assumed to carry every notes namespace.
Reference: https://git-scm.com/docs/git-notes
git bundle
— move a repository graph as a fileA bundle packages Git objects and references into a file that can be verified and transported without a live Git server. This is useful for air-gapped transfer, offline handoff, archival snapshots, and controlled repository migration.
git bundle create repo.bundle main
git bundle verify repo.bundle
git clone -b main repo.bundle repo-copyA bundle is not merely a tarball of .git/; it is a Git
transport representation containing the commits/objects needed by its
advertised refs and their prerequisite relationships. Incremental
bundles can be constructed with explicit prerequisite revisions when a
base repository already has part of the graph.
Reference: https://git-scm.com/docs/git-bundle
git archive
— export a tree, not a repositorygit archive --format=tar.gz --prefix=project-1.4.0/ v1.4.0 > project-1.4.0.tar.gzThe output is a source snapshot of the selected tree. It deliberately does not contain the Git database, reflogs, refs, or object history. This makes it appropriate for source distributions but inappropriate as a repository backup or recovery mechanism.
Reference: https://git-scm.com/docs/git-archive
git replace —
local graph substitutiongit replace <object-to-replace> <replacement-object>
git replace -l
git replace -d <object-to-replace>Replace refs let compatible Git commands temporarily treat one object
as another. This can be useful for experiments, history analysis, or
debugging a repository whose stored graph should not be permanently
rewritten. Because the substitution changes how local commands interpret
the graph, it is an easy way to create “I see a different history”
confusion. Inspect refs/replace/ and use
--no-replace-objects when you need to bypass
replacements.
Reference: https://git-scm.com/docs/git-replace
Aliases should shorten inspection and repetitive safe operations; they should not turn destructive history rewriting into muscle memory.
Useful examples:
git config --global alias.lg 'log --graph --decorate --oneline --all'
git config --global alias.last 'log -1 --stat'
git config --global alias.root 'rev-parse --show-toplevel'
git config --global alias.unstage 'restore --staged --'
git config --global alias.review 'diff --check'
git config --global alias.refs 'for-each-ref --format="%(refname:short) %(objectname:short)"'Avoid aliases whose names imply a harmless “undo” while silently
executing reset --hard, push --force,
clean -fdx, or similar operations. The goal of an alias is
reduced typing, not reduced awareness.
A useful rule is:
alias = faster inspection / faster repetition
not
alias = conceal the state transition
These are related but solve different dimensions of repository scale.
Repository graph / objects
│
partial clone controls
what objects are downloaded
│
▼
local object DB
│
sparse-checkout controls
what is populated locally
│
▼
working tree
Sparse checkout keeps tracked files in the repository graph while
limiting the populated working tree. The implementation uses
SKIP_WORKTREE information in the index; sparse-index can
further reduce index expansion for large sparse workspaces.
git sparse-checkout init --cone
git sparse-checkout set services/payment libs/common
git sparse-checkout reapplyThe important mental model is not “unselected files are untracked.” They are still tracked; they are simply omitted from the populated sparse working tree under the sparse-checkout specification.
References:
Partial clone controls which reachable objects are transferred initially.
git clone --filter=blob:none --sparse <url> repoA blobless partial clone can hold commits and trees while deferring
blob downloads until operations require those blobs. Newer Git releases
also provide git backfill to proactively populate missing
objects in supported workflows.
References:
Do not confuse the dimensions:
| Feature | Primary concern |
|---|---|
| sparse-checkout | working-tree population |
| sparse-index | index scalability under sparse checkout |
| partial clone | object transfer/storage |
| shallow clone | history depth |
| LFS | large-file payload storage |
They can be combined, but each introduces different failure and tooling considerations. A sparse checkout does not make a repository smaller on the network; a partial clone does not by itself define which files are present in the working tree.
A commit contains author and committer identity fields. A cryptographic signature, when present, is additional commit payload that can be verified against a key. These are different concepts.
git commit -S -m 'Release fix'
git verify-commit HEAD
git show --show-signature HEADA verified signature means the signed payload validated against the relevant key/signing policy. It does not, by itself, prove that the change was reviewed, bug-free, authorized for a specific production system, or semantically correct.
Git supports multiple signing backends via gpg.format,
including OpenPGP, X.509, and SSH. Trust and key-ownership policy are
external to the cryptographic syntax itself.
References:
Annotated signed tags are especially useful when a release process wants an independently verifiable object naming a release point.
git tag -s v2.7.0 -m 'Release 2.7.0'
git tag -v v2.7.0
git rev-parse v2.7.0^{commit}A signed tag authenticates the tag object’s signed payload. Release systems still need their own policy for which signing keys are trusted and which artifacts correspond to a release.
When a credential is committed, treat the credential as compromised first. Removing the file from Git history does not make a previously exposed credential valid again.
1. revoke / rotate credential
2. identify blast radius
3. preserve forensic evidence as needed
4. rewrite history only when required
5. coordinate remote / fork / clone cleanup
6. invalidate CI caches / artifacts where relevant
7. verify the new credential path
GitHub’s sensitive-data-removal guidance explicitly warns that history rewriting changes commit IDs and can affect clones, forks, pull requests, and cached copies. History cleanup is a separate operation from credential rotation.
Reference: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository
A robust deployment pipeline should distinguish between mutable names and immutable commit IDs.
branch / tag name
│
▼
exact commit OID
│
▼
source checkout
│
▼
reproducible build
│
▼
immutable artifact digest
│
▼
deployment record
A build should be able to answer:
Which exact commit did we build?
Which exact tree did we build?
Which exact dependencies were resolved?
Which exact artifact digest was deployed?
Use git rev-parse HEAD, the CI provider’s immutable
source identifier, and artifact digests. Do not make a mutable branch
name the sole provenance identifier.
A merge queue can test a change against a temporary integration state created from several refs/commits. That state may not become a durable branch ref. Therefore:
reviewed branch tip ≠ necessarily tested integration tip ≠ deployed artifact
The pipeline must preserve the identity of the tested input and the identity of the produced artifact.
GitHub’s protected-branch features and merge queue, and GitLab’s merge trains/merged-results pipelines, are hosting-level controls around these Git states rather than replacements for Git’s object/ref model.
References:
A Git revert changes code history by adding an inverse commit. A deployment rollback changes runtime state to an earlier artifact. They often happen together, but neither is a substitute for the other.
Git change: A---B---C---R
runtime artifact: artifact(C) → artifact(B)
Use the deployment system’s rollback primitive when runtime state is the incident target; use Git history operations when source lineage or future integration state is the target.
If two tips have no common ancestor, normal merge cannot construct a
three-way merge base. --allow-unrelated-histories is an
explicit request to combine such histories; it should not be used merely
to silence a confusing error.
git merge --allow-unrelated-histories other-historyFirst determine why the repositories are unrelated. Common causes include independently initialized repositories, incorrect remotes, or an accidental repository boundary.
A shallow clone deliberately omits historical ancestry. Commands whose reasoning depends on older ancestors can produce different results or fail because required history is absent.
git rev-parse --is-shallow-repository
git fetch --unshallow
# or deepen incrementally
git fetch --deepen=500Do not diagnose a history problem as “Git lost the commit” until you have checked whether the local clone simply does not contain the relevant ancestry.
ours /
theirsConflict terminology is operation-relative. During ordinary merge, “ours” means the current side and “theirs” the merged side. During rebase, the conceptual roles are reversed in parts of the conflict machinery because the branch being replayed is treated as “theirs” while the temporary base is “ours.” Always inspect the conflict context instead of relying on intuition.
References:
Git can be configured to borrow objects from another object database
using alternates. This can be useful for storage efficiency and
controlled infrastructure, but it creates a dependency outside the
repository’s obvious .git/objects path.
For incident work, inspect the environment if object lookup behaves unexpectedly:
git rev-parse --git-dir
git count-objects -v
git cat-file -t <oid>Replace refs alter local object interpretation without changing the stored commit IDs. They are therefore useful for experiments but dangerous as an implicit collaboration mechanism. Before comparing histories across machines, inspect:
git replace -l
git --no-replace-objects log --graph --oneline --allA local origin/main ref is a local record of fetched
remote state. Its reflog, when retained, records local movements of that
ref. Neither should be assumed to be the authoritative server-side
historical record for an incident. Hosting platforms may have additional
server-side retention, but that is platform-specific policy.
Git performance problems are easier to diagnose when decomposed:
network transfer
│
├── pack negotiation / object filtering
▼
object storage
│
├── packfiles / deltas / multi-pack-index / bitmaps
▼
index
│
├── sparse index / fsmonitor
▼
working tree
│
└── file-system traversal / checkout
Useful inspection and maintenance commands include:
git count-objects -v
git maintenance run --auto
git commit-graph write --reachable
git multi-pack-index write
git rev-list --objects --all > /tmp/reachable-objects.txtInterpret evidence instead of blindly repacking. For a monorepo, reducing the amount of populated working-tree state may matter more than shaving a small percentage from pack size. For a CI runner repeatedly cloning, protocol/object transfer may dominate. For a developer repeatedly scanning millions of files, index/worktree traversal may dominate.
Git’s packfiles delta-compress related objects. Commit-graphs accelerate graph traversals. Multi-pack-index lets Git efficiently address objects across multiple packfiles. Bitmaps can accelerate reachability enumeration for suitable operations. None of these changes the conceptual fact that refs name objects and commits form a DAG; they are storage/traversal optimizations around that model.
References:
When an incident report says “the branch is fixed,” make that statement testable.
git rev-parse refs/heads/main
git show-ref refs/heads/main
git symbolic-ref HEADgit merge-base --is-ancestor <expected-base> <expected-tip>
git rev-list --left-right --count <expected-tip>...<other-tip>
git branch --contains <commit>git show --no-ext-diff --stat <commit>
git cat-file -p <commit>
git ls-tree -r <commit>git status --short
git diff
git diff --cached
git diff HEAD
git ls-files --stage
git ls-files -ugit reflog --date=iso --all
git fsck --full --no-reflogs --unreachableThe point is not to run every command every time. Choose the smallest evidence set that proves the desired postcondition. But during history surgery, always prove both the ref position and the content state; a visually plausible log can still hide staged or working-tree changes.
A Git operation is complete only when:
1. the intended refs point where they should,
2. the intended commit graph exists,
3. the index contains the intended snapshot,
4. the working tree is in the intended state,
5. remote state is known where relevant,
6. deployment state is separately verified where relevant,
7. recovery evidence has been preserved if the operation was destructive.
git --versionObserved validation environment: Git 2.47.3.
Current Git documentation was consulted separately because the installed test binary can lag current manuals. The artifact therefore distinguishes experimentally validated behavior from documentation-current behavior.
the reproducible test suite covers:
--no-ff and two-parent topology;B..Y vs B^..Y;rebase --onto selective suffix movement;write-tree, ls-tree,
hash-object, cat-file,
symbolic-ref, and show-ref;commit-tree + update-ref
construction.| Assertion | Observed result |
|---|---|
| soft reset | branch/HEAD moved; index and working tree retained changes |
| mixed reset | branch/HEAD moved; index reset toward target; working tree retained changes |
| hard reset | branch/HEAD moved; index and tracked working tree synchronized to target |
| rebase | feature commit IDs changed after replay onto a new base |
merge --no-ff |
result commit had two parent IDs |
| B..Y | excluded B, included later descendants |
| B^..Y | included B and later descendants |
| cherry-pick | source and resulting target commit IDs differed in divergent-target test |
| rebase –onto | only the selected suffix was replayed with new IDs |
| reflog recovery | pre-reset commit was recoverable and re-named with a branch |
| force-with-lease | worked when lease matched; rejected a stale remote state |
| merge conflict | index contained three stages for the conflicted path |
| plumbing | tree/commit object types and refs matched the expected model |
This manual was shaped directly by the supplied authoring specification: it requires commands to be taught through current state → command → state transition → resulting state; it requires current-state vs desired-state reasoning, shared/private history classification, progressive depth, decision trees, emergency procedures, and verification. See the supplied specification around the state-first teaching rule, recovery framework, and quality test. fileciteturn0file0L128-L183
The expanded supplied outline also requires the three-tree model, object model, refs, DAGs, remote internals, reset/revert/rebase/cherry-pick, reflog/GC, conflicts, stash, monorepos, LFS, submodules, security, CI/CD, platform boundaries, and a final operator handbook. fileciteturn0file1L142-L193 fileciteturn0file1L868-L928 fileciteturn0file1L1507-L1584
git checkout and
git reset remain relevant; modern workflows often use
git switch and git restore for clearer
intent.git filter-repo is an external modern history-rewrite
tool; it is not a built-in Git porcelain command. Git’s own
filter-branch documentation warns against using
filter-branch for history rewriting in new work.The document is successful when an engineer can answer all of the following without cargo-culting a command:
HEAD right now?The engineer should finish this manual thinking: “I can derive the Git operation from the state I have, the state I want, and the collaboration constraints around it.”
Blob: Git object containing file content.
Tree: Git object describing directory entries and
modes.
Commit: Git object containing a tree, parent IDs,
metadata and message.
Ref: named pointer to an object ID, often a
commit.
Branch: normally a movable ref under
refs/heads/.
Remote-tracking ref: local ref recording the last
fetched state of a remote ref.
HEAD: current checkout target; symbolic when attached
to a branch, direct when detached.
Index: staging snapshot / conflict-state
database.
Reflog: local log of ref movements.
Reachable: discoverable from protected reference/object
roots.
Fast-forward: move a ref to a descendant without
creating a merge commit.
Three-way merge: merge using a common ancestor plus two
tips.
Rebase: replay selected commits onto another base,
normally generating new commit objects.
Cherry-pick: apply one or more commits’ patches onto
the current branch, normally creating new commits.
Revert: create new commits that reverse the effect of
existing commits.
Remote: configured location used for network
operations.
FETCH_HEAD: file recording fetched refs from a fetch
operation.
ORIG_HEAD: pseudoref used to retain a pre-operation
HEAD in certain drastic operations.
Merge base: common ancestor used for integration
reasoning.
Refspec: mapping between source refs and destination
refs during fetch/push.