threads / discuss / 14172

is rebase the same as merging every commit?

Subject: is rebase the same as merging every commit?

## tl;dr

11 messages between Jun 26, 2008 and Aug 14, 2016.

replies: 10people: 5as markdown or json

David Jeske· Jun 26, 2008, 23:42 UTC · lore

Rebasing is described in the docs I've read as turning this: (sorry for the dots)

..........A---B---C topic ........./ ....D---E---F---G master

Into this:

...................A'--B'--C' topic ................../ .....D---E---F---G master

If I understand it right (and that's a BIG if), it's the same as doing a merge of C into G where every individual commit in the C-line is individually committed into the new C' line.

...........-------------A---B---C ........../ / / / ........./ /---A'--B'--C' topic ......../ / ....D---E---F---G - master

(1) Is the above model a valid explanation?

(2) From the documentation diagrams, it looks like the rebased A' has only (G) as a parent, not (A,G). If this is the case, why? (i.e. not connecting those nodes throws away useful information)

(3) If it only has (G) as a parent, does the rebase explicitly remove the source A,B,C nodes from the repository? (the diagrams make it look like it does) ..or do they just get cleaned up during GC?

Junio C Hamano· Jun 27, 2008, 00:51 UTC · re: David Jeske · lore

Re: is rebase the same as merging every commit?

"David Jeske" <jeske@willowmail.com> writes:
Show 25 quoted lines
> Rebasing is described in the docs I've read as turning this: (sorry for the
> dots)
>
> ..........A---B---C topic
> ........./
> ....D---E---F---G master
>
> Into this:
>
> ...................A'--B'--C' topic
> ................../
> .....D---E---F---G master
>
> If I understand it right (and that's a BIG if), it's the same as doing a merge
> of C into G where every individual commit in the C-line is individually
> committed into the new C' line.
>
> ...........-------------A---B---C
> ........../            /   /   /
> ........./        /---A'--B'--C'  topic
> ......../        /
> ....D---E---F---G - master
>
>
> (1) Is the above model a valid explanation?

I would presume that the resulting trees A' in the second picture and in the first picture would be the same, so are B' and C'. But that is only true when commits between A and C do not have any duplicate with the development that happened between E and G.

Thinking about it like that is an interesting mental exercise, but it is not very useful otherwise.

> (2) From the documentation diagrams, it looks like the rebased A' has
> only (G) as a parent, not (A,G). If this is the case, why?  (i.e. not
> connecting those nodes throws away useful information)

You would rebase ONLY WHEN the project as the whole (either "other people in the project", or "yourself down the road one year from now") is interested mostly in the progress of 'master' D-E-F-G, and nobody cares whether you developed your A (or B or C) on top of E or G. So the answer is definite "no" --- the line you drew between A and A' is a useless information. Nobody cares you did it first on top of E but then you have redone the patches based on G (because things changed between E and G).

If there were no "rebase", your changes will be integrated into 'master' branch like this:

          A---B---C
         /         \
    D---E---F---G---M

Rebasing is a way to _help you_ pretend that you did _not_ start working on an ancient code base that was at E. You redo your series on top of the latest and greatest G, the commit that everybody else agrees is the current state of affairs when he sees your changes for the first time, to produce a history like this:

    D---E---F---G---A'--B'--C'

Doing so tends to make the history easier to understand, and more importantly, it reduces mistakes during the integration _and_ distributes the burden of integration from central point.

If E..G and A..C happen to have conflicting changes, rebasing puts the burden to rewrite the changes A..C into A'..C', based on the modified base code G, on _you_ (the person who is rebasing). Some people do not like this, as they feel that is an added, unwanted burden. On the other hand, if your upstream maintainer is integrating like the above picture to create a merge 'M', it is more likely that he would make mistakes during the conflict resolution, than you make incorrect adjustment during your rebasing to recreate the series A'..C'. You read what G gives you as the foundation to build your changes on, determine what got changed since E, on which you originally based your changes, and adjust your changes to better integrate on top of G. After all, A..C is _your code_ and you understand what it assumes better than anybody else.

If the fact that parallel developments have happened is important, instead of the second picture like you drew, you will just do the real merge naturally to create a merge "M" like the picture I drew above.

Your "A' is merge between E and A, B' is merge between A' and B" is not something anybody is interested in if you are going to rebase. It is not interesting because it is not how things happened in the real life at all, and it is not interesting because it is not simplifying the history for later analysis nor reducing mistakes during the conflict resolution.

Junio C Hamano· Jun 27, 2008, 01:08 UTC · re: Junio C Hamano · lore

Re: is rebase the same as merging every commit?

Junio C Hamano <gitster@pobox.com> writes:
Show 13 quoted lines
> "David Jeske" <jeske@willowmail.com> writes:
> ...
>> (2) From the documentation diagrams, it looks like the rebased A' has
>> only (G) as a parent, not (A,G). If this is the case, why?  (i.e. not
>> connecting those nodes throws away useful information)
>
> You would rebase ONLY WHEN the project as the whole (either "other people
> in the project", or "yourself down the road one year from now") is
> interested mostly in the progress of 'master' D-E-F-G, and nobody cares
> whether you developed your A (or B or C) on top of E or G.  So the answer
> is definite "no" --- the line you drew between A and A' is a useless
> information.  Nobody cares you did it first on top of E but then you have
> redone the patches based on G (because things changed between E and G).
The last sentence came out in somewhat inappropriate way.
	In the situation "rebase" (which is a way to help you pretend you
	did not start building on a stale codebase) is appropriate, nobody
	wants to know you did it first on E

is what I meant. More importantly, _you_ do not want anybody to know. That is the whole reason you would rebase.

With that clarification in mind, the explanation would flow more smoothly to this part...

> If the fact that parallel developments have happened is important, instead
> of the second picture like you drew, you will just do the real merge
> naturally to create a merge "M" like the picture I drew above.

So you have a choice between merging and rebasing. And your extra parents goes against the reason you chose rebasing in the first place. That is why we do not record the original parents anywhere.

David Jeske· Jun 27, 2008, 06:50 UTC · re: Junio C Hamano · lore

Re: is rebase the same as merging every commit?

Thanks for the explanation.

However, when considering an SCM perspective, I don't understand why I have to make a tradeoff between personal reproducibility (which I get from the original changes), and upstream readability (which the community gets from my rebase).

I could get both of these if the rebase kept both the old and new.

Is there some reason that losing personal reproducability, and personal/local tracking back to those changes of A-B-C is necessary as part of the process?

Further, the rebase machinery seems like it would be great for operations that are even more 'dangerous', where I would really really want the history of the transitions in case I realized a problem later.

Consider this set of commits on a personal branch

0 - feature a 1 - feature b 2 - bugfix a 3 - feature c / d 4 - bugfix b 5 - bugfix a2

>From all I've read about rebase, bisect, and the big tree management, it seems

like the three steps are Reorder, combine, rebase. (In a more complicated situation, i'd want to split a commit into pieces)

(1'') 0' - feature A 1' - bugfix a 2' - bugfix a2 (2'') 3' - feature b 4' - bugfix b (3'') 5' - feature c (split) (4'') 6' - feature d (split)

Frankly, I'm super impressed, because I can imagine how I might do this in git. I'm guessing some of you are already doing this. But how do you do it? Can you rebase a patch back into it's own history? (such as bugfix a from 2, to 1')

I want to mess around and try this stuff out, but I'm scared of doing bad things to the tree and them being unrecoverable because rebase tosses the old stuff. I don't understand why I have to lose my original work and/or the connection to my original work, in order to reorder/combine/split for public consumption. What is the argument for that? (other than the fact that the current dag link propagation model would force others to get these changes if they remained connected together. Something easily remidied by out of band metadata, or different link types)

David Jeske· Aug 14, 2016, 00:43 UTC · re: Junio C Hamano · lore

Re: is rebase the same as merging every commit?

Thanks for the explanation.

However, when considering an SCM perspective, I don't understand why I have to make a tradeoff between personal reproducibility (which I get from the original changes), and upstream readability (which the community gets from my rebase).

I could get both of these if the rebase kept both the old and new.

Is there some reason that losing personal reproducability, and personal/local tracking back to those changes of A-B-C is necessary as part of the process?

Further, the rebase machinery seems like it would be great for operations that are even more 'dangerous', where I would really really want the history of the transitions in case I realized a problem later.

Consider this set of commits on a personal branch

0 - feature a 1 - feature b 2 - bugfix a 3 - feature c / d 4 - bugfix b 5 - bugfix a2

>From all I've read about rebase, bisect, and the big tree management, it seems

like the three steps are Reorder, combine, rebase. (In a more complicated situation, i'd want to split a commit into pieces)

(1'') 0' - feature A 1' - bugfix a 2' - bugfix a2 (2'') 3' - feature b 4' - bugfix b (3'') 5' - feature c (split) (4'') 6' - feature d (split)

Frankly, I'm super impressed, because I can imagine how I might do this in git. I'm guessing some of you are already doing this. But how do you do it? Can you rebase a patch back into it's own history? (such as bugfix a from 2, to 1')

I want to mess around and try this stuff out, but I'm scared of doing bad things to the tree and them being unrecoverable because rebase tosses the old stuff. I don't understand why I have to lose my original work and/or the connection to my original work, in order to reorder/combine/split for public consumption. What is the argument for that? (other than the fact that the current dag link propagation model would force others to get these changes if they remained connected together. Something easily remidied by out of band metadata, or different link types)

Matthieu Moy· Jun 27, 2008, 07:34 UTC · re: David Jeske · lore

Re: is rebase the same as merging every commit?

"David Jeske" <jeske@willowmail.com> writes:
> However, when considering an SCM perspective, I don't understand why I have to
> make a tradeoff between personal reproducibility (which I get from the original
> changes), and upstream readability (which the community gets from my rebase).

Well, look at the [PATCH] messages on this list, and how they evolve. Patch series give a clean way to go from a point to another. That's what you want to see in upstream history.

Then, patch series usually get reviewed, and the patches themselves are modified. There's a kind of meta-history: the changes you make to your own changes.

Suppose I send a patch containing
+	int * x = malloc(sizeof(char));
and someone notices how wrong it is. I send another patch with
+	int * x = malloc(sizeof(int));

The first version was basicaly a mistake, and if it hasn't been released, no one want to bother with it longer that the time to resend the patch. No one want to be hit by the bug while using bisect later on the upstream repository. And no one wants to see both patches when reviewing or "git blame"-ing.

Things you rebase in Git are just like things for which you don't make intermediate commits in SVN.

Show 18 quoted lines
>>From all I've read about rebase, bisect, and the big tree management, it seems
> like the three steps are Reorder, combine, rebase.  (In a more complicated
> situation, i'd want to split a commit into pieces)
>
> (1'')
> 0' - feature A
> 1' - bugfix a
> 2' - bugfix a2
> (2'')
> 3' - feature b
> 4' - bugfix b
> (3'')
> 5' - feature c (split)
> (4'')
> 6' - feature d (split)
>
> Frankly, I'm super impressed, because I can imagine how I might do
> this in git.
git rebase -i will help you to do that painlessly.
> I want to mess around and try this stuff out, but I'm scared of doing bad
> things to the tree and them being unrecoverable

They won't. The reflog is still there. Try it, an cancel it if you don't like.

The huge difference between the reflog and the history is that the reflog is local, it's your own mess, other people won't get disturbed by how messy it can be.

> (other than the fact that the current dag
> link propagation model would force others to get these changes if
> they remained connected together. Something easily remidied by out
> of band metadata, or different link types)

No. One fundamental principle of Git is that objects are immutable. If your objects have a link, of whatever kind, then the same object moved in another repository have the same link.

But what's wrong with the reflog?
-- 
Matthieu
David Jeske· Jun 27, 2008, 15:07 UTC · re: Matthieu Moy · lore

Re: is rebase the same as merging every commit?

This example you provided Matthieu is exactly my confusion with rebase..

If I want to bring a 'broken feature-a' branch into my topic branch to build on it, one commit of which is this:

> +	int * x = malloc(sizeof(char));
if I merge, my tree looks like:

: /<--G<--H<--Qj jeske/topic1 : / / : /<--P<------Q feature-a : / : -----A<---B<---C master

or if I rebase, it looks like:

: /<--G'<--H' jeske/topic1 : / : /<--P<---Q feature-a : / : -----A<---B<---C master

-----

..and then through 'fixing' the patch, it ends up rebased and accepted onto the mainline origin/master, as a single patch, which (among other things) changed this line to:

> +	int * x = malloc(sizeof(int));
...if I merged above, it will look like,

: /<--G<--H<--Qj jeske/topic1 : / / : /<--P<------Q feature-a : / : -----A<---B<---C<---Q' master

...if I rebased above, it will look like:

: /<--G'<--H' jeske/topic1 : / : /<--P<---Q feature-a : / : -----A<---B<---C<---Q' master

However, in both cases, because Q' is not connected to Q, I don't see how git will do anything sane to help me accept Q' correctly.

If I rebased my merge-q-branch against the master, I would expect to get this (which will cause a conflict I have to resolve):

: /<--G<--H<--Qj jeske/topic1 : / : <--C<---Q' master

If I rebased my rebase-q-branch against master, I would expect to get this (which will cause a conflict I have to resolve):

: /<--G'<--H' jeske/topic1 : / : /<--P<---Q feature-a : / : --C<---Q' master

However, if that Q' rebase contained a link back to (P,Q), it would know that the Q' rebase was replacing (P,Q), and would know to back them out of my tree when I rebased back onto the head, producing this in BOTH cases above (whether I rebased or merged from the feature-a branch):

: /<--G'<--H' jeske/topic1 : / : --C<---Q' master

This operation above of "working will pulling uncompleted patches into my tree" seems like a fairly common thing for developers. I've never provided any patches to linux-kernel, but when I did try hacking on it years ago, I was doing exactly this. (pulling unaccepted patches into my kernel, then building on those patches). When I read about the DAG and its universal naming, I always assumed that the above workflow was what it was DESIGNED to make automatic. I'm confused, how does this work in git?

-- Matthieu Moy wrote:
Show 21 quoted lines
> Well, look at the [PATCH] messages on this list, and how they evolve.
> Patch series give a clean way to go from a point to another. That's
> what you want to see in upstream history.
>
> Then, patch series usually get reviewed, and the patches themselves
> are modified. There's a kind of meta-history: the changes you make to
> your own changes.
>
> Suppose I send a patch containing
>
> +	int * x = malloc(sizeof(char));
>
> and someone notices how wrong it is. I send another patch with
>
> +	int * x = malloc(sizeof(int));
>
> The first version was basicaly a mistake, and if it hasn't been
> released, no one want to bother with it longer that the time to resend
> the patch. No one want to be hit by the bug while using bisect later
> on the upstream repository. And no one wants to see both patches when
> reviewing or "git blame"-ing.
David Jeske· Aug 14, 2016, 00:43 UTC · re: Matthieu Moy · lore

Re: is rebase the same as merging every commit?

This example you provided Matthieu is exactly my confusion with rebase..

If I want to bring a 'broken feature-a' branch into my topic branch to build on it, one commit of which is this:

> +	int * x = malloc(sizeof(char));
if I merge, my tree looks like:

: /<--G<--H<--Qj jeske/topic1 : / / : /<--P<------Q feature-a : / : -----A<---B<---C master

or if I rebase, it looks like:

: /<--G'<--H' jeske/topic1 : / : /<--P<---Q feature-a : / : -----A<---B<---C master

-----

..and then through 'fixing' the patch, it ends up rebased and accepted onto the mainline origin/master, as a single patch, which (among other things) changed this line to:

> +	int * x = malloc(sizeof(int));
...if I merged above, it will look like,

: /<--G<--H<--Qj jeske/topic1 : / / : /<--P<------Q feature-a : / : -----A<---B<---C<---Q' master

...if I rebased above, it will look like:

: /<--G'<--H' jeske/topic1 : / : /<--P<---Q feature-a : / : -----A<---B<---C<---Q' master

However, in both cases, because Q' is not connected to Q, I don't see how git will do anything sane to help me accept Q' correctly.

If I rebased my merge-q-branch against the master, I would expect to get this (which will cause a conflict I have to resolve):

: /<--G<--H<--Qj jeske/topic1 : / : <--C<---Q' master

If I rebased my rebase-q-branch against master, I would expect to get this (which will cause a conflict I have to resolve):

: /<--G'<--H' jeske/topic1 : / : /<--P<---Q feature-a : / : --C<---Q' master

However, if that Q' rebase contained a link back to (P,Q), it would know that the Q' rebase was replacing (P,Q), and would know to back them out of my tree when I rebased back onto the head, producing this in BOTH cases above (whether I rebased or merged from the feature-a branch):

: /<--G'<--H' jeske/topic1 : / : --C<---Q' master

This operation above of "working will pulling uncompleted patches into my tree" seems like a fairly common thing for developers. I've never provided any patches to linux-kernel, but when I did try hacking on it years ago, I was doing exactly this. (pulling unaccepted patches into my kernel, then building on those patches). When I read about the DAG and its universal naming, I always assumed that the above workflow was what it was DESIGNED to make automatic. I'm confused, how does this work in git?

-- Matthieu Moy wrote:
Show 21 quoted lines
> Well, look at the [PATCH] messages on this list, and how they evolve.
> Patch series give a clean way to go from a point to another. That's
> what you want to see in upstream history.
>
> Then, patch series usually get reviewed, and the patches themselves
> are modified. There's a kind of meta-history: the changes you make to
> your own changes.
>
> Suppose I send a patch containing
>
> +	int * x = malloc(sizeof(char));
>
> and someone notices how wrong it is. I send another patch with
>
> +	int * x = malloc(sizeof(int));
>
> The first version was basicaly a mistake, and if it hasn't been
> released, no one want to bother with it longer that the time to resend
> the patch. No one want to be hit by the bug while using bisect later
> on the upstream repository. And no one wants to see both patches when
> reviewing or "git blame"-ing.
Pascal Obry· Jun 27, 2008, 06:31 UTC · re: Junio C Hamano · lore

Re: is rebase the same as merging every commit?

Junio C Hamano a écrit :
> You would rebase ONLY WHEN the project as the whole (either "other people
> in the project", or "yourself down the road one year from now") is
> interested mostly in the progress of 'master' D-E-F-G, and nobody cares
> whether you developed your A (or B or C) on top of E or G.  So the answer

Or if you are using git-svn as nobody will ever see your local branches. So rebasing is just the right way to go when tracking a Subversion tree I would say.

Pascal.
-- 
--|------------------------------------------------------
--| Pascal Obry                           Team-Ada Member
--| 45, rue Gabriel Peri - 78114 Magny Les Hameaux FRANCE
--|------------------------------------------------------
--|              http://www.obry.net
--| "The best way to travel is by means of imagination"
--|
--| gpg --keyserver wwwkeys.pgp.net --recv-key C1082595
しらいしななこ· Jun 27, 2008, 10:33 UTC · re: Junio C Hamano · lore

Re: is rebase the same as merging every commit?

Quoting Junio C Hamano <gitster@pobox.com>:
Show 19 quoted lines
> "David Jeske" <jeske@willowmail.com> writes:
>
>> If I understand it right (and that's a BIG if), it's the same as doing a merge
>> of C into G where every individual commit in the C-line is individually
>> committed into the new C' line.
>>
>> ...........-------------A---B---C
>> ........../            /   /   /
>> ........./        /---A'--B'--C'  topic
>> ......../        /
>> ....D---E---F---G - master
>>
>>
>> (1) Is the above model a valid explanation?
>
> I would presume that the resulting trees A' in the second picture and in
> the first picture would be the same, so are B' and C'.  But that is only
> true when commits between A and C do not have any duplicate with the
> development that happened between E and G.
Sorry, but I think you are wrong, Junio.
Rebase can be used to backport changes, not just porting your changes forward, using --onto option:

..........maint ............1-------A'--B'--C' .........../ . . . <-- ??? ........../.......A---B---C ........./......./ ......../......./ .......0--...--D---E---F---G - master

Here, A, B, C that are based on D (that is way ahead of the top of the maintenance branch 1) is rebased to the maintenance branch.
But in this case, A' is *not* a merge between 1 and A.  For A' to be a merge between 1 and A, it *must* contain all the development that happened up to 1 and all the development that happened up to A since these two branches were forked (that is 0 in the above picture).
Instead, the difference to go from 1 to A' is similar to the difference to go from D to A. It does not and must not include anything that happened between 0 and D.  That is not a merge.
I agree that your explanation why A is not recorded as a parent of A' is right for the philosophical reason (the purpose of rebasing to create A' is so that you do not have to record them).  But from the point-of-view of correctness of commit history, I think A must not be recorded as a parent of A', either.
-- 
Nanako Shiraishi
http://ivory.ap.teacup.com/nanako3/
Junio C Hamano· Jun 27, 2008, 21:51 UTC · re: しらいしななこ · lore

Re: is rebase the same as merging every commit?

しらいしななこ  <nanako3@lavabit.com> writes:
Show 29 quoted lines
> Quoting Junio C Hamano <gitster@pobox.com>:
>
>> "David Jeske" <jeske@willowmail.com> writes:
>>
>>> If I understand it right (and that's a BIG if), it's the same as doing a merge
>>> of C into G where every individual commit in the C-line is individually
>>> committed into the new C' line.
>>>
>>> ...........-------------A---B---C
>>> ........../            /   /   /
>>> ........./        /---A'--B'--C'  topic
>>> ......../        /
>>> ....D---E---F---G - master
>>>
>>>
>>> (1) Is the above model a valid explanation?
>>
>> I would presume that the resulting trees A' in the second picture and in
>> the first picture would be the same, so are B' and C'.  But that is only
>> true when commits between A and C do not have any duplicate with the
>> development that happened between E and G.
>
> Sorry, but I think you are wrong, Junio.
> ...
> I agree that your explanation why A is not recorded as a parent of A' is
> right for the philosophical reason (the purpose of rebasing to create A'
> is so that you do not have to record them).  But from the point-of-view
> of correctness of commit history, I think A must not be recorded as a
> parent of A', either.
All correct.  Sorry about the confusion.

← back to recent threads