Rebase your code! Or: how to fix your git history before requesting a pull

As git is not easy to handle for new contributors and often first time contributors having trouble with branches here is a guide how to rebase your contribution before creating a pull request. This is necessary to get a proper history as the openHAB project with many contributors. If you don’t rebase or merge the right way, you may create unintentional changes in other modules you did not even touch. Best thing to solve this, is to rebase your code changes on the current main branch. Okay let’s begin.

First of all: make sure to get a proper Terminal (Unixes do have usually a good one integrated, Windows Users should take a look on the Windows Terminal) and git setup. There are many GUIs there which should help you, but they hide the complexity you need to understand to not break the history. Do not use them if you have not done the same actions in command line once.

In my example i’ve made a change in my existing binding. It consists just in an edit of the README. However this is enough to demonstrate how to do things. Here is my change on a branch at github:

So far so good… but the main branch i forked from is already three months old! This is bad, maybe my change won’t even work, because the code i’ve changed collides with other changes that happened in between. (In this academic example where only the README.md was touched this is no problem. But in other files this could be a massive problem.)

As the changes always need to be compatible with the current main we need to update our branch to contain the current state (usually you want to merge into main). So you could now do a merge. Okay let’s do that. Before let’s take a backup just in case and take a look in the local commit history (git log, use git status for checking if the copy is “clean” and if you are on the right branch).

Screenshot_20211128_173553

You can see my commit and commits of other people, where origin/main is marking the commit that is “live” in my forked main. To get a fresh version from openHAB’s main, github offers a nice button solution to synchronize changes. But we do not know what’s happening in background. Maybe it’s good, maybe not. So let’s do that on CLI. We need to add the original repository as “remote” (repository) to sync from:

Here i added the openHAB repository using the alias upstream (command git remote add upstream https://github.com/openhab/openhab-addons). After that, fetch the remote to make git know the changes of the original project:

Now we can update our branch. Normally, the helper GUI tools tend to offer a merge instead of a rebase. Please don’t do that. Here i will do that once, just to show what happens.

Wait, what? How did the merge make so many changes? Let’s look into history:

Okay, another commit was made, a merge commit. It contains any changes that happened since i forked from main and is now part of my branch. Okay, at least the official changes are now also in my branch. But there is a problem. They are part of the commit history after (!) the repository was forked and are in that case also part of the future pull request. That is bad, as i don’t want to be responsible for changes of other people. And now i’ve seen i did not sign off my commit. So the DCO-check at github when doing a Pull Request will block it. Let’s fix it.

It’s required to base our changes on the current main. One solution would be to create a new fork, but that’s somehow cumbersome, let’s do it right.

Rebasing your commits

What does rebasing mean? Git is primarily just a collection of diffs with descriptions where the order matters. To fix our issue, my changes have to be applied at the last position. This changes the history as my change was added before i’ve merged. Rebasing is potentially destructive, so make sure you have a backup.

As my commit message is broken, i’ll need to change it also. This can be done doing an interactive rebase using following command:

git rebase -i upstream/main <your branch name>

You will get an editor listing all your commits and hopefully no commits of other people. If the latter is the case you had a merge conflict or somehow messed up your history (usually by merging things). Then you need to pick only your commits and delete all of the other commits to ensure your branch only contains your changes. In this case my commit just needs a new message so i can sign it off. So i’m replacing the pick of the commit by reword and save the file. Before closing the editor please take a quick look on the available commands, maybe you’ll need them in future, just good to know.

When closing the editor, git knows to continue and now “replays” your changes basing on upstream/main. As reword is an interactive rebase-command, another editor opens so i can edit my commit message and add my signature:

After saving and closing, other listed changes in the branch will be also applied in the order you set in the file opened by git rebase -i .... Commits not listed there won’t be applied - so this is also a way to remove commits completely. If something conflicts, you will be asked by git to correct the files and after that to resume the rebase, in that case the exact commands to continue are displayed.

Now the branch is rebased. Let’s look into the log if our history is now better and contains fresh commits from the openHAB project:

Yes, looks better. The time of the commit before mine is close to now (when i took the screenshot) and only the one commit i made is listed after upstream/main. Additionally a very relevant detail also changed: The commit id (for the reference, check output of git log before and after rebase). So the the history has been changed, the old commit id is intentionally lost. Okay let’s push our change to github, as the log looks good.

As we changed the history and the server did already know the last state, it rejects the branch. That is intended to make sure you are not breaking the history. To solve this, a git push --force (Note 1) is even better, when working with other is needed:

And you are ready to go! Now you can create a pull request in a proper way with a cleaned history. The contributors will thank you!

Notes/Edits:
1: @doktormo recommends a git push --force-with-lease instead of a git push --force when working with multiple people. I did not know this parameter before. What is happening there: a --force will push anything what’s in your local copy, no matter what the server’s repository contains. This can be fatal if a second person is working on the branch while you are rebasing because a --force would throw away the changes. --force-with-lease rejects the push in that case. Pull the branch and rebase then again.

12 Likes

Very well guide.
Going in fast track:

git fetch origin
git checkout mybinding
git pull —rebase origin main
// solve conflicts
git push myfork mybinding:mybinding

By this way you pull directly remote changes in your branch. If you fail to solve changes just do git rebase —abort.

Few points origin - points to openhab addons. Remote myfork points contributor openhab-addons fork. The mybinding is branch name both in local checkout and fork.

Thanks for the guide!
Please change the last push to git push --force-with-lease
It might not be necessary when working on your own, but there is a potential chance to overwrite other’s work with git push --force

1 Like

Thanks, i did not know this parameter before. A note has been added.

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.