A commit is a snapshot of your entire project at a specific point in time. Every time you run git commit, Git records the exact state of every tracked file, who made the change, when it happened, and a message describing why. Once created, a commit is essentially immutable — it gets a unique SHA hash that acts as its fingerprint.
Terminal
Each line in the log above is a commit. The hex string on the left (e4f5a6b) is the abbreviated SHA-1 hash — a 40-character identifier generated from the commit's contents. Even the tiniest change produces a completely different hash, which is how Git detects modifications and ensures data integrity.
Unlike some version control systems that store differences between files, Git stores full snapshots. It is smart enough to avoid duplication — if a file has not changed since the last commit, Git simply points to the previous version instead of storing a copy. This design makes operations like switching branches and comparing commits extremely fast.
Think of commits as save points in a video game. You can always go back to any previous save, compare what changed between saves, and even create parallel timelines (branches) from any save point.
Staging files with git add
Before you can commit changes, you need to tell Git which changes to include. This is called staging. The staging area (also called the index) sits between your working directory and the repository. It lets you handpick exactly which changes go into the next commit.
$ git add src/
Hover over each part to see what it does
The staging area is one of Git's most powerful features. It means you do not have to commit everything at once. You can make ten changes to five files and then organize them into two or three focused commits, each with a clear purpose.
Staging workflow in practice
Here is a typical workflow. You have modified two files and created a new one. You want to commit the UI changes separately from the utility changes:
Terminal
Notice how after running git add with specific file paths, only those files moved to the "Changes to be committed" section. The helper file remains unstaged and will not be included if you commit now.
Common git add variations
git add . — Stage all changes in the current directory and below.
git add -A — Stage all changes in the entire repository, including deletions.
git add -p — Interactively stage individual hunks within files. Extremely useful for splitting changes into logical commits.
git add *.tsx — Stage all files matching a glob pattern.
Writing commit messages
A commit message is your explanation to your future self and your teammates. Six months from now, when someone runs git blame on a confusing line of code, the commit message is the first thing they will read. A good message saves hours of detective work.
Terminal
The -m flag lets you write the message inline. For simple changes this is fine, but for anything non-trivial you should write a multi-line message. Just run git commit without -m and your configured editor will open:
Editor
$ git commit
# Opens your editor with:
Add email verification for new signups
Users now receive a verification email after registration.
The account remains inactive until the link is clicked.
Token expires after 24 hours.
Closes #142
The anatomy of a good commit message
Subject line examples
# Imperative mood — like giving a command
Add email verification for new signups
Fix off-by-one error in pagination
Remove deprecated API v1 endpoints
Subject + body format
Add email verification for new signups
← blank line
Users now receive a verification email after registration.
The account remains inactive until the link is clicked.
Token expires after 24 hours.
Good commit body
Refactor database connection to use connection pool
The previous approach opened a new connection for every query,
which caused timeouts under load (>50 concurrent users).
Using a connection pool with a max of 20 connections reduces
average query time from 120ms to 15ms.
See monitoring dashboard: https://metrics.internal/db
Good vs. bad messages
Compare these examples. The good messages tell you exactly what happened and could be understood by someone who has never seen the code:
Commit message comparison
# Good commit messages
Fix off-by-one error in pagination
Add user authentication with JWT tokens
Refactor database connection to use connection pool
Remove deprecated API v1 endpoints
# Bad commit messages
fix stuff
updates
WIP
asdfgh
changed some files
Amending commits
Made a typo in your commit message? Forgot to stage a file? The --amend flag replaces the most recent commit with a new one that includes your staged changes and optionally a revised message.
Terminal
$ git commit --amend --no-edit
Hover over each part to see what it does
Under the hood, --amend does not actually modify the old commit (commits are immutable, remember). Instead, Git creates a brand new commit with a new hash and moves the branch pointer to it. The old commit still exists in the object database until Git garbage-collects it.
Warning: Never amend a commit that has already been pushed to a shared branch. Amending changes the commit hash, which means anyone who pulled the original commit now has a different history. This causes merge conflicts and confusion. Only amend commits that are still local to your machine.
Amending just the message
If you only want to fix the commit message without changing any files, make sure nothing is staged and run:
Staging files from the command line works well, but a visual tool makes it far easier to review exactly what you are about to commit. In Komitly, the staging panel shows all your changed files at a glance. Click a file to move it between staged and unstaged — no commands to remember.
Komitly - Staging Panel
Staged (2)
Msrc/styles/layout.css
Atests/header.test.ts
Changes (4)
Msrc/App.tsx
Asrc/components/Header.tsx
Asrc/components/Footer.tsx
Msrc/utils/helpers.ts
Add header component and update layout
Commit
Try clicking the files above to stage and unstage them. In the real app, staging is instantaneous and you can see the diff update in real time as you select files.
Hunk-level staging
Sometimes a single file contains changes that belong in different commits. Komitly's diff viewer lets you stage individual hunks or even individual lines. This is the visual equivalent of git add -p, but without the awkward terminal interface:
Komitly - Diff Viewer
src/utils/helpers.ts
@@ -12,8 +12,11 @@ export function formatDate(date: Date) {
11 const year = date.getFullYear();
22 const month = date.getMonth() + 1;
33 const day = date.getDate();
4- return `${year}-${month}-${day}`;
4+ const pad = (n: number) => n.toString().padStart(2, '0');
5+ return `${year}-${pad(month)}-${pad(day)}`;
56}
67
78export function parseDate(str: string) {
8- const parts = str.split('-');
9- return new Date(+parts[0], +parts[1] - 1, +parts[2]);
10+ if (isNaN(year) || isNaN(month) || isNaN(day)) {
11+ throw new Error(`Invalid date string: ${str}`);
12+ }
13+ return new Date(year, month - 1, day);
1014}
The diff viewer highlights additions in green and removals in red. In Komitly, you can click the gutter next to any hunk or line to stage just that portion. This makes it trivial to split a large set of changes into clean, logical commits.
Combined with Komitly's commit graph, you get immediate visual feedback after every commit. You can see exactly where your new commit landed, which branch it is on, and how it relates to the rest of your history.
Best practices
Following these conventions will make your Git history a valuable resource rather than a cluttered timeline that nobody reads:
Commit early, commit often. Small, focused commits are easier to review, easier to revert, and easier to understand months later. If you find yourself writing "and" in a commit message, consider splitting the commit in two.
Each commit should represent one logical change. A bug fix and a new feature should be separate commits, even if you wrote them in the same coding session. Use the staging area to separate them.
Write messages in the imperative mood. "Add search feature" reads better than "Added search feature" or "Adding search feature". The convention is that a commit message completes the sentence: "If applied, this commit will ...".
Keep the subject line under 50 characters. This ensures it displays cleanly in git log, GitHub, and tools like Komitly. Use the body for longer explanations.
Do not commit generated files. Build outputs, node_modules, .env files, and compiled binaries should live in your .gitignore, not in your history.
Review the diff before committing. Run git diff --staged to see exactly what will be committed. In Komitly, the diff viewer shows this automatically for every staged file. This catches debug statements, leftover console.log calls, and accidental changes.
Use branches for unfinished work. If a feature is not ready, commit it on a branch rather than cluttering the main branch with half-finished code. Branches are cheap in Git — use them liberally.
Never commit secrets. API keys, passwords, and private keys should never appear in a commit. Even if you remove them in the next commit, they remain in the Git history forever. Use environment variables and .gitignore.
The git commit command is something you will run thousands of times throughout your career. Building good habits now — focused commits, clear messages, and careful staging — will pay dividends every time you need to debug, review, or revert a change.