GIT Rebase
A common workflow in git is to create a feature or bugfix branch for your work before you start. In case you are using rebase workflow, or just want to keep a clean commit log, you should rebase your branch to the target branch.
Shortcut #
A quick way to do rebase is to use the -r switch in the pull command.
For example: you are working on your branch and want to merge to master. To rebase you can check out your branch and use the following command: git pull -r origin master
What is rebase #
Rebase reverts all commits from a branch, removes a current branch, creates a new one from the target branch and replies the commits. This means that the commit history is modified. It is important to never rebase public branch or a branch that we shared with someone. We can only rebase a branch that is our private. If not, we can cause a lot of troubles to our fellow contributors.
What if we pushed the branch before rebasing #
Let's imagine we have a branch named main
. There is only one file with the content:
# LearnGit
## First chapter
TODO
## Second chapter
TODO
## Third chapter
TODO
## Fourth chapter
TODO
Now, we create a second branch named second
. After that, we add another commit to the main
branch, so the file looks like:
# LearnGit
## First chapter
Description of the first chapter
## Second chapter
TODO
## Third chapter
TODO
## Fourth chapter
TODO
We switch to the second
branch and add description of the second chapter and commit it. The file content is:
# LearnGit
## First chapter
TODO
## Second chapter
Description of the second chapter
## Third chapter
TODO
## Fourth chapter
TODO
After we add commit on second branch we check the git log and see:
bdd614a Add desc 2
3aab9bb Initial commit
We show output of
git log --oneline
command. The first column is commit SHA and the second one is commit message. The HEAD info is omitted for clarity. This structure is used in all further examples below.
Here is the graph of the log above:
gitGraph LR: commit id: "3aab9bb" branch second commit id: "bdd614a" checkout main commit id: "88c96b4"
Let's push the current state to the remote and then rebase the second
branch. After we rebase we see the following in the log:
e6ef94e Add desc 2
88c96b4 Add desc 1
3aab9bb Initial commit
Here is the graph of the log above:
gitGraph LR: commit id: "3aab9bb" commit id: "88c96b4" branch second commit id: "e6ef94e"
We received a commit 88c96b4
with description 1. The original commit that was committed to second
branch with id bdd614a
is no longer there. The reason is that git reverted that commit, took all commits from the main
branch and replayed a content of the bdd614a
as a new commit. This commit is different, because it has different parent. If it was modifying the same line that was modified in the any of the newly added commits, then we would have a conflict. But this is for another time.
Let's push our changes to remote #
Normally, after we are happy with our commits we want tp push our changes. In case we try to push after rebase we would be met with the following error:
! [rejected] second -> second (non-fast-forward)
error: failed to push some refs to 'https://github.com/bojanpikl/LearnGit.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Because we pushed the second
branch to the remote before rebasing, we now have different commits on local second
branch and remote second
branch. Eventually, they should be synchronized. From what I have seen, often times we can do a mistake on this step and take the wrong direction.
The wrong step after rebase - pull #
Since the branches locally and on remote are different, we can pull changes from remote. This is usually what we do, since we want to receive other's changes before contributing our own. However, as we have seen, during rebasing the history changed. So, if we pull from remote, we get:
1a3ebe9 Merge branch 'second' of https://github.com/bojanpikl/LearnGit into second
e6ef94e Add desc 2
bdd614a Add desc 2
88c96b4 Add desc 1
3aab9bb Initial commit
Here is the graph of the log above:
gitGraph LR: commit id: "3aab9bb" commit id: "88c96b4" branch second commit id: "bdd614a" commit id: "e6ef94e" commit id: "1a3ebe9" type:HIGHLIGHT
Reading the output we see that the last 3 commits are strange. We have two commits with the same message Add desc 2
, however, we only ever created one such commit. To understand what happened, we can check the SHA. One commit is from the original commit, and the second from the rebased commit. The last commit is a merge commit. We will ignore it in scope of this article, but by default git pull
does merge operation, so we get merge commit.
What we see is that if we pull after rebase, our commits will duplicate. This happens because GIT doesn't know that the commits bdd614a
and e6ef94e
have the same content. To git these two commits are simply two different commits. But what should be done?
The correct step after rebase - force push #
After rebase, we know that our push
will be rejected. Now, we have learned that pull
is a bad option, because it makes ugly log history and can even lead to bugs (in case of merge conflicts). If the branch exists on the remote, it is important that we push our changes there. To do this, git offers a force
and force-with-lease
flags. In general, if you plan to use force
be sure you know very well what are you doing, because this is a very powerful command that can lead to loose of data.
Much safer flag is force-with-lease
. This flag allows us to push after modifying the history. It however, checks that the remote was not modified after we last pushed. This guarantees us that in case our fellow contributor pushed some changes we will be notified about this instead of simply loosing them. So, after rebase, make sure to git push --force-with-lease
, so that the remote will receive a new log history as well.
What if you rebased on remote #
In case you rebased on the remote, I would advise to remove your local branch git branch -d second
and then fetch changes. A new branch will be created for you. Of course, be careful not to have any commits locally that you haven't pushed before rebasing, or deleting branch.
- Previous: GIT Clean repo
- Next: Docker Copy file from/to container