Sometimes its nice to clean up commits before merging them into your main code repo; in this lesson, we go over using
git rebase to
squash commits together and then rename the condensed commit message. We also talk about potential issues with rebasing and where to be careful.
[00:01] In our command line, we're inside of a directory called utility functions, which is a Git repository. I've been working on refactoring some of the code in this project to use ECMAScript 2015 syntax. If I run Git status, we can see that we have six commits that haven't been pushed to master yet.
[00:18] We'd like to clean up our commits before we push them. The first thing we need to do is grab the latest code from our remote using Git fetch. Now, let's use the Git log command with our origin master branch, and we'll compare it with our current branch.
[00:34] Here, we have the six commits that we've made that we have not pushed to master yet. It looks like we fixed some lint warnings, and we also made some implicit return refactoring changes. It looks like there are a couple of those.
[00:49] We upgraded to ECMAScript 2015 modules. It looks to me like these commits were all small pieces of what should be one commit, which is upgrading to ECMAScript 2015 modules. Let's close our log, and get back to our command line.
[01:04] Now, let's run Git rebase-i, which stands for interactive, and then give it the branch name that we want to rebase off of. In this case, that's the origin master branch. Because we use this interactive option in with the command, when we run it, it's going to open up an interactive rebase session in the editor that we have configured in our Git config file.
[01:28] If you haven't set up your editor yet in your Git config, it will use Vim by default. In our code editor, what we're seeing here is a list of the commits that are different between our local branch and the branch that we used with the Git rebase option.
[01:44] We can see all six commits that we saw in our log. By default, each of these commit lines has the word pick in front of it. If you look down in this command section, it says that pick, which you could also write p for short, means that we're going to use that commit as-is.
[02:00] If we were to save and close this file right now, it would use all six of these commits. While we're in this rebase session, we can change our commits to clean them up. We see other options here that we can use other than pick.
[02:12] There's reword, edit, and squash. If I keep scrolling down the page, you see there's even more. In our case, we want to use the squash command to meld the previous commits into another commit.
[02:26] The way that works is we leave the pick option on our first commit, and then we change the other commits to use the squash command, which we can abbreviate to S for short, as it says down here. S is the same as squash.
[02:38] Let's do that for our other four commits. Now, when we save and close this file, Git is going to proceed with the rebase. We saw Git processing our commands from the rebase options. Now, it's opened up another screen in our editor that says that this is a culmination of six commits.
[02:57] It's showing us what the commit message will be for the combined commit. If that commit message looks good, which it does in this case, we can save and close our file, and it will continue with the rebase.
[03:07] If we wanted to, we could come down to this line, and change it to some other commit message. In this case, I'm going to leave it as upgrade to ECMAScript 2016 modules. Let's save and close this file, and let Git finish the rebase. Now, it says here that it has successfully rebased and updated our project.
[03:29] Now, if we run Git status, it says that our branch is ahead of master by only one commit. If we rerun our Git log command, there's only a single commit that has the combined commit messages of all of the commits that we squashed, with the first commit message being the main commit message.
[03:48] Let's run Git push now to push our single condensed commit to our remote repo. Now, if we take a look at our commits on our remote repository, it's nice and clean, with a single commit for everything that we did to upgrade to ECMAScript 2015 modules.
[04:05] If we go into that commit, we can see all of our commit code that was combined into a single commit. Our commit message has the condensed commit message. By default, it also included the other commit messages from our squashed commits.
[04:20] We can see the same clean Git history in our local repo by running Git log with the one line command. We have this single upgrade to ECMAScript 2015 commit.
[04:30] One thing to note is that a rebase is destructive. It actually changes your Git history. You shouldn't use a rebase on code that's already been put in your master branch on your remote repository that other developers might be using. A rebase has the same function as a Git merge, but it cleans up and destroys history, whereas a merge preserves all history, and includes a merge commit.
[04:53] The bottom line is that, as long as you only need to clean up commits that you've made locally or in a pull request branch, you can use rebase to clean them up before you merge them into your main master branch.
[05:04] If you have already pushed your commits to a pull request branch, then after you run the rebase, because it's destructive, you'll need to run Git push -f, for force, to let Git know that you're OK with destroying the history that's in a remote branch.
[05:19] Again, be careful with this, and only use a rebase and a force push if you're working on code that hasn't been made public yet. One other thing to note is that, if at any time during a rebase, you realize you've made a mistake, you can get run the Git rebase command with the abort flag to stop the rebase, and return your repo to its state before you started the rebase.
Does git push --force-with-lease will do a better job?
@omar, thanks for the comment. I didn't include it
--force-with-lease in this course for two reasons 1) it is only available in later versions of git that not everyone has and 2) in my opinion, you should never use
--force except on your own branch that only you are working on, so
---force-with-lease have the same result (since there is nobody else's work that you could possibly override). So, in my mind,
---force-with-lease should never be needed because you shouldn't force a push if it is going to a place where someone else is contributing as well - where history could be damaged. But that's my 2 cents :)