git
gitpython
git log
version control
software development

git log --follow, the gitpython way

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

If you want the equivalent of git log --follow in Python, the simplest practical answer is that GitPython does not expose a high-level rename-following history API that fully replaces the CLI behavior. The reliable approach is usually to call the underlying Git command through GitPython's command wrapper and let Git itself handle rename tracking.

Why --follow Is Special

git log --follow -- path/to/file is not the same as a plain history walk filtered by path. Git uses rename detection heuristics to keep tracing history when the file name changes.

A normal GitPython history call like this:

python
1from git import Repo
2
3repo = Repo(".")
4for commit in repo.iter_commits(paths="src/app.py"):
5    print(commit.hexsha, commit.summary)

can show commits for the current path, but it does not guarantee the same rename-follow behavior as git log --follow. That is the gap most people run into.

Use the Wrapped Git Command Directly

GitPython gives you access to the raw Git executable through repo.git. That means you can run the exact log command you would trust in the shell.

python
1from git import Repo
2
3repo = Repo(".")
4output = repo.git.log("--follow", "--", "src/app.py")
5print(output)

This is usually the right answer because it preserves Git's own path-follow and rename-detection logic instead of trying to reconstruct it manually in Python.

If you want a custom output format for parsing:

python
1from git import Repo
2
3repo = Repo(".")
4output = repo.git.log(
5    "--follow",
6    "--format=%H%x09%an%x09%ad%x09%s",
7    "--",
8    "src/app.py"
9)
10
11for line in output.splitlines():
12    sha, author, date, subject = line.split("\t", 3)
13    print(sha, author, subject)

That gives you structured data without having to scrape the default human-readable log format.

When iter_commits Is Still Fine

If the file was never renamed, or if rename following is not important, iter_commits(paths=...) is still a perfectly good solution.

python
1from git import Repo
2
3repo = Repo(".")
4commits = list(repo.iter_commits(paths="README.md", max_count=5))
5
6for commit in commits:
7    print(commit.hexsha, commit.summary)

This is cleaner when you only need straightforward path filtering. The problem appears only when you need the semantic behavior of --follow.

Avoid Reimplementing Rename Tracking Yourself

It is technically possible to walk commits manually, inspect diffs, detect renames, and keep updating the tracked path in Python. In practice, that is almost always the wrong level of abstraction unless you are building a specialized Git analysis tool.

Git already has nontrivial rename heuristics. Reimplementing that logic yourself usually means more code, more edge cases, and behavior that still does not quite match the CLI command people already trust.

If the requirement is "do what git log --follow does," the cleanest solution is to call that command through GitPython.

Parse Output Deliberately

When you wrap raw Git commands, think about output format up front. Using --format makes parsing predictable and avoids brittle string handling.

For example, a small helper can return structured records:

python
1from git import Repo
2
3
4def log_follow(repo_path, file_path):
5    repo = Repo(repo_path)
6    output = repo.git.log(
7        "--follow",
8        "--format=%H|%an|%s",
9        "--",
10        file_path,
11    )
12    return [line.split("|", 2) for line in output.splitlines() if line]
13
14for entry in log_follow(".", "src/app.py"):
15    print(entry)

This keeps the Python side small and lets Git stay responsible for history semantics.

Common Pitfalls

  • Expecting iter_commits(paths=...) to behave exactly like git log --follow leads to confusion because rename following is the missing piece.
  • Parsing the default git log text output is brittle. Use --format so each record is machine-friendly.
  • Trying to reimplement rename tracking in Python usually creates edge cases and still may not match Git's heuristics.
  • Forgetting the -- separator before the file path can cause Git to interpret the path as another revision argument.
  • Assuming --follow works for arbitrary combinations of paths and complex history filters can still lead to surprises because the Git CLI has its own limitations and heuristics.

Summary

  • GitPython does not provide a perfect high-level replacement for git log --follow.
  • The practical solution is to call repo.git.log("--follow", "--", path) and let Git do the heavy lifting.
  • Use iter_commits only when plain path filtering is enough and rename history does not matter.
  • Prefer --format when you need to parse the result programmatically.
  • If the goal is CLI-equivalent rename tracking, invoking the underlying Git command is the cleanest and most reliable approach.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.