| name | git-archaeology |
| description | Investigate how code reached its current state — when a line, function, import, or whole file was changed or deleted, who removed it, and what it looked like before. Use when `git blame` came up empty, when content has been refactored away, or when you need the full evolution of a function across commits. |
Git history archaeology
git blame only sees what's still in the working tree. For anything that was
deleted, moved, or refactored away, you need the commands below. Most agents
under-use them and end up scrolling through git log -p instead.
Output discipline (read first)
git log -p on a long-lived file can dump tens of thousands of lines and blow
the context window. Always:
- Start narrow. Use
--oneline or --stat to get a list of candidate
commits.
- Drill in. Use
git show <sha> -- <path> for the diff of one specific
commit.
- Scope the search. Add
--since="3 months ago", -n 20, or a path
restriction (-- <path>) so output stays manageable.
- Avoid
git log -p without a path filter on any non-trivial repo.
Decision tree (by agent intent)
"When did this exact line, string, or import disappear?"
git log -S'<exact-string>' --oneline -- <file>
The pickaxe. Returns commits that changed the count of that string in the
file. The most recent hit is typically the removal commit. Add -p only after
you've narrowed to a few candidates.
Notes:
-S is exact-string by default. Add --pickaxe-regex to make it a regex.
- The argument is "cuddled" with
-S (-S'foo bar'), no space.
-S will not detect pure in-file moves (count unchanged). Use -G for that.
--pickaxe-all shows the entire changeset of matching commits, useful when
a commit changes both a definition and its call sites in other files.
"When did the diff stop matching this regex?"
git log -G'<regex>' --oneline -- <file>
Like -S but matches any added or removed hunk line against the regex. Use
-G when:
- You don't know the exact string but know a pattern.
- You want to catch in-file moves (
-S won't).
- You want to find any diff that touched a pattern, even if the count was
preserved (e.g., a refactor that changed call sites without removing the
function).
"How did this function evolve over time?"
git log -L :<function-name>:<file>
Every commit that touched the function, with diffs scoped to just the function
body. Works for languages git understands (most mainstream ones).
"How did lines N–M evolve?"
git log -L <N>,<M>:<file>
"What's the full history of this file, including across renames?"
git log --follow --oneline -- <file>
git log --follow -p -- <file>
--follow only works for a single file, not directories.
"Where was a now-deleted line last present?"
Two-step pattern when you have an exact deleted string:
git log -S'<deleted-string>' --oneline --all -- <file>
git blame --reverse <old-sha>..HEAD -- <file>
The reverse blame tells you, for each line, the last commit it survived in
before being modified or deleted. Pinpoints the exact deletion commit.
"This file no longer exists — when was it deleted, and what was in it?"
git log --all --full-history --oneline -- <deleted-path>
git show <sha> --stat
git show <sha>^:<deleted-path>
If you don't know the path, find it from filename alone:
git log --all --diff-filter=D --summary | grep -i '<filename>'
git log --all --oneline -- '**/<filename>.*'
"Who deleted it, in one shot?"
git rev-list -n 1 HEAD -- <deleted-path>
git show $(git rev-list -n 1 HEAD -- <deleted-path>) -- <deleted-path>
"Restore a deleted file (locally, no commit)"
git restore --source=<deletion-sha>^ -- <deleted-path>
git checkout <deletion-sha>^ -- <deleted-path>
The ^ is critical — at the deletion commit the file is already gone, so we
read from its parent.
"Search commit messages, not content"
git log --all --grep='<text>' --oneline
git log --all --grep='<text>' -i --oneline
Orthogonal to -S/-G, which only see the diff.
Standard workflow for "why does this code look like this"
git log --follow --oneline -- <file> — overview of commits touching it.
- If a recent commit looks suspicious:
git show <sha> -- <file>.
- If you expected to find something and it's missing:
git log -S'<expected-string>' --oneline -- <file>.
- For a specific function's full lifecycle:
git log -L :<fn>:<file>.
- For the deletion point of a known string: pickaxe to find an old commit
that contained it, then
git blame --reverse <old-sha>..HEAD -- <file>.
Useful flags reference
| Flag | Effect |
|---|
--all | Search all refs, not just the current branch. Use when investigating something that may have lived only on a feature branch. |
--full-history | Keeps commits that history-simplification would otherwise drop. Needed for accurate history across merges. |
--follow | Track a single file across renames. Single-file only. |
-M / -C | Detect renames (-M) and copies (-C) when reading diffs. |
--diff-filter=D | Restrict to commits that deleted something. A=added, M=modified, R=renamed. |
--source | When combined with --all, annotate each commit with the ref it was reached from. |
--pickaxe-all | With -S/-G, show all files in the matching commit, not just the matching file. |
--pickaxe-regex | Treat the -S argument as a regex. |
--since / --until | Time-bound the search. Cheap perf win on big repos. |
-n <count> | Cap result count. |
--stat | Per-commit file stats instead of full patches. Good first pass. |
Notes and pitfalls
- Always include
-- before paths to disambiguate from refs (e.g.
git log -S'foo' -- src/auth.ts).
-S triggers on count change. A pure refactor that moves a line within
the same file will not match. Use -G for those.
-G runs diff twice and greps; it's slower than -S. Scope with paths and
--since on big repos.
- Without
--all, git log -- <path> shows nothing if the path never existed
on the current branch. When in doubt, add --all.
git log --full-history -- <path> alone has had bugs in some git versions
for deleted files; pair with --all for reliability.
- For files that were renamed,
git log -- <new-path> only shows post-rename
history. Use --follow (one file) or git log --all -- <old-path> when
hunting across rename events.