Skip to content
LatchkeyLatchkey home

git reset --soft/--mixed/--hard: The Three Modes

git reset moves the branch tip; --soft keeps the index and working tree, --mixed (default) resets the index but keeps the working tree, and --hard discards both.

The single most consequential difference in git reset is the mode, because --hard throws away uncommitted work. This page lays out exactly what each mode touches so a CI cleanup step does not delete more than intended.

What it does

git reset <commit> repoints the current branch to <commit>. --soft stops there, leaving your changes staged. --mixed also resets the index to match (changes become unstaged). --hard additionally overwrites the working tree to match the commit, discarding all uncommitted changes.

Common usage

Terminal
# undo last commit, keep changes staged
git reset --soft HEAD~1
# unstage everything but keep edits
git reset --mixed HEAD     # or just: git reset
# discard all local changes and match origin/main exactly
git reset --hard origin/main

Options

ModeMoves branchResets indexResets working tree
--softyesnono
--mixed (default)yesyesno
--hardyesyesyes
--keepyesyeskeeps local edits, aborts if they conflict
--mergeyesyeskeeps unmerged changes, resets the rest

In CI

git reset --hard origin/<branch> is the standard way to force a runner workspace to an exact state, but it silently destroys uncommitted artifacts; pair it with git clean to remove untracked files too. After --hard, the prior commits are still recoverable from the reflog (on a non-fresh clone) until gc runs.

Common errors in CI

"fatal: ambiguous argument 'origin/main'" means the ref was not fetched (shallow or missing remote-tracking ref). "error: Entry '<file>' not uptodate. Cannot merge." appears with --keep/--merge when local edits conflict. On a detached HEAD, reset moves HEAD itself, not a branch, which can surprise scripts expecting a branch to move.

Using this in CI

CI checkouts are shallow and detached by default, which changes the answer this command gives you. Commands that read history, branch names, or tags need the checkout configured for it.

.github/workflows/ci.yml
- uses: actions/checkout@v4
  with:
    fetch-depth: 0   # history, tags, and git describe all need this

- run: |
    git rev-parse --is-shallow-repository   # expect false
    git rev-parse --abbrev-ref HEAD          # prints HEAD when detached

Frequently asked questions

git reset --soft/--mixed/--hard: The Three Modes?
The single most consequential difference in git reset is the mode, because --hard throws away uncommitted work. This page lays out exactly what each mode touches so a CI cleanup step does not delete more than intended.
What it does?
git reset <commit> repoints the current branch to <commit>. --soft stops there, leaving your changes staged. --mixed also resets the index to match (changes become unstaged). --hard additionally overwrites the working tree to match the commit, discarding all uncommitted changes.
In CI?
git reset --hard origin/<branch> is the standard way to force a runner workspace to an exact state, but it silently destroys uncommitted artifacts; pair it with git clean to remove untracked files too. After --hard, the prior commits are still recoverable from the reflog (on a non-fresh clone) until gc runs.
Common errors in CI?
"fatal: ambiguous argument 'origin/main'" means the ref was not fetched (shallow or missing remote-tracking ref). "error: Entry '<file>' not uptodate. Cannot merge." appears with --keep/--merge when local edits conflict. On a detached HEAD, reset moves HEAD itself, not a branch, which can surprise scripts expecting a branch to move.

Related guides

References

Run this faster and cheaper on Latchkey managed runners - self-healing included. Start free → 30-day trial · No credit card