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
# 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/mainOptions
| Mode | Moves branch | Resets index | Resets working tree |
|---|---|---|---|
| --soft | yes | no | no |
| --mixed (default) | yes | yes | no |
| --hard | yes | yes | yes |
| --keep | yes | yes | keeps local edits, aborts if they conflict |
| --merge | yes | yes | keeps 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.
- 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