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 (user.name / user.email at the time git commit ran, or carried over when a commit is applied by someone else, e.g. via git cherry-pick or a patch).

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:

gitGraph commit id: "a1b2c3d Init project" commit id: "e4f5a6b Add README" commit id: "c7d8e9f Configure build" commit id: "1a2b3c4 Fix typo in README"

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)

git diff and git diff --staged (a synonym for --cached) answer two different questions: "what have I changed but not staged yet" versus "what will actually be committed if I run git commit right now." Derived from git-diff and git-show.

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

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

git add <file>

Stage only some of a file’s changes

git add -p

Add a forgotten file to the last (unpushed) commit

git add <file> then git commit --amend

Fix the last (unpushed) commit’s message

git commit --amend -m "…​"

Discard unstaged changes to a file (back to the staged version)

git restore <file>

Discard all uncommitted changes to a file (back to the last commit)

git restore --source=HEAD --staged --worktree <file>

Unstage a file, keep its changes

git restore --staged <file>

Undo a commit that is already pushed/shared

git revert <sha>

Undo local, unpushed commits but keep the changes staged

git reset --soft <sha>

Undo local, unpushed commits and unstage the changes

git reset --mixed <sha> (or git reset <sha>)

Throw away local, unpushed commits and their changes entirely

git reset --hard <sha>

On GitHub.com

Editing a file directly in the browser also creates a commit — useful for small fixes without cloning locally:

  1. Navigate to the file in the repository and click the pencil (edit) icon.

  2. Make the change in the web editor; use the Preview tab to check rendered Markdown/AsciiDoc.

  3. Click Commit changes…​ (top right of the editor), write a commit message (subject and, optionally, an extended description).

  4. Choose the target:

    • Commit directly to the main branch — 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.

  5. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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).

  5. 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.

  6. 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 to git reset, and therefore subject to the same history-rewriting warning above).

In VS Code

  1. Open the Source Control view (Ctrl+Shift+G on Windows/Linux, ⌃⇧G on macOS) — it lists changes under Changes (unstaged) and Staged Changes.

  2. 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 to git add -p.

  3. Type the commit message in the box at the top of the view, then click the checkmark (Commit) button.

  4. 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).

  5. 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.

Common mistakes / tips

  • Forgetting that git commit -a never stages new files — an untracked file silently stays out of the commit until you git add it explicitly.

  • Amending or hard-resetting a commit that a teammate has already pulled: use git revert instead 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 --oneline should read like a changelog on its own.

  • Confusing git diff (working tree vs. staging area) with git 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 --hard to "clean up" without first checking git log for commits that only exist locally — once discarded this way, their content is not recoverable through the normal working tree.