Skip to content

AI-Powered Git History Analysis

Lesson 2 of 8Intermediate12 min readGitHub Copilot & AI Engineering · AI + GitVerified: git 2.43.0 on Ubuntu 24.04, September 2026

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.

Run the command, feed the output to the model, ask a specific question.

Terminal window
{/* What happened in this range, compactly */}
git log --oneline v1.4.0..v1.5.0
Terminal window
{/* The same, with files and line counts more signal, more volume */}
git log --stat v1.4.0..v1.5.0
Terminal window
{/* Every commit that changed the number of occurrences of a string */}
git log -S 'processPayment' --oneline

The 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.

AI is a reader, so the leverage is in giving it the right output to read.

CommandAnswers
git log --oneline RANGEWhat commits are in this range
git log --stat RANGE…and which files they touched
git log -p PATHFull 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 PATHFile history across renames
git blame PATHWho last touched each line, and in which commit
git log --mergesMerge commits only — integration points
git shortlog -snCommit counts by author
git log --since --untilA 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.

Terminal window
git log --oneline v1.4.0..v1.5.0

What 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.

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.

Archaeology. Genuinely useful, and the answer quality depends entirely on whether the original author wrote a message body.

Terminal window
{/* Who last touched each line, and in which commit */}
git blame -L 40,80 src/payments.py

Take 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.

Reading a file’s whole history to understand its shape.

Terminal window
git log --follow --stat -- src/auth/session.py

Useful 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.

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.

Terminal window
{/* 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.

Terminal window
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.

Terminal window
{/* Exclude merge commits */}
git log --oneline --no-merges v1.4.0..v1.5.0

Summarise 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.

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.

  1. Bound the window and the area.

    Terminal window
    git log --oneline --since='3 weeks ago' -- src/checkout/ src/payments/
  2. 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.

  3. Read the candidates it names. git show <hash> for each. This is the step that converts a hypothesis into something you believe.

  4. 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.

  5. Prove it with git bisect, using a test that reproduces the latency.

  6. 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.

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:

Terminal window
{/* 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.

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.

A distinct category, because the questions have precise answers and getting them wrong is expensive.

“Is this fix in the release we shipped?”

Terminal window
{/* Which tags contain this commit? */}
git tag --contains a3f9c21
Terminal window
{/* Is it reachable from this branch? */}
git branch --contains a3f9c21

These 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?”

Terminal window
git log --oneline main..release/1.5

Both 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?”

Terminal window
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.

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.

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.

Terminal window
{/* 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)"'
Terminal window
{/* The discussion on a specific pull request */}
gh pr view 482 --comments

Combining 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.

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.

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.

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.

  • The leverage is in choosing the right command; AI reads the output
  • git log -S finds when a symbol appeared or vanished, without knowing its file
  • git log --follow is required for any file that has been renamed
  • AI narrows a regression to candidates; git bisect identifies 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

Use a repository with real history — one of your own, or a public one you can clone.

  1. Pick two tags and run git log --oneline between them. Ask for a summary grouped by area. Predict: does it group meaningfully, or just re-order the list?

  2. Ask it to cite a commit hash for each group. Spot-check two. Predict: do the commits support the claims?

  3. Pick a function you know and run git log -S 'functionName' --oneline. Predict: does it find the commit that introduced it?

  4. Find a renamed file. Run git log and then git log --follow on it. Compare what a summary of each would tell you.

  5. Take a confusing block of code, run git blame -L on it, and ask why it is that way. Predict: does the answer come from a message body, or from inference?

  6. Ask about a regression, then verify with git bisect. Compare the answer to the proof.

AI-assisted engineering learning pathEleven lessons on getting value from Copilot and agents without giving up review discipline.