Commits
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages. |
A commit is the unit Git records history in, and staging is how you decide exactly what goes into the next one.
This page covers what a commit actually stores, how to build one with git add and git commit, how to read
history back with git log, git show and git diff, and the difference between the safe ways to change your
mind (restore, revert) and the ones that rewrite history (amend, reset). Everything below is written
against Git 2.55.x, GitHub CLI 2.101.x, IntelliJ IDEA 2026.2, current stable VS Code with the GitHub Pull
Requests and Issues extension, github.com as of September 2026, and a repository whose default branch is main.
Concept
A common misconception is that a commit stores a diff against the previous commit. It does not: a commit stores a full snapshot of every tracked file at that point, plus a bit of metadata. Git only stores the byte content of a file once per unique content (deduplicated by a content hash) and reuses it across snapshots that did not change that file, so the "snapshot, not diff" model is cheap in practice even though conceptually every commit is a complete picture of the project.
Each commit is identified by a hash of its own content: the tree, the parent id(s), the author, the committer and
the message. Git has used a 40-character SHA-1 hex id since its creation; Git itself can create
SHA-256 repositories (git init --object-format=sha256), but GitHub and most tooling still use SHA-1, which
remains the default you will see in almost every existing repository. A
commit object holds:
| Field | What it is |
|---|---|
Tree |
A pointer to the root of the snapshot: a tree object that lists the project’s files and sub-directories (each sub-directory is itself a tree, each file a blob) as they existed at commit time. |
Parent(s) |
The id of the commit this one was made on top of. A normal commit has one parent, the first commit in a repository has none, and a merge commit has two or more — this is what turns individual commits into a graph rather than a flat list. |
Author |
Who originally wrote the change and when ( |
Committer |
Who last applied this exact commit object and when. For a commit you make and push yourself, author and committer are the same person and timestamp; they differ when a commit is amended, rebased or applied by another tool — the committer identity and date change, the author identity does not. |
Message |
A short summary plus optional body describing why the change was made — see Writing good commit messages. |
Because a commit id is a hash of all of this, changing anything — even just the message — produces a new commit with a new id; nothing about a commit object can be edited in place.
HEAD is a pointer to "where you are right now" — normally a symbolic reference to the tip of the branch you
have checked out (e.g. refs/heads/main), which itself points at the latest commit on that branch. Making a new
commit creates a commit object whose parent is the current HEAD, then moves the branch (and therefore HEAD) to
point at the new commit. A linear history — no branching, no merging — looks like this:
HEAD (via main) points at the rightmost commit; each commit’s single parent arrow points left, back toward the
first commit. Derived from
Pro Git 2.2, Recording Changes to
the Repository and Pro Git 10.2, Git Objects. See
Branches for what a branch pointer adds on top of this, and
Repositories and Cloning for the working
tree/staging area/repository model a commit is built from.
Command line
Staging and committing
git add copies the current content of a file from the working tree into the staging area (the index) — it does
not commit anything, it only marks what the next commit will contain:
git add README.adoc # stage one file exactly as it is right now
git add src/ docs/ # stage everything changed under these directories
git add -p # interactively choose which hunks to stage, file by file
git add -p walks each changed file hunk by hunk and asks y/n/s (split the hunk further) /q for each one,
which is the way to build a commit that contains only some of the changes currently in your working tree.
Derived from git-add.
git commit -m "Add retry logic to the payment client" # commit exactly what is staged
git commit -a -m "Fix off-by-one in pagination" # stage every tracked, modified file, then commit
git commit -a is a shortcut that stages modifications and deletions of files Git already tracks before
committing — it never picks up new, untracked files, so a brand-new file still needs an explicit git add.
Running git commit with no -m opens core.editor for the message instead. Derived from
git-commit.
Writing good commit messages
The convention Git itself and most projects (including this account’s own commit history) follow is the 50/72
rule: a subject line under about 50 characters, a blank line, then a body wrapped at about 72 characters per
line. Write the subject in the imperative mood — "Add retry logic", not "Added retry logic" or "Adds retry
logic" — because that is the mood Git’s own generated messages use (Merge branch…, Revert…), so a real
subject line reads naturally after it: "This commit will Fix null pointer in the export job." The body explains
why the change was made and what alternatives were considered, not a restatement of the diff, which git show
already displays.
On GitHub, a commit message body (or a pull request description) containing a phrase like Closes #123,
Fixes #123 or Resolves #123 automatically closes issue 123 in the same repository once that commit lands on
the default branch — see Pull Requests for how this is
normally used from a pull request description rather than an individual commit. Derived from
Pro Git 2.2, Recording Changes to
the Repository.
Inspecting history
git log # full history, newest first
git log --oneline # one line per commit: short hash + subject
git log --oneline --graph --all # ASCII graph of every branch, not just the checked-out one
--graph draws the parent/merge structure with *, | and \ characters, and --all includes every ref
(branches, tags), not only HEAD’s ancestry — the combination is the fastest way to see how branches diverged
and merged from the terminal. Derived from
Pro Git 2.3, Viewing the Commit History and
`git-log.
git show <sha> # the full commit: metadata + the diff it introduced
git show HEAD # the most recent commit
git diff # working tree vs. the staging area (unstaged changes)
git diff --staged # staging area vs. HEAD (what the next commit will contain)
Changing and undoing
Only amend a commit that has not been pushed yet, or that you are certain nobody else has already fetched or
built on — git commit --amend replaces the last commit with a brand-new one (new id, new timestamp), so
amending a commit others already have forces everyone who has it to rewrite their own history to match.
git add forgotten-file.adoc
git commit --amend # fold the staged changes into the previous commit, keep its message
git commit --amend -m "New message" # also replace the message
Derived from Pro Git 2.4, Undoing Things.
git restore (Git 2.23+) undoes uncommitted changes without touching history at all:
git restore <file> # discard unstaged changes in the working tree, back to the staged (index) version
git restore --source=HEAD --staged --worktree <file> # discard staged and unstaged changes, back to the last commit
git restore --staged <file> # unstage a file (keep the change in the working tree, just not staged)
git restore <file> with no other options is destructive to uncommitted work: the discarded edits are not
recoverable. It never touches commits that already exist. Derived from
git-restore; see
Checkout, Switch and Restore for the full
checkout/switch/restore picture, including detached HEAD.
git revert and git reset both "undo" a commit, but in very different ways:
git revert <sha> # create a NEW commit that undoes <sha>'s changes; history keeps growing forward
git revert is the safe choice once a commit has been pushed or shared: it never removes or changes an
existing commit, it only adds a new one on top, so collaborators who already have the old commits are unaffected.
Derived from git-revert.
git reset --soft <sha> # move HEAD (and the branch) to <sha>; keep the index and working tree as they are
git reset --mixed <sha> # also reset the staging area to match <sha> (the default mode); working tree kept
git reset --hard <sha> # also overwrite the working tree to match <sha>
git reset rewrites history: it moves the branch pointer itself, so any commit left "above" <sha> is no
longer on the branch. --hard additionally overwrites tracked files in the working tree to match <sha>,
discarding uncommitted changes and the content of the commits being reset away — there is no interactive
confirmation, and on a branch already pushed and shared this causes exactly the same problem as amending a shared
commit. Reach for git revert instead of git reset --hard on any branch other people can already see.
Derived from git-reset.
| I want to… | Command |
|---|---|
Stage a file for the next commit |
|
Stage only some of a file’s changes |
|
Add a forgotten file to the last (unpushed) commit |
|
Fix the last (unpushed) commit’s message |
|
Discard unstaged changes to a file (back to the staged version) |
|
Discard all uncommitted changes to a file (back to the last commit) |
|
Unstage a file, keep its changes |
|
Undo a commit that is already pushed/shared |
|
Undo local, unpushed commits but keep the changes staged |
|
Undo local, unpushed commits and unstage the changes |
|
Throw away local, unpushed commits and their changes entirely |
|
On GitHub.com
Editing a file directly in the browser also creates a commit — useful for small fixes without cloning locally:
-
Navigate to the file in the repository and click the pencil (edit) icon.
-
Make the change in the web editor; use the Preview tab to check rendered Markdown/AsciiDoc.
-
Click Commit changes… (top right of the editor), write a commit message (subject and, optionally, an extended description).
-
Choose the target:
-
Commit directly to the
mainbranch — only reasonable when you are confident and are not on a protected default branch. -
Create a new branch for this commit and start a pull request — the recommended default, especially when editing the repository’s default branch, since it lets someone review before the change lands; see Pull Requests.
-
-
Click Propose changes / Commit changes.
Creating a brand-new file works the same way, from the Add file > Create new file button in a repository listing. Derived from GitHub Docs — Editing files and GitHub Docs — About commits.
To look at history: open the Commits link above a file listing (or a file’s own History button) to see every
commit that touched that path; click any commit’s SHA to open its page, which shows the full message, the author
and committer, and a Files changed diff — the same information git show <sha> prints locally. Derived from
GitHub
Docs — About commits.
In IntelliJ IDEA
-
Open the Commit tool window (Ctrl+K on Windows/Linux, ⌘K on macOS) — it lists every changed file, grouped by changelist, with a checkbox next to each.
-
To commit only part of a file, open its diff from the Commit tool window (Ctrl+D) and select the checkbox next to each individual chunk (hunk) of changed code to include; leave the rest unchecked to commit it later — IntelliJ’s equivalent of
git add -p. -
Type the commit message in the message field (IntelliJ nudges the subject line toward the 50/72 convention), then click Commit (local only) or Commit and Push.
-
To fold staged changes into the previous commit instead of creating a new one, check Amend commit in the Commit tool window before committing (only offered for commits that have not been pushed).
-
Open the Git log (Alt+9 on Windows/Linux, ⌘9 on macOS, the Log tab of the Git tool window) to browse history graphically — equivalent to
git log --graph --all. -
From the Git log, right-click a commit for Revert Commit (creates a new commit undoing it — safe for pushed commits, equivalent to
git revert) or Reset Current Branch to Here (moves the branch pointer, with a choice of Soft / Mixed / Hard / Keep — equivalent togit reset, and therefore subject to the same history-rewriting warning above).
Derived from JetBrains — Commit and push changes and JetBrains — Undo changes.
In VS Code
-
Open the Source Control view (Ctrl+Shift+G on Windows/Linux, ⌃⇧G on macOS) — it lists changes under Changes (unstaged) and Staged Changes.
-
Hover a file and click the
+icon to stage it (or the-icon to unstage), or stage/unstage everything from the section’s…menu; opening a file’s diff lets you stage individual selected lines, VS Code’s nearest equivalent togit add -p. -
Type the commit message in the box at the top of the view, then click the checkmark (Commit) button.
-
Run Git: Commit (Amend) from the Command Palette to fold staged changes into the previous commit and edit its message, and Git: Undo Last Commit to move the branch back one commit while keeping its changes staged (VS Code’s soft-reset shortcut for a commit you just made locally).
-
Use the Timeline view (in the Explorer sidebar, per file) or the Source Control Graph under Source Control to browse commit history, inspect an individual commit’s diff, or check out an older revision of a file.
Derived from VS Code Docs — Source Control overview.
Common mistakes / tips
-
Forgetting that
git commit -anever stages new files — an untracked file silently stays out of the commit until yougit addit explicitly. -
Amending or hard-resetting a commit that a teammate has already pulled: use
git revertinstead once anything is pushed, as covered above. -
Writing a subject line that describes the diff ("changed 3 files") instead of the intent ("Fix pagination off-by-one") —
git log --onelineshould read like a changelog on its own. -
Confusing
git diff(working tree vs. staging area) withgit diff --staged(staging area vs. last commit) — reach for the wrong one and a review of "what am I about to commit" shows the wrong changes. -
Running
git reset --hardto "clean up" without first checkinggit logfor commits that only exist locally — once discarded this way, their content is not recoverable through the normal working tree.