← back to the section

A commit is the unit of history. Six months from now, it is the commits that you (or a colleague) will use to figure out what changed and why. The difference between a history that helps and a history that is a dumping ground is craft: what to put into a commit, how to label it, and what to keep out of the repository entirely.

Atomic commit: one change — one snapshot

A good commit is atomic: one logical change in its entirety. Fixed a bug — commit; reformatted code — a separate commit; added a feature — a third one. The bad extremes: a "dumpster commit" (bug + refactoring + new feature in one) and a "saved just in case" commit with half of an unfinished thought.

Why it matters: atomic commits can be reverted one by one, moved between branches, and understood during review. A dumpster — only as a whole.

Precision of the set is what the index provides:

git add src/OrderService.java      # only the files you need
git add -p                          # interactively: piece by piece within a file!
git commit -m "fix: recalculate total when removing the last item"

git add -p is an underrated command: it shows changes in chunks and asks which ones to include. That is how one file with two edits becomes two clean commits.

The commit message: answering "why"

The first line is a short summary (up to ~70 characters), in the imperative mood, no trailing period. What exactly changed is visible from the code; the message should answer what the code does not contain: why.

  • Bad: fix, edits, wip, changed OrderService.
  • Good: fix: cart total was not reset when removing the last item.

Many teams use conventional commits prefixes (feat:, fix:, refactor:, docs:) — they make it easy to filter history and generate a changelog automatically. If more context is needed — a blank line after the summary and a paragraph of details.

Reading history: log, diff, blame

git log --oneline -15        # the last 15 commits, briefly
git log -p file.txt          # history of a specific file with changes
git diff                     # what changed but is not yet in the index
git diff --staged            # what is already in the index (will go into the commit)
git show abc123              # what a specific commit did
git blame OrderService.java  # who changed each line and in which commit

git blame sounds accusatory, but it is a tool of archaeology, not of finding culprits: you found a strange line → blame → the commit → its message explains "why" → and if it doesn't, you know whom to ask. The "diff before commit" habit cuts off accidental changes: a second of review saves you from committed debug output.

.gitignore: what does not belong in history

Git offers to commit everything it sees — but not everything should end up in the repository:

build/          # build artifacts — reproducible from the code
node_modules/   # dependencies — restored from the lock file
*.log           # logs
.idea/          # IDE settings
.env            # SECRETS — never in git!

The .gitignore file in the project root lists such paths — Git stops noticing them. Two rules. First: generated and personal things are ignored, source and shared things are committed. Second, and most important: secrets (passwords, API keys) must never get into history — history is distributed and eternal, truly deleting from it is hard; a key that made it into a commit is considered compromised and must be rotated. On automatic protection against this — Gitleaks in the security series.

Fixing the last commit

Forgot a file or made a typo in the message — do not create a "fix the previous one" commit:

git add forgotten-file.txt
git commit --amend           # replaces the last commit with a new one

One caution: --amend rewrites the commit — it is safe as long as the commit has not been sent to the server. The "don't rewrite what's been pushed" rule is covered in detail in the rebase article.

In short

  • An atomic commit: one logical change. git add -p builds a commit piece by piece.
  • The message answers "why": a short summary, with a paragraph of details when needed. feat/fix prefixes help with filtering.
  • History is a tool: log for the timeline, diff before committing, blame for archaeology.
  • .gitignore: build output, dependencies, IDE files — out; secrets — never in git, and if they got in — rotate them.
  • --amend fixes the last commit, as long as it has not been pushed.
  • Branches and merging — the next topic in the series.
  • Undo and recovery — reset, revert, and reflog when something goes wrong.
  • What is Git — if you missed the beginning.