What is Git Rebase?
Rebasing is nothing more than taking one branch and adding it to the tip of another, where the tip is simply the last commit in the branch. Based on this analogy, git rebase is used to integrate changes from one branch into another
Following image would give you a clear idea of git rebase in realtime:
Workflow of Git rebase
This is the general workflow of git rebase
which we will explore using some examples in the next section:
clone the remote repository git checkout -b my-feature-branch >>>..work and commit some stuff..<<< git rebase master >>>..work and commit some stuff..<<< git rebase master >>>..finish the feature, commit..<<< git rebase master git checkout master git merge my-feature-branch
Clone repository to local workstation
I have cloned git_examples
project from my gitlab:
Create backup branch before git rebase
Anyhow to be on the safer side it is recommended to create a backup branch before performing git rebase so you can reset your base branch from the backup in case of any issues.
Switch to your base branch:
deepak@ubuntu:~/git_examples$ git checkout my-feature-branch
Switched to branch 'my-feature-branch'
Create a new backup branch and switch to that branch:
deepak@ubuntu:~/git_examples$ git checkout -b my-feature-branch-backup
Switched to a new branch 'my-feature-branch-backup'
Now you can switch back to your base branch and proceed with the rebasing:
deepak@ubuntu:~/git_examples$ git checkout my-feature-branch
Switched to branch 'my-feature-branch'
If you encounter any issues while performing git rebase, you can switch to my-feature-branch
:
deepak@ubuntu:~/git_examples$ git checkout my-feature-branch
Switched to branch 'my-feature-branch'
And perform a hard reset against the backup branch:
deepak@ubuntu:~/git_examples$ git reset --hard my-feature-branch-backup
Although if you did any changes after creating the backup branch then those will be lost.
Example-1: Steps to perform git rebase
Step-1: Checkout to feature branch
We are currently at my-feature-branch
:
deepak@ubuntu:~/git_examples$ git branch
main
* my-feature-branch
my-feature-branch-backup
Following are the list of commits performed on this repository:
Step-2: Commit changes in feature branch
This step is optional, just to demonstrate the impact of changes. I will go ahead and make some changes in git_rebase/script2.sh
for my-feature-branch
:
deepak@ubuntu:~/git_examples$ vim git_rebase/script2.sh
Commit the changes with a proper message:
deepak@ubuntu:~/git_examples$ git commit -m "Added some comment in script2" git_rebase/script2.sh [my-feature-branch 5313ae5] Added some comment in script2 1 file changed, 1 insertion(+)
Verify the list of commits in my-feature-branch
:
deepak@ubuntu:~/git_examples$ git log --oneline 5313ae5 (HEAD -> my-feature-branch) Added some comment in script2 9177492 (origin/main, origin/HEAD, main) Print date in script2 3754278 Print working directory detail 9afa6c2 Added script2.sh under git_rebase 784b0ae Added new script under git_rebase ddce879 Initial commit
Step-3: Commit changes in main branch
Now let us also make some changes under our main branch. This step is again optional and purely to demonstrate the behaviour. So we will switch to our main
branch:
deepak@ubuntu:~/git_examples$ git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
Make some changes to a different script git_rebase/script1.sh
:
deepak@ubuntu:~/git_examples$ vim git_rebase/script1.sh
my-feature-branch
. If the same file is modified in both the branches then git rebase
will cause conflicts which we will analyse in next section of this tutorial.Commit the changes under main
branch:
deepak@ubuntu:~/git_examples$ git commit -m "Added name in script1.sh" git_rebase/script1.sh [main 4357c59] Added name in script1.sh 1 file changed, 1 insertion(+)
As you can see, the commits from my-feature-branch
are not visible here at this stage:
deepak@ubuntu:~/git_examples$ git log --oneline 4357c59 (HEAD -> main) Added name in script1.sh 9177492 (origin/main, origin/HEAD) Print date in script2 3754278 Print working directory detail 9afa6c2 Added script2.sh under git_rebase 784b0ae Added new script under git_rebase ddce879 Initial commit
Step-4: Perform git rebase
Let's switch back to my-feature-branch
:
deepak@ubuntu:~/git_examples$ git checkout my-feature-branch Switched to branch 'my-feature-branch' deepak@ubuntu:~/git_examples$ git branch main * my-feature-branch
Now we feel that may be we should rebase our branch with main
branch to inline the changes:
deepak@ubuntu:~/git_examples$ git rebase main
Successfully rebased and updated refs/heads/my-feature-branch.
The rebase was successful because different set of files were modified in both the branches. At this stage, the my-feature-branch
will have all the changes from main branch:
deepak@ubuntu:~/git_examples$ git log --oneline 812ceb5 (HEAD -> my-feature-branch) Added some comment in script2 4357c59 (main) Added name in script1.sh 9177492 (origin/main, origin/HEAD) Print date in script2 3754278 Print working directory detail 9afa6c2 Added script2.sh under git_rebase 784b0ae Added new script under git_rebase ddce879 Initial commit
Here is a flow diagram explaining what we did above:
Let's switch to main
branch:
As the above message says, the main branch is ahead of my-feature-branch
. This is because we have not yet merged my-feature-branch
into main
branch:
Let's make some more changes into the main
branch:
deepak@ubuntu:~/git_examples$ vim git_rebase/script1.sh deepak@ubuntu:~/git_examples$ git commit -m "Modified script1 before merging with my-feature-branch" git_rebase/script1.sh [main 0d2ce16] Modified script1 before merging with my-feature-branch 1 file changed, 1 insertion(+)
List the commits in main branch:
deepak@ubuntu:~/git_examples$ git log --oneline 0d2ce16 (HEAD -> main) Modified script1 before merging with my-feature-branch 4357c59 Added name in script1.sh 9177492 (origin/main, origin/HEAD) Print date in script2 3754278 Print working directory detail 9afa6c2 Added script2.sh under git_rebase 784b0ae Added new script under git_rebase ddce879 Initial commit
Step-5: Merge feature branch into main branch
Now let's merge my-feature-branch
into main
branch:
my-feature-branch
and push the same to gitlab repository. But here we will merge this into main branch and push all the changes into main branch itself.deepak@ubuntu:~/git_examples$ git merge my-feature-branch
Merge made by the 'recursive' strategy.
git_rebase/script2.sh | 1 +
1 file changed, 1 insertion(+)
Get the list of commits. Now we have all the changes from my-feature-branch
also merged into main
branch.
deepak@ubuntu:~/git_examples$ git log --oneline fa16ae6 (HEAD -> main) Merge branch 'my-feature-branch' 0d2ce16 Modified script1 before merging with my-feature-branch 812ceb5 (my-feature-branch) Added some comment in script2 4357c59 Added name in script1.sh 9177492 (origin/main, origin/HEAD) Print date in script2 3754278 Print working directory detail 9afa6c2 Added script2.sh under git_rebase 784b0ae Added new script under git_rebase ddce879 Initial commit
Step-6: Push commits to remote repository
Let's push these changes to our remote gitlab repository under main
branch:
How to solve git rebase conflicts
A conflict arises when your current branch and the branch to be merged have diverged, and there are commits in your current branch that aren’t present in the other branch, and vice versa. Git isn’t able to determine which changes to keep, so it raises a conflict to ask the user to review the changes. The last common commit between the two branches—which is also the point where they diverged—is called the base commit.
Step-1: Commit changes in feature branch
Let's switch to my-feature-branch
and make some changes to one of the scripts in our repo:
deepak@ubuntu:~/git_examples$ git checkout my-feature-branch
Switched to branch 'my-feature-branch'
Make some changes to git_rebase/script2.sh script
:
deepak@ubuntu:~/git_examples$ echo "##Adding in script2 under my-feature-branch" >> git_rebase/script2.sh deepak@ubuntu:~/git_examples$ git commit -m "Updated script2.sh" git_rebase/script2.sh [my-feature-branch 7bb4ce4] Updated script2.sh 1 file changed, 2 insertions(+)
List the commit changes, 7bb4ce4
is the latest commit ID for our latest change under my-feature-branch
:
Step-2: Commit changes in main branch
To intentionally create a git rebase conflict, we will modify the same file under main branch:
deepak@ubuntu:~/git_examples$ git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
Now since we are under main
branch, let's modify the same file with some new content:
deepak@ubuntu:~/git_examples$ echo "##Adding in script2 under main-branch" >> git_rebase/script2.sh deepak@ubuntu:~/git_examples$ git commit -m "Updated script2.sh in main branch" git_rebase/script2.sh [main 6297a37] Updated script2.sh in main branch 1 file changed, 1 insertion(+)
You can verify the list of commits. Here 6297a37
is the latest commit ID under main branch:
Step-3: Perform git rebase to main branch
Now switch to my-feature-branch
:
deepak@ubuntu:~/git_examples$ git checkout my-feature-branch
Switched to branch 'my-feature-branch'
Try to perform git rebase with main branch:
deepak@ubuntu:~/git_examples$ git rebase main Auto-merging git_rebase/script2.sh CONFLICT (content): Merge conflict in git_rebase/script2.sh error: could not apply 7bb4ce4... Updated script2.sh Resolve all conflicts manually, mark them as resolved with "git add/rm <conflicted_files>", then run "git rebase --continue". You can instead skip this commit: run "git rebase --skip". To abort and get back to the state before "git rebase", run "git rebase --abort". Could not apply 7bb4ce4... Updated script2.sh
The same behaviour is explained in this image:
Step-4: Fix the conflicting files
As expected, the git rebase has failed with conflicts. You can check the output of above command which claims that 7bb4ce4 commit ID from my-feature-branch is having a conflict with main branch changes. So we must manually fix this conflict.
Check the content of the conflicted file:
deepak@ubuntu:~/git_examples$ cat git_rebase/script2.sh #!/bin/bash echo "This is script2" echo "Date: `date`" ## Some comment <<<<<<< HEAD ##Adding in script2 under main-branch ======= ##Adding in script2 under my-feature-branch >>>>>>> 7bb4ce4 (Updated script2.sh)
<<<<<<<:
Indicates the start of the lines that had a merge conflict.=======:
Indicates separation of the two conflicting changes.>>>>>>>
: Indicates the end of the lines that had a merge conflict.
‘<<<<<<<
’, ‘=======
’, and ’>>>>>>>’
in the file.So based on the indicated lines, we know the content added as part of main and my-feature-branch. Here you must decide the content which you wish to keep or delete. Let us delete the content added as part of my-feature-branch
. Here is our final content of this file after removing the extra data:
deepak@ubuntu:~/git_examples$ cat git_rebase/script2.sh
#!/bin/bash
echo "This is script2"
echo "Date: `date`"
## Some comment
##Adding in script2 under main-branch
Step-5: Add conflicting files to staging environment
Add the files to the stage after you have fixed the conflict:
deepak@ubuntu:~/git_examples$ git add git_rebase/script2.sh
Step-6: Perform git rebase --continue
When you have resolved this problem, run git rebase --continue
. If you prefer to skip this patch, run git rebase --skip
instead. To check out the original branch and stop rebasing, run git rebase --abort
.
deepak@ubuntu:~/git_examples$ git rebase --continue
Successfully rebased and updated refs/heads/my-feature-branch.
This time the git rebase was successful. Here is the diagram flow explaining the fix:
Verify the list of commits, you can see that the changes done as part of commit ID 7bb4ce4 is not there as we had manually removed those changes to fix the conflict:
Step-7: Perform git merge to main branch
Now we can merge this to our main branch. Again this is optional and if you wish to continue working on my-feature-branch then you can ignore this step.
Switch to main
branch:
deepak@ubuntu:~/git_examples$ git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
Merge my-feature-branch
into main
branch
deepak@ubuntu:~/git_examples$ git merge my-feature-branch
Here is an image demonstrating this scenario:
What are the main differences between merge and rebase
- merge executes only one new commit. rebase typically executes multiple (number of commits in current branch).
- merge produces a new generated commit (the so called merge-commit). rebase only moves existing commits.
When should you use git rebase
Rebase "lifts off" your changes and puts all the changes of the rebased branch into your current branch and then puts your changes on top of it. It therefore changes the history of your branch.
- no one else is working on the same content (as you are) on a different branch
- you want your to see all your changes at one point together when merging back to the source branch
- you want to avoid the auto-generated "merged .." commit messages
I said "you want to see all your changes at one place" because sometimes a merge operation puts all your changes together in one commit (some: merged from ... message). Rebase makes your change look like you made your commits all after each other with no one else doing something in between. This makes it easier to see, what you changed for your feature.
You can also use git merge feature-branch --ff-only
to make sure there are no conflicts creating a single commit when you are merging your feature back to main/master.
Why git rebase is better than git merge
The reason why a rebase is then better than a merge is that:
- You rewrite your local commit history with the one of the master (and then reapply your work, resolving any conflict then)
- The final merge will certainly be a "fast forward" one, because it will have all the commit history of the master, plus only your changes to reapply.
Use rebase whenever you want to add changes of a base branch back to a branched out branch. Typically, you do this in feature branches whenever there's a change in the main branch.
What are the problems of using git rebase
- Because a rebase moves commits (technically re-executes them), the commit date of all moved commits will be the time of the rebase and the git history loses the initial commit time. So, if the exact date of a commit is needed for some reason, then merge is the better option. But typically, a clean git history is much more useful than exact commit dates.
- If the rebased branch has multiple commits that change the same line and that line was also changed in the base branch, you might need to solve merge conflicts for that same line multiple times, which you never need to do when merging. So, on average, there's more merge conflicts to solve.
Summary
Rebasing commits is the one concept in Git that has no counterpart inside the traditional version control world. Using git rebase, you can rewrite the history of a repository in a variety of ways. It is one of the most powerful commands in Git, which makes it one of the most dangerous.
In this tutorial, we covered how to:
- clone from a remote repository
- create, update and merge branches
- keep a local repository updated
- send the changes from a local repository to a remote
- manage conflicts during rebase
References
Git workflow and rebase vs merge questions
When do you use Git rebase instead of Git merge?