Re: [PATCH 2/2] Teach rebase an interactive mode

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

Re: [PATCH 2/2] Teach rebase an interactive mode

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:43:18

Johannes Schindelin [off-list ref] writes:
quoted hunk
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 0c00090..2e474e8 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
...
+Interactive mode
+----------------
+
+Rebasing interactively means that you have a chance to edit the commits
+which are rebased.  You can not only reorder the commits, but also
+remove them (weeding out bad or otherwise unwanted patches).
Maybe it's just my bad English comprehension skill, but I needed
to read "You can not only ... but also" twice.  "You can reorder
and also remove" would mean the same thing and would be much
easier for non natives to understand.
+
+The list will look 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, so do not delete or edit the
+names.
By "commit name", do you mean deadbee?  After reading the full
patch I know that is what you meant, but it was a bit unclear
during my initial pass.
+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 commits into one, just replace the command "pick"
+with "squash" for the second commit.  After squashing the commits,
+`git-rebase` will start an editor with both commit messages, so you
+can compose the commit message for the squashed commit.
There was a question about squashing more than two on the list,
and you explained it as "one would get the idea", but I am not
sure I got it right.  Would this be what you meant?

	If you want to fold two or more commits into one,
	replace the command "pick" with "squash" for the second
	and subsequent commit.
quoted hunk
diff --git a/Makefile b/Makefile
index 0d904a9..edb421b 100644
--- a/Makefile
+++ b/Makefile
...
+# MOTIVATION
+#
+# Consider this type of workflow:
...
+# 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.
+#
+# Use this script after plenty of "A"s and "B"s, by rearranging, and
+# possibly editing and merging commits.
This part is missing from Documentation/git-rebase.txt and
should move there, and the USAGE: part of the documentation
should be removed from here; otherwise you need to maintain the
two in sync.
+USAGE='(--continue | --abort | --skip | [--onto <branch>] <upstream> [<branch>])'
+
+. git-sh-setup
+require_work_tree
+
+DOTEST="$GIT_DIR/.dotest-merge"
+TODO="$DOTEST"/todo
+DONE="$DOTEST"/done
+STRATEGY=
+VERBOSE=
+
+warn () {
+	echo "$@" >&2
+}
I would have used "$*" instead.
+require_clean_work_tree () {
+	# test if working tree is dirty
+	git rev-parse --verify HEAD > /dev/null &&
+	git update-index --refresh &&
+	test -z "`git diff-files --name-only`" &&
+	test -z "`git diff-index --cached --name-only HEAD`" ||
+	die "Working tree is dirty"
+}
Heh, I think I showed

	git diff-files --quiet && git diff-index --cached --quiet

to somebody else today.  Let's be modern ;-).

Is today my "nitpick shell scripts day"? ;-)
+ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
+
+comment_for_reflog () {
+	if test -z "$ORIG_REFLOG_ACTION"; then
+		GIT_REFLOG_ACTION="rebase --interactive ($1)"
+		export GIT_REFLOG_ACTION
Please use shorter prefix, "rebase -i".  "git reflog" output is
easer to read that way.
+	fi
+}
+
+mark_action_done () {
+	sed -n 1p < "$TODO" >> "$DONE"
+	sed -n '2,$p' < "$TODO" >> "$TODO".new
+	mv -f "$TODO".new "$TODO"
+}
I would have written "sed -e 1q" and "sed -e 1d".
+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"
+}
+
+pick_one () {
+	case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
+	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
+}
I wonder what happens when the user mistakenly breaks the commit
object name on the 'pick' command line and the above --verify
fails.  A bit better error checking is needed here.
+
+do_next () {
+	read command sha1 rest < "$TODO"
+	case "$command" in
+	\#)
Hmph.  Don't you allow a blank line also as a comment (for readability)?
+		mark_action_done
+		continue
+	;;
Style.  Indent ';;' one more level, that is:

	case "$to_test" in
        arm1)
        	action
                ;;
	...
        esac                
+	squash)
+		comment_for_reflog squash
+
+		test -s "$DONE" ||
+			die "Cannot 'squash' without a previous commit"
As '#' comment line is sent to DONE file with mark_action_done,
non empty DONE file does not necessarily mean you have a
previous commit --- am I mistaken?
+
+		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"
+		git cat-file commit HEAD | sed -n '/^$/,$p' >> "$MSG"
+		echo >> "$MSG"
+		echo "# And this is the 2nd commit message:" >> "$MSG"
+		echo >> "$MSG"
+		git cat-file commit $sha1 | sed -n '/^$/,$p' >> "$MSG"
So you have one blank line after "# The first commit's message is:"
but have two blank lines after "# And this is the 2nd"?

Style.  Always prefix sed scripts with "-e", like so:

	git cat-file commit HEAD | sed -e '1,/^$/d'
+		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
Missing dq, like the other patch to git-commit?
+			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" && continue
It is not clear to me from reading the POSIX that this
"continue" is allowed, although both bash and dash seem to work
as you expect.  continue "shall return to the top of the
smallest enclosing for, while or until loop" and the enclosing
while loop you are continuing is actually the one in do_rest
function, which is the caller of this.  If POSIX meant dynamic
scoping rules, that is fine, but lexically that 'while' does not
enclose this 'continue'.

I think this one can safely be changed to "return" and there
won't be any need for such an worry.
+
+	HEAD=$(git rev-parse HEAD)
+	HEADNAME=$(cat "$DOTEST"/head-name)
+	git update-ref $HEADNAME $HEAD &&
+	git symbolic-ref HEAD $HEADNAME || exit
Don't we want reflog entries for these two operations?
+	rm -rf "$DOTEST" &&
+	warn "Successfully rebased and updated $HEADNAME."
+
+	exit $?
+}
+
+do_rest () {
+	while :
+	do
+		do_next
+	done

+	test $? = 0 -a -f "$DOTEST"/verbose &&
+		git diff --stat $(cat "$DOTEST"/head)..HEAD
I am not sure what command's exit status $? refers to at this
point.  do_next is not run in a subshell, so when it exits, you
would not reach here, would you?
+	exit
+}
One says "exit $?" the other says "exit" -- they mean the same
;-).
+while case "$#" in 0) break ;; esac
+do
No need to quote $#.
+	case "$1" in
+	--continue)
+		comment_for_reflog continue
+
+		test -d "$DOTEST" || die "No interactive rebase running"
+
+		require_clean_work_tree
+		do_rest
+	;;
Indent ;; one more level please.
+	--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)
+		case "$#,$1" in
+		*,*=*)
+			STRATEGY="-s `expr "z$1" : 'z-[^=]*=\(.*\)'`" ;;
+		1,*)
+			usage ;;
+		*)
+			STRATEGY="-s $2"
+			shift ;;
+		esac
Are we missing a shift before the above "case"?
+	;;
+	--merge)
+		# we use merge anyway
+	;;
+	-C*)
+		die "Interactive rebase uses merge, so $1 does not make sense"
+	;;
+	-v)
+		VERBOSE=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
+
+		test -z "$2" || git checkout "$2" ||
+			die "Could not checkout $2"
Can you afford to detach HEAD here?  Later you check with
symbolic-ref so I think not, which means "$2" must be a valid
branch name, so it should be tested like:

	git show-ref --verify --quiet "refs/heads/$2"
+		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
+
+		cat > "$TODO" << EOF
+# Reorder by exchanging lines.  Skip by removing lines.  If you want to
+# edit a commit, replace the "pick" command with "edit".  If you want to
+# squash the changes of a commit A into a commit B, place A directly
+# after B, and replace the "pick" command with "squash".
+EOF
+		git rev-list --no-merges --pretty=oneline --abbrev-commit \
+			--abbrev=7 --reverse $UPSTREAM..$HEAD | \
+			sed "s/^/pick /" >> "$TODO"
+
+		test -s "$TODO" || die "Nothing to do"
+
+		cp "$TODO" "$TODO".backup
+		${VISUAL:-${EDITOR:-vi}} "$TODO" ||
+			die "Could not execute editor"
+
+		git reset --hard $ONTO && do_rest
+	;;
+	esac
+	shift
+done
quoted hunk
diff --git a/git-rebase.sh b/git-rebase.sh
index 2aa3a01..9e25158 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -3,7 +3,7 @@
 # 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>
@@ -120,6 +120,16 @@ finish_rb_merge () {
 	echo "All done."
 }
 
+is_interactive () {
+	test -f "$dotest"/interactive ||
+	while case "$1" in ''|-i|--interactive) break ;; esac
+	do
+		shift
+	done && test -n "$1"
+}
I think by case "$1" in '') you meant "we ran out", but that is
not a good pattern.  Check $# at the same time, otherwise you
would stop at an empty argument that is followed by other
arguments.

This is just an idea, but I have been wondering if it would be
useful if we teach rebase (interactive or not) to handle a merge
from an unrelated (wrt the rebase that is being performed)
branch.  That is, if you had this development on top of the
origin 'O':

             X
              \  
           A---M---B     
          /
  ---o---O

that you committed A, merged X and then committed B, you should
be able to rebase on top of an updated upstream 'Q':

             X
              \  
           A---M---B     
          /
  ---o---O---P---Q

by 'pick A/merge M/pick B', which would do:

                     X
                      \
                   A'--M'--B'
                  /
  ---o---O---P---Q

Note that A', M' and B' are different commit objects (rebase
rewrites the history) from the original picture, but X is the
same commit from the original picture.

Re: [PATCH 2/2] Teach rebase an interactive mode

From: Johannes Schindelin <hidden>
Date: 2016-06-15 22:43:18

Hi,

thanks to both of you, for your comments. I am a little pressed on time, 
so I will only be able to fix all this up this evening, but I thought I 
let you know what the state is.

I am not answering point by point, because I agree with all of the nits.

The four missing points are:

- fix up the commit message so that it is clear from where the list of 
  commits comes,
- put a sensible reflog message at the last update,
- move the usage section into the man page, and
- fix the quoting in patch 1/2.

Will resend (after using rebase -i myself, yeah!) tonight.

---
 Documentation/git-rebase.txt |   12 ++---
 git-rebase--interactive.sh   |   90 ++++++++++++++++++++++++------------------
 2 files changed, 56 insertions(+), 46 deletions(-)
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 2e474e8..e96d72e 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -235,7 +235,7 @@ Interactive mode
 ----------------
 
 Rebasing interactively means that you have a chance to edit the commits
-which are rebased.  You can not only reorder the commits, but also
+which are rebased.  You can reorder the commits, and you can
 remove them (weeding out bad or otherwise unwanted patches).
 
 The list will look like this:
@@ -247,18 +247,16 @@ 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, so do not delete or edit the
-names.
+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 commits into one, just replace the command "pick"
-with "squash" for the second commit.  After squashing the commits,
-`git-rebase` will start an editor with both commit messages, so you
-can compose the commit message for the squashed commit.
+If you want to fold two or more commits into one, replace the command
+"pick" with "squash" for the second and subsequent commit.
 
 A common use case for the interactive mode is when you want to reorder
 the last 5 commits, such that what was HEAD~4 becomes the new HEAD. To
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 6a960b4..1d1e927 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -78,30 +78,31 @@ STRATEGY=
 VERBOSE=
 
 warn () {
-	echo "$@" >&2
+	echo "$*" >&2
 }
 
 require_clean_work_tree () {
 	# test if working tree is dirty
 	git rev-parse --verify HEAD > /dev/null &&
 	git update-index --refresh &&
-	test -z "`git diff-files --name-only`" &&
-	test -z "`git diff-index --cached --name-only HEAD`" ||
+	git diff-files --quiet &&
+	git diff-index --cached --quiet HEAD ||
 	die "Working tree is dirty"
 }
 
 ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
 
 comment_for_reflog () {
-	if test -z "$ORIG_REFLOG_ACTION"; then
-		GIT_REFLOG_ACTION="rebase --interactive ($1)"
+	case "$ORIG_REFLOG_ACTION" in
+	''|rebase*)
+		GIT_REFLOG_ACTION="rebase -i ($1)"
 		export GIT_REFLOG_ACTION
-	fi
+	esac
 }
 
 mark_action_done () {
-	sed -n 1p < "$TODO" >> "$DONE"
-	sed -n '2,$p' < "$TODO" >> "$TODO".new
+	sed -e 1q < "$TODO" >> "$DONE"
+	sed -e 1d < "$TODO" >> "$TODO".new
 	mv -f "$TODO".new "$TODO"
 }
 
@@ -117,6 +118,7 @@ die_with_patch () {
 
 pick_one () {
 	case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
+	git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
 	parent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null)
 	current_sha1=$(git rev-parse --verify HEAD)
 	if [ $current_sha1 = $parent_sha1 ]; then
@@ -131,17 +133,17 @@ pick_one () {
 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
 
@@ -155,11 +157,11 @@ do_next () {
 		warn "	git commit --amend"
 		warn
 		exit 0
-	;;
+		;;
 	squash)
 		comment_for_reflog squash
 
-		test -s "$DONE" ||
+		test -z "$(grep -ve '^$' -e '^#' < $DONE)" &&
 			die "Cannot 'squash' without a previous commit"
 
 		mark_action_done
@@ -168,20 +170,21 @@ do_next () {
 		MSG="$DOTEST"/message
 		echo "# This is a combination of two commits." > "$MSG"
 		echo "# The first commit's message is:" >> "$MSG"
-		git cat-file commit HEAD | sed -n '/^$/,$p' >> "$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 -n '/^$/,$p' >> "$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
+			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
@@ -192,21 +195,22 @@ do_next () {
 			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" && continue
+	test -s "$TODO" && return
 
 	HEAD=$(git rev-parse HEAD)
 	HEADNAME=$(cat "$DOTEST"/head-name)
+# TODO: reflog?
 	git update-ref $HEADNAME $HEAD &&
 	git symbolic-ref HEAD $HEADNAME || exit
 	rm -rf "$DOTEST" &&
 	warn "Successfully rebased and updated $HEADNAME."
 
-	exit $?
+	exit
 }
 
 do_rest () {
@@ -214,12 +218,12 @@ do_rest () {
 	do
 		do_next
 	done
-	test $? = 0 -a -f "$DOTEST"/verbose &&
+	test -f "$DOTEST"/verbose &&
 		git diff --stat $(cat "$DOTEST"/head)..HEAD
 	exit
 }
 
-while case "$#" in 0) break ;; esac
+while case $# in 0) break ;; esac
 do
 	case "$1" in
 	--continue)
@@ -229,7 +233,7 @@ do
 
 		require_clean_work_tree
 		do_rest
-	;;
+		;;
 	--abort)
 		comment_for_reflog abort
 
@@ -241,15 +245,16 @@ do
 		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-[^=]*=\(.*\)'`" ;;
@@ -259,22 +264,22 @@ do
 			STRATEGY="-s $2"
 			shift ;;
 		esac
-	;;
+		;;
 	--merge)
 		# we use merge anyway
-	;;
+		;;
 	-C*)
 		die "Interactive rebase uses merge, so $1 does not make sense"
-	;;
+		;;
 	-v)
 		VERBOSE=t
-	;;
+		;;
 	-i|--interactive)
 		# yeah, we know
-	;;
+		;;
 	''|-h)
 		usage
-	;;
+		;;
 	*)
 		test -d "$DOTEST" &&
 			die "Interactive rebase already started"
@@ -295,8 +300,13 @@ do
 
 		require_clean_work_tree
 
-		test -z "$2" || git checkout "$2" ||
-			die "Could not checkout $2"
+		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"
@@ -314,23 +324,25 @@ do
 		test t = "$VERBOSE" && : > "$DOTEST"/verbose
 
 		cat > "$TODO" << EOF
-# Reorder by exchanging lines.  Skip by removing lines.  If you want to
-# edit a commit, replace the "pick" command with "edit".  If you want to
-# squash the changes of a commit A into a commit B, place A directly
-# after B, and replace the "pick" command with "squash".
+# Rebasing $UPSTREAM..$HEAD onto $ONTO
+#
+# Commands:
+#  pick = use commit
+#  edit = use commit, but stop for amending
+#  squash = use commit, but meld into previous commit
 EOF
 		git rev-list --no-merges --pretty=oneline --abbrev-commit \
 			--abbrev=7 --reverse $UPSTREAM..$HEAD | \
 			sed "s/^/pick /" >> "$TODO"
 
-		test -s "$TODO" || die "Nothing to do"
+		test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
+			die "Nothing to do"
 
 		cp "$TODO" "$TODO".backup
 		${VISUAL:-${EDITOR:-vi}} "$TODO" ||
 			die "Could not execute editor"
 
 		git reset --hard $ONTO && do_rest
-	;;
 	esac
 	shift
 done
-- 
1.5.2.2.279.g9b198-dirty

Re: [PATCH 2/2] Teach rebase an interactive mode

From: Johannes Schindelin <hidden>
Date: 2016-06-15 22:43:18

Hi,

On Sun, 24 Jun 2007, Junio C Hamano wrote:
This is just an idea, but I have been wondering if it would be useful if 
we teach rebase (interactive or not) to handle a merge from an unrelated 
(wrt the rebase that is being performed) branch.  That is, if you had 
this development on top of the origin 'O':

             X
              \  
           A---M---B     
          /
  ---o---O

that you committed A, merged X and then committed B, you should
be able to rebase on top of an updated upstream 'Q':

             X
              \  
           A---M---B     
          /
  ---o---O---P---Q

by 'pick A/merge M/pick B', which would do:

                     X
                      \
                   A'--M'--B'
                  /
  ---o---O---P---Q

Note that A', M' and B' are different commit objects (rebase
rewrites the history) from the original picture, but X is the
same commit from the original picture.
Yes, I think that would be useful. But at the same time, I think it should 
not be the default. Why? Because I will use rebase to beautify my history, 
and that looks mostly like this:
--- A - B - C - D - E - junio/master
      \       \   \
        M - N - O - P - my
So what I want is really clean up my side branch.

Therefore I would like to have this --preserve-merges (short option -p?) 
_after_ rebase -i.

It will get a little tricky with different merge strategies, and saying 
which commits have to be rebased (A in your example), and which will be 
preserved (X in your example), and evil merges (merges which have fix ups 
in addition to the merge), but doable.

Ciao,
Dscho

[PATCH 1/2] Move the pick_author code to git-sh-setup

From: Johannes Schindelin <hidden>
Date: 2016-06-15 22:43:18

At the moment, only git-commit uses that code, to pick the author name,
email and date from a given commit.

This code will be reused in git rebase --interactive.

Signed-off-by: Johannes Schindelin <redacted>
---

	Just the eval was changed, by dq'ing its argument.

 git-commit.sh   |   30 ++----------------------------
 git-sh-setup.sh |   27 +++++++++++++++++++++++++++
 2 files changed, 29 insertions(+), 28 deletions(-)
diff --git a/git-commit.sh b/git-commit.sh
index 5547a02..d43bdd8 100755
--- a/git-commit.sh
+++ b/git-commit.sh
@@ -483,34 +483,8 @@ fi >>"$GIT_DIR"/COMMIT_EDITMSG
 # 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
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index f24c7f2..d861db3 100755
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -53,6 +53,33 @@ require_work_tree () {
 	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"
-- 
1.5.2.2.279.g9b198-dirty

[PATCH 2/2] Teach rebase an interactive mode

From: Johannes Schindelin <hidden>
Date: 2016-06-15 22:43:18

Don't you just hate the fact sometimes, that git-rebase just applies
the patches, without any possibility to edit them, or rearrange them?
With "--interactive", git-rebase now lets you edit the list of patches,
so that you can reorder, edit and delete patches.

Such a list will typically look like this:

	pick deadbee The oneline of this commit
	pick fa1afe1 The oneline of the next commit
	...

By replacing the command "pick" with the command "edit", you can amend
that patch and/or its commit message, and by replacing it with "squash"
you can tell rebase to fold that patch into the patch before that.

It is derived from the script sent to the list in
[off-list ref]

Signed-off-by: Johannes Schindelin <redacted>
---

	Junio wrote:

	> Is today my "nitpick shell scripts day"? ;-)

	Seems so ;-)

	In addition to your comments, I actually fixed the reflog
	entries, since they picked up the comment from rebase
	(without -i).

	Also, the last updating step was not necessary at all, since
	rebase -i works on the same branch all the time.

	Furthermore, your comment that a check for an empty $DONE file
	is not sufficient applies as well for the check that $TODO
	contains some actions. Both are fixed.

	The continue without a loop in that function was (as you probably
	suspected) a refactoring leftover.

	All other suggestions were included as-are.

 Documentation/git-rebase.txt  |   85 ++++++++++++-
 Makefile                      |    2 +-
 git-rebase--interactive.sh    |  289 +++++++++++++++++++++++++++++++++++++++++
 git-rebase.sh                 |   12 ++-
 t/t3404-rebase-interactive.sh |  163 +++++++++++++++++++++++
 5 files changed, 546 insertions(+), 5 deletions(-)
 create mode 100755 git-rebase--interactive.sh
 create mode 100755 t/t3404-rebase-interactive.sh
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 0c00090..2e3363a 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -8,7 +8,8 @@ git-rebase - Forward-port local commits to the updated upstream head
 SYNOPSIS
 --------
 [verse]
-'git-rebase' [-v] [--merge] [-C<n>] [--onto <newbase>] <upstream> [<branch>]
+'git-rebase' [-i | --interactive] [-v | --verbose] [--merge] [-C<n>]
+	[--onto <newbase>] <upstream> [<branch>]
 'git-rebase' --continue | --skip | --abort
 
 DESCRIPTION
@@ -208,6 +209,10 @@ OPTIONS
 	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.
+
 include::merge-strategies.txt[]
 
 NOTES
@@ -226,9 +231,83 @@ pre-rebase hook script for an example.
 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.
+
+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
 --------------
diff --git a/Makefile b/Makefile
index a98e27a..4ea5e45 100644
--- a/Makefile
+++ b/Makefile
@@ -204,7 +204,7 @@ SCRIPT_SH = \
 	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 \
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
new file mode 100755
index 0000000..ab36572
--- /dev/null
+++ b/git-rebase--interactive.sh
@@ -0,0 +1,289 @@
+#!/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 | [--onto <branch>] <upstream> [<branch>])'
+
+. git-sh-setup
+require_work_tree
+
+DOTEST="$GIT_DIR/.dotest-merge"
+TODO="$DOTEST"/todo
+DONE="$DOTEST"/done
+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"
+}
+
+pick_one () {
+	case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
+	git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
+	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
+}
+
+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
+
+	HEAD=$(git rev-parse HEAD)
+	HEADNAME=$(cat "$DOTEST"/head-name)
+	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=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
+
+		cat > "$TODO" << EOF
+# Rebasing $UPSTREAM..$HEAD onto $ONTO
+#
+# Commands:
+#  pick = use commit
+#  edit = use commit, but stop for amending
+#  squash = use commit, but meld into previous commit
+EOF
+		git rev-list --no-merges --pretty=oneline --abbrev-commit \
+			--abbrev=7 --reverse $UPSTREAM..$HEAD | \
+			sed "s/^/pick /" >> "$TODO"
+
+		test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
+			die "Nothing to do"
+
+		cp "$TODO" "$TODO".backup
+		${VISUAL:-${EDITOR:-vi}} "$TODO" ||
+			die "Could not execute editor"
+
+		git reset --hard $ONTO && do_rest
+	esac
+	shift
+done
diff --git a/git-rebase.sh b/git-rebase.sh
index 2aa3a01..3887526 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -3,7 +3,7 @@
 # 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>
@@ -120,6 +120,16 @@ finish_rb_merge () {
 	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
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
new file mode 100755
index 0000000..48aa8ea
--- /dev/null
+++ b/t/t3404-rebase-interactive.sh
@@ -0,0 +1,163 @@
+#!/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 '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 &&
+	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 &&
+	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_done
-- 
1.5.2.2.279.g9b198-dirty
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help