Re: What I want rebase to do

3 messages, 3 authors, 2016-06-15 · open the first message on its own page

Re: What I want rebase to do

From: Thomas Rast <hidden>
Date: 2016-06-15 22:56:19

worley@alum.mit.edu (Dale R. Worley) writes:
[...snip...]

Isn't that just a very long-winded way of restating what Junio said
earlier:
quoted
It was suggested to make it apply the first-parent diff and record
the result, I think.  If that were an acceptable approach (I didn't
think about it through myself, though), that would automatically
cover the evil-merge case as well.
You can fake that with something like

git rev-list --first-parent --reverse RANGE_TO_REBASE |
while read rev; do
    if git rev-parse $rev^2 >/dev/null 2>&1; then
        git cherry-pick -n -m1 $rev
        git rev-parse $rev^2 >.git/MERGE_HEAD
        git commit -C$rev
    else
        git cherry-pick $rev
    fi
done

Only tested very lightly.  Dealing with octopii, conflicts and actually
preserving the commit's attributes is left as an exercise to the
reader[1].

I still think that the _right_ solution is first redoing the merge on
its original parents and then seeing how the actual merge differs from
that.  --preserve-merges has bigger issues though, like Junio said.

Perhaps a new option to git-rebase could trigger the above behavior for
merges, who knows.  (It could be called --first-parent.)


[1]  If you don't get the sarcasm: that would amount to reinventing
large parts of git-rebase.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

Re: What I want rebase to do

From: Johannes Sixt <hidden>
Date: 2016-06-15 22:56:19

Am 3/7/2013 9:48, schrieb Thomas Rast:
worley@alum.mit.edu (Dale R. Worley) writes:
[...snip...]

Isn't that just a very long-winded way of restating what Junio said
earlier:
quoted
quoted
It was suggested to make it apply the first-parent diff and record
the result, I think.  If that were an acceptable approach (I didn't
think about it through myself, though), that would automatically
cover the evil-merge case as well.
You can fake that with something like

git rev-list --first-parent --reverse RANGE_TO_REBASE |
while read rev; do
    if git rev-parse $rev^2 >/dev/null 2>&1; then
        git cherry-pick -n -m1 $rev
        git rev-parse $rev^2 >.git/MERGE_HEAD
        git commit -C$rev
    else
        git cherry-pick $rev
    fi
done

Only tested very lightly.  Dealing with octopii, conflicts and actually
preserving the commit's attributes is left as an exercise to the
reader[1].
I proposed this long ago, but by modifying preserve-merges rather than
with a new option (--first-parent):
http://thread.gmane.org/gmane.comp.version-control.git/198125

It works very well. I'm using it frequently in the field.

-- Hannes

Re: What I want rebase to do

From: Dale R. Worley <hidden>
Date: 2016-06-15 22:56:19

From: Thomas Rast <redacted>

worley@alum.mit.edu (Dale R. Worley) writes:
[...snip...]

Isn't that just a very long-winded way of restating what Junio said
earlier:
quoted
quoted
It was suggested to make it apply the first-parent diff and record
the result, I think.  If that were an acceptable approach (I didn't
think about it through myself, though), that would automatically
cover the evil-merge case as well.
Well, I believe what I said was a fleshed-out way of saying what I
*think* Junio said, but...
You can fake that with something like

git rev-list --first-parent --reverse RANGE_TO_REBASE |
while read rev; do
    if git rev-parse $rev^2 >/dev/null 2>&1; then
        git cherry-pick -n -m1 $rev
        git rev-parse $rev^2 >.git/MERGE_HEAD
        git commit -C$rev
    else
        git cherry-pick $rev
    fi
done
This code doesn't do that.  I don't want something that rebases a
single thread of the current branch, I want something that rebases
*all* the commits between the head commit and the merge base.  Which
is what is illustrated in my message.
[1]  If you don't get the sarcasm: that would amount to reinventing
large parts of git-rebase.
Yes, that is the point of the exercise.

I've done a proof-of-concept implementation of what I want to see,
calling it git-rebase--merge-safe.  But I'm new here and likely that
is a pretty crude solution.  I suspect that a real implementation
could be done by inserting this logic into the framework of
git-filter-tree.  Following is git-rebase--merge-safe, and the script
I use to test it (and explore rebase problems).

Dale
----------------------------------------------------------------------
git-rebase--merge-safe

#!/bin/bash

. git-sh-setup

prec=4

set -ex

# Ensure the work tree is clean.
require_clean_work_tree "rebase" "Please commit or stash them."

onto_name=$1        
onto=$(git rev-parse --verify "${onto_name}^0") ||
    die "Does not point to a valid commit: $1"

head_name=$( git symbolic-ref HEAD )
orig_head=$(git rev-parse --verify $head_name) ||
    exit 1

echo onto=$onto
echo head_name=$head_name
echo orig_head=$orig_head

# Get the merge base, which is the root of the branch that we are rebasing.
# (For now, ignore the question of whether there is more than one merge base.)
mb=$(git merge-base "$onto" "$orig_head")
echo mb=$mb

# Get the list of commits to rebase, which is everything between $mb and
# $orig_head.
# Note that $mb is not included.
revisions=`git rev-list --reverse --ancestry-path $mb..$orig_head`
echo revisions=$revisions

# Set up the list mapping the commits on the original branch to the commits
# on the branch we are creating.
# Its format is ",old-hash1/new-hash1,old-hash2/new-hash2,...,".
# The initial value maps $mb to $onto.
map=",$mb/$onto,"

# Export these so git commit can see them.
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE

# Process each commit in forward topological order.
for cmt in $revisions
do
    # Examine the commit to extract information we will need to reconstruct it.
    # First parent of the commit that has a mapping, i.e., is part of the
    # branch (and has thus been rebuilt already.
    first_mapped_parent=
    # The new commit that was made of $first_mapped_parent.
    first_mapped_parent_mapped=
    # List of -p options naming the parent commits, or their new commits if they
    # are in the branch.
    parents=
   # Dissect the old commit's data.
    # Output the commit data into FD 3.
    exec 3< <( git cat-file commit $cmt )

    while read keyword rest <&3
    do
	case $keyword in
	    tree)
		# Ignored
		;;
	    parent)
		# See if the parent is mapped, i.e., is in the
		# original branch.
		if [[ "$map" == *,$rest/* ]]
		then
		    # This parent has been mapped.  Get the new commit.
		    parent_mapped=${map#*,$rest/}
		    parent_mapped=${parent_mapped%%,*}
		    if test -z "$first_mapped_parent"
		    then
			first_mapped_parent=$rest
			first_mapped_parent_mapped=$parent_mapped
		    fi
		else
		    # This parent has not been mapped.
		    parent_mapped=$rest
		fi
		# $parent_mapped is a parent of the new commit.
		parents="$parents -p $parent_mapped"
		;;
	    author)
		# Extract the information about the author.
		GIT_AUTHOR_NAME="${rest%% <*}"
		GIT_AUTHOR_EMAIL="${rest##* <}"
		GIT_AUTHOR_EMAIL="${GIT_AUTHOR_EMAIL%%> *}"
		GIT_AUTHOR_DATE="${rest##*> }"
		;;
	    committer)
		# Ignored:  The new commit will have this use's name
		# as committer.
		;;
	    '')
		# End of fixed fields, remainder is the commit comment.
		# Leave contents of FD 3 queued to be read later by
		# git commit-tree.
		break
		;;
	    *)
		# Ignore all other keywords.
		;;
	esac
    done

    echo GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME"
    echo GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL"
    echo GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE"
    echo parents="$parents"
    echo first_mapped_parent=$first_mapped_parent
    echo first_mapped_parent_mapped=$first_mapped_parent_mapped

    test -n "$first_mapped_parent" || exit 1

    # Do the three-way merge.
    # Empty the tree so git read-tree will merge into it.
    git read-tree --empty
    git read-tree -m --aggressive \
	$first_mapped_parent $cmt $first_mapped_parent_mapped
    git merge-index git-merge-one-file -a

    # Construct the file tree for the new commit.
    tree=$( git write-tree )

    # Create the new commit
    # Note that FD 3 contains the remainder of the commit description
    # from the git cat-file above.
    new_commit=$( git commit-tree $tree $parents <&3 )
    echo new_commit=$new_commit

    # Add the new commit to the map.
    map="$map$cmt/$new_commit,"

done

echo Final commit is $new_commit

# Update the branch pointers.
git update-ref ORIG_HEAD $orig_head
git update-ref $head_name $new_commit

# Go to the new head of the branch.
git checkout ${head_name#refs/heads/}
----------------------------------------------------------------------
script.fixed

set -e

# Create a temporary directory and go into it.
DIR=temp.$$
mkdir $DIR
cd $DIR

# Create a Git repository.
git init

# Create a file containing the lines 1 to 10.
seq 1 10 >file
git add file
git commit -m 'Commit A'

# Start the dev branch at commit A.
git branch dev HEAD

# Add lines 1.5, 2.5, and 3.5 in a series of commits on master.

# This sed command adds a line 1.5 before the line 2.
sed --in-place -e '/^2$/i1.5' file
git commit -a -m 'Commit B'

sed --in-place -e '/^3$/i2.5' file
git commit -a -m 'Commit C'

sed --in-place -e '/^4$/i3.5' file
git commit -a -m 'Commit D'

# Show the commit structure of master.
#git log --graph --oneline master
#git log --graph -p master
echo 'On master:'
cat file

# Go to the dev branch and create commits with a non-trivial merge.
git checkout dev

sed --in-place -e '/^5$/i4.5' file
git commit -a -m 'Commit P'

git branch dev1 HEAD

sed --in-place -e '/^6$/i5.5' file
git commit -a -m 'Commit Q'

git checkout dev1

sed --in-place -e '/^7$/i6.5' file
git commit -a -m 'Commit R'

git checkout dev

# Merge commits Q and R, but add the additional line 7.5 (to simulate
# fixes that were needed to resolve the merge).

git merge --no-commit dev1
sed --in-place -e '/^8$/i7.5' file
git commit -a -m 'Commit S'

sed --in-place -e '/^9$/i8.5' file
git commit -a -m 'Commit T'

# Show the commit structure of dev.
#git log --graph --oneline dev
# *** Note that the diffs do not show the line 7.5 added in commit S.
#git log --graph -p dev
echo 'On dev:'
cat file

# Show the branch structure.
git show-branch --sha1-name
git log --all --oneline --graph

# Rebase the dev branch to the tip of master using our hack script.
git checkout dev
git branch -f rebase dev
git checkout rebase
PATH=/usr/libexec/git-core:$PATH
../git-rebase--merge-safe master

# Show the commit structure.
git log --graph --oneline
# *** Note that the line 7.5 added in commit S isn't carried into the new branch.
git log --graph -p

echo 'After rebasing:'
cat file
----------------------------------------------------------------------
[EOF]
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help