Skip to main content
RunBook Academy

Git, CI/CD & GitOpsIV · Commit Graph and HistoryCommit Graph and History

History traversal — log options that actually change the graph walk

Intermediate⏱ ~20 mingit

What you'll learn

  • Explain how `git log` walks the DAG by default and how each flag changes the walk or the rendering
  • Choose between `--topo-order` and `--date-order` for code review versus chronological output
  • Use `--reverse` to walk from the oldest commit in a range to the newest, which is the order needed for replay and changelog generation
  • Use `--follow` to track renames in the history of a single path
  • Combine `--all`, `--decorate`, `--graph`, and `--oneline` to render the full DAG with every ref
  • Filter by merge commits with `--merges` and `--no-merges` to separate trunk history from feature-branch history

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

git log is the most-used Git command, and most of its flags do not change what commits are returned - they change either the walk strategy or the rendering. This lesson collects the flags that actually matter for an infrastructure team: which change the graph walk (which commits are visited and in what order), and which only change the formatting (one line per commit, decorations, etc.). The distinction is what separates a useful log from a misleading one.

The default walk

Without flags, git log starts at HEAD and walks parent edges back to the root, emitting one commit per line. The walk is depth-first along the first-parent edge, with non-first-parent branches visited when their merge commit is reached. This is the default behaviour, and it produces output that looks linear but is in fact a first-parent-biased DAG walk.

git log --oneline
# 8a3f9d2 bump terraform module to v1.4.0
# 4d2c8e0 Merge branch 'feature/iam-rotation' into main
# 9f3c1d7 rotate iam keys
# 7a1b2c3 add rotation cron
# 6f4e5a6 initial commit

The middle two lines are the feature branch, reached because the walk visited the merge commit and then descended into the second parent. Without --first-parent, the default walk visits every reachable commit; with --first-parent, only the trunk chain is visited.

—graph, —oneline, —decorate, —all: the rendering four

These four flags do not change the walk’s reachability - they change what is shown about each commit and what starting points the walk considers.

  • --graph: renders the DAG edges as ASCII characters. Every commit is positioned according to its parents; branches and merges are visible as pipe-and-slash characters.
  • --oneline: renders each commit as a single line: abbreviated OID plus subject. Replaces the verbose default with a one-line format.
  • --decorate: annotates each commit with the refs that point at it (branches, tags, HEAD). Without it, refs are invisible.
  • --all: extends the walk to include every ref’s tip, not just HEAD. Without it, only the current branch is enumerated.
git log --graph --oneline --decorate --all
# * 8a3f9d2 (HEAD -> main, tag: v1.4.0) bump terraform module to v1.4.0
# *   4d2c8e0 (origin/main) Merge branch 'feature/iam-rotation' into main
# |\
# | * 9f3c1d7 (origin/feature/iam-rotation) rotate iam keys
# | * 7a1b2c3 add rotation cron
# |/
# * 6f4e5a6 initial commit

This four-flag combination is the canonical “show me the DAG” command. Every ref is visible (HEAD, branches, tags, remote tracking), every commit is on one line, and the parent edges are drawn.

—topo-order versus —date-order

The walk strategy determines the order in which commits are emitted. --topo-order is the default; it ensures parents always appear before children. --date-order orders by committer timestamp instead.

# Topological order: parents before children, regardless of date
git log --oneline --topo-order

# Date order: by committer timestamp, parents may appear after children
git log --oneline --date-order

The difference matters when a commit’s children have older committer timestamps than the parent - which happens after a rebase, a backport, or a squash merge from a long-lived branch. --topo-order keeps the DAG readable: a parent is always shown above its children. --date-order keeps the timeline readable: a commit from 2023 appears before a commit from 2024, even if the 2024 commit is a child.

For code review, --topo-order is almost always better: the reviewer reads parents before children, which matches the causal order. For audit trails and chronological event logs, --date-order is appropriate. The default is --topo-order.

—reverse: walk from root to tip

By default, git log walks from the starting commit toward the root (newest to oldest). --reverse flips the walk to oldest to newest. The DAG is the same; only the emission order changes.

# Default: newest first
git log --oneline
# 8a3f9d2 bump terraform module to v1.4.0
# 6f4e5a6 initial commit

# Reverse: oldest first
git log --oneline --reverse
# 6f4e5a6 initial commit
# 8a3f9d2 bump terraform module to v1.4.0

--reverse is the correct flag for changelog generation, replay scripts, and any other “process commits in chronological order” operation. Without it, the script will process the newest commit first and may apply changes in reverse order, producing a different result.

—follow: track renames

Git tracks renames by content similarity, not by file path. By default, git log -- <path> only shows commits that touched the path’s current name. After a rename, history appears to be cut off. --follow extends the walk back through renames, treating the path as a moving target.

# Default: stops at the rename
git log --oneline -- ansible/roles/webserver/tasks/main.yml

# With --follow: walks through the rename
git log --oneline --follow -- ansible/roles/webserver/tasks/main.yml

--follow is restricted: it works on a single path, and it follows renames only along the first-parent chain by default (this is an implementation detail of the rename-detection heuristic). For multi-path history, use git log --diff-filter=R --name-only to find the rename commit and reconstruct the history manually.

—merges and —no-merges: filter by commit type

--merges includes only merge commits; --no-merges excludes them. Combined with --first-parent, these flags separate the trunk’s commits into “ordinary commits” and “merge commits”.

# Every merge commit reachable from main
git log --oneline --merges main

# Every ordinary commit on main's trunk (first-parent only, no merges)
git log --oneline --first-parent --no-merges main

--first-parent --no-merges is the canonical “what work landed on the trunk, excluding the merge events themselves” view. It is the input to release notes that do not list merge commits explicitly and to deployment counters that count “work commits” rather than “trunk steps”.

flowchart LR
    M1["merge M1"] --> T1["trunk commit T1"]
    M1 --> F1["feature commit F1"]
    M2["merge M2"] --> T2["trunk commit T2"]
    M2 --> F2["feature commit F2"]
    T1 --> T2

In the diagram, git log --first-parent --no-merges walks the trunk chain (T1, T2) and skips both merge commits and feature commits. git log --merges walks both merge commits. git log main walks everything.

Combining flags

Flags compose. The combination git log --graph --oneline --decorate --all --topo-order --reverse shows every commit, in topological order reversed to chronological, drawn as a graph with decorations. This is rarely useful interactively but is the baseline for “process every commit in chronological order with its branch context” scripts.

# All-in-one: every commit, every ref, decorated, graphed
git log --graph --oneline --decorate --all

# Replay every commit in chronological order, no graph noise
git log --oneline --reverse --no-merges main

# Find every rename commit reachable from any ref
git log --diff-filter=R --name-status --oneline --all

Production discipline

  1. Choose flags by question, not by habit. “What is on the trunk?” is --first-parent. “What is on either side of the divergence?” is A...B. “What is on this branch but not on that?” is A..B. “Show me the graph” is --graph --oneline --decorate --all.
  2. Use --reverse for replay and changelog generation. A script that processes commits without --reverse may apply changes in the wrong order.
  3. Use --follow for single-file history across renames. It is the only flag that does rename detection within the walk.
  4. Always set --merges or --no-merges deliberately. A script that does not specify either captures both kinds and may produce surprising output.

Cross-course references

  • GitOps with Argo CD - Part VII (Diffing) uses git log --oneline --reverse to enumerate the manifests that must be replayed in order when reconciling a drifted cluster.
  • CI/CD Pipeline Patterns - Part II (TriggerLogic) uses --first-parent --no-merges to identify the commits that should trigger a build, excluding merge commits that have no new content of their own.
  • Ansible for Production Sysadmins - Part XXXIX (HistoryAudit) uses --follow to reconstruct the history of a renamed role across organisational restructuring.

Quiz

Knowledge check · 4 questions

  1. Q1. Which combination of `git log` flags renders the full DAG with every ref, draws parent edges as ASCII, and shows each commit on a single line?

  2. Q2. `--reverse` changes which commits `git log` walks, not just the order in which they are emitted.

  3. Q3. Which `git log` flag tracks a file through renames by extending the walk to treat the path as a moving target, and what is its main limitation?

  4. Q4. Construct the `git log` invocation that produces the commit list for a chronological replay of all trunk work since the last release, excluding merge commits.

    An infrastructure team needs a script that replays every Terraform apply on the trunk in chronological order, skipping the merge commits themselves (which carry no new apply content). The trunk is `main`, the last release tag is `v1.3.0`, and the script must enumerate OIDs only.

Passing score: 75%. Answers are checked in this browser.