Read the Git history of one file

How-to Last tested 27 October 2026

Use Git log with a path to inspect commits affecting that file, and add --follow when tracing a single file through a rename. The result is a history selected by the command, not proof of who first conceived each line.

Trace one path through a rename

The -- separator in the example makes the final argument a path rather than a revision name. The log reference says the follow option continues one file’s history beyond renames and works for a single file1. A path-limited history and the complete repository history answer different questions: the former narrows attention, while the latter can show context that a single path leaves out.

The script writes a note, commits a second line and then renames the file in a later commit. It asks for the renamed path’s history with follow enabled. The subjects appear newest first: the rename, the added detail and the original creation. The count assertion verifies three returned lines for this constructed history, not for every future project.

Read the returned commits

A commit subject is a convenient label, not a complete explanation of the change. In an unfamiliar repository, inspect the relevant commit and the changes around it before assuming a title explains intent. A rename may be accompanied by edits, and a commit may touch related files that a path-limited list does not display. The list is a starting point for investigation, not a complete chronology of every dependency.

Record the path you searched and the revision range if you narrow one. A path can be reused, deleted or recreated. The same spelling at two points in history does not by itself establish that both files have the same lineage. Conversely, a useful earlier version may have had a different name, which is why the one-file rename behavior is relevant here.

Know what line attribution adds

A file history identifies commits selected for that path; it is not the same operation as assigning the current lines to revisions. A line-attribution view answers a different question: which commit Git associates with the current lines under its selected rules. Even then, a recorded author and a commit are not definitive evidence about who originally invented an idea, reviewed it or approved it.

If your question is “when did this behavior change,” first define the behavior and choose the relevant file and revision range. A rename-only commit can be important to navigation without being the semantic change you seek. Follow the evidence from the history into the actual patch and, where necessary, adjacent tests or documentation. Do not infer intent from a filename alone.

Interpret missing results cautiously

No output can mean the chosen path or range has no matching recorded change. It can also mean you are looking at the wrong name or at incomplete history. Check the spelling and the repository state before concluding that a file was never changed. A shallow or partial local history may not contain the earlier commits you expected; this fictional test uses a complete local repository and does not test those cases.

The follow option has a deliberate single-file limit. For a directory or a set of files, do not assume it produces a unified rename-aware history. Separate the paths you need to inspect, record the commands and compare the results with the surrounding repository history. When the source material cannot establish a lineage, keep that uncertainty in the note rather than filling the gap with a guess.

Choose what the list can prove

The printed example asks only for subject lines. That makes the expected output compact, but it intentionally omits the patch, author, timestamp and other fields a fuller investigation may require. A matching subject is not proof that the change you seek occurred in that commit. Use it to identify a candidate, then inspect the change itself and its surrounding context before drawing a conclusion.

The example’s rename occurs in its own commit after a small content change. This is a deliberately simple case: real commits may rename and substantially rewrite a file at the same time. Rename detection is a comparison of content and paths, so a difficult history may require examining adjacent commits rather than treating a missing entry as a definitive boundary. The test does not establish a detection threshold for a different file.

If a line seems to have appeared in an unexpected commit, consider whether it was moved, copied or reformatted. Those events can change the answer to a line-focused question without changing the underlying intent. Keep the question specific: a path history can show recorded changes to a file, while a code-review record or discussion may be needed to explain a decision. Neither should be silently substituted for the other.

Run the complete local example

Before running: use an empty directory. This script creates example-work, including a fictional working repository and a local bare remote; it stops if that directory already exists. The script creates and renames files only in its new example directory. Review the commands first and do not point it at a real project.

Download the complete MIT-licensed example and run it with Git 2.56.0. The printed script is the tested file. The two recorded runs used fresh directories and produced the same output; the remote in this example is a local directory.

#!/bin/sh
# SPDX-License-Identifier: MIT
set -eu
export TZ=UTC LC_ALL=C
export GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null
export GIT_AUTHOR_NAME='Example Author' GIT_AUTHOR_EMAIL='author@example.invalid'
export GIT_COMMITTER_NAME='Example Author' GIT_COMMITTER_EMAIL='author@example.invalid'
export GIT_AUTHOR_DATE='2026-10-27T12:00:00+00:00'
export GIT_COMMITTER_DATE='2026-10-27T12:00:00+00:00'
test "$(git --version)" = 'git version 2.56.0'
mkdir example-work
cd example-work
git init --bare -q --template= --initial-branch=main remote.git
git init -q --template= --initial-branch=main project
cd project
git config commit.gpgsign false
git config core.autocrlf false
git remote add origin ../remote.git
printf 'first\n' > note.txt
git add note.txt
git commit -qm 'Create note'
git push -q -u origin main
printf 'second\n' >> note.txt
git add note.txt
git commit -qm 'Add detail'
git mv note.txt notes-renamed.txt
git commit -qm 'Rename note'
printf 'History with --follow:\n'
git log --follow --format='%s' -- notes-renamed.txt
test "$(git log --follow --format='%s' -- notes-renamed.txt | wc -l | tr -d ' ')" = 3

Both runs produced:

History with --follow:
Rename note
Add detail
Create note

The script leaves example-work in the directory where it ran, including the local repositories and their data. After inspecting the result, remove that example directory when it is no longer needed. Do not remove a directory with the same name in a real project unless you have verified its contents and decided it is safe to delete.

Not covered on this page

Sources

  1. git-log — Git 2.56.0. Git 2.56.0; checked 27 October 2026 UTC. ↩

How we test: methodology. Found a mistake? Corrections.