Chapter 4. Undoing: Fixing Your Mistakes

Image

We all make mistakes, right? Humans have been making mistakes since time immemorial, and for a long time, making mistakes was pretty expensive (with punch cards and typewriters, we had to redo the whole thing). The reason was simple—we didn’t have a version control system. But now we do! Git gives you ample opportunities to undo your mistakes, easily and painlessly. Whether you’ve accidentally added a file to the index, made a typo in a commit message, or made a badly formed commit, Git gives you plenty of levers to pull and buttons to push so that no one will ever know about that little, ahem, “slip-up.”

After this chapter, if you trip up, it won’t matter what kind of mistake you’ve made, you’ll know exactly what to do. So let’s go make some mistakes—and learn how to fix ’em.

Planning an engagement party

Love is in the air, and we’ve got some news to share with you. Gitanjali and Aref are newly engaged! They want to throw an engagement party with their closest friends, and to make sure they get it right, they’ve decided to hire Trinity, an event planner.

Trinity and her partner Armstrong are true professionals—and huge proponents of Git. All of their ideas for invitation cards, guests, and gift lists are always tucked away in a Git repository that they create specifically for that client. This way, they can always use Git as their second (or third, in this case) brain. This is particularly useful, since Trinity and Armstrong are all about helping their clients figure out all their options—plans do change, and Git is a tool that allows Trinity and Armstrong to iterate quickly.

Trinity just finished a conversation with Gitanjali and Aref. She initialized a new Git repository almost as soon as she put the phone down: she wanted to capture her notes about their guest list and gift registry ideas right away. She created two files, guest-list.md and gift-registry.md, and committed them on the master branch.

Her mind was racing with ideas for their invitation card, so she created a file called invitation-card.md and jotted down some ideas, along with a tentative date for the festivities. She committed that as well. This is what her commit history looks like:

Image

As you can see, Trinity’s repository contains one branch, master, and the two commits she has made so far. This engagement party is off to a great start! Now Trinity and Armstrong need to brainstorm some party themes.

An error in judgment

Image

Trinity has just realized something: she made a scheduling error! Gitanjali and Aref suggested July 3 for the engagement party date. There were many things to discuss, so Trinity simply made the change to the invitation-card.md file, and they moved on to other topics.

But July 4 is the US Independence Day, a bank holiday, full of traffic and people headed to picnics. Oops! Trinity called the couple and brought this to their attention. They agreed it probably wasn’t the best weekend for their celebration, so they decided to keep the date they’d originally agreed on. Except they couldn’t remember what the original date was!

Fortunately, Trinity had not committed her changes yet. She used the git diff command to compare the changes in her working directory with the state of the index in the gitanjali-aref repository (which, as you know, contains a copy of the file from her first commit). This is the output she saw when she ran git diff:

Note

If you are wondering how Trinity would have fixed this if she had already committed her changes, worry not! We will see how to fix commits as well in this chapter.

Image

The git diff command, by default, compares the working directory with the index. Here is the state of the invitation-card.md file in the three regions of Git:

Image

So how does Trinity recover from this?

Cubicle conversation

Image

Armstrong: Good thing we use Git for all of our ideas. It’s so easy to see what changes we’ve made. Do you want me to just use the output of the git diff command and use that to bring back all the changes?

Trinity: You could do that, and in this case, it’s only two changes, so it’s certainly possible. But here’s something even better: we can ask Git to undo our change for us.

Armstrong: Really? How?

Trinity: Git is our memory store. We already committed invitation-card.md. This means there is a copy of this file in the index and in the object database. We can ask Git to replace the copy in the working directory with the one in the index.

Armstrong: OK, I get that. But how do I get the copy from the index into the working directory?

Trinity: The answer lies in a command called git restore. Here, take a look at the output of git status and see what it’s telling you:

Image

Armstrong: Ah! I see. Git is telling us we can supply the file path to the git restore command, and that will discard any changes in the working directory.

Trinity: Yep. git restore is the opposite of git add. It takes the copy of a file in the index and moves it back into the working directory.

Armstrong: Cool! Can we do that now?

Undoing changes to the working directory

Image

Trinity has to undo the changes she made to the working directory, by replacing her changes with the ones in the index. She can use the git restore command, supplying it the path to the file that is to be put back.

Image

If all goes well, Git will not report anything. The only way to find out is to resort to our good friend, git status.

Image

The git restore command’s default behavior, as you can see, is the exact opposite of the git add command. The add command takes the version of a file that’s in the working directory and makes a copy of it in the index, overwriting the previous version. The restore command, on the other hand, takes the version of the file stored in the index, and overwrites the version in the working directory.

Image

Undoing changes in the index

Image

When Trinity fixed her error, she had not yet added the invitation-card.md file to the index. But what if she had? How would she go about restoring her changes?

When a file is added to the index, Git makes a copy of the file in the working directory and places it in the index. This is what the state of the working directory would look like for Trinity if she had added invitation-card.md to the index.

Image

The answer lies in the output of git status:

Image

Git tells us exactly what to do to fix this. We can use the same restore command, except this time we have to give it the --staged flag, followed by the filename, like so:

Image

The git restore with the --staged flag is the command you can use to restore files in the index to their previous state. But what does this command actually do? You know git restore (without any flags) replaces the contents of the working directory with the contents held by the index.

When the git restore command is supplied with the --staged flag, Git takes the content of the file in the object database, specifically the contents as they were last recorded in a commit, and overwrites the contents of the file in the index with that content. This is what it looks like:

Image

Earlier we discussed that git restore is the opposite of git add—the latter copies the contents of a file from the working directory into the index, the former copies from the index into the working directory. You can think of git restore with the --staged flag as having the opposite effect on your files as the git commit command. The git commit command, as you know, takes the contents of the index and stores them in the object database. The git restore command takes the previously committed contents of a file and overwrites the index with them.

Note

Note: git restore with the --staged flag is not undoing the commit! It’s simply copying the contents of the file as they were last committed into the index.

Image

Deleting files from Git repositories

Image

“Huh. Well, that’s a first,” thought Trinity, as she read Gitanjali’s email informing her that the engaged couple have decided not to set up a gift registry for their engagement party. Instead, they want to set up a “home fund” to allow their families and friends to contribute money directly toward their first home.

However, in previous discussions, Gitanjali and Aref did have some ideas for gifts, which Trinity listed in a file called gift-registry.md and committed on her master branch. Here is the list of files in the master branch:

Image

Trinity would rather not have superfluous files in her repository, so she needs to delete the gift registry. But how?

Git has a command for this—git rm. Just like the git restore command, the git rm command takes the paths of one or more tracked files and removes them from the working directory and the index. To remove the gift-registry.md, this is what Trinity would use:

Image

And this is the state of Trinity’s repository after she runs this command:

Image

Note that the object database is not affected. The question is—what is the status of the repository after we run the git rm command?

Committing to delete

Image

What does the git rm command really do? Its role is to remove (“rm”) tracked files. After running git rm gift-registry.md, when Trinity lists the files in her working directory, this is what she sees:

Image

As you can see, one effect of the git rm command is to delete the file from the working directory. It also removes the file from the index, as git status highlights:

Image

The output of the git status command is something you haven’t seen before. It’s telling us that a file was deleted, and that if you are indeed sure that this is what you want, you should commit these changes.

In other words, this commit will record the fact that a previously added and committed file is being deleted! That’s different from what you have done so far in this book, where you have always committed new or edited files.

At this point you can choose to make a commit with an appropriate message or use the restore command to undo the deletion.

Note

Read that again! You can use the git restore command to get back a file that you just deleted but haven’t committed yet. Super handy if you make a typo in the filename and accidentally remove the wrong file.

There are two things to note here. First, you can only use the git rm command to delete tracked files. If you’ve only added a new file to the working directory (that is, it’s an “untracked” file), you can just delete it like you would any other file: by moving it to the Trash (Mac) or the Recycle Bin (Windows).

Second, the git rm command only deletes files from the working directory and the index. Versions of the file that were previously committed remain as they were in the object database. This is because a commit represents the changes you made at the time of the commit. If a file existed at the time a commit was made, the commit will remember that for as long as the repository exists.

Note

This may sound surprising. However, a commit is a snapshot in time. Think of those childhood photos of you with a weird haircut. Just because you are sporting a trendy haircut now doesn’t mean that was always the case. And we have pictures to prove it!

Renaming (or moving) files

Let’s look at another operation that’s closely related to deleting files—renaming or moving files. Git affords you another command—the git mv command. The git mv command has all of the same characteristics as the git rm command—the git mv command only works with tracked files, and like the git rm command, the git mv command renames or moves the files you tell it to in both the working directory and the index. Let’s say you have a file called file-a.md and you want to rename it to file-b.md:

Image

git status will report the file rename:

Image

As with removing files, you can always choose to rename files using the Finder or File Explorer, but you’ll still have to update the index to reflect the new filenames. However, like the git rm command, the git mv command not only updates the working directory for you, it also updates the index to reflect the change—so you’re just one step away from committing your changes.

Editing commit messages

The engagement-party planning is in full swing, with Gitanjali and Aref bouncing ideas off Trinity. One thing they feel all their friends will enjoy is spending time in nature, so on their last call they suggested a camping party. The plan is simple—everyone can bring a tent and contribute supplies like food and drinks, cooking supplies, and plasticware. They’ll make s’mores over the campfire and celebrate under the stars.

Trinity realizes that this is just one of many ideas that she’s going to be working with, so she creates a branch in her repository called camping-trip. She creates a new file called outdoor-supplies.md to draft a checklist of guests and supplies. She adds the file to the index, then commits it with the commit message “final outdoors plan”.

Trinity knows she’s messed up as soon as she hits the Return key. Gitanjali and Aref are still coming up with ideas and have yet to iron out all the details. They’ll probably ask for changes or even switch plans altogether, so the commit message “final outdoors plan” seems a little premature.

Trinity is nothing if not a stickler for details. She is going to have to edit that commit message.

It’s a good thing Trinity caught the bad commit message as soon as she made it. Git allows for editing commit messages using the git commit command, with a special flag called --amend.

Image

The first thing to check is that you are on the same branch as the commit you wish to edit. The next thing, and this is super important, is that you want to have a clean working directory. You can verify both of these with our good friend the git status command.

Next, you can amend the last commit on the branch:

Image

After this, Git will record a commit replacing the one you had, except this time it will reflect the new commit message. This commit will have all the same changes as the original commit, including all the same metadata, like your name and email and the timestamp (which will also be the same). In other words, the only difference between the previous commit and the amended commit is the commit message.

Image

Aren’t you the observant one!

When you ask Git to amend a commit, Git pulls a fast one. It essentially looks at the commit you are appending and copies all the changes you made in that commit back into the index. It leaves the original commit as is. It then runs git commit again, this time with the new commit message, which records the changes put in the index by the commit you are amending.

You see, Git commits are immutable. That is, once you create a commit, that version of the commit is preserved. Any edits to the commit (like amending it) will create a new commit that replaces the old commit in your history. Think of it as like writing in pen versus pencil: with a pen, you can cross out your mistakes, but you can’t erase them. Immutable commits are one of Git’s biggest strengths, and a lot of the power in Git comes from this simple idea.

Image

This is why you should always check the status of your repository before you amend a commit. If by chance you’ve added files to the index and you proceed to amend a commit, all of the files in the index will show up in the new commit. That is, the new commit will record more changes (both the files you had in the index and the files Git added from the amended commit).

As for the commit you amend? Git keeps it around for a while, but will eventually delete it from your repository.

Note

This is yet another instance of Git’s cautious nature. By keeping old commits around for a while, it gives you even more chances to recover. How you would go about doing that goes beyond the scope of the book. And don’t worry about those commits lying around—Git is very good at housekeeping.

Renaming branches

Trinity finds herself bemused: Gitanjali and Aref have just informed her that their outdoors engagement party isn’t just camping—it’s “glamping,” short for “glamorous camping.” “Glamping” still involves spending time with nature, but with all the comforts of home and then some: electricity, a roof over your head, and some fabulous furniture and decorations.

Trinity is always keen to learn new things, and she wants to get the details right. The branch name “camping-trip” seems incorrect now that she knows about glamping. She’s going to have to rename that branch.

Image

There are plenty of reasons you might want to rename branches like Trinity did. Perhaps you don’t like the name “master” and you want to use “main” instead. Perhaps you made a typo in the name of your branch. No matter the reason, Git aims to please.

Note

Yep, we hate tpyos too!

In Chapter 2, as you probably recall, you learned that a branch works like a sticky note that records the name of the branch and the ID of the latest commit on that branch. Creating a branch is simply creating a new “sticky note.”

Renaming the branch is just as easy. Git simply grabs the “sticky note” that represents the branch and overwrites the name!

To rename a branch, you use the git branch command—except this time, you supply the -m (or --move) flag.

Trinity wants to rename camping-trip to glamping-trip. There are two ways she can go about this.

  1. Switch to the branch you wish to fix, then rename:

    Image
  2. Rename a branch without switching:

    Image

The second option works regardless of what branch you are currently on—the command works even if the branch you are attempting to rename is the current branch. This is why we always prefer using the second option.

Note

Most Git commands offer different ways of achieving the same thing. Having a consistent way of always doing something frees up your brain so you can think of more important things in life—like, is it a good idea to smear avocado on fruit? What does it mean to put fruit on fruit?

Note

Didn’t realize avocado is a fruit, did ya? See what we mean by more important things in life?

Making alternative plans

When Trinity plans big events, she likes to have several ideas in her back pocket, just in case something falls through. Gitanjali and Aref are huge board-game fans with a massive collection of games. The second idea they’ve been throwing around is to celebrate their engagement doing what they love: strategizing, rolling the dice, and bonding with their friends over board games!

To capture this idea, Trinity creates a new branch based on main, which she calls boardgame-night. She next creates a file called indoor-party.md and takes notes about which games are on the list, then commits it. Gitanjali, Aref, and Trinity also discuss potential venues to host the party, which Trinity puts in a file called boardgame-venues.md. She adds a note about venue selection to indoor-party.md and makes another commit.

Trinity feels good now. Gitanjali and Aref have two strong party ideas—one outdoors event, and one indoors.

Image

Turns out there is! Its name is HEAD. You’ve seen HEAD before. In fact, in Chapter 2 we even gave you a rhyme to help you remember what it means. Here it is again:

If you’ve ever used a smartphone with a map application, then you already know what HEAD is. It’s the pin that shows you exactly where you are on the map.

Similarly, if you can visualize your commit history as a series of different timelines (branches), then HEAD marks your current location. Furthermore, HEAD knows about the “stops” you made along the way, also known as commits, so you can use HEAD to reference commits in relation to your current location and even to hop between commits.

HEAD Tells All

An exclusive interview

Image

Head First: Welcome, HEAD! We realize you’re super busy out there, what with playing a role in every Git repository, so we’re glad you took the time to speak with us.

HEAD: No problem. You’re right, I have a lot riding on my shoulders. Every Git repository uses me all the time. It’s kind of a lot to handle!

Head First: That’s impressive, given that you’re just a reference.

HEAD: Well, sure, but I’m the user’s compass as they navigate their commit history. Without me, they would be lost. And hey, I’m so important, I’m in the title of this book!

Head First: Ahem. Moving on, we know you have an especially large role to play when our readers use the git log command. Care to tell us a little bit more about that?

HEAD: You bet. Every time your readers view their Git log, there I am, standing right next to the branch that they’re currently on. Here’s a picture of me on the red carpet in a recent appearance:

97a2899 (HEAD -> boardgame-night)

Head First: Looking good there, HEAD! Any other recent appearances that you want to share with us?

HEAD: My talent agent sure has my calendar full. I make a cameo experience every time your readers use the git branch command to list all the branches in their repository.

Head First: Really? How does that work?

HEAD: You know that asterisk that shows up in the branch list? Yeah, that’s me! It took me months of training to get into character, but it was totally worth it.

Head First: So that’s it? Your role centers around telling our readers where they are in the commit history.

HEAD: That’s it? You don’t know anything about me, do you? My role isn’t just to tell your users where they are in the commit history. Git itself needs me. It can’t work correctly without me. Did you know that?

Head First: Wow. You got me.

HEAD: Your readers know that every time you make a commit, the new commit has a reference to the parent commit.

Head First: Sure. That’s how the commit history is built over time.

HEAD: How do you think Git knows what the parent commit is?

Head First: Huh. That’s interesting.

HEAD: I don’t get enough credit for that role. I think that’s what will get me the Oscar nomination. You see, when your readers make a new commit, Git looks to me first to see which commit I’m pointing to. It then records that commit ID as the parent of the new commit.

Head First: Impressive! That’s a pretty important role. Are you proud of the work you’re doing there?

HEAD: Absolutely! The commit I am pointing to will always be the parent commit of the next commit in the repository. That’s HUGE!

Head First: Well, thank you so much for your time.

HEAD: Wait, I haven’t told you about my upcoming role in this superhero—

Head First: We’ll have to leave it here. Looking forward to next time. Thanks for joining us, HEAD!

The role of HEAD

Every time you switch branches, HEAD moves to reflect the branch you switched to. Consider a hypothetical commit history. Let’s say you are on the master branch, so HEAD points to the latest commit on that branch. When you switch branches, HEAD moves to the new branch:

Image

HEAD is simply a reference, like branches are. This difference is that a Git repository can have many branches, but there is only one HEAD. HEAD also serves as the launch point to decide how the commit history will change, in that the commit that HEAD points to will be the parent of the next commit—it’s how Git knows where to add the new commit in the commit history.

Recall that every time you make a commit on a branch, Git rewrites the branch sticky note to point to the new commit on that branch. Well, there is one more thing that happens—Git moves HEAD to the new commit as well.

Image

In Chapter 2, we spoke of merging branches. We referred to the branch you are on as the proposer, and the branch that is being merged in as the proposee. Once you merge the two branches, the proposing branch moves to reflect the merge—in the case of a fast-forward merge, the proposing branch moves to the latest commit on the proposee branch. In the case of a merge that creates a merge commit, again, the proposing branch moves to the merge commit that is created. In both cases, HEAD moves as well.

Referencing commits using HEAD

Given that HEAD points to the commit you are on, you can reference other commits relative to HEAD. Git offers a special operator, the tilde (~), that allows you to do this. Consider this hypothetical commit history:

Image

A number n following the tilde operator represents the nth generational ancestor. For example, HEAD~1 references the first parent of the commit you are on. HEAD~2 means the parent of the parent of the commit you are, and so on and so forth.

So how does this help you? Suppose you want to find the difference between the commit you are on and the previous commit, using the git diff command. Instead of having to look up commit IDs, here is how you would go about doing it:

Image

Traversing merge commits

Merge commits, as we discussed in Chapter 2, are special. They have more than one parent. So how do you go about navigating from HEAD to the first parent? Or the second parent? Recall that the first parent is the latest commit on the proposing branch, and the second commit is the latest commit from the proposee branch.

Git offers another operator that works with HEAD: the caret (^), which helps when you’re navigating from commits with multiple parents. Take a look to see how that works for this hypothetical commit graph:

Image

Like the tilde operator, the caret operator uses a number to figure out which parent of a merge commit you want to reference.

Finally, you can combine the ~ operator and the ^ operator. Here is how HEAD^1~2 would traverse the commit history:

Image

Undoing commits

Turns out, all the scouting Trinity did to find a place to host board-game night was in vain. Gitanjali and Aref have decided it will be much easier to host the party at their home. This way, if the party goes on late into the night, the guests won’t have to drive back home—they can just crash there for the night.

Unfortunately, Trinity has already committed the boardgame-venues.md file (with options for venues) in her repository. She also hinted at a possible venue selection coming soon in the indoor-party.md file she created for board-game night. Here is Trinity’s commit history:

Image

As you can see, Trinity has a commit on the boardgame-night branch that is no longer needed. And now she has to figure out how to get rid of it.

Removing commits with reset

How does Trinity undo a commit? She has two options. The first option is to simply move the board-game branch back one commit. If she could do this, all her problems would be gone. Essentially, after moving the branch back, her commit graph would look like this:

Image

In other words, you want to move HEAD to HEAD~1. The command that allows you to do this is the git reset. You can supply git reset with a reference to a commit, either a commit ID or using one of the operators we spoke of, namely tilde (~) or caret (^).

Image

The git reset command has two immediate effects—it moves the HEAD and the branch to the commit you specify. But every commit that you make in a repository records a set of changes—you might have added or removed files, or edited existing files, or both. So what happens to those changes?

Well, that’s the million-dollar question, isn’t it?

The three types of reset

Git has three distinct places where changes can live—the working directory, the index, and the object database. Therefore, the git reset command offers three options to undo a commit, each option doing different things with regards to the changes that you are undoing and how it affects each of the three areas of Git.

Bear in mind that the one thing the git reset command always does is to move the HEAD and the branch to the commit you specify. The only question we are aiming to answer is, what happens to the changes you had committed? Let’s say your repository had two commits—a commit with ID B, and its parent A.

Image

git reset --soft

The git reset command can be given the --soft flag. This flag takes the edits you committed and moves them back into the index, and then from the index it copies those changes into the working directory.

In other words, the edits you had committed (in commit B) are gone from the object database. It’s like you never made the commit to begin with! But because they are in the index, you’re just one git commit command away from committing them back. Those changes are still available to you—in the index and the working directory. HEAD now points to commit A.

Image

Image Answer in “Sharpen your pencil Solution”.

Using git reset (or git reset --mixed)

Image

The git reset command’s default mode is --mixed, so you can invoke git reset or git reset --mixed with the same results. This is how to use it:

git reset A     OR     git reset --mixed A

The --mixed mode does a bit more work than the --soft mode does. It has two steps:

  1. First, it moves the changes in commit B (the commit you are undoing) into the index, and then copies those changes from the index into the working directory, just like --soft mode does.

  2. It then copies the contents of commit A into the index. That is, the index now looks exactly like the commit you just reset to.

Image

Contrasting the soft and mixed behavior: --soft mode leaves both the index and the working directory changed. But --mixed mode only leaves the working directory changed. With mixed mode, the changes you committed in “B” reside only in the working directory—the index looks like the changes in commit “A.”.

git reset --hard

Image

Finally, the third flag that reset offers is --hard. Remember, the intent is to undo the changes in a commit. --soft mode moves the changes in the commit you are undoing and puts them in both the index and the working directory. --mixed mode on the other hand puts the changes in the commit you are undoing (“B”) into the working directory, but the index and the object database look like the commit you reset to (“A”). Effectively, --mixed mode takes the changes you had in the commit that you just undid, and makes them appear in the working directory.

Finally, the --hard mode takes what the --mixed mode does to its logical end. In mixed mode, the second step copies the contents commited in “A” into the index, and stops there. --hard mode does not. It takes the contents of the index (which have the changes as they are in commit A) and overwrites the working directory. This means that the object database, the index, and the working directory all look the same. It’s as if commit B never happened! After a hard reset, the working directory, the index, and HEAD all look like commit “A.”

Image

Congratulations, you time traveler, you!

Image

Seriously. You just traveled through time. It’s been quite a journey, but for the first time, you’ve used Git’s time-traveling abilities. The git reset command sets the state of the working directory and the index to one that you had recorded in a previous commit! That is, the git reset command rewrites your history! Remember, with great power comes great responsibility. We will talk of the potential pitfalls with this approach in future chapters, but for now, sit back and bask in your newfound powers.

Another way to undo commits

When we started talking about undoing commits, we mentioned that Trinity has two options. The first approach is using the git reset command.

However, Git offers us another approach, but before we get to that, let’s take a minute to talk about what a commit is. A commit records a set of changes—you might have edited a bunch of files, maybe added or deleted a few. You’ll see these changes if you use the git diff command to compare a commit with, say, its parent. They appear as a set of pluses (“+”) and minuses (“-”). This is referred to as the “delta,” or the variation between two commits.

Image

Another approach to undoing a commit is as simple as negating a commit—for every file added, you could delete it, and vice versa. For every line in every file that was added, you delete it, and for every line that was deleted, bring it back.

Image

Given that Git can calculate the differences introduced by a commit, it can also calculate the reverse of the differences, or if you like, the “anti-commit.” And you can use this to “undo” a commit.

Note

If it helps, think of matter and anti-matter coming in contact with each other. End result: complete annihilation!

Reverting commits

Image

You can create “anti-commits” by using the git revert command. The revert command, like the reset command, can be given a commit ID or a reference to a commit. There is a big difference, though—the git revert command is to be given the ID or reference of the commit you want to undo. Consider our hypothetical repository again—let’s say you want to undo commit B. This is how you would use the git revert command:

Image

Git looks at the changes introduced in B and calculates the anti-commit. This is an actual commit that Git will prepare. Now just like any other commit, this commit needs a commit message. So Git will use your preconfigured editor and bring it up, prompting you to supply a message for the newly created commit:

Note

We have seen this before, in Chapter 2: when we merge two branches, that results in an merge commit. Recall that Git brings up your editor and prompts you for a commit message.

Image

We usually prefer to keep the message as is. Once you close the editor, Git will confirm creating a new commit. So what is the effect of a revert? This is what your commit history will look like after a revert:

Image

Like the git reset command, the git revert command moves the HEAD and the branch, except in this case, you are not erasing commits. Rather, you are adding new commits. However, both commands allow you to “undo” commits.

Image

Tonight’s talk: The RESET and REVERT commands answer the question: “Who’s the better undoer?”

The RESET command: The REVERT command:
Look, I have incredible powers. I mean, come on: I have the ability to erase history! I am absolutely what everyone should be using to undo bad commits.  
  Yeah, but you’re so negative. Going back in time? Really? I’m a glass-half-full kind of person—I let folks undo their mistakes by adding to their commit history. This keeps their commit history intact. Not to mention I’m a lot easier for people to wrap their heads around.
So two wrongs make a right, huh? No way! I allow for a clean commit history. If you’ve mistakenly committed some work, why would you want a constant reminder?  
  Sure. But you are more complicated to use. There’s the “soft” mode, and then there’s the “mixed” mode. Not to mention “hard” mode, which is destructive. People can lose their changes if they accidentally run you in hard mode!
It’s called flexibility! I give the people what they want—choices. What do you do? Create a commit that’s the exact opposite of the commit to be undone. Pfft! Our readers could do that manually. So what good are you?  
  Undoing commits by hand can involve hundreds of files or changes. It’s so tedious, not to mention error-prone. I automate that away. Isn’t that what computers are for?
Fine. Whatever.  
  I’m just going say one last thing, so listen up: once our readers learn how to use Git as a collaboration tool, they won’t need you. Maybe you should consider another line of work.
Ha! We’ll see about that.  
Note

We will be revisiting this topic in Chapter 5. Stay tuned!

Aaaaand that’s a wrap!

Trinity is so excited for Gitanjali and Aref—all the effort that she put into planning their party paid off. Everyone had a wonderful time celebrating their engagement. She wishes them the very best in their life together.

Trinity’s commit history looks clean: just the way she likes it. She still has some cleanup to do, so she merges the boardgame-night branch into the master branch. She also deletes the unmerged glamping-trip branch. Planning the glamping trip was indeed a lot of work, but hey! As long as her clients are happy, Trinity’s happy.

Note

We talked about cleaning up your branches (merged and unmerged) in Chapter 2.

Not to mention, Gitanjali now wants Trinity to plan the wedding, too! She wants something really exotic. (The South Pole has been mentioned once or twice—Trinity may have to talk her out of that one. Or not!) Oh well. Time to create another repository.

Image

As for you, well done! It was quite a journey learning how to undo your work in Git. Just remember: Git, for the most part, is not destructive when you undo. In other words, you can undo an undo if you need to, so breathe easy.

Undo Crossword

Image

Here’s a bonus tip for undoing your errors: solve this crossword using a pencil.

Image

Across

3 Git reset modes include soft, ___, and hard

5 The -m flag is short for this

6 Command to compare the index’s contents with the working directory (2 words)

9 The git ___ command replaces the version of the file in the working directory with the version in the index

10 She’s getting married to Aref

12 Character that appears in the git branch output to tell you where HEAD is

13 This chapter is all about ___-ing your mistakes

14 Before you edit a commit, make sure your working directory is ___

15 Flag used with the git restore command to retrieve the last contents of your files as they were in the last commit

16 Fancier version of camping

17 The -u flag to the git add command is short for this

Down

1 Character used with HEAD to reference a commit’s parent

2 The git ___ command copies the contents of the index into the object database

4 It’s a fruit!

7 Deleting a file won’t remove it from the object ____

8 A piece of metadata that tells you when the commit was made

9 The git ___ command moves HEAD and a branch to a specific commit

11 Flag that lets you fix a mistake in a commit message

15 Use the git ___ command to check the state of your Git repository

16 The ___ ___ command removes tracked files from the working directory and the index (2 words)

Image Answers in “Undo Crossword Solution”.

Undo Crossword Solution

Image

From “Undo Crossword”.

Here’s a bonus tip for undoing your errors: solve this crossword using a pencil.

Image

Get Head First Git now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.