Skip to main content
RunBook Academy

Git, CI/CD & GitOpsVII · Repository InspectionInspection

git log formatting — pretty formats, custom output, and machine-readable contracts

Intermediate⏱ ~20 mingit

What you'll learn

  • Use --pretty=format with placeholders (%h, %H, %an, %ae, %ad, %s, %b) to build custom log lines
  • Choose --date= formats (short, iso, iso-strict, relative, local) for the right audience
  • Distinguish machine-readable contracts (--pretty=tformat:, --format=%H) from human-readable output
  • Use --author, --since, --until, --grep, -S, and -G as scope filters that compose with format flags
  • Identify why parsing the default subject line is unsafe and what to do instead

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.

The default git log output is fine for a human reading a terminal and wrong for anything else. The subject line is free-form text and almost certainly contains spaces, quotes, newlines, and emoji. A shell loop that does for line in $(git log); do ... will miscount every commit whose message wraps. The fix is a format string: a template that names each field, separates fields with a guaranteed delimiter, and produces output that is safe to parse.

This lesson is the production reference for git log --pretty=format, --date=, and the machine-readable contracts that CI scripts consume.

The format placeholders

Each placeholder in a --pretty=format template resolves to one field of the commit object. The most-used placeholders are:

  • %H — full commit hash (64 hex on SHA-256, 40 hex on SHA-1).
  • %h — abbreviated commit hash (the core.abbrev prefix, default 7 characters).
  • %an — author name.
  • %ae — author email.
  • %ad — author date (formatted by --date=).
  • %cn — committer name.
  • %ce — committer email.
  • %cd — committer date.
  • %s — subject (first line of the message).
  • %b — body (everything after the first blank line).
  • %D — ref names (the “decorate” output, e.g. HEAD -> main, origin/main).
  • %C(<color>) — colour directive (reset, red, green, yellow, blue, magenta, cyan, white).
  • %n — newline.
  • %x00 — literal NUL byte (the safe delimiter for parsing).

The full placeholder list is in the git log and git pretty-options documentation. The above ten cover ninety percent of production use.

git log --pretty=format:"%h %an %ad %s" --date=short -n 5
# 8a3f9d2 Ada Lovelace 2026-08-18 fix(terraform): pin router module to v3.2.7
# 7d2e1f4 Ada Lovelace 2026-08-17 feat: add edge MTU to provider config
# 5b6f3a2 Bertrand Russell 2026-08-17 ci: add policy bundle to plan job
# 1e0d9c4 Augusta King 2026-08-15 feat: support per-region IAM partition
# 9c4a8b1 Ada Lovelace 2026-08-14 chore: cut release v3.2.6

This is the canonical human-readable format for a CI log and a post-mortem. The hash is short, the author is named, the date is unambiguous (YYYY-MM-DD), and the subject is on the same line.

The five date formats

The author and committer dates are stored as Unix epoch plus a timezone offset. The --date=<format> flag selects how the value is rendered. The five formats that matter for production:

  • --date=shortYYYY-MM-DD. The default for git log and the right format for human-facing output. Loses the time and timezone.
  • --date=iso (or --date=iso8601) — YYYY-MM-DD HH:MM:SS ±HHMM. The right format for an audit log; the timezone is preserved.
  • --date=iso-strictYYYY-MM-DDTHH:MM:SS+HH:MM with the T separator. RFC 3339 compliant; the right format for piping into jq or a JSON-aware tool.
  • --date=relative2 hours ago, 3 days ago. The right format for a chat notification; the worst format for an audit log because the value is not parseable.
  • --date=local — local timezone with the system’s default format. The default if --date= is not specified. Localised; do not parse.
# Audit log: full ISO timestamp for the auditor
git log --pretty=format:"%h %ad %s" --date=iso -n 5
# 8a3f9d2 2026-08-18 14:23:09 +0000 fix(terraform): pin router module to v3.2.7
# 7d2e1f4 2026-08-17 09:12:44 +0000 feat: add edge MTU to provider config
# 5b6f3a2 2026-08-17 08:47:11 +0000 ci: add policy bundle to plan job
# 1e0d9c4 2026-08-15 16:30:02 +0000 feat: support per-region IAM partition
# 9c4a8b1 2026-08-14 11:05:33 +0000 chore: cut release v3.2.6

The same %ad placeholder produces a different value depending on the --date= flag. The format string is the template; the date flag is the rendering.

Machine-readable formats

The default git log is unsafe for scripts because the subject line can contain any byte sequence. The two formats that are safe to parse:

--pretty=format:"%H" outputs one full SHA per line. This is the canonical machine-readable commit list. Scripts that need a list of commits (for a CI baseline, for a release manifest, for a script that needs to enumerate every commit on a branch) should consume this format and nothing else.

--pretty=tformat:"..." is the same as --pretty=format:"..." but suppresses the trailing newline that format adds. The trailing semantics matter when the format is being concatenated into a larger string (a release manifest, a JSON file, a CI variable). tformat is the format --oneline uses internally.

# Safe: one SHA per line, no extra delimiters
git log --pretty=format:"%H" -n 5 > /tmp/commits.txt

# Safe: SHA + author + ISO date, separated by a literal NUL byte
git log --pretty=format:"%H%x00%ae%x00%ad" --date=iso-strict -n 5
# 8a3f9d2c1b4e7f0a9d6c5b2e8f1a4d7c0b3e6f9a\x00ada@example.com\x002026-08-18T14:23:09+00:00
# 7d2e1f4...

The NUL-delimited form (%x00) is the safest contract for a script: every byte between the delimiters is guaranteed to be a field value, and no field value can contain a %x00 literal because Git does not allow NUL bytes in commit messages.

flowchart LR
    H["Human reads"] --> H1["git log - default verbose"]
    H --> H2["git log --pretty=format with %h %an %ad %s"]
    S["Script reads"] --> S1["git log --pretty=tformat:%H"]
    S --> S2["git log --pretty=format with %x00 delimiters"]
    S --> S3["git log --pretty=oneline"]
    H1 --> Wrap["wrap in CI log"]
    H2 --> Wrap
    S1 --> Pipe["pipe to xargs / jq / awk"]
    S2 --> Pipe
    S3 --> Pipe

Scope filters that compose with format

The format flags from the previous lesson are only one half of the production query. The other half is the scope filters that decide which commits appear. The most useful scope flags:

  • --author=<pattern> — match the author name or email against a regex (--author=Ada matches Ada Lovelace).
  • --since=<date> and --until=<date> — match commits with an author date in the range. Both accept 2 weeks ago, 2026-08-01, and 2026-08-01T12:00:00.
  • --grep=<pattern> — match the subject line against a regex. Add --regexp-ignore-case (or -i) for case-insensitive matching. Add --all-match to require multiple --grep flags to all match.
  • -S<string> — pick the commits that change the count of a string in the diff. The flag is the change of occurrences, not the presence in the file. -S is the right tool for “when was this constant introduced or removed?”.
  • -G<regex> — pick the commits whose diff matches a regex. The flag is the presence of the regex in the diff, not the count. -G is the right tool for “when did this code stop using this function?”.
  • --first-parent — follow only the first parent of each merge.
# Who changed terraform/aws/ in the last 30 days?
git log --author=".*" --since="30 days ago" --pretty=format:"%h %an %ad %s" --date=short -- terraform/aws/

# When was the variable `image_tag` introduced?
git log -S "image_tag" --pretty=format:"%h %an %ad %s" --date=short

# When was the call to `apply_immediately` removed?
git log -G "apply_immediately" --pretty=format:"%h %an %ad %s" --date=short

The -S and -G flags are the code-archaeology primitives of Git. They are why git log is more than a list of commits: it is a query language over the history of the repository.

Production discipline

  1. Use --pretty=format:"%h %an %ad %s" with --date=short for human logs. The compact four-field format fits in a CI log without truncation.
  2. Use --pretty=tformat:"%H" for scripts that take a list of commits. The contract is one SHA per line, no other fields mixed in.
  3. Use NUL-delimited format (%x00) when the script needs more than the hash. The NUL byte cannot appear in a commit message, so the delimiter is guaranteed safe.
  4. Use -S and -G for code archaeology. Both flags turn the commit log into a query language over the history of the repository.
  5. Never parse the default output. The subject line is free-form text; parsing it is a bug.

Cross-course references

  • Linux for Production Sysadmins - Part XVIII (LogAggregation) discusses the same discipline: a structured log format on the producer side, a parser on the consumer side, and a delimiter that is provably absent from the field values.
  • Ansible for Production Sysadmins - Part XXXVII (RepoArch) uses git log --pretty=format:"%h %s" --since="1 week ago" as the standard “what changed in this repo recently” query.
  • Terraform for Production Sysadmins - Part IX (State) uses git log -S "module.router" as the standard query for “when did this module version change?”.

Quiz

Knowledge check · 4 questions

  1. Q1. A CI script needs an enumerated list of commit SHAs from the last 24 hours on the `main` branch to feed into a release manifest. Which command is the safest contract?

  2. Q2. `-S "image_tag"` and `-G "image_tag"` are interchangeable: both find the commits that mention the string `image_tag` in their diff.

  3. Q3. Name two format placeholders for the commit hash, the author name, and the author date, and the `--date=` flag that produces an ISO 8601 timestamp.

  4. Q4. An incident investigation needs to know which commit first introduced a Terraform variable `image_tag = "latest"` and which commit replaced it with a pinned digest. Compose the right `git log` query pair and explain why

    The security team has flagged that the production Terraform still uses `:latest` for two container images. The auditor wants to know: when was `:latest` first introduced, who introduced it, and when was it first replaced with a pinned digest (`@sha256:...`). The team has a single `main` branch, no squashing, and roughly 4000 commits. The auditor runs a naïve `git log --grep=image_tag` and gets noise — every commit that mentions the variable in the subject matches.

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