A Detailed Guide To Understand How Git-Rebase Works | by Dwen | Jul, 2022

Utilization particulars and dealing precept

Picture by Andyone on Unsplash

After every week of in-depth research, I totally understood the working precept of Git-Rebase. At this time, I’m going to write down a extra in-depth evaluation weblog and share it with you.

The documentation for the git rebase command says “Reapply commits on prime of one other base tip”, which accurately means reapply commits on prime of one other base tip.

This definition sounds a bit summary, however it may be understood as altering the bottom of a department from one commit to a different, making it seem as if the department was created from one other commit.

As proven under:

Suppose we create a Characteristic department from Grasp’s commit A for brand spanking new characteristic growth, then A is the bottom finish of Characteristic.

Then Grasp added two new commits B and C, and Characteristic added two new commits D and E.

Now for some motive, for instance, the event of latest options will depend on commits B and C, and we have to combine the 2 new commits of Grasp into the Characteristic department. With a view to maintain the commit historical past clear, we are able to swap to the Characteristic department to carry out the rebase operation:

git rebase grasp

The execution technique of rebase is to first discover the closest frequent ancestor commit A of those two branches (that’s, the present department Characteristic, the goal base department Grasp of the rebase operation).

Then evaluate the earlier commits (D and E) of the present department relative to the ancestor commit, extract the corresponding modifications and save them as short-term information, after which level the present department to the commit Cpointed to by the goal base Grasp.

Lastly, utilizing this as the brand new base finish, the modifications beforehand saved as short-term information are utilized sequentially.

We will additionally perceive the above as altering the bottom of the Characteristic department from commit A to commit C, which appears to be like just like the department was created from commit C, and commits D and E.

However really, that is simply “wanting”, internally Git copies the contents of commits D and E, creates new commits D’ and E’ and applies them to a selected base (A→B→C).

Though the brand new Characteristic department appears to be like the identical as earlier than, it’s made up of brand name new commits.

The essence of the rebase operation is to discard some present commits, after which correspondingly create some new commits which might be similar in content material however are literally completely different.

The primary objective.

rebase is commonly used to rewrite commit historical past. The next utilization situations are frequent in most Git workflows:

  • We pulled a characteristic department from the grasp department for characteristic growth domestically.
  • The distant grasp department later merged some new commits.
  • We need to combine the most recent modifications from grasp within the characteristic department.

The above situations may also be achieved utilizing merge, however utilizing rebase permits us to take care of a linear and cleaner commit historical past. Suppose we’ve got the next branches:

D---E characteristic
/
A---B---C grasp

Now we are going to use the merge and rebase respectively to combine the B and C commits of the grasp department into the characteristic department, add a commit F to the characteristic department, then merge the characteristic department into the grasp, and eventually evaluate the commits fashioned by the 2 strategies differ in historical past.

Use merge.

  • Change to the characteristic department: git checkout characteristic.
  • To merge updates from the grasp department: git merge grasp.
  • Add a brand new commit F: git add . && git commit -m “commit F”.
  • Change again to the grasp department and carry out a fast-forward merge: git checkout grasp && git merge fature.

The execution course of is proven within the following determine:

We are going to get the next commit historical past:

* 6fa5484 (HEAD -> grasp, characteristic) commit F
* 875906b Merge department 'grasp' into characteristic
|
| | 5b05585 commit E
| | f5b0fc0 commit D
* * d017dff commit C
* * 9df916f commit B
|/
* cb932a6 commit A

Use rebase.

The steps are mainly the identical as utilizing merge, the one distinction is that the command in step 2 is changed with: git rebase grasp.

The execution course of is proven within the following determine:

We are going to get the next commit historical past:

* 74199ce (HEAD -> grasp, characteristic) commit F
* e7c7111 commit E
* d9623b0 commit D
* 73deeed commit C
* c50221f commit B
* ef13725 commit A

It may be seen that the commit historical past fashioned through the use of the rebase methodology is totally linear and in comparison with the merge methodology, there may be one much less merge commit, which appears to be like cleaner.

Why maintain your commit historical past clear?

What’s the good thing about a seemingly cleaner commit historical past?

  • Fulfill the cleanliness of some builders.
  • When you might want to return to the commit historical past for some bug, it’s simpler to find which commit the bug was launched from.

Particularly when you might want to use git bisect to troubleshoot bugs from dozens or a whole lot of commits, or when there are some massive characteristic branches that have to steadily pull updates from the distant grasp department.

Utilizing rebase to combine the distant modifications into the native repository is a greater choice.

The results of pulling distant modifications with merge is that each time you need to get the most recent progress on the undertaking, there will likely be a redundant merge commit.

And the results of utilizing rebase is extra according to our intent: I need to construct on different folks’s accomplished work with my modifications.

Different methods to rewrite commit historical past.

Utilizing git commit --amend is extra handy after we solely need to amend the latest commit.

It really works within the following situations:

  • We simply made a dedication, however haven’t pushed to the general public department but.
  • All of a sudden I discovered that the final commit nonetheless left some small tails unfinished, corresponding to a line of feedback that we forgot to delete or a small clerical error. We will rapidly full the revision, however we don’t need to add a separate commit.
  • Or we simply really feel that the commit message from the final commit was not written effectively sufficient and need to make some modifications.

Presently, we are able to add new modifications (or skip), and use the git commit --amend command to execute the submission. After execution, a brand new editor window will likely be entered, and the submission info of the final submission may be modified. These modifications will likely be utilized to the final commit.

If we’ve got already pushed the final decide to the distant department and executing the push now will immediate an error and be rejected. On the premise of making certain that the department will not be a public department, we are able to use git push --force Pressure push.

Word that as with rebase, Git doesn’t really modify and exchange the earlier commit internally, however as an alternative creates a model new commit and repoints this new commit.

Rewrite commit historical past utilizing rebase’s interactive mode.

The git rebase command has two modes: commonplace and interactive. Within the earlier examples, we used the default commonplace mode. Add the -i or --interactive choice after the command to make use of the interactive mode.

The distinction between the 2 modes?

As we talked about earlier, rebase is “reapplying commits on prime of one other base”, and within the technique of reapplying, these commits are recreated and may naturally be modified.

In the usual mode of rebase, the commits of the present working department are utilized on to the tip of the incoming department.

In interactive mode, it permits us to rewrite, reorder, and delete commits by way of the editor and particular command guidelines earlier than reapplying.

The most typical utilization situations of the 2 are due to this fact completely different:

  • Normal mode is commonly used to combine the most recent modifications from different branches within the present department.
  • Interactive mode is commonly used to edit the commit historical past of the present department, corresponding to merging a number of small commits into a bigger one.

It’s extra than simply branches.

Though our earlier examples carried out rebase operations between two completely different branches, actually, the parameters handed to the rebase command will not be restricted to branches.

Any commit reference may be thought of a legitimate rebase base object, together with a commit ID, department title, tag title, or a relative reference corresponding to HEAD~1.

Naturally, if we rebase a historic commit on the present department, the result’s that each one commits after this commit will likely be reapplied to the present department, permitting us to make modifications to these commits in interactive mode.

Lastly getting into the subject of this text, as talked about earlier, if we execute rebase on a commit of the present department in interactive mode, that’s (not directly) rewrites all commits after this commit. Subsequent, we are going to introduce it intimately by way of the next instance.

Suppose we’ve got the next commits within the characteristic department:

74199cebdd34d107bb67b6da5533a2e405f4c330 (HEAD -> characteristic) commit F
e7c7111d807c1d5209b97a9c75b09da5cd2810d4 commit E
d9623b0ef9d722b4a83d58a334e1ce85545ea524 commit D
73deeedaa944ef459b17d42601677c2fcc4c4703 commit C
c50221f93a39f3474ac59228d69732402556c93b commit B
ef1372522cdad136ce7e6dc3e02aab4d6ad73f79 commit A

The following factor we are going to do is:

  • Merge B and C into a brand new commit, maintaining solely the commit info of the unique commit C.
  • delete commit D.
  • Transfer to commit E after commit F and rename (i.e. modify commit data) to commit H.
  • Embody a brand new file change in commit F and rename it to commit G.

For the reason that commit we have to modify is B→C→D→E, we have to use commit A as the brand new “base”, and all commit after commit A will likely be reapplied:

git rebase -i ef1372522cdad136ce7e6dc3e02aab4d6ad73f79 # The parameter is the ID of commit A

Subsequent, you’ll enter the next editor interface:

decide c50221f commit B
decide 73deeed commit C
decide d9623b0 commit D
decide e7c7111 commit E
decide 74199ce commit F
# Rebase ef13725..74199ce onto ef13725(5 instructions)
#
# Instructions:
# p, decide <commit> = use commit
# r, reword <commit> = use commit, however edit the commit message
# e, edit <commit> = use commit, however cease for amending
# s, squash <commit> = use commit, however meld into earlier commit
# f, fixup <commit> = like "squash", however discard this commit's log message
# x, exec <command> = run command (the remainder of the road) utilizing shell
# b, break = cease right here (proceed rebase later with 'git rebase --continue')
# d, drop <commit> = take away commit
# l, label <label> = label present HEAD with a reputation
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
......

Word: the commit info after the commit ID above is for descriptive functions solely, modifying them right here can have no impact.

The particular operation instructions have been defined in nice element within the editor’s feedback, so we immediately carry out the next operations:

  1. Make the next modifications to commits B and C.
decide c50221f commit B
f 73deeed commit C

Since commit B is the primary of those commits, we are able to’t squash or fixup it (there’s no earlier commit), and we don’t have to reword commit B to change its commit data, as a result of it’s later within the When commit C is merged into commit B, it permits us to make modifications to the merged commit.

Word that the show order of submissions on this interface is from prime to backside, from previous to new, so we modify the command of submission C to s (or squash) or f (or fixup), which is able to merge it into the earlier submission B (above). , the distinction between the 2 instructions is whether or not to retain the commit info of C.

2. Take away commit D.

d d9623b0 commit D

3. Transfer commit E after commit F and modify its commit data.

decide 74199ce commit F
r e7c7111 commit E

4. Add a brand new file change in commit F.

e 74199ce commit F

5. Save and exit.

Subsequent, the instructions we modify or retain for every commit are executed so as from prime to backside:

  • The decide command for commit B is executed robotically, so no interplay is required.
  • Then execute the squash command for commit C, which is able to enter a brand new editor interface that permits us to change the commit info after merging B and C.
# This can be a mixture of two commits。
# That is the primary commit notice:
commit B# Listed here are the commit directions #2:commit C
......

We delete the commit B line and save and exit and the merged commit will use commit C because the commit info.

  • The drop operation on commit D can be carried out robotically, with no interactive steps.
  • Conflicts might happen through the rebase course of. Presently, the rebase will likely be quickly suspended, and we have to edit the conflicting information to merge the conflicts manually.
  • As soon as a battle is resolved, mark it as resolved with git add/rm <conflicted_files> after which execute git rebase --continue to proceed with the rebase step.
  • Or you are able to do git rebase --abort to abort the rebase operation and revert to the state earlier than the operation.
  • Since we moved up the place of commit F, the edit operation on F will likely be carried out subsequent. A brand new shell session will likely be entered:
cease at 74199ce... commit F
Now you can patch this commit, utilizing
git commit --amendWhen you find yourself happy with the change, executegit rebase --continue
  • We add a brand new code file and execute git commit --amend to merge it into the present earlier commit (ie: F), then modify its commit info to commit G within the editor interface, and eventually execute git rebase --continue to proceed the rebase operation.
  • Lastly, carry out the reword operation on commit E, and modify its commit info to commit H within the editor interface.

You’re executed! Lastly, let’s verify the commit historical past after the rebase.

64710dc88ef4fbe8fe7aac206ec2e3ef12e7bca9 (HEAD -> characteristic) commit H
8ab4506a672dac5c1a55db34779a185f045d7dd3 commit G
1e186f890710291aab5b508a4999134044f6f846 commit C
ef1372522cdad136ce7e6dc3e02aab4d6ad73f79 commit A

Precisely as anticipated, and you too can see that each one commit IDs after commit A have modified, which confirms that Git recreated these commits as we mentioned earlier.

# 1. Do a rebase earlier than merging.

One other frequent use of rebase is to carry out a rebase earlier than pushing to the distant for merging, usually to make sure a clear commit historical past.

We first develop in our personal perform department. When the event is accomplished, we have to rebase the present perform department to the most recent grasp department, resolve potential conflicts prematurely, after which submit the modifications to the distant.

This manner, the maintainer of the grasp department of the distant repository now not must merge and create an extra merge commit, however solely must carry out a fast-forward merge.

Even with a number of branches being developed in parallel, you find yourself with a very linear commit historical past.

# 2. rebase to a different department.

We will evaluate the 2 branches by rebase, taking out the corresponding modifications, after which making use of them to the opposite department. E.g:

F---G patch
/
D---E characteristic
/
A---B---C grasp

Suppose we created a department patch primarily based on commit D of the characteristic department, and added commits F and G.

Now we need to merge the modifications made by the patch to grasp and publish it, however we don’t need to merge the characteristic but. On this case, we are able to use — -onto <department>choice for rebase:

git rebase —onto grasp characteristic patch

The above will take the patch department, evaluate the modifications it made primarily based on the characteristic, and reapply these modifications on the grasp department, making the patch look as if the modifications have been immediately primarily based on grasp. The patch after execution is as follows.

A---B---C---F'---G' patch

Then we are able to swap to the grasp department and carry out a fast-forward merge on the patch:

git checkout grasp
git merge patch

git pull through rebase technique.

After a sure model of Git, operating git pull immediately will immediate the next message:

warning: Doing a pull operation with out specifying a merge technique for the deviating department will not be beneficial. You possibly can suppress this message by executing the next command earlier than the subsequent pull operation:  git config pull.rebase false  # Merge (default coverage)
git config pull.rebase true # rebase
git config pull.ff solely # quick ahead solely
......

It seems that git pull may also be merged by rebase, as a result of git pull is definitely equal to git fetch + git merge .

Within the second step, we are able to immediately exchange git merge with git rebase to merge the modifications obtained by fetch, which additionally avoids extra merge commits to take care of a linear commit historical past.

The distinction between the 2 has been in contrast above. We will regard the Grasp department within the comparability instance as a distant department and the Characteristic department as a neighborhood department.

Once we carry out a git pull domestically, we really pull the modifications from the Grasp and merge them into the Characteristic department.

If each branches have completely different commits, the default git merge methodology produces a single merge decide to combine these commits.

Utilizing git rebase is equal to recreating the native department primarily based on the most recent commit of the distant department, after which reapplying the domestically added commit.

There are a number of particular methods to make use of it:

  • Add particular choices every time you execute the pull command: git pull — -rebase.
  • Set the configuration merchandise for the present repository: git config pull.rebase true, add the --global choice after git config to make the configuration merchandise efficient for all repositories.

Within the above situations, rebase may be very highly effective, however we additionally want to appreciate that it’s not a panacea, and it’s even harmful for novices.

In case you are slightly careless, you will see that the commits the git log are lacking, or you might be caught in a sure step of the rebase and don’t know the way to restore it.

We’ve talked about above that rebase has the benefit of sustaining a clear, linear commit historical past, but it surely’s vital to pay attention to the next potential drawbacks:

If it entails commits which have already been pushed, a compelled push is required to push the commits after the native rebase to the distant.

  • So by no means do a rebase on a public department (that’s, there are different folks growing on this department), in any other case another person doing a git pull will merge a complicated native historical past of commits, which will likely be pushed again to the distant department. The distant submission historical past will likely be disrupted, and in extreme instances, it might pose a danger to your private security.
  • Not pleasant to novices, novices are more likely to “lose” some commits by mistake in interactive mode (however they’ll really be retrieved).
  • When you use rebase steadily to combine updates to the grasp department, a possible consequence is that you’ll encounter an increasing number of conflicts that have to be merged.
  • Though you may deal with these conflicts through the rebase course of, this isn’t a everlasting answer, and it’s extra beneficial to steadily merge into the grasp department after which create new characteristic branches, quite than utilizing a long-lived characteristic department.

There are additionally some factors that we must always attempt to keep away from rewriting the commit historical past.

There may be an argument that the commit historical past of a repository is a file of what really occurred. It’s a historic doc, which is effective in itself and can’t be altered arbitrarily.

From this viewpoint, altering the commit historical past is blasphemy, you’re utilizing a mislead cowl up what really occurred.

What if the commit historical past ensuing from the merge is a multitude? Since that is the case, these traces must be preserved in order that future generations can confer with them.

And frequent use of rebase could make it more durable to find bugs from historic commits, see Why you should stop using Git rebase.

After rebase in interactive mode and executing instructions like squash or drop on commits, commits are eliminated immediately from the department’s git log. When you make a mistake, you’ll break a sweat pondering these commits are gone ceaselessly.

However these commits are usually not really deleted. As talked about above, Git doesn’t modify (or delete) the unique commit, however recreates a brand new batch of commits and factors the present department tip to the brand new commit.

So we are able to use git reflog to search out and repoint the unique commits to revive them, which undoes the complete rebase.

Because of Git, even in the event you do one thing like rebase or commit --amend to rewrite the commit historical past, it doesn’t actually lose any commits.

git reflog command.

Reflogs are a mechanism that Git makes use of to file updates to the department tip of the native repository. It information all commits that the department tip has ever pointed to, so reflogs permit us to search out and swap to a commit that’s not at present referenced by any department or tag.

Each time the department tip is up to date for any motive (by switching branches, pulling new modifications, rewriting historical past, or including new commits), a brand new file will likely be added to the reflogs.

This manner, each commit we make domestically will certainly be recorded in reflogs.

Even after rewriting the commit historical past, reflogs comprise details about the previous state of the department and permit us to revert to that state if wanted.

Word that reflogs are usually not saved completely, they’ve an expiration time of 90 days.

Let’s proceed from the earlier instance. Suppose we need to restore the A→B→C→D→E→F commit historical past of the characteristic department earlier than the rebase, however there aren’t any final 5 commits within the git log at the moment, so it must be retrieved from the reflogs Discover, run git reflog and the outcomes are as follows:

64710dc (HEAD -> characteristic) HEAD@0: rebase (proceed) (end): returning to refs/heads/characteristic
64710dc (HEAD -> characteristic) HEAD@1: rebase (proceed): commit H
8ab4506 HEAD@2: rebase (proceed): commit G
1e186f8 HEAD@3: rebase (squash): commit C
c50221f HEAD@4: rebase (begin): checkout ef1372522cdad136ce7e6dc3e02aab4d6ad73f79
74199ce HEAD@5: checkout: shifting from grasp to characteristic
......

reflogs utterly information the entire technique of switching branches and rebase. Persevering with to go looking down, we discovered the commit F that disappeared from the git log:

74199ce HEAD@15: commit: commit F

Subsequent, we re-point the tip of the characteristic department to the unique commit F through git reset:

# We need to restore the information within the workspace as effectively, so we use the --hard choice   
$ git reset --hard 74199ce
HEAD now positioned in 74199ce commit F

Operating git log once more will discover that every thing is again to earlier than:

74199cebdd34d107bb67b6da5533a2e405f4c330 (HEAD -> characteristic) commit F
e7c7111d807c1d5209b97a9c75b09da5cd2810d4 commit E
d9623b0ef9d722b4a83d58a334e1ce85545ea524 commit D
73deeedaa944ef459b17d42601677c2fcc4c4703 commit C
c50221f93a39f3474ac59228d69732402556c93b commit B
ef1372522cdad136ce7e6dc3e02aab4d6ad73f79 commit A

Thanks for studying this text.

Keep tuned for extra.

More Posts