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 add copies changes from the working directory here.
  • Repository (HEAD) — the last commit on your current branch. git commit copies the staging area here, permanently.
Git's Three-Tree Model Working Directory files as you edit them on disk git add Staging Area (the index) -- a snapshot in progress git commit Repository (HEAD) -- permanent history git add copies changes from the working directory into the staging area; git commit copies the staging area into the repository, permanently.
The three trees git tracks: the working directory holds edited files, git add copies changes into the staging area (the index), and git commit copies the index into the repository as a permanent snapshot at HEAD.

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.

Commits Form a Chain: Each Points to Its Parent HEAD -> main a1b2c3d Commit 1 (oldest) f4e5d6c Commit 2 9c8b7a6 Commit 3 2d3e4f5 Commit 4 (HEAD) each arrow points to a parent Every commit's SHA depends on its content, metadata, and its parent's SHA -- forming a directed acyclic graph that git log walks backward from HEAD.
Each commit's SHA is derived partly from its parent's SHA, chaining commits into a directed acyclic graph. HEAD, via the branch pointer, marks the tip -- git log walks backward through parent pointers from there.

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.

Verify Before You Commit: status -> diff --staged -> commit MINGW64 -- git-commit-command $ git status staged: 1 unstaged: 1 untracked: 1 categorizes files -- doesn't show any content $ git diff --staged - return items.reduce(sum, 0) + return items.reduce(sum, tax) exact staged content -- line by line, word by word $ git commit -m "Add tax calc" [main 7c4a9d2] Add tax calc 1 file changed, 1 insertion(+), 1 deletion(-) git status shows WHAT changed and WHERE it sits; git diff --staged shows EXACTLY what will be committed -- only then does git commit make sense to run.
The habit that prevents bad commits: git status shows which files are staged, unstaged, or untracked; git diff --staged shows the literal lines about to be committed; only then does git commit make sense to run.

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 -m as -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 --amend to 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 — skips pre-commit and commit-msg hooks. Useful for a genuine emergency commit; a habit to avoid otherwise, since those hooks usually exist to catch something specific.
  • -v / --verbose — when no -m is 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.

git add -p: Splitting One File Into Two Commits' Worth of Hunks src/app.js hunk A: + const tax = getTaxRate() hunk B: + console.log(items) two unrelated edits, one file Stage this hunk [y,n,q,a,d,s,e,?]? y = stage n = skip s = split further Staging Area (Index) hunk A staged, ready to commit Working Directory hunk B stays unstaged git add -p asks per hunk instead of per file -- it lets the commit boundary match the logical change, not the file-save boundary.
git add -p walks through each hunk and asks whether to stage it. Answering per hunk splits src/app.js so hunk A lands in the staging area while hunk B stays in the working directory, unstaged.

Atomic Commits: What Belongs in One Commit

Atomic Commits: One Bloated Commit vs Three Focused Ones Before After commit 8f3a2c1 fix: null pointer in checkout style: reformat 3 files chore: bump lodash to 4.17.21 one commit, three unrelated changes git add -p fix: null pointer in checkout commit 1 -- bug fix only style: reformat 3 files commit 2 -- reformat only chore: bump lodash to 4.17.21 commit 3 -- dependency bump only Each focused commit now reverts cleanly and bisects cleanly on its own -- instead of three unrelated changes sharing one hash.
An atomic commit does one thing. Splitting a bloated commit that mixes a bug fix, a reformat, and a dependency bump into three focused commits makes each one revert cleanly and bisect cleanly on its own.

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
Anatomy of a Well-Formed Commit Message .git/COMMIT_EDITMSG 50 72 feat: add dark mode toggle to settings panel (blank line separates summary from body) Adds a settings toggle that switches between light and dark themes and persists the choice using localStorage across sessions. feat: is an optional Conventional Commits prefix that changelog tools parse. The summary stays under 50 chars; the body wraps near 72 for git log's indent.
feat: is an optional Conventional Commits prefix that changelog tooling parses. The summary line stays under 50 characters so it doesn't truncate in git log --oneline; the body wraps near 72 characters and is separated from the summary by one blank line.

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 show against that commit. Prevention (a .gitignore entry, 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 -am caught new files. Covered in detail above-a stages modifications to tracked files only, silently skipping anything untracked.
  • Trusting git diff instead of git diff --staged right before committing. Plain git diff shows 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. --amend creates 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 log or git blame is the only context available for why a line looks the way it does.
  • Confusing an uncommitted stash with a commit. git stash shelves changes outside normal history; expecting to find stashed work with git log instead of git stash list is 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.