Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXV · ResetModes

Soft reset — moving HEAD and keeping staged changes

Advanced⏱ ~18 mingit

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

Not yet marked complete on this device.

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

  1. --soft is 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.
  2. Combine --soft with --force-with-lease on push. When a --soft reset is followed by a force-push, --force-with-lease refuses the push if the remote branch has moved since the last fetch — a defence against clobbering a teammate’s commit.
  3. For older commits, reach for git rebase -i instead. --soft rewinds from HEAD; rebase -i lets 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 -i with the edit action is the right tool; --soft is right only for HEAD.
  • Git, CI/CD & GitOps — Part VI (Resetting and restore) — the index-level version of the same pattern; --soft is 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: --soft rewrites are local-only until they survive a --force-with-lease push.

Quiz

Knowledge check · 4 questions

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

  2. Q2. `git reset --soft HEAD~1` rewrites the working tree bytes for any file that was modified in the undone commit.

  3. Q3. Name the four canonical use cases for `git reset --soft HEAD~1` on a local unpushed branch.

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