Rebasing in Git (Web Dev Rev wrap-up for March 26, 2021)

Rebasing is changing the base of a set of changes from one commit to another. The most common use is this: You've made a set of commits on a feature branch which was based on the latest development branch. Then, some commits were merged to the development branch. Now your feature branch is based on a commit that is no longer the latest. When you rebase, you are moving your feature branch commits onto the tip of the development branch again.

Rebasing in GitLab

Our WCMS GitLab repositories are configured to use the "Merge commit with semi-linear history" method for merging. This means that if you have a merge request which is based on a commit that is no longer the development branch tip, the merge request will not have a "Merge" button. Instead it will have a "Rebase" button which will rebase on the development branch tip. Click the button to rebase automatically.

In some cases, the rebasing cannot be done automatically because of merge conflicts. In this case, the rebasing will need to be done on the command line. After it is done, force-push the changes to your feature branch in GitLab and the merge request will update to use the new state of the feature branch.

Rebasing on the command line

To rebase on the command line, checkout your feature branch:
git checkout feature/example

Update your repository:
git fetch

Now start the rebase onto the latest version of the remote development branch:
git rebase origin/development-branch
Example:
git rebase origin/8.x-1.x

Git will try to do the rebase automatically. If there is a merge conflict, it will stop and inform you. Fix the conflict the same way as during any other merge conflict. Then continue:
git rebase --continue

If you want to stop and put everything back how it was:
git rebase --abort

You will be reminded of these commands and what to do next by running:
git status

Cleaning-up history with interactive rebasing

Imagine you have two commits: One fixed a bug and the other added a comment about something unrelated. Then you realize your bug fix has a problem, so you fix that. Next you notice a typo in your new comment and you fix that. You now have four commits: bug fix, comment, bug fix fix, comment fix. You want to combine them so you have only just two commits. Use interactive rebasing:
git rebase -i

This will rebase all commits since your last push, allowing you to re-write this part of the history. You can also specify the commit you want to rebase onto:
git rebase -i origin/8.x-1.x

Either way, this command will open an editor with a list of commits. This list is a set of instructions for Git to use as it does the rebase. It will process the commits in order from top to bottom (note that this is backwards from what you see in git log). When you pick; a commit, that commit is applied as-is. You can use fixup to combine a commit with the preceding commit. You can change the order that the commits are applied by moving lines around in the file. You can use reword to change a commit message without changing the contents. The list of commits includes help text which explains these commands and others. For example, your list of commits might start like this:

pick HASH1 Fix the bug
pick HASH2 Add a comment
pick HASH3 Make change to bug fix
pick HASH4 Correct typo in comment

Edit it like this:

pick HASH2 Add a comment
f HASH4 Correct typo in comment
r HASH1 Fix the bug
f HASH3 Make change to bug fix

After the rebase, the list of commits will be:

Add a comment
Fix the image upload bug

The commits have been combined and the bug fix commit message reworded.

Editing during interactive rebasing

You can also edit commits when rebasing. For example, consider that you have one commit which both fixes a bug and corrects an unrelated typo. When doing your interactive rebase, mark this commit edit; or e. Git will stop when it gets to this commit. You could use git commit --amend to change the commit. You could use git reset HEAD~ to un-do the commit and then re-do it in two parts. Whatever you do, when you are ready, use git rebase --continue.

Creating a clean history with interactive adding

If you have made multiple changes to a single file and you want to stage just some of them for commit, you can use interactive adding:
git add -i

Git will give a list of changed files and a menu. Type p and hit enter to run the patch command. Git will again list the files. Use their numbers to choose the files you want to work with. Git will step through the files one altered hunk at a time. For each hunk, you can choose whether or not to add it to your next commit. If a hunk has more than one change, but they are separated by at least one un-changed line you can use s to split that hunk into smaller hunks, splitting at unchanged lines. If you need to do something more elaborate, you can use e to edit the patch. Make the patch you want to apply in the next commit. The remaining changes will stay in your working copy and will not be staged for commit. If you accidentally make a patch that doen't apply, you will have the option to try editing it again.