Subject: Re: [BUG] Cannot push some grafted branches

From: Junio C Hamano <[email-masked]>
Date: Tue, 18 Dec 2012 08:09:35 -0800

Yann Dirson <[email-masked]> writes:

> On Mon, 17 Dec 2012 13:14:56 -0800
> Junio C Hamano <[email-masked]> wrote:
>
>> Andreas Schwab <[email-masked]> writes:
>> 
>> > Christian Couder <[email-masked]> writes:
>> >
>> >> Yeah, at one point I wanted to have a command that created to craft a
>> >> new commit based on an existing one.
>> >
>> > This isn't hard to do, you only have to resort to plumbing:
>> >
>> > $ git cat-file commit fef11965da875c105c40f1a9550af1f5e34a6e62 | sed s/bfae342c973b0be3c9e99d3d86ed2e6b152b4a6b/790c83cda92f95f1b4b91e2ddc056a52a99a055d/ | git hash-object -t commit --stdin -w
>> > bb45cc6356eac6c7fa432965090045306dab7026
>> 
>> Good.  I do not think an extra special-purpose command is welcome
>> here.
>
> Well, I'm not sure this is intuitive enough to be useful to the average user :)

I do not understand why you even want to go in the harder route in
the first place, only to complicate things?

All you want to do is to craft a commit object that records a
specific tree shape, has a set of parents you want, and has the log
information you want.  Once you have the commit, you can replace an
unwanted commit with it.

    ----A----B----o---- ....

           X----Y----Z---- ....

Suppose you want to pretend that X is a child of A, even though it
is not in the real life.  So you want to create a commit that 

    - has the same tree as X;
    - has A as its parent; and
    - records log and authorship of X.

and then use "git replace" to replace X, right?  How about doing it
this way?

    $ git checkout X^0 ;# detach
    $ git reset --soft A
    $ git commit -C X

The first gives you the index and the working tree that is the same
as X, the second moves HEAD while keeping the index and the working
tree so that the commit you create will be a child of A, and the
last makes that commit with the metainformation from X [*1*].  If
you want, you can even tweak the contents of the tree before making
the commit in the final step, or tweak the log message during the
final step.

Then you can take the resulting commit and replace X with it, no?

Alternatively, you can do:

    $ git checkout X^0 ;# detach
    $ git reset --soft B
    $ git commit --amend -C X

that is, find an existing commit B that has the desired set of
parents, and amend it with the same tree and the metainformation as
X.  This would even work when you want to come up with a commit that
replaces a merge.  For example, if you want to pretend that B were a
merge between A and X in the above topology, you could

    $ git checkout -b temp A
    $ git merge -s ours X ;# the recorded tree does not matter
    $ git checkout B^0 ;# detach
    $ git reset --soft temp
    $ git commit --amend -c B

which would create one merge that has the desired set of parents
(i.e. A and X) in the first two steps on temp branch, prepares the
index and the working tree to match the tree of B, and with that
tree and the metainformation from B, amends that merge.  The
resulting commit will be a merge between A and X that has the tree
of B and metainformation of B (with a chance to edit it further, as
I used -c there).

Is this not intuitive enough?

[Footnote]

*1* If you are not tweaking the tree contents, you can do this
all in the index without affecting the working tree, e.g.

    $ git checkout HEAD^0 ;# totally random state unrelated to X nor A
    $ git read-tree X ;# just update the index to match tree of X
    $ git reset --soft A ;# next commit will be child of A
    $ git commit -C X ;# and with metainformation from X

After you are done, you can "read-tree $branch" followed by
"checkout $branch" to come back to where you were.