240 lines
7.2 KiB
Markdown
240 lines
7.2 KiB
Markdown
---
|
|
categories:
|
|
- DevOps
|
|
tags:
|
|
- git
|
|
---
|
|
|
|
# Rebasing
|
|
|
|
Rebasing is a way to integrate changes from one branch into another. In this
|
|
regarding it is like merging a branch B into another branch A. However rebasing
|
|
differs from normal merging in the way in which it modifies the Git history.
|
|
|
|
In a normal merge of branch B into branch A, Git creates a new commit that
|
|
combines the changes of branches B and A. This is known as the merge commit and
|
|
is evident from the following automatic commit message that is generated:
|
|
|
|
```
|
|
Merge branch B of github.com:thomasabishop/remote-repository into A
|
|
```
|
|
|
|

|
|
|
|
In this scenario the merge commit has two or more parent commits each
|
|
representing the history of the merged branches. The resulting history of A will
|
|
include the commits of B. Basically the two histories are combined.
|
|
|
|
This would give us a history that looks like the following, with different
|
|
colours for the separate SHAs of each merged branch:
|
|
|
|

|
|
|
|
If we were to create a rebase branch of A from B, there would be a new singular
|
|
history without distinguishing multiple parents that combines the commits of A
|
|
and B. The rebased branch's commits are recreated with new commit IDs and new
|
|
timestamps, based on the current state of the branch that you are rebasing onto.
|
|
This is obviously potentially destructive because it does not preserve the
|
|
respective branch's separate history. It rewrites history as a continuous stream
|
|
of commits in a single branch.
|
|
|
|
When a rebase is applied, it will put the diverging B commits at the tip of A
|
|
like so:
|
|
|
|

|
|
|
|
And then rebrand the previous A commits to be continuous with B presenting a
|
|
flat and linear Git history like the following:
|
|
|
|

|
|
|
|
## Benefits, use-cases
|
|
|
|
> The purpose of rebasing is to ensure that the commit history of a project is
|
|
> as linear and simple as possible, by incorporating changes from one branch
|
|
> into another, as if the changes had been made on the other branch all along.
|
|
|
|
A common use-case is with feature branches as it makes the features fit more
|
|
seamlessly with the `main` or `develop` branch.
|
|
|
|
It is also a technique that can be used to integrate recent commits without
|
|
merging.
|
|
|
|
When to use one over the other:
|
|
|
|
- **Merge** to allow commits to stand out or to be clearly grouped
|
|
- **Merge** to bring large feature branches back into main
|
|
- **Rebase** to add minor commits in a main branch into a feature branch
|
|
- **Rebase** when you need to move commits from one branch to another
|
|
|
|
So in terms of my personal workflow, I should be using
|
|
|
|
- Merge when I want to bring a feature into develop
|
|
- Merge when I want to bring develop into main for a release
|
|
- Merge any time the feature branch is already public and being used by others.
|
|
- Rebase to capture small changes in the parent branch
|
|
- Rebase when I have branched from a branch because a refactor or something has
|
|
become more complex than originally anticipated.
|
|
|
|
## Syntax
|
|
|
|
For example if we are working on a feature branch off of `main`, we would run
|
|
the following from this feature branch:
|
|
|
|
```
|
|
git rebase main
|
|
```
|
|
|
|
This will move the feature branch to the tip of `main`.
|
|
|
|
If this completes successfully, we would then go on to merge the feature with
|
|
main.
|
|
|
|
If conflicts occur, the rebase will halt. You fix the conflicts and then run:
|
|
|
|
```
|
|
git rebase --continue
|
|
```
|
|
|
|
Or if you don't want to resolve the conflict (not advised):
|
|
|
|
```
|
|
git rebase --skip
|
|
```
|
|
|
|
Or stop the whole process:
|
|
|
|
```
|
|
git rebase --abort
|
|
```
|
|
|
|
### Isolate the point at which one branch diverges from another
|
|
|
|
This can be useful to check before you create a rebase:
|
|
|
|
```
|
|
git merge-base main feature/some-feature
|
|
|
|
c33acc84f06fcb94e0e87d9adb240c038da6d71c
|
|
```
|
|
|
|
## Golden rule of rebasing
|
|
|
|
When we rebase we remove the additional merge commit but we are changing
|
|
history. The commits are still there but the SHAs are changed. For this reason
|
|
**you should not rebase a public branch**, otherwise you will mess up your
|
|
collaborators history. You should do your rebasing locally and then make a pull
|
|
request.
|
|
|
|
## Difference from cherry-picking
|
|
|
|
The main difference between the two approaches is that
|
|
[cherry-picking](/DevOps/Git/Cherry_picking_a_branch.md) is a more selective
|
|
process, where you can pick and choose specific commits that you want to include
|
|
in another branch. This can be useful when you only want to apply specific
|
|
changes or fixes from one branch to another, without including all the changes
|
|
made in the original branch.
|
|
|
|
On the other hand, rebasing is a more comprehensive process that can include all
|
|
the changes from one branch to another, but it modifies the commit history,
|
|
making it more linear and easier to understand. Unlike rebasing, cherry-picking
|
|
does not modify the commit history of either branch.
|
|
|
|
## Squashing commits
|
|
|
|
This is a handy feature of rebasing. It allows you to combine multiple commits
|
|
into one. This is useful when you have been doing a lot of trial and error to
|
|
resolve a bug and you have lots of small commits that are not that important in
|
|
themselves.
|
|
|
|
We use the interactive rebase tool for this.
|
|
|
|
Checkout the branch where the commit you want to squash is located.
|
|
|
|
Then run:
|
|
|
|
```
|
|
git rebase -i HEAD~n
|
|
```
|
|
|
|
Where `n` is the number of commits you want to squash starting from the most
|
|
recent. This will open an interactive rebase window, listing the commits. You
|
|
can then use the keywords to decide what you want to do with them. In our case
|
|
this will be `s` for squash.
|
|
|
|

|
|
|
|
### Example
|
|
|
|
I made a small fix commit because something went wrong during build. I do not
|
|
want this commit to be in the history because it is so minor. Instead I want to
|
|
incorporate these changes into the last public commit.
|
|
|
|
Trivial commit is `xy` Previous commit is `aa`
|
|
|
|
```
|
|
git rebase -i HEAD~2
|
|
```
|
|
|
|
This displays:
|
|
|
|
```
|
|
pick xy
|
|
pick aa
|
|
```
|
|
|
|
Change this to:
|
|
|
|
```
|
|
pick xy
|
|
squash aa
|
|
```
|
|
|
|
Now `xy == aa`
|
|
|
|
### Another example
|
|
|
|
Here we will rebase the interim commits into the last feature commit
|
|
|
|
```sh
|
|
git rebase -i HEAD~10
|
|
```
|
|
|
|
```
|
|
pick a691a7667 feature (shared-nav) add active app check
|
|
pick 74a07fdb1 interim: add logging
|
|
pick 9af4e2652 interim: more logging
|
|
pick 51b36f667 interim: use href not origin
|
|
pick 33f161198 interim : try catch handler
|
|
pick 41c144ddf interim: remove didLoadCheck
|
|
pick ebd1c9f24 interim: fix flash star
|
|
pick 77af452e7 interim: rm redundant active app check
|
|
pick 79a9b737a iterim: test ternary
|
|
pick a90d23223 interim: further tidying
|
|
```
|
|
|
|
```
|
|
pick a691a7667 feature (shared-nav) add active app check
|
|
squash 74a07fdb1 interim: add logging
|
|
squash 9af4e2652 interim: more logging
|
|
squash 51b36f667 interim: use href not origin
|
|
squash 33f161198 interim : try catch handler
|
|
squash 41c144ddf interim: remove didLoadCheck
|
|
squash ebd1c9f24 interim: fix flash star
|
|
squash 77af452e7 interim: rm redundant active app check
|
|
squash 79a9b737a iterim: test ternary
|
|
squash a90d23223 interim: further tidying
|
|
```
|
|
|
|
## `git pull rebase`
|
|
|
|
We can use this command to combine a rebase with a pull. This way we silently
|
|
update our local version of a branch with the remote, without adding a merge
|
|
commit.
|
|
|
|
```
|
|
git pull --rebase
|
|
```
|
|
|
|
Add `--rebase=preserve` to keep merges that have been done _locally_. It is
|
|
generally a good idea to do this.
|