Git, the easy way: changing history using rebase—part II
Or Kamara
17 de junho de 2020
0 minutos de leituraWelcome back to the second part of how to work with git, the easy way!
In the previous article, we discussed the basics of Rebase as well as two common scenarios that all of us might encounter when working with git. You can find the first part here. In this part, we will go over four more common scenarios and some conclusions.
Let’s jump right in!
Common scenarios (and how to handle them!)—part II
Let’s continue where we left off the last time with some common scenarios where you might want to change your code history, and how to do it using Git:
1. Split an old commit into two (edit)
Let’s say you just realized you've made a mistake when merging 2 commits (see part I for more details), and you want to revert the squash of 17a73c1 into e2952e7, so the new file will be in a different commit. The solution here would be to run all the commits to the specific point you need to edit, stop, do the changes, and continue. Or in other words, use the edit option.
We’ll start by running git rebase -i master again, but this time with the edit option on the commit we want like to split (the 2nd commit, `6871683`):
After exiting the rebase page, we should see:
So, git basically ran the 1st and the 2nd commits, stopped, and now the ball is in our hands. We can do all the changes we want!
This is our git graph right now:
As mentioned before, we basically want to break the 2nd commit into 2 different commits, one per file. The first step would be to reset the changes of the 2nd commit by running git reset --soft HEAD^. The reset command changes that commit the branch HEAD is currently pointing at. By using the --soft option, we can keep the index and the working directory, while all of the files changed between the original HEAD and the commit will be staged.
This is the new git status. As you can see, both of the files are stashed:
Now, let’s take b2.txt from the staged files list by running git restore --staged b2.txt:
With only b.txt as a staged file, we can now commit the first part of the 2nd commit. Then we stage b2.txt and commit the second part of the 2nd commit:
Our git graph now looks like this:
The final step would be to continue with the rebase and run the rest of the commits by doing
git rebase --continue. The final git graph should look like this:
2. Modify all the files in a specific commit (edit)
You’re probably all familiar with the next scenario—you pushed all of your code, perfectly split into multiple commits, but then you understand that you had forgotten to run linter on your code. As we explained earlier, the solution shouldn’t be another commit named fix linting but actually to fix the linting for each and every commit.
If the change is a minor one, we can use the squash option, but if there are multiple changes across multiple commits, it will be for the best to run our linting script 1-by-1 on every commit (or on the specific commits we want to fix). For that, we can use the edit option again (as we did in the previous example).
Let’s run git commit rebase -i again while using the edit option on the 1st commit (a commit with multiple files):
As before, git stops after running the 1st commit:
Now we can run our linter (e.g. npm run lint) and check the status of our repo:
As you can see, the linter script changed the content of 6 files (a*.txt). The only thing to do now is to amend the current changes (by staging and committing the changes) and continue with the rebase:
And the git graph should be:
3. Make sure that tests pass for every commit (exec)
When splitting your code into multiple commits, you should remember that every commit should stand by itself. In other words, after merging multiple commits into one of the main branches, you should be able to jump back to each one of them, while expecting the code to compile / all the tests to pass.
So how can you validate that before merging the commits?
Let’s run the out tests on each and every commit manually! Just kidding, of course—we should do it automatically :) for that, we can use the exec option.
This option basically helps us by running shell scripts as part of the rebase process. For that, we just need to add new lines with the exec command wherever we want. For example, let’s run again git rebase -i master :
Now to the fun part—running the tests (by executing the script ./run_tests.sh) after each commit:
And this is the output when exiting the rebase windows:
As you can see, git executed the tests script after running each commit—so, now we know that something in the commit feat: 2nd commit fails the tests. It’s also possible to add a single exec command after each commit by running:
$ git rebase -i --exec "./run_tests.sh"
Last (actually first) thing to know—how to abort
So this is not a scenario, but more a warning—the rebase command might be dangerous!Using it without understanding the effects of the commands, might ruin your work. That’s why I’ll start with 2 recommendations:
Before you start playing with the commits, always make sure you have a proper backup of your git repo. The simplest thing I like to do is to push all of my changes to a remote branch. If something bad happens, I can always come back to my original code (using
git reset).Make sure you understand the commands. Instead of using them for the first time on your actual code, test them before on a temp git repo.
Keep in mind that you can always stop the rebase process, and return back to the original state:
On the initial rebase screen—just delete all of the commit lines, and nothing will change.
In the middle of the rebase process—if for some reason you got lost, and you’re not sure what to do, you can always
git rebase --abort. This command stops the rebase process, and returns the state of your repository back to the original one (before runninggit rebase -i).
Summing up
As we saw in the last few examples, git rebase is a powerful command, specifically when using the interactive way. There is an endless list of cases where we can use it, but I really hope I’ve managed to give you a basic understanding of when each one of the options is useful.
The main thing I would like you to take from this article is to use git rebase smartly and bravely. I know that the beginning might be a bit frightening, and you probably want someone to hold your hand while pressing the enter key, but that’s totally OK. As long as you keep practicing, it will become more of a routine. What’s more, I’m sure there are many more scenarios, tailored to your specific use cases, where this command is useful.
Enjoy!
Primeiros passos com Capture the Flag
Saiba como resolver desafios de Capture the Flag assistindo ao nosso workshop virtual de conceitos básicos sob demanda.
