15%

Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code:

Skills
Get Started
22.10.2024

Git Reset vs. Git Checkout vs. Git Revert: A Complete Technical Guide

Understanding the difference between git reset, git checkout, and git revert is essential for any developer working with version control. In short: git reset rewrites history by moving the HEAD pointer; git checkout navigates between branches, commits, or files without altering history; and git revert undoes a commit by creating a new inverse commit, leaving history intact. Choosing the wrong command — especially on a shared branch — can corrupt your team's commit history or cause irreversible data loss.

This guide goes beyond the surface-level syntax to explain the internal Git mechanics, the exact state of your working tree and index after each operation, and the real-world scenarios where each command is the correct (or catastrophically wrong) choice.

How Git Manages State: The Three Trees

Before comparing commands, you need a firm mental model of how Git tracks state. Git operates across three distinct layers:

  • Working directory — the files you see and edit on disk.
  • Staging area (index) — a snapshot of what will go into the next commit.
  • Commit history (HEAD) — the chain of immutable commit objects stored in .git/.

Every undo command in Git targets one or more of these layers. The confusion between reset, checkout, and revert almost always stems from not knowing which layers a given command touches.

git reset: Rewriting Local History

git reset moves the current branch's HEAD pointer to a specified commit. Depending on the mode flag you pass, it can also update the index and the working directory. It is a history-rewriting operation and should be treated as destructive when used with --hard.

Reset Modes Explained

git reset --soft <commit>   # Move HEAD only; index and working tree unchanged
git reset --mixed <commit>  # Move HEAD + reset index; working tree unchanged (default)
git reset --hard <commit>   # Move HEAD + reset index + reset working tree
ModeHEAD MovesIndex ResetWorking Tree ResetChanges Preserved
--softYesNoNoStaged and unstaged
--mixedYesYesNoUnstaged only
--hardYesYesYesNone — permanently lost

Practical Use Cases

Squashing commits before a push. If you have three messy work-in-progress commits that have not yet been pushed, git reset --soft HEAD~3 collapses them back into the index so you can recommit as a single clean commit.

Unstaging a file accidentally added. Running git reset HEAD <file> (or git reset without a commit reference) removes a file from the index without touching the working directory — identical in effect to git restore --staged <file> in Git 2.23+.

Recovering from a botched local merge. git reset --hard ORIG_HEAD restores the state that existed immediately before a merge, because Git writes the pre-merge HEAD to ORIG_HEAD automatically.

Critical Pitfall: Pushing After a Reset

If you reset a branch that has already been pushed to a remote and then force-push, every collaborator whose local branch tracks that remote will have a diverged history. This is one of the most common causes of lost commits in team environments. Never run git reset followed by git push --force on a shared branch without explicit team coordination.

# Dangerous on shared branches — use only on private/local branches
git reset --hard HEAD~2
git push --force origin feature/my-branch

git checkout: Navigation Without History Modification

git checkout is a multi-purpose command that switches the working tree to match a branch, a specific commit, or a single file. It does not modify commit history. In Git 2.23 and later, its responsibilities were split into git switch (for branches) and git restore (for files), but git checkout remains fully functional and is still dominant in production environments.

Syntax Reference

git checkout <branch_name>          # Switch to an existing branch
git checkout -b <new_branch>        # Create and switch to a new branch
git checkout <commit_hash>          # Enter detached HEAD state at a specific commit
git checkout -- <file_name>         # Discard working directory changes to a file
git checkout <commit_hash> -- <file> # Restore a single file from a specific commit

Detached HEAD State: What It Actually Means

When you run git checkout <commit_hash>, Git moves HEAD to point directly at a commit object rather than at a branch ref. This is called detached HEAD state. Any commits you make in this state are not reachable from any branch — they are orphaned and will eventually be garbage-collected by Git unless you create a branch to capture them.

git checkout 4f7a2c1          # HEAD now points directly to commit 4f7a2c1
git checkout -b hotfix/patch  # Rescue those commits by creating a branch

A common real-world scenario: a developer checks out an old commit to test a regression, accidentally makes a fix, commits it, and then switches back to main — losing the fix entirely because it was never attached to a branch.

Restoring a Single File from History

One of the most underused forms of git checkout is targeted file restoration:

git checkout HEAD~3 -- src/config/database.php

This pulls database.php as it existed three commits ago directly into your index and working directory, without touching any other file or moving HEAD. It is the surgical equivalent of a full branch switch.

git revert: Safe Undo for Shared History

git revert creates a new commit that applies the inverse diff of a specified commit. The original commit remains in history untouched. This is the only safe undo mechanism for commits that have already been pushed to a shared remote branch, because it does not rewrite history — it extends it.

Syntax Reference

git revert HEAD                  # Revert the most recent commit
git revert <commit_hash>         # Revert a specific commit by hash
git revert HEAD~3..HEAD          # Revert a range of commits (creates multiple revert commits)
git revert -n <commit_hash>      # Stage the revert without committing (--no-commit)

The –no-commit Flag: Batching Reverts

When reverting multiple commits, creating one revert commit per original commit can pollute the log. The -n (or --no-commit) flag stages all the reversals without committing, letting you bundle them into a single revert commit:

git revert -n HEAD~4..HEAD
git commit -m "Revert: roll back broken authentication refactor"

Merge Commit Reverts Require Extra Care

Reverting a merge commit requires specifying the mainline parent with the -m flag, because Git needs to know which side of the merge to treat as the "correct" history:

git revert -m 1 <merge_commit_hash>

Here, -m 1 designates the first parent (typically the branch you merged into) as the mainline. Omitting this flag on a merge commit will cause Git to throw an error. This is a common stumbling block when reverting a bad release merge in CI/CD pipelines.

Side-by-Side Comparison: Reset vs. Checkout vs. Revert

Criterion`git reset``git checkout``git revert`
Modifies commit historyYesNoNo (adds to it)
Affects working directoryWith --hard or --mixedYesYes (via new commit)
Affects staging area (index)Yes (except --soft)Only with file formNo
Safe on shared/remote branchesNoYes (read-only)Yes
Creates a new commitNoNoYes
ReversiblePartially (via ORIG_HEAD)YesYes
Handles merge commitsNoYes (navigation)Yes (with -m)
Git 2.23+ modern equivalentSamegit switch / git restoreSame

When to Use Each Command: Decision Matrix

Use git reset when:

  • You are working on a local, unpushed branch and want to clean up, squash, or discard commits.
  • You need to unstage files before a commit.
  • You want to undo a local merge that has not been shared.
  • You are the sole developer on a feature branch and need to rewrite its history before opening a pull request.

Use git checkout when:

  • You need to switch between branches during active development.
  • You want to inspect the state of the repository at a historical commit without changing anything.
  • You need to restore a single file to its state at a specific commit.
  • You are creating a new branch from a specific point in history.

Use git revert when:

  • A commit has already been pushed to a remote or shared branch.
  • You are working in a team and need to maintain a transparent, auditable history.
  • You need to undo a specific commit that is not the most recent one (non-linear revert).
  • Your project requires compliance or audit trails where history deletion is prohibited.

Advanced Scenario: Recovering from git reset –hard

If you accidentally ran git reset --hard and lost commits, they are not immediately gone. Git's reflog records every position HEAD has pointed to, even after a hard reset:

git reflog
# Output example:
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# 7e8f9a0 HEAD@{1}: commit: Add payment gateway integration
# ...

git reset --hard HEAD@{1}   # Restore to the commit before the accidental reset

The reflog entries expire after 90 days by default (gc.reflogExpire), so this recovery window is not infinite. On a production server or a VPS Hosting environment running a self-hosted Git service like Gitea or GitLab, you should ensure your .git directory is included in regular backup routines precisely because of this expiry.

Hosting Your Git Infrastructure

Running a self-hosted Git server — whether GitLab CE, Gitea, or Gogs — demands reliable storage I/O and consistent uptime. A single corrupted pack file or an interrupted push during a git gc cycle can damage repository integrity. For teams managing multiple repositories, a Dedicated Server provides isolated resources, full root access for fine-tuning Git's core.packedGitWindowSize and pack.threads settings, and the NVMe throughput needed for large monorepos.

For smaller teams or individual developers who need a clean Linux environment to run Git hooks, CI scripts, and deployment pipelines, a VPS with cPanel offers a managed control plane alongside full SSH access — removing the overhead of manual server administration while retaining the flexibility to configure Git server-side hooks and access controls.

If your workflow involves automated deployments triggered by git push, securing your server with a valid SSL Certificate is non-negotiable — both for encrypting webhook payloads and for authenticating HTTPS-based Git remotes without disabling certificate verification.

Key Technical Takeaways

  • git reset --hard is the only command among the three that can cause permanent, unrecoverable data loss if the reflog has expired.
  • git revert is the only command that is safe to use after a git push without requiring a force-push.
  • Detached HEAD state from git checkout <hash> does not delete commits — but any new commits made in that state will be orphaned unless you immediately run git checkout -b <new_branch>.
  • The -n flag on git revert is critical for keeping a clean log when rolling back multiple commits at once.
  • Git 2.23+ splits git checkout into git switch and git restore for clarity — understanding the original command makes both modern alternatives immediately intuitive.
  • Always verify your current branch with git status and git log --oneline -5 before running any undo operation.
  • On shared infrastructure, enforce branch protection rules (GitHub, GitLab, Gitea all support this) to block git push --force on main and release branches at the server level.

Frequently Asked Questions

What is the difference between git reset --soft and git reset --mixed?

Both move HEAD to the specified commit, but --soft leaves your changes staged in the index, while --mixed (the default) also clears the index, leaving changes only in the working directory. Neither touches files on disk.

Can I recover commits after git reset --hard?

Yes, within the reflog expiry window (90 days by default). Run git reflog to find the commit hash of the lost state, then run git reset --hard <hash> or git checkout -b recovery <hash> to restore it.

Why does git revert on a merge commit require the -m flag?

A merge commit has two parents. Git cannot determine which parent's diff to invert without you specifying the mainline. -m 1 tells Git to treat the first parent as the trunk, reverting the changes introduced by the merged branch.

Is git checkout -- <file> the same as git restore <file>?

Functionally yes — both discard unstaged working directory changes to a file by restoring it from the index. git restore was introduced in Git 2.23 as a less ambiguous replacement, but git checkout -- <file> produces identical results on all Git versions.

When should I absolutely never use git reset on a branch?

Never use git reset (especially with --hard or --mixed) on any branch that other developers have already cloned or pulled from. Doing so diverges their local history from the remote, requiring every collaborator to perform a forced reset or re-clone — and risks silently discarding commits that exist only on their machines.

15%

Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code:

Skills
Get Started