How to Discover Files from Previous Commits in Git

⏱ 7 min read TECH

Git doesn’t just track your current code — it holds the entire history of your project. Every commit is a snapshot you can inspect, compare, or restore from at any time. This post covers the most practical ways to navigate that history and work with files from the past.


What Problem Are We Solving?

You’ve likely run into at least one of these scenarios:

  • “I deleted a file two weeks ago and now I need it back.”
  • “What exactly changed between these two releases?”
  • “What did this file look like before my colleague refactored it?”
  • “When was this file added, and who touched it last?”

Git has tools for all of these. Let’s go through them.


Inspect a Single Commit

git show --name-only <commit_hash>

What it does: Shows the commit message, the diff, and the list of files that were touched in that commit. The --name-only flag suppresses the full diff and gives you just the filenames.

If you want only the filenames with no extra output at all:

git show --pretty="" --name-only <commit_hash>

This is the fastest way to answer “what did this commit actually touch?”


Compare Two Commits

git diff --name-only <old_commit> <new_commit>

What it does: Instead of showing the content of the diff, --name-only lists only the files that differ between the two commits. Useful when you want a high-level picture of what changed over a range — for example, between a release tag and HEAD:

git diff --name-only v1.0.0 HEAD

See the Full File Tree at a Past Commit

git ls-tree -r --name-only <commit_hash>

What it does: ls-tree lists the contents of a Git tree object. The -r flag makes it recursive (descends into subdirectories), and --name-only gives you clean paths. The result is a complete snapshot of every file that existed in the repository at that exact commit — including files that have since been deleted or renamed.

This is the command you want when you need to explore what the project looked like at a specific point in time.


Read a File from a Past Commit Without Checking It Out

git show <commit_hash>:<path/to/file>

What it does: Git can print the content of any file at any commit directly to stdout — no branch switching, no working tree changes. The colon syntax tells Git to look inside a specific commit’s tree for a specific path.

git show ae0d112:src/main/java/com/example/UserService.java

This is completely safe and non-destructive. Pipe it to a file or your editor if you want to work with the content.


Trace a File’s Entire History

git log -- <path/to/file>

What it does: Filters the commit log to only show commits that touched a specific file. The -- separator is important — it tells Git that what follows is a file path, not a branch name or commit reference.

To follow renames across the file’s history (Git tracks renames as a rename + content change):

git log --follow -- <path/to/file>

For a compact view:

git log --oneline -- <path/to/file>

Find Deleted Files

git log --diff-filter=D --name-only

What it does: --diff-filter limits log output to commits where certain types of changes occurred. D stands for “deleted”. This walks the entire history and prints only the commits where files were deleted, along with which files disappeared.

Once you find the commit hash where the file was deleted, use git show <hash>^:<path> on the parent commit (the ^ means “one commit before”) to recover the content before it was removed.


Find Who Wrote a Specific Line — and When

git blame <path/to/file>

What it does: Annotates every single line in a file with the commit hash, author name, and timestamp of the last modification to that line. It answers not just what the code does, but when and by whom it was written.

git blame -L 10,20 src/main/java/App.java

The -L flag restricts output to a line range — useful for large files where you only care about a specific section.


Search for a String Across All History (The “Pickaxe”)

git log -S "search_term"

What it does: This is Git’s “pickaxe” search. It scans the entire commit history and returns only the commits that added or removed the exact string you provide — not just commits where the file changed, but specifically commits where the count of that string in the codebase went up or down.

git log -S "calculateDiscount"

Invaluable when you deleted a function weeks ago and remember its name but not where it lived. Once you find the commit, use git show <hash>:path/to/file to read the old content.

For regex-based search instead of an exact string:

git log -G "pattern.*here"

-G matches any commit where the diff contains a line matching the regex, which is broader than -S.


Search Commit Messages for Keywords

git log --grep="search_term"

What it does: Filters the log to only commits whose message contains the given string. Useful when you remember writing something like “fix login bug” but don’t remember when or what files were involved.

git log --grep="login" --oneline

Browse History Visually in the Terminal

git log --oneline --graph --decorate --all

What it does: Combines several flags to give you a compact, readable history view:

  • --oneline — one line per commit (hash + message)
  • --graph — draws branch/merge topology as ASCII art
  • --decorate — shows branch and tag names next to commits
  • --all — includes all branches and remotes, not just the current one

Good for orienting yourself before drilling into a specific commit.


Safely Explore an Old State (Detached HEAD)

git switch --detach <commit_hash>

What it does: Checks out the commit without creating or switching to a branch. You’re now in “detached HEAD” state — your working tree reflects the project exactly as it was at that commit. You can look around, run the code, build it, whatever you need.

Nothing you do here affects your branches unless you explicitly create one. To return to where you were:

git switch main

Prefer git switch --detach over the older git checkout <commit_hash> — it’s explicit about intent and won’t accidentally trigger branch switching if you mistype a name.


Restore a Specific File from a Past Commit

git checkout <commit_hash> -- path/to/file

What it does: Copies a single file from the specified commit directly into your working tree and staging area, without moving HEAD or touching any other files. This is how you recover a deleted or overwritten file — you get the old version back on your current branch, ready to commit.

git checkout ae0d112 -- README.md

After running this, git status will show the file as modified (staged). Review it, then commit.


Compare a Single File Between Two Commits

git diff <old_commit> <new_commit> -- path/to/file

What it does: Scopes git diff to a single file instead of the whole tree. You get the line-by-line diff for that file between the two commits — nothing else. Great when you know exactly which file changed and want to understand how.

git diff HEAD~5 HEAD -- src/main/java/UserService.java

Check If a File Was Ever Renamed

git log --follow --diff-filter=R --name-status -- path/to/file

What it does: The --follow flag enables rename tracking across history. --diff-filter=R filters to only rename events, and --name-status shows the old and new name side by side. This reveals the full rename chain of a file across its lifetime — useful in large codebases that have gone through refactoring.


Quick Reference

GoalCommand
What files did this commit touch?git show --name-only <commit>
What changed between two commits?git diff --name-only <old> <new>
Full file tree at a past commitgit ls-tree -r --name-only <commit>
Read a file from the pastgit show <commit>:path/to/file
History of a single filegit log --follow -- path/to/file
Find deleted filesgit log --diff-filter=D --name-only
Who wrote this line?git blame -L <from,to> path/to/file
Search text across all historygit log -S "string"
Search commit messagesgit log --grep="term" --oneline
Explore old state safelygit switch --detach <commit>
Restore a file to current branchgit checkout <commit> -- path/to/file
Diff a single file between commitsgit diff <old> <new> -- path/to/file
Find renames in historygit log --follow --diff-filter=R --name-status -- file

The Mental Model

Think of Git as a filesystem with a built-in time dimension. Every commit is a complete snapshot — not a delta, not a diff. When you run git show or git ls-tree, you’re not reconstructing history; you’re reading a snapshot that has always been there. The commands above are just different lenses for viewing and extracting from those snapshots.

Once this clicks, Git stops feeling like a version control system and starts feeling like a database you can query.

Comments

Please ensure Giscus is configured with your correct Repository ID and Category ID at giscus.app to enable comments.