Skip to main content
RunBook Academy

Git, CI/CD & GitOpsVII · Repository InspectionInspection

git show and the commit object — inspecting one commit in full

Intermediate⏱ ~17 mingit

What you'll learn

  • Explain what git show <rev> displays: the commit object header and the diff against the parent
  • Use git show <rev>:<path> to extract a single file at a single commit without checking it out
  • Use git show <rev> -- <path> to view the diff of a single path at a single commit
  • Distinguish git show from git log -p and git diff for the same commit
  • Recognise show as the right command for post-mortem inspection of a single commit

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 answers the question “what happened?”. git show answers the question “what does this one commit look like?”. The two commands overlap with git diff, but show is the right tool for the single-commit case: it pairs the commit object metadata with the diff against the parent in one command, and the <rev>:<path> syntax it inherits from git rev-parse is the production primitive for extracting a single file from history without checking it out.

The post-mortem operation “show me what the Terraform plan looked like at the commit that introduced the incident”, the CI operation “extract the Dockerfile from the commit that was tagged as the release”, and the audit operation “show me the contents of the IAM policy at the commit hash recorded in the change ticket” are all the same command: git show <rev>:<path>.

What show displays

git show <rev> produces three sections in this order:

  1. The commit object header — the same output as git log -1 <rev>: full hash, author, committer, date, subject, and body.
  2. A blank line, then the commit message body if it was not already printed.
  3. The diff against the parent — the same output as git diff <rev>~1..<rev> or git log -p -1 <rev>.
git show 8a3f9d2
# commit 8a3f9d2c1b4e7f0a9d6c5b2e8f1a4d7c0b3e6f9a (HEAD -> main, origin/main)
# Author: Ada Lovelace <ada@example.com>
# Date:   Mon Aug 18 14:23:09 2026 +0000
#
#     fix(terraform): pin router module to v3.2.7
#
#     The 3.2.6 release introduced a regression in the
#     us-east-1 default route that triggered a fan-out
#     of duplicate routing entries.
#
# diff --git a/modules/router/versions.tf b/modules/router/versions.tf
# index 1b2c3d4..5e6f7a8 100644
# --- a/modules/router/versions.tf
# +++ b/modules/router/versions.tf
# @@ -1,5 +1,5 @@
#  module "router" {
#    source  = "git::ssh://git@github.com/example/router.git"
# -  version = "3.2.6"
# +  version = "3.2.7"
#  }

The output is exactly what a post-mortem reader needs: the what (commit metadata) and the change (diff) in one scroll. The same information from git log would require two commands and a pipe; git show is the single command that does both.

The rev:path syntax

The most powerful form of git show is not the default. It is the &lt;rev&gt;:&lt;path&gt; syntax, which extracts a single file at a single commit and writes its contents to standard output:

git show 8a3f9d2:modules/router/versions.tf
# module "router" {
#   source  = "git::ssh://git@github.com/example/router.git"
#   version = "3.2.7"
# }

The output is the raw file contents, not the diff. This is the form that production scripts consume: a CI pipeline that needs to read the Dockerfile at the tagged release uses the command to extract the bytes, and an incident post-mortem that needs the IAM policy at the offending commit uses the command to retrieve the policy without touching the working tree.

The &lt;rev&gt; can be any revision expression: a full hash, an abbreviated hash, a branch name, a tag name, a relative reference like HEAD~3, or a reflog entry like HEAD@{2}. The &lt;path&gt; is the path as it exists in the commit object, not the path in the working tree. The two are usually identical, but a rename can make them differ.

# Extract a file from a tagged release
git show v3.2.7:deploy/prod/main.tf > /tmp/main.tf

# Extract a file from the most recent commit that touched it
git show HEAD~3:terraform/backend.tf

# Extract a file from a specific commit in a different branch
git show feature/edge-mtu:terraform/main.tf

The &lt;rev&gt;:&lt;path&gt; syntax is the production primitive for “give me the version of this file at this commit”. It is the difference between checking out a commit (which moves the working tree and resets the index) and reading a file from history (which leaves the working tree untouched).

The — separator for path-restricted diffs

The &lt;rev&gt; -- &lt;path&gt; form is different: it shows the diff of the path at the named commit, not the file contents. The double dash separates the revision from the path arguments; any path after the dash is a path filter, not a revision.

# Diff of one file at one commit
git show 8a3f9d2 -- modules/router/versions.tf
# commit 8a3f9d2...
# Author: Ada Lovelace <ada@example.com>
# Date:   Mon Aug 18 14:23:09 2026 +0000
#
#     fix(terraform): pin router module to v3.2.7
#
# diff --git a/modules/router/versions.tf b/modules/router/versions.tf
# ...

The two forms are the answer to two different questions:

  • git show &lt;rev&gt;:&lt;path&gt; — “what are the contents of this file at this commit?”
  • git show &lt;rev&gt; -- &lt;path&gt; — “what was the change to this file at this commit?”

The first is the right primitive for extracting a file; the second is the right primitive for inspecting a single change in a commit that touched multiple paths.

flowchart LR
    Q1["What changed in this commit?"] --> A1["git show &lt;rev&gt;"]
    Q2["What were the contents of this file at this commit?"] --> A2["git show &lt;rev&gt;:&lt;path&gt;"]
    Q3["What was the change to this file at this commit?"] --> A3["git show &lt;rev&gt; -- &lt;path&gt;"]
    Q4["What is the diff between two commits?"] --> A4["git diff &lt;rev1&gt;..&lt;rev2&gt;"]
    Q1 --> B1["commit metadata + diff vs parent"]
    A2 --> B2["raw file bytes to stdout"]
    A3 --> B3["commit metadata + diff for one path"]
    A4 --> B4["diff only, no commit metadata"]

show versus log -p versus diff

Three commands all produce a diff. The differences are small but matter for the right production use:

  • git show &lt;rev&gt; — one commit’s metadata plus the diff against the parent. The right command for “what is in this single commit?”.
  • git log -p -n 1 &lt;rev&gt; — same output as git show &lt;rev&gt;, but with the log machinery that supports --pretty and the traversal flags. The right command when the format string needs to be customised.
  • git diff &lt;rev&gt;~1..&lt;rev&gt; — the diff only, no metadata. The right command when the output is being piped to a patch applier or a code-review tool.

The three commands are interchangeable for the basic case. The choice is “do I want the metadata or just the diff?”.

Production discipline

  1. Use git show &lt;rev&gt;:&lt;path&gt; to read a file from history. The command is read-only, leaves the working tree untouched, and produces raw file bytes to stdout. It is the right primitive for CI, post-mortem, and audit queries.
  2. Use git show &lt;rev&gt; -- &lt;path&gt; to inspect a single path’s change. The form is the diff-restricted-to-one-path version of git show &lt;rev&gt;, and is the right command when the commit touched many paths but the question is about one of them.
  3. Prefer git show over git log -p -n 1 for inspecting a single commit. The intent is clearer; the output is the same.
  4. Never use git checkout &lt;rev&gt; -- &lt;path&gt; to read a file. The command updates the index, which can silently change the next commit. Use git show instead.

Cross-course references

  • Linux for Production Sysadmins - Part IX (Filesystem) distinguishes the same way between “read the file” and “check out the file”, and covers the discipline of leaving the working tree untouched when only a read is needed.
  • Ansible for Production Sysadmins - Part XXXVII (RepoArch) uses git show &lt;tag&gt;:playbooks/site.yml as the canonical “what was in this release?” query.
  • Terraform for Production Sysadmins - Part IX (State) uses git show &lt;rev&gt;:deploy/prod/main.tf as the canonical “what was the production Terraform at this commit?” query.

Quiz

Knowledge check · 4 questions

  1. Q1. A CI pipeline needs to extract the `Dockerfile` at the release tag `v3.2.7` to inspect it without modifying the working tree. Which command is the safest?

  2. Q2. `git show &lt;rev&gt; -- &lt;path&gt;` and `git show &lt;rev&gt;:&lt;path&gt;` produce the same output: both extract the raw bytes of `&lt;path&gt;` at `&lt;rev&gt;` to standard output.

  3. Q3. Name the two Git commands that read a file from history without modifying the working tree or the index, and explain why `git checkout &lt;rev&gt; -- &lt;path&gt;` is not the same.

  4. Q4. Diagnose a contaminated index by identifying the wrong command among the four that an engineer ran, and recommend the safe primitive for the same query.

    An engineer is preparing a release commit. They want to inspect the Ansible playbook that was tagged as `v3.2.6` to verify the contents before cutting `v3.2.7`. They run `git checkout v3.2.6 -- playbooks/site.yml` and read the file. They then run `git status` and see `M playbooks/site.yml`. They realise the index now holds the contents of `v3.2.6`, not the current working tree. They run `git restore --staged playbooks/site.yml` to undo the contamination, then re-run the read with `git show v3.2.6:playbooks/site.yml`. The contamination is reversed.

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