In the previous blog, we saw about moving around branches and merging two branches. This time around we are going to explore a more ‘feared‘ portion of git. Rebasing
What is Rebasing ?
git rebase is a complex tool in git which can be used to perform various operations such as editing commits, reforming commits , changing commit order etc. But on a more specific note , rebasing is the operation by which the structure of a commit tree can be changed.
The most basic rebase command is syncing commits between an upstream branch and the current branch. ( An upstream is a branch/repository from which the current branch/repository is forked/branched from ! ).
Consider the tree,
* ad9631e (HEAD -> bugfix) commit 13 * ebc508e commit 12 | * 6405f66 (new_feature) commit 11 | * 4cf8fe7 commit 10 |/ * 0e180e8 commit 9 * 79d8019 commit 8 * 75d11f0 commit 7 * f6338e0 (master) Sixth commit * 5c0595d fifth file * b4798cb Fourth commit * 71764ed fajfgj * 677358d second file * d6ed37b first file
There is this important bugfix that is happening from the master branch. But concurrently your team is working on a feature that would only be useful when the bug is fixed. So your team has branched from your bugfix branch to create a new feature branch. But still.. your bugfix is not over so you are pushing commits to the bugfix branch.
One of the branch features is that, once diverged the commits of one branch dont affect another. So whatever bugfix that has happened will not be available in the new_feature branch. But inorder to test the new feature, the updates to the bugfix is badly needed. What will you do in this situation ?
One way of handling this situation is to generate a patch between commits ad9631 and 0e180e8 and apply it on the new feature branch. But applying patches are often messy.Applying the patch takes the bugfix commits above the commits done in feature branch, which is logically confusing and wrong sometimes. Doing this will involve taking patches of both the branches , reverting all commits of this branch , applying the bugfix patches and applying the features patch. The most direct way to acheive this is to use ‘git rebase’.
$ git rebase bugfix new_feature First, rewinding head to replay your work on top of it... Applying: commit 10 Applying: commit 11 $ git log --oneline --decorate --graph --all * eb502f2 (HEAD -> new_feature) commit 11 * 117f043 commit 10 * ad9631e (bugfix) commit 13 * ebc508e commit 12 * 0e180e8 commit 9 * 79d8019 commit 8 * 75d11f0 commit 7 * f6338e0 (master) Sixth commit * 5c0595d fifth file * b4798cb Fourth commit * 71764ed fajfgj * 677358d second file * d6ed37b first file
Once we use rebase, the first line indicates that `First, rewinding head to replay your work on top of it…`. This indicates that commit10 and commit11 of the feature branch were reverted first , then commits 12,13 were applied. Then it is also shown that commits 10,11 were reapplied. So all of that in one command . Now the tree for new feature is in sync with bugfix.
Rebase vs merge
Merging and rebasing sounds similar now .. Doesnt it ? But a merge updates the upstream , whereas a rebase updates the downstream. i.e. a Merge pushes changes from the current branch to the genesis branch , whereas a rebase pulls updates from the genesis branch to the current branch.
Also a merge tries to connect the 2 branches with a seperate commit, which ensures that the commit information need not be disturbed in any way. But when doing a rebase, Git tries to change the parent of certain commits unlike a merge, where the sequence is maintained. When changing anything related to a commit, the commit SHA changes which essentially means that it creates a new commit with the new information.
This is exactly why rebasing is considered dangerous as they completely change commit information and when done carelessly can cause loss of data
Rebases help us avoid complex branching and the need for merge commits. But rebasing can make a complex git tree disfunctional. Another problem with changing commit IDs is when using shared repositories or when using remote repos. During such operations , rebase must be used very carefully , because a badly done rebase breaks others code.
Consider this repo
$ gl * 24bab9f (HEAD -> bugfix2_bugfix) commit18 * 3869b21 commit17 | * 078300e (bugfix2) commit16 |/ * f58bf7b commit15 * 589eacd commit14 | * eb502f2 (new_feature) commit 11 | * 117f043 commit 10 | * ad9631e (bugfix) commit 13 | * ebc508e commit 12 | * 0e180e8 commit 9 | * 79d8019 commit 8 | * 75d11f0 commit 7 |/ * f6338e0 (master) Sixth commit * 5c0595d fifth file * b4798cb Fourth commit * 71764ed fajfgj * 677358d second file * d6ed37b first file
If we try to rebase bugfix2 and bugfix .. What we may expect is that commits 7,8,9,12,13 be applied first before commits 14,15,16. But this is what happens when we try that.
$ git rebase bugfix bugfix2 First, rewinding head to replay your work on top of it... $ gl * 24bab9f (bugfix2_bugfix) commit18 * 3869b21 commit17 * f58bf7b commit15 * 589eacd commit14 | * eb502f2 (new_feature) commit 11 | * 117f043 commit 10 | * ad9631e (HEAD -> bugfix2, bugfix) commit 13 | * ebc508e commit 12 | * 0e180e8 commit 9 | * 79d8019 commit 8 | * 75d11f0 commit 7 |/ * f6338e0 (master) Sixth commit * 5c0595d fifth file * b4798cb Fourth commit * 71764ed fajfgj * 677358d second file * d6ed37b first file
It is different from what we might expect. And also , even if we had gotten the state that we desired, bugfix2_bugfix would have remained floating, because if SHA of commit15 changes for bugfix2 , the bugfix2_bugfix branch would have had an invalid commit as its parent and would have resulted in an ambiguous state. There are a lot of troubles while using rebasing like these. Let us look at them in future articles.
Having seen rebasing and merging, we have seen how to integrate changes from a different branches. But there are bound to be some problems with this. One of them is Merge Conflicts. Let us discuss about them in our next article.