Git history is a complete record of how a codebase reached its current state. It is also, in any repository older than a year, effectively unreadable by a human in one sitting.
That combination — high information density, poor human ergonomics — is exactly where AI helps most. This lesson is about extracting answers from history, and about the one discipline that keeps those answers trustworthy.
The short answer
Section titled “The short answer”Run the command, feed the output to the model, ask a specific question.
{/* What happened in this range, compactly */}git log --oneline v1.4.0..v1.5.0{/* The same, with files and line counts — more signal, more volume */}git log --stat v1.4.0..v1.5.0{/* Every commit that changed the number of occurrences of a string */}git log -S 'processPayment' --onelineThe question you ask determines whether this is useful. “Summarise this” produces a list. “Which of these commits could have changed how timeouts are handled?” produces an answer you can check.
The commands worth knowing
Section titled “The commands worth knowing”AI is a reader, so the leverage is in giving it the right output to read.
| Command | Answers |
|---|---|
git log --oneline RANGE | What commits are in this range |
git log --stat RANGE | …and which files they touched |
git log -p PATH | Full history of one file, with diffs |
git log -S 'string' | Which commits changed occurrences of a string |
git log -G 'regex' | Which commits’ diffs match a pattern |
git log --follow PATH | File history across renames |
git blame PATH | Who last touched each line, and in which commit |
git log --merges | Merge commits only — integration points |
git shortlog -sn | Commit counts by author |
git log --since --until | A time window |
Two are underused and disproportionately valuable with AI.
git log -S — the “pickaxe” — finds commits where a string’s occurrence count changed. It is how
you find when a function was introduced or removed without knowing which file it lived in. Feed its
output to a model with “which of these introduced the retry behaviour?” and you have narrowed a
regression hunt to a handful of commits.
git log --follow survives renames, which ordinary git log PATH does not. A file that has moved
twice has three disconnected histories unless you ask for the connected one.
Four questions history actually gets asked
Section titled “Four questions history actually gets asked”“What changed between these releases?”
Section titled ““What changed between these releases?””The most common, and the one AI does best.
git log --oneline v1.4.0..v1.5.0What it doesLists commits reachable from v1.5.0 but not v1.4.0, one per line.
Why we run itA release range is the natural unit for 'what is new', and the compact form fits comfortably in a model's context.
Expected resultOne line per commit: abbreviated hash and subject.
Ask for grouping rather than a list: “Group these by area, and flag anything that looks like a behaviour change rather than an internal refactor.” Grouping is genuine work; enumeration is not.
This is also the input to generated release notes, which is the same technique with a different audience.
“When did this break?”
Section titled ““When did this break?””The regression question, and the one where the AI/Git division of labour is clearest.
AI narrows. Given git log -S output for the relevant symbol, or the log for the file, it can
propose which commits are plausible causes and say why.
git bisect proves. It runs your test against actual commits and identifies the exact one. No
amount of reading replaces it.
The workflow: use AI to pick the range and the test, then let bisect do the work. Handing bisect a tight range with a good test is most of the effort, and that is the part AI shortens.
“Why is this code like this?”
Section titled ““Why is this code like this?””Archaeology. Genuinely useful, and the answer quality depends entirely on whether the original author wrote a message body.
{/* Who last touched each line, and in which commit */}git blame -L 40,80 src/payments.pyTake the commit hashes blame reports, get their full messages, and ask what problem the change was solving. Where the commits reference issues or pull requests, that context is often richer than the message.
The honest limitation: if the history does not record the reason, no amount of analysis recovers it. A model asked why a line exists, given commits with messages like “fix”, will produce a plausible explanation. That explanation is a guess presented in the same tone as a fact.
This is the strongest argument for the commit message discipline in the previous lesson: you are writing for this query.
“How did this component evolve?”
Section titled ““How did this component evolve?””Reading a file’s whole history to understand its shape.
git log --follow --stat -- src/auth/session.pyUseful when joining a project or before a significant refactor. The question worth asking is not “what happened” but “what does the sequence of changes suggest about what this component is actually responsible for?” — which is synthesis rather than summary, and is where the technique earns its keep.
Getting history into a context window
Section titled “Getting history into a context window”The practical constraint on all of this is size. A year of history in a busy repository is far more text than any model will read, and the failure is silent: the input is truncated and the summary describes whatever fitted.
Four techniques, in the order worth trying them.
Narrow the range. Almost always the answer. A question about a regression has a window; a question about a release has two tags.
{/* A specific window, rather than everything */}git log --oneline --since='2026-06-01' --until='2026-07-01'Narrow the path. Most questions are about part of the codebase.
git log --oneline -- src/payments/Drop the noise. Merge commits often carry no information beyond “these branches joined”, and in a squash-merge repository they are the only commits — so this flag cuts either everything useful or nothing useful, depending on your workflow. Check which before using it.
{/* Exclude merge commits */}git log --oneline --no-merges v1.4.0..v1.5.0Summarise in stages. For genuinely large history, summarise per-area or per-month and then summarise the summaries. Compression compounds — each stage loses detail — so cite hashes at the first stage and carry them through, or the final answer is unverifiable.
A worked example: an incident timeline
Section titled “A worked example: an incident timeline”The highest-value application, because it happens under time pressure and the manual version is slow.
The situation: checkout latency degraded, somewhere in the last three weeks, cause unknown.
-
Bound the window and the area.
Terminal window git log --oneline --since='3 weeks ago' -- src/checkout/ src/payments/ -
Ask a specific question, not for a summary:
Here are the commits touching checkout and payments in the last three weeks. Which of these could plausibly affect request latency? Cite hashes and say why for each.
-
Read the candidates it names.
git show <hash>for each. This is the step that converts a hypothesis into something you believe. -
Widen if nothing fits. Latency regressions often come from a dependency bump or a configuration change rather than from the obvious module — so check lock files and config paths, which the first query excluded.
-
Prove it with
git bisect, using a test that reproduces the latency. -
Record what you found in the incident write-up, with hashes. The next person searching for this will find your write-up before they find the commits.
The division of labour is the point. Steps 1, 2 and 4 are where AI saves real time — reading a hundred commit subjects and forming hypotheses is exactly the drudgery it removes. Steps 3 and 5 are where the answer becomes true.
Reconstructing architectural decisions
Section titled “Reconstructing architectural decisions”A specialised and genuinely useful case: understanding why a system is shaped the way it is, when nobody who built it is still available.
History carries more of this than people expect, spread across three places:
Commit message bodies, where they exist.
Merge commits, which name the branch and often the pull request number — and the pull request usually contains the discussion the commit does not.
The sequence itself. A component rewritten three times in six months and then untouched for two years is telling you something the messages may not.
The query that works:
{/* Integration points, with dates */}git log --merges --format='%h %ad %s' --date=short -- src/scheduler/Feed that to a model with the pull request numbers, and ask what the sequence suggests about how responsibilities moved. Then read the pull requests it points at.
Two cautions specific to this use.
Distinguish inference from record. Ask explicitly: “for each conclusion, say whether it is stated in a commit or inferred from the pattern.” Without that instruction the two are presented identically, and an inferred rationale becomes a repeated fact very quickly.
A reconstruction is not an ADR. If the reasoning matters, write it down as a decision record now, attributed to the reconstruction rather than to the original authors. See AI repository documentation for the general problem of derived documentation.
Authorship, and where to stop
Section titled “Authorship, and where to stop”git shortlog -sn and git blame make it easy to produce statistics about people. Two boundaries
worth holding.
Blame is for finding context, not for assigning fault. The name attached to a line is whoever last touched it — frequently somebody who reformatted the file. Its value is as a route to the commit and the discussion around it.
Commit counts measure commit counts. Not productivity, not contribution, not value. A large refactor is one commit; a day of careful debugging may be one commit. Using this data for performance assessment measures how somebody uses Git.
There are legitimate uses: identifying who has context on an unfamiliar module, finding a reviewer, noticing that a critical component has a single contributor and therefore a bus factor problem. Those are questions about the codebase. Questions about individuals need a different tool and, usually, a conversation.
Release history questions
Section titled “Release history questions”A distinct category, because the questions have precise answers and getting them wrong is expensive.
“Is this fix in the release we shipped?”
{/* Which tags contain this commit? */}git tag --contains a3f9c21{/* Is it reachable from this branch? */}git branch --contains a3f9c21These are the authoritative answers, and they are the ones to use. A model asked “did the timeout fix ship in 1.4?” will reason from commit dates and be wrong whenever cherry-picking, reverts or long-lived release branches are involved — which is exactly when the question gets asked.
“What is on the release branch that is not on main?”
git log --oneline main..release/1.5Both directions matter. release/1.5..main shows what main has that the release does not, which is
how you find a hotfix that never got forward-ported.
“Was this reverted?”
git log --oneline --grep='Revert' --since='6 months ago'Worth checking before concluding a fix is present. A commit can be in the history and undone three commits later, and a summary of the range will happily list both without noting the relationship.
The pattern across all three: AI is good at the narrative question and unreliable on the reachability question. “What went into this release” is narrative. “Is commit X in release Y” is a graph query with a command that answers it exactly.
Making the answers checkable
Section titled “Making the answers checkable”The discipline that makes this trustworthy is small and worth stating as a rule:
Ask for answers that cite commits.
A summary that says “retry behaviour changed in a3f9c21 and 7b2e004” can be verified in twenty
seconds. A summary that says “retry handling was reworked during the 1.5 cycle” cannot be verified at
all, and the two require the same effort to produce.
Include the request explicitly: “cite the commit hash for each claim”. Then spot-check one or two. The point is not distrust for its own sake — it is that a citation makes the difference between a finding and an impression, and you will be repeating the finding to somebody else.
Adding GitHub context
Section titled “Adding GitHub context”Git history records what changed. The discussion about why frequently lives in pull requests and issues, which are not in the repository at all — and pulling both together produces noticeably better answers than either alone.
{/* Merged pull requests in a window, as JSON */}gh pr list --state merged --limit 50 --json number,title,mergedAt,labels,author --jq '.[] | "\(.number) \(.mergedAt[0:10]) \(.title)"'{/* The discussion on a specific pull request */}gh pr view 482 --commentsCombining a git log range with the corresponding pull request titles and labels gives a model two
things it cannot get from commits: the framing the author chose when proposing the change, and the
objections reviewers raised. For the archaeology question in particular, a reviewer asking “why not
just use the existing queue?” and the answer they got is often the entire explanation.
Two practical notes. gh output is JSON, which is compact and easy to filter — prefer --jq to
pasting everything. And the same privacy consideration from
commit messages applies: pull request bodies and comments can contain
customer detail, incident specifics and occasionally credentials.
See gh pr and gh api for the wider command surface.
Common mistakes
Section titled “Common mistakes”Summarising without a question. “Summarise this history” produces a list. A specific question produces analysis.
Accepting a causal claim. “This commit caused the regression” is a hypothesis. git bisect tests
it.
Forgetting --follow. A renamed file has a truncated history, and the summary will confidently
describe only the part after the rename.
Trusting reconstructed intent. If the message body is empty, the reason is not in the history and anything supplied is inference.
Treating blame as attribution. It reports who last touched a line, which is often a formatting change.
Pasting more than fits. A truncated log produces a summary of an arbitrary subset, silently.
Using commit counts as a performance measure. They measure commit granularity.
Building it into a habit
Section titled “Building it into a habit”The techniques are only useful if they are reached for, and the ones that stick are the ones attached to a moment that already exists.
Before a refactor. Read the component’s history first. Ten minutes of “how did this get this way” routinely changes the plan, and it is the case where the sequence-of-changes question pays off most.
During an incident. The timeline workflow above. Worth rehearsing once when nothing is broken, so the commands are familiar when something is.
When joining a codebase. Pick the five files you will work in most and read their histories. This is faster than reading the code cold and gives you the reasons as well as the shape.
Before a release. The range summary, the reachability checks, and the revert check. This one is already a release notes task; treating it as a history question first produces better notes.
When a reviewer asks “why is this like this?” The archaeology query, with citations, pasted into the review thread. It answers the question and leaves the answer where the next person will find it.
That last one has a compounding effect worth noticing: an answer written into a pull request becomes part of the record the next reconstruction reads. History analysis is easier in repositories where somebody has been doing it.
Mental model
Section titled “Mental model”Git history is a complete, badly-formatted record. AI is a reader that turns it into prose. Reading is not proving — so use the summary to know where to look, and the commands to know what is there.
What you learned
Section titled “What you learned”- The leverage is in choosing the right command; AI reads the output
git log -Sfinds when a symbol appeared or vanished, without knowing its filegit log --followis required for any file that has been renamed- AI narrows a regression to candidates;
git bisectidentifies the commit - Archaeology only recovers reasons the history actually recorded
- Ask for answers that cite commit hashes, then spot-check them
- Blame reports the last toucher, not the author of the logic
- Commit counts measure commit granularity, not contribution
- Large history must be filtered before it is pasted, or it is truncated silently
Exercise
Section titled “Exercise”Use a repository with real history — one of your own, or a public one you can clone.
-
Pick two tags and run
git log --onelinebetween them. Ask for a summary grouped by area. Predict: does it group meaningfully, or just re-order the list? -
Ask it to cite a commit hash for each group. Spot-check two. Predict: do the commits support the claims?
-
Pick a function you know and run
git log -S 'functionName' --oneline. Predict: does it find the commit that introduced it? -
Find a renamed file. Run
git logand thengit log --followon it. Compare what a summary of each would tell you. -
Take a confusing block of code, run
git blame -Lon it, and ask why it is that way. Predict: does the answer come from a message body, or from inference? -
Ask about a regression, then verify with
git bisect. Compare the answer to the proof.