Git Rebase: usage guide

  • Tutorial
Rebase is one of two ways to combine changes made in one branch with another branch. Novice and even experienced git users sometimes feel reluctant to use it, because they don’t see the point of mastering another way to merge the changes when they already have a good command of the merge operation. In this article, I would like to examine in detail the theory and practice of using rebase.

Theory


So, let's refresh the theoretical knowledge of what rebase is. To begin with briefly - you have two branches - master and feature , both local, feature was created from master in state A and contains the commits C, D and E. In the master branch, after separating the feature branch from it, 1 commit B was made .



After applying the rebase master operation in the feature branch, the commit tree will look like:



Note that the commits C ', D' and E 'are not equal to C, D and E, they have different hashes, but the changes (deltas) that they carry within themselves are ideally the same. The difference in commits is due to the fact that they have a different base (in the first case - A, in the second - B), the differences in deltas, if any, are due to the resolution of conflict situations that occurred during rebase. More on this later.

This state has one important advantage over the first one, when merging the feature branch into the master branch, it can be combined on fast-forward, which eliminates the occurrence of conflicts during this operation, in addition, the code in the feature branch is more relevant, since it takes into account changes made in the branch master in commit B.

Rebase process in detail


Now let's deal with the mechanics of this process, how exactly did tree 1 turn into tree 2?

Let me remind you that before rebase you are in the feature branch, that is, your HEAD looks at the feature pointer, which in turn looks at the commit E. You pass the identifier of the master branch to the command as an argument:

git rebase master

First, git finds the base commit, the common parent of these two states. In this case, it is commit A. Next, moving in the direction of your current HEAD git calculates the difference for each pair of commits, in the first step between A and C, let's call it Δ AC . This delta applies to the current state of the master branch. If a conflict state does not occur, a commit C 'is created, thus C' = B + Δ AC . The master and feature branches are not shifted, however, HEAD is moved to a new commit (C '), bringing your repository to a detached HEAD state.


Having successfully created the commit C ', git proceeds to transfer the following changes - Δ CD . Suppose that when these changes were applied to the commit C ', a conflict arose. The rebase process stops (at this very moment, by typing git status you may find that you are in detached HEAD state). The changes made by Δ CD are in your working copy and (except for the conflicting ones) prepared for the commit (the dotted line indicates the stage zone):


Then you can take the following steps:

1. Cancel the rebase process by typing in the console

git rebase --abort

In this case, the HEAD marker will be transferred back to the feature branch, and already added commits will hang in the air (not a single pointer will point to them) and will be deleted soon.

2. Resolve the conflict in your favorite merge-tool, prepare files for the commit by typing git add %filename%. Having done this with all conflicting files, continue the rebase process by typing in the console

git rebase --continue

In this case, if all conflicts are really resolved, a D 'commit will be created and rebase will go to the next, in this example, last step.

3. If the changes made during the formation of commit B and commit D are completely mutually exclusive, and the “correct” changes are made in commit B, then you will not be able to continue typing git rebase --continue, since resolving conflicts you will find that there are no changes in the working copy. In this case, you need to skip creating the commit D 'by typing

git rebase --skip

After applying the changes Δ DE , the last commit E 'will be created, the feature branch pointer will be set to E', and HEAD will point to the feature branch - now, you are in the state in the second figure, rebase is over. The old commits C, D, and E are no longer needed.


In this case, the commits created during the rebase process will contain data both about the original author and the date of changes (Author), and about the user who made rebase (Commiter):

commit 0244215614ce6886c9e7d75755601f94b8e19729
Author: sloot69 <*** @ ****. Com>
AuthorDate: Mon Nov 26 26:19:08 2012 +0400
Commit: Alex <*** @ ****. Com>
CommitDate: Mon Nov 26 13:33:27 2012 +0400


From heaven to earth - rebase in real life


In fact, you usually do not work with two branches, but with four in the simplest case: master, origin / master, feature and origin / feature. At the same time, rebase is possible both between a branch and its origin, for example feature and origin / feature, and between local branches of feature and master.

Rebase branches with origin


If you want to start working with rebase, it is best to start with a rebase of your changes in the branch relative to its copy in the remote repository. The fact is that when you add a commit and a commit is added to the remote repository, merge is used by default to merge the changes. It looks something like this:



Imagine a speculative situation - 3 developers are actively working with the master branch in a remote repository. Making commit at the same time on their machines, they send each 1 change to the branch. At the same time, the first one sends them without problems. The second and third are faced with the fact that the branch cannot be sent by the operation git push origin master, since it already has changes that are not synchronized to the local machines of the developers. Both developers (2 and 3) do it git pull origin masterwhile creating local merge commits in their repository. The second makes git push the first. The third one, when trying to send changes, again encounters updating the remote branch and again does git pull, creating another merge commit. Finally, the third developer makes a successfulgit push origin master. If the remote repository is located on github, for example, then network, that is, the commit graph will look like this:

Three commits turned into 6 (we do not consider the basic commit), the history of changes is unjustifiably confused, information on the union of local branches with remote ones, in my opinion, is superfluous. If you scale this situation into several thematic branches and a larger number of developers, the graph may look, for example, like this:



Analysis of changes in such a column is an unreasonably laborious task. How can rebase help here?
If instead of git pull origin masterexecuting git pull --rebase origin master, then your local changes, like the commits C, D and E from the first example, will be transferred to the top of the updated state of the origin / master branch, allowing you to transfer them to the remote server using without creating additional commits git push origin master. That is, the merger of the changes will already look like this:



As you can see, there were no "extra" merge commits; your changes to the C commit were used to create the C 'commit. The commit C itself remained in your local repository, only C 'was transferred to the remote repository. If all programmers in the team make it a rule to use git pull --rebase, then each of the branches in the remote repository will look linear.

How to share a branch that rebase is applied to with a colleague


In detail, the process of rebasing a local topic branch relative to the master branch was considered at the very beginning of the article. I can only make a reservation that the rebase process contains the number of steps equal to the number of commits in your local branch. The probability of potential conflicts, as in the case of merge, grows in proportion to the increase in the number of commits in the base branch (master). Therefore, it is better to periodically rebase for long-lived theme branches. But if a topic branch has its original on a remote server, how do I transfer the branch to a remote repository? If you can push only for changes that can be accepted by fast-forward, but in this case, as we know, this is not so:


It's simple, type the command in the console:

git push origin feature --force

Force mode simply copies the missing parent commits of the feature branch to origin and forcibly sets the branch pointer to the same commit as your local one.

Be careful! If you forget to specify a branch identifier, then force-push will be executed for all local branches that have a remote original. It should be understood that some local branches may be in an irrelevant state. That is, changes that you did not have time to tighten will be deleted in origin. Of course, the commits themselves will not be deleted - only the branch pointers will be reset. This situation is fixable - it’s enough for each branch to find the person who was the last to push changes to it or has already managed to pick them up. He can do a regular push, again passing them to origin. But all this trouble is useless to you, so it’s better just to be careful.

Your colleague is in the same situation before pull that you were in before pushing. Only feature and origin / feature positions differ exactly the opposite. If he does the usual one git pull origin feature, then an attempt will be made to merge the old and new branches using merge. Since the old and new branches contain the same changes, all of them will be conflicting. In this case, the command will help us again:

git pull --rebase origin feature

She will pick up the new feature branch and push your colleague's local changes to its top.
In general git pull --rebase origin feature, this is a win-win option, since if rebase is not required, normal fast-forward pointer joining will occur.

Reintegration of the thematic branch in master


We examined all the necessary operations for working with branches in the rebase style. It remains only to switch to the master branch and make a git merge feature. The branch prepared by rebase will merge into master by fast-forward, that is, the pointer will simply be moved forward.

However, this approach has one drawback - after a fast-forward merge, it will be difficult to determine which commits were made in your branch and which commits were made in another branch infused in front of yours or directly in the master. A good tone can be the adoption of a rule in your team - when integrating changes from a topic branch into the main branch, use the merge operation with the --no-ff option. In this case, a merge commit will be created, one of whose parents will be the position of the master branch, and the other - the position of the feature branch. Compare the commit graph when combining branches by fast-forward (left) and with merge commits obtained with the --no-ff option (right):

In this case, in my opinion, merge commits are useful and carry information about when the branches merged. This graph looks like a case study, but such a structure is quite real if some simple rules are followed by all team members.

Conclusion


We see that the readability of the change graph can be improved by an order of magnitude, subject to a few simple rules, although they require little additional time.

The official git manual strongly discourages applying rebase to a branch that has been launched into the public repository; problems related to the merge of an old branch that has not yet been applied to rebase (stored by someone who managed to download it from the repository ) and a new branch, which leads to conflicts and commits with the same dates and messages in the change log, here we looked at how easily this problem can be avoided with git pull --rebase.

This article makes one assumption. All this is true with a simple branching model - there is one main branch master and several thematic ones that are created from it. When another thematic branch is created from a topic branch, there are some nuances when rebasing the primary and secondary branches. You can read about them in that very official guide .

Sometimes disputes about what is better merge or rebase reach the holivar. From myself, I can say that ultimately the choice is yours, but this choice cannot be dictated by the level of ownership of a particular instrument. A reasonable choice can be made only when it is not difficult for you to work in either style. I do not campaign for the widespread use of rebase, but simply explain how to use it. I hope this article will help you to remove issues related to the mechanism of work of rebase and its application in daily work.

PS. I want to thank my colleagues, productive conversations with which allowed me to better understand the material underlying this article.

Also popular now: