Git Show — The Commit That Looked Innocent but Contained a Backdoor
A security audit scanned commit messages but didn't inspect the actual diff.
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
- ✓Git 2.x installed, basic familiarity with the command line, understanding of Git concepts: commits, branches, tags, and the staging area. No prior experience with git show required.
git show shows a commit's full diff. git show --stat shows only the file summary. git show shows the file contents at that commit. git show shows the tag's message and object.
Git show is like a magnifying glass for any single point in your repository's history. A commit message says 'Fix login bug' — git show reveals what actually changed. A tag says 'v1.0.0' — git show reveals what commit it points to and who signed it. It's the command you use when someone says 'check this commit' and you need to verify what's really inside.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
Commit messages can lie — not intentionally, but they can be misleading, incomplete, or vague. git show is the tool that looks past the message and reveals the actual changes. It's essential for code review, security audits, and debugging. A commit message might say 'Fix formatting' while git show reveals a logic change buried in the diff. This guide covers how to use git show to inspect every object type in Git, and the security audit where a commit message hid a malicious backdoor.
What Is Git Show and When to Use It
Git show is a versatile command that displays information about Git objects: commits, tags, trees, and blobs. Unlike git log, which lists multiple commits, git show focuses on a single object and provides a detailed view. For beginners, it's the go-to command to inspect a specific commit: what changed, who made it, and why. In production, you'll use it to quickly verify a hotfix, review a tag before a release, or check if a cherry-pick applied correctly. It's also invaluable for debugging when you need to see the exact state of a file at a given commit. The command's output adapts to the object type: for commits, it shows the diff; for tags, it shows the tag message and the commit it points to; for trees, it lists the directory contents; for blobs, it shows the raw file content. Mastering git show saves you from scrolling through endless logs or opening GUI tools.
Inspecting Commits: The Default Behavior
Running git show without arguments defaults to showing the HEAD commit. But you can pass any commit reference: a SHA, branch name, tag, or relative reference like HEAD~2. The output includes the commit metadata (author, date, message) and a unified diff of all changes. For large commits, this can be overwhelming. Use the --stat flag to see a summary of changed files and line counts, or -p to control patch output. In production, you often need to inspect a specific commit from a CI pipeline or a code review. For example, after a failed deployment, you might run git show <deploy-commit> to see exactly what changed. The diff shows additions in green and deletions in red, with context lines. If the commit is a merge commit, git shows the combined diff against both parents by default, which can be confusing. Use -m to split merge diffs into separate patches against each parent.
Viewing Tags: Annotated vs Lightweight
Tags in Git come in two flavors: lightweight (just a pointer) and annotated (stored as a full object with message, author, and date). git show behaves differently for each. For an annotated tag, it shows the tag message, the tagger info, and then the commit details (as if you ran git show on the commit). For a lightweight tag, it simply shows the commit it points to. This distinction matters in production: annotated tags are preferred for releases because they carry metadata like release notes and GPG signatures. To see only the tag object without the commit, use git show <tag> --no-patch. When verifying a release, run git show v1.2.3 and check the tag message matches the changelog. If the tag is signed, git show will also display the signature status if you have the public key.
Inspecting Files and Directories with Tree and Blob References
Git show isn't limited to commits and tags. You can pass a tree or blob SHA to inspect a directory or file at a specific point in history. This is extremely useful when you need to see the contents of a file as it existed in a previous commit without switching branches. For example, git show <commit>:<path> shows the file content at that commit. To list a directory, use git show <commit>:<dir>/ which shows the tree object. In production, you might want to verify a configuration file from a previous release: git show v1.0.0:config/prod.yaml. This command is read-only and doesn't affect your working directory. You can also use it to recover a deleted file: git show <commit>^:<file> > restored_file.txt. The caret (^) refers to the parent commit, so you get the version before deletion.
Formatting Output for Scripts and Automation
The default output of git show is human-readable but not machine-friendly. For scripting, use the --format option to customize the commit metadata. Common placeholders: %H for full commit hash, %h for abbreviated, %an for author name, %s for subject, %b for body, %ai for author date (ISO 8601). Combine with --no-patch to suppress the diff. For example, git show --format="%h %an %s" --no-patch HEAD outputs a one-line summary. In production CI/CD, you might extract the commit message to enforce policies: git show --format="%s" --no-patch HEAD | grep -E "^(feat|fix|chore):" . You can also output JSON-like structures using %n for newlines. For tags, use --format with %(contents) to get the tag message. This is essential for automated release notes generation.
Comparing git show with git log, git diff, and git cat-file
Newcomers often confuse git show with similar commands. git log lists multiple commits in reverse chronological order; git show shows one commit in detail. git diff compares two states (working directory vs index, or two commits) without commit metadata; git show includes the diff plus metadata. git cat-file -p <object> shows raw object content without any formatting; git show adds headers and diff formatting. In production, choose git show when you need the full picture of a single commit. Use git log for history traversal, git diff for comparing arbitrary states, and git cat-file for low-level object inspection. For example, to see what changed between two tags, use git diff v1.0.0 v2.0.0. To see the commit that introduced a bug, use git log --oneline -S"buggyFunction" and then git show on that commit.
Using git show with Reflog to Recover Lost Commits
The reflog records every movement of HEAD, including commits that are no longer reachable from any branch. git show can inspect those lost commits. After a destructive operation like git reset --hard or an accidental branch deletion, run git reflog to find the SHA of the lost commit, then git show <SHA> to verify its contents. This is a lifesaver in production when someone force-pushes or resets a shared branch. For example, if a developer accidentally resets main to an older commit, you can recover the lost commits from the reflog of the remote? No, reflog is local. But if you have access to the machine where the reset happened, you can recover. To avoid this, enforce protected branches and use revert instead of reset. Still, git show with reflog is your last line of defense.
Practical Workflow: Inspecting a Release Candidate
Let's walk through a real production scenario: you're about to deploy a release candidate tagged rc-2.3.0. First, verify the tag is annotated: git show rc-2.3.0. Check the tag message matches the changelog. Then inspect the commit it points to: git show rc-2.3.0^{commit} (or just git show rc-2.3.0 as it defaults to the commit). Look for any suspicious changes. Use --stat to get a file summary. If the diff is large, focus on critical files: git show rc-2.3.0 -- src/config/ src/auth/. Finally, verify the exact file content of the configuration: git show rc-2.3.0:config/prod.yaml. If everything looks good, proceed with deployment. This workflow ensures you never deploy a broken commit. In practice, automate these checks in CI with scripts that parse git show output.
Common Pitfalls and How to Avoid Them
Even experienced developers make mistakes with git show. One common pitfall: assuming git show on a merge commit shows a combined diff that is easy to read. In reality, combined diffs omit lines that didn't change relative to both parents, which can hide important context. Use git show -m to split into separate diffs. Another pitfall: forgetting that git show on a lightweight tag shows the commit, not the tag object. If you need to verify the tag itself, use git tag -l <pattern> or git cat-file -p <tag>. Also, git show on a blob (file) shows the raw content without any diff markers, which can be confusing if you expected a diff. Finally, performance: git show on a large commit with many files can be slow. Use --no-patch if you only need metadata. In production, avoid running git show on huge commits in scripts that need to be fast.
Integrating git show into Your Daily Workflow
Make git show a habit. Before committing, run git show --stat to review your staged changes? Actually, that's git diff --cached --stat. But after commit, use git show HEAD to double-check. When reviewing a colleague's commit, use git show <sha> to see the full context. In code reviews, paste the output of git show --stat to give a quick overview. In your IDE, many Git integrations use git show under the hood to display commit details. Understanding the raw command helps you debug when the GUI fails. For daily use, create aliases: git config --global alias.s 'show --stat' and git config --global alias.sf 'show --format="%h %an %s" --no-patch'. These save keystrokes. In production, always use the full command in scripts for clarity.
Advanced: Customizing Output with Pretty Formats and Colors
Git show supports --pretty=format:... for highly customized output. You can include colors using %C(...) placeholders. For example: git show --pretty=format:"%C(yellow)%h%Creset %C(blue)%an%Creset %s" --no-patch HEAD. This is useful for creating human-friendly summaries in scripts. You can also define custom formats in your git config: git config --global pretty.myshow "format:%C(auto)%h %ad %an %s" and then use git show --pretty=myshow. In production, use consistent formats for logging. For example, in a CI pipeline, you might output JSON: git show --pretty=format:"{\"commit\":\"%H\",\"author\":\"%an\",\"subject\":\"%s\"}" --no-patch HEAD. This makes it easy to parse with jq or other tools. Remember that --pretty overrides the default output, so combine with --no-patch to suppress the diff.
Troubleshooting: When git show Doesn't Show What You Expect
Sometimes git show behaves unexpectedly. If you run git show and see nothing, the object might be missing or the reference is ambiguous. Use git rev-parse <ref> to resolve the reference to a SHA. If the output is truncated, increase the pager buffer: git --no-pager show <ref> or set core.pager=cat. If you see "fatal: bad revision", the reference doesn't exist. For a file path, ensure the colon syntax is correct: <ref>:<path> with no spaces. If you're in a detached HEAD state, git show HEAD shows the current commit, but you might expect a branch. Use git branch -a to list all branches. For binary files, git show shows a diff with "Binary files differ" instead of content. Use git show --binary to get a base64 representation. In production, these edge cases can cause scripts to fail silently. Always validate the output.
The Commit Message Said 'Fix Linting' — The Diff Added a Backdoor
/api/internal/access that authenticated with a hardcoded token. 4. The diff was 15 lines — 10 lines of genuine linting fixes (whitespace, imports) and 5 lines of hidden backdoor code in the same file. 5. The backdoor was disguised by placing it near the linting changes, making it look like part of the fix. 6. The commit was merged and deployed. 7. Three months later, a security audit using git log -S 'hardcoded' --all found the backdoor.git show -S 'internal|backdoor|bypass' --all. 3. Changed code review policy: every commit message that says 'linting', 'formatting', 'style', or 'cleanup' must be expanded in the diff view before approval. 4. Added CI check: git diff --diff-filter=A --name-only HEAD^ lists new files in every PR; unexpected new endpoints or routes trigger a flag. 5. Implemented signed commits with GPG (article 200) to verify the contributor's identity. 6. Ran a team-wide training on code review best practices.- Never trust a commit message at face value.
- 'Fix linting', 'Refactor', 'Cleanup' are common disguises for hidden changes.
- Always expand and review the full diff.
- Use
git showto inspect the actual changes behind every commit message. - Security-sensitive changes should be in separate, clearly-labeled commits.
git show <sha> — shows commit details and full diffgit show --stat <sha> — files changed, insertions, deletionsgit show <sha>:path/to/file — cat a file from any point in historygit show <tag-name> — shows tagger, date, message, and signed commitgit show -S 'password' <sha> — highlight matches in the diff| File | Command / Code | Purpose |
|---|---|---|
| terminal | git show HEAD | What Is Git Show and When to Use It |
| terminal | git show --stat abc1234 | Inspecting Commits |
| terminal | git show v1.0.0 | Viewing Tags |
| terminal | git show HEAD~1:src/main.go | Inspecting Files and Directories with Tree and Blob Referenc |
| terminal | git show --format="%h %an %ai %s" --no-patch HEAD | Formatting Output for Scripts and Automation |
| terminal | git diff v1.0.0 v2.0.0 -- src/main.go | Comparing git show with git log, git diff, and git cat-file |
| terminal | git reflog | Using git show with Reflog to Recover Lost Commits |
| terminal | git show rc-2.3.0 --stat | Practical Workflow |
| terminal | git show -m merge-commit-sha | Common Pitfalls and How to Avoid Them |
| terminal | git config --global alias.s 'show --stat' | Integrating git show into Your Daily Workflow |
| terminal | git show --pretty=format:"{\"commit\":\"%H\",\"author\":\"%an\",\"subject\":\"%s... | Advanced |
| terminal | git rev-parse HEAD~2 | Troubleshooting |
Key takeaways
git show <sha> shows full commit diffgit show --stat <sha> for a summary of changed filesgit show <sha>:<file> reads file contents from any point in historygit show <tag> inspects annotated tag objects and signatures^{commit} and ^{tree} to navigate between Git object typesgit show -S for suspicious patterns like 'TODO', 'FIXME', 'hardcoded'Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
That's Git. Mark it forged?
6 min read · try the examples if you haven't