git commit is the command that turns staged changes into a permanent point in
your project's history. Type git commit -m "message", press Enter, and Git snapshots
whatever you staged, wraps it in an author, a timestamp, and a message, and gives it a unique ID.
That's the mechanic. What most guides skip is the step between staging and committing — actually
looking at what you're about to commit before you commit it. This guide covers the full command,
every flag worth knowing, and that missing verification step, in the order you'll actually use
them. If you haven't set an identity yet, configuring
your Git username and email comes first — git commit refuses to run without one.
Quick Answer: The Git Commit Command in One Line
git commit -m "your message" commits whatever is currently staged. To stage and
commit every tracked, modified file in one step, use git commit -am "your message" —
but note the word tracked: it skips brand-new files Git hasn't seen yet. Knowing how to
commit is really knowing three commands rather than one, and these are the three git commit steps
most sessions actually run:
git add . # stage everything that changed
git status # confirm what's staged, what isn't
git commit -m "message" # commit what's staged, with a message That's how to git commit safely in three lines: git commit itself, plus the two commands around it that keep you from committing the wrong thing. The rest of this guide expands each piece — what a commit actually is, how to check exactly what's about to be committed, every flag worth memorizing, and the mistakes that show up most often in real repositories.
What Is a Git Commit?
A git commit is a saved snapshot of your project at a specific point in time, plus metadata: who
made it, when, and why. It is not a diff, even though tools display it as one — Git stores the
full state of every tracked file at that moment, and computes the diff against the previous
commit only when you ask it to display one. Each commit also records a pointer to its parent
commit (or parents, for a merge), which is how git log can walk backward through
your entire history one link at a time.
Every commit gets a unique 40-character SHA-1 hash — something like
a3f5c9e1b2d4... — generated from the snapshot's contents, its metadata, and its
parent's hash. Change any of those inputs and the hash changes completely, which is why editing
an old commit's message or content produces a new hash rather than silently patching the old one.
That single property is also what makes amending a commit
message safe on unpushed work and risky on shared history: the amended commit is a different
object entirely, not a patched version of the original. It's the same reason every commit after a
removed one gets a fresh hash, which
Diff Checker's commit deletion guide walks through.
What Does Git Commit Actually Do?
What does git commit do, mechanically? It reads the staging area (also called the index), builds
a tree object representing that exact set of files and contents, wraps it in a commit object with
your author info, timestamp, message, and parent pointer, and moves the current branch pointer
forward to the new commit. Nothing in your working directory changes — git commit
never touches files on disk, it only records what's already staged.
This is Git's three-tree model, and understanding it is the fastest way to stop being surprised by
git status output:
- Working directory — the actual files on disk, as you're editing them.
- Staging area (index) — a snapshot-in-progress.
git addcopies changes from the working directory here. - Repository (HEAD) — the last commit on your current branch.
git commitcopies the staging area here, permanently.
A commit's SHA depends on its parent's SHA, which depends on its parent's SHA, all the way back to the first commit in the repository. That chain is what makes history a directed acyclic graph rather than a flat list — every commit is provably connected to everything before it, and resetting a branch just moves the branch pointer to a different node in that same graph rather than deleting anything immediately.
Before You Commit: Staging with git add
git commit only ever commits what's staged — nothing more. If you edited five files
and staged three, the commit contains three. This is deliberate: staging exists so you can build
a commit out of a subset of your changes instead of being forced to commit everything you've
touched.
git add file.js # stage one specific file
git add file1.js file2.js # stage several specific files
git add src/ # stage everything inside a directory
git add . # stage everything changed in the current directory and below
git add -A # stage everything in the whole repository, including deletions git add . and git add -A are close but not identical: . is
scoped to the current directory and below, while -A covers the entire working tree
regardless of where you run it from. If a file gets staged that shouldn't be — a debug log, a
half-finished experiment — unstaging it is a single command
and leaves the edits themselves untouched.
The Verification Step: git diff --staged
This is the step most git commit tutorials skip, and it's the one that actually prevents bad
commits. git add . stages by filename, not by intent — it doesn't know or care
whether a file contains exactly the change you meant, an extra console.log you
forgot to remove, or half of an unrelated edit you made while debugging something else. Staging is
not review. It's just copying.
The fix is a three-command habit, run every single time before you commit:
git status # WHAT changed and WHERE it currently sits (staged / unstaged / untracked)
git diff --staged # EXACTLY what will be committed, line by line, word by word
git commit -m "..." # commit it, now that you've actually looked git status answers a categorization question — which files are staged, which are
modified but unstaged, which are untracked. It does not show content. git diff --staged
(identical to git diff --cached — both flags exist, pick one and stay consistent)
answers the content question: the literal lines that will land in the next commit, printed in
unified diff format exactly like a normal
git diff, but scoped to what's in the index instead of what's in the working
directory.
The distinction that trips people up: plain git diff (no flag) shows changes in the
working directory that are not yet staged — the opposite of what you're about to commit.
Run git diff right before a commit and you're reviewing the wrong diff; the staged
changes won't show up there at all once they've been added. This is doubly relevant with
comparing two files directly when a change is
large enough that scrolling terminal output stops being a reasonable way to read it — dump the
staged content to a file and open it in a real diff viewer instead of squinting at
+/- lines in a scrollback buffer.
# If a staged diff is long or heavily reformatted, write it to a file instead of scrolling
git diff --staged > staged-changes.diff
That's exactly the workflow Diff Checker is built around. It's a Chrome extension
(Manifest V3) that opens in a full browser tab, built on Monaco — the same editor engine behind VS
Code — with two editable panes and a live, color-coded diff. Paste the output of
git diff --staged into one pane, or use its Open Files picker to pull
in two file versions directly, and read the change in Split or Unified view instead of parsing
plus/minus prefixes in a terminal. "Show Diff Only" collapses everything unchanged down to just
the lines that moved, with an adjustable context window, which turns a 40-line diff buried in a
400-line file into something you can actually review in one screen. Your text never leaves your
device unless you turn on the optional AI summary, which requires your own OpenAI API key.
Basic git commit Syntax and the -m Flag
The basic git commit syntax is:
git commit -m "<message>" -m is short for --message, and it's how the overwhelming majority of
commits get made — git commit with message supplied directly on the command line, no editor
involved. Drop the flag entirely and run just git commit, and Git opens your
configured text editor (Vim by default on most installs) so you can write a longer message
directly, including a body paragraph below the summary line. Both are git commit from the command
line; one just skips the editor round-trip for short messages.
$ git commit -m "Fix null pointer in checkout handler"
[main 7c4a9d2] Fix null pointer in checkout handler
2 files changed, 14 insertions(+), 3 deletions(-)
The output line confirms three things at once: the branch (main), the new commit's
short SHA (7c4a9d2), and the message you gave it. That short SHA is a truncated,
human-readable prefix of the full 40-character hash — enough characters to be unique in almost any
repository, and what most other git commands (git show, git checkout,
git reset) accept interchangeably with the full hash.
Git Commit Command Examples
A working set of git commit command examples, covering the situations that come up in nearly every session:
| Situation | Command |
|---|---|
| Commit staged changes with a message | git commit -m "Add search filter to dashboard" |
| Stage every tracked, modified file and commit in one step | git commit -am "Fix typo in README" |
| Fix the message of the commit you just made | git commit --amend -m "Corrected message" |
| Add a forgotten file to the last commit, message unchanged | git add forgotten.js && git commit --amend --no-edit |
| Write a multi-line message with a summary and a body | git commit -m "Add rate limiting" -m "Caps API calls at 100/min per key." |
| Sign a commit cryptographically | git commit -S -m "Signed release commit" |
| Create a placeholder commit with no file changes | git commit --allow-empty -m "Trigger CI rebuild" |
| Commit without running pre-commit/commit-msg hooks | git commit --no-verify -m "WIP: skip lint hook temporarily" |
None of the examples above stage anything on their own except the -a and
--amend --no-edit rows — the rest assume you already ran git add and,
ideally, already looked at git diff --staged.
Essential git commit Flags Explained
The full flag list is long — the official git commit documentation is the reference for every option. These are the ones worth knowing by name.
-m "<message>"— supply the commit message inline, skipping the editor. Repeat it (-m "summary" -m "body") to build a message with a blank-line-separated body.-a— stage all modifications and deletions to files Git already tracks, then commit. Combined with-mas-am, covered in detail below.--amend— replace the most recent commit with a new one that includes whatever is currently staged, and optionally a new message. This is the command behind amending a commit message after the fact — safe before you've pushed, since it rewrites the commit's hash and history along with it.--no-edit— used with--amendto keep the existing commit message unchanged while still updating its content (typically after staging one more forgotten file).--allow-empty— creates a commit with no file changes at all. Rare outside CI triggers, empty markers, or testing hooks, but occasionally exactly what you need.-S— GPG-signs the commit, attaching cryptographic proof of authorship that tools like GitHub can display as "Verified."--no-verify— skipspre-commitandcommit-msghooks. Useful for a genuine emergency commit; a habit to avoid otherwise, since those hooks usually exist to catch something specific.-v/--verbose— when no-mis given, shows the staged diff directly inside the editor below the message prompt, so you're reviewing content while you write the message rather than switching windows.
git add and Commit in One Line
git commit -am "message" is the shortcut for "stage everything modified, then
commit" — a genuine git add and commit in one line for the common case of editing files Git
already knows about. It expands to roughly git add -u && git commit -m "message",
where -u stages modifications and deletions but not new files.
That last clause is the trap. -a only stages changes to files already tracked by
Git — anything brand new sits in "Untracked files" in git status, completely ignored
by -am. Create a new file, edit three existing ones, run git commit -am "...",
and the commit contains the three edits but not the new file, with no warning that anything was
left out.
$ git status
Changes not staged for commit:
modified: src/app.js
Untracked files:
(use "git add <file>" to include in what will be committed)
new-helper.js
$ git commit -am "Add helper function"
[main 9b2f1a3] Add helper function
1 file changed, 8 insertions(+)
# new-helper.js was never staged -- it's still untracked after this commit
The reliable fix is the same verification habit from earlier: run git status before
-am, not after, and check the "Untracked files" section for anything that belongs in
the same commit. If it's there, stage it explicitly with git add new-helper.js first.
Interactive Staging with git add -p
git add . stages whole files. git add -p (short for
--patch) stages individual chunks — "hunks" — inside a file, one at a time, which
matters when a single file has two unrelated edits and only one belongs in the current commit.
$ git add -p src/app.js
diff --git a/src/app.js b/src/app.js
@@ -12,6 +12,7 @@ function calculateTotal(items) {
let total = 0
+ const tax = getTaxRate()
return items.reduce(sum, total)
}
Stage this hunk [y,n,q,a,d,s,e,?]?
Git walks through every changed hunk in the file and asks a yes/no question for each one:
y stages it, n skips it, s splits a hunk that's too large
into smaller pieces if possible, and e opens a manual editor for surgical control
over exactly which lines get staged. This is the tool for keeping commits atomic when your editor
session wasn't — it lets the commit boundary match the logical change, not the file-save boundary.
Atomic Commits: What Belongs in One Commit
An atomic commit contains exactly one logical change — a single bug fix, a single feature slice, a single refactor — and nothing incidental bundled in alongside it. The test is simple: could you describe this commit's content in one sentence without using "and"? If the honest description is "fixes the login bug and reformats three unrelated files and bumps a dependency version," that's three commits wearing one.
Atomic commits matter for reasons beyond tidiness. A commit that mixes concerns can't be reverted
cleanly — reverting it undoes the good change along with the bad one. git bisect,
which binary-searches history to find which commit introduced a regression, only works well when
each commit is small and does one thing; a commit with five unrelated changes makes "which part of
this broke it" a manual investigation instead of a five-second answer. Code review gets easier too:
a reviewer evaluating one coherent change reads faster and catches more than one evaluating a
combined diff.
git add -p is the main tool for keeping commits atomic in practice — it lets you split
a working session's edits into the commits they should have been, instead of committing everything
touched in one sitting. If a commit turns out too large after the fact,
squashing commits works the other direction, collapsing several
small commits into one when the history got too granular instead of not granular enough.
How to Write a Good Commit Message
A commit message has two jobs: explain what changed, for anyone scanning history later, and explain why, since the diff itself already shows what. A few conventions cover most of the gap between a useless message and a useful one. (Searches for "git commit comment" land here too — git has no separate comment field, only the message, so the two terms mean the same thing.)
Imperative mood. Write the summary line as an instruction, not a description:
"Fix login timeout" rather than "Fixed login timeout" or "Fixes login timeout." The convention
exists because git merge and git revert generate their own commit
messages in the same imperative form ("Merge branch 'feature-x'"), so a consistent tense keeps
machine-generated and human-written messages reading the same way in the log.
The 50/72 rule. Keep the summary line under about 50 characters so it doesn't
truncate in git log --oneline, GitHub's commit list, or a terminal-width log view.
If a body paragraph is needed, leave one blank line after the summary, then wrap body text at
roughly 72 characters — a width that renders cleanly with git log's default indent
without wrapping mid-word in most terminals.
Add retry logic to webhook delivery
Webhook POSTs were failing silently on transient network errors.
Retries up to 3 times with exponential backoff before giving up
and logging the failure. Conventional Commits. A widely adopted format, specified at
conventionalcommits.org,
prefixes the summary with a type:
feat: for a new feature, fix: for a bug fix, docs:,
refactor:, test:, chore:, and a few others. It's not a git
feature — nothing in git enforces or reads it — but tooling built around it (automated changelog
generation, semantic-release version bumping) parses the prefix to decide what changed and how to
bump the version number.
feat: add dark mode toggle to settings panel
fix: correct off-by-one error in pagination
docs: update README install instructions
refactor: extract validation logic into separate module git commit vs Other Commands
git commit gets confused with several neighboring commands often enough to be worth a direct comparison — each one operates on a different part of Git's pipeline, and mixing them up is one of the more common sources of "where did my change go."
| Command | What It Does | Scope |
|---|---|---|
git add | Copies working-directory changes into the staging area | Local, staging area only |
git commit | Snapshots the staging area into a permanent commit on the current branch | Local repository only |
git push | Uploads local commits to a remote repository | Local to remote |
git stash | Temporarily shelves uncommitted changes without creating a commit | Local, outside history entirely |
| "Save" (general term) | Not a git command — usually means either a plain file save in an editor, or loosely "commit" in casual conversation | N/A |
The distinction that matters most in practice: git commit changes only your local repository.
Nothing leaves your machine until a separate, explicit
git push to a remote — a commit sitting locally
unpushed is invisible to everyone else with access to that repository. That locality is also what
makes mistakes cheap: undoing the last commit is a
one-command fix while it's still local, and a much larger conversation once it isn't.
git stash
solves a related but different problem: shelving in-progress, uncommitted work temporarily to
switch branches, without it ever becoming a permanent commit at all.
Running git commit in Git Bash and VS Code
git commit behaves identically no matter which shell runs it — the git binary itself
doesn't care whether it's invoked from Git Bash, PowerShell, CMD, or a Unix terminal. What differs
is the surrounding environment, and Git Bash is the terminal
most Windows developers reach for specifically because it pairs the git binary with a Unix-style
shell in one window.
# Identical in Git Bash, macOS Terminal, or a Linux shell
$ git add .
$ git status
$ git diff --staged
$ git commit -m "Add pagination to results list"
[main 4d8e2f1] Add pagination to results list
3 files changed, 41 insertions(+), 6 deletions(-)
One Git Bash-specific detail worth knowing: if no -m flag is given, Git opens
whatever core.editor is configured to — Vim by default — directly inside the MinTTY
terminal window. If you don't know Vim's save-and-quit sequence (Esc, then
:wq, then Enter), it looks like the terminal has frozen; it hasn't, the editor is
just waiting. Setting a friendlier default avoids that entirely:
git config --global core.editor "code --wait" # VS Code as the commit-message editor
git config --global core.editor "notepad" # Notepad, simplest possible option on Windows
That's how to use git commit in any shell: the flags, the message, and the output are identical,
and only the editor that opens for a missing -m depends on your setup.
VS Code's own Source Control panel wraps the same commands in a GUI — stage with the
+ next to a file, type a message in the box at the top, commit with
Ctrl+Enter — but every action there maps directly to the
git add / git commit pair underneath. Nothing it does is unavailable
from the terminal; it's a visual layer over the same plumbing.
Common git commit Mistakes and Fixes
- Committing secrets. An API key or password pasted into a config file and
committed is in history permanently, even after the file is later edited to remove it — the old
commit still contains it, and anyone with clone access can retrieve it with
git showagainst that commit. Prevention (a.gitignoreentry, a pre-commit secret scanner) is far cheaper than the cleanup, which requires rewriting history and rotating the exposed credential regardless of whether the rewrite is done perfectly. - Assuming
-amcaught new files. Covered in detail above —-astages modifications to tracked files only, silently skipping anything untracked. - Trusting
git diffinstead ofgit diff --stagedright before committing. Plaingit diffshows unstaged changes — the opposite of what's about to be committed. Reviewing the wrong diff at the wrong moment is how unintended content ends up in a commit despite having "checked" beforehand. - Amending a commit that's already been pushed and shared.
--amendcreates a new commit object with a new hash; anyone who already pulled the original now has a diverged history. It's safe on commits that exist only locally, and requires a force-push plus coordination with collaborators once it's shared — worth reading the full amend workflow before running it on anything already pushed. - Vague, generic messages. "fix stuff," "wip," "updates" — technically valid
commit messages, functionally useless six months later when
git logorgit blameis the only context available for why a line looks the way it does. - Confusing an uncommitted stash with a commit.
git stashshelves changes outside normal history; expecting to find stashed work withgit loginstead ofgit stash listis a common early mixup, covered in the git stash guide.
Frequently Asked Questions
What does git commit do?
git commit takes everything currently in the staging area and records it as a permanent snapshot
in your local repository. Mechanically, it builds a tree object from the staged files, wraps that
tree in a commit object carrying your name, email, a timestamp, your message, and a pointer to the
parent commit, then moves the current branch pointer forward to the new commit. Nothing in your
working directory changes, and nothing is uploaded anywhere — the commit stays on your machine
until you run git push. Each commit gets a unique SHA-1 hash derived from its content,
its metadata, and its parent's hash.
What is the difference between git add and git commit?
git add stages, git commit records. git add copies changes out of your working
directory into the staging area (the index), where they sit as a snapshot in progress you can
still extend or undo. git commit takes whatever is in that staging area at that
moment and writes it into history as one commit with a message, an author, and a hash. Nothing
you edit ever reaches history unless it was staged first, which is why git commit changes only
the files you added — edits you never staged stay in the working directory, untouched and
uncommitted.
How do I write a commit message from the command line?
Use the -m flag: git commit -m "Fix null pointer in checkout handler".
That is git commit with message text supplied inline, no editor involved. Repeat the flag to build
a multi-paragraph message: git commit -m "summary" -m "body paragraph" inserts the
blank line between them for you. Omit -m entirely and git opens whatever editor
core.editor points at (Vim on most default installs) so you can write a longer
message there. Keep the summary under about 50 characters, in the imperative mood — "Add retry
logic", not "Added retry logic" — then wrap any body text near 72 characters.
How do I change a commit message after committing?
For the most recent commit, run git commit --amend -m "Corrected message". Amending
does not edit the existing commit in place; it builds a replacement commit with a new SHA, because
a commit's hash is derived from its content and metadata. That makes it safe on commits that exist
only on your machine and disruptive once they are pushed — anyone who already pulled the original
ends up with a diverged history, and republishing requires git push --force-with-lease
plus a heads-up to collaborators. To reword an older commit, use an interactive rebase:
git rebase -i and mark the target commit as reword.
Do I need to push after every commit?
No. git commit writes only to your local repository, so commits pile up on your
machine until you run git push — you can make a commit twenty times before any of
them is visible to anyone else. The usual habit is to commit often, in small atomic units, and
push at a natural boundary: when the work is coherent enough for a teammate to pull, when you want
an off-machine backup, or when CI should run. Pushing after every commit is fine too; it just
means every intermediate state is public and harder to rewrite afterward.