{"thread":{"id":"32621","subject":"Announcing git-reparent","startedAt":"2013-01-14T06:15:06Z","lastAt":"2013-01-14T20:28:23Z","messageCount":6,"participants":["Mark Lodato","Jonathan Nieder","Piotr Krukowiecki","Junio C Hamano","Andreas Schwab"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"206762","messageId":"CAHREChhnf44CprHnS=z9KO5aOkfDPSG6Xb2GU=Kvaz38eTgbUg@mail.gmail.com","threadId":"32621","inReplyTo":null,"subject":"Announcing git-reparent","fromName":"Mark Lodato","fromEmail":"lodatom@gmail.com","sentAt":"2013-01-14T06:15:06Z","receivedAt":"2013-01-14T06:15:06Z","isPatch":false,"sender":{"key":"lodatom@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58860?v=4"},"body":"I threw together a small utility called \"git-reparent\", available on GitHub at:\n\n    https://github.com/MarkLodato/git-reparent\n\nI welcome any comments or suggestions.  To make discussion easier,\nI've copied the README and code below.\n\n--- 8< ---\n\nNAME\n====\n\ngit-reparent - Recommit HEAD with a new set of parents.\n\n\nSYNOPSIS\n========\n\n``git reparent [OPTIONS] ((-p <parent>)... | --no-parent)``\n\n\nDESCRIPTION\n===========\n\nCreate a new commit object that has the same tree and commit message as HEAD\nbut with a different set of parents.  If ``--no-reset`` is given, the full\nobject id of this commit is printed and the program exits; otherwise, ``git\nreset`` is used to update HEAD and the current branch to this new commit.\n\nThis command can be used to manually shape the history of a repository.\nWhereas ``git rebase`` moves around *diffs*, ``git reparent`` moves around\n*snapshots*.  See EXAMPLES for a reason why you might want to use this\ncommand.\n\n\nOPTIONS\n=======\n\n-h, --help                show the help\n-e, --edit                edit the commit message in an editor first\n-m, --message <message>   use the given message instead of that of HEAD\n-p, --parent <commit>     new parent to use; may be given multiple times\n--no-parent               create a parentless commit\n-q, --quiet               be quiet; only report errors\n--no-reset                print the new object id instead of updating HEAD\n\n\nINSTALLATION\n============\n\nMake executable and place somewhere in your $PATH.\n\n\nEXAMPLES\n========\n\nReparenting the tip of a branch\n-------------------------------\n\nSuppose we create some commit *B* and then accidentally pass the ``--amend``\nflag when creating new commit *C*, resulting in the following history::\n\n                B\n               /\n        ...---A---C (HEAD)\n\nWhat we really wanted was one linear history, ``...---A--B--C``.  If we\nwere to use ``git rebase`` or ``git cherry-pick`` to reconstruct the history,\nthis would try to apply the *diff* of *A..C* onto *B*, which might fail.\nInstead, what we really want to do is use the exact message and tree from *C*\nbut with parent *B* instead of *A*.  To do this, we run ::\n\n        $ git reparent -p B\n\nwhere the name *B* can be found by running ``git reflog`` and looking for the\nfirst \"commit (amend)\".  The resulting history is now just what we wanted::\n\n                C\n               /\n        ...---A---B---C' (HEAD)\n\n\nReparenting an inner commit\n---------------------------\n\nWe can also update the parents of a commit other than the most recent.\nSuppose that we want to perform a rebase-like operation, moving *master* onto\n*origin/master*, but we want to completely ignore any changes made in the\nremote branch.  That is, our history currently looks like this::\n\n                B---C (master, HEAD)\n               /\n        ...---A---D---E (origin/master)\n\nand we want to make it look like this::\n\n                B---C   (origin/master)\n               /       /\n        ...---A---D---E---B'---C' (master, HEAD)\n\nWe can accomplish this by using ``git rebase --interactive`` along with ``git\nreparent``::\n\n        $ git rebase -i A\n        # select the \"edit\" command for commit B\n        # git rebase will dump us out at commit B\n        $ git reparent -p origin/master\n        $ git rebase --continue\n\nNow the history will look as desired, and the trees, commit messages, and\nauthors of *B'* and *C'* will be identical to those of *B* and *C*,\nrespectively.\n\n\nSEE ALSO\n========\n\ngit-filter-branch(1) combined with either git grafts or git-replace(1) can be\nused to achieve the same effect\n\ngit-rebase(1) can be used to re-apply the *diffs* of the current branch to\nanother\n\n\nAUTHOR\n======\n\nMark Lodato <lodatom@gmail.com>\n\n--- 8< ---\n\n#!/bin/sh\n# Copyright (c) Mark Lodato, 2013\n\nOPTIONS_SPEC=\"\\\ngit reparent [OPTIONS] ((-p <parent>)... | --no-parent)\n\nRecommit HEAD with a new set of parents.\n--\nh,help          show the help\ne,edit          edit the commit message in an editor first\nm,message=      use the given message instead of that of HEAD\np,parent=!      new parent to use; may be given multiple times\nno-parent!      create a parentless commit\nq,quiet         be quiet; only report errors\nreset*          default behavior\nno-reset!       print the new object id instead of updating HEAD\n\"\nSUBDIRECTORY_OK=Yes\n. \"$(git --exec-path)/git-sh-setup\" || exit $?\nrequire_clean_work_tree reparent \"Please commit or stash them first.\"\n\n# Location of the temporary message.\nmsg_file=\"$GIT_DIR/reparent-msg\"\n\ndie_with_usage() {\n\techo \"error: $1\" >&2\n\tusage\n}\n\nedit=\nmessage=\nno_parent=\nno_reset=\nparent_flags=\nquiet=\nwhile [ $# -gt 0 ]; do\n\tcase \"$1\" in\n\t-p)\n\t\t[ $# -eq 0 ] && die_with_usage \"-p requires an argument\"\n\t\tshift\n\t\tparent_flags=\"$parent_flags$(git rev-parse --sq-quote -p \"$1\")\"\n\t\t;;\n\t--no-parent) no_parent=1 ;;\n\t-e)          edit=1 ;;\n\t--no-edit)   edit= ;;\n\t-m)          message=\"$2\"; shift ;;\n\t--no-message)message= ;;\n\t-q)          quiet=-q ;;\n\t--no-quiet)  quiet= ;;\n\t--reset)     no_reset= ;;\n\t--no-reset)  no_reset=1 ;;\n\t--)          shift; break ;;\n\t*)           die \"internal error: unknown flag $1\" ;;\n\tesac\n\tshift\ndone\n[ $# -gt 0 ] && \\\n\tdie_with_usage \"no positional arguments expected\"\n[ -z \"$no_parent\" -a -z \"$parent_flags\" ] && \\\n\tdie_with_usage \"either -p or --no-parent is required\"\n[ -n \"$no_parent\" -a -n \"$parent_flags\" ] && \\\n\tdie_with_usage \"-p and --no-parent are mutually exclusive\"\n\n# Create the commit.\nif [ -n \"$message\" ]; then\n\techo \"$message\" > \"$msg_file\"\nelse\n\tgit cat-file commit HEAD | sed \"1,/^$/d\" > \"$msg_file\"\nfi\nif [ -n \"$edit\" ]; then\n\t# TODO: use the normal `git commit` comment stripping stuff\n\tgit_editor \"$msg_file\" || die \"no editor configured\"\n\t[ -s \"$msg_file\" ] || die \"aborting due to empty commit message\"\nfi\neval \"$(get_author_ident_from_commit HEAD)\"\nold_head=\"$(git rev-parse --short HEAD)\" || exit $?\nnew_head=\"$(eval \"git commit-tree HEAD: $parent_flags\" '< $msg_file')\" || \\\n\texit $?\nrm \"$msg_file\"\n\n# Print out the commit if --no-reset; otherwise update HEAD.\nif [ -n \"$no_reset\" ]; then\n\techo \"$new_head\"\nelse\n\tset_reflog_action reparent\n\tgit reset $quiet \"$new_head\" || exit $?\n\tnew_abbrev=\"$(git rev-parse --short HEAD)\" || exit $?\n\t[ -z \"$quiet\" ] && echo \"Moved HEAD to $new_abbrev (was $old_head)\"\nfi\n"},{"id":"206770","messageId":"20130114071608.GL3125@elie.Belkin","threadId":"32621","inReplyTo":"CAHREChhnf44CprHnS=z9KO5aOkfDPSG6Xb2GU=Kvaz38eTgbUg@mail.gmail.com","subject":"Re: Announcing git-reparent","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2013-01-14T07:16:08Z","receivedAt":"2013-01-14T07:16:08Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi Mark,\n\nMark Lodato wrote:\n\n> Create a new commit object that has the same tree and commit message as HEAD\n> but with a different set of parents.  If ``--no-reset`` is given, the full\n> object id of this commit is printed and the program exits\n\nI've been wishing for something like this for a long time.  I used to\nfake it using \"cat-file commit\", sed, and \"hash-object -w\" when\nstitching together poorly imported history using \"git replace\".\n\nThanks for writing it.\n\nCiao,\nJonathan\n"},{"id":"206781","messageId":"CAA01Csoh24ppo37fzptzZKvFrzGQyhz-0eDTQsP8tZiTRQ2YwA@mail.gmail.com","threadId":"32621","inReplyTo":"20130114071608.GL3125@elie.Belkin","subject":"Re: Announcing git-reparent","fromName":"Piotr Krukowiecki","fromEmail":"piotr.krukowiecki@gmail.com","sentAt":"2013-01-14T08:03:31Z","receivedAt":"2013-01-14T08:03:31Z","isPatch":false,"sender":{"key":"piotr.krukowiecki@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3259959?v=4"},"body":"Hello,\n\nOn Mon, Jan 14, 2013 at 8:16 AM, Jonathan Nieder <jrnieder@gmail.com> wrote:\n> Hi Mark,\n>\n> Mark Lodato wrote:\n>\n>> Create a new commit object that has the same tree and commit message as HEAD\n>> but with a different set of parents.  If ``--no-reset`` is given, the full\n>> object id of this commit is printed and the program exits\n>\n> I've been wishing for something like this for a long time.  I used to\n> fake it using \"cat-file commit\", sed, and \"hash-object -w\" when\n> stitching together poorly imported history using \"git replace\".\n\nJust wondering, is the result different than something like\n\ngit checkout commit_to_reparent\ncp -r * ../snapshot/\ngit reset --hard new_parent\nrm -r *\ncp -r ../snapshot/* .\ngit add -A\n\n(assumes 1 parent, does not cope with .dot files, and has probably\nother small problems)\n\n--\nPiotr Krukowiecki\n"},{"id":"206782","messageId":"7v7gngfau9.fsf@alter.siamese.dyndns.org","threadId":"32621","inReplyTo":"20130114071608.GL3125@elie.Belkin","subject":"Re: Announcing git-reparent","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-01-14T08:05:50Z","receivedAt":"2013-01-14T08:05:50Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> Hi Mark,\n>\n> Mark Lodato wrote:\n>\n>> Create a new commit object that has the same tree and commit message as HEAD\n>> but with a different set of parents.  If ``--no-reset`` is given, the full\n>> object id of this commit is printed and the program exits\n>\n> I've been wishing for something like this for a long time.  I used to\n> fake it using \"cat-file commit\", sed, and \"hash-object -w\" when\n> stitching together poorly imported history using \"git replace\".\n>\n> Thanks for writing it.\n>\n> Ciao,\n> Jonathan\n\nThe scriptq is simple enough, and from a cursory look, it may be\nfine to throw it in contrib/ after some minor style fixes and such,\nif many find it useful.\n"},{"id":"206837","messageId":"m21udnzfvp.fsf@igel.home","threadId":"32621","inReplyTo":"CAA01Csoh24ppo37fzptzZKvFrzGQyhz-0eDTQsP8tZiTRQ2YwA@mail.gmail.com","subject":"Re: Announcing git-reparent","fromName":"Andreas Schwab","fromEmail":"schwab@linux-m68k.org","sentAt":"2013-01-14T20:08:58Z","receivedAt":"2013-01-14T20:08:58Z","isPatch":false,"sender":{"key":"schwab@linux-m68k.org","avatar":"https://avatars.githubusercontent.com/u/2175493?v=4"},"body":"Piotr Krukowiecki <piotr.krukowiecki@gmail.com> writes:\n\n> Just wondering, is the result different than something like\n>\n> git checkout commit_to_reparent\n> cp -r * ../snapshot/\n> git reset --hard new_parent\n> rm -r *\n> cp -r ../snapshot/* .\n> git add -A\n\nI think you are looking for \"git reset --soft new_parent\", which keeps\nboth the index and the working tree intact.\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n"},{"id":"206838","messageId":"CAHREChigPiRpwcM5QPEG8UJVro_fTN_a76AZaqowTWbwtw_M-g@mail.gmail.com","threadId":"32621","inReplyTo":"CAA01Csoh24ppo37fzptzZKvFrzGQyhz-0eDTQsP8tZiTRQ2YwA@mail.gmail.com","subject":"Re: Announcing git-reparent","fromName":"Mark Lodato","fromEmail":"lodatom@gmail.com","sentAt":"2013-01-14T20:28:23Z","receivedAt":"2013-01-14T20:28:23Z","isPatch":false,"sender":{"key":"lodatom@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58860?v=4"},"body":"On Mon, Jan 14, 2013 at 3:03 AM, Piotr Krukowiecki\n<piotr.krukowiecki@gmail.com> wrote:\n> Just wondering, is the result different than something like\n>\n> git checkout commit_to_reparent\n> cp -r * ../snapshot/\n> git reset --hard new_parent\n> rm -r *\n> cp -r ../snapshot/* .\n> git add -A\n>\n> (assumes 1 parent, does not cope with .dot files, and has probably\n> other small problems)\n\nThe result is similar, but your script would also lose the commit\nmessage and author.  I think the following would do exactly as my\nscript does (untested):\n\ngit checkout commit_to_reparent\ngit branch tmp\ngit reset --soft new_parent\ngit commit -C tmp\ngit branch -D tmp\n\nI actually contemplated using the above method in my script, rather\nthan git-commit-tree and git-reset.  In the end, I decided to stick\nwith my original approach because it does not create any intermediate\nstate; either an early command fails and nothing changes, or the git\nreset works and everything is done.  Using the above might be cleaner\nfor the --edit flag since it allows the git-commit cleanup of the\ncommit message, but this would require much more careful error\nhandling, and might make the reflog uglier.\n\nI'd be interested to hear a git expert's opinion on the choice.\n"}]}