git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* Announcing git-reparent
@ 2013-01-14  6:15 Mark Lodato
  2013-01-14  7:16 ` Jonathan Nieder
  0 siblings, 1 reply; 6+ messages in thread
From: Mark Lodato @ 2013-01-14  6:15 UTC (permalink / raw
  To: git list

I threw together a small utility called "git-reparent", available on GitHub at:

    https://github.com/MarkLodato/git-reparent

I welcome any comments or suggestions.  To make discussion easier,
I've copied the README and code below.

--- 8< ---

NAME
====

git-reparent - Recommit HEAD with a new set of parents.


SYNOPSIS
========

``git reparent [OPTIONS] ((-p <parent>)... | --no-parent)``


DESCRIPTION
===========

Create a new commit object that has the same tree and commit message as HEAD
but with a different set of parents.  If ``--no-reset`` is given, the full
object id of this commit is printed and the program exits; otherwise, ``git
reset`` is used to update HEAD and the current branch to this new commit.

This command can be used to manually shape the history of a repository.
Whereas ``git rebase`` moves around *diffs*, ``git reparent`` moves around
*snapshots*.  See EXAMPLES for a reason why you might want to use this
command.


OPTIONS
=======

-h, --help                show the help
-e, --edit                edit the commit message in an editor first
-m, --message <message>   use the given message instead of that of HEAD
-p, --parent <commit>     new parent to use; may be given multiple times
--no-parent               create a parentless commit
-q, --quiet               be quiet; only report errors
--no-reset                print the new object id instead of updating HEAD


INSTALLATION
============

Make executable and place somewhere in your $PATH.


EXAMPLES
========

Reparenting the tip of a branch
-------------------------------

Suppose we create some commit *B* and then accidentally pass the ``--amend``
flag when creating new commit *C*, resulting in the following history::

                B
               /
        ...---A---C (HEAD)

What we really wanted was one linear history, ``...---A--B--C``.  If we
were to use ``git rebase`` or ``git cherry-pick`` to reconstruct the history,
this would try to apply the *diff* of *A..C* onto *B*, which might fail.
Instead, what we really want to do is use the exact message and tree from *C*
but with parent *B* instead of *A*.  To do this, we run ::

        $ git reparent -p B

where the name *B* can be found by running ``git reflog`` and looking for the
first "commit (amend)".  The resulting history is now just what we wanted::

                C
               /
        ...---A---B---C' (HEAD)


Reparenting an inner commit
---------------------------

We can also update the parents of a commit other than the most recent.
Suppose that we want to perform a rebase-like operation, moving *master* onto
*origin/master*, but we want to completely ignore any changes made in the
remote branch.  That is, our history currently looks like this::

                B---C (master, HEAD)
               /
        ...---A---D---E (origin/master)

and we want to make it look like this::

                B---C   (origin/master)
               /       /
        ...---A---D---E---B'---C' (master, HEAD)

We can accomplish this by using ``git rebase --interactive`` along with ``git
reparent``::

        $ git rebase -i A
        # select the "edit" command for commit B
        # git rebase will dump us out at commit B
        $ git reparent -p origin/master
        $ git rebase --continue

Now the history will look as desired, and the trees, commit messages, and
authors of *B'* and *C'* will be identical to those of *B* and *C*,
respectively.


SEE ALSO
========

git-filter-branch(1) combined with either git grafts or git-replace(1) can be
used to achieve the same effect

git-rebase(1) can be used to re-apply the *diffs* of the current branch to
another


AUTHOR
======

Mark Lodato <lodatom@gmail.com>

--- 8< ---

#!/bin/sh
# Copyright (c) Mark Lodato, 2013

OPTIONS_SPEC="\
git reparent [OPTIONS] ((-p <parent>)... | --no-parent)

Recommit HEAD with a new set of parents.
--
h,help          show the help
e,edit          edit the commit message in an editor first
m,message=      use the given message instead of that of HEAD
p,parent=!      new parent to use; may be given multiple times
no-parent!      create a parentless commit
q,quiet         be quiet; only report errors
reset*          default behavior
no-reset!       print the new object id instead of updating HEAD
"
SUBDIRECTORY_OK=Yes
. "$(git --exec-path)/git-sh-setup" || exit $?
require_clean_work_tree reparent "Please commit or stash them first."

# Location of the temporary message.
msg_file="$GIT_DIR/reparent-msg"

die_with_usage() {
	echo "error: $1" >&2
	usage
}

edit=
message=
no_parent=
no_reset=
parent_flags=
quiet=
while [ $# -gt 0 ]; do
	case "$1" in
	-p)
		[ $# -eq 0 ] && die_with_usage "-p requires an argument"
		shift
		parent_flags="$parent_flags$(git rev-parse --sq-quote -p "$1")"
		;;
	--no-parent) no_parent=1 ;;
	-e)          edit=1 ;;
	--no-edit)   edit= ;;
	-m)          message="$2"; shift ;;
	--no-message)message= ;;
	-q)          quiet=-q ;;
	--no-quiet)  quiet= ;;
	--reset)     no_reset= ;;
	--no-reset)  no_reset=1 ;;
	--)          shift; break ;;
	*)           die "internal error: unknown flag $1" ;;
	esac
	shift
done
[ $# -gt 0 ] && \
	die_with_usage "no positional arguments expected"
[ -z "$no_parent" -a -z "$parent_flags" ] && \
	die_with_usage "either -p or --no-parent is required"
[ -n "$no_parent" -a -n "$parent_flags" ] && \
	die_with_usage "-p and --no-parent are mutually exclusive"

# Create the commit.
if [ -n "$message" ]; then
	echo "$message" > "$msg_file"
else
	git cat-file commit HEAD | sed "1,/^$/d" > "$msg_file"
fi
if [ -n "$edit" ]; then
	# TODO: use the normal `git commit` comment stripping stuff
	git_editor "$msg_file" || die "no editor configured"
	[ -s "$msg_file" ] || die "aborting due to empty commit message"
fi
eval "$(get_author_ident_from_commit HEAD)"
old_head="$(git rev-parse --short HEAD)" || exit $?
new_head="$(eval "git commit-tree HEAD: $parent_flags" '< $msg_file')" || \
	exit $?
rm "$msg_file"

# Print out the commit if --no-reset; otherwise update HEAD.
if [ -n "$no_reset" ]; then
	echo "$new_head"
else
	set_reflog_action reparent
	git reset $quiet "$new_head" || exit $?
	new_abbrev="$(git rev-parse --short HEAD)" || exit $?
	[ -z "$quiet" ] && echo "Moved HEAD to $new_abbrev (was $old_head)"
fi

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Announcing git-reparent
  2013-01-14  6:15 Announcing git-reparent Mark Lodato
@ 2013-01-14  7:16 ` Jonathan Nieder
  2013-01-14  8:03   ` Piotr Krukowiecki
  2013-01-14  8:05   ` Junio C Hamano
  0 siblings, 2 replies; 6+ messages in thread
From: Jonathan Nieder @ 2013-01-14  7:16 UTC (permalink / raw
  To: Mark Lodato; +Cc: git list

Hi Mark,

Mark Lodato wrote:

> Create a new commit object that has the same tree and commit message as HEAD
> but with a different set of parents.  If ``--no-reset`` is given, the full
> object id of this commit is printed and the program exits

I've been wishing for something like this for a long time.  I used to
fake it using "cat-file commit", sed, and "hash-object -w" when
stitching together poorly imported history using "git replace".

Thanks for writing it.

Ciao,
Jonathan

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Announcing git-reparent
  2013-01-14  7:16 ` Jonathan Nieder
@ 2013-01-14  8:03   ` Piotr Krukowiecki
  2013-01-14 20:08     ` Andreas Schwab
  2013-01-14 20:28     ` Mark Lodato
  2013-01-14  8:05   ` Junio C Hamano
  1 sibling, 2 replies; 6+ messages in thread
From: Piotr Krukowiecki @ 2013-01-14  8:03 UTC (permalink / raw
  To: Jonathan Nieder; +Cc: Mark Lodato, git list

Hello,

On Mon, Jan 14, 2013 at 8:16 AM, Jonathan Nieder <jrnieder@gmail.com> wrote:
> Hi Mark,
>
> Mark Lodato wrote:
>
>> Create a new commit object that has the same tree and commit message as HEAD
>> but with a different set of parents.  If ``--no-reset`` is given, the full
>> object id of this commit is printed and the program exits
>
> I've been wishing for something like this for a long time.  I used to
> fake it using "cat-file commit", sed, and "hash-object -w" when
> stitching together poorly imported history using "git replace".

Just wondering, is the result different than something like

git checkout commit_to_reparent
cp -r * ../snapshot/
git reset --hard new_parent
rm -r *
cp -r ../snapshot/* .
git add -A

(assumes 1 parent, does not cope with .dot files, and has probably
other small problems)

--
Piotr Krukowiecki

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Announcing git-reparent
  2013-01-14  7:16 ` Jonathan Nieder
  2013-01-14  8:03   ` Piotr Krukowiecki
@ 2013-01-14  8:05   ` Junio C Hamano
  1 sibling, 0 replies; 6+ messages in thread
From: Junio C Hamano @ 2013-01-14  8:05 UTC (permalink / raw
  To: Jonathan Nieder; +Cc: Mark Lodato, git list

Jonathan Nieder <jrnieder@gmail.com> writes:

> Hi Mark,
>
> Mark Lodato wrote:
>
>> Create a new commit object that has the same tree and commit message as HEAD
>> but with a different set of parents.  If ``--no-reset`` is given, the full
>> object id of this commit is printed and the program exits
>
> I've been wishing for something like this for a long time.  I used to
> fake it using "cat-file commit", sed, and "hash-object -w" when
> stitching together poorly imported history using "git replace".
>
> Thanks for writing it.
>
> Ciao,
> Jonathan

The scriptq is simple enough, and from a cursory look, it may be
fine to throw it in contrib/ after some minor style fixes and such,
if many find it useful.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Announcing git-reparent
  2013-01-14  8:03   ` Piotr Krukowiecki
@ 2013-01-14 20:08     ` Andreas Schwab
  2013-01-14 20:28     ` Mark Lodato
  1 sibling, 0 replies; 6+ messages in thread
From: Andreas Schwab @ 2013-01-14 20:08 UTC (permalink / raw
  To: Piotr Krukowiecki; +Cc: Jonathan Nieder, Mark Lodato, git list

Piotr Krukowiecki <piotr.krukowiecki@gmail.com> writes:

> Just wondering, is the result different than something like
>
> git checkout commit_to_reparent
> cp -r * ../snapshot/
> git reset --hard new_parent
> rm -r *
> cp -r ../snapshot/* .
> git add -A

I think you are looking for "git reset --soft new_parent", which keeps
both the index and the working tree intact.

Andreas.

-- 
Andreas Schwab, schwab@linux-m68k.org
GPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5
"And now for something completely different."

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Announcing git-reparent
  2013-01-14  8:03   ` Piotr Krukowiecki
  2013-01-14 20:08     ` Andreas Schwab
@ 2013-01-14 20:28     ` Mark Lodato
  1 sibling, 0 replies; 6+ messages in thread
From: Mark Lodato @ 2013-01-14 20:28 UTC (permalink / raw
  To: Piotr Krukowiecki; +Cc: Jonathan Nieder, git list

On Mon, Jan 14, 2013 at 3:03 AM, Piotr Krukowiecki
<piotr.krukowiecki@gmail.com> wrote:
> Just wondering, is the result different than something like
>
> git checkout commit_to_reparent
> cp -r * ../snapshot/
> git reset --hard new_parent
> rm -r *
> cp -r ../snapshot/* .
> git add -A
>
> (assumes 1 parent, does not cope with .dot files, and has probably
> other small problems)

The result is similar, but your script would also lose the commit
message and author.  I think the following would do exactly as my
script does (untested):

git checkout commit_to_reparent
git branch tmp
git reset --soft new_parent
git commit -C tmp
git branch -D tmp

I actually contemplated using the above method in my script, rather
than git-commit-tree and git-reset.  In the end, I decided to stick
with my original approach because it does not create any intermediate
state; either an early command fails and nothing changes, or the git
reset works and everything is done.  Using the above might be cleaner
for the --edit flag since it allows the git-commit cleanup of the
commit message, but this would require much more careful error
handling, and might make the reflog uglier.

I'd be interested to hear a git expert's opinion on the choice.

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2013-01-14 20:29 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-01-14  6:15 Announcing git-reparent Mark Lodato
2013-01-14  7:16 ` Jonathan Nieder
2013-01-14  8:03   ` Piotr Krukowiecki
2013-01-14 20:08     ` Andreas Schwab
2013-01-14 20:28     ` Mark Lodato
2013-01-14  8:05   ` Junio C Hamano

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).