{"thread":{"id":"33067","subject":"\"git rebase\" loses the additional changes in \"evil\" merges","startedAt":"2013-03-04T20:58:58Z","lastAt":"2013-03-07T22:06:28Z","messageCount":13,"participants":["Dale R. Worley","Junio C Hamano","Thomas Rast","Dale Worley","Philip Oakley","Johannes Sixt"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"210586","messageId":"201303042058.r24KwwEx012408@freeze.ariadne.com","threadId":"33067","inReplyTo":null,"subject":"\"git rebase\" loses the additional changes in \"evil\" merges","fromName":"Dale R. Worley","fromEmail":"worley@alum.mit.edu","sentAt":"2013-03-04T20:58:58Z","receivedAt":"2013-03-04T20:58:58Z","isPatch":false,"sender":{"key":"worley@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/19911107?v=4"},"body":"(git version 1.7.7.6)\n\nI've been learning how to use Git.  While exploring \"git rebase\", I've\ndiscovered that if the branch being rebased contains an \"evil\" merge,\nthat is, a merge which contains changes that are in addition to the\nchanges in any of the parent commits, the rebase operation will\nsilenty lose those additional changes.\n\nI believe that this is a serious bug.\n\nThe problem is not that Git does not propagate the additional changes\n(although I'd like it to do so), the problem is that the rebase\n*silently loses* changes in the code.  It would be acceptable if Git\ndetected the evil merge, notified me of it, and forced me to manually\nre-do it in order to finish the rebase.\n\nThroughout the Git design (as in other SCMs), care has been taken to\nnot lose changes that the user has introduced.  In this situation,\nthat principle is violated.\n\nI have a script that demonstrates this failure, if that would be\nuseful for testing purposes.\n\nDale\n"},{"id":"210588","messageId":"7vtxoqx3s1.fsf@alter.siamese.dyndns.org","threadId":"33067","inReplyTo":"201303042058.r24KwwEx012408@freeze.ariadne.com","subject":"Re: \"git rebase\" loses the additional changes in \"evil\" merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-03-04T21:18:22Z","receivedAt":"2013-03-04T21:18:22Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"worley@alum.mit.edu (Dale R. Worley) writes:\n\n> (git version 1.7.7.6)\n>\n> I've been learning how to use Git.  While exploring \"git rebase\", I've\n> discovered that if the branch being rebased contains an \"evil\" merge,\n> that is, a merge which contains changes that are in addition to the\n> changes in any of the parent commits, the rebase operation will\n> silenty lose those additional changes.\n\nI think this is to be expected for \"git rebase\", as it does not even\nlook at merges.  It is a way to find non-merge commits that haven't\nbeen applied yet, and apply them to the upstream to create a new\nlinear history.\n"},{"id":"210590","messageId":"87sj4aoo3s.fsf@pctrast.inf.ethz.ch","threadId":"33067","inReplyTo":"7vtxoqx3s1.fsf@alter.siamese.dyndns.org","subject":"Re: \"git rebase\" loses the additional changes in \"evil\" merges","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2013-03-04T21:24:07Z","receivedAt":"2013-03-04T21:24:07Z","isPatch":false,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> worley@alum.mit.edu (Dale R. Worley) writes:\n>\n>> (git version 1.7.7.6)\n>>\n>> I've been learning how to use Git.  While exploring \"git rebase\", I've\n>> discovered that if the branch being rebased contains an \"evil\" merge,\n>> that is, a merge which contains changes that are in addition to the\n>> changes in any of the parent commits, the rebase operation will\n>> silenty lose those additional changes.\n>\n> I think this is to be expected for \"git rebase\", as it does not even\n> look at merges.  It is a way to find non-merge commits that haven't\n> been applied yet, and apply them to the upstream to create a new\n> linear history.\n\nI can see the problem with --preserve-merges though.  It will actually\njust *redo* the merge; the name is wrong in the sense that it undertakes\nnothing to preserve whatever evilness was in it.\n\nI suppose in theory it could first redo the merge on the original\nparents, and diff against the existing merge; that would be the evilness\ncomponent.  Then it could attempt to apply the same evilness on the new\nmerge.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"210591","messageId":"7vppzex2pq.fsf@alter.siamese.dyndns.org","threadId":"33067","inReplyTo":"87sj4aoo3s.fsf@pctrast.inf.ethz.ch","subject":"Re: \"git rebase\" loses the additional changes in \"evil\" merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-03-04T21:41:21Z","receivedAt":"2013-03-04T21:41:21Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@student.ethz.ch> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> worley@alum.mit.edu (Dale R. Worley) writes:\n>>\n>>> (git version 1.7.7.6)\n>>>\n>>> I've been learning how to use Git.  While exploring \"git rebase\", I've\n>>> discovered that if the branch being rebased contains an \"evil\" merge,\n>>> that is, a merge which contains changes that are in addition to the\n>>> changes in any of the parent commits, the rebase operation will\n>>> silenty lose those additional changes.\n>>\n>> I think this is to be expected for \"git rebase\", as it does not even\n>> look at merges.  It is a way to find non-merge commits that haven't\n>> been applied yet, and apply them to the upstream to create a new\n>> linear history.\n>\n> I can see the problem with --preserve-merges though.  It will actually\n> just *redo* the merge; the name is wrong in the sense that it undertakes\n> nothing to preserve whatever evilness was in it.\n\nWith or without evil, preserve-merges have other issues to make me\nnot to trust it. Which side branches to take literally (i.e. just\nmerge again) vs which side branches to also rebase (i.e. rebuild the\nhistory and then merge).\n\nIt was suggested to make it apply the first-parent diff and record\nthe result, I think.  If that were an acceptable approach (I didn't\nthink about it through myself, though), that would automatically\ncover the evil-merge case as well.\n"},{"id":"210658","messageId":"201303052118.r25LIoAC000463@freeze.ariadne.com","threadId":"33067","inReplyTo":"7vtxoqx3s1.fsf@alter.siamese.dyndns.org","subject":"Re: \"git rebase\" loses the additional changes in \"evil\" merges","fromName":"Dale Worley","fromEmail":"worley@c-66-31-108-177.hsd1.ma.comcast.net","sentAt":"2013-03-05T21:18:50Z","receivedAt":"2013-03-05T21:18:50Z","isPatch":false,"sender":{"key":"worley@c-66-31-108-177.hsd1.ma.comcast.net","avatar":null},"body":"> From: Junio C Hamano <gitster@pobox.com>\n> \n> I think this is to be expected for \"git rebase\", as it does not even\n> look at merges.  It is a way to find non-merge commits that haven't\n> been applied yet, and apply them to the upstream to create a new\n> linear history.\n\nI disagree. \"git rebase\" is not characterized as a way to \"find\nnon-merge commits that haven't been applied yet\", but rather (as\ndescribed in the git-rebase manual page):\n \n    git-rebase - Forward-port local commits to the updated upstream head\n\n    All changes made by commits in the current branch but that are not\n    in <upstream> are saved to a temporary area. [...]  The commits\n    that were previously saved into the temporary area are then\n    reapplied to the current branch, one by one, in order.\n\nNow if you read far enough down the page, I'm sure it warns about\nmerge commits.  But the stated basic *intention* is to replicate the\nexisting branch, re-rooted at a new place on the upstream branch.\n\nThe current implementation fails this intention by losing changes made\nin merges.  It fails this intention in a *dangerous* way by causing\nchanges to be lost without warning.\n\nDale\n"},{"id":"210660","messageId":"7vy5e1sf6b.fsf@alter.siamese.dyndns.org","threadId":"33067","inReplyTo":"201303052118.r25LIoAC000463@freeze.ariadne.com","subject":"Re: \"git rebase\" loses the additional changes in \"evil\" merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-03-05T21:35:40Z","receivedAt":"2013-03-05T21:35:40Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Dale Worley <worley@c-66-31-108-177.hsd1.ma.comcast.net> writes:\n\n>> From: Junio C Hamano <gitster@pobox.com>\n>> \n>> I think this is to be expected for \"git rebase\", as it does not even\n>> look at merges.  It is a way to find non-merge commits that haven't\n>> been applied yet, and apply them to the upstream to create a new\n>> linear history.\n>\n> I disagree. \"git rebase\" is not characterized as ...\n\nThe intention has always been \"I have these patches, some were\napplied upstream already, now what do I have left?\".\n\nYou do realize that you are disagreeing with somebody who designed\nthe original \"git rebase\" (before the --preserve-merges was added),\ndo you?\n"},{"id":"210670","messageId":"D9E55432793A444F941789A493F97B1C@PhilipOakley","threadId":"33067","inReplyTo":"7vy5e1sf6b.fsf@alter.siamese.dyndns.org","subject":"Re: \"git rebase\" loses the additional changes in \"evil\" merges","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":null,"receivedAt":"2013-03-05T22:47:18Z","isPatch":false,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Junio C Hamano\" <gitster@pobox.com>\nSent: Tuesday, March 05, 2013 9:35 PM\n> Dale Worley <worley@c-66-31-108-177.hsd1.ma.comcast.net> writes:\n>\n>>> From: Junio C Hamano <gitster@pobox.com>\n>>>\n>>> I think this is to be expected for \"git rebase\", as it does not even\n>>> look at merges.  It is a way to find non-merge commits that haven't\n>>> been applied yet, and apply them to the upstream to create a new\n>>> linear history.\n>>\n>> I disagree. \"git rebase\" is not characterized as ...\n>\n> The intention has always been \"I have these patches, some were\n> applied upstream already, now what do I have left?\".\n\nGiven that many folk appear to trip up with their rebase mindset, does\nthis suggest that a few tweaks to the manual may be in order to stop\nsuch misunderstandings?\n\nPerhaps:\n    git-rebase - Forward-port local commits, not in upstream,\n     to the updated upstream head\n\nand to avoid \"hidden\" asides, add a few more paragraph breaks into the\ndescription texts, and perhaps bring the \"Note that any commits in HEAD\nwhich introduce the same textual changes as a commit in HEAD..<upstream>\nare omitted\" sentence nearer the start.\n\nThat is, don't let folks get a too simplistic view of rebase, missing \nits\nhidden powers.\n\n>\n> You do realize that you are disagreeing with somebody who designed\n> the original \"git rebase\" (before the --preserve-merges was added),\n> do you?\n\nHowever the broader userbase brings with it a better class of fool ;-)\n"},{"id":"210671","messageId":"7vmwuhs9t1.fsf@alter.siamese.dyndns.org","threadId":"33067","inReplyTo":"D9E55432793A444F941789A493F97B1C@PhilipOakley","subject":"Re: \"git rebase\" loses the additional changes in \"evil\" merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-03-05T23:31:38Z","receivedAt":"2013-03-05T23:31:38Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Philip Oakley\" <philipoakley@iee.org> writes:\n\n> Given that many folk appear to trip up with their rebase mindset, does\n> this suggest that a few tweaks to the manual may be in order to stop\n> such misunderstandings?\n\nPerhaps.\n"},{"id":"210708","messageId":"201303061855.r26ItKtM018797@freeze.ariadne.com","threadId":"33067","inReplyTo":"7vppzex2pq.fsf@alter.siamese.dyndns.org","subject":"What I want rebase to do","fromName":"Dale R. Worley","fromEmail":"worley@alum.mit.edu","sentAt":"2013-03-06T18:55:20Z","receivedAt":"2013-03-06T18:55:20Z","isPatch":false,"sender":{"key":"worley@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/19911107?v=4"},"body":"This is how I see what rebase should do:\n\nThe simple case for rebase starts from\n\n               P---Q---R---S master\n                    \\\n                     A---B---C topic\n\nThen \"git checkout topic ; git rebase master\" will change it to\n\n               P---Q---R---S master\n                            \\\n                             A'--B'--C' topic\n\nA' is created by a three-way merge that can be symbolized\n\n        Q-->S\n        |   |\n        v   v\n        A-->A'\n\nThat is, Q, applying the changes from Q to A and the changes from Q to\nS, becomes A'.\n\nAfter that\n\n        A-->A'\n        |   |\n        v   v\n        B-->B'\n        |   |\n        v   v\n        C-->C'\n\nA more complex case is when there is a merge from an external source\n\n               P---Q---R---S master\n                    \\\n                     A---M---C topic\n                        /\n                    ---X\n\nWe want to produce\n\n               P---Q---R---S master\n                            \\\n                             A'--M'--C' topic\n                                /\n                            ---X\n\nSo we have to merge\n\n        Q-->S\n        |   |\n        v   v\n        A-->A'\n        |   |\n        v   v\n        M-->M'\n        |   |\n        v   v\n        C-->C'\n\nAny \"evil\" changes in M will be in the changes A->M (along with the\nchanges introduced from X), and so they will be reincorporated in\nA'->M'.  M' lists A' and X as its parents.  (And not M!)\n\nIf there is an internal merge in the topic branch, things look like\nthis\n\n               P---Q---R---S master\n                    \\\n                     \\   B\n                      \\ / \\\n                       A   M---D topic\n                        \\ /\n                         C\n\nand we want to produce this\n\n               P---Q---R---S master\n                            \\\n                             \\    B'\n                              \\  /  \\\n                               A'    M'--D' topic\n                                 \\  /\n                                  C'\n\nWhich can be done with these merges\n\n\n        Q-->S\n        |   |\n        v   v\n        A-->A'        A-->A'\n        |   |         |   | \n        v   v         v   v \n        B-->B'        C-->C'\n\nThere are two choices for constructing M' (which ought to produce the\nsame results under ordinary circumstances)\n\n        B-->B'        C-->C'\n        |   |         |   | \n        v   v         v   v \n        M-->M'        M-->M'\n\nand finally\n\n        M-->M'\n        |   |\n        v   v\n        D-->D'\n\nDale\n"},{"id":"210752","messageId":"87r4jra942.fsf@pctrast.inf.ethz.ch","threadId":"33067","inReplyTo":"201303061855.r26ItKtM018797@freeze.ariadne.com","subject":"Re: What I want rebase to do","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2013-03-07T08:48:29Z","receivedAt":"2013-03-07T08:48:29Z","isPatch":false,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"worley@alum.mit.edu (Dale R. Worley) writes:\n[...snip...]\n\nIsn't that just a very long-winded way of restating what Junio said\nearlier:\n\n> > It was suggested to make it apply the first-parent diff and record\n> > the result, I think.  If that were an acceptable approach (I didn't\n> > think about it through myself, though), that would automatically\n> > cover the evil-merge case as well.\n\nYou can fake that with something like\n\ngit rev-list --first-parent --reverse RANGE_TO_REBASE |\nwhile read rev; do\n    if git rev-parse $rev^2 >/dev/null 2>&1; then\n        git cherry-pick -n -m1 $rev\n        git rev-parse $rev^2 >.git/MERGE_HEAD\n        git commit -C$rev\n    else\n        git cherry-pick $rev\n    fi\ndone\n\nOnly tested very lightly.  Dealing with octopii, conflicts and actually\npreserving the commit's attributes is left as an exercise to the\nreader[1].\n\nI still think that the _right_ solution is first redoing the merge on\nits original parents and then seeing how the actual merge differs from\nthat.  --preserve-merges has bigger issues though, like Junio said.\n\nPerhaps a new option to git-rebase could trigger the above behavior for\nmerges, who knows.  (It could be called --first-parent.)\n\n\n[1]  If you don't get the sarcasm: that would amount to reinventing\nlarge parts of git-rebase.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"210756","messageId":"51386732.1020708@viscovery.net","threadId":"33067","inReplyTo":"87r4jra942.fsf@pctrast.inf.ethz.ch","subject":"Re: What I want rebase to do","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2013-03-07T10:08:50Z","receivedAt":"2013-03-07T10:08:50Z","isPatch":false,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 3/7/2013 9:48, schrieb Thomas Rast:\n> worley@alum.mit.edu (Dale R. Worley) writes:\n> [...snip...]\n> \n> Isn't that just a very long-winded way of restating what Junio said\n> earlier:\n> \n>>> It was suggested to make it apply the first-parent diff and record\n>>> the result, I think.  If that were an acceptable approach (I didn't\n>>> think about it through myself, though), that would automatically\n>>> cover the evil-merge case as well.\n> \n> You can fake that with something like\n> \n> git rev-list --first-parent --reverse RANGE_TO_REBASE |\n> while read rev; do\n>     if git rev-parse $rev^2 >/dev/null 2>&1; then\n>         git cherry-pick -n -m1 $rev\n>         git rev-parse $rev^2 >.git/MERGE_HEAD\n>         git commit -C$rev\n>     else\n>         git cherry-pick $rev\n>     fi\n> done\n> \n> Only tested very lightly.  Dealing with octopii, conflicts and actually\n> preserving the commit's attributes is left as an exercise to the\n> reader[1].\n\nI proposed this long ago, but by modifying preserve-merges rather than\nwith a new option (--first-parent):\nhttp://thread.gmane.org/gmane.comp.version-control.git/198125\n\nIt works very well. I'm using it frequently in the field.\n\n-- Hannes\n"},{"id":"210773","messageId":"7vwqtjm7oj.fsf@alter.siamese.dyndns.org","threadId":"33067","inReplyTo":"87r4jra942.fsf@pctrast.inf.ethz.ch","subject":"Re: What I want rebase to do","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-03-07T17:38:36Z","receivedAt":"2013-03-07T17:38:36Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@student.ethz.ch> writes:\n\n> I still think that the _right_ solution is first redoing the merge on\n> its original parents and then seeing how the actual merge differs from\n> that.\n\nI think that is what was suggested in\n\n    http://article.gmane.org/gmane.comp.version-control.git/198316\n\n> Perhaps a new option to git-rebase could trigger the above behavior for\n> merges, who knows.  (It could be called --first-parent.)\n\nYeah, I think that is what the old thread concluded to be the way to\nmove forward:\n\n    http://thread.gmane.org/gmane.comp.version-control.git/198125\n\nI'll throw it in to the \"leftover bits\".\n\n    http://git-blame.blogspot.com/2013/02/more-leftover-bits.html\n"},{"id":"210792","messageId":"201303072206.r27M6SAN008424@freeze.ariadne.com","threadId":"33067","inReplyTo":"87r4jra942.fsf@pctrast.inf.ethz.ch","subject":"Re: What I want rebase to do","fromName":"Dale R. Worley","fromEmail":"worley@alum.mit.edu","sentAt":"2013-03-07T22:06:28Z","receivedAt":"2013-03-07T22:06:28Z","isPatch":false,"sender":{"key":"worley@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/19911107?v=4"},"body":"> From: Thomas Rast <trast@student.ethz.ch>\n> \n> worley@alum.mit.edu (Dale R. Worley) writes:\n> [...snip...]\n> \n> Isn't that just a very long-winded way of restating what Junio said\n> earlier:\n> \n> > > It was suggested to make it apply the first-parent diff and record\n> > > the result, I think.  If that were an acceptable approach (I didn't\n> > > think about it through myself, though), that would automatically\n> > > cover the evil-merge case as well.\n\nWell, I believe what I said was a fleshed-out way of saying what I\n*think* Junio said, but...\n\n> You can fake that with something like\n> \n> git rev-list --first-parent --reverse RANGE_TO_REBASE |\n> while read rev; do\n>     if git rev-parse $rev^2 >/dev/null 2>&1; then\n>         git cherry-pick -n -m1 $rev\n>         git rev-parse $rev^2 >.git/MERGE_HEAD\n>         git commit -C$rev\n>     else\n>         git cherry-pick $rev\n>     fi\n> done\n\nThis code doesn't do that.  I don't want something that rebases a\nsingle thread of the current branch, I want something that rebases\n*all* the commits between the head commit and the merge base.  Which\nis what is illustrated in my message.\n\n> [1]  If you don't get the sarcasm: that would amount to reinventing\n> large parts of git-rebase.\n\nYes, that is the point of the exercise.\n\nI've done a proof-of-concept implementation of what I want to see,\ncalling it git-rebase--merge-safe.  But I'm new here and likely that\nis a pretty crude solution.  I suspect that a real implementation\ncould be done by inserting this logic into the framework of\ngit-filter-tree.  Following is git-rebase--merge-safe, and the script\nI use to test it (and explore rebase problems).\n\nDale\n----------------------------------------------------------------------\ngit-rebase--merge-safe\n\n#!/bin/bash\n\n. git-sh-setup\n\nprec=4\n\nset -ex\n\n# Ensure the work tree is clean.\nrequire_clean_work_tree \"rebase\" \"Please commit or stash them.\"\n\nonto_name=$1        \nonto=$(git rev-parse --verify \"${onto_name}^0\") ||\n    die \"Does not point to a valid commit: $1\"\n\nhead_name=$( git symbolic-ref HEAD )\norig_head=$(git rev-parse --verify $head_name) ||\n    exit 1\n\necho onto=$onto\necho head_name=$head_name\necho orig_head=$orig_head\n\n# Get the merge base, which is the root of the branch that we are rebasing.\n# (For now, ignore the question of whether there is more than one merge base.)\nmb=$(git merge-base \"$onto\" \"$orig_head\")\necho mb=$mb\n\n# Get the list of commits to rebase, which is everything between $mb and\n# $orig_head.\n# Note that $mb is not included.\nrevisions=`git rev-list --reverse --ancestry-path $mb..$orig_head`\necho revisions=$revisions\n\n# Set up the list mapping the commits on the original branch to the commits\n# on the branch we are creating.\n# Its format is \",old-hash1/new-hash1,old-hash2/new-hash2,...,\".\n# The initial value maps $mb to $onto.\nmap=\",$mb/$onto,\"\n\n# Export these so git commit can see them.\nexport GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE\n\n# Process each commit in forward topological order.\nfor cmt in $revisions\ndo\n    # Examine the commit to extract information we will need to reconstruct it.\n    # First parent of the commit that has a mapping, i.e., is part of the\n    # branch (and has thus been rebuilt already.\n    first_mapped_parent=\n    # The new commit that was made of $first_mapped_parent.\n    first_mapped_parent_mapped=\n    # List of -p options naming the parent commits, or their new commits if they\n    # are in the branch.\n    parents=\n   # Dissect the old commit's data.\n    # Output the commit data into FD 3.\n    exec 3< <( git cat-file commit $cmt )\n\n    while read keyword rest <&3\n    do\n\tcase $keyword in\n\t    tree)\n\t\t# Ignored\n\t\t;;\n\t    parent)\n\t\t# See if the parent is mapped, i.e., is in the\n\t\t# original branch.\n\t\tif [[ \"$map\" == *,$rest/* ]]\n\t\tthen\n\t\t    # This parent has been mapped.  Get the new commit.\n\t\t    parent_mapped=${map#*,$rest/}\n\t\t    parent_mapped=${parent_mapped%%,*}\n\t\t    if test -z \"$first_mapped_parent\"\n\t\t    then\n\t\t\tfirst_mapped_parent=$rest\n\t\t\tfirst_mapped_parent_mapped=$parent_mapped\n\t\t    fi\n\t\telse\n\t\t    # This parent has not been mapped.\n\t\t    parent_mapped=$rest\n\t\tfi\n\t\t# $parent_mapped is a parent of the new commit.\n\t\tparents=\"$parents -p $parent_mapped\"\n\t\t;;\n\t    author)\n\t\t# Extract the information about the author.\n\t\tGIT_AUTHOR_NAME=\"${rest%% <*}\"\n\t\tGIT_AUTHOR_EMAIL=\"${rest##* <}\"\n\t\tGIT_AUTHOR_EMAIL=\"${GIT_AUTHOR_EMAIL%%> *}\"\n\t\tGIT_AUTHOR_DATE=\"${rest##*> }\"\n\t\t;;\n\t    committer)\n\t\t# Ignored:  The new commit will have this use's name\n\t\t# as committer.\n\t\t;;\n\t    '')\n\t\t# End of fixed fields, remainder is the commit comment.\n\t\t# Leave contents of FD 3 queued to be read later by\n\t\t# git commit-tree.\n\t\tbreak\n\t\t;;\n\t    *)\n\t\t# Ignore all other keywords.\n\t\t;;\n\tesac\n    done\n\n    echo GIT_AUTHOR_NAME=\"$GIT_AUTHOR_NAME\"\n    echo GIT_AUTHOR_EMAIL=\"$GIT_AUTHOR_EMAIL\"\n    echo GIT_AUTHOR_DATE=\"$GIT_AUTHOR_DATE\"\n    echo parents=\"$parents\"\n    echo first_mapped_parent=$first_mapped_parent\n    echo first_mapped_parent_mapped=$first_mapped_parent_mapped\n\n    test -n \"$first_mapped_parent\" || exit 1\n\n    # Do the three-way merge.\n    # Empty the tree so git read-tree will merge into it.\n    git read-tree --empty\n    git read-tree -m --aggressive \\\n\t$first_mapped_parent $cmt $first_mapped_parent_mapped\n    git merge-index git-merge-one-file -a\n\n    # Construct the file tree for the new commit.\n    tree=$( git write-tree )\n\n    # Create the new commit\n    # Note that FD 3 contains the remainder of the commit description\n    # from the git cat-file above.\n    new_commit=$( git commit-tree $tree $parents <&3 )\n    echo new_commit=$new_commit\n\n    # Add the new commit to the map.\n    map=\"$map$cmt/$new_commit,\"\n\ndone\n\necho Final commit is $new_commit\n\n# Update the branch pointers.\ngit update-ref ORIG_HEAD $orig_head\ngit update-ref $head_name $new_commit\n\n# Go to the new head of the branch.\ngit checkout ${head_name#refs/heads/}\n----------------------------------------------------------------------\nscript.fixed\n\nset -e\n\n# Create a temporary directory and go into it.\nDIR=temp.$$\nmkdir $DIR\ncd $DIR\n\n# Create a Git repository.\ngit init\n\n# Create a file containing the lines 1 to 10.\nseq 1 10 >file\ngit add file\ngit commit -m 'Commit A'\n\n# Start the dev branch at commit A.\ngit branch dev HEAD\n\n# Add lines 1.5, 2.5, and 3.5 in a series of commits on master.\n\n# This sed command adds a line 1.5 before the line 2.\nsed --in-place -e '/^2$/i1.5' file\ngit commit -a -m 'Commit B'\n\nsed --in-place -e '/^3$/i2.5' file\ngit commit -a -m 'Commit C'\n\nsed --in-place -e '/^4$/i3.5' file\ngit commit -a -m 'Commit D'\n\n# Show the commit structure of master.\n#git log --graph --oneline master\n#git log --graph -p master\necho 'On master:'\ncat file\n\n# Go to the dev branch and create commits with a non-trivial merge.\ngit checkout dev\n\nsed --in-place -e '/^5$/i4.5' file\ngit commit -a -m 'Commit P'\n\ngit branch dev1 HEAD\n\nsed --in-place -e '/^6$/i5.5' file\ngit commit -a -m 'Commit Q'\n\ngit checkout dev1\n\nsed --in-place -e '/^7$/i6.5' file\ngit commit -a -m 'Commit R'\n\ngit checkout dev\n\n# Merge commits Q and R, but add the additional line 7.5 (to simulate\n# fixes that were needed to resolve the merge).\n\ngit merge --no-commit dev1\nsed --in-place -e '/^8$/i7.5' file\ngit commit -a -m 'Commit S'\n\nsed --in-place -e '/^9$/i8.5' file\ngit commit -a -m 'Commit T'\n\n# Show the commit structure of dev.\n#git log --graph --oneline dev\n# *** Note that the diffs do not show the line 7.5 added in commit S.\n#git log --graph -p dev\necho 'On dev:'\ncat file\n\n# Show the branch structure.\ngit show-branch --sha1-name\ngit log --all --oneline --graph\n\n# Rebase the dev branch to the tip of master using our hack script.\ngit checkout dev\ngit branch -f rebase dev\ngit checkout rebase\nPATH=/usr/libexec/git-core:$PATH\n../git-rebase--merge-safe master\n\n# Show the commit structure.\ngit log --graph --oneline\n# *** Note that the line 7.5 added in commit S isn't carried into the new branch.\ngit log --graph -p\n\necho 'After rebasing:'\ncat file\n----------------------------------------------------------------------\n[EOF]\n"}]}