Home / Alt manpages / git-annotate(1)

  • git-annotate(1)
  • User command
  • linux

Trace File Lines to Their Commits with git annotate

A line in a file looks wrong and nobody remembers who wrote it or why, so you need git annotate to point at the commit. You will finish with a repeatable way to identify the commit, author and date behind each line of a tracked file. You will also narrow the report to a small range and produce machine-readable output when a script needs the result.

Allow about fifteen minutes. You need Git and a checked-out repository with at least one committed file. The examples use Git 2.43.0 from Ubuntu package git 1:2.43.0-1ubuntu7.3, matching the installed git-annotate(1) manual.

Safety boundary

This is a read-only investigation. git annotate does not edit files, commits or repository configuration, so it does not need sudo.

1. Check the installed command

Run this from the repository that contains the file:

$ git --version
git version 2.43.0
$ git rev-parse --show-toplevel
/path/to/project

If the second command fails with "not a git repository", change directory to a clone or worktree before continuing. Confirm the path you intend to inspect:

$ git ls-files --error-unmatch path/to/file
path/to/file

Replace path/to/file with a path relative to the repository root. Keeping the path explicit avoids accidentally annotating a similarly named file in another directory.

2. Read the normal annotation

Run the command with the file after --. The separator makes it clear that the remaining argument is a path, even if it resembles a revision:

$ git annotate -- path/to/file

Each output line holds four things:

  • Object name. A shortened commit hash.
  • Author and date. Who last changed the line, and when.
  • Line number. Counted from 1.
  • Line text. The current content.

A small fixture might look like this:

d9f341eb  (     Guide  2026-09-24 00:59:05 +0100  1)first line
3db02276  (     Guide  2026-09-24 00:59:05 +0100  2)changed second line
d9f341eb  (     Guide  2026-09-24 00:59:05 +0100  3)third line

Your object names, author fields and dates will differ. The useful result is the association: line 2 came from commit 3db02276, while the other two lines came from d9f341eb.

Checkpoint

Copy one object name from the report and inspect it.

$ git show --stat --oneline 3db02276
3db02276 Change second line
 notes.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

The abbreviated name is normally enough for Git to resolve the commit. If it is ambiguous, use the full name from a porcelain report or ask Git to show the commit history.

3. Restrict the report to the lines you need

Use -L start,end when a whole-file report is distracting or expensive:

$ git annotate -L 40,65 -- path/to/file

Line numbers start at 1. You can omit one side, so -L 40 covers line 40 to the end and -L ,65 covers the start through line 65. You may pass multiple -L ranges, including overlapping ranges.

For source files, a function-based range can be easier to maintain than fixed line numbers:

$ git annotate -L :function_name -- path/to/file

The function name is interpreted using the same hunk-header rules that Git uses for diffs. If it finds no matching function, treat that as a range-selection problem and try a numbered range. Not every language has equally useful function detection.

4. Start from a revision when current history misleads

The optional revision tells Git where to stop its backward walk:

$ git annotate RELEASE_TAG -- path/to/file

Use a real tag, branch or commit name in place of RELEASE_TAG. This asks which commits had introduced the lines by that revision, rather than by the current checkout. Verify the selected revision before drawing a conclusion:

$ git rev-parse --verify RELEASE_TAG^{commit}
0123456789abcdef0123456789abcdef01234567

Tip

A line can be attributed differently depending on the revision and path history. Annotation is evidence about Git's history walk, not proof that the named author wrote every character on the line.

5. Handle moves, copies and merges deliberately

Git can spend extra work looking for moved or copied text. Start with -M for moves within the same file, then add -C when code may have come from another file:

$ git annotate -M -C -- path/to/file

The default similarity thresholds are 20 alphanumeric characters for -M and 40 for -C. A lower explicit threshold can find smaller fragments, but it also increases the chance of a weak attribution. For a refactor review, compare the ordinary and move-aware reports instead of treating the second one as automatically correct.

When a merge is involved, --first-parent follows only the first parent of merge commits:

$ git annotate --first-parent -- path/to/file

This answers a narrower question: when did the line arrive on the integration branch? Without it, Git follows the history in which the line was originally introduced. State which question you are answering in a review note.

6. Use porcelain output for automation

Do not parse the aligned human format in a script. Use --porcelain:

$ git annotate --porcelain -L 2,2 -- path/to/file
3db0227666d02a5a4381a97d29c58b692d3dab79 2 2 1
author Guide
author-mail <[email protected]>
author-time 1790207945
author-tz +0100
committer Guide
committer-mail <[email protected]>
committer-time 1790207945
committer-tz +0100
summary Change second line
filename path/to/file
	changed second line

The exact metadata and commit vary. Porcelain output repeats commit metadata only when a commit is first referenced, so a parser must understand the format rather than assume one complete record per line. Use --line-porcelain when each line needs its own commit metadata. Both forms are intended for machine consumption.

7. Investigate common false leads

A boundary commit can appear with a caret or a shortened identifier, depending on the options and history being examined. -b shows a blank object name for boundary commits, while --root treats root commits as ordinary sources instead of boundaries:

$ git annotate -b -- path/to/file
$ git annotate --root -- path/to/file

These options change how the boundary is displayed or treated. They do not rewrite history. If the first commit is the answer you need, prefer --root and verify it with git show.

  • Audit trail. --show-stats appends counts such as blobs read, patches retrieved and commits examined. Keep it away from parsers that expect only annotation records.
  • Full names. -l requests full revision names.
  • Raw timestamps. -t requests raw timestamps. Use both when abbreviated identifiers or formatted dates are insufficient.

Done means

  • Right repository. You ran git annotate -- path/to/file in the intended repository.
  • One commit verified. You checked at least one reported commit with git show.
  • Range recorded. You used -L for a focused range and noted whether line numbers or function detection selected it.
  • History options justified. You used -M, -C or --first-parent only when that history question matched the investigation.
  • Automation is safe. Any script uses porcelain output, and appended statistics are kept out of its parser.