Chapters

Hide chapters

Advanced Git

Second Edition · Git 2.32 · Console

Section I: Advanced Git

Section 1: 7 chapters
Show chapters Hide chapters

7. The Many Faces of Undo
Written by Jawwad Ahmad & Chris Belanger

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

One of the best aspects of Git is the fact that it remembers everything. You can always go back through the history of your commits with git log, see the history of your team’s activities and cherry-pick commits from other places.

But one of the most frustrating aspects of Git is also that it remembers everything. At some point, you’ll inevitably create a commit that you didn’t want or that contains something you didn’t intend to include.

While you can’t rewrite shared history, you can get your repository back into working order without a lot of hassle.

In this chapter, you’ll learn how to use the reset, reflog and revert commands to undo mistakes in your repository. While doing so, you’ll find a new appreciation for Git’s infallible memory.

Working with git reset

Developers quickly become familiar with the git reset command, usually out of frustration. Most people see git reset as a “scorched earth” approach to fix a repository that’s messed up beyond repair. But when you delve deeper into how the command works, you’ll find that reset can be useful for more than a last-ditch effort to get things working again.

To learn how reset works, it’s worth revisiting another command you’re intimately familiar with: checkout.

Comparing reset with checkout

Take the example below, where the branch mybranch is a straightforward branch off of main:

Working with the three flavors of reset

Remember that Git needs to track three things: your working directory, the staging area and the internal repository index. Depending on your needs, you can provide parameters to reset to roll back either all those things or just a selection:

Testing git reset –hard

git reset --hard is most people’s first introduction to “undoing” things in Git. The --hard option says to Git, “Please forget about the grievous things I’ve done to my repository and restore my entire environment to the commit I’ve specified”.

Removing an utterly useless directory

Start by going to the command line and navigating to the root directory of your repository. Execute the following command to get rid of that pesky js directory, which doesn’t look very important:

rm -rf js
git commit -am "Deletes the pesky js directory"

git log --oneline --graph
* 77c1302 (HEAD -> main) Deletes the pesky js directory

Restoring your directory

In this case, you want to return to the last commit before you made your blunder. You don’t even need to know the commit hash; you can provide relative references to git reset instead.

git reset HEAD~ --hard

Trying out git reset –mixed

Imagine that you’re working on another software project. You’re up late, the coffee ran out hours ago and you’re tired. That never happens in real life, of course, but bear with me.

touch SECRETS
echo 'password=correcthorsebatterystaple' >> SECRETS
git add .
git commit -m "Adds final styling for website"

Removing your unwanted commit

You could use git reset HEAD~ --hard, as above, but that would blow away hours of hard work. Instead, use git reset --mixed to reset the commit index and the staging area, but leave your working directory alone.

git reset HEAD~ --mixed
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	SECRETS
echo SECRETS >> .gitignore
git add .gitignore
git commit -m "Updates .gitignore"

Using git reset –soft

If you like to build up commits bit by bit, staging changes as you make them, then you may encounter a situation where you’ve staged various changes and committed them prematurely.

touch setup.config
git add setup.config
echo "For configuration instructions, call Sam on 555-555-5309 any time" >> README.md

Making a mistake

Just before you add that to the staging area, Will and Xanthe call you excitedly with their plans for their next big project: to create a — wait for it — magic triangle generator. You humor them for a while, then turn your attention back to your project.

git commit -m "Adds configuration file and instructions"
[main c416751] Adds configuration file and instructions
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 setup.config

Cleaning up your commit

So now, you need to clean up that commit so it includes both the change to README.md and the addition of setup.config.

git reset HEAD~ --soft
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   setup.config

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:   README.md
git add README.md
git commit -m "Adds configuration file and instructions"
[main 1814f46] Adds configuration file and instructions
 2 files changed, 1 insertion(+)
 create mode 100644 setup.config
git log -p -1
diff --git a/README.md b/README.md
index 7259499..7c6a422 100644
--- a/README.md
+++ b/README.md
@@ -24,3 +24,4 @@ For info on this project, please contact [Xanthe](mailto:xanthe@example.com).
 ## Contributing

 To contribute to this project, simply create a pull request on this repository and we’ll review it when we can.
+For configuration instructions, call Sam on 555-555-5309 any time
diff --git a/setup.config b/setup.config
new file mode 100644
index 0000000..e69de29

Using git reflog

You know that Git remembers everything, but you probably don’t realize just how deep Git’s memory goes.

git reflog
1814f46 (HEAD -> main) HEAD@{0}: commit: Adds configuration file and instructions
9e017e1 HEAD@{1}: reset: moving to head~
5bb9c12 HEAD@{2}: commit: Adds configuration file and instructions
9e017e1 HEAD@{3}: commit: Updates .gitignore
d5f6cba HEAD@{4}: reset: moving to HEAD~
dc60b80 HEAD@{5}: commit: Adds final styling for website
d5f6cba HEAD@{6}: reset: moving to head~
77c1302 HEAD@{7}: commit: Deletes the pesky js directory
d5f6cba HEAD@{8}: reset: moving to HEAD
d5f6cba HEAD@{9}: reset: moving to HEAD
.
.
.

Finding old commits

You’ve rethought your changes above. Putting configuration elements in a separate file in the repo along with instructions isn’t the best way to go about things. It obviously makes more sense to put those settings, along with Sam’s mobile number, on the main wiki page for this project.

git reset HEAD~ --hard
git log --oneline --graph
git reflog
9e017e1 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~
1814f46 HEAD@{1}: commit: Adds configuration file and instructions

Recovering your commit with git checkout

Even though you usually use git checkout to switch between branches, as you saw way back at the beginning of this chapter, you can use git checkout and specify a commit hash, or in this case, a reflog entry, to create a detached HEAD state. You’ll do that now.

git checkout HEAD@{1}
Note: switching to 'HEAD@{1}'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 1814f46 Adds configuration file and instructions
git log --oneline --graph -n2
* 1814f46 (HEAD) Adds configuration file and instructions
* 9e017e1 (main) Updates .gitignore
git switch -c temp

Checking that your changes worked

Look at your commit tree again with git log --oneline --graph --all and you’ll see something like this:

* 1814f46 (HEAD -> temp) Adds configuration file and instructions
* 9e017e1 (main) Updates .gitignore
git log --oneline --graph
* 1814f46 (temp) Adds configuration file and instructions
* 9e017e1 (HEAD -> main) Updates .gitignore

Using git revert

In all of this work with git reset and git reflog, you haven’t pushed anything to a remote repository. That’s by design. Remember, you can’t change shared history. Once you’ve pushed something, it’s a lot harder to get rid of a commit since you have to synchronize with everyone else.

Setting up your merge

First, merge in that branch. Ensure you’re on main to start:

git checkout main
git merge temp
Updating 9e017e1..1814f46
Fast-forward
 README.md    | 1 +
 setup.config | 0
 2 files changed, 1 insertion(+)
 create mode 100644 setup.config
* 1814f46 (HEAD -> main, temp) Adds configuration file and instructions
* 9e017e1 Updates .gitignore

Reverting your changes

While you can’t change shared history, you can at least revert the changes you’ve made here to get back to the previous commit.

git revert HEAD --no-edit
Removing setup.config
[main 6e67b05] Revert "Adds configuration file and instructions"
 Date: Mon Aug 23 06:12:27 2021 -0500
 2 files changed, 1 deletion(-)
 delete mode 100644 setup.config
[main 1814f46] Adds configuration file and instructions
 2 files changed, 1 insertion(+)
 create mode 100644 setup.config
* 6e67b05 (HEAD -> main) Revert "Adds configuration file and instructions"
* 1814f46 (temp) Adds configuration file and instructions
* 9e017e1 Updates .gitignore
diff --git a/README.md b/README.md
index 7c6a422..7259499 100644
--- a/README.md
+++ b/README.md
@@ -24,4 +24,3 @@ For info on this project, please contact [Xanthe](mailto:xanthe@example.com).
 ## Contributing

 To contribute to this project, simply create a pull request on this repository and we’ll review it when we can.
-For configuration instructions, call Sam on 555-555-5309 any time
diff --git a/setup.config b/setup.config
deleted file mode 100644
index e69de29..0000000

Key points

Congratulations on finishing this chapter! Here’s a quick recap of what you’ve covered:

Where to go from here?

You’ve already covered quite a lot in this chapter, but I recommend reading a bit more about how relative references work in Git.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now