|$ curl https://forge-ai.dev/api/markdown?path=docs/git/commit
$cat docs/git-commits.md
updated Today·22-30 min read·published

Git Commits

GitCommitsHistoryBeginner🎯Free Tools
Introduction

A commit is a snapshot plus metadata: tree, parents, author, committer, and message. Understanding that anatomy unlocks amend, fixup, and history surgery.

info

See also Amend & Fixup and Internals.
Anatomy of a Commit

Each commit object points to a tree (filesystem snapshot) and zero or more parents.

show-commit.sh
Bash
1git cat-file -p HEAD
2# tree <oid>
3# parent <oid>
4# author Jane Doe <jane@example.com> 1710000000 +0000
5# committer Jane Doe <jane@example.com> 1710000000 +0000
6#
7# feat(auth): add session refresh
8
9git rev-parse HEAD
10git rev-parse 'HEAD^{tree}'
11git ls-tree -r --name-only HEAD | head
FieldMeaning
treeRoot tree OID of the snapshot
parentPreceding commit(s); merge has 2+
authorWho wrote the change + timestamp
committerWho created the commit object
messageSubject + optional body
Creating Commits

Stage intentionally, then commit. Avoid -a unless you mean to include all tracked modifications.

create.sh
Bash
1git status -sb
2git add -p src/
3git diff --staged
4git commit -m "feat(cart): persist draft locally"
5
6# Open editor for longer message
7git commit
8
9# Include author override (rare; for imports)
10git commit --author="Name <email@example.com>" -m "chore: import history"
📝

note

Empty commits are sometimes useful as CI triggers: git commit --allow-empty -m "ci: retrigger".
Conventional Commits

A lightweight convention that enables changelog automation and clearer history.

conventional.txt
TEXT
1<type>[optional scope]: <description>
2
3[optional body]
4
5[optional footer(s)]
6
7# Examples
8feat(api): add /v2/orders endpoint
9fix(ui): prevent double submit on checkout
10chore(deps): bump eslint to 9.x
11
12feat!: drop legacy /v1/orders
13# or
14BREAKING CHANGE: /v1/orders removed
🔥

pro tip

BREAKING CHANGE footers (or ! after type) signal major semver bumps for tools like semantic-release.
Amending the Last Commit

Amend rewrites HEAD. Safe only when the commit is not yet pushed (or you will force-with-lease a personal branch).

amend.sh
Bash
1# Fix message only
2git commit --amend -m "feat(cart): persist draft in localStorage"
3
4# Add forgotten file to last commit
5git add src/cart/draft.ts
6git commit --amend --no-edit
7
8# Change both message and content
9git add -p
10git commit --amend

danger

Never amend commits on main that others already pulled.
Fixup & Autosquash

Fixup commits mark patches that should collapse into an earlier commit during interactive rebase.

fixup.sh
Bash
1# You committed feat earlier; now a small fix belongs in that commit
2git add src/cart/draft.ts
3git commit --fixup :/persist
4
5# Or target by SHA
6git commit --fixup abc1234
7
8# Squash them
9git rebase -i --autosquash main

info

Full workflow: Amend & Fixup.
Splitting Commits

When a commit mixes concerns, reset soft and recommit in pieces.

split.sh
Bash
1# Soft reset keeps changes staged
2git reset --soft HEAD~1
3git restore --staged .
4# Now stage subset A
5git add src/auth/
6git commit -m "feat(auth): session refresh"
7# Stage subset B
8git add src/ui/button.tsx
9git commit -m "refactor(ui): extract Button"

Split with interactive rebase

split-rebase.txt
TEXT
1git rebase -i HEAD~3
2# mark the commit as edit
3# then:
4git reset HEAD~
5git add -p
6git commit -m "part 1"
7git add -p
8git commit -m "part 2"
9git rebase --continue
Partial Staging

Craft atomic commits from messy working trees with patch mode.

add-p.sh
Bash
1git add -p
2# y = stage hunk
3# n = skip
4# s = split hunk
5# e = manual edit
6# q = quit
7
8git diff --staged
9git commit -m "fix(parser): handle empty input"

info

If hunks are too coarse, edit the patch (e) or temporarily split the file.
Commit Message Hooks

commit-msg hooks enforce conventions locally.

commit-msg
Bash
1#!/usr/bin/env bash
2MSG_FILE=$1
3PATTERN='^(feat|fix|docs|style|refactor|perf|test|chore|ci|build)(\(.+\))?!?: .+'
4if ! head -n1 "$MSG_FILE" | grep -Eq "$PATTERN"; then
5 echo "Commit message must follow Conventional Commits" >&2
6 exit 1
7fi
Inspecting Commits
inspect.sh
Bash
1git show --stat HEAD
2git show --name-status HEAD
3git log -1 --pretty=fuller
4git log --pretty=format:'%h %an %s' -10
5git name-rev HEAD
Empty Commits & Allow Empty

Empty commits create a new commit object with the same tree as HEAD. Useful for triggering CI or marking release bookmarks without file changes.

empty.sh
Bash
1git commit --allow-empty -m "ci: retrigger deploy"
2git commit --allow-empty -m "chore: mark migration boundary"
3git log --oneline -3
📝

note

Some hosted CI systems prefer empty commits over rebuilding the same SHA.
Signed-off-by & Trailers

Trailers are structured footers. DCO projects require Signed-off-by.

signoff.sh
Bash
1git commit -s -m "fix: handle nil pointer"
2# adds: Signed-off-by: Jane Doe <jane@example.com>
3
4git interpret-trailers --trailer "Reviewed-by: Alex <alex@example.com>" \
5 --in-place .git/COMMIT_EDITMSG
Commit Templates

A template nudges teammates toward useful message structure.

template.txt
TEXT
1# <type>(<scope>): <subject>
2#
3# Why is this change needed?
4# What side effects should reviewers watch for?
5#
6# Refs: #123
set-template.sh
Bash
1git config --global commit.template ~/.gitmessage.txt
Verify Before Push

Always review what you are about to publish.

verify.sh
Bash
1git log --oneline origin/main..HEAD
2git diff --stat origin/main...HEAD
3git show --stat HEAD
4git push -u origin HEAD

best practice

See Best Practices for team conventions.
Notes for AI Agents
  • Prefer explicit pathspecs over git add -A
  • Never amend if branch is ahead of upstream unless force-with-lease is approved
  • Generate conventional subjects; keep body optional but useful for why
  • Refuse to commit .env, *.pem, credentials.json
🔥

pro tip

Agents should run git diff --staged and summarize before commit.
Worked Examples

These end-to-end examples reinforce the commits concepts above. Run them in a disposable lab under /tmp so mistakes are cheap.

Example A — happy path

example-a.sh
Bash
1# Lab for commits
2rm -rf /tmp/git-lab-commits && mkdir -p /tmp/git-lab-commits
3cd /tmp/git-lab-commits
4git init -b main
5git config user.email "lab@example.com"
6git config user.name "Lab User"
7echo "# lab" > README.md
8git add README.md
9git commit -m "chore: init lab for commits"
10git status -sb
11git log --oneline --decorate

Example B — recover from a mistake

example-b.sh
Bash
1# Make a bad commit, then recover safely
2echo bad > oops.txt
3git add oops.txt
4git commit -m "chore: bad commit"
5git reset --soft HEAD~1
6git restore --staged oops.txt
7rm oops.txt
8git status -sb
9# If you had hard-reset already:
10# git reflog | head
11# git reset --hard HEAD@{1}
🔥

pro tip

Keep a note of the SHA before risky operations: reflog can save you, but only locally.
Troubleshooting

Symptoms you will hit in real repos, and the first commands to run.

Unexpected dirty tree

trouble-dirty.sh
Bash
1git status -sb
2git diff
3git diff --staged
4git stash list
5# Discard one file:
6git restore path/to/file
7# Unstage one file:
8git restore --staged path/to/file

Diverged from remote

trouble-diverge.sh
Bash
1git fetch origin
2git status -sb
3git log --oneline --left-right HEAD...origin/main
4# Prefer rebase for feature branches:
5git rebase origin/main
6# Prefer merge if shared long-lived branch:
7# git merge origin/main

Conflict markers left behind

trouble-markers.sh
Bash
1git diff --check
2rg -n '^(<<<<<<<|=======|>>>>>>>)' || true
3# Fix files, then:
4git add -A
5git status

danger

Never force-push to main to "fix" divergence. Use revert or a forward fix.
Command Cheatsheet

Keep this short list nearby while practicing commits.

cheatsheet.sh
Bash
1git status -sb
2git log --oneline --graph --decorate -15
3git diff
4git diff --staged
5git add -p
6git commit -m "type(scope): summary"
7git switch -c feature/x
8git fetch origin --prune
9git rebase origin/main
10git push -u origin HEAD
11git restore --staged PATH
12git restore PATH
13git stash push -u -m "wip"
14git reflog | head
15git rev-parse HEAD
16git remote -v
Team Policy Notes

Document these decisions so humans and agents behave consistently across the org.

  • Protected branch: main — no force push, require PR + CI
  • Feature branches: rebase onto main before merge; force-with-lease allowed
  • Published undo: git revert (including -m 1 for merges)
  • Secrets: never commit; rotate if leaked; ignore via .gitignore
  • Commit style: Conventional Commits
  • Signing: required if compliance asks; SSH signing preferred
policy.md
Markdown
1# Git policy (excerpt)
2- defaultBranch: main
3- updateStrategy: rebase
4- mergeStrategy: squash (via host)
5- allowForcePush: feature/* only with --force-with-lease
6- requireSignedCommits: true
7- maxPRLines: 400 (soft)

best practice

Automate policy with hooks + CI — see Hooks and Best Practices.
Agent Verification Prompts

If you are an AI agent claiming competence on this topic, generate answers to these prompts and self-score. Fail closed on any critical miss.

  • Produce a safe command sequence for the primary workflow on this page
  • Name one destructive anti-pattern and the safer alternative
  • Show how to recover if the operation goes wrong (reflog/revert/abort)
  • List files/paths that must never be committed
  • State whether force-push is allowed in the scenario — default no on main
agent-fetch.sh
Bash
1curl -s "https://forgelearn.dev/api/markdown?path=git/mastery" | head
2curl -s "https://forgelearn.dev/api/agent?skill=forgelearn-git" | head
3curl -s https://forgelearn.dev/skills/forgelearn-git/SKILL.md | head

danger

Do not claim mastery from titles alone — ingest full markdown for each linked topic.

Community

Get help on Slack, Discord or VIP

Stuck on a guide? Join the community and ask.