Git, CI/CD & GitOpsVII · Repository InspectionInspection
git log formatting — pretty formats, custom output, and machine-readable contracts
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
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 (thecore.abbrevprefix, 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=short—YYYY-MM-DD. The default forgit logand 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-strict—YYYY-MM-DDTHH:MM:SS+HH:MMwith theTseparator. RFC 3339 compliant; the right format for piping intojqor a JSON-aware tool.--date=relative—2 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=AdamatchesAda Lovelace).--since=<date>and--until=<date>— match commits with an author date in the range. Both accept2 weeks ago,2026-08-01, and2026-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-matchto require multiple--grepflags 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.-Sis 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.-Gis 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
- Use
--pretty=format:"%h %an %ad %s"with--date=shortfor human logs. The compact four-field format fits in a CI log without truncation. - Use
--pretty=tformat:"%H"for scripts that take a list of commits. The contract is one SHA per line, no other fields mixed in. - 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. - Use
-Sand-Gfor code archaeology. Both flags turn the commit log into a query language over the history of the repository. - 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
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?
Q2. `-S "image_tag"` and `-G "image_tag"` are interchangeable: both find the commits that mention the string `image_tag` in their diff.
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.
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.