Git, CI/CD & GitOpsXV · ResetModes
Soft reset — moving HEAD and keeping staged changes
What you'll learn
- Predict the state of HEAD, the index, and the working tree after `git reset --soft <commit>`
- Use `git reset --soft HEAD~1` to fix the most recent commit message
- Use `git reset --soft HEAD~1` to add a missed file to the most recent commit
- Use `git reset --soft` to split a commit into multiple commits
- Recognise why --soft is safe on shared branches when combined with a follow-up push
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
git reset --soft <commit> is the precise undo: it moves HEAD
back to <commit> and stops there. The index and the working
tree are not touched, which means the changes from the commits
that were just “undone” are still staged in the index, ready to
be re-committed with a new message, a new author, or a different
shape. The branch tip rewinds; the work does not. This is the
mode for “I committed too early” and “I committed the wrong
thing into the same commit” — the two most common commit-level
mistakes on an infrastructure repository.
What —soft does, precisely
A --soft reset against a target commit performs exactly one
operation: the current branch pointer is rewritten to point at
<commit>. The index continues to hold whatever it held before
the reset. The working tree continues to hold whatever it held
before the reset. The net visible effect is that the commits
that used to be between <commit> and the previous tip have
disappeared from the branch tip but the changes from those
commits are still staged for a new commit.
COMMIT=abc1234
git reset --soft $COMMIT
git status
# On branch main
# Changes to be committed:
# modified: terraform/main.tf
# modified: ansible/roles/web.yaml
git log --oneline -5
# the intervening commits are gone from the log
The commits are not deleted. They are unreachable from any
branch, so git log does not show them, but they still exist as
objects in .git/objects/ and they are still recorded in the
reflog. A subsequent git reset --soft <orphan-oid> brings them
back as the branch tip.
Use case 1: fix the last commit message
The most common commit mistake is a typo in the commit message.
The fix is --soft HEAD~1 followed by git commit -m (or, if
only the message needs to change, git commit --amend directly):
git reset --soft HEAD~1
git status
# staged: the changes from the undone commit
git commit -m 'feat(terraform): correct subnet CIDR in vpc module'
Note that --soft HEAD~1 and --amend are not equivalent:
--soft HEAD~1 moves the branch tip back and leaves the changes
staged, while --amend rewrites the most recent commit in place.
For “fix the message only”, --amend is one step; for “fix the
message and add a missed file”, --soft HEAD~1 followed by
git add and git commit is the cleaner sequence.
Use case 2: add a missed file to the last commit
The second most common mistake is “I forgot to git add a file
before committing”. The commit is one file short of what was
intended, and the engineer wants the file in the same logical
change, not as a separate commit:
git reset --soft HEAD~1
git add terraform/missed.tf
git commit -m 'feat(terraform): include missed.tf in the change'
Because the reset left the original commit’s changes staged,
git status shows them as “Changes to be committed”. Adding the
missed file is a single git add; the resulting git commit
produces a single new commit whose tree contains both the
original changes and the missed file.
gitGraph
commit id: "parent"
commit id: "old tip (missing file)"
commit id: "new tip (with file)" tag: "HEAD"
Use case 3: split a commit
The third use case is “I committed three changes into one commit
and they should have been three separate commits”. The fix is
--soft HEAD~1 followed by a partial unstage and three separate
commits:
git reset --soft HEAD~1
git restore --staged terraform/a.tf ansible/b.yaml # unstage everything
git add terraform/a.tf
git commit -m 'feat(terraform): change A'
git add ansible/b.yaml
git commit -m 'feat(ansible): change B'
The --soft reset moves HEAD back and leaves all three files
staged. git restore --staged unstages them all. From there,
git add and git commit produce three independent commits in
the order the engineer wants.
Use case 4: stage a missed change into an older commit
The fourth use case is rarer but important: an engineer notices
that a change belongs in an earlier commit, not in a new commit.
The fix is an interactive rebase with edit, not --soft,
because --soft HEAD~N rewinds too far and re-stages everything
between <commit> and HEAD, which is more state than the
engineer wants to manage.
The --soft mode is the right tool for the last commit. For an
older commit, the right tool is git rebase -i HEAD~N with the
edit action, which lets the engineer rewrite one commit in
isolation without disturbing the commits around it.
—soft does not touch the working tree
The single most important property of --soft is that the
working tree is untouched. Uncommitted edits, untracked files,
build artifacts in .gitignore paths — all survive a --soft
reset unchanged. This is the property that distinguishes --soft
from --hard and makes --soft the safe mode for “I want to
undo the commit, not the work”.
Production discipline
--softis the local-only undo for the last commit. The changes survive, the message can be corrected, the commit can be split, and nothing is lost. The cost is that the branch tip is rewritten, so the mode is unsafe on a pushed branch.- Combine
--softwith--force-with-leaseon push. When a--softreset is followed by a force-push,--force-with-leaserefuses the push if the remote branch has moved since the last fetch — a defence against clobbering a teammate’s commit. - For older commits, reach for
git rebase -iinstead.--softrewinds from HEAD;rebase -ilets the engineer rewrite any commit in isolation without disturbing the commits around it.
Cross-course references
- Git, CI/CD & GitOps — Part XI (Rebase) — for rewinding
commits older than HEAD,
git rebase -iwith theeditaction is the right tool;--softis right only for HEAD. - Git, CI/CD & GitOps — Part VI (Resetting and restore) —
the index-level version of the same pattern;
--softis the commit-level extension. - Git, CI/CD & GitOps — Part XIV (Revert) — when the commit is already on a shared branch, revert is the alternative to reset that preserves history.
- Ansible for Production Sysadmins — Part XXXVII
(RepositoryArchitecture) — the production rule that the
default branch is sacred applies here too:
--softrewrites are local-only until they survive a--force-with-leasepush.
Quiz
Knowledge check · 4 questions
Q1. An engineer has just committed a single Terraform change and wants to amend the commit message without losing the staged files. Which sequence preserves the staged state?
Q2. `git reset --soft HEAD~1` rewrites the working tree bytes for any file that was modified in the undone commit.
Q3. Name the four canonical use cases for `git reset --soft HEAD~1` on a local unpushed branch.
Q4. Recover from a commit that bundled three independent changes when the intent was three separate commits, using `git reset --soft`.
An engineer committed three Terraform changes — a subnet CIDR fix, a security group rule addition, and a tag rename — into a single commit titled 'misc fixes'. The team lead has asked for the changes to be split into three commits with meaningful messages, one per concern. The branch is local and has not been pushed.
Passing score: 75%. Answers are checked in this browser.