Commits

Anonymous committed f36db54 Merge

Merge branch 'js/rebase'

* js/rebase:
Teach rebase -i about --preserve-merges
rebase -i: provide reasonable reflog for the rebased branch
rebase -i: several cleanups
ignore git-rebase--interactive
Teach rebase an interactive mode
Move the pick_author code to git-sh-setup

  • Participants
  • Parent commits e1bc8dc, f09c9b8

Comments (0)

Files changed (8)

 git-quiltimport
 git-read-tree
 git-rebase
+git-rebase--interactive
 git-receive-pack
 git-reflog
 git-relink

File Documentation/git-rebase.txt

 SYNOPSIS
 --------
 [verse]
-'git-rebase' [-v] [--merge] [-C<n>] [--onto <newbase>] <upstream> [<branch>]
+'git-rebase' [-i | --interactive] [-v | --verbose] [--merge] [-C<n>]
+	[-p | --preserve-merges] [--onto <newbase>] <upstream> [<branch>]
 'git-rebase' --continue | --skip | --abort
 
 DESCRIPTION
 	context exist they all must match.  By default no context is
 	ever ignored.
 
+-i, \--interactive::
+	Make a list of the commits which are about to be rebased.  Let the
+	user edit that list before rebasing.
+
+-p, \--preserve-merges::
+	Instead of ignoring merges, try to recreate them.  This option
+	only works in interactive mode.
+
 include::merge-strategies.txt[]
 
 NOTES
 You must be in the top directory of your project to start (or continue)
 a rebase.  Upon completion, <branch> will be the current branch.
 
-Author
+INTERACTIVE MODE
+----------------
+
+Rebasing interactively means that you have a chance to edit the commits
+which are rebased.  You can reorder the commits, and you can
+remove them (weeding out bad or otherwise unwanted patches).
+
+The interactive mode is meant for this type of workflow:
+
+1. have a wonderful idea
+2. hack on the code
+3. prepare a series for submission
+4. submit
+
+where point 2. consists of several instances of
+
+a. regular use
+ 1. finish something worthy of a commit
+ 2. commit
+b. independent fixup
+ 1. realize that something does not work
+ 2. fix that
+ 3. commit it
+
+Sometimes the thing fixed in b.2. cannot be amended to the not-quite
+perfect commit it fixes, because that commit is buried deeply in a
+patch series.  That is exactly what interactive rebase is for: use it
+after plenty of "a"s and "b"s, by rearranging and editing
+commits, and squashing multiple commits into one.
+
+Start it with the last commit you want to retain as-is:
+
+	git rebase -i <after-this-commit>
+
+An editor will be fired up with all the commits in your current branch
+(ignoring merge commits), which come after the given commit.  You can
+reorder the commits in this list to your heart's content, and you can
+remove them.  The list looks more or less like this:
+
+-------------------------------------------
+pick deadbee The oneline of this commit
+pick fa1afe1 The oneline of the next commit
+...
+-------------------------------------------
+
+The oneline descriptions are purely for your pleasure; `git-rebase` will
+not look at them but at the commit names ("deadbee" and "fa1afe1" in this
+example), so do not delete or edit the names.
+
+By replacing the command "pick" with the command "edit", you can tell
+`git-rebase` to stop after applying that commit, so that you can edit
+the files and/or the commit message, amend the commit, and continue
+rebasing.
+
+If you want to fold two or more commits into one, replace the command
+"pick" with "squash" for the second and subsequent commit.  If the
+commits had different authors, it will attribute the squashed commit to
+the author of the last commit.
+
+In both cases, or when a "pick" does not succeed (because of merge
+errors), the loop will stop to let you fix things, and you can continue
+the loop with `git rebase --continue`.
+
+For example, if you want to reorder the last 5 commits, such that what
+was HEAD~4 becomes the new HEAD. To achieve that, you would call
+`git-rebase` like this:
+
+----------------------
+$ git rebase -i HEAD~5
+----------------------
+
+And move the first patch to the end of the list.
+
+You might want to preserve merges, if you have a history like this:
+
+------------------
+           X
+            \
+         A---M---B
+        /
+---o---O---P---Q
+------------------
+
+Suppose you want to rebase the side branch starting at "A" to "Q". Make
+sure that the current HEAD is "B", and call
+
+-----------------------------
+$ git rebase -i -p --onto Q O
+-----------------------------
+
+Authors
 ------
-Written by Junio C Hamano <junkio@cox.net>
+Written by Junio C Hamano <junkio@cox.net> and
+Johannes E. Schindelin <johannes.schindelin@gmx.de>
 
 Documentation
 --------------
 	git-fetch.sh \
 	git-ls-remote.sh \
 	git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
-	git-pull.sh git-rebase.sh \
+	git-pull.sh git-rebase.sh git-rebase--interactive.sh \
 	git-repack.sh git-request-pull.sh git-reset.sh \
 	git-sh-setup.sh \
 	git-tag.sh git-verify-tag.sh \

File git-commit.sh

 # Author
 if test '' != "$use_commit"
 then
-	pick_author_script='
-	/^author /{
-		s/'\''/'\''\\'\'\''/g
-		h
-		s/^author \([^<]*\) <[^>]*> .*$/\1/
-		s/'\''/'\''\'\'\''/g
-		s/.*/GIT_AUTHOR_NAME='\''&'\''/p
-
-		g
-		s/^author [^<]* <\([^>]*\)> .*$/\1/
-		s/'\''/'\''\'\'\''/g
-		s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
-
-		g
-		s/^author [^<]* <[^>]*> \(.*\)$/\1/
-		s/'\''/'\''\'\'\''/g
-		s/.*/GIT_AUTHOR_DATE='\''&'\''/p
-
-		q
-	}
-	'
-	encoding=$(git config i18n.commitencoding || echo UTF-8)
-	set_author_env=`git show -s --pretty=raw --encoding="$encoding" "$use_commit" |
-	LANG=C LC_ALL=C sed -ne "$pick_author_script"`
-	eval "$set_author_env"
-	export GIT_AUTHOR_NAME
-	export GIT_AUTHOR_EMAIL
-	export GIT_AUTHOR_DATE
+	eval "$(get_author_ident_from_commit "$use_commit")"
+	export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
 fi
 if test '' != "$force_author"
 then

File git-rebase--interactive.sh

+#!/bin/sh
+#
+# Copyright (c) 2006 Johannes E. Schindelin
+
+# SHORT DESCRIPTION
+#
+# This script makes it easy to fix up commits in the middle of a series,
+# and rearrange commits.
+#
+# The original idea comes from Eric W. Biederman, in
+# http://article.gmane.org/gmane.comp.version-control.git/22407
+
+USAGE='(--continue | --abort | --skip | [--preserve-merges] [--verbose]
+	[--onto <branch>] <upstream> [<branch>])'
+
+. git-sh-setup
+require_work_tree
+
+DOTEST="$GIT_DIR/.dotest-merge"
+TODO="$DOTEST"/todo
+DONE="$DOTEST"/done
+REWRITTEN="$DOTEST"/rewritten
+PRESERVE_MERGES=
+STRATEGY=
+VERBOSE=
+
+warn () {
+	echo "$*" >&2
+}
+
+require_clean_work_tree () {
+	# test if working tree is dirty
+	git rev-parse --verify HEAD > /dev/null &&
+	git update-index --refresh &&
+	git diff-files --quiet &&
+	git diff-index --cached --quiet HEAD ||
+	die "Working tree is dirty"
+}
+
+ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
+
+comment_for_reflog () {
+	case "$ORIG_REFLOG_ACTION" in
+	''|rebase*)
+		GIT_REFLOG_ACTION="rebase -i ($1)"
+		export GIT_REFLOG_ACTION
+	esac
+}
+
+mark_action_done () {
+	sed -e 1q < "$TODO" >> "$DONE"
+	sed -e 1d < "$TODO" >> "$TODO".new
+	mv -f "$TODO".new "$TODO"
+}
+
+make_patch () {
+	parent_sha1=$(git rev-parse --verify "$1"^ 2> /dev/null)
+	git diff "$parent_sha1".."$1" > "$DOTEST"/patch
+}
+
+die_with_patch () {
+	make_patch "$1"
+	die "$2"
+}
+
+die_abort () {
+	rm -rf "$DOTEST"
+	die "$1"
+}
+
+pick_one () {
+	case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
+	git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
+	test -d "$REWRITTEN" &&
+		pick_one_preserving_merges "$@" && return
+	parent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null)
+	current_sha1=$(git rev-parse --verify HEAD)
+	if [ $current_sha1 = $parent_sha1 ]; then
+		git reset --hard $sha1
+		sha1=$(git rev-parse --short $sha1)
+		warn Fast forward to $sha1
+	else
+		git cherry-pick $STRATEGY "$@"
+	fi
+}
+
+pick_one_preserving_merges () {
+	case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
+	sha1=$(git rev-parse $sha1)
+
+	if [ -f "$DOTEST"/current-commit ]
+	then
+		current_commit=$(cat "$DOTEST"/current-commit) &&
+		git rev-parse HEAD > "$REWRITTEN"/$current_commit &&
+		rm "$DOTEST"/current-commit ||
+		die "Cannot write current commit's replacement sha1"
+	fi
+
+	# rewrite parents; if none were rewritten, we can fast-forward.
+	fast_forward=t
+	preserve=t
+	new_parents=
+	for p in $(git rev-list --parents -1 $sha1 | cut -d\  -f2-)
+	do
+		if [ -f "$REWRITTEN"/$p ]
+		then
+			preserve=f
+			new_p=$(cat "$REWRITTEN"/$p)
+			test $p != $new_p && fast_forward=f
+			case "$new_parents" in
+			*$new_p*)
+				;; # do nothing; that parent is already there
+			*)
+				new_parents="$new_parents $new_p"
+			esac
+		fi
+	done
+	case $fast_forward in
+	t)
+		echo "Fast forward to $sha1"
+		test $preserve=f && echo $sha1 > "$REWRITTEN"/$sha1
+		;;
+	f)
+		test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
+
+		first_parent=$(expr "$new_parents" : " \([^ ]*\)")
+		# detach HEAD to current parent
+		git checkout $first_parent 2> /dev/null ||
+			die "Cannot move HEAD to $first_parent"
+
+		echo $sha1 > "$DOTEST"/current-commit
+		case "$new_parents" in
+		\ *\ *)
+			# redo merge
+			author_script=$(get_author_ident_from_commit $sha1)
+			eval "$author_script"
+			msg="$(git cat-file commit $sha1 | \
+				sed -e '1,/^$/d' -e "s/[\"\\]/\\\\&/g")"
+			# NEEDSWORK: give rerere a chance
+			if ! git merge $STRATEGY -m "$msg" $new_parents
+			then
+				echo "$msg" > "$GIT_DIR"/MERGE_MSG
+				warn Error redoing merge $sha1
+				warn
+				warn After fixup, please use
+				die "$author_script git commit"
+			fi
+			;;
+		*)
+			git cherry-pick $STRATEGY "$@" ||
+				die_with_patch $sha1 "Could not pick $sha1"
+		esac
+	esac
+}
+
+do_next () {
+	read command sha1 rest < "$TODO"
+	case "$command" in
+	\#|'')
+		mark_action_done
+		continue
+		;;
+	pick)
+		comment_for_reflog pick
+
+		mark_action_done
+		pick_one $sha1 ||
+			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		;;
+	edit)
+		comment_for_reflog edit
+
+		mark_action_done
+		pick_one $sha1 ||
+			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		make_patch $sha1
+		warn
+		warn "You can amend the commit now, with"
+		warn
+		warn "	git commit --amend"
+		warn
+		exit 0
+		;;
+	squash)
+		comment_for_reflog squash
+
+		test -z "$(grep -ve '^$' -e '^#' < $DONE)" &&
+			die "Cannot 'squash' without a previous commit"
+
+		mark_action_done
+		failed=f
+		pick_one -n $sha1 || failed=t
+		MSG="$DOTEST"/message
+		echo "# This is a combination of two commits." > "$MSG"
+		echo "# The first commit's message is:" >> "$MSG"
+		echo >> "$MSG"
+		git cat-file commit HEAD | sed -e '1,/^$/d' >> "$MSG"
+		echo >> "$MSG"
+		echo "# And this is the 2nd commit message:" >> "$MSG"
+		echo >> "$MSG"
+		git cat-file commit $sha1 | sed -e '1,/^$/d' >> "$MSG"
+		git reset --soft HEAD^
+		author_script=$(get_author_ident_from_commit $sha1)
+		case $failed in
+		f)
+			# This is like --amend, but with a different message
+			eval "$author_script"
+			export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+			git commit -F "$MSG" -e
+			;;
+		t)
+			cp "$MSG" "$GIT_DIR"/MERGE_MSG
+			warn
+			warn "Could not apply $sha1... $rest"
+			warn "After you fixed that, commit the result with"
+			warn
+			warn "  $(echo $author_script | tr '\012' ' ') \\"
+			warn "	  git commit -F \"$GIT_DIR\"/MERGE_MSG -e"
+			die_with_patch $sha1 ""
+		esac
+		;;
+	*)
+		warn "Unknown command: $command $sha1 $rest"
+		die_with_patch $sha1 "Please fix this in the file $TODO."
+	esac
+	test -s "$TODO" && return
+
+	comment_for_reflog finish &&
+	HEADNAME=$(cat "$DOTEST"/head-name) &&
+	OLDHEAD=$(cat "$DOTEST"/head) &&
+	SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
+	if [ -d "$REWRITTEN" ]
+	then
+		test -f "$DOTEST"/current-commit &&
+			current_commit=$(cat "$DOTEST"/current-commit) &&
+			git rev-parse HEAD > "$REWRITTEN"/$current_commit
+		NEWHEAD=$(cat "$REWRITTEN"/$OLDHEAD)
+	else
+		NEWHEAD=$(git rev-parse HEAD)
+	fi &&
+	message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
+	git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD &&
+	git symbolic-ref HEAD $HEADNAME &&
+	rm -rf "$DOTEST" &&
+	warn "Successfully rebased and updated $HEADNAME."
+
+	exit
+}
+
+do_rest () {
+	while :
+	do
+		do_next
+	done
+	test -f "$DOTEST"/verbose &&
+		git diff --stat $(cat "$DOTEST"/head)..HEAD
+	exit
+}
+
+while case $# in 0) break ;; esac
+do
+	case "$1" in
+	--continue)
+		comment_for_reflog continue
+
+		test -d "$DOTEST" || die "No interactive rebase running"
+
+		require_clean_work_tree
+		do_rest
+		;;
+	--abort)
+		comment_for_reflog abort
+
+		test -d "$DOTEST" || die "No interactive rebase running"
+
+		HEADNAME=$(cat "$DOTEST"/head-name)
+		HEAD=$(cat "$DOTEST"/head)
+		git symbolic-ref HEAD $HEADNAME &&
+		git reset --hard $HEAD &&
+		rm -rf "$DOTEST"
+		exit
+		;;
+	--skip)
+		comment_for_reflog skip
+
+		test -d "$DOTEST" || die "No interactive rebase running"
+
+		git reset --hard && do_rest
+		;;
+	-s|--strategy)
+		shift
+		case "$#,$1" in
+		*,*=*)
+			STRATEGY="-s `expr "z$1" : 'z-[^=]*=\(.*\)'`" ;;
+		1,*)
+			usage ;;
+		*)
+			STRATEGY="-s $2"
+			shift ;;
+		esac
+		;;
+	--merge)
+		# we use merge anyway
+		;;
+	-C*)
+		die "Interactive rebase uses merge, so $1 does not make sense"
+		;;
+	-v|--verbose)
+		VERBOSE=t
+		;;
+	-p|--preserve-merges)
+		PRESERVE_MERGES=t
+		;;
+	-i|--interactive)
+		# yeah, we know
+		;;
+	''|-h)
+		usage
+		;;
+	*)
+		test -d "$DOTEST" &&
+			die "Interactive rebase already started"
+
+		git var GIT_COMMITTER_IDENT >/dev/null ||
+			die "You need to set your committer info first"
+
+		comment_for_reflog start
+
+		ONTO=
+		case "$1" in
+		--onto)
+			ONTO=$(git rev-parse --verify "$2") ||
+				die "Does not point to a valid commit: $2"
+			shift; shift
+			;;
+		esac
+
+		require_clean_work_tree
+
+		if [ ! -z "$2"]
+		then
+			git show-ref --verify --quiet "refs/heads/$2" ||
+				die "Invalid branchname: $2"
+			git checkout "$2" ||
+				die "Could not checkout $2"
+		fi
+
+		HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
+		UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
+
+		test -z "$ONTO" && ONTO=$UPSTREAM
+
+		mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
+		: > "$DOTEST"/interactive || die "Could not mark as interactive"
+		git symbolic-ref HEAD > "$DOTEST"/head-name ||
+			die "Could not get HEAD"
+
+		echo $HEAD > "$DOTEST"/head
+		echo $UPSTREAM > "$DOTEST"/upstream
+		echo $ONTO > "$DOTEST"/onto
+		test t = "$VERBOSE" && : > "$DOTEST"/verbose
+		if [ t = "$PRESERVE_MERGES" ]
+		then
+			# $REWRITTEN contains files for each commit that is
+			# reachable by at least one merge base of $HEAD and
+			# $UPSTREAM. They are not necessarily rewritten, but
+			# their children might be.
+			# This ensures that commits on merged, but otherwise
+			# unrelated side branches are left alone. (Think "X"
+			# in the man page's example.)
+			mkdir "$REWRITTEN" &&
+			for c in $(git merge-base --all $HEAD $UPSTREAM)
+			do
+				echo $ONTO > "$REWRITTEN"/$c ||
+					die "Could not init rewritten commits"
+			done
+			MERGES_OPTION=
+		else
+			MERGES_OPTION=--no-merges
+		fi
+
+		SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
+		SHORTHEAD=$(git rev-parse --short $HEAD)
+		SHORTONTO=$(git rev-parse --short $ONTO)
+		cat > "$TODO" << EOF
+# Rebasing $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
+#
+# Commands:
+#  pick = use commit
+#  edit = use commit, but stop for amending
+#  squash = use commit, but meld into previous commit
+EOF
+		git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
+			--abbrev=7 --reverse $UPSTREAM..$HEAD | \
+			sed "s/^/pick /" >> "$TODO"
+
+		test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
+			die_abort "Nothing to do"
+
+		cp "$TODO" "$TODO".backup
+		${VISUAL:-${EDITOR:-vi}} "$TODO" ||
+			die "Could not execute editor"
+
+		test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
+			die_abort "Nothing to do"
+
+		git checkout $ONTO && do_rest
+	esac
+	shift
+done

File git-rebase.sh

 # Copyright (c) 2005 Junio C Hamano.
 #
 
-USAGE='[-v] [--onto <newbase>] <upstream> [<branch>]'
+USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]'
 LONG_USAGE='git-rebase replaces <branch> with a new branch of the
 same name.  When the --onto option is provided the new branch starts
 out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
 	echo "All done."
 }
 
+is_interactive () {
+	test -f "$dotest"/interactive ||
+	while case $#,"$1" in 0,|*,-i|*,--interactive) break ;; esac
+	do
+		shift
+	done && test -n "$1"
+}
+
+is_interactive "$@" && exec git-rebase--interactive "$@"
+
 while case "$#" in 0) break ;; esac
 do
 	case "$1" in

File git-sh-setup.sh

 	die "fatal: $0 cannot be used without a working tree."
 }
 
+get_author_ident_from_commit () {
+	pick_author_script='
+	/^author /{
+		s/'\''/'\''\\'\'\''/g
+		h
+		s/^author \([^<]*\) <[^>]*> .*$/\1/
+		s/'\''/'\''\'\'\''/g
+		s/.*/GIT_AUTHOR_NAME='\''&'\''/p
+
+		g
+		s/^author [^<]* <\([^>]*\)> .*$/\1/
+		s/'\''/'\''\'\'\''/g
+		s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
+
+		g
+		s/^author [^<]* <[^>]*> \(.*\)$/\1/
+		s/'\''/'\''\'\'\''/g
+		s/.*/GIT_AUTHOR_DATE='\''&'\''/p
+
+		q
+	}
+	'
+	encoding=$(git config i18n.commitencoding || echo UTF-8)
+	git show -s --pretty=raw --encoding="$encoding" "$1" |
+	LANG=C LC_ALL=C sed -ne "$pick_author_script"
+}
+
 if [ -z "$LONG_USAGE" ]
 then
 	LONG_USAGE="Usage: $0 $USAGE"

File t/t3404-rebase-interactive.sh

+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='git rebase interactive
+
+This test runs git rebase "interactively", by faking an edit, and verifies
+that the result still makes sense.
+'
+. ./test-lib.sh
+
+# set up two branches like this:
+#
+# A - B - C - D - E
+#   \
+#     F - G - H
+#       \
+#         I
+#
+# where B, D and G touch the same file.
+
+test_expect_success 'setup' '
+	: > file1 &&
+	git add file1 &&
+	test_tick &&
+	git commit -m A &&
+	git tag A &&
+	echo 1 > file1 &&
+	test_tick &&
+	git commit -m B file1 &&
+	: > file2 &&
+	git add file2 &&
+	test_tick &&
+	git commit -m C &&
+	echo 2 > file1 &&
+	test_tick &&
+	git commit -m D file1 &&
+	: > file3 &&
+	git add file3 &&
+	test_tick &&
+	git commit -m E &&
+	git checkout -b branch1 A &&
+	: > file4 &&
+	git add file4 &&
+	test_tick &&
+	git commit -m F &&
+	git tag F &&
+	echo 3 > file1 &&
+	test_tick &&
+	git commit -m G file1 &&
+	: > file5 &&
+	git add file5 &&
+	test_tick &&
+	git commit -m H &&
+	git checkout -b branch2 F &&
+	: > file6 &&
+	git add file6 &&
+	test_tick &&
+	git commit -m I &&
+	git tag I
+'
+
+cat > fake-editor.sh << EOF
+#!/bin/sh
+test "\$1" = .git/COMMIT_EDITMSG && exit
+test -z "\$FAKE_LINES" && exit
+grep -v "^#" < "\$1" > "\$1".tmp
+rm "\$1"
+cat "\$1".tmp
+action=pick
+for line in \$FAKE_LINES; do
+	case \$line in
+	squash)
+		action="\$line";;
+	*)
+		echo sed -n "\${line}s/^pick/\$action/p"
+		sed -n "\${line}p" < "\$1".tmp
+		sed -n "\${line}s/^pick/\$action/p" < "\$1".tmp >> "\$1"
+		action=pick;;
+	esac
+done
+EOF
+
+chmod a+x fake-editor.sh
+VISUAL="$(pwd)/fake-editor.sh"
+export VISUAL
+
+test_expect_success 'no changes are a nop' '
+	git rebase -i F &&
+	test $(git rev-parse I) = $(git rev-parse HEAD)
+'
+
+test_expect_success 'rebase on top of a non-conflicting commit' '
+	git checkout branch1 &&
+	git tag original-branch1 &&
+	git rebase -i branch2 &&
+	test file6 = $(git diff --name-only original-branch1) &&
+	test $(git rev-parse I) = $(git rev-parse HEAD~2)
+'
+
+test_expect_success 'reflog for the branch shows state before rebase' '
+	test $(git rev-parse branch1@{1}) = $(git rev-parse original-branch1)
+'
+
+test_expect_success 'exchange two commits' '
+	FAKE_LINES="2 1" git rebase -i HEAD~2 &&
+	test H = $(git cat-file commit HEAD^ | tail -n 1) &&
+	test G = $(git cat-file commit HEAD | tail -n 1)
+'
+
+cat > expect << EOF
+diff --git a/file1 b/file1
+index e69de29..00750ed 100644
+--- a/file1
++++ b/file1
+@@ -0,0 +1 @@
++3
+EOF
+
+cat > expect2 << EOF
+<<<<<<< HEAD:file1
+2
+=======
+3
+>>>>>>> b7ca976... G:file1
+EOF
+
+test_expect_success 'stop on conflicting pick' '
+	git tag new-branch1 &&
+	! git rebase -i master &&
+	diff -u expect .git/.dotest-merge/patch &&
+	diff -u expect2 file1 &&
+	test 4 = $(grep -v "^#" < .git/.dotest-merge/done | wc -l) &&
+	test 0 = $(grep -v "^#" < .git/.dotest-merge/todo | wc -l)
+'
+
+test_expect_success 'abort' '
+	git rebase --abort &&
+	test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
+	! test -d .git/.dotest-merge
+'
+
+test_expect_success 'retain authorship' '
+	echo A > file7 &&
+	git add file7 &&
+	test_tick &&
+	GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" &&
+	git tag twerp &&
+	git rebase -i --onto master HEAD^ &&
+	git show HEAD | grep "^Author: Twerp Snog"
+'
+
+test_expect_success 'squash' '
+	git reset --hard twerp &&
+	echo B > file7 &&
+	test_tick &&
+	GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 &&
+	echo "******************************" &&
+	FAKE_LINES="1 squash 2" git rebase -i --onto master HEAD~2 &&
+	test B = $(cat file7) &&
+	test $(git rev-parse HEAD^) = $(git rev-parse master)
+'
+
+test_expect_success 'retain authorship when squashing' '
+	git show HEAD | grep "^Author: Nitfol"
+'
+
+test_expect_success 'preserve merges with -p' '
+	git checkout -b to-be-preserved master^ &&
+	: > unrelated-file &&
+	git add unrelated-file &&
+	test_tick &&
+	git commit -m "unrelated" &&
+	git checkout -b to-be-rebased master &&
+	echo B > file1 &&
+	test_tick &&
+	git commit -m J file1 &&
+	test_tick &&
+	git merge to-be-preserved &&
+	echo C > file1 &&
+	test_tick &&
+	git commit -m K file1 &&
+	git rebase -i -p --onto branch1 master &&
+	test $(git rev-parse HEAD^^2) = $(git rev-parse to-be-preserved) &&
+	test $(git rev-parse HEAD~3) = $(git rev-parse branch1) &&
+	test $(git show HEAD:file1) = C &&
+	test $(git show HEAD~2:file1) = B
+'
+
+test_done