A comprehensive reference covering every stage of the software delivery lifecycle — from planning and branching strategy through CI/CD, governance, and post-deployment monitoring.
14 lifecycle stages
6 reference tools
153 git commands, each rated for safety
27 rescue recipes, tested against real git
1Develop
Planning
Turn a request into a work item a developer can start on without asking a question, and a branch name that points back to it.
Every change starts as a written work item, not a conversation — so anyone can pick it up without asking what was meant.
A story is ready to start when its acceptance criteria are unambiguous, its dependencies are unblocked, and it fits in a sprint.
Acceptance criteria are written as Given/When/Then, so "done" is a fact everyone can check, not an opinion.
The branch name carries the issue key, so every commit, PR and deploy traces back to the work item that asked for it.
A draft PR opened on day one is a better planning signal than a status update: it shows real progress, in review, before the work is finished.
Match the issue type to the work
Five shapes cover almost everything a team tracks. Pick the narrowest one that fits.
Issue types
Type
Use it for
Typical fields
Story
User-visible feature work
As a / I want to / So that, acceptance criteria, story points
Bug
A defect: something worked and stopped, or never matched the spec
Steps to reproduce, expected vs. actual, environment
Task
Technical work with no direct user visibility (upgrades, refactors)
Objective, definition of done, dependencies
Epic
A body of work spanning multiple sprints, grouping stories
Time-boxed research to reduce uncertainty before committing to a design
Question to answer, time box, decision criteria
Meet the Definition of Ready before starting
A story a developer cannot start without asking a question is not ready. Check, before it enters a sprint:
Acceptance criteria are unambiguous and written as Given/When/Then.
The team doing the work estimated it, not the person who filed it.
Every dependency is identified and unblocked.
Designs or wireframes are attached, for UI work.
No open question would block a developer from starting today.
It fits comfortably inside a single sprint — if not, it is an epic, not a story.
Write acceptance criteria as Given/When/Then
Behaviour-driven acceptance criteria state a precondition, an action and an observable result, so "done" can be checked mechanically instead of argued about:
acceptance-criteria.mdmarkdown
Given a logged-in user on the dashboard
When they click "Export to CSV"
Then a CSV file is downloaded containing all visible rows
And the filename includes today's date
Name the branch after the issue, and link the PR to it
A branch name that carries the issue key (feature/PROJ-123-add-oauth) means every commit and PR traces back to the work item without anyone looking it up. On GitHub, a pull request whose description contains a closing keyword and an issue reference (Closes #123) closes that issue automatically when the PR merges GitHub 2026 — one more reason the issue key belongs in the branch and the PR, not only in a person's memory.
Loading the branch-name generator…
Open a draft PR on day one for anything that will take more than a day
A draft pull request cannot be merged until it is marked ready for review, but it is visible, diffable and commentable from the first commit GitHub 2026. It replaces a status update with something reviewers can actually look at, and lets a reviewer flag a wrong direction before the work is finished rather than after.
Priority, separately from size
Priority says how soon; story points say how much work. Conflating them means an easy P3 jumps the queue over a hard P1, or a big P0 sits half-estimated while the team argues about its size.
Priority levels
Priority
Meaning
Response
P0 — Critical
Production is down, or data is being lost
Fix immediately, all hands
P1 — High
A core feature is broken for users
Fix within the current sprint
P2 — Medium
A non-critical degradation
Next sprint or sooner
P3 — Low
A nice-to-have improvement
Scheduled as capacity allows
A story enters the sprint without acceptance criteria
The team discovers what "done" means during the PR review, which is the most expensive place to discover it — the code already exists and now has to change shape.
The branch name has no connection to the issue
Six months later, nobody can tell why fix-thing exists, whether it shipped, or what it was for. git log --grep on an issue key finds every commit for a feature; a name like fix-thing is unsearchable.
Create the repository, its default branch, its ignore and attributes files, and choose how it will scale before the first commit.
A repository is a directory plus a hidden .git — git init creates the second part; nothing else changes on disk.
.gitignore stops git tracking files it should never track; .gitattributes controls how it treats the files it does.
Neither file has any effect on a file git already tracks — untrack a mistakenly committed secret with git rm --cached, then rotate it.
A monorepo trades independent deploys for atomic cross-project changes; a polyrepo trades that back for isolation. Neither is free.
Sparse checkouts, partial clones and shallow clones each cut a different cost — disk, network, or both — at the price of some command no longer working the normal way.
This writes .git/ and nothing else: no commit exists yet, and no files are tracked until you add them. The name of the first branch comes from init.defaultBranch if it is set, or git config --global init.defaultBranch main to set it once for every new repository Git project 2026.
Three files a reader looks for before anything else. None is required by git; all three are expected by everyone reading the repository on a host.
Root files
File
Answers
README.md
What this is, how to run it, where to start
LICENSE
What anyone may legally do with the code — required for it to be reused at all outside the org
CONTRIBUTING.md
How to propose a change: branch naming, commit style, how to run tests, who reviews
Ignore build output, dependencies and secrets before the first commit
Write .gitignore before adding files, not after — a pattern only stops git tracking a file it has never seen. A file already committed keeps being tracked no matter what you add to .gitignore later; remove it from the index first with git rm --cached <path>Git project 2026.
Fix line endings and diff behaviour with .gitattributes
* text=auto lets git normalise line endings to LF in the repository and convert them on checkout per core.autocrlf; marking binary files binary stops git trying to diff or merge them as text; marking a lockfile -diff replaces a multi-thousand-line diff with "Binary files differ" without changing how it is stored or merged Git project 2026.
Generate both files from what the project actually uses
Pick languages, tools, editors and operating systems below; every rule comes with the reason it is there, and the tester checks a path the same way git check-ignore -v would.
Loading the generator…
Monorepo vs. polyrepo
One repository per project, or one for everything
Monorepo
Polyrepo
Cross-project changes
One commit, one PR, reviewed together
A PR per repository, coordinated by hand or tooling
CI
Needs affected-package detection or every push tests everything
Each repository's pipeline only ever sees its own code
Access control
Coarse — usually the whole repository or nothing
Fine-grained per repository
Shared tooling
One version of every dependency, enforced by the layout
Each repository can pick its own, and drift
Options for a large repository
Each of these makes one specific thing smaller — files on disk, bytes fetched, or history downloaded — none is free, and they compose.
Scale options
Option
What it reduces
What stops working the normal way
git sparse-checkout
Files written to disk — only the paths you choose appear in the working tree
A path outside the cone will not appear until you add it back
git clone --filter=blob:none
Bytes fetched — file contents download on demand, the first time each is needed
The first checkout, and the first diff or blame touching an old file, fetches over the network
git clone --depth N
History downloaded — only the last N commits
log, blame and bisect only see those N commits; pushing new history back can be rejected
Git LFS
Repository size for large binaries — the file is stored on an LFS server, a pointer in git
Everyone who clones needs Git LFS installed, or they get pointer files instead of content
git maintenance
Time spent on gc, repacking and the commit-graph, spread out instead of done all at once
Nothing — it runs in the background once <code>git maintenance start</code> schedules it
A clone made with --filter or --depth is a partial or shallow clone respectively; both are configured with --sparse, --filter and --depth on git clone itself Git project 2026, and git sparse-checkout can narrow an existing clone's working tree afterwards Git project 2026. git maintenance start schedules background repository maintenance instead of leaving it to an occasional manual git gcGit project 2026.
A secret was committed before .gitignore existed
Adding the pattern afterwards does not remove it from history — every commit that ever included the file still has it.
Decide which branches live long, which live for a day, and how work reaches main — trunk-based, GitHub Flow, GitLab Flow or Git Flow.
A branching strategy answers one question: how does a change reach main, and how long does a branch live before it does?
Trunk-based development and GitHub Flow keep almost everything short-lived; Git Flow adds long-lived develop and release branches for versioned software.
Git Flow's own author, ten years after writing it, now recommends most teams use something simpler Driessen 2010.
The right choice follows release cadence and CI maturity, not preference — see the simulator below with your own team's numbers.
Four strategies, compared
Branching strategies
Strategy
Long-lived branches
Release model
Suits
Trunk-based development
None — everyone commits to main (or trunk) multiple times a day, behind feature flags for anything incomplete
Continuous — main is always releasable
High-velocity teams with strong CI and feature-flag infrastructure
GitHub Flow
None — main, plus a short-lived branch per pull request
Continuous — main deploys as soon as a PR merges
Web apps deployed continuously to one environment
GitLab Flow
Environment branches (e.g. staging, production), or a release branch per version
Either continuous (environment branches) or versioned (release branches) — GitLab documents both variants
Teams that need an explicit environment or version boundary GitHub Flow does not give them
Git Flow
develop (integration) plus one release branch per version
Versioned, scheduled releases, with parallel hotfix support
Software distributed as versioned releases, or supporting multiple versions in the wild at once
GitLab documents its own flow as a family of variants built on top of a GitHub-Flow-like base, not one fixed model GitLab 2026; GitHub's own flow is deliberately the simplest of the four GitHub 2026; trunk-based development is the strategy behind continuous integration's original premise — that everyone's changes meet, in one place, at least daily Hammant 2020.
Match the strategy to release cadence and CI maturity, not preference
Every long-lived branch is a batch of changes that has not yet met the others — the longer it lives, the more expensive the eventual merge. Continuous delivery wants that cost at zero, which is why trunk-based development and GitHub Flow both minimise long-lived branches; software that ships in dated, supported versions needs somewhere to put a fix for a version that already shipped, which is what Git Flow's release branches are for.
Name branches so the type is obvious without opening them
Branch naming
Pattern
Use it for
feature/PROJ-123-add-oauth
New functionality
bugfix/PROJ-456-fix-null-pointer
A fix for something already merged
hotfix/v2.1.1-payment-null-fix
An urgent fix cut from a release branch, not from main
release/v2.2.0
A branch that stabilises one version before it ships
chore/update-dependencies
No user-visible change
Git Flow adopted for a continuously deployed web app
Every feature waits for develop, then a release branch, before it can ship — for software that could deploy the moment a PR merges, that delay buys nothing.
A feature branch lives for weeks
It drifts further from main every day it is not merged; when it finally does merge, the conflict is bigger than it would have been on day two. Trunk-based development and GitHub Flow both exist specifically to prevent this.
Move changes between the working tree, the index and HEAD; write commits someone can review, revert and bisect.
Every file has up to three versions: in HEAD (the last commit), the index (what the next commit will contain) and the working tree (on disk). Add, commit, restore and reset each copy between two of these.
A commit is a unit of review, revert and bisection — one logical change, not one save point.
Conventional Commits gives a message a type a machine can read (fix:, feat:, a ! or BREAKING CHANGE: footer for a breaking change), which is what makes an automatic changelog and version bump possible.
reset --soft only moves HEAD; reset (mixed) also unstages; reset --hard also overwrites the working tree. Each is a strictly bigger hammer than the last.
A..B on git log means commits reachable from B but not A; A...B means either but not both — the second is what a PR diff and git log --left-right usually want.
The three trees, live
Run a command in this stage's terminal — add, commit, restore, reset — and watch which column changes.
Loading the three trees…
Stage what belongs in this commit, not everything you touched
Staging the whole working tree with git add . is fine when every change in it really is one commit; when it is not, stage file by file, or use git add -p to stage individual hunks within a file and leave the rest for a later commit.
One logical change per commit
The unit of review, revert and bisection is the commit, not the pull request:
Revertability — reverting one concern reverts exactly what broke, not unrelated code that happened to ship alongside it.
Bisectability — git bisect only finds the right commit if every commit in between is a consistent, buildable state.
Review quality — a reviewer reasons about one change at a time; a commit that mixes a rename with a behaviour change hides the behaviour change inside the noise.
Write commit messages a machine and a person can both use
Conventional Commits gives every message a type, an optional scope, and a description: fix(cart): correct total on discount. fix maps to a patch release and feat to a minor one; a ! before the colon, or a BREAKING CHANGE: footer, means major Conventional Commits contributors 2019. Footers also carry authorship and provenance trailers such as Co-authored-by (a commit with more than one author GitHub 2026) and Signed-off-by (the Developer Certificate of Origin: the signer attests they have the right to submit the change under the project's license Linux Foundation 2004), both parsed the same way by git interpret-trailersGit project 2026.
Loading the commit checker…
Fix the last commit or the working tree with the smallest tool that does it
Undo, from smallest to largest
Command
Undoes
Rewrites shared history?
git commit --amend
Replaces the last commit — its message, or its content if you staged more first
Yes, if already pushed — don't amend a commit others have based work on
git restore <path>
Discards uncommitted changes to a file, back to what the index has
No — nothing was committed
git restore --staged <path>
Unstages a file without touching its content
No
git reset --soft HEAD~1
Moves HEAD back one commit; index and working tree untouched — the commit's changes are now staged
Yes, if already pushed
git reset HEAD~1
The same, and also unstages — the changes are now just in the working tree
Yes, if already pushed
git reset --hard HEAD~1
The same, and also overwrites the working tree — uncommitted changes are gone
A stash is a commit outside any branch's history — git stash list shows every one, and popping applies and drops the most recent.
Read history with the flag that answers the actual question
Reading history
Command
Answers
git log --oneline --graph
What happened, and in what order, across branches
git log A..B
Commits reachable from B but not A — what B has that A doesn't
git log A...B
Commits reachable from either but not both — the symmetric difference
git blame <file>
Which commit last touched each line
git log -S"needle"
Which commits added or removed an exact string — not just touched the file
git log --left-right A...B marks each commit with which side of the symmetric difference it is reachable from (< or >) Git project 2026, which is what most "how have these two branches diverged" questions actually want; the range syntax itself — A..B, A...B, and @{upstream} — is gitrevisions' own subject Git project 2026.
Read a diff
Loading the diff reader…
Hooks: local checks that run automatically
Common client-side hooks
Hook
Runs
Typical use
pre-commit
Before a commit is created
Lint, format, type-check, or scan for secrets
commit-msg
After the message is written, before the commit is created
Enforce Conventional Commits or a ticket-number prefix
pre-push
Before git push sends anything
Run the test suite one last time
Hooks live in .git/hooks/, are not cloned with the repository, and must be executable Git project 2026. Most teams install them through a tool (Husky for Node, the Python pre-commit framework) that writes them from a checked-in config, so every clone gets the same ones.
.husky/pre-commitbash
What Husky writes for a Node project; the Python pre-commit framework reads an equivalent .pre-commit-config.yaml instead.
This checks out hotfix/urgent into a second directory, sharing the same .git — no stash, no switching back and forth, and both working trees stay usable at the same time.
git commit --amend after pushing
The pushed commit and the amended one are now different objects with the same message — anyone who already pulled the old one now has a divergent history.
git stash pop dropped, and the stash is needed again
A dropped stash is unreachable from any ref, but its commit is not deleted immediately — the reflog and git fsck --unreachable can often still find it.
Share commits through a remote without losing anyone's work: fetch, pull, push, force-with-lease, merge versus rebase, and conflicts.
A remote is just another repository your clone knows the URL of. fetch copies its objects and updates your remote-tracking branches; nothing else changes until you merge or rebase them in.
git pull is fetch plus either a merge or a rebase — which one depends on pull.rebase, and git will refuse to guess once your branch has diverged from upstream.
A push is rejected when the remote has a commit you don't — fetch and integrate first, or force only if you are certain the remote's version is disposable.
--force-with-lease refuses to overwrite a remote-tracking ref that has moved since your last fetch; plain --force has no such check and can silently discard a teammate's push.
The rule that makes all of this safe: never rewrite commits other people have already based work on. Rebase and amend freely on a branch that is yours alone; once it's shared, only add to it.
Remotes, fetch and the state that changes
This stage's repository has a teammate who pushed while you were working locally — fetch shows what they did without touching your working tree.
Fetch updates origin/main (your remote-tracking branch) to wherever the remote's main now points. It never touches your own main, your index, or your working tree — that is what makes it safe to run at any time, as often as you like.
Pull is fetch followed by an integration step. Which step depends on pull.rebase: unset or false merges origin/main into your branch; true rebases your branch onto it instead Git project 2026.
Decide merge pull or rebase pull once, in config, not commit by commit
git pull's two modes
Merge pull (default)
Rebase pull (--rebase, or pull.rebase=true)
Result
A merge commit joining your local work and the remote's
Your commits replayed on top of the remote's — no merge commit
History shape
Records exactly what happened, including the pull itself
Linear, as if you had started from the latest remote
Safe on a shared branch you also push to?
Yes
Only if you haven't pushed your local commits yet — rebase rewrites them
$git pull --rebase origin mainRewrites local historyReference
Force only what you are sure is disposable, and check before you overwrite it
--force-with-lease fails if the remote-tracking ref has moved since your last fetch — meaning someone pushed something you haven't seen — instead of silently overwriting it. --force-if-includes, used alongside it, additionally verifies that whatever updated the remote-tracking ref in the background is already in your own history before allowing the push Git project 2020. Plain --force has neither check.
Choose how a branch rejoins main: merge, --no-ff, squash, or rebase
Same starting graph, four outcomes — the comparator runs all four through the same commands the terminal uses.
Loading the comparator…
Resolve a conflict, in whichever marker style helps
Loading the conflict tool…
git rerere ("reuse recorded resolution") remembers how you resolved a conflict and replays that resolution automatically the next time the same conflict appears — useful when rebasing a long branch keeps hitting the same collision on every replayed commit Git project 2026. git merge --abort (or git rebase --abort) bails out of a merge or rebase in progress and restores the state from immediately before it started.
Your local history and theirs have now diverged in a way a normal pull can't reconcile; check what actually happened before choosing how to reconcile it.
Deployment is the beginning, not the end. Observability, incident response, and continuous learning close the loop.
Monitoring Stack
◈APM
Application Performance Monitoring — traces, latency, throughput. (OpenTelemetry is vendor-neutral instrumentation, not a backend — it pairs with any of the tools below.)
Datadog APMNew RelicDynatraceOpenTelemetry
⚡Error Tracking
Real-time exception capture, stack traces, user context
SentryBugsnagRollbarHoneybadger
≡Log Aggregation
Centralised log storage, search, and alerting
ELK StackLoki + GrafanaSplunkDatadog Logs
◎Metrics
Time-series data, dashboards, threshold alerts
PrometheusGrafanaCloudWatchDatadog Metrics
Incident Response Flow
Detect0–2 min
Alert fires in PagerDuty / Slack. On-call engineer acknowledges.
▶ACK alert
▶Check dashboard
▶Assess severity (P1–P4)
SLA / SLO Dashboard
99.97%
Uptime (30d)
42ms
P50 Latency
187ms
P95 Latency
843ms
P99 Latency
0.03%
Error Rate
0.96
Apdex Score
Apdex = (Satisfied + Tolerating ÷ 2) ÷ Total requests, scored 0–1 against a response-time threshold. 0.96 is "Excellent."
A/B Test Result — Checkout Button Colour
Control (Blue)
3.2%
n = 12,400
Variant (Green)
3.9%
n = 12,350
Significance: 99.7%Lift +21.9%
Ship Variant B — statistically significant at p < 0.05
Feature Flag Cleanup Checklist
✓Confirm rollout has held at 100% for a full bake period
✓Remove flag evaluation code from all call sites
✓Remove flag-specific tests and mocks
✓Delete the old code path (the 'off' branch) entirely
✓Delete the flag from LaunchDarkly / ConfigCat
✓Remove the corresponding database column / config key
✓Update changelog and notify stakeholders
12
Documentation & Knowledge
Code that isn't documented is an asset with an expiry date. The best docs live in the same repo as the code they describe.
Documentation Types
◈API Docs
OpenAPI / Swagger
Machine-readable API specification. Auto-generates SDKs, val…
◇ADRs
Architecture Decision Records
Immutable record of a significant architectural choice, the …
≡Runbooks
Operational How-Tos
Step-by-step operator guides for routine tasks, deployments,…
Attribution only. Use freely in proprietary software.
Apache 2.0Low
Attribution + patent grant. Notice file required.
GPL v3Low for SaaS
Derivative works must also be GPL. Copyleft triggers on distribution, not network/SaaS use — see AGPL.
LGPLMedium
Can link to LGPL library without GPL propagation.
AGPLVery High
Network use = distribution. Strongest copyleft.
BSD 2/3Low
Attribution. Non-endorsement clause (3-clause).
CI Security Scanning
Secret Scanning
GitHub secret scanning / gitleaks
Blocks pushes containing credentials.
Dependency Scanning (SCA)
Dependabot / Snyk
CVE alerts surfaced on PRs.
14
Team Communication & Notifications
The right signal to the right person at the right time. Too little: surprises. Too much: everyone ignores everything.
Integration Map
Notification Preferences Matrix
slack
email
dashboard
PR Opened
Review Requested
PR Merged
Pipeline Failed
Pipeline Passed
Deployed to Staging
Deployed to Prod
Alert Fired
Deployment Announcement Template
*🚀 Deployed to Production* v2.15.0
*What shipped:*
• Payment gateway v2 (Stripe + Apple Pay)
• Checkout redesign — 22% fewer steps
• Fix: null pointer in order history (hotfix #863)
*Stats:*
Pipeline: ✅ 847 tests passed | Build: 2m 34s
Canary: 5% for 30min, no error rate change
Rollout: 100% of traffic as of 09:45 UTC
*Risk:* Low — canary held at 5% for 30min with no error-rate increase. No DB/schema migration in this release.
*Rollback:* `kubectl set image deploy/api api=app:v2.14.1` — safe, nothing to reverse migration-wise. Faster option: flip the Apple Pay flag off (seconds, no redeploy) if only the new payment path is misbehaving.
*Links:* Runbook | Grafana | Sentry | Release Notes
*On-call:* @alice (primary) @bob (secondary)
On-Call Rotation
A
Alice
Mar 11–17
ACTIVE
B
Bob
Mar 18–24
C
Carol
Mar 25–31
D
Dave
Apr 1–7
Incident Status Update Template
Investigating→
Identified→
Monitoring→
Resolved
We've identified a spike in 5xx errors on checkout tied to the 09:45 UTC deploy. Rolling back now.
Next update ETA15 min, or on state change
Alert Fatigue Prevention
⏱Set minimum duration thresholds — alerts must fire for >5min before paging
◉Group related alerts into a single incident, not separate pages
◎Review and tune alert thresholds monthly — aim for <2 false positives/week
✓Auto-resolve alerts when metrics recover; no manual ACK needed
Reference
Git Commands
153 entries: what each command does, the flags you will use, examples to copy, and four safety answers — does it rewrite history, can it discard uncommitted work, is it safe on a shared branch, and how to get back if it goes wrong.
153 entries
Setup & config
git configRead and write settings, per repository (.git/config), per user (~/.gitconfig) or for the whole machine.Safe
git commit --amendReplace the tip commit with a new one: add forgotten changes or fix its message. The old commit is left out of the branch.Rewrites local history
git diff # working tree vs index (unstaged changes)
git diff --staged # index vs HEAD (what you are about to commit)
git diff <a> <b> # two commits
git diff <a>...<b> # b against the merge base of a and b
--staged, --cached
Compare the index with HEAD.
--stat
A per-file summary with +/- bars.
--name-only, --name-status
List only the file names (with A/M/D/R status).
-w, --ignore-all-space
Ignore whitespace changes.
--word-diff
Show changed words inline instead of whole lines.
--color-moved
Colour blocks of lines that moved rather than changed.
--check
Warn about whitespace errors and conflict markers left in the diff.
--merge-base
With commits: diff against their merge base (Git 2.30+).
-U<n>, --unified=<n>
Lines of context around each hunk (default 3).
--no-index
Compare two paths outside a repository.
git diff
What you changed but have not staged.
git diff --staged
What the next commit will contain.
git diff main...feature
What feature changed since it branched from main — what a pull request shows.
git checkoutThe older command that both switches branches and restores files. Still supported; git switch and git restore split its two jobs.Discards work
Throw away local changes when switching; with paths, ignore unmerged entries.
--ours, --theirs
With paths during a conflict: take one side.
-p, --patch
Pick hunks to restore.
git checkout feature/login
Switch branches (git switch feature/login).
git checkout HEAD~3 -- src/payments.js
Restore a file from three commits ago (git restore --source=HEAD~3 -- src/payments.js).
Rewrites history?
No.
Discards uncommitted work?
Yes with paths: git checkout -- <path> overwrites edits in those paths without asking.
Safe on a shared branch?
Yes.
If it goes wrong
None for overwritten edits; switching back with git checkout - is fine.
One word doing two jobs is why git checkout is easy to misuse: git checkout name switches to a branch if one is called name, and otherwise overwrites a file called name.
No, but commits only that branch had are no longer named by anything.
Discards uncommitted work?
No uncommitted work.
Safe on a shared branch?
Yes: it only deletes your local name (git push --delete removes the remote one).
If it goes wrong
git prints the tip id on deletion (Deleted branch x (was a1b2c3d)); git branch x a1b2c3d restores it. HEAD's reflog has it too if you had it checked out.
Detect lines moved or copied from other files changed in the same commit; twice, also from the commit that created the file; three times, from any commit.
--ignore-rev <rev>
Skip a commit (a formatting sweep); blame the commit before it.
--ignore-revs-file <file>
Skip every commit listed (conventionally .git-blame-ignore-revs).
git blame -w -L 40,60 src/payments.ts
Who last changed lines 40–60, ignoring whitespace.
git config set blame.ignoreRevsFile .git-blame-ignore-revs
Always skip the commits in that file (GitHub reads it too).
The reflog of <ref> (default HEAD); the default subcommand.
-n <n>
Only the last n entries.
expire
Delete old entries (gc does this: 90 days by default, 30 for entries not reachable from the tip).
git reflog
Every recent move of HEAD: HEAD@{0} is now, HEAD@{1} the one before.
git reflog show main
Every value main has had.
git reflog --date=iso
When each move happened, instead of @{n} (reflog show takes git log's options).
git reset --hard HEAD@{1}
Go back to where HEAD was before the last command.
git branch rescued HEAD@{3}
Give a lost commit a branch name again.
Rewrites history?
No.
Discards uncommitted work?
No.
Safe on a shared branch?
Yes.
If it goes wrong
The reflog is the recovery tool. expire and delete remove entries (and with gc, the commits).
Reflogs are local: they are not pushed or cloned. Entries expire after 90 days (gc.reflogExpire), or 30 days for commits no longer on the branch (gc.reflogExpireUnreachable).
git fetchDownload new commits and refs from a remote and update the remote-tracking branches (origin/main…). Your branches and files do not change.Safe
Rebase your local commits onto the fetched branch instead of merging.
--no-rebase
Merge (creates a merge commit when the histories diverged).
--ff-only
Update only if it is a fast-forward; otherwise stop.
--autostash
Stash local changes around the pull.
--squash, --no-commit
Passed to the merge.
git pull --rebase
Replay your unpushed commits on top of what others pushed: a straight history.
git pull --ff-only
Update a branch you do not commit to (a local main, say).
git config set --global pull.rebase true
Make --rebase the default.
Rewrites history?
With --rebase: your unpushed commits get new ids. Merging and fast-forwarding rewrite nothing.
Discards uncommitted work?
No; it refuses when local changes conflict (use --autostash).
Safe on a shared branch?
Yes: it only changes your branch.
If it goes wrong
git reset --hard ORIG_HEAD undoes the merge or rebase it just did.
When your branch and the upstream have diverged and you have not chosen how to reconcile them, git pull stops with "Need to specify how to reconcile divergent branches" and names the three settings: pull.rebase false (merge), pull.rebase true (rebase) and pull.ff only.
git push --force-with-leaseOverwrite a remote branch after rewriting history — but only if nobody pushed to it since you last fetched.Rewrites shared history
Require each remote branch to equal your remote-tracking branch for it (origin/x).
--force-with-lease=<ref>:<expect>
Require <ref> to be exactly <expect>: the form git documents as not experimental.
--force-if-includes
Also require the remote tip to be in your branch's reflog, which catches a background fetch having updated origin/x (Git 2.30+).
git push --force-with-lease
After rebasing your own branch.
git push --force-with-lease --force-if-includes
The same, safe even if an editor fetches in the background.
git config set --global alias.pushf "push --force-with-lease --force-if-includes"
Make the safe form the easy one.
Rewrites history?
Yes: it replaces the remote branch's history.
Discards uncommitted work?
Only commits you have seen (they were in origin/x); a teammate's newer push makes it fail instead.
Safe on a shared branch?
Only on a branch you alone push to, or by agreement. Never on main.
If it goes wrong
Anyone who had the old tip can push it back; the old commits are in your reflog (<branch>@{1}).
A plain git fetch updates origin/x, and then --force-with-lease alone will overwrite whatever that fetch brought in. --force-if-includes closes that gap.
git push --forceOverwrite a remote branch with yours unconditionally. Commits others pushed since you last fetched are removed from it.Rewrites shared history
History is complete (log, blame work); contents arrive as you check out.
Rewrites history?
No.
Discards uncommitted work?
No.
Safe on a shared branch?
Yes.
If it goes wrong
Nothing to recover: it does not change commits or your files.
Unlike a shallow clone (--depth), a partial clone keeps all history, so log, bisect and merge-base behave normally. The server must support filters (GitHub and GitLab do).
git pull --rebaseFetch, then replay your unpushed commits on top of the upstream instead of merging: no merge commit, a straight history.Rewrites local history
git resetMove the current branch to another commit and, depending on the mode, reset the index and working tree to match. With paths, it only unstages.Discards work
git reset --hardMake the branch, the index and the working tree exactly match a commit. Uncommitted changes to tracked files are destroyed.Discards work
git filter-repoRewrite a whole repository's history: remove a file or secret everywhere, move paths, change emails. A separate tool the git project recommends over filter-branch.Rewrites shared history
Replace strings (secrets) listed in <file> in every file of every commit.
--subdirectory-filter <dir>
Make <dir> the new root (split a directory out).
--mailmap <file>
Rewrite author and committer names and emails.
--analyze
Report sizes and paths first, to see what to remove.
git filter-repo --invert-paths --path .env
Remove .env from all history (in a fresh clone).
git push --force --all && git push --force --tags
Publish the rewritten history; every clone must be re-cloned.
Rewrites history?
Yes: every commit from the first changed one onward gets a new id, on every branch.
Discards uncommitted work?
It refuses to run outside a fresh clone unless --force, to avoid losing local work.
Safe on a shared branch?
It rewrites shared history by design: coordinate, and have everyone re-clone.
If it goes wrong
Keep the original clone until the rewrite is verified.
A leaked secret must be rotated first: rewriting history does not remove copies in forks, clones, caches or pull request refs on the host. GitHub's guide to removing sensitive data covers the host side.
See also git filter-branch, git log -S. Not part of git: install git-filter-repo (Elijah Newren) separately.
git filter-branchThe original history-rewriting command. Its own documentation warns against it and points to git filter-repo.Rewrites shared history
git replaceMake git read one object in place of another, without rewriting history (for example, to graft an old history onto a new repository).Rewrites local history
git historyExperimental: rewrite one commit without an interactive rebase — fix it up with staged changes, reword it, or split it — and move the branches built on it.Rewrites local history
git sparse-checkout set services/api packages/shared
Work on just these.
Rewrites history?
No.
Discards uncommitted work?
No: files with uncommitted changes outside the new set stay on disk, and git warns about them.
Safe on a shared branch?
Yes.
If it goes wrong
git sparse-checkout disable restores every file.
Since Git 2.25: The sparse-checkout command. Cone mode (whole directories) is the default; non-cone patterns are deprecated since 2.37. See also git clone --filter, git worktree, scalar.
git clone --depthShallow clone: only the last n commits. Fast for CI; limited for development, because history commands see only those commits.Safe
git maintenanceRun (or schedule) the housekeeping that keeps a large repository fast: prefetch, commit-graph, loose-object packing, incremental repack.Safe
git multi-pack-indexWrite one index across all pack files, so object lookups do not search each pack in turn (maintenance's incremental-repack uses it).Safe
git format-patchWrite commits as email-ready patch files (one per commit), for projects that review by mail or to move commits without a shared remote.Safe
git patch-idCompute an id for a patch's content, ignoring line numbers and whitespace — how git recognises the same change under different commit ids.Safe
git fmt-merge-msgProduce the default merge commit message (Merge branch 'x' …) from FETCH_HEAD data, optionally with a one-line log of the merged commits.Safe
git for-each-repoRun a git command in every repository listed in a multi-valued config key (how scheduled maintenance reaches each registered repository).Safe
Checked against the Git 2.55.0 documentation: every option listed here appears in that command's manual page, and every “since” version in the release notes. Commands marked Try it run in the page's engine, which is tested line by line against real git.
Reference
Rescue
“I did X — how do I undo it?” 27 situations, each with the exact commands in order, a safety rating, and a Preview that loads the broken repository so you can run the fix and watch the graph first.
27 recipes
Find what happened, check the When line, and run the steps in order. Preview loads a repository in exactly that broken state; each step's Run applies it there first. Every recipe here is tested against real git: the broken state is recorded from git 2.55.0, and the fix is checked to produce the outcome it states.
The last commit
I want to undo my last commit but keep the changes
Rewrites local history
When: The commit is not pushed.
$git reset --soft HEAD~1Rewrites local history
Move the branch back one commit. The changes stay staged, ready to commit again. (git reset HEAD~1 leaves them unstaged instead.)
$git status
The undone commit's files are listed under Changes to be committed.
Fixed when: main is back at Add README, and parser.js is staged with the undone commit's content.
Otherwise: Pushed already? Use git revert HEAD instead, which adds an undoing commit (see "Undo a commit I already pushed").
$git commit --amend -m "Fix the login bug"Rewrites local history
Replace the last commit with one that has the new message (and a new id).
Fixed when: The tip commit reads "Fix the login bug" and sits directly on Add README; there is still one commit, not two.
Otherwise: Pushed already? Amending means a force push. On a branch only you use: amend, then git push --force-with-lease. On a shared branch, leave the typo.
When: The commits are on your own branch and not yet merged or shared.
$git reset --soft HEAD~3Rewrites local history
Move the branch back three commits; all their changes stay staged.
$git commit -m "Add search"
Record them as one commit.
Fixed when: feature/search has one commit, Add search, containing all three changes. (git rebase -i with squash does the same and lets you keep some commits.)
Otherwise: Already pushed to a branch others use? Squash when merging instead (the host's Squash and merge, or git merge --squash).
Fix the identity for this repository (add --global for all of them).
$git config user.email ada@example.com
Hosts link commits to your account by this email.
$git commit --amend --reset-author --no-editRewrites local history
Rewrite the last commit with the corrected author.
Fixed when: The last commit's author is Ada Lovelace <ada@example.com>.
Otherwise: Many commits? git rebase --exec "git commit --amend --no-edit --reset-author" <base> rewrites each one. A .mailmap file changes how names display, without rewriting anything.
I committed a secret (.env, a key) and haven't pushed
Rewrites local history
When: The commit is only local.
$git rm --cached .env
Stop tracking the file; it stays on disk.
$echo .env >> .gitignore
Keep it from being added again.
$git add .gitignore
Stage the ignore rule.
$git commit --amend --no-editRewrites local history
Rewrite the commit without the secret.
Fixed when: No commit on any branch contains .env; the file is still on disk and now ignored.
Otherwise: The old commit stays in your local reflog until it expires. It was never pushed, so nobody else has it; if the secret was ever pushed, use the next recipe.
When: The commit reached a remote. Treat the secret as public.
$git rm --cached .env
First, outside git: revoke and rotate the secret with its provider. Then stop tracking the file.
$echo .env >> .gitignore
Ignore it from now on.
$git add .gitignore
Stage the rule.
$git commit -m "Stop tracking .env"
This removes .env from the latest commit only.
$git push
The earlier commit — and the secret — is still in origin's history. Removing it needs git filter-repo and a force push (below).
Fixed when: The latest commit no longer has .env, but origin's history still contains it — the reason rotating the secret comes first.
Otherwise: To purge history (outside this page's engine): in a fresh clone run git filter-repo --invert-paths --path .env, then git push --force --all and --tags; ask everyone to re-clone; ask the host to purge cached views and pull request refs (GitHub's guide: Removing sensitive data from a repository). Secret scanning with push protection stops the next one.
What a repository is made of — objects, ids, refs and HEAD, the three trees, and what the reflog keeps — on one screen, with an inspector that computes real object ids as you type.
A repository is a store of four kinds of object — blobs (file contents), trees (directories), commits and annotated tags — each named by a hash of its content.
A commit records a whole snapshot (one tree), its parent commits, who made it and when. Diffs are computed when you ask for them.
A branch is a file holding one commit id. Committing moves it. HEAD names the branch you are on (or, detached, a commit).
Your changes move between three places: the working tree, the index (staging area) and HEAD. Every add, commit, restore and reset is a copy between two of them.
Nothing that was committed is lost at once: the reflog remembers where refs pointed for 90 days, and unreachable objects are deleted only after two weeks.
Snapshots, not diffs
Each commit points at a tree describing every file in the project at that moment. Files that did not change are not stored again: the new tree refers to the same blob id as the old one, because the id depends only on the content. git diff, git show and merges compute differences between snapshots when they need them Chacon 2014.
Renames are not recorded either: git detects them by comparing contents (a file that disappears and a similar one that appears), which is why git log --follow and git diff -M can find them and why a rename with heavy edits may show as a delete and an add.
The four objects, and the inspector
Type below. The blob id is the SHA-1 of the header blob <size>\0 followed by the content, so one changed character gives a different id — and a different tree id, and a different commit id.
Author and committer: Ada Lovelace <ada@example.com>, time 1767225600 +0000 (2026-01-01 00:00 UTC). With the defaults, these are the ids git 2.55.0 prints for the same input.
blob82359009487218af7a33dba25d3522d1bf5755fb
# Hello, git
The file's content only — no name, no date. Two files with the same content are one blob.
A directory: each entry is a mode, a type, the id of a blob or subtree, and a name.
commit870a871ceb0fdfdc7300b2492d6a6fcf45c2b30e
tree 1dd1b4e8b1e2b5771bc74d1231a7ede1460f1af7
author Ada Lovelace <ada@example.com> 1767225600 +0000
committer Ada Lovelace <ada@example.com> 1767225600 +0000
First commit
A snapshot: the id of the top tree, the parents (none for a first commit), who and when, and the message.
The object types
Object
Holds
Points to
Made by
blob
A file's bytes (no name, no mode)
Nothing
git add, git hash-object -w
tree
One directory: mode, type, id and name per entry
Blobs and other trees
git commit (via git write-tree)
commit
Author, committer, dates, message
One tree and zero or more parent commits
git commit, merge, rebase, cherry-pick…
tag (annotated)
Tagger, date, message, optional signature
One object, usually a commit
git tag -a
Object ids
Ids are 40 hexadecimal characters of SHA-1. Git has hardened its SHA-1 against the known collision attacks, and supports SHA-256 repositories with git init --object-format=sha256; at present a SHA-256 repository cannot exchange objects with a SHA-1 one git-init. The Git project plans to make SHA-256 the default for new repositories in Git 3.0, along with the reftable ref format and main as the initial branch name Git: breaking changes.
A short id such as 1dd1b4e is any unambiguous prefix; git prints 7 characters or more as the repository grows.
Refs and HEAD
Refs
Ref
Example
What it is
Moves when
Branch
refs/heads/main
A name for one commit
You commit, merge, reset or rebase on it
HEAD
ref: refs/heads/main
Which branch you are on — or, detached, a commit id
You switch, or check out a commit
Remote-tracking branch
refs/remotes/origin/main
Where main was on origin when you last fetched
git fetch, pull or push
Tag
refs/tags/v1.0.0
A fixed name for a release
Only if forced (-f)
Stash
refs/stash
The newest stash entry; older ones are its reflog
git stash push, pop, drop
ORIG_HEAD
ORIG_HEAD
HEAD before the last reset, merge or rebase
Those commands
Refs live as files under .git/refs/, packed into .git/packed-refs, or in the newer reftable format (git init --ref-format=reftable, Git 2.45+) gitrepository-layoutGit 2.45.
The three trees
Every file has up to three versions: in HEAD (the last commit), in the index (what the next commit will contain) and in the working tree (on disk). Commands copy between them.
Which command moves content where
Command
From
To
HEAD moves?
git add <path>
working tree
index
No
git commit
index
a new commit
Yes: the branch moves to it
git restore <path>
index
working tree
No
git restore --staged <path>
HEAD
index
No
git restore --source=<c> -SW <path>
commit <c>
index and working tree
No
git reset --soft <c>
—
—
Yes, to <c>; index and files unchanged
git reset [--mixed] <c>
commit <c>
index
Yes, to <c>
git reset --hard <c>
commit <c>
index and working tree
Yes, to <c>
git switch <branch>
<branch>'s commit
index and working tree (local changes carried)
Yes: HEAD names <branch>
git stash
index and working tree
a stash commit; both reset to HEAD
No
Stage 4 has the three-trees tool, which shows each file's three versions as you run these commands.
Reachability, the reflog and garbage collection
A commit is reachable if a ref, or a commit reachable from one, points at it. git log, clone and push only see reachable commits. After a reset, amend or rebase, the old commits are unreachable from branches — but not gone:
The reflog records every value HEAD and each branch has had. git reflog lists them; HEAD@{2} and main@{1} name them. Entries expire after 90 days (gc.reflogExpire), or 30 days for commits no longer on the branch (gc.reflogExpireUnreachable) git-config: gc.*.
Objects no ref or reflog reaches are deleted by git gc once they are older than gc.pruneExpire, two weeks by default. git fsck --lost-found finds them before that.
Reflogs are local. They are not pushed, fetched or cloned — your teammate's reflog cannot rescue your commit.
Uncommitted changes are the exception: a working-tree edit that was never staged or stashed is in no object, and reset --hard, restore or checkout -- <path> destroy it for good. The terminal's dry run warns about exactly this.
Distributed, not centralised
Every clone is a complete repository: all commits, all history, its own branches. A remote such as origin is another repository your clone knows the URL of; fetch and push copy objects and move refs between the two. Centralised systems (Subversion, Perforce) keep history on a server and give each client one checkout; there, committing means sending to the server, while in git it is a local operation you publish later.
Click any strategy to see the full branching rules, workflow, and commands.
GitFlow
Scheduled releases, complex branching
Branches5+ branch types
PR FrequencyLow — batched
Release CadenceScheduled (sprint / monthly)
Best forEnterprise software, packaged apps
solosmallmediumlargeenterprise
GitHub Flow
Simple, CI/CD-first, one rule
Branches2 branch types
PR FrequencyHigh — every feature
Release CadenceContinuous (every merge to main)
Best forSaaS products, web apps, API services
solosmallmediumlargeenterprise
Trunk-Based
Maximum velocity, feature flags, minimal branching
Branches1 branch (+ short-lived)
PR FrequencyContinuous (daily commits to trunk)
Release CadenceContinuous / on-demand
Best forHigh-velocity teams, mature CI, Google/Meta scale
solosmallmediumlargeenterprise
Interactive Feature
Follow the Code
One feature. Eight milestones. From idea to production — every command, every artifact.
◉
Issue Created
Planning
1 / 8
A bug or feature is captured in the backlog with acceptance criteria and priority.
Key Commands
$ gh issue create --title 'Add Apple Pay to checkout'
$ gh issue view 847
Artifacts
◆JIRA ticket
◆Acceptance criteria
◆Priority label
Reference
Lab
A live repository, full width. Pick a starting repository, type git commands, and watch the graph change. Undo and redo move between states; the session exports as a shell script you can run against real git, and a link replays it.