Git Rebase



Rebasing consists of moving or combining a series of commits to a new base commit. Rebasing is most used in the context of a feature branching workflow. Rebasing a branch is the process of updating one branch into another by applying the sequence commits of one branch on top of the other branch's commits. This is done by executing the git rebase command.

Rebasing is often used as an alternative to merging.

The general process of rebasing can be illustrated as follows:

git rebase

Git accomplishes rebasing by creating new commits from old commits and applying them to the specified base. Even if the branch looks the same, it is composed entirely of new commits.


Usage of the Git Rebase Command

The main reason for rebasing is to maintain a linear project history. For example, consider the case where some new commits have been added to the Master branch after you started working in the Feature branch. You want to include the recent update to the Master branch in your Feature branch, but you want to reserve your Feature branch history clean, so it looks like you are working from the latest master branch.

There are two ways for integrating a Feature branch into the Master branch:

  • Merging directly: This option results in a 3-way merge and a merge commit
  • Rebasing and then merging: This option results in a fast-forward merge and perfectly linear history.

Rebasing is a popular way to include upstream modifications into your local repository. Pulling upstream modifications with Git merge results in an excessive merge commit every time you want to see the project's progression.


What are the Advantages of Rebasing

No Merge Commits

Merging two branches every time requires a merge commit. So if a Feature branch is continually updated with a Master branch using a merge, the results will be the creation of different merge commits that will make the History less clear. These merge commits can be avoided if rebasing is used instead of merging to update the Feature branch.

Linear and Sequential Git History

When rebasing a branch is used before a merge, all the Feature branch commits are grouped at the end of the Master branch. While the Feature branch commits are distributed in the Master branch when a rebase is not used before a merge. The reason for this is that merging arranges the commits chronologically.

Rebasing groups the Feature commits together, which makes it simple to understand and analyze the Git History.

Simpler Time Resolving Conflicts

When using the rebase, Git applies each commit one at a time to resolve conflicts progressively. However, all the conflicts must be resolved using the merge at once, making it a little more difficult to handle.

While rebasing if a conflict is encountered, Git will indicate which files are causing the conflict. After resolving the conflict, Git offers the git rebase --continue command to resume the process of rebasing and the git rebase --abort command to cancel the rebase and leave the branch untouched.

Well Organized and Clean Git History

Rebase also has the advantage of changing commit messages, combining commits, and reorganizing commits. Using the rebase makes the History very clean so easy to understand. Having a clean Git History helps find when a certain bug or Feature was introduced to the project.


Avoid Rebasing Public History

Never rebase commits that have already been published in a public repository. Same as resetting and amending, rebasing if used in public history will cause problems for team collaboration. The rebase would replace the old commits with new ones, and it would seem like that part of project history has vanished.


Git Rebase Standard vs. Git Rebase Interactive

There are two ways to use the git rebase command:

  • Standard Rebasing
  • Interactive Rebasing

Standard Rebasing:

In standard mode git rebase command will take the commits in the current working branch and apply them in the passed branch's HEAD.

$ git rebase <base>

Above the current branch will be rebased onto . The base () can be any commit references like a branch name, an ID, a tag, or a relative reference to HEAD.

Interactive Rebasing

The git rebase command's interactive mode is executed with the -i or --interactive flag, which will start an interactive rebasing session. Rather than using a standard rebase that will move all the commits to the new base at once without changing anything, the interactive rebase allow altering individual commits in the process. This gives the possibility to clean up history by removing, combining, and changing the existing sequence of commits.

To start an interactive rebase, run the following command :

$ git rebase -i <base>

The above command will start the interactive rebasing by opening the default editor, where you can choose between commands (described below ) to be applied before the commit is rebased. These commands define how individuals commit will be moved to the new base. Once you finished selecting commands for each commit in the rebase, Git will start playing back commits implementing the rebase commands.

The rebasing edit commands look as follows:

pick 4b3251c adding more text on file1
pick c3bf48e file1 modified

# Rebase 6b5548f..c3bf48e onto 6b5548f (2 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

Additional interactive rebase commands

The git rebase command has some additional command options that can be useful in more complex scenarios.

  • git rebase option d means that the commit will be removed from the final combined commit block during playback.
  • git rebase option p means that the commit will stay at it is. The commit's message or its content will not be touched.
  • git rebase option x means that during the playback, executes a command-line shell script on each marked commit. Running a test series on specific commits will be a useful example.

Interactive Rebase Recap

Interactive rebasing gives complete control over what the project History will look like. This gives developers the freedom to work in the way they want on their Feature branch because they have the possibility to come back after and clean the History before merging into the main codebase.

The use of interactive rebasing keeps the project History clean and significant.


Configuration options

Some rebase properties can be set using the help of the git config command. These options change the git rebase output look and sense.

  • rebase.autoSquash: A boolean value toggling the --autosquash behavior

  • rebase.stat: A boolean, the default option is False. The option switches display of visual diffstat content that indicates what changed since the last rebase.

  • rebase.instructionFormat: A string in git log format used for formatting interactive rebase output.

  • rebase.missingCommitsCheck: It can be set to different values, which change rebase behavior around missing commits.

    warn Warning output is printed in the interactive mode and warns about deleted commits
    error The rebase is stopped and deleted commit warning messages are printed
    ignore The default option that ignores any missing commit warnings

Advanced Rebase Application: git rebase --onto

The --onto option of the git rebase command allows to change the ref where a rebased branch start and finishes. The git rebase --onto is an accurate and elastic solution. It gives the controls over what and where it is being rebased.

In the example below, we will change the branch starting from C to F, and we will delete the commit D from the 'new_feature' branch. Run the following command:

Syntax :

$  git rebase --onto <newbase> <oldbase>

Example:

$ git rebase --onto F D 

The output will look like this:

Before                                    After
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                             \
          D---E---H---I (HEAD new_feature)              E'---H'---I' (HEAD new_feature)

In the output above, we can see that the commit reachable attainable from HEAD (new_feature), which parent commit is D on top of the F commit. So in another way, we have changed the parent of commit E from D to F.

We will have the same output as above if we run the following command:

$ git rebase --onto F D new_feature

The situation will look different when we use the I commit ref as a third argument. The command will be as follow:

$ git rebase --onto F D I 

The output will look like this:

Before                                    After
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                        |    \
          D---E---H---I (HEAD new-feature)        |     E'---H'---I' (HEAD)
                                                   \
                                                    D---E---H---I (new-feature)

In the above output, as in the default git rebase command, we switched HEAD to the last argument of the git rebase --onto command. In our situation, it is the commit I. We noticed that the 'new_feature' branch did not change. And we have a new branch which the HEAD points.

We will have the same output as above if we run the following command:

$ git rebase --onto F D HEAD 

The Dangers of Rebasing

One of the dangers when using git rebase is that the merge conflicts may occur more frequently during the rebase workflow. This happens especially in cases when you have a long-lived branch that has deviated from Master. When you want to rebase against the Master, the Master branch may contain many new commits that may conflict with changes from the Feature branch. The solution to this is to rebase the Feature branch frequently against Master and make more periodic commits. To navigate on the git rebase workflows, there are two command-line arguments for that: --continue and --abort.

The use of git rebase can also cause the loss of commits, especially when using the interactive mode for history rewriting. When running squash or drop during the interactive mode, it will remove commits from the branch log. Those commits can be stored using the help of git reflog command.

Git rebase is not very dangerous. The real dangerous situation came when rewriting the History in interactive rebase and force pushing to a remote branch that's shared by other developers.


Recovering from Upstream Rebase

In the situation that another developer has rebased and force pushed to the branch that you're committing to, the git pull command will overwrite any commit you have based on that previous branch. Using git reflog can get the reflog of the remote branch to find a ref before it was rebased. Then you can rebase your branch against that remote ref with the help of the --onto option of the git rebase command.



ExpectoCode is optimized for learning. Tutorials and examples are constantly reviewed to avoid errors, but we cannot warrant full correctness of all content. While using this site, you agree to have read and accepted our terms of use, cookie and privacy policy.
Copyright 2020-2021 by ExpectoCode. All Rights Reserved.