threads / discuss / 20040

request for documentation about branch surgery

Subject: request for documentation about branch surgery

## tl;dr

10 messages between Jul 6, 2009 and Jul 7, 2009.

replies: 9people: 5as markdown or json

Bruno Haible· Jul 6, 2009, 23:05 UTC · lore
Hi,
Here are a few things that I would have expected to find in the Git user's
manual, chapter "Rewriting history and maintaining patch series"
  <http://www.kernel.org/pub/software/scm/git/docs/user-manual.html#cleaning-up-history>

It is meant for a user who does not yet know about "git rebase", "git merge", "git reset" and the like. Organized by the goal that a user has ("how do I?").

1) After the section "Rewriting a single commit", it may be useful to
have a section "Inserting one or more new commits". This is something that
cannot be done with the "detached head" technique. I found this sequence
of commands useful:
  If you want to add a commit in the middle of a branch:
            A---C---...---Z    master
  =>
            A---B---C---...---Z    master
  it is achieved by
    $ git checkout A
    $ git branch temp
    $ git checkout temp
    [make changes for B]
    $ git commit -a
  now:
            A---C---...---Z    master
             \
              --B              temp
    $ git checkout master
    $ git rebase temp
    $ git branch -d temp
2) About the section "Reordering or selecting from a patch series".
   The "git rebase" documentation contains a good example:
   Reordering, possibly merging, the last 5 commits:
   $ git rebase -i HEAD~5
   See "man git-rebase" under "INTERACTIVE MODE".
   I find this technique very useful, because it guarantees that no
   commit gets lost (unlike "git cherry-pick"). Maybe the section could
   be split into two sections
      "Reordering a patch series"
   and
      "Selecting from a patch series"?
3) When do I need "git merge", and when do I need "git rebase", in the
   context of branch surgery?
   The simple answer, that I would find worth mentioning, is:
     - "git merge" copies commits from one branch to another.
     - "git rebase" only moves commits around to make history more linear.
4) It would be good to have a section "Cutting branches"
   How do I remove the N most recent commits from a branch?
               D---E---F---G---H---.........---Y---Z master
  =>
               D---E master
   It goes like this:
     $ git checkout master
     $ git reset --hard E
5) Then, it would be good to have a section "Replacing branches"
   How do I copy the contents of a branch over to another branch, replacing
   the recent development on that branch?
  If you want to copy a branch into another, while throwing away commits:
                     F1---F2---F3                         released
                    /
               D---E---F---G---H---.........---Y---Z master
  =>
                     F1---F2---F3   released
                    /
               D---E---F1---F2---F3 master
  This is achieved by
    $ git checkout master
    $ git reset --hard E      # Cut the branch "master"
    $ git merge released      # Copy commits from branch "released" to "master"
6) Also, it would be good to have a section "Reconnecting branches after rebase".
   If you want to reconnect a branch to a rebased master, here's how to do it:
                   /--C'--...---P'--Q'--...---Z'  new rebased master
              A---B---C---...---P---Q---...---Z   old master
                                 \
                                  --BA---...---BZ  release-branch
  =>
              A---B---C'--...---P'--Q'--...---Z'  new rebased master
                                 \
                                  --BA---...---BZ  release-branch
  This is achieved by
    $ git checkout release-branch
    $ git rebase --onto P' P

This might seem exotic, but these use cases all came up while rewriting the history after a "git cvsimport".

Bruno
Elijah Newren· Jul 7, 2009, 02:30 UTC · re: Bruno Haible · lore

Re: request for documentation about branch surgery

Hi,

It's been a long time since I read the user manual, so I'm only comment to point out a few shortcuts (whether or not they're more logical to present to new users) and clarifications on the usecases you presented.

On Mon, Jul 6, 2009 at 5:05 PM, Bruno Haible<bruno@clisp.org> wrote:
Show 30 quoted lines
> 1) After the section "Rewriting a single commit", it may be useful to
> have a section "Inserting one or more new commits". This is something that
> cannot be done with the "detached head" technique. I found this sequence
> of commands useful:
>
>  If you want to add a commit in the middle of a branch:
>
>            A---C---...---Z    master
>
>  =>
>
>            A---B---C---...---Z    master
>
>  it is achieved by
>
>    $ git checkout A
>    $ git branch temp
>    $ git checkout temp
>    [make changes for B]
>    $ git commit -a
>
>  now:
>
>            A---C---...---Z    master
>             \
>              --B              temp
>
>    $ git checkout master
>    $ git rebase temp
>    $ git branch -d temp
Or compress those 8 steps down to 4 using a detached HEAD and
multi-argument rebase:
  $ git checkout A
  [make changes for B]
  $ git commit -a
  $ git rebase HEAD master

The last command above means "rebase master against HEAD". I think the rebase command would be much easier to understand for new users if it used an "--against" before the first reference(*). For example, "git rebase --against temp" for rebasing the current branch against temp (instead of "git rebase temp") or "git rebase --against HEAD master" (rather than "git rebase HEAD master"); the real git rebase commands don't parse so well for new users and take a long time to wrap ones' head around, whereas this simple change seems to make it much easier.

(*) Except when --onto is specified. Then --since may make more sense than --against. Also, I'm not yet suggesting we make this change (since I have no patch handy), but note that if anyone wants to try this then the "--against" should be optional for backwards compatibility. It's mostly a documentation change.

<snip case 2>
Show 5 quoted lines
> 3) When do I need "git merge", and when do I need "git rebase", in the
>   context of branch surgery?
>
>   The simple answer, that I would find worth mentioning, is:
>     - "git merge" copies commits from one branch to another.

git merge does not copy commits at all; unlike cvs/svn, a commit may (and often is) part of more than one branch in git. A merge simply creates a new commit which has more than one parent: the previous tip of the current branch, and the tip of the other branch you are merging.

>     - "git rebase" only moves commits around to make history more linear.

git rebase actually doesn't move around commits; the old commits still exist with no modifications (which is useful for undoing rebases if you later decide you don't like them). Instead, git rebase just re-applies commits, effectively making an extra "copy" of each commit (I put "copy" in quotes because the "copied" commits will have different parents, different commit times, and potentially different contents especially when conflict resolution is necessary, so they're not quite copies).

Show 13 quoted lines
> 4) It would be good to have a section "Cutting branches"
>
>   How do I remove the N most recent commits from a branch?
>
>               D---E---F---G---H---.........---Y---Z master
>
>  =>
>               D---E master
>
>   It goes like this:
>
>     $ git checkout master
>     $ git reset --hard E
If master is the active branch, you only need the latter command.  If
master is not the active branch, then you can achieve the same with
much less cost by
  $ git branch -f master E
Show 21 quoted lines
> 5) Then, it would be good to have a section "Replacing branches"
>
>   How do I copy the contents of a branch over to another branch, replacing
>   the recent development on that branch?
>
>  If you want to copy a branch into another, while throwing away commits:
>
>                     F1---F2---F3                         released
>                    /
>               D---E---F---G---H---.........---Y---Z master
>
>  =>
>                     F1---F2---F3   released
>                    /
>               D---E---F1---F2---F3 master
>
>  This is achieved by
>
>    $ git checkout master
>    $ git reset --hard E      # Cut the branch "master"
>    $ git merge released      # Copy commits from branch "released" to "master"
I'm assuming master isn't checked out.  If so, the following is faster:
  $ git branch -f master E
(If master is checked out, just 'git reset ---hard released')
Show 17 quoted lines
> 6) Also, it would be good to have a section "Reconnecting branches after rebase".
>   If you want to reconnect a branch to a rebased master, here's how to do it:
>
>                   /--C'--...---P'--Q'--...---Z'  new rebased master
>              A---B---C---...---P---Q---...---Z   old master
>                                 \
>                                  --BA---...---BZ  release-branch
>
>  =>
>              A---B---C'--...---P'--Q'--...---Z'  new rebased master
>                                 \
>                                  --BA---...---BZ  release-branch
>
>  This is achieved by
>
>    $ git checkout release-branch
>    $ git rebase --onto P' P
or,
  $ git rebase --onto P' P release-branch
> This might seem exotic, but these use cases all came up while rewriting the
> history after a "git cvsimport".

They may seem exotic at first to cvs/svn refugees, but it isn't very long before lots of users end up doing these kinds of things routinely.

Amusing story: I spent several months at $dayjob attempting to convince our group to switch from cvs to git instead of from cvs to svn. I often mentioned cool features like pulling directly from other developers, but was often ignored and laughed off for mentioning exotic cases that nearly no one would care about. And our surveys showed that most developers didn't care about anything other than "checkout, update, and commit". I succeeded in the end anyway, and we found out that developers only cared about simple stuff because cvs made anything else painful. The very first week after switching to git, we had multiple requests from people about how to pull changes directly from other developers (including from people that previously only cared about "checkout, update, and commit").

When the cost of certain activities changes dramatically (which git does by making lots new things possible and fast), formerly "exotic" usecases can become natural and common -- and really helpful.

Anyway, I hope some of my ramblings are useful to you.
Elijah
Elijah Newren· Jul 7, 2009, 03:45 UTC · re: Elijah Newren · lore

Re: request for documentation about branch surgery

On Mon, Jul 6, 2009 at 8:30 PM, Elijah Newren<newren@gmail.com> wrote:
Show 8 quoted lines
>>  This is achieved by
>>
>>    $ git checkout master
>>    $ git reset --hard E      # Cut the branch "master"
>>    $ git merge released      # Copy commits from branch "released" to "master"
>
> I'm assuming master isn't checked out.  If so, the following is faster:
>  $ git branch -f master E
Sorry, I meant
  $ git branch -f master released
because we want to set master to the tip of the released branch, not to E.
> (If master is checked out, just 'git reset ---hard released')
>
Bruno Haible· Jul 7, 2009, 09:51 UTC · re: Elijah Newren · lore

Re: request for documentation about branch surgery

Hi Elijah,
> When the cost of certain activities changes dramatically (which git
> does by making lots new things possible and fast), formerly "exotic"
> usecases can become natural and common -- and really helpful.
Yes. With cvs, I would not never have dared to do branch surgery. With
git, I can - assuming some documentation. "git clone", "git checkout",
"git commit" each serves a particular purpose, so one can understand
when to use which command. But for branch surgery, several commands
are available:
  - "git reset --hard"
  - "git rebase"
  - "git rebase --onto"
  - "git merge" (simple case, no merge commit)
The mapping from "How do I ..." questions to command is not easy,
therefore a user's manual like
  <http://www.kernel.org/pub/software/scm/git/docs/user-manual.html#rewriting-one-commit>
is needed.

Thanks for correcting me and showing simpler alternatives to what I said.

Still, can someone please extend the cited chapter of the user's manual,
so that it answers these questions?
  - How do I change the last commit in a branch?       [DONE]
  - How do I change an older commit in a branch?       [DONE]
  - How do I insert some commits between other commits in a branch?
                                                       [TODO]
  - How do I reorder commits in a branch?              [TODO - mention "git rebase -i"]
  - How do I copy selected commits from a branch to another?
                                                       [DONE]
  - How do I cut a branch?                             [TODO]
  - How do I replace a branch tip with the contents of another branch?
                                                       [TODO]
  - How do I reconnect a branch to another branch point?
                                                       [TODO]
> I think
> the rebase command would be much easier to understand for new users if
> it used an "--against" before the first reference(*).

Don't know, this is just a cosmetic change. The thing that confused me about "git rebase" is that its thinking is focused on the current branch. Whereas when I'm doing branch surgery, I'm creating a new branch bottom-up, so my thinking is "here I have some commits, what can I do with them". It requires a good user's manual to map this to the right "git rebase" command.

Bruno
Andreas Ericsson· Jul 7, 2009, 10:06 UTC · re: Bruno Haible · lore

Re: request for documentation about branch surgery

Bruno Haible wrote:
> I said.
> 
> Still, can someone please extend the cited chapter of the user's manual,
Presumably you can. I'll jot down some notes for you though ;-)
> so that it answers these questions?
>   - How do I insert some commits between other commits in a branch?
>                                                        [TODO]
git rebase --interactive (don't do this on published branches).
>   - How do I reorder commits in a branch?              [TODO - mention "git rebase -i"]
git rebase --interactive
>   - How do I cut a branch?                             [TODO]

Define "cut". Possibly "git branch -d" or it's less forgiving sibling "git branch -D", in case the branch to be removed isn't fully merged.

>   - How do I replace a branch tip with the contents of another branch?
>                                                        [TODO]

Easily understandable: git checkout branch git reset --hard otherbranch

The low-level way: git update-ref [-m <reason>] [--no-deref] <full-ref-name> <newvalue>

There are more options to git-update-ref. The man-page lists them all.
>   - How do I reconnect a branch to another branch point?
>                                                        [TODO]

I don't quite understand what you mean by "reconnect", but this might do something along the lines of what you want: git checkout branch-to-connect-to git merge branch-to-be-connected

-- 
Andreas Ericsson                   andreas.ericsson@op5.se
OP5 AB                             www.op5.se
Tel: +46 8-230225                  Fax: +46 8-230231

Considering the successes of the wars on alcohol, poverty, drugs and
terror, I think we should give some serious thought to declaring war
on peace.
Junio C Hamano· Jul 7, 2009, 02:50 UTC · re: Bruno Haible · lore

Re: request for documentation about branch surgery

Bruno Haible <bruno@clisp.org> writes:
> 1) After the section "Rewriting a single commit", it may be useful to
> have a section "Inserting one or more new commits". This is something that
> cannot be done with the "detached head" technique.
You learn new things every day, and today is such a day ;-)
Show 9 quoted lines
>   If you want to add a commit in the middle of a branch:
>
>             A---C---...---Z    master
>
>   =>
>
>             A---B---C---...---Z    master
>
>   it is achieved by
        $ git checkout master~25 ;# detach HEAD at A
        $ edit edit edit
        $ git commit ;# creates B
which makes
               B              HEAD (detached)
              /
             A---C---...---Z    master
and then
        $ git rebase HEAD master
which reshapes the history into
               B---C'--...---Z' master
              /
             A---C---...---Z    master@{1}
and you are done.
Show 6 quoted lines
> 3) When do I need "git merge", and when do I need "git rebase", in the
>    context of branch surgery?
>
>    The simple answer, that I would find worth mentioning, is:
>      - "git merge" copies commits from one branch to another.
>      - "git rebase" only moves commits around to make history more linear.

If you think "git merge" _copies_, you will never understand what "merge" does.

If you have master and origin branch, merging them together
                X---Y---Z  origin
               /
              /
             O----A----B master
        $ git checkout master
        $ git merge origin
will give you this:
                X---Y---Z  origin
               /         \
              /           \
             O----A----B---M master
                       ^
                       master@{1}

There is no copying involved anywhere . It only creates a new commit, M, and that commit allows 'master' (that used to point at B) to reach both the history leading to B and the history leading to Z.

On the other hand, rebase _copies_. It _does_ make new commits A' and B', whose _effect_ to the tree mimics those of A and B.

        $ git rebase origin master
                X---Y---Z---A'---B' master
               /        ^origin
              /
             O----A----B master@{1}
Show 8 quoted lines
> 4) It would be good to have a section "Cutting branches"
>
>    How do I remove the N most recent commits from a branch?
>
>                D---E---F---G---H---.........---Y---Z master
>
>   =>
>                D---E master
And it is not even cutting.  It merely makes this:
                      F---G---H---.........---Y---Z master@{1}
                     /
                D---E
                    ^master

so that a new history can grow at E that is parallel to the history that leads to Z.

I think your confusion is primarily coming from not understanding what a branch in git is. A branch in git does not have its own identity per-se, and a commit does _not_ belong to a branch, in the sense that a commit object does not record anywhere on which branch it was created on. A branch is just a pointer into a dag and the pointer can be moved.

Bruno Haible· Jul 7, 2009, 10:13 UTC · re: Junio C Hamano · lore

Re: request for documentation about branch surgery

Hi Junio,
Show 34 quoted lines
> You learn new things every day, and today is such a day ;-)
> 
> >   If you want to add a commit in the middle of a branch:
> >
> >             A---C---...---Z    master
> >
> >   =>
> >
> >             A---B---C---...---Z    master
> >
> >   it is achieved by
> 
>         $ git checkout master~25 ;# detach HEAD at A
>         $ edit edit edit
>         $ git commit ;# creates B
> 
> which makes
> 
>                B              HEAD (detached)
>               /
>              A---C---...---Z    master
> 
> and then
> 
>         $ git rebase HEAD master
> 
> which reshapes the history into
> 
> 
>                B---C'--...---Z' master
>               /
>              A---C---...---Z    master@{1}
> 
> and you are done.

Cool! I wouldn't have guessed that. Now you wrote it into the mailing list archives. It would be even better if it were mentioned in the user's manual, chapter "Rewriting history and maintaining patch series"

Show 10 quoted lines
> > 3) When do I need "git merge", and when do I need "git rebase", in the
> >    context of branch surgery?
> >
> >    The simple answer, that I would find worth mentioning, is:
> >      - "git merge" copies commits from one branch to another.
> >      - "git rebase" only moves commits around to make history more linear.
> 
> If you think "git merge" _copies_, you will never understand what "merge"
> does. ... There is no copying involved anywhere .  It only creates a new
> commit 

There are two cases of "git merge" operation: the one that creates a diamond commit, and the one that doesn't (the "simple" case of "git merge"). The latter operation I found useful in achieving this surgery:

            C---D---E              topic
           /
      A---B                        master
  =>
            C---D---E              topic
           /
      A---B---C---D---E            master

How do I do this, if not by using "git merge"? Is there a way to do it with "git rebase" only?

If I have a certain task at hand, what rule of thumb would you give me, when can I do it with "git rebase", and when can I do it with the simple case of "git merge"?

Show 15 quoted lines
> > 4) It would be good to have a section "Cutting branches"
> >
> >    How do I remove the N most recent commits from a branch?
> >
> >                D---E---F---G---H---.........---Y---Z master
> >
> >   =>
> >                D---E master
> 
> And it is not even cutting.  It merely makes this:
> 
>                       F---G---H---.........---Y---Z master@{1}
>                      /
>                 D---E
>                     ^master

I regularly use "git repack -a -d", so that branches like that master@{1} get garbage collected. So from the point of view of someone who considers only the contents of the commits that sit on branches, it *does* cut the branch.

I know that all commits are still present and reachable by their ID, as long as they have not been garbage collected. But when doing branch surgery, only the contents of the labelled branches matters to me.

Show 5 quoted lines
> I think your confusion is primarily coming from not understanding what a
> branch in git is.  A branch in git does not have its own identity per-se,
> and a commit does _not_ belong to a branch, in the sense that a commit
> object does not record anywhere on which branch it was created on.  A
> branch is just a pointer into a dag and the pointer can be moved.
Oh, I do and did know all this. There is no confusion about that. My problem
was that
  - I had a couple of "how do I ..." questions regarding branch surgery,
  - the mapping between such a task and the git command to use is not clear
    (combinations of "git checkout", "git branch", "git rebase", "git merge",
    "git reset", "git cherry-pick" - enough complicated commands to get
    confused),
  - the user's manual answered only half of my questions.
Bruno
Andreas Ericsson· Jul 7, 2009, 11:03 UTC · re: Bruno Haible · lore

Re: request for documentation about branch surgery

Bruno Haible wrote:
Show 40 quoted lines
> Hi Junio,
> 
>> You learn new things every day, and today is such a day ;-)
>>
>>>   If you want to add a commit in the middle of a branch:
>>>
>>>             A---C---...---Z    master
>>>
>>>   =>
>>>
>>>             A---B---C---...---Z    master
>>>
>>>   it is achieved by
>>         $ git checkout master~25 ;# detach HEAD at A
>>         $ edit edit edit
>>         $ git commit ;# creates B
>>
>> which makes
>>
>>                B              HEAD (detached)
>>               /
>>              A---C---...---Z    master
>>
>> and then
>>
>>         $ git rebase HEAD master
>>
>> which reshapes the history into
>>
>>
>>                B---C'--...---Z' master
>>               /
>>              A---C---...---Z    master@{1}
>>
>> and you are done.
> 
> Cool! I wouldn't have guessed that. Now you wrote it into the mailing list
> archives. It would be even better if it were mentioned in the user's manual,
> chapter "Rewriting history and maintaining patch series"
> 
Anyone can submit patches. I find your persistent urging that someone else
do this for you slightly annoying. Now that you've been helped along the
way to understanding, it's your turn to do your bit and write up the info
you've received as a proper patch. This will help ensure that:
a) Other people can find the relevant information quickly
b) We won't have to answer the same questions again
c) You gain an even deeper understanding about how the various features
   actually work as your patches are submitted for review and improvements
   are suggested for them by the list members
d) We answer your questions again next time you have any

You can ofcourse refrain from submitting patches and just hope that d) happens anyway. It probably will, but not indefinitely.

Thanks.
-- 
Andreas Ericsson                   andreas.ericsson@op5.se
OP5 AB                             www.op5.se
Tel: +46 8-230225                  Fax: +46 8-230231

Considering the successes of the wars on alcohol, poverty, drugs and
terror, I think we should give some serious thought to declaring war
on peace.
Junio C Hamano· Jul 7, 2009, 15:52 UTC · re: Bruno Haible · lore

Re: request for documentation about branch surgery

Bruno Haible <bruno@clisp.org> writes:
Show 17 quoted lines
>> If you think "git merge" _copies_, you will never understand what "merge"
>> does. ... There is no copying involved anywhere .  It only creates a new
>> commit 
>
> There are two cases of "git merge" operation: the one that creates a diamond
> commit, and the one that doesn't (the "simple" case of "git merge"). The latter
> operation I found useful in achieving this surgery:
>
>             C---D---E              topic
>            /
>       A---B                        master
>
>   =>
>
>             C---D---E              topic
>            /
>       A---B---C---D---E            master

If C, D, E on the above two lines are the _same_ commit, i.e. with the same history and same object IDs, then the picture should instead look like this:

             C---D---E topic
            /
       A---B master
   =>
                       master
             C---D---E topic
            /
       A---B master@{1}

If that is the case, you drew the picture incorrectly, and it shows the misunderstanding of your git object model and what a git branch is.

The latter I have already explained to you, but here is a hint.
    Do not think of a branch as "the upper line is topic, the lower line
    is master".  A branch is just a pointer to _a commit_.  IOW, in the
    picture I drew to correct yours, master and topic point at "E".  It
    does _not_ point at the line that C, D, and E are on.  Similarly,
    master@{1} points at the commit "B", not at the line A and B are on.

You claimed that you understand in your response, but judging from the way you wrote the above picture, I can tell that you don't understand what a branch in git is. Otherwise you would have drew it like how I did, and you wouldn't have used the word "copy".

If you instead for some reason _want_ a forked history where C, D and E are _duplicated_, then you would start from the first picture, fast forward master to "E", and would force rebase onto B, to end up with a picture like this.

             C---D---E topic
            /
       A---B---C'--D'--E' master
But there is no reason to do this.
Daniel Barkalow· Jul 7, 2009, 18:28 UTC · re: Bruno Haible · lore

Re: request for documentation about branch surgery

On Tue, 7 Jul 2009, Bruno Haible wrote:
Show 12 quoted lines
> 6) Also, it would be good to have a section "Reconnecting branches after rebase".
>    If you want to reconnect a branch to a rebased master, here's how to do it:
> 
>                    /--C'--...---P'--Q'--...---Z'  new rebased master
>               A---B---C---...---P---Q---...---Z   old master
>                                  \
>                                   --BA---...---BZ  release-branch
> 
>   =>
>               A---B---C'--...---P'--Q'--...---Z'  new rebased master
>                                  \
>                                   --BA---...---BZ  release-branch
This is impossible; the parent of BA is P, not P'.
                   /--C'--...---P'--Q'--...---Z'  new rebased master
              A---B---C---...---P---Q---...---Z   old master
                                 \
                                  --BA---...---BZ  release-branch
  =>
                                  --BA'--...---BZ' release branch
                                 /
                   /--C'--...---P'--Q'--...---Z'  new rebased master
              A---B---C---...---P---Q---...---Z
                                 \
                                  --BA---...---BZ

In order to draw well-formed tree sequences, you can only add items to the trees, and only use each letter once in the whole thing. When you've got a complete history following those rules, you can elide parts of the history that aren't referenced any more, but you still can't reuse their letters.

You're drawing some of your trees as if it were possible to have commits that are not eq but are equal; in fact, all information about a commit, including its parentage, is immutable and contributes to the way it is referenced, so any equal commits are eq (and therefore only appear in one spot on the graph).

	-Daniel
*This .sig left intentionally blank*

← back to recent threads