Skip to main content
RunBook Academy

Git, CI/CD & GitOpsIII · Git ObjectsGit Objects

Commit objects — parents, tree pointers, author versus committer, signatures

Intermediate⏱ ~22 min🧪 Lab requiredgit

What you'll learn

  • Read a commit object with git cat-file -p and identify each header field
  • Distinguish author from committer and know when they should differ
  • Trace how merge commits carry two parents and octopus merges carry more
  • Recognise the encoding of a GPG signature in the commit payload
  • Construct a commit by hand with git commit-tree and explain its plumbing role

Prerequisites

Practice

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.

A commit object is the unit of history in Git. It names a tree (the snapshot), zero or more parent commits (the history edge), an author and a committer, an optional GPG signature, and a free-form message. Commits are the only object type that can appear in a ref; branches, tags, and HEAD are all just pointers to commit OIDs. Reading a commit object by hand is the single most useful Git skill, because it turns every Git command from magic into predictable plumbing.

Anatomy of a commit object

A commit object, printed by git cat-file -p <oid>, has this shape:

tree <root-tree-oid>
parent <parent-oid>
parent <second-parent-oid>      (only on merges)
author <name> <<email>> <unix-timestamp> <timezone>
committer <name> <<email>> <unix-timestamp> <timezone>
<optional gpgsig header with embedded signature>

<commit message, free-form, on its own lines>
git cat-file -p "$COMMIT_OID"
# tree 4d2c8e01a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7
# parent 9f3c1d72e1a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7
# author Ops <ops@example.com> 1730000000 +0000
# committer Ops <ops@example.com> 1730000000 +0000
#
# bump terraform module to v1.4.0

Every commit has exactly one tree line. The tree OID identifies the directory layout at the moment of the commit. The parent lines identify the predecessor commits: one parent for an ordinary commit, two or more for a merge, and zero for the very first commit in a repository (the root commit has no history).

Parents and the history DAG

The parent lines are the edges of the history DAG. An ordinary commit has one parent (the previous tip of the branch). A merge commit has two or more parents — one for each tip that was merged. The first parent is the branch that was checked out when the merge was performed; the remaining parents are the branches that were merged in. This convention is what makes git log --first-parent follow only the trunk of a history and skip the side-branch merges:

flowchart LR
    A["main: commit A"] --> M["merge commit M\nparents: A, B"]
    B["feature: commit B"] --> M
    M --> C["main: commit C"]
# A merge commit, pretty-printed
git cat-file -p "$MERGE_OID"
# tree <merged-tree-oid>
# parent <main-oid>
# parent <feature-oid>
# author Ops <ops@example.com> 1730000000 +0000
# committer Ops <ops@example.com> 1730000000 +0000
#
# Merge branch 'feature' into 'main'

Octopus merges (three or more parents) follow the same pattern: one parent per merged-in branch, with the first parent being the branch that was checked out. git log --first-parent is the canonical way to view history ignoring the merge branches.

Author and committer fields

Both fields have the same shape: a name, an email in angle brackets, a Unix timestamp in seconds, and a timezone offset (e.g. +0000 for UTC). The timestamp is part of the OID, so two commits with identical tree, parent, author, message, and committer but different committer timestamps will have different OIDs.

The angle brackets around the email are part of the on-disk format and are not user-friendly syntax. They are not Mermaid syntax — they are literally written into the commit payload by Git. When parsing commits by hand, treat the email as the bytes between < and > on the author/committer line.

Encoding and the gpgsig header

The commit object header includes an encoding line if the message was not written in UTF-8 (most commits today are UTF-8 and omit the line). When a commit is GPG-signed, the signature is embedded in a multi-line header called gpgsig:

git cat-file -p "$SIGNED_COMMIT_OID"
# tree 4d2c8e01...
# parent 9f3c1d72...
# author Ops <ops@example.com> 1730000000 +0000
# committer Ops <ops@example.com> 1730000000 +0000
# gpgsig -----BEGIN PGP SIGNATURE-----
#          iQEzBAABCgAdFiEE...
#          -----END PGP SIGNATURE-----
#
# bump terraform module to v1.4.0

The signature covers the commit object minus the gpgsig header itself (the signature cannot cover its own bytes without recursion). git verify-commit &lt;oid&gt; reconstructs the signed bytes, runs them through GPG, and prints the signing key and the result.

Building a commit by hand

The plumbing command is git commit-tree. It takes a tree OID, zero or more parent commit OIDs, and a message, and writes a commit object:

# Build a commit with no parents (the very first commit)
git commit-tree "$TREE_OID" -m "initial commit"

# Build a commit with one parent (the next commit on a branch)
git commit-tree "$TREE_OID" -p "$PARENT_COMMIT_OID" -m "second commit"

# Build a merge commit with two parents
git commit-tree "$TREE_OID" -p "$MAIN_OID" -p "$FEATURE_OID" \
    -m "Merge branch 'feature' into 'main'"

git commit-tree is the entry point for low-level tooling that needs to construct commits without using the index or working tree: CI pipelines that build commits from a fixed manifest, test frameworks that construct repositories from fixtures, and forensic tools that reconstruct history from a known set of blobs and trees.

Production discipline

  1. Inspect a commit by hand before trusting it. git cat-file -p &lt;oid&gt; is the cheapest way to confirm what a commit actually contains. If a CI log claims a commit has a certain tree, the cat-file output is the proof.
  2. Verify signatures with git verify-commit. A signed commit without verification is a signature in name only. Verification is one command and one GPG round-trip.
  3. Use git log --first-parent for the trunk of history. The merge history (the second-and-later parents) is detail; the first parent chain is what a deployment tracks.

Cross-course references

  • Docker for Production Sysadmins - Part XIV (SupplyChain) shows signed OCI image attestations that chain back to a Git commit OID. The signature covers the commit OID; the commit OID commits to the bytes; the bytes are the source.
  • Terraform for Production Sysadmins - Part IX (State) shows how Terraform state versions record an OID-equivalent identifier plus an author and a committer (the engineer who ran terraform apply), mirroring Git’s commit metadata.
  • Ansible for Production Sysadmins - Part XXXVIII (Review) draws the analogy between a commit message and an Ansible playbook changelog entry; both are read by the next engineer to touch the change.

Quiz

Knowledge check · 4 questions

  1. Q1. Which statement about a merge commit is correct?

  2. Q2. The author and committer fields of a commit can identify different people and timestamps.

  3. Q3. Which plumbing command constructs a commit object by hand from a tree OID, parent commit OIDs, and a message?

  4. Q4. Audit a merge commit and identify whether the merge was performed by the same engineer who authored the feature, and whether the merge commit is signed.

    A security review needs to know who merged a feature branch into main, whether the feature was authored by the same engineer, and whether the merge commit carries a GPG signature. The merge commit OID is `m3rge01...`. The auditor has access to the repository but not to the pull-request UI.

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