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.
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:
-
In your test project, run
git log --onelineto see all commits -
Pick a commit hash from the middle of your history
-
Run
git show <hash>to see what it changed -
Run
git diff <hash>..HEADto see everything that changed since then -
Run
git checkout <hash>to view the project at that point -
Run
git checkout mainto 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.
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.
About the Author
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.
Related Articles
Working with GitHub: Remote Repositories and Collaboration
Connect your local Git repository to GitHub. Learn to push, pull, clone, and collaborate with remote...
Git Branches and Merging: Parallel Development Without Chaos
Learn to use Git branches for safe experimentation and parallel development. Create branches, merge...
Your First Git Repository: init, add, commit
Create your first Git repository and make your first commits. Learn the core Git workflow with hands...
Article Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!