Chapter 6: Undoing Changes

Mistakes are an inevitable part of development, and Git provides a robust set of tools for undoing changes and recovering from errors. This chapter will explore the various techniques for undoing changes in Git, allowing you to confidently experiment and correct mistakes without fear of losing your work. We'll cover how to undo changes in the working directory, the staging area, and even committed changes, ensuring you have the flexibility to manage your project's history effectively.

We'll begin by examining how to undo changes in the working directory, reverting files to their last committed state. From there, we'll explore how to unstage changes in the staging area, allowing you to remove files from the next commit. We'll then delve into amending commits, reverting commits, and resetting commits, providing you with the tools to modify and rewrite your project's history. By the end of this chapter, you'll have a comprehensive understanding of how to undo changes in Git, empowering you to maintain a clean and accurate project history.

Undoing Changes in the Working Directory (git checkout -- )

The git checkout -- command is used to discard changes made to a file in the working directory, reverting it to its last committed state. This command is useful when you've made modifications to a file that you want to undo, effectively restoring it to the version in the HEAD commit.

How it Works:

  • Reverts to Last Committed State: git checkout -- overwrites the specified file in the working directory with the version from the HEAD commit.
  • Discards Unstaged Changes: This command only affects unstaged changes. Any changes that have been added to the staging area (using git add) will not be affected.
  • Irreversible: Once you run git checkout -- , the changes in your working directory are permanently discarded. There is no way to recover them.

Basic Usage:

  • Reverting a single file:
git checkout -- filename.txt

Replace filename.txt with the name of the file you want to revert.

  • Reverting multiple files:
git checkout -- file1.txt file2.txt file3.txt

You can revert multiple files by listing them separated by spaces.

  • Reverting all modified files in the working directory:
git checkout -- .

This command reverts all modified files in the current directory and its subdirectories. Be cautious when using this command, as it will discard all unstaged changes.

Example Scenario:

  1. You modify a file named main.py in your working directory.
  2. You realize that you want to discard the changes and revert the file to its last committed state.
  3. You run git checkout -- main.py.
  4. The changes in main.py are discarded, and the file is reverted to the version in the HEAD commit.

Important Considerations:

  • Unstaged Changes Only: git checkout -- only affects unstaged changes. If you have already added changes to the staging area, you will need to unstage them using git reset HEAD before using git checkout -- .
  • Irreversibility: Be careful when using this command, as it permanently discards changes. Ensure that you have backed up any important changes before running git checkout -- .
  • Working Directory vs. Staging Area: It is essential to distinguish between the working directory and the staging area. git checkout -- only affects the working directory.

By understanding how to use git checkout -- , you can effectively discard unwanted changes in your working directory, ensuring that your codebase remains clean and accurate.

Undoing Changes in the Staging Area (git reset HEAD )

The git reset HEAD command is used to remove files from the staging area (index), effectively unstaging them. This command is useful when you've added changes to the staging area that you no longer want to include in your next commit.

How it Works:

  • Unstages Files: git reset HEAD removes the specified file from the staging area, but it does not modify the file in the working directory.
  • Preserves Working Directory Changes: The changes you made in the working directory are preserved.
  • Does Not Affect Commit History: This command only affects the staging area; it does not modify the commit history.

Basic Usage:

  • Unstaging a single file:
git reset HEAD filename.txt

Replace filename.txt with the name of the file you want to unstage.

  • Unstaging multiple files:
git reset HEAD file1.txt file2.txt file3.txt

You can unstage multiple files by listing them separated by spaces.

  • Unstaging all files:
git reset HEAD

This command unstages all files in the staging area.

Example Scenario:

  1. You modify a file named main.py and add it to the staging area using git add main.py.
  2. You realize that you don't want to include the changes in your next commit.
  3. You run git reset HEAD main.py.
  4. The file main.py is removed from the staging area, but your changes in the working directory are preserved.

Important Considerations:

  • Staging Area Only: git reset HEAD only affects the staging area. It does not modify the working directory or the commit history.
  • Working Directory Remains Unchanged: The changes you made in the working directory are preserved. If you want to discard those changes, you can use git checkout -- .
  • No Commit History Modification: This command does not modify the commit history. If you want to undo committed changes, you'll need to use other commands like git revert or git reset --hard .
  • HEAD Pointer: HEAD is a pointer to the current branch's latest commit. git reset HEAD moves the staging area to match the HEAD commit without altering the working directory.

By understanding how to use git reset HEAD , you can effectively unstage changes, ensuring that your commits contain only the desired modifications.

Amending Commits (git commit --amend)

The git commit --amend command is used to modify the most recent commit. This allows you to change the commit message, add or remove files, or modify the changes included in the last commit. Amending commits is useful for correcting mistakes or refining your commit history.

How it Works:

  • Modifies the Last Commit: git commit --amend replaces the last commit with a new commit that incorporates the changes you specify.
  • Updates the Commit Hash: Because the commit content and metadata change, the commit hash is also updated.
  • Staging Area and Working Directory: You can modify the staging area and working directory before running git commit --amend to include additional changes in the amended commit.

Basic Usage:

  • Changing the commit message:
git commit --amend

This command opens your default text editor, allowing you to edit the commit message.

  • Adding staged changes to the last commit:
git add <file(s)>
git commit --amend

This adds the specified files to the staging area and then amends the last commit to include those changes.

  • Modifying the last commit without changing the message:
git commit --amend --no-edit

This command amends the last commit without opening the text editor, preserving the existing commit message.

Example Scenarios:

  1. Correcting a typo in the commit message:

  2. You make a commit with a typo in the commit message.

  3. You run git commit --amend and correct the typo in the editor.

  4. Adding a forgotten file to the last commit:

  5. You make a commit but forget to include a file.

  6. You add the file to the staging area using git add .
  7. You run git commit --amend to include the file in the last commit.

  8. Modifying the changes in the last commit:

  9. You make a commit but realize that you made a mistake in the code.

  10. You fix the mistake in the working directory and add the changes to the staging area.
  11. You run git commit --amend to update the last commit with the corrected changes.

Important Considerations:

  • Last Commit Only: git commit --amend only modifies the most recent commit.
  • Do Not Amend Public Commits: Avoid amending commits that have already been pushed to a shared repository. This can cause issues for other developers who havebased their work on the original commit.  
  • Rewrites History: Amending commits rewrites the commit history, which can make it difficult to collaborate if others have based their work on the original commit.
  • Staging Area: The staging area is considered when amending. If you have any staged changes, they will be included in the amended commit.
  • No-Edit Option: the --no-edit option is very useful when you have already staged your changes, and do not need to change the commit message.

By understanding how to use git commit --amend, you can effectively modify the last commit, ensuring that your commit history is clean and accurate.

Reverting Commits (git revert)

The git revert command is used to create a new commit that undoes the changes introduced by a specific commit.This command is useful when you want to undo changes without modifying the existing commit history. Git revert is a safe way to undo changes, especially in shared repositories, as it doesn't rewrite history.  

How it Works:

  • Creates a New Commit: git revert creates a new commit that reverses the changes made in the specified commit.
  • Preserves History: Unlike git reset --hard, git revert does not modify the existing commit history. It adds a new commit that explicitly undoes the changes.
  • Safe for Shared Repositories: Because it doesn't rewrite history, git revert is safe to use in shared repositories, as it doesn't cause issues for other developers.

Basic Usage:

  • Reverting a specific commit:
git revert <commit-hash>

Replace with the SHA-1 hash of the commit you want to revert.

  • Reverting multiple commits:
git revert <commit-hash1> <commit-hash2>

You can revert multiple commits by listing them separated by spaces.

  • Reverting a range of commits:
git revert <commit-hash1>..<commit-hash2>

This reverts all commits in the specified range.

  • Skipping the editor:
git revert --no-edit <commit-hash>

This will revert the commit and not open the editor for a commit message.

Example Scenario:

  1. You make a commit that introduces a bug.
  2. You realize that you need to undo the changes made in that commit.
  3. You run git revert to create a new commit that reverts the changes.
  4. Git opens your default text editor, allowing you to edit the revert commit message.
  5. You save the commit message and close the editor.
  6. Git creates a new commit that undoes the changes made in the specified commit.

Important Considerations:

  • New Commit: git revert creates a new commit. It does not modify the existing commit.
  • Safe for Shared Repositories: Because it doesn't rewrite history, git revert is safe to use in shared repositories.
  • Conflicts: If the changes introduced by the revert commit conflict with other changes, you'll need to resolve the conflicts manually.
  • Commit Message: The revert commit message typically includes a reference to the commit that was reverted.
  • Undo a Merge: git revert -m parent_number . You must specify the parent number of the merge you want to revert, as a merge commit has multiple parents.

By understanding how to use git revert, you can effectively undo changes in your Git repository without rewriting history, ensuring that your project remains stable and collaborative.

Resetting commits (git reset)

The git reset command is used to move the current branch pointer to a specified commit. It can also be used to modify the staging area and working directory, depending on the options used. git reset is a powerful command that can be used to undo changes, but it can also be dangerous if used incorrectly, especially in shared repositories.

How it Works:

  • Moves the Branch Pointer: git reset moves the current branch pointer (and HEAD) to the specified commit.
  • Modifies Staging Area and Working Directory (Optional): Depending on the options used, git reset can also modify the staging area and working directory.
  • Rewrites History: git reset rewrites the commit history, which can cause issues for other developers who have based their work on the original commits.

Basic Usage and Options:

git reset --soft <commit-hash>:
  • Moves the branch pointer to the specified commit.
  • Does not modify the staging area or working directory.
  • Changes remain in the staging area and working directory.
git reset --mixed <commit-hash> (Default):
  • Moves the branch pointer to the specified commit.
  • Resets the staging area to match the specified commit.
  • Changes remain in the working directory.
git reset --hard <commit-hash>:
  • Moves the branch pointer to the specified commit.
  • Resets the staging area and working directory to match the specified commit.
  • Discards all changes in the working directory. Use with extreme caution!

Example Scenarios:

  1. Undoing the last commit (soft reset):

  2. You make a commit but realize that you want to undo it without losing the changes.

  3. You run git reset --soft HEAD^ to move the branch pointer to the previous commit.
  4. The changes remain in the staging area and working directory.

  5. Undoing the last commit and unstaging the changes (mixed reset):

  6. You make a commit but realize that you want to undo it and unstage the changes.

  7. You rungit reset --mixed HEAD^ to move the branch pointer to the previous commit and reset the staging area.
  8. The changes remain in the working directory but are unstaged.

  9. Undoing the last commit and discarding the changes (hard reset):

  10. You make a commit but realize that you want to undo it and completely discard the changes.

  11. You run git reset --hard HEAD^ to move the branch pointer to the previous commit and reset the staging area and working directory.
  12. All changes in the working directory are permanently discarded.

  13. Moving back multiple commits:

  14. git reset --hard HEAD~3 moves the branch pointer back 3 commits and discard all changes.

Important Considerations:

  • Rewrites History: git reset rewrites the commit history, which can cause issues for other developers who have based their work on the original commits.
  • Hard Reset is Dangerous: git reset --hard permanently discards changes in the working directory. Use with extreme caution!
  • Staging Area and Working Directory: The options used with git reset determine how the staging area and working directory are affected.
  • Shared Repositories: Avoid using git reset on commits that have already been pushed to a shared repository.
  • Reflog: If you accidentally discard changes with git reset --hard, you may be able to recover them using git reflog.

By understanding how to use git reset and its various options, you can effectively undo changes in your Git repository. However, it's essential to use this command with caution, especially in shared repositories.