Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXV · ResetModes

Mixed reset — moving HEAD and resetting the index

Advanced⏱ ~20 mingit

What you'll learn

  • Predict the state of HEAD, the index, and the working tree after `git reset --mixed <commit>`
  • Use `git reset` (with no flag) to unstage everything while preserving working tree bytes
  • Use `git reset <commit> -- <path>` to unstage a single file or path
  • Distinguish `git reset --mixed` from `git restore --staged` for the index-rewind operation
  • Recognise why --mixed is the right default for accidental `git add`

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 --mixed <commit> is the mode that gets the most use and the least credit. It is the default — git reset <commit> is git reset --mixed <commit> — and it does the operation behind the most common staging mistake: “I added a file I should not have added”. The branch tip moves to <commit>, the index is rewritten to match, and the working tree is untouched. The bytes on disk survive; the staging decision is reversed; the engineer can re-stage deliberately without losing the work.

What —mixed does, precisely

A --mixed reset performs two operations:

  1. The current branch pointer is rewritten to point at <commit> (same as --soft).
  2. The index is rewritten so every entry matches <commit>’s tree.

The working tree is not changed. The net visible effect is that the branch tip has moved and the staging area is now consistent with the new tip; the bytes the engineer added, edited, or removed in the working tree are still in the working tree, either as unstaged modifications or as their original contents.

COMMIT=abc1234
git reset $COMMIT          # default mode is --mixed
git status
# On branch main
# Changes not staged for commit:
#   modified:   terraform/main.tf
git log --oneline -5
# tip is now $COMMIT; the intervening commits are gone from history

The index is now empty for any file that has the same tree entry as <commit>. The working tree may still show files as “modified” (if the working tree bytes differ from <commit>’s tree). The branch pointer has been rewound.

Use case 1: unstage everything

The most common staging mistake is git add . followed by “I added too much”. The fix is one command:

git restore --staged .
# equivalent to:
git reset

git reset with no ref defaults to --mixed against HEAD, which is “reset the index to match HEAD”. After the command, every file that was staged is now unstaged, and the working tree bytes are unchanged. The engineer can re-stage deliberately with git add <path> per file.

gitGraph
    commit id: "tip" tag: "HEAD"
    commit id: "parent"

The state before and after the reset is:

TreeBeforeAfter
HEADtipunchanged
Indexmixed (some staged, some not)matches HEAD
Working treesome editsunchanged

The working tree is the same in both columns. The index has been cleared to match HEAD. The branch tip is unchanged.

Use case 2: unstage a single file

The second use case is “I added one file I should not have added”. The fix:

git restore --staged terraform/backend.tf
# equivalent to:
git reset HEAD -- terraform/backend.tf

git reset HEAD -- <path> is the path-scoped form. Note the double dash: it separates the commit argument (here HEAD) from the path arguments. With no double dash, Git parses the path as a ref and complains. The path-scoped form is one of the safest tools in Git because it acts on exactly one path and never moves HEAD.

—mixed versus git restore —staged

The two commands are exact synonyms for the unstage operation:

git restore --staged $FILE_PATH
git reset HEAD -- $FILE_PATH     # equivalent

The difference is style. git restore was added in Git 2.23 (2019) to give the operation a name that does not overlap with the four other things git reset can do. For a workflow that uses git restore for working-tree operations and git switch for branch operations, the unstage operation should also be git restore --staged. For a workflow that has not migrated, git reset HEAD -- <path> is fine.

The --mixed mode itself remains the right tool for “rewind the index to a specific commit’s tree, not just HEAD”. That operation is not a git restore case because git restore --source=<commit> --staged <path> only writes one path at a time and is awkward for the “reset everything” case.

—mixed is the right mode after a wrong branch

A common scenario: an engineer is on main, makes changes, and realises the changes belong on a feature branch. The fix is a sequence of --mixed resets and branch operations:

git reset --mixed HEAD              # unstage everything
git stash                           # move unstaged changes to the stash
git switch feature/new-thing        # create or switch to the feature branch
git stash pop                       # bring the changes onto the feature branch

The first step is --mixed because the working tree must survive intact. The second step stashes the unstaged working tree changes. The third step switches branches. The fourth step brings the changes back. At no point is the working tree overwritten.

What —mixed does NOT do

Three things --mixed does not do, and they are the boundaries of the mode:

  1. It does not touch the working tree. Uncommitted edits survive.
  2. It does not rewrite the branch pointer to a commit that has not been named explicitly. git reset with no ref defaults to HEAD, which is the “reset the index to match HEAD” case, not a history rewind.
  3. It does not delete untracked files. Untracked files are untouched because they were never in the index.

Production discipline

  1. git reset without a flag is the safe default. It is --mixed, it does not touch the working tree, and it is the right answer for most accidental git add cases.
  2. Prefer git restore --staged in new workflows. The newer porcelain gives the unstage operation a name that does not overlap with HEAD-movement, branch-pointers, and history rewrites.
  3. Verify with git status after every reset. A reset that changes the index shows the affected files as unstaged; the working tree files are unchanged. If the working tree files are different, the mode was --hard, not --mixed.

Cross-course references

  • Git, CI/CD & GitOps — Part II (Three trees model) — the HEAD/index/working-tree model that --mixed operates on.
  • Git, CI/CD & GitOps — Part VI (Resetting and restore) — the index-level operations and the git restore introduction that this lesson extends.
  • Git, CI/CD & GitOps — Part XIV (Revert) — for a commit that has been pushed, git revert is the additive alternative that does not require a reset of any kind.
  • Linux for Production Sysadmins — Part XII (Filesystem hierarchy) — the analogue is a soft unmount: the layers are preserved, only the binding changes.

Quiz

Knowledge check · 4 questions

  1. Q1. An engineer runs `git reset` with no mode flag and no commit argument. What is the resulting state?

  2. Q2. `git reset --mixed <commit>` rewrites the working tree bytes for every file whose tree entry differs between `<commit>` and the current index.

  3. Q3. What is the default mode of `git reset` when no mode flag is given, and what is the equivalent `git restore` command for unstaging everything?

  4. Q4. Recover from an accidental `git add .` that staged untracked files which should not be in the next commit, using `git reset` and `git restore`.

    An engineer ran `git add .` from the root of an infrastructure repository and accidentally staged build artifacts, editor backups, and a credentials file. The engineer wants to unstage everything without losing any working tree edits and without disturbing the most recent commit on `main`.

Passing score: 75%. Answers are checked in this browser.