Git Rebasing: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(48 intermediate revisions by the same user not shown)
Line 1: Line 1:
=External=
=External=


* https://git-scm.com/book/en/v2/Git-Branching-Rebasing
* https://www.atlassian.com/git/tutorials/merging-vs-rebasing
* https://www.atlassian.com/git/tutorials/merging-vs-rebasing


Line 9: Line 10:
* [[Git_Concepts#Rewriting_History|Rewriting History]]
* [[Git_Concepts#Rewriting_History|Rewriting History]]


=TODO=
=Overview=


<font color=darkgray>
Rebasing is one of the two main ways to integrate changes from a branch into another. The other way is [[Git_Concepts#Merging|merging]].
* https://www.atlassian.com/git/tutorials/merging-vs-rebasing
* “Rebase Commits” section from the O’Reilly book
* Understand merge really well, especially
* What happens if I have several commits on the topic branch?
* Read git rebase --help.
* Understand git pull --rebase.  
</font>


=Overview=
Rebasing works as follows: the operation goes to the common ancestor of the [[Git_Concepts#Head_Branch|head branch]] (the branch you are on) and [[Git_Concepts#Base_Branch|base branch]] (the branch you're rebasing into), getting the diff introduced by each head branch commit, saving these diffs into temporary files, resetting the head branch to the same commit as the base branch, and the finally applying each change in turn. Intuitively, is equivalent with shifting and morphing each commit of the head branch, after the common ancestor, in top of the base branch. This modification allows us to merge the head branch into the base branch without having to create a merge commit - the base branch can simply be fast forwarded with the commits from the head branch.
There are two situations when rebasing is useful: on an individual branch, to coalesce several logically-related commits into one, or when two connected branches are involved, to shift [[Git_Concepts#Head_Branch|head branch]] commits relative to the [[Git_Concepts#Base_Branch|base branch]] commits. This last situation is useful when we intend to merge the branches and we want to avoid a merge commit - the [[Git_Concepts#Base_Branch|base branch]] can simply be fast forwarded with the commits from the [[Git_Concepts#Head_Branch|head branch]].
 
Rebasing [[#Rewriting_History|rewrites history]], by removing the commits that are being rebased and creating new, equivalent ones.


Rebasing [[#Rewriting_History|rewrites history]], by removing the commits that are being rebased and creating new, equivalent ones. See [[#Merge_by_Rebasing|Merge by Rebasing]] below.
::::[[File:GitRebase.png]]
::::[[File:GitRebase.png]]
Merging two branches as described above is the main use case for the rebase. However, rebase can also be used when only one branch is involved to [[#Applying_Extra_Changes_to_the_Last_Commit|modify the message of the last commit]], or [[#Update_the_Commit_Message_of_a_Specific_Commit|even arbitrary commits]], [[#Reordering_Commits|reorder commits]], delete commits or coalesce several logically-related commits into one commit ([[Git_Rebasing#Squashing_Commits|squash commits]]).


=Rewriting History=
=Rewriting History=


Rebasing removes the commits that are being rebased and creating new, equivalent ones.
Rebasing removes the commits that are being rebased and creating new, equivalent ones.  
 
Also see: {{Internal|Git_Concepts#Rewriting_History|Rewriting History}}


=Practical Use Cases=
=Practical Use Cases=


==Squashing Commits==
==<span id='Shifting_Head_Branch_Commits_at_the_HEAD_of_the_Base_Branch'></span>Merge by Rebasing==
 
{{Internal|Git Squashing Commits|Squashing Commits}}
 
==Applying Extra Changes to the Last Commit==
 
{{Internal|Git_commit#Apply_Extra_Changes_to_the_Last_Commit|Applying Extra Changes to the Last Commit}}
 
==Shifting Head Branch Commits at the HEAD of the Base Branch==


One might to "shift" the head branch in preparation of a merge of the head branch into the base brach. The result of the operation is that all commits applied on the head branch are shifted at the top of the base branch - a side effect is that the commits are modified in the process (the history is rewritten). The advantage of doing that is that when the merge occurs, is a simple fast forward merge, and no additional [[Git_Concepts#Merge_Commit|merge commit]] is created - the changes required by the merge are already worked into the commits that are rebased.
One might to "shift" the head branch in preparation of a merge of the head branch into the base brach. The result of the operation is that all commits applied on the head branch are shifted at the top of the base branch - a side effect is that the commits are modified in the process (the history is rewritten). The advantage of doing that is that when the merge occurs, is a simple fast forward merge, and no additional [[Git_Concepts#Merge_Commit|merge commit]] is created - the changes required by the merge are already worked into the commits that are rebased.
Line 47: Line 34:
::[[File:GitRebaseMerge.png]]
::[[File:GitRebaseMerge.png]]


  git checkout <''head-branch''>
<font color=darkgray>TODO: research. Before doing that, don't we want to pull --rebase the base branch?</font>
  git rebase <''base-branch''>
 
  git checkout <''head-branch''>  
  git rebase <''base-branch''>  


At the end of this step, the branches still exist, but now the head branch can be merged into the base brach by fast forwarding it - a merge commit is not required.
At the end of this step, the branches still exist, but now the head branch can be merged into the base brach by fast forwarding it - a merge commit is not required.
Line 58: Line 47:


::[[Image:GitRebaseMerge2.png]]
::[[Image:GitRebaseMerge2.png]]
===What to Do if the Base Branch was itself Rebased===
It is not uncommon to attempt to merge into a base branch only to find out that the base branch itself was rebased, so the commits we were relying on were rewritten. It is still possible to rebase/merge with minimum of effort, without to have to wade through a massive amount of conflicts. The procedure is described below:
{{Internal|Git Fixing a Merge Broken by Base Branch Rebase|Fixing a Merge Broken by Base Branch Rebase}}


<font color=darkgray>
==Move a Branch Forward==


==To Link To==
It is sometimes desirable to move an entire branch forward, "in top" of its base branch, to take advantage of the latest developments on the base branch, without necessarily merging. For that, the simplest procedure is to find the common ancestor, then use the following syntax, assuming we are on the head branch we want to move forward:


[[Git Fixing a Merge Broken by Base Branch Rebase|Fixing a Merge Broken by Base Branch Rebase]]
git rebase --onto <''base-branch''> --fork-point <''common-ancestor-commit''>


==Moving a Branch Forward==
Immediately after that, the head branch must be force-pushed:


You are working on a feature branch, named "A", created when the "develop" branch HEAD was commit "ef5". You committed work on the "A" branch, and your commit is "3ba". After a while, you want to apply your changes on the HEAD of "develop", since "develop" has evolved and you want to try
git push --force


  On branch task/of/PLAT-15252
otherwise we get this:
  Your branch and 'origin/task/of/PLAT-15252' have diverged,
 
  and have 164 and 1 different commits each, respectively.
  On branch topic/...
  Your branch and 'origin/topic/...' have diverged,
  and have 1 and 2 different commits each, respectively.
   (use "git pull" to merge the remote branch into yours)
   (use "git pull" to merge the remote branch into yours)


==Rebasing at the HEAD of the Base Branch==
==Squashing Commits==
 
{{Internal|Git Squashing Commits|Squashing Commits}}
 
==Applying Extra Changes to the Last Commit==
 
{{Internal|Git_commit#Apply_Extra_Changes_to_the_Last_Commit|Applying Extra Changes to the Last Commit}}
 
==Update the Commit Message of a Specific Commit==
{{Internal|Git_commit#Update_the_Commit_Message_of_a_Specific_Commit|Update the Commit Message of a Specific Commit}}
 
==Reordering Commits==
 
The order of commits on a branch can be changed by initiating an interactive rebase, and reordering the commits with the help of the editor.


This procedure takes all commits from the head branch that diverged from the base branch, squashes them into one, and applies to the HEAD of the base branch.
==Rebasing Merge Commits==


You will be given the chance to squash extraneous commits. Change "pick" into "s" for the commits you want squashed into the previous (above) commits.
===Convert the Merge Commits into a Normal Commit===


You will then be given the chance to edit comments.
Assuming HEAD is pointing to merge commit <font color=darkgray>(if the merge commit is lower in the commit history, adjust </font><font face='menlo' color='darkslategray' size=-1>HEAD~<commit-count></font> <font color=darkgray>below)</font>:


If you don't want to squash, you can omit -i.
Keep changes in index:


  On branch topic/...
  git reset --soft HEAD~1
Your branch and 'origin/topic/...' have diverged,
 
  and have 1 and 2 different commits each, respectively.
Create a new commit, this time not a merge commit:
  (use "git pull" to merge the remote branch into yours)
 
  git commit
 
Do an interactive rebase and squash the new commit:


  git push --force
  git rebase -i HEAD~4


You can delete the branch (locally and remotely)
===Preserving Merge Commits during a Rebase===


</font>
<font color=darkgray>By default, a rebase will drop merge commits from the TODO list, and put the rebased commits into a single linear branch. With --rebase-merges, the rebase will instead try to preserve the branching structure within the commits that are to be rebased, by recreating the merge commits. Any resolved merge conflicts or manual amendments in these merge commits will have to be resolved/re-applied manually.</font>

Latest revision as of 20:18, 29 April 2021

External

Internal

Overview

Rebasing is one of the two main ways to integrate changes from a branch into another. The other way is merging.

Rebasing works as follows: the operation goes to the common ancestor of the head branch (the branch you are on) and base branch (the branch you're rebasing into), getting the diff introduced by each head branch commit, saving these diffs into temporary files, resetting the head branch to the same commit as the base branch, and the finally applying each change in turn. Intuitively, is equivalent with shifting and morphing each commit of the head branch, after the common ancestor, in top of the base branch. This modification allows us to merge the head branch into the base branch without having to create a merge commit - the base branch can simply be fast forwarded with the commits from the head branch.

Rebasing rewrites history, by removing the commits that are being rebased and creating new, equivalent ones. See Merge by Rebasing below.

GitRebase.png

Merging two branches as described above is the main use case for the rebase. However, rebase can also be used when only one branch is involved to modify the message of the last commit, or even arbitrary commits, reorder commits, delete commits or coalesce several logically-related commits into one commit (squash commits).

Rewriting History

Rebasing removes the commits that are being rebased and creating new, equivalent ones.

Also see:

Rewriting History

Practical Use Cases

Merge by Rebasing

One might to "shift" the head branch in preparation of a merge of the head branch into the base brach. The result of the operation is that all commits applied on the head branch are shifted at the top of the base branch - a side effect is that the commits are modified in the process (the history is rewritten). The advantage of doing that is that when the merge occurs, is a simple fast forward merge, and no additional merge commit is created - the changes required by the merge are already worked into the commits that are rebased.

GitRebaseMerge.png

TODO: research. Before doing that, don't we want to pull --rebase the base branch?

git checkout <head-branch> 
git rebase <base-branch> 

At the end of this step, the branches still exist, but now the head branch can be merged into the base brach by fast forwarding it - a merge commit is not required.

To merge, we just simply fast forward:

git checkout <base-branch>
git merge <head-branch>
GitRebaseMerge2.png

What to Do if the Base Branch was itself Rebased

It is not uncommon to attempt to merge into a base branch only to find out that the base branch itself was rebased, so the commits we were relying on were rewritten. It is still possible to rebase/merge with minimum of effort, without to have to wade through a massive amount of conflicts. The procedure is described below:

Fixing a Merge Broken by Base Branch Rebase

Move a Branch Forward

It is sometimes desirable to move an entire branch forward, "in top" of its base branch, to take advantage of the latest developments on the base branch, without necessarily merging. For that, the simplest procedure is to find the common ancestor, then use the following syntax, assuming we are on the head branch we want to move forward:

git rebase --onto <base-branch> --fork-point <common-ancestor-commit>

Immediately after that, the head branch must be force-pushed:

git push --force

otherwise we get this:

On branch topic/...
Your branch and 'origin/topic/...' have diverged,
and have 1 and 2 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

Squashing Commits

Squashing Commits

Applying Extra Changes to the Last Commit

Applying Extra Changes to the Last Commit

Update the Commit Message of a Specific Commit

Update the Commit Message of a Specific Commit

Reordering Commits

The order of commits on a branch can be changed by initiating an interactive rebase, and reordering the commits with the help of the editor.

Rebasing Merge Commits

Convert the Merge Commits into a Normal Commit

Assuming HEAD is pointing to merge commit (if the merge commit is lower in the commit history, adjust HEAD~<commit-count> below):

Keep changes in index:

git reset --soft HEAD~1

Create a new commit, this time not a merge commit:

git commit

Do an interactive rebase and squash the new commit:

git rebase -i HEAD~4

Preserving Merge Commits during a Rebase

By default, a rebase will drop merge commits from the TODO list, and put the rebased commits into a single linear branch. With --rebase-merges, the rebase will instead try to preserve the branching structure within the commits that are to be rebased, by recreating the merge commits. Any resolved merge conflicts or manual amendments in these merge commits will have to be resolved/re-applied manually.