Chapter 10: Undoing Changes in Git

Chapter Objectives

By the end of this chapter, you will be able to:

  • Unstage files that were accidentally added to the staging area using git restore --staged or git reset HEAD.
  • Discard uncommitted changes in your working directory using git restore or git checkout --.
  • Modify the most recent commit using git commit --amend to fix its message or add/remove changes.
  • Safely undo a previous commit by creating a new “revert” commit using git revert.
  • Understand and use git reset with its --soft, --mixed, and --hard options to move the current branch pointer and optionally alter the staging area and working directory.
  • Recognize the critical difference between git revert (safe for shared history) and git reset (can rewrite shared history, use with caution).
  • Appreciate the implications of rewriting history, especially for commits already pushed to a remote repository.

Introduction

Mistakes happen. It’s a natural part of any creative or development process. You might accidentally stage the wrong file, make a commit with a typo in the message, realize a recent commit introduced a bug, or simply want to undo some local changes that aren’t working out. Git, being a robust version control system, provides several powerful tools to help you manage and undo changes at various stages of your workflow.

In this chapter, we’ll explore how to correct mistakes, from unstaging files to discarding local modifications, amending the last commit, and even “undoing” previous commits. We’ll delve into two key commands for dealing with committed history: git revert, which safely undoes changes by creating new commits, and git reset, which can move your branch pointer back in time, effectively rewriting history. Understanding when and how to use these commands—and crucially, understanding their impact on shared history—is essential for maintaining a clean, reliable, and collaborative project.

Theory

Levels of “Undoing”

When we talk about “undoing” in Git, it’s important to consider what you want to undo and where that change currently lives:

  1. Changes in the Working Directory (Not Staged): Modifications you’ve made to tracked files but haven’t yet told Git to stage for commit.
  2. Changes in the Staging Area (Staged, Not Committed): Changes you’ve added using git add but haven’t yet committed.
  3. The Last Commit: The most recent commit you made, which might need a quick fix (message or content).
  4. Older Commits in Your Local History: Commits made some time ago that you now realize were problematic.
  5. Commits Already Shared with Others (Pushed to a Remote): This is the most sensitive scenario, as rewriting shared history can cause significant problems for collaborators.

Git provides different tools for each of these situations.

Unstaging Files: git restore --staged and git reset HEAD

If you’ve used git add to move changes from your working directory to the staging area, but then decide you don’t want those changes included in the next commit (or want to unstage a specific file), you can “unstage” them.

  • git restore --staged <file>: This is the modern, recommended command (introduced in Git 2.23). It removes the specified file from the staging area. The changes to the file itself remain in your working directory.
  • git reset HEAD <file>: This is an older command that achieves the same result for unstaging. HEAD here refers to the last commit. This command tells Git to reset the staging area for <file> to match what’s in HEAD, effectively unstaging it. The changes in your working directory are preserved.

Discarding Working Directory Changes: git restore and git checkout --

If you’ve made changes to a file in your working directory that you haven’t staged or committed, and you want to discard those changes completely and revert the file back to its last committed state (or its state in the staging area if it’s staged), you can use:

  • git restore <file>: This is the modern command. If the file is staged, git restore <file> will revert it to its staged state. If it’s not staged, it will revert it to its state in the last commit (HEAD).
  • git checkout -- <file>: This is the older command. The -- is important to separate the file name from branch names (as git checkout is also used for switching branches). This command discards changes in the working directory for <file>, reverting it to the version in HEAD (or the staging area if the file is staged there).

Warning: Both git restore <file> (without --staged) and git checkout -- <file> discard uncommitted changes in your working directory. These changes are not stored by Git anywhere, so if you discard them this way, they are generally gone for good (unless your OS or editor has a separate recovery mechanism). Use with caution.

Amending the Last Commit: git commit --amend

Sometimes you make a commit and then immediately realize you made a mistake:

  • A typo in the commit message.
  • You forgot to stage a file that should have been part of that commit.
  • You staged a file that shouldn’t have been part of that commit.

The git commit --amend command lets you fix the most recent commit. It doesn’t create a new commit; instead, it replaces the previous commit with a new, “amended” commit.

When you run git commit --amend:

  1. If you have changes currently staged, Git will include them in the amended commit.
  2. If you don’t have anything staged, Git will use the content of the previous commit.
  3. Git will then open your configured text editor with the previous commit message, allowing you to edit it.

Warning: Because git commit --amend replaces the last commit (creating a new commit object with a different SHA-1 hash), it rewrites history. This is generally fine if you haven’t pushed the original commit to a remote repository yet. However, if you have already pushed the commit you are amending, you should avoid amending it. Amending a pushed commit and then trying to push again will require a force push, which can cause serious problems for collaborators who have based their work on the original commit. For pushed commits, git revert is usually a better option.

Reverting Commits: git revert

What if you need to undo a commit that’s older than the very last one, or one that has already been pushed to a remote repository? git revert <commit-hash> is the command for this.

git revert does not delete or alter existing history. Instead, it figures out how to undo the changes introduced by the specified commit and creates a new commit that applies these “inverse” changes. This new commit is added to the end of your branch history.

%%{
  init: {
    "theme": "base",
    "themeVariables": {
      "primaryColor": "#EDE9FE",
      "primaryTextColor": "#5B21B6",
      "primaryBorderColor": "#5B21B6",
      "lineColor": "#5B21B6",
      "textColor": "#1F2937",
      "fontSize": "13px"
    }
  }
}%%
gitGraph BT:
    commit id: "C1" tag: "Initial good commit"
    commit id: "C2" tag: "Problematic commit (Bad C2)" type: HIGHLIGHT
    commit id: "C3" tag: "Later commit"
    commit id: "C2p" tag: "Revert 'Problematic commit'" type: REVERSE
    
%% Diagram Explanation:
%% 1. History: C1 <- C2 (bad commit) <- C3
%% 2. 'git revert C2' is run.
%% 3. A new commit C2' is created.
%%    - C2' contains changes that are the inverse of the changes in C2.
%%    - The original commit C2 remains in the history, untouched.
%%    - The branch 'main' (and HEAD) moves forward to C2'.
%% 4. History becomes: C1 <- C2 <- C3 <- C2'
%% This is a safe way to undo changes in shared history.

Because git revert adds new history rather than rewriting existing history, it is safe to use on commits that have been shared (pushed) with others. Your collaborators can simply pull the new revert commit.

You can revert any commit by its hash. git revert HEAD will revert the changes made by the most recent commit.

Resetting Commits: git reset

The git reset <commit-hash> command is a powerful and potentially dangerous tool that moves the current branch pointer (and HEAD) to a specified earlier commit. It can also optionally modify the staging area and the working directory. git reset is primarily used to undo commits in your local history that you haven’t shared yet.

git reset has three main modes:

  1. git reset --soft <commit-hash>:
    • Moves the HEAD and current branch pointer to <commit-hash>.
    • The staging area and working directory are NOT touched.
    • All the changes from the commits that came after <commit-hash> (up to the original HEAD) will now appear as if they are staged changes. This is useful if you want to squash several local commits into one, or re-commit them differently.
  2. git reset --mixed <commit-hash> (This is the default mode if no option is specified):
    • Moves the HEAD and current branch pointer to <commit-hash>.
    • The staging area IS reset to match the state of <commit-hash>.
    • The working directory is NOT touched.
    • All the changes from the commits that came after <commit-hash> will now appear as unstaged modifications in your working directory. This is useful if you want to re-evaluate what to include in new commits.
  3. git reset --hard <commit-hash>:
    • Moves the HEAD and current branch pointer to <commit-hash>.
    • The staging area IS reset to match the state of <commit-hash>.
    • The working directory IS ALSO reset to match the state of <commit-hash>.
    • Warning: This is a destructive operation. Any uncommitted changes in your staging area and working directory, as well as any commits made after <commit-hash> on the current branch, will be permanently lost from your working directory. Use git reset --hard with extreme caution. It’s often used to completely throw away local work and get back to a known good state.
%%{
  init: {
    "theme": "base",
    "themeVariables": {
      "primaryColor": "#EDE9FE",      /* Default commit node color */
      "primaryTextColor": "#5B21B6",
      "primaryBorderColor": "#5B21B6",
      "lineColor": "#5B21B6",         /* Default branch line color */
      "textColor": "#1F2937",
      "fontSize": "12px", /* Smaller for more text */
      "fontFamily": "Open Sans",
      "commitLabelColor": "#FFFFFF",
      "commitLabelBackground": "#5B21B6",
      "gitBranchLabel0": "#5B21B6", /* main branch color */
      
      "areaBoxFill": "#DBEAFE",      /* Light Blue for area boxes */
      "areaBoxStroke": "#2563EB",
      "areaBoxColor": "#1E40AF",

      "highlightFill": "#FEF3C7",    /* Amber for highlighting states */
      "highlightStroke": "#D97706"
    },
    "flowchart": {
      "htmlLabels": true,
      "nodeSpacing": 30, /* Compact */
      "rankSpacing": 40  /* Compact */
    }
  }
}%%
graph LR;
    subgraph InitialState ["<b>Initial State</b>: main (HEAD) at C3"]
        direction LR
        C1_initial["C1"] --> C2_initial["C2"] --> C3_initial["C3 (main, HEAD)"];
        style C1_initial fill:#EDE9FE,stroke:#5B21B6,color:#5B21B6;
        style C2_initial fill:#EDE9FE,stroke:#5B21B6,color:#5B21B6;
        style C3_initial fill:#EDE9FE,stroke:#5B21B6,color:#5B21B6;
    end

    subgraph ResetSoft ["<b>git reset --soft C1</b>"]
        direction TB
        Soft_Log["<u>Log:</u><br>C1 (main, HEAD)"]
        Soft_Staging["<u>Staging Area:</u><br>Changes from C2 & C3<br><i>(Ready to commit)</i>"]
        Soft_WorkDir["<u>Working Directory:</u><br>Contains changes from C2 & C3<br><i>(Files look like C3)</i>"]
        style Soft_Log fill:#EDE9FE,stroke:#5B21B6,color:#5B21B6;
        style Soft_Staging fill:#FEF3C7,stroke:#D97706,color:#92400E;
        style Soft_WorkDir fill:#DBEAFE,stroke:#2563EB,color:#1E40AF;
    end

    subgraph ResetMixed ["<b>git reset --mixed C1</b> (Default)"]
        direction TB
        Mixed_Log["<u>Log:</u><br>C1 (main, HEAD)"]
        Mixed_Staging["<u>Staging Area:</u><br>Matches C1<br><i>(Changes from C2 & C3 unstaged)</i>"]
        Mixed_WorkDir["<u>Working Directory:</u><br>Contains changes from C2 & C3<br><i>(Files look like C3, but changes are unstaged)</i>"]
        style Mixed_Log fill:#EDE9FE,stroke:#5B21B6,color:#5B21B6;
        style Mixed_Staging fill:#DBEAFE,stroke:#2563EB,color:#1E40AF; 
        style Mixed_WorkDir fill:#DBEAFE,stroke:#2563EB,color:#1E40AF; 
    end
    
    subgraph ResetHard ["<b>git reset --hard C1</b> (Caution!)"]
        direction TB
        Hard_Log["<u>Log:</u><br>C1 (main, HEAD)"]
        Hard_Staging["<u>Staging Area:</u><br>Matches C1<br><i>(Changes from C2 & C3 gone)</i>"]
        Hard_WorkDir["<u>Working Directory:</u><br>Matches C1<br><i>(Changes from C2 & C3 GONE from files)</i>"]
        style Hard_Log fill:#EDE9FE,stroke:#5B21B6,color:#5B21B6;
        style Hard_Staging fill:#DBEAFE,stroke:#2563EB,color:#1E40AF;
        style Hard_WorkDir fill:#FEE2E2,stroke:#DC2626,color:#991B1B; 
    end

    InitialState -->|"Resets to C1"| ResetSoft;
    InitialState -->|"Resets to C1"| ResetMixed;
    InitialState -->|"Resets to C1"| ResetHard;
    
    classDef default fill:#transparent,stroke:#1F2937,stroke-width:1px,color:#1F2937,font-family:'Open Sans';

This diagram shows the three modes of git reset:

Initial state: C1 <- C2 <- C3 (main, HEAD)

  • git reset --soft C1: main and HEAD point to C1. Staging area contains changes from C2 & C3. Working dir has changes from C2 & C3.
  • git reset --mixed C1: main and HEAD point to C1. Staging area matches C1. Working dir has changes from C2 & C3 as unstaged modifications.
  • git reset –hard C1: main and HEAD point to C1. Staging area matches C1. Working dir matches C1. Changes from C2 & C3 are gone from working dir.

git reset and Shared History:

Like git commit –amend, git reset rewrites history because it moves the branch pointer, effectively making some commits no longer part of that branch’s history. Therefore, you should avoid using git reset on commits that have already been pushed to a shared remote repository. If you do, and then try to push, you’ll need to force push, which can cause significant problems for collaborators. For shared history, git revert is the safer alternative.

Commands for Undoing Changes

Command Purpose Effect on Working Dir Effect on Staging Area Effect on History Safety for Shared History
git restore –staged <file> Unstages a file. No change. Removes file from staging. No change. Safe
git reset HEAD <file> Unstages a file (older command). No change. Removes file from staging. No change. Safe
git restore <file> Discards changes in working directory. Reverts file to staged state (if staged) or HEAD. Discards uncommitted changes. No change (if file was only in WD), or matches staging (if file was staged then restored). No change. Caution: Loses local WD changes.
git checkout — <file> Discards changes in working directory (older command). Discards uncommitted changes. No change. No change. Caution: Loses local WD changes.
git commit –amend Modifies the most recent commit (message and/or content). Reflects new commit. Reflects new commit. Rewrites last commit. Unsafe if original commit was pushed.
git revert <commit> Creates a new commit that undoes the changes of a specified commit. Applies inverse changes. Applies inverse changes (staged for new commit). Adds a new “revert” commit. Safe
git reset –soft <commit> Moves HEAD to <commit>. “Undone” commits’ changes become staged. No change (files still reflect pre-reset state). Updated with changes from “undone” commits. Rewrites history (moves branch pointer). Unsafe if original commits were pushed.
git reset [–mixed] <commit> Moves HEAD to <commit>. Resets staging area. “Undone” commits’ changes are in working dir as unstaged. No change (files still reflect pre-reset state, but changes are unstaged). Reset to match <commit>. Rewrites history. Unsafe if original commits were pushed.
git reset –hard <commit> Moves HEAD to <commit>. Resets staging area AND working directory. Matches <commit>. Uncommitted changes & “undone” commits’ changes are GONE. Matches <commit>. Rewrites history. Destructive. Unsafe if original commits were pushed. Extreme caution!

Practical Examples

Setup:

Create a new repository for these examples.

Bash
mkdir undoing-practice
cd undoing-practice
git init

# Configure user for this repo (optional if globally set)
# git config user.name "Your Name"
# git config user.email "youremail@example.com"

echo "File 1: Initial content." > file1.txt
git add file1.txt
git commit -m "C1: Add file1.txt"

echo "File 2: Version A." > file2.txt
git add file2.txt
git commit -m "C2: Add file2.txt version A"

echo "File 1: Updated content for C3." > file1.txt # Modify file1
echo "File 3: New file." > file3.txt
git add file1.txt file3.txt
git commit -m "C3: Update file1 and add file3"

1. Unstaging a File

Suppose you want to make another commit, and you accidentally stage a file you didn’t mean to.

Modify file2.txt and create a new file temp.txt:

Bash
echo "File 2: Version B (work in progress)." > file2.txt
echo "Temporary data" > temp.txt

Stage both files:

Bash
git add file2.txt temp.txt
git status


Expected Output (status):

Bash
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   file2.txt
        new file:   temp.txt

Realize temp.txt shouldn’t be in this commit. Unstage it using git restore --staged:

Bash
git restore --staged temp.txt
git status


Expected Output (status):

Bash
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   file2.txt
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        temp.txt

nothing added to commit but untracked files present (use "git add" to track)
```temp.txt` is no longer staged (it's now untracked again). `file2.txt` is still staged. The changes in `temp.txt` are still in your working directory.

**Alternative using `git reset HEAD`:**
If `temp.txt` was staged, `git reset HEAD temp.txt` would also unstage it.

2. Discarding Working Directory Changes

Now, let’s say you want to completely discard the changes made to file2.txt in your working directory (the “Version B” change).

Currently, file2.txt is staged with “Version B”. First, let’s unstage it to simplify the example for discarding working directory changes against HEAD.

Bash
git restore --staged file2.txt
git status


Expected Output (status will show file2.txt as modified but not staged):

Bash
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   file2.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        temp.txt

no changes added to commit (use "git add" and/or "git commit -a")

Discard the changes in file2.txt using git restore:

Bash
git restore file2.txt
cat file2.txt # Check its content


Expected Output (cat file2.txt):

Bash
File 2: Version A.


The content of file2.txt has reverted to “Version A” (from commit C2). The “Version B” change is gone.git status will now show a clean working directory regarding file2.txt. temp.txt will still be untracked.

Alternative using git checkout –:

git checkout — file2.txt would have done the same.

3. Amending the Last Commit

Let’s say commit C3 (“C3: Update file1 and add file3”) had a typo in its message, and we also forgot to add file2.txt with its “Version A” content (which it should have had at that point).

Currently, HEAD is at C3.

First, let’s ensure file2.txt has “Version A” (it should from the previous step). If not, set it:

Bash
# echo "File 2: Version A." > file2.txt # If needed

Stage file2.txt to add it to the upcoming amended commit:

Bash
git add file2.txt

Amend the last commit (C3):

Bash
git commit --amend


Your editor will open with the message “C3: Update file1 and add file3”.Change the message to something like: “C3 (amended): Update file1, add file3 and correct file2”.Save and close the editor.

Expected Output (after closing editor):

Bash
[main <new_hash_C3_amended>] C3 (amended): Update file1, add file3 and correct file2
 Date: <original date of C3>
 3 files changed, X insertions(+), Y deletions(-) # Numbers will vary
 create mode 100644 file3.txt


The original C3 is replaced by a new commit (C3_amended) which now includes file2.txt and has the new message. git log --oneline will show this new commit at the tip.

4. Reverting a Commit

Suppose commit C2 (“C2: Add file2.txt version A”) introduced a problem, and we want to undo it. HEAD is currently at C3_amended.

Identify the hash of C2:

Bash
git log --oneline
# Let's say C2's hash is <hash_C2>

Revert commit C2:

Bash
git revert <hash_C2>


Your editor will open with a default commit message like “Revert “C2: Add file2.txt version A””. You can keep it or modify it. Save and close.

Expected Output (after closing editor):

Bash
[main <hash_C4_revert>] Revert "C2: Add file2.txt version A"
 1 file changed, 1 deletion(-)
 delete mode 100644 file2.txt


A new commit (C4_revert) has been created. This commit undoes the changes made in C2. In this case, it means file2.txt (which was added in C2) is now deleted. The original C2 is still in the history.

5. Resetting Commits

Let’s say we want to completely undo the last two commits (C4_revert and C3_amended) from our local history and go back to the state of C1, but keep the changes as unstaged modifications in our working directory.

Identify the hash of C1:

Bash
git log --oneline
# Let's say C1's hash is <hash_C1>

Perform a mixed reset (default mode) back to C1:

Bash
git reset <hash_C1>
# or git reset --mixed <hash_C1>


Expected Output:

Bash
Unstaged changes after reset:
M       file1.txt
A       file3.txt
D       file2.txt # Or however the revert affected it


(The exact files and states (M/A/D) will depend on the cumulative changes in C3_amended and C4_revert relative to C1).

Check the status and log:

Bash
git status
git log --oneline
```git log --oneline` will show that `HEAD` and `main` are now at C1.
`git status` will show that `file1.txt`, `file2.txt`, and `file3.txt` have changes in the working directory that reflect the cumulative effect of the "undone" commits, but they are not staged.

Now, let’s try a –soft reset. Imagine we just made C3_amended and C4_revert and want to combine them into a single new commit.

Assume HEAD is at C4_revert. We want to go back to C1 but keep all changes from C3_amended and C4_revert staged.

Bash
# First, get HEAD back to C4_revert (if you followed the mixed reset example)
# You might need to recommit the changes or use git reflog to find C4_revert's hash and reset to it.
# For simplicity, let's assume we are at C4_revert.
# git log --oneline # to find <hash_C1>

git reset --soft <hash_C1>
git status

git log --oneline will show HEAD and main at C1.

git status will show all changes from C3_amended and C4_revert as “Changes to be committed” (staged). You could then make a single new commit.

Finally, a –hard reset (USE WITH EXTREME CAUTION).

Let’s say we want to completely discard C3_amended and C4_revert and make our local repository identical to the state of C1, losing all subsequent changes.

Bash
# Ensure you know what you're doing! This is destructive for unpushed work.
# git log --oneline # to find <hash_C1>

git reset --hard <hash_C1>
git status
git log --oneline

git log --oneline shows HEAD and main at C1.

git status shows a clean working directory (no changes).

The content of file1.txt, file2.txt, file3.txt will be exactly as they were in commit C1. All changes from C3_amended and C4_revert are gone from your working directory and staging area.

OS-Specific Notes

  • File Recovery After git reset --hard or git restore:
    • Git itself does not keep a separate trash can for changes discarded from the working directory by these commands. Once they are gone from your working directory (and weren’t committed or stashed), they are generally unrecoverable through Git.
    • Windows: Files might go to the Recycle Bin if deleted by some editors before a hard reset, but git reset --hard itself doesn’t use the Recycle Bin.
    • macOS: Similar to Windows, no automatic “Trash” for git reset --hard changes.
    • Linux: No system-wide trash for command-line operations by default.
    • Your best bet for recovery in such cases might be filesystem-level recovery tools or editor-specific local history/backup features, but these are outside of Git. This underscores the importance of committing frequently and using --hard resets with extreme care.
  • Editor for git commit --amend and git revert:
    • As with regular commits, Git will use the editor configured via core.editor (or your system default) when these commands require you to edit or confirm a commit message. This editor behavior is consistent across OSs, though the specific editor path might vary.

Common Mistakes & Troubleshooting Tips

Git Issue / Error Symptom(s) Troubleshooting / Solution
Lost work after git reset –hard Uncommitted local changes disappear from working directory. Solution: Extreme caution with –hard. Commit/stash important changes first. Recovery outside Git (editor history, backups) might be possible. git reflog shows previous HEAD states but doesn’t recover uncommitted WD changes.
Amended a pushed commit git push rejected (non-fast-forward) after git commit –amend. Solution: Avoid amending pushed commits. Use git revert for pushed changes. If amend is necessary and pushed, coordinate git push –force-with-lease with team.
git reset on shared history Local history diverges; force push needed, disrupting collaborators. Solution: For shared/pushed commits, use git revert <commit>. It creates a new commit, preserving history.
Confusing restore –staged vs. restore Working directory changes discarded when only intending to unstage. Solution: git restore –staged <file> = unstage. git restore <file> = discard WD changes.
Unsure what a git reset option will do Hesitation before running git reset due to potential data loss. Solution: Review differences: –soft (HEAD moves, changes staged), –mixed (HEAD moves, staging reset, changes in WD), –hard (HEAD moves, staging & WD reset). Always check git status. Start with –soft or –mixed if unsure.
Reverted the wrong commit A git revert introduced an undesired state. Solution: A revert is just a commit. You can revert the revert commit: git revert HEAD (if the problematic revert was the last commit). Or, identify the revert commit’s hash and git revert <revert_commit_hash>.

Exercises

Use the undoing-practice repository. Reset it to a known state if needed for each exercise (e.g., back to C3 by finding its hash in git reflog and doing git reset --hard <C3_hash>).

  1. Staging, Unstaging, and Discarding:
    1. Modify file1.txt and file2.txt.
    2. Create a new file extra.txt.
    3. Stage file1.txt and extra.txt.
    4. Check git status.
    5. Unstage extra.txt. Check git status again.
    6. Discard the changes made to file1.txt in your working directory. Check its content and git status.
    7. What happens to file2.txt‘s modifications and extra.txt?
  2. Amending and Reverting:
    1. Ensure your repository has at least two commits.
    2. Make a new commit with the message “Featur X addedd” (notice the typo) and add a new file feature-x.txt.
    3. Use git commit --amend to fix the typo in the commit message of the last commit without changing any files.
    4. Now, amend the last commit again, this time also adding a modification to feature-x.txt.
    5. Suppose the commit before your amended one introduced a bug. Find its hash.
    6. Use git revert to undo the changes from that problematic commit. Inspect the new revert commit message and the state of your files.
  3. Exploring git reset Modes (Local History Only):
    1. Make three new commits on top of your current main branch (C4, C5, C6).
    2. Use git log --oneline to note their hashes and messages.
    3. Perform git reset --soft HEAD~2. What does git status show? What does git log --oneline show? How do the files in your working directory look?
    4. Now, from this state, perform git reset --mixed HEAD~1 (this will take you back one more commit from the current HEAD, which is now effectively C4). What does git status show now? What about git log --oneline?
    5. (Carefully!) Perform git reset –hard <hash_of_original_C4>. What is the state of your repository now (log, status, file contents)?Important: This exercise is for understanding reset on local, unshared commits.

Summary

Git provides a versatile toolkit for undoing changes at different stages:

  • Unstaging:
    • git restore --staged <file>: Removes file from staging; changes remain in working dir.
    • git reset HEAD <file>: Older equivalent to unstage.
  • Discarding Working Directory Changes:
    • git restore <file>: Reverts file in working dir to its state in staging (if staged) or HEAD.
    • git checkout -- <file>: Older equivalent to discard working dir changes.
    • Caution: These discard uncommitted work permanently from the working directory.
  • Amending the Last Commit:
    • git commit --amend: Modifies the most recent commit (message and/or content).
    • Rewrites history; avoid on pushed commits.
  • Reverting Commits:
    • git revert <commit-hash>: Creates a new commit that undoes the changes of a specified commit.
    • Safe for shared history as it doesn’t alter existing commits.
  • Resetting Commits:
    • git reset [--soft | --mixed | --hard] <commit-hash>: Moves the current branch pointer.
      • --soft: Moves HEAD; changes from “undone” commits become staged.
      • --mixed (default): Moves HEAD, resets staging area; changes become unstaged in working dir.
      • --hard: Moves HEAD, resets staging area and working directory; changes are lost.
    • Rewrites history; dangerous for pushed commits. Use primarily for local cleanup.

Understanding the difference between history-preserving operations (git revert) and history-rewriting operations (git commit --amend, git reset) is crucial for effective and safe version control, especially in collaborative environments.

Further Reading

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top