Understanding Git Commits: Snapshots, History, and Time Travel

Understanding Git Commits: Snapshots, History, and Time Travel

7 min read
14 views

Support Free C++ Education

Help us create more high-quality C++ learning content. Your support enables us to build more interactive projects, write comprehensive tutorials, and keep all content free for everyone.

Become a Patron

More Than Just Save Points

In the previous article, you made your first commits. But what actually happened when you ran git commit? Understanding commits deeply makes Git intuitive rather than mysterious.

This article explores what commits contain, how they form history, and how to work with that history effectively.

Anatomy of a Commit

A commit is more than a "save point." Each commit contains:

1. A Snapshot of All Tracked Files

When you commit, Git stores the complete state of every tracked file. Not the differences from the previous version, but the actual content.

This surprises many people. "Won't that waste space?" Git is clever: if a file hasn't changed, Git reuses the stored version from the previous commit. Only changed files are stored anew. The result is efficient storage with fast access to any version.

2. Metadata

Every commit includes:

  • Author: Who created the commit (name and email)
  • Committer: Who applied the commit (usually the same as author)
  • Timestamp: When the commit was created
  • Commit message: Your explanation of what changed and why

3. Parent Reference

Each commit points to its parent (the previous commit). This creates a chain:

Commit A ← Commit B ← Commit C ← HEAD

The first commit has no parent. Every other commit points back to where it came from. This chain is your project's history.

4. A Unique Hash

Every commit gets a SHA-1 hash, a 40-character string like:

a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0

This hash is computed from the commit's contents: the snapshot, metadata, and parent reference. If any of these change, the hash changes.

You'll usually see shortened versions (7 characters) since they're unique enough:

a1b2c3d

Viewing Commit Details

See full details of any commit:

git show a1b2c3d

Output includes:

  • Commit hash
  • Author and date
  • Commit message
  • Diff showing what changed

For the most recent commit:

git show HEAD

HEAD always points to the current commit you're working from.

Writing Good Commit Messages

Commit messages are documentation. Future you (and collaborators) will read them to understand why changes were made.

The Anatomy of a Good Message

Short summary (50 chars or less)

Longer description if needed. Wrap lines at 72 characters.
Explain what changed and why, not how (the code shows how).

- Can use bullet points
- Reference issue numbers if relevant

Good Examples

Add division function with zero-division handling

Division by zero now returns 0 and prints a warning.
This prevents crashes when users input invalid values.
Fix memory leak in particle system

Particles weren't being deallocated when emitter was destroyed.
Added destructor to properly clean up particle array.
Fixes #42.
Refactor collision detection for clarity

Split monolithic function into smaller, testable pieces.
No behavior change, just code organization.

Poor Examples

stuff
fix
changes
WIP

These messages provide no information. When you look at history later, you'll have no idea what these commits did.

Guidelines

Be specific: "Fix bug" is useless. "Fix crash when player health reaches zero" tells you exactly what was fixed.

Use imperative mood: Write "Add feature" not "Added feature" or "Adds feature." It reads like a command: "Apply this commit and it will add feature."

Explain why, not just what: The code shows what changed. The message should explain why the change was needed.

Keep the summary short: 50 characters or less. Many Git tools truncate longer summaries.

Add details when needed: For complex changes, add a blank line after the summary and write a longer explanation.

Navigating History

The Log

View commit history:

git log

Useful variations:

# Compact, one line per commit
git log --oneline

# Show changed files
git log --stat

# Show actual changes
git log -p

# Limit to recent commits
git log -5

# Filter by author
git log --author="Your Name"

# Filter by date
git log --since="2024-01-01" --until="2024-01-31"

# Search commit messages
git log --grep="fix"

Visual History

See branching graphically:

git log --oneline --graph --all

This shows how commits connect and where branches diverge.

Examining Specific Commits

View a commit:

git show a1b2c3d

See what changed between two commits:

git diff a1b2c3d..b2c3d4e

See files changed in a commit:

git show --stat a1b2c3d

Understanding HEAD

HEAD is a pointer to your current position in history. Usually, it points to the latest commit on your current branch.

When you make a new commit, HEAD moves forward to point to it.

# See where HEAD points
git log -1 HEAD

# Same as
git log -1

You can also reference commits relative to HEAD:

HEAD      # Current commit
HEAD~1    # One commit before current
HEAD~2    # Two commits before current
HEAD^     # Parent of current (same as HEAD~1 for simple history)

Going Back in Time

Viewing Old Versions

See what a file looked like in an earlier commit:

git show HEAD~3:main.cpp

Check out an old version temporarily:

git checkout a1b2c3d

You're now in "detached HEAD" state, viewing the old commit. Look around, then return to the present:

git checkout main

Undoing Changes

Git provides several ways to undo, depending on what you need:

Discard uncommitted changes to a file:

git restore main.cpp

This reverts the file to its last committed state. Uncommitted changes are lost.

Unstage a file (keep changes in working directory):

git restore --staged main.cpp

Undo the last commit, keep changes staged:

git reset --soft HEAD~1

Undo the last commit, keep changes unstaged:

git reset HEAD~1

Undo the last commit, discard changes completely:

git reset --hard HEAD~1

Warning: --hard destroys uncommitted changes. Use carefully.

Create a new commit that undoes a previous commit:

git revert a1b2c3d

This is safe because it doesn't change history. It creates a new commit that does the opposite of the specified commit.

Amending Commits

Made a typo in your last commit message?

git commit --amend -m "Corrected message"

Forgot to include a file in the last commit?

git add forgotten-file.cpp
git commit --amend --no-edit

--no-edit keeps the same commit message.

Important: Amending rewrites history. Only amend commits that haven't been shared with others. Once you push to a shared repository, treat commits as permanent.

Commit Best Practices

Commit Often

Make small, focused commits. Each commit should represent one logical change:

Good:
- "Add player jump mechanics"
- "Fix jump height calculation"
- "Add jump sound effect"

Bad:
- "Add jump mechanics, fix calculation, add sound, refactor movement code"

Small commits are easier to understand, review, and revert if needed.

Commit Working Code

Each commit should leave the project in a working state. Avoid committing code that doesn't compile or breaks tests.

This isn't always possible during heavy development. If you need to save incomplete work, consider using branches or Git's stash feature.

Group Related Changes

If you change a function and update all its callers, that's one logical commit even if it touches many files.

If you fix a bug and also add an unrelated feature, that's two commits.

Use staging selectively to create clean commits:

# Stage only some changes in a file
git add -p main.cpp

This lets you interactively choose which changes to stage.

Practical Exercise

Let's practice navigating history:

  1. In your test project, run git log --oneline to see all commits

  2. Pick a commit hash from the middle of your history

  3. Run git show <hash> to see what it changed

  4. Run git diff <hash>..HEAD to see everything that changed since then

  5. Run git checkout <hash> to view the project at that point

  6. Run git checkout main to return to the present

This kind of exploration becomes natural with practice. You're not just saving code; you're building a navigable history.

What's Next

You understand commits: what they are, how to write good messages, and how to navigate history. The next step is branches, which let you work on multiple things without interference.

Next article: Branches and Merging - Learn to create parallel development paths and combine them.

Commits are the building blocks. Branches are the scaffolding that lets you build ambitious structures.

Part of the Git Fundamentals series.

Your First Repository | Branches and Merging

Support Free C++ Education

Help us create more high-quality C++ learning content. Your support enables us to build more interactive projects, write comprehensive tutorials, and keep all content free for everyone.

Become a Patron

About the Author

Imran Bajerai

Software engineer and C++ educator passionate about making programming accessible to beginners. With years of experience in software development and teaching, Imran creates practical, hands-on lessons that help students master C++ fundamentals.

Article Discussion

Share your thoughts and questions

💬

No comments yet. Be the first to share your thoughts!