{"id":15104,"date":"2022-08-26T13:25:57","date_gmt":"2022-08-26T10:25:57","guid":{"rendered":"https:\/\/railsware.com\/blog\/?p=15104"},"modified":"2023-02-07T16:09:17","modified_gmt":"2023-02-07T13:09:17","slug":"how-to-use-git-rebase-onto","status":"publish","type":"post","link":"https:\/\/railsware.com\/blog\/how-to-use-git-rebase-onto\/","title":{"rendered":"New Level of Control with git rebase \u2011\u2011onto"},"content":{"rendered":"\n<div class=\"intro-text\">Git rebase is a great tool to rearrange your commit history and make it tidier. But during my long history of using it, I collected a bunch of use cases that were hard to handle with a usual rebase. It turns out that <em>rebase<\/em> has a little-known option <em>\u2011\u2011onto<\/em> which was a game changer for me.<\/div>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"538\" src=\"https:\/\/railsware.com\/blog\/wp-content\/uploads\/2022\/08\/Git-onto_Featured-image_2400x1260-1024x538.jpg\" alt=\"\" class=\"wp-image-15101\" srcset=\"https:\/\/railsware.com\/blog\/wp-content\/uploads\/2022\/08\/Git-onto_Featured-image_2400x1260-1024x538.jpg 1024w, https:\/\/railsware.com\/blog\/wp-content\/uploads\/2022\/08\/Git-onto_Featured-image_2400x1260-360x189.jpg 360w, https:\/\/railsware.com\/blog\/wp-content\/uploads\/2022\/08\/Git-onto_Featured-image_2400x1260-768x403.jpg 768w, https:\/\/railsware.com\/blog\/wp-content\/uploads\/2022\/08\/Git-onto_Featured-image_2400x1260-1536x806.jpg 1536w, https:\/\/railsware.com\/blog\/wp-content\/uploads\/2022\/08\/Git-onto_Featured-image_2400x1260-2048x1075.jpg 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>In a nutshell, <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">git rebase \u2011\u2011onto<\/code><\/code> allows you to move or copy a bunch of commits from one branch atop another. You can treat it as a <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">git cherry-pick<\/code><\/code> on steroids. Without further ado, let&#8217;s see it in action.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What\u2019s the problem with <em>rebase<\/em><\/h2>\n\n\n\n<p>Imagine, there is a branch <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">feature1<\/code><\/code>, that was created by your colleague. Your goal is to add some new stuff to it. You happily branched off of <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">feature1<\/code><\/code> and implemented new functionality in commits <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">C<\/code><\/code> and <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">D<\/code><\/code>. Here is what Git history looks like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>A --- B feature1\n       \\     \n        C --- D feature2<\/code><\/pre>\n\n\n\n<p>While you worked on your stuff, your colleague added some new functionality in <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">feature1<\/code><\/code>. But they also like to use rebase, so they modified commit <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">B<\/code><\/code> a bit. On the diagram below, it&#8217;s named <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">B*<\/code><\/code>. Here is what the working tree looks like now:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>A --- B* --- E --- F feature1\n \\     \n  B --- C --- D feature2<\/code><\/pre>\n\n\n\n<p>You need to catch up with their changes, so you decided to rebase:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>git rebase feature1<\/code><\/pre>\n\n\n\n<p>And now the bummer &#8211; you caught a conflict between <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">B<\/code><\/code> and <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">B<\/code><\/code>* and need to resolve it. Sound familiar?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Solving the issue with <em>git rebase \u2011\u2011onto<\/em><\/h2>\n\n\n\n<p>Before blaming your colleague, you need to realize that the issue is not with the change itself. The issue lies in the way standard <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">rebase<\/code><\/code> does its job. In the example above, rebase takes <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">feature1<\/code><\/code> as a starting point and then tries to apply all new commits atop.<\/p>\n\n\n\n<p>In other words, it takes commit <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">F<\/code><\/code> as a new starting point. Then, it finds the place where both branches have diverged. In our case, it&#8217;s commit <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">A<\/code><\/code>. So, <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">rebase<\/code><\/code> takes commits <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">B<\/code><\/code>, <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">C<\/code><\/code>, and <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">D<\/code><\/code>, and one by one, tries to add them on top of commit <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">F<\/code><\/code>. The diff for commit <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">B<\/code><\/code> cannot be applied to existing source tree and thus we need to resolve the conflict.<\/p>\n\n\n\n<p>Ideally, we want to avoid resolving the conflict and just get the next history tree:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>A --- B* --- E --- F feature1\n                    \\     \n                     C' --- D' feature2<\/code><\/pre>\n\n\n\n<p>(Notice: <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">C<\/code><\/code>&#8216; and <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">D<\/code><\/code>&#8216; have the same changesets as <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">C<\/code><\/code> and <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">D<\/code><\/code> but we updated the naming to reflect changes in <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">SHA<\/code><\/code>)<\/p>\n\n\n\n<p>So, we want to take commits<code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\"> C<\/code><\/code> and <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">D<\/code><\/code> and attach them on top of <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">feature1<\/code><\/code>, ignoring the previous history altogether. This can be achieved by git cherry-pick, but it&#8217;s a tedious and error-prone job.<\/p>\n\n\n\n<p>Instead, we can use <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">git rebase \u2011\u2011onto<\/code><\/code>. We need to be on <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">feature2<\/code><\/code> and execute this command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>git checkout feature2\ngit rebase --onto feature1 B<\/code><\/pre>\n\n\n\n<p>That means: take all commits after commit <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">B<\/code><\/code> (in our case it will be <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">C<\/code><\/code> and <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">D<\/code><\/code>) and place them atop of <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">feature1<\/code><\/code>.<\/p>\n\n\n\n<p>After applying this command, we&#8217;ll get the desired working tree without the need to resolve the conflict between <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">B<\/code><\/code> and <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">B*.<\/code><\/code> We can still have a conflict if <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">C<\/code><\/code> or <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">D<\/code><\/code> conflicts with <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">B*<\/code><\/code>, <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">E,<\/code><\/code> or <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">F<\/code><\/code>, but that&#8217;s a normal case.<\/p>\n\n\n\n<p>Instead of providing<code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\"> B<\/code><\/code> as the second argument, we can use relative addressing with <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">HEAD~N<\/code><\/code>. For our example it will be:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>git checkout feature2\ngit rebase \u2011\u2011onto feature1 HEAD~2<\/code><\/pre>\n\n\n\n<p>That can be read as: take two last commits from my branch and put them atop of <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">feature1<\/code><\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Solving the problem of branching from a wrong place<\/h2>\n\n\n\n<p>There are many cases when you can start your work in the wrong place or for some reason you need to move your commits to a totally different place.<\/p>\n\n\n\n<p>Here\u2019s a widespread example. You branched off of <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">main<\/code><\/code> branch, but suddenly a product manager tells you that your stuff is critical for production and you need to change the base of your branch to <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">hotfix<\/code><\/code> instead. Here is a commit history:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>       E --- F --- G feature\n       \/\nA --- B main\n \\\n  C --- D hotfix<\/code><\/pre>\n\n\n\n<p>You need to get the next history instead:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>A --- B main\n \\\n  C --- D hotfix\n         \\\n          E' --- F' --- G' feature<\/code><\/pre>\n\n\n\n<p>(Notice: <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">E'<\/code><\/code>, <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">F'<\/code><\/code>, and <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">G'<\/code><\/code> have the same changesets as <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">E,<\/code><\/code> <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">F<\/code><\/code>, and <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">G<\/code><\/code> but we updated the naming to reflect changes in <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">SHA<\/code><\/code>)<\/p>\n\n\n\n<p>If you use regular <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">rebase<\/code>, commit <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">B <\/code>will sneak into <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">hotfix<\/code> branch. In practice, <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">main<\/code> branch can have dozens of other features merged and you would bring all of them into <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">hotfix<\/code>.<\/p>\n\n\n\n<p>But <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">git rebase \u2011\u2011onto<\/code> does not have this issue. Here is a solution:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>git checkout feature\ngit rebase \u2011\u2011onto hotfix B<\/code><\/pre>\n\n\n\n<p>or the same with relative addressing:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>git checkout feature\ngit rebase \u2011\u2011onto hotfix HEAD~3<\/code><\/pre>\n\n\n\n<p>Actually, almost all cases of moving around commits can be handled with <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">\u2011\u2011onto<\/code><\/code> option.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Dropping commits<\/h2>\n\n\n\n<p>For the sake of completeness, let&#8217;s discuss how to remove commits with <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">\u2011\u2011onto<\/code> option. Here is an example of a working tree:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>A \u2011\u2011\u2011 B \u2011\u2011\u2011 C \u2011\u2011\u2011 D \u2011\u2011\u2011 E&nbsp; feature<\/code><\/pre>\n\n\n\n<p>We want to remove commits <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">C<\/code> and <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">D<\/code> to obtain the next commit history:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>A \u2011\u2011\u2011 B \u2011\u2011\u2011 E'&nbsp; feature<\/code><\/pre>\n\n\n\n<p>(Notice: <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">E<\/code> and E&#8217; have the same changeset, but <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">E'<\/code> has another <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">SHA<\/code> due to a change in commit history)<\/p>\n\n\n\n<p>Here is the solution:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>git checkout feature\ngit rebase \u2011\u2011onto B HEAD~1<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">A word of caution<\/h2>\n\n\n\n<p>Using <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">rebase<\/code> always has the risk of doing something wrong and losing your commits. If you are still new to this tool, be sure to follow the usual safety measures: keep SHA of the HEAD of your branch before doing rebase or be fluent with using <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">git reflog<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>As I said, <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">git rebase \u2011\u2011onto<\/code> greatly improved my commit housekeeping experience and removed a lot of pain points I had before. Despite the logic of <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">\u2011\u2011onto<\/code> option being quite simple, it took me some time to find a proper way and practical understanding of how to use it in various scenarios.<\/p>\n\n\n\n<p>So I hope this article will save you some time and extend your Git tricks collection.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A simple tutorial on how to use git rebase &#8211;onto to easily move or copy a bunch of commits from one branch atop another<\/p>\n","protected":false},"author":25,"featured_media":15103,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[3],"tags":[],"coauthors":["Sergii Boiko"],"class_list":["post-15104","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"acf":[],"aioseo_notices":[],"categories_data":[{"name":"Engineering","link":"https:\/\/railsware.com\/blog?category=development"}],"post_thumbnails":"https:\/\/railsware.com\/blog\/wp-content\/uploads\/2022\/08\/Git-onto_Featured-image_2400x1260-1024x538.jpg","amp_enabled":true,"_links":{"self":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/15104","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/users\/25"}],"replies":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/comments?post=15104"}],"version-history":[{"count":32,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/15104\/revisions"}],"predecessor-version":[{"id":15650,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/15104\/revisions\/15650"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/media\/15103"}],"wp:attachment":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/media?parent=15104"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/categories?post=15104"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/tags?post=15104"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/coauthors?post=15104"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}