--- 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: 1. **Start narrow.** Use `--oneline` or `--stat` to get a list of candidate commits. 2. **Drill in.** Use `git show -- ` for the diff of one specific commit. 3. **Scope the search.** Add `--since="3 months ago"`, `-n 20`, or a path restriction (`-- `) so output stays manageable. 4. **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?" ```bash git log -S'' --oneline -- ``` 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?" ```bash git log -G'' --oneline -- ``` 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?" ```bash git log -L :: ``` 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?" ```bash git log -L ,: ``` ### "What's the full history of this file, including across renames?" ```bash git log --follow --oneline -- # overview git log --follow -p -- # with diffs (use sparingly) ``` `--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: ```bash # 1. find a historical commit that contained the string git log -S'' --oneline --all -- # 2. reverse-blame from that commit to find the last commit it survived in git blame --reverse ..HEAD -- ``` 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?" ```bash # find all commits that touched the path, even on other branches git log --all --full-history --oneline -- # the most recent of those is usually the deletion. confirm: git show --stat # view the file's contents at any commit where it existed git show ^: ``` If you don't know the path, find it from filename alone: ```bash # list all delete events with paths git log --all --diff-filter=D --summary | grep -i '' # or glob across all branches git log --all --oneline -- '**/.*' ``` ### "Who deleted it, in one shot?" ```bash git rev-list -n 1 HEAD -- # the deletion commit git show $(git rev-list -n 1 HEAD -- ) -- ``` ### "Restore a deleted file (locally, no commit)" ```bash git restore --source=^ -- # or, on older git: git checkout ^ -- ``` The `^` is critical — at the deletion commit the file is already gone, so we read from its parent. ### "Search commit messages, not content" ```bash git log --all --grep='' --oneline git log --all --grep='' -i --oneline # case-insensitive ``` Orthogonal to `-S`/`-G`, which only see the diff. ## Standard workflow for "why does this code look like this" 1. `git log --follow --oneline -- ` — overview of commits touching it. 2. If a recent commit looks suspicious: `git show -- `. 3. If you expected to find something and it's missing: `git log -S'' --oneline -- `. 4. For a specific function's full lifecycle: `git log -L ::`. 5. For the deletion point of a known string: pickaxe to find an old commit that contained it, then `git blame --reverse ..HEAD -- `. ## 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 ` | 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 -- ` shows nothing if the path never existed on the current branch. When in doubt, add `--all`. - `git log --full-history -- ` alone has had bugs in some git versions for deleted files; pair with `--all` for reliability. - For files that were renamed, `git log -- ` only shows post-rename history. Use `--follow` (one file) or `git log --all -- ` when hunting across rename events.