git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/3] Add git-sequencer shell prototype
@ 2008-07-16 20:45 Stephan Beyer
  2008-07-16 20:45 ` [PATCH 2/3] Add git-sequencer documentation Stephan Beyer
  0 siblings, 1 reply; 8+ messages in thread
From: Stephan Beyer @ 2008-07-16 20:45 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Daniel Barkalow, Christian Couder, Junio C Hamano

git-sequencer is planned as a backend for user scripts
that execute a sequence of git instructions and perhaps
need manual intervention, for example git-rebase or git-am.

Mentored-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 .gitignore       |    1 +
 Makefile         |    1 +
 command-list.txt |    1 +
 git-sequencer.sh | 2064 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 2067 insertions(+), 0 deletions(-)
 create mode 100755 git-sequencer.sh

diff --git a/.gitignore b/.gitignore
index a213e8e..a617039 100644
--- a/.gitignore
+++ b/.gitignore
@@ -110,6 +110,7 @@ git-revert
 git-rm
 git-send-email
 git-send-pack
+git-sequencer
 git-sh-setup
 git-shell
 git-shortlog
diff --git a/Makefile b/Makefile
index 9b52071..391a4ac 100644
--- a/Makefile
+++ b/Makefile
@@ -248,6 +248,7 @@ SCRIPT_SH += git-rebase--interactive.sh
 SCRIPT_SH += git-rebase.sh
 SCRIPT_SH += git-repack.sh
 SCRIPT_SH += git-request-pull.sh
+SCRIPT_SH += git-sequencer.sh
 SCRIPT_SH += git-sh-setup.sh
 SCRIPT_SH += git-stash.sh
 SCRIPT_SH += git-submodule.sh
diff --git a/command-list.txt b/command-list.txt
index 3583a33..44bb5b0 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -101,6 +101,7 @@ git-rev-parse                           ancillaryinterrogators
 git-rm                                  mainporcelain common
 git-send-email                          foreignscminterface
 git-send-pack                           synchingrepositories
+git-sequencer                           plumbingmanipulators
 git-shell                               synchelpers
 git-shortlog                            mainporcelain
 git-show                                mainporcelain common
diff --git a/git-sequencer.sh b/git-sequencer.sh
new file mode 100755
index 0000000..6c406dc
--- /dev/null
+++ b/git-sequencer.sh
@@ -0,0 +1,2064 @@
+#!/bin/sh
+# A git sequencer prototype.
+
+SUBDIRECTORY_OK=Yes
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git sequencer [options] [--] [<file>]
+git sequencer (--continue | --abort | --skip | --edit | --status)
+--
+ Options to start a sequencing process
+B,batch        run in batch-mode
+onto=          checkout the given commit or branch first
+no-advice      suppress advice when pausing (conflicts, etc)
+q,quiet        suppress output
+v,verbose      be more verbose
+ Options to restart/change a sequencing process or show information
+continue       continue paused sequencer process
+abort          restore original branch and abort
+skip           skip current patch and continue
+status         show the status of the sequencing process
+edit           invoke editor to let user edit the remaining insns
+ Options to be used by user scripts
+caller=        provide information string: name|abort|cont|skip
+"
+
+. git-sh-setup
+require_work_tree
+
+git var GIT_COMMITTER_IDENT >/dev/null ||
+	die 'You need to set your committer info first'
+
+SEQ_DIR="$GIT_DIR/sequencer"
+TODO="$SEQ_DIR/todo"
+DONE="$SEQ_DIR/done"
+MSG="$SEQ_DIR/message"
+PATCH="$SEQ_DIR/patch"
+AUTHOR_SCRIPT="$SEQ_DIR/author-script"
+ORIG_AUTHOR_SCRIPT="$SEQ_DIR/author-script.orig"
+CALLER_SCRIPT="$SEQ_DIR/caller-script"
+WHY_FILE="$SEQ_DIR/why"
+MARK_PREFIX='refs/sequencer-marks'
+
+warn () {
+	echo "$*" >&2
+}
+
+cleanup () {
+	for ref in $(git for-each-ref --format='%(refname)' "$MARK_PREFIX")
+	do
+		git update-ref -d "$ref" "$ref" || return
+	done
+	rm -rf "$SEQ_DIR"
+}
+
+print_advice () {
+	case "$ADVICE,$WHY" in
+	t,conflict)
+		echo "
+After resolving the conflicts, mark the corrected paths with
+
+	git add <paths>
+
+and run
+
+	$(print_caller --continue)
+
+Note, that your working tree must match the index."
+		;;
+	t,pause)
+		echo "
+You can now edit files and add them to the index.
+Once you are satisfied with your changes, run
+
+	$(print_caller --continue)
+
+If you only want to change the commit message, run
+git commit --amend before."
+		;;
+	t,run)
+		echo "
+Running failed:
+	$@
+
+You can now fix the problem.  When manual runs pass,
+add the changes to the index and invoke
+
+	$(print_caller --continue)"
+		;;
+	t,todo)
+		echo "
+Fix with git sequencer --edit or abort with $(print_caller --abort)."
+		;;
+	esac
+}
+
+# Die if there has been a conflict
+die_to_continue () {
+	warn "$*"
+	test -z "$BATCHMODE" ||
+		die_abort 'Aborting, because of batch mode.'
+	test -n "$WHY" || WHY=conflict
+	echo "$WHY" >"$WHY_FILE"
+	print_advice
+	exit 3
+}
+
+die_abort () {
+	restore
+	cleanup
+	die "$1"
+}
+
+quit () {
+	cleanup
+	test -z "$*" ||
+		echo "$*"
+	exit 0
+}
+
+perform () {
+	case "$VERBOSE" in
+	0)
+		"$@" >/dev/null
+		;;
+	1)
+		output=$("$@" 2>&1 )
+		status=$?
+		test $status -ne 0 && printf '%s\n' "$output"
+		return $status
+		;;
+	2)
+		"$@"
+		;;
+	esac
+}
+
+require_clean_work_tree () {
+	# test if working tree is dirty
+	git rev-parse --verify HEAD >/dev/null &&
+	git update-index --ignore-submodules --refresh &&
+	git diff-files --quiet --ignore-submodules &&
+	git diff-index --cached --quiet HEAD --ignore-submodules -- ||
+	die 'Working tree is dirty'
+}
+
+ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
+
+comment_for_reflog () {
+	if test -z "$ORIG_REFLOG_ACTION"
+	then
+		GIT_REFLOG_ACTION='sequencer'
+		test -z "$CALLER" ||
+			GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION ($CALLER)"
+		export GIT_REFLOG_ACTION
+	fi
+}
+
+# Get commit message from commit $1
+commit_message () {
+	git cat-file commit "$1" | sed -e '1,/^$/d'
+}
+
+LAST_COUNT=
+mark_action_done () {
+	sed -e 1q <"$TODO" >>"$DONE"
+	sed -e 1d <"$TODO" >"$TODO.new"
+	mv -f "$TODO.new" "$TODO"
+	todo_ack "$TODO"
+	if test "$VERBOSE" -gt 0
+	then
+		count=$(grep -c '^[^#]' <"$DONE")
+		total=$(expr "$count" + "$(grep -c '^[^#]' <"$TODO")")
+		if test "$LAST_COUNT" != "$count"
+		then
+			LAST_COUNT="$count"
+			test "$VERBOSE" -lt 1 ||
+				printf 'Sequencing (%d/%d)\r' "$count" "$total"
+			test "$VERBOSE" -lt 2 || echo
+		fi
+	fi
+}
+
+# Generate message, patch and author script files
+make_patch () {
+	parent_sha1=$(git rev-parse --verify "$1^" 2>/dev/null ||
+		echo '--root')
+	git diff-tree -p "$parent_sha1" "$1" >"$PATCH"
+	test -f "$MSG" ||
+		commit_message "$1" >"$MSG"
+	test -f "$AUTHOR_SCRIPT" ||
+		get_author_ident_from_commit "$1" >"$AUTHOR_SCRIPT"
+}
+
+# Generate a patch and die with "conflict" status code
+die_with_patch () {
+	make_patch "$1"
+	git rerere
+	die_to_continue "$2"
+}
+
+restore () {
+	git rerere clear
+
+	read HEADNAME <"$SEQ_DIR/head-name"
+	read HEAD <"$SEQ_DIR/head"
+	case $HEADNAME in
+	refs/*)
+		git symbolic-ref HEAD "$HEADNAME"
+		;;
+	esac &&
+	perform git reset --hard "$HEAD"
+}
+
+has_action () {
+	grep '^[^#]' "$1" >/dev/null
+}
+
+# Check if text file $1 contains a commit message
+has_message () {
+	test -n "$(sed -n -e '/^Signed-off-by:/d;/^[^#]/p' <"$1")"
+}
+
+# Count parents of commit $1
+count_parents() {
+	git cat-file commit "$1" | sed -n -e '1,/^$/p' | grep -c '^parent'
+}
+
+# Evaluate the author script to get author information to
+# apply it with "with_author git foo" then.
+get_current_author () {
+	if test -f "$AUTHOR_SCRIPT"
+	then
+		. "$AUTHOR_SCRIPT"
+	else
+		. "$ORIG_AUTHOR_SCRIPT"
+	fi || die_abort 'Author script is damaged. This must not happen!'
+}
+
+# Run command with author information
+with_author () {
+	GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
+	GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
+	GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
+		"$@"
+}
+
+# clean WHY_FILE and reset WHY
+clean_why () {
+	rm -f "$WHY_FILE"
+	WHY=
+}
+
+# Usage: pick_one (cherry-pick|revert) [-*|--edit] sha1
+pick_one () {
+	what="$1"
+	shift
+
+	case "$what,$1" in
+	revert,*)
+		test "$1" != '--edit' &&
+			what='revert --no-edit'
+		;;
+	cherry-pick,-*)
+		;;
+	cherry-pick,*)
+		# fast forward
+		if test "$(git rev-parse --verify "$1^" 2>/dev/null)" = \
+			"$(git rev-parse --verify HEAD)"
+		then
+			perform git reset --hard "$1"
+			return
+		fi
+		;;
+	esac
+	$use_perform git $what "$@"
+}
+
+nth_string () {
+	case "$1" in
+	*1[0-9]|*[04-9])
+		echo "$1th"
+		;;
+	*1)
+		echo "$1st"
+		;;
+	*2)
+		echo "$1nd"
+		;;
+	*3)
+		echo "$1rd"
+		;;
+	esac
+}
+
+make_squash_message () {
+	if test -f "$squash_msg"
+	then
+		count=$(($(sed -n -e 's/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p' \
+			<"$squash_msg" | sed -n -e '$p')+1))
+		echo "# This is a combination of $count commits."
+		sed -e '1d' -e '2,/^./{
+			/^$/d
+		}' <"$squash_msg"
+	else
+		count=2
+		echo '# This is a combination of 2 commits.'
+		echo '# The first commit message is:'
+		echo
+		commit_message HEAD
+	fi
+	echo
+	echo "# This is the $(nth_string "$count") commit message:"
+	echo
+	commit_message "$1"
+}
+
+make_squash_message_multiple () {
+	revlist=$(git rev-list --reverse "$sha1..HEAD")
+	count=$(echo "$revlist" | wc -l)
+	squash_i=0
+	echo "# This is a combination of $count commits."
+	for cur_sha1 in $revlist
+	do
+		squash_i=$(($squash_i+1))
+		if test -f "$squash_msg"
+		then
+			count=$(($count + $(sed -n -e \
+				's/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p' \
+				<"$squash_msg" | sed -n -e '$p')+1))
+			sed -e '1d' -e '2,/^./{
+				/^$/d
+			}' <"$squash_msg"
+		fi
+		echo
+		echo "# This is the $(nth_string "$squash_i") commit message:"
+		echo
+		commit_message "$cur_sha1"
+	done
+}
+
+peek_next_command () {
+	sed -n -e '/^#/d' -e '1s/ .*$//p' <"$TODO"
+}
+
+# If $1 is a mark, make a ref from it; otherwise keep it.
+# Note on marks:
+#  * :0 is allowed
+#  * :01 is the same as :1
+mark_to_ref () {
+	arg="$1"
+	ref=$(expr "x$arg" : 'x:0*\([0-9][0-9]*\)$')
+	test -n "$ref" &&
+		arg="$MARK_PREFIX/$ref"
+	printf '%s\n' "$arg"
+}
+
+mark_to_commit () {
+	git rev-parse --verify "$(mark_to_ref "$1")"
+}
+
+
+cannot_fallback () {
+	echo "$1"
+	die_to_continue 'Cannot fall back to three-way merge. Please hand-edit.'
+}
+
+
+fallback_3way () {
+	O_OBJECT=$(cd "$GIT_OBJECT_DIRECTORY" && pwd)
+
+	rm -fr "$PATCH-merge-"*
+	mkdir "$PATCH-merge-tmp-dir"
+
+	# First see if the patch records the index info that we can use.
+	git apply --build-fake-ancestor "$PATCH-merge-tmp-index" "$PATCH" &&
+	GIT_INDEX_FILE="$PATCH-merge-tmp-index" \
+		git write-tree >"$PATCH-merge-base+" ||
+	cannot_fallback 'Repository lacks necessary blobs to fall back on 3-way merge.'
+
+	echo 'Using index info to reconstruct a base tree...'
+	if GIT_INDEX_FILE="$PATCH-merge-tmp-index" \
+		git apply --cached "$PATCH"
+	then
+		mv "$PATCH-merge-base+" "$PATCH-merge-base"
+		mv "$PATCH-merge-tmp-index" "$PATCH-merge-index"
+	else
+		cannot_fallback "Did you hand edit your patch?
+It does not apply to blobs recorded in its index."
+	fi
+
+	test -f "$PATCH-merge-index" &&
+	his_tree=$(GIT_INDEX_FILE="$PATCH-merge-index" git write-tree) &&
+	orig_tree=$(cat "$PATCH-merge-base") &&
+	rm -fr "$PATCH-merge-"* || exit 1
+
+	echo 'Falling back to patching base and 3-way merge...'
+
+	# This is not so wrong.  Depending on which base we picked,
+	# orig_tree may be wildly different from ours, but his_tree
+	# has the same set of wildly different changes in parts the
+	# patch did not touch, so recursive ends up canceling them,
+	# saying that we reverted all those changes.
+
+	eval GITHEAD_$his_tree='"$firstline"'
+	export GITHEAD_$his_tree
+	git merge-recursive "$orig_tree" -- HEAD "$his_tree" || {
+		git rerere
+		die 'Failed to merge in the changes.'
+	}
+}
+
+# Run hook "$@" (with arguments) if executable
+run_hook () {
+	test -z "$1" || return
+	hookname="$1"
+	hook="$GIT_DIR/hooks/$hookname"
+	shift
+	if test -x "$hook"
+	then
+		"$hook" "$@" ||
+			die_to_continue "Hook $hookname failed."
+	fi
+}
+
+# Add Signed-off-by: line if general option --signoff is given
+dashdash_signoff () {
+	add_signoff=
+	if test -n "$SIGNOFF"
+	then
+		last_signed_off_by=$(
+			sed -n -e '/^Signed-off-by: /p' <"$MSG" | sed -n -e '$p'
+		)
+		test "$last_signed_off_by" = "$SIGNOFF" ||
+			add_signoff=$(
+				test '' = "$last_signed_off_by" && echo
+				echo "$SIGNOFF"
+			)
+	fi
+	{
+		test -s "$MSG" && cat "$MSG"
+		test -n "$add_signoff" && echo "$add_signoff"
+	} >"$MSG.new"
+	mv "$MSG.new" "$MSG"
+}
+
+
+### --caller-related functions
+
+# Show string for caller invocation for --abort/--continue/--skip
+print_caller_info () {
+	case "$1" in
+	--abort)
+		echo "$CALLER_ABRT"
+		;;
+	--continue)
+		echo "$CALLER_CONT"
+		;;
+	--skip)
+		echo "$CALLER_SKIP"
+		;;
+	*)
+		warn 'Internal error: Unknown print_caller argument!'
+		;;
+	esac
+}
+
+# Print the program to invoke to (--)abort/continue/skip
+print_caller() {
+	caller_info=$(print_caller_info "$1")
+	if test -n "$CALLERCOMPARE" -a -n "$caller_info"
+	then
+		test -n "$CALLER" && printf "$CALLER "
+		echo "$caller_info"
+	else
+		echo "git sequencer $1"
+	fi
+}
+
+# Test if --caller was set correctly
+# $1 must be abort/continue/skip
+test_caller () {
+	caller_info=$(print_caller_info "--$1")
+	test -n "$CALLERCOMPARE" -a \
+		"$CALLERCOMPARE" != "$CALLERSTRING" -a \
+		-n "$caller_info" &&
+		die "You must use '$CALLER $caller_info' to $1!"
+}
+
+# Generate $CALLER_SCRIPT file from "git foo|--abort|--continue|--skip"
+# string $1
+generate_caller_script () {
+	echo "$1" | sed -e 's/^\(.*\)|\(.*\)|\(.*\)|\(.*\)$/\
+CALLERCOMPARE="\0"\
+CALLER="\1"\
+CALLER_ABRT="\2"\
+CALLER_CONT="\3"\
+CALLER_SKIP="\4"/' >"$CALLER_SCRIPT"
+}
+
+# Run caller script and set GIT_CHERRY_PICK_HELP
+assure_caller () {
+	test -r "$CALLER_SCRIPT" &&
+		. "$CALLER_SCRIPT"
+
+	# we do not want cherry-pick to print *any* help
+	GIT_CHERRY_PICK_HELP=""
+	export GIT_CHERRY_PICK_HELP
+}
+
+
+### Helpers for check_* functions:
+
+# Print a warning on todo checking
+todo_warn () {
+	printf 'Warning at line %d, %s: %s\n' "$line" "$command" "$*"
+}
+
+# Raise an error on todo checking
+todo_error () {
+	printf 'Error at line %d, %s: %s\n' "$line" "$command" "$*"
+	retval=1
+}
+
+# Test if $1 is a commit on todo checking
+commit_check () {
+	test "$(git cat-file -t "$1" 2>/dev/null)" = commit ||
+		todo_error "'$1' is not a commit."
+}
+
+# A helper function to check if $1 is a an available mark during check_*
+arg_is_mark_check () {
+	for cur_mark in $available_marks
+	do
+		test "$cur_mark" -eq "${1#:}" && return
+	done
+	todo_error "Mark $1 is not yet defined."
+}
+
+# A helper function for check_* and mark usage:
+# check if "$1" is a commit or a defined mark
+arg_is_mark_or_commit_check () {
+	if expr "x$1" : 'x:[0-9][0-9]*$' >/dev/null
+	then
+		arg_is_mark_check "$1"
+	else
+		commit_check "$1"
+	fi
+}
+
+
+### Author script functions
+
+# Take "Name <e-mail>" in stdin and outputs author script
+make_author_script_from_string () {
+	sed -e "s/'/'"'\\'"''/g" \
+	    -e 's/^\(.*\) <\(.*\)>.*$/GIT_AUTHOR_NAME='\''\1'\''\
+GIT_AUTHOR_EMAIL='\''\2'\''\
+GIT_AUTHOR_DATE=/'
+}
+
+
+### General option functions (and options spec)
+
+OPTIONS_GENERAL=' General options:
+author= Override author
+C,reuse-commit= Reuse message and authorship data from commit
+F,file= Take commit message from given file
+m,message= Specify commit message
+M,reuse-message= Reuse message from commit
+signoff Add signoff
+e,edit Invoke editor to edit commit message'
+
+# Check if option is a general option
+check_general_option () {
+	general_shift=1
+	case "$1" in
+	--signoff)
+		return 0
+		;;
+	--author)
+		general_shift=2
+		author_opt="t$author_opt"
+		expr "x$2" : 'x.* <.*>' >/dev/null ||
+			todo_error "Author \"$2\" not in the correct format \"Name <e-mail>\"."
+		;;
+	-m)
+		general_shift=2
+		msg_opt="t$msg_opt"
+		;;
+	-C)
+		general_shift=2
+		msg_opt="t$msg_opt"
+		author_opt="t$author_opt"
+		commit_check "$2"
+		;;
+	-M)
+		general_shift=2
+		msg_opt="t$msg_opt"
+		commit_check "$2"
+		;;
+	-F)
+		general_shift=2
+		msg_opt="t$msg_opt"
+		test -r "$2" ||
+			todo_error "Cannot read file '$2'."
+		;;
+	-e)
+		test -z "$BATCHMODE" ||
+			todo_error '--batch and --edit options do not make sense together.'
+		;;
+	*)
+		return 1
+		;;
+	esac
+}
+
+# Set a general option variable or return 1
+handle_general_option () {
+	general_shift=1
+	case "$1" in
+	--signoff)
+		SIGNOFF=$(git var GIT_COMMITTER_IDENT | sed -e '
+				s/>.*/>/
+				s/^/Signed-off-by: /')
+		;;
+	--author)
+		general_shift=2
+		AUTHOR=t
+		echo "$2" |
+			make_author_script_from_string >"$AUTHOR_SCRIPT"
+		;;
+	-m)
+		general_shift=2
+		MESSAGE="$2"
+		;;
+	-C)
+		general_shift=2
+		AUTHOR=t
+		get_author_ident_from_commit "$2" >"$AUTHOR_SCRIPT"
+		MESSAGE=$(commit_message "$2")
+		;;
+	-M)
+		general_shift=2
+		MESSAGE=$(commit_message "$2")
+		;;
+	-F)
+		general_shift=2
+		MESSAGE=$(cat "$2")
+		;;
+	-e)
+		EDIT=--edit
+		;;
+	*)
+		return 1
+		;;
+	esac
+}
+
+
+### Functions for checking and realizing TODO instructions
+# Note that options_*, check_* and insn_* function names are reserved.
+
+options_pause="\
+pause
+--
+"
+
+# Check the "pause" instruction
+check_pause () {
+	shift
+	test -z "$BATCHMODE" ||
+		todo_error '"pause" instruction and --batch do not make sense together.'
+	test $# -eq 0 ||
+		todo_error 'The pause instruction takes no arguments.'
+	return 0
+}
+
+# Realize the "pause" instruction
+insn_pause () {
+	mark_action_done
+	make_patch HEAD
+	echo 'pause' >"$WHY_FILE"
+	WHY=pause print_advice
+	exit 2
+}
+
+
+options_run="\
+run [--dir=<path>] [--] <cmd> <args>...::
+--
+dir= Change directory before running command
+"
+
+# Check the "run" instruction
+check_run () {
+	while test $# -gt 0
+	do
+		case "$1" in
+		--dir)
+			test -e "$2" ||
+				todo_error "Path $2 does not exist."
+			test -d "$2" ||
+				todo_error "Path $2 is not a directory."
+			shift 2
+			;;
+		--)
+			shift
+			break
+			;;
+		-*)
+			todo_error "Unknown option $1"
+			;;
+		esac
+	done
+	# we don't check if cmd exists
+	return 0
+}
+
+# Realize the "run" instruction
+insn_run () {
+	runpath=./
+	while test $# -gt 0
+	do
+		case "$1" in
+		--dir)
+			runpath="$2"
+			shift 2
+			;;
+		--)
+			shift
+			break
+			;;
+		esac
+	done
+
+	mark_action_done
+
+	savepath="$PWD"
+	cd "$runpath"
+	"$@"
+	success="$?"
+	cd "$savepath"
+
+	if test "$success" -ne 0
+	then
+		test -z "$BATCHMODE" ||
+			die_abort "Running $1 failed. Aborting because of batch mode."
+		make_patch HEAD
+		echo 'run' >"$WHY_FILE"
+		WHY=run print_advice "$@"
+		exit 3
+	fi
+}
+
+
+options_patch="\
+patch [options] <file>
+--
+3,3way Fall back to 3-way merge
+k Pass to git-mailinfo (keep subject)
+n Pass to git-mailinfo (no utf8)
+$OPTIONS_GENERAL
+ Options passed to git-apply:
+R,reverse Reverse changes
+context= Ensure context of ... lines
+p= Remove ... leading slashes
+unidiff-zero Bypass unidiff checks
+exclude= Do not apply changes to given files
+no-add Ignore additions of patch
+whitespace= Set whitespace error behavior
+inaccurate-eof Support inaccurate EOFs
+u no-op (backward compatibility)
+binary no-op (backward compatibility)
+"
+
+# Check the "file" instruction
+check_patch () {
+	while test $# -gt 1
+	do
+		case "$1" in
+		-3|-k|-n|-u|--binary|-R|--reverse|--unidiff-zero|--no-add|--inaccurate-eof)
+			:
+			;;
+		-p|--whitespace|--exclude|--context)
+			shift
+			;;
+		--)
+			shift
+			break
+			;;
+		-*)
+			check_general_option "$@" ||
+				todo_warn "Unknown option $1"
+			;;
+		*)
+			todo_error "Wrong number of arguments. ($# given, 1 wanted)"
+			break
+			;;
+		esac
+		shift $general_shift
+	done
+
+	if test -f "$1" -a -r "$1"
+	then
+		grep -e '^diff' "$1" >/dev/null ||
+			todo_error "File '$1' contains no patch."
+	else
+		todo_error "Cannot open file '$1'."
+	fi
+	return 0
+}
+
+# Realize the "patch" instruction
+insn_patch () {
+	comment_for_reflog patch
+
+	apply_opts=
+	mailinfo_opts=
+	threeway=
+
+	# temporary files
+	infofile="$SEQ_DIR/patch-info"
+	msgfile="$SEQ_DIR/patch-msg"
+
+	while test "$#" -gt 1
+	do
+		case "$1" in
+		-3)
+			threeway=t
+			;;
+		-k|-n)
+			mailinfo_opts="$mailinfo_opts $1"
+			;;
+		-u|--binary)
+			: Do nothing. It is there due to b/c only.
+			;;
+		-R|--reverse|--unidiff-zero|--no-add|--inaccurate-eof)
+			apply_opts="$apply_opts $1"
+			;;
+		-p)
+			shift
+			apply_opts="$apply_opts -p$1"
+			;;
+		--whitespace|--exclude)
+			apply_opts="$apply_opts $1=$2"
+			shift
+			;;
+		--context)
+			shift
+			apply_opts="$apply_opts -C$1"
+			;;
+		--)
+			shift
+			break
+			;;
+		*)
+			handle_general_option "$@"
+			;;
+		esac
+		shift $general_shift
+	done
+
+	filename="$1"
+
+	mark_action_done
+
+	git mailinfo $mailinfo_opts "$msgfile" "$PATCH" \
+		<"$filename" >"$infofile" ||
+		die_abort 'Could not read or parse mail'
+
+	# if author not set by option, read author information of patch
+	if test -z "$AUTHOR"
+	then
+		cp "$ORIG_AUTHOR_SCRIPT" "$AUTHOR_SCRIPT"
+		sed -e "s/'/'"'\\'"''/g" -n -e '
+			s/^Author: \(.*\)$/GIT_AUTHOR_NAME='\''\1'\''/p;
+			s/^Email: \(.*\)$/GIT_AUTHOR_EMAIL='\''\1'\''/p;
+			s/^Date: \(.*\)$/GIT_AUTHOR_DATE='\''\1'\''/p
+		' <"$infofile" >>"$AUTHOR_SCRIPT"
+		# If sed's result is empty, we keep the original
+		# author script by appending.
+	fi
+
+	# Ignore every mail that's not containing a patch
+	test -s "$PATCH" || {
+		warn 'Does not contain patch!'
+		return 0
+	}
+
+	edit_msg=
+	if grep -e '^Subject:' "$infofile" >/dev/null
+	then
+		# add subject to commit message
+		sed -n -e '/^Subject:/ s/Subject: //p' <"$infofile"
+		echo
+		echo
+		cat "$msgfile"
+	else
+		cat "$msgfile"
+		edit_msg=t
+	fi | git stripspace >"$MSG"
+	rm -f "$infofile" "$msgfile"
+
+	firstline=$(sed -e '1q' <"$MSG")
+
+	get_current_author
+
+	test -n "$MESSAGE" && printf '%s\n' "$MESSAGE" >"$MSG"
+	test -z "$firstline" && firstline=$(sed -e '1q' <"$MSG")
+
+	dashdash_signoff
+
+	with_author run_hook applypatch-msg "$MSG"
+	failed=
+	git apply $apply_opts --index "$PATCH" || failed=t
+
+	if test -n "$failed" -a -n "$threeway" && (with_author fallback_3way)
+	then
+		# Applying the patch to an earlier tree and merging the
+		# result may have produced the same tree as ours.
+		git diff-index --quiet --cached HEAD -- && {
+			echo 'No changes -- Patch already applied.'
+			return 0
+			# XXX: do we want that?
+		}
+		# clear apply_status -- we have successfully merged.
+		failed=
+	fi
+
+	if test -n "$failed"
+	then
+		die_to_continue 'Patch failed.'
+		# XXX: We actually needed a git-apply flag that creates
+		# conflict markers and sets the DIFF_STATUS_UNMERGED flag.
+	fi
+
+	with_author run_hook pre-applypatch
+
+	test -n "$EDIT" && edit_msg=t
+	if ! has_message "$MSG" || test -n "$edit_msg"
+	then
+		echo "
+# Please enter the commit message for the applied patch.
+# (Comment lines starting with '#' will not be included)" >>"$MSG"
+
+		git_editor "$MSG" ||
+			die_with_patch 'Editor returned error.'
+		has_message "$MSG" ||
+			die_with_patch 'No commit message given.'
+	fi
+
+	tree=$(git write-tree) &&
+	parent=$(git rev-parse --verify HEAD) &&
+	commit=$(with_author git commit-tree "$tree" -p "$parent" <"$MSG") &&
+	git update-ref -m "$GIT_REFLOG_ACTION: $firstline" HEAD "$commit" "$parent" ||
+		die_to_continue 'Could not commit tree.'
+
+	test -x "$GIT_DIR/hooks/post-applypatch" &&
+		with_author "$GIT_DIR/hooks/post-applypatch"
+
+	return 0
+}
+
+
+options_pick="\
+pick [options] <commit>
+--
+R,reverse Revert introduced changes
+mainline= Specify parent number to use for merge commits
+$OPTIONS_GENERAL
+"
+
+# Check the "pick" instruction
+check_pick () {
+	mainline=
+	while test $# -gt 1
+	do
+		case "$1" in
+		-R)
+			;;
+		--mainline)
+			shift
+			mainline="$1"
+			test "$mainline" -gt 0 || {
+				todo_error '--mainline needs an integer beginning from 1.'
+				mainline=
+			}
+			;;
+		--)
+			shift
+			break
+			;;
+		-*)
+			check_general_option "$@" ||
+				todo_warn "Unknown option $1"
+			;;
+		*)
+			todo_error "Wrong number of arguments. ($# given, 1 wanted)"
+			break
+			;;
+		esac
+		shift $general_shift
+	done
+
+	if test -n "$mainline"
+	then
+		parents=$(count_parents "$1")
+		test "$parents" -lt "$mainline" &&
+			todo_error "Commit has only $parents (less than $mainline) parents."
+		test "$parents" -eq 1 &&
+			todo_warn 'Commit is not a merge at all.'
+	fi
+
+	commit_check "$1"
+
+	return 0
+}
+
+# Realize the "pick" instruction
+insn_pick () {
+	op=cherry-pick
+	mainline=
+	while test $# -gt 1
+	do
+		case "$1" in
+		-R)
+			op=revert
+			;;
+		--mainline)
+			mainline="$1 $2"
+			shift
+			;;
+		--)
+			shift
+			break
+			;;
+		-*)
+			handle_general_option "$@"
+			;;
+		esac
+		shift $general_shift
+	done
+
+	sha1=$(git rev-parse --verify "$1")
+
+	comment_for_reflog pick
+
+	mark_action_done
+
+	edit_msg="$EDIT"
+
+	# Don't edit on pick, but later, if author or message given.
+	test -n "$AUTHOR" -o -n "$MESSAGE" && edit_msg=
+
+	# Be kind to users and ignore --mainline=1 on non-merge commits
+	test -n "$mainline" -a 2 -gt $(count_parents "$sha1") && mainline=
+
+	use_perform=
+	test -n "$edit_msg" ||
+		use_perform=perform
+
+	pick_one "$op" $edit_msg $mainline $sha1 ||
+		die_with_patch $sha1 "Could not apply $sha1"
+
+	test -n "$EDIT" ||
+		use_perform=perform
+
+	get_current_author
+	signoff=
+	test -n "$SIGNOFF" && signoff=-s
+	if test -n "$AUTHOR" -a -n "$MESSAGE"
+	then
+		# this is just because we only want to do ONE amending commit
+		$use_perform git commit --amend $EDIT $signoff --no-verify \
+			--author "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" \
+			--message="$MESSAGE"
+	elif test -n "$AUTHOR"
+	then
+		# correct author if AUTHOR is set
+		$use_perform git commit --amend $EDIT --no-verify -C HEAD \
+			--author "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>"
+	elif test -n "$MESSAGE"
+	then
+		# correct commit message if MESSAGE is set
+		$use_perform git commit --amend $EDIT $signoff --no-verify \
+			-C HEAD --message="$MESSAGE"
+	elif test -n "$SIGNOFF"
+	then
+		# only add signoff
+		$use_perform git commit --amend $EDIT $signoff --no-verify \
+			-C HEAD
+	fi
+
+	return 0
+}
+
+options_edit="\
+edit <commit>
+--
+"
+
+# Check the "edit" instruction
+check_edit () {
+	shift
+	test -z "$BATCHMODE" ||
+		todo_error '"edit" instruction and --batch do not make sense together.'
+	test $# -eq 1 ||
+		todo_error "Wrong number of arguments. ($# given, 1 wanted)"
+	return 0
+}
+
+# Realize the "edit" instruction
+insn_edit () {
+	shift
+	insn_pick "$1"
+
+	# work around mark_action_done in insn_pause
+	echo '# pausing' >"$TODO.new"
+	cat "$TODO" >>"$TODO.new"
+	mv -f "$TODO.new" "$TODO"
+	insn_pause
+}
+
+
+options_squash="\
+squash <commit>
+squash [options] --from <mark>
+--
+from Squash all commits from <mark>
+collect-signoffs Collect Signed-off-by: lines
+include-merges Do not fail on merge commits
+$OPTIONS_GENERAL
+"
+
+# Check the "squash" instruction
+check_squash () {
+	from=
+	collect=
+	merges=
+	while test $# -gt 1
+	do
+		case "$1" in
+		--from)
+			from=t
+			;;
+		--collect-signoffs)
+			collect=t
+			todo_warn 'Not yet implemented.'
+			;;
+		--include-merges)
+			merges=t
+			;;
+		--)
+			shift
+			break
+			;;
+		*)
+			check_general_option "$@" ||
+				todo_error "Unknown option $1"
+			;;
+		esac
+		shift $general_shift
+	done
+
+	# in --from mode?
+	if test -n "$from"
+	then
+		test -z "$merges" &&
+			cat "$DONE" "$TODO" |
+			sed -n -e '/^[ \t]*mark[ \t]*:\{0,1\}'"${1#:}"'\($\|[^0-9]\)/,'"$line"'p' |
+			grep '^[ \t]*merge' >/dev/null &&
+			todo_error "$1..HEAD contains a merge commit. You may try --include-merges."
+
+		arg_is_mark_check "$1"
+	else
+		test -n "$merges" &&
+			todo_error '--include-merges only makes sense with --from <mark>.'
+		test -n "$collect" &&
+			todo_error '--collect-signoffs only makes sense with --from <mark>.'
+
+		commit_check "$1"
+	fi
+
+	return 0
+}
+
+# Realize the "squash" instruction
+insn_squash () {
+	squash_msg="$MSG-squash"
+	from=
+	while test $# -gt 1
+	do
+		case "$1" in
+		--from)
+			from=t
+			;;
+		--collect-signoffs)
+			warn '--collect-signoffs is not implemented.'
+			# XXX
+			;;
+		--include-merges)
+			: # This has to be done during check_squash
+			;;
+		--)
+			shift
+			break
+			;;
+		*)
+			handle_general_option "$@"
+			;;
+		esac
+		shift $general_shift
+	done
+
+	if test -n "$from"
+	then
+		sha1=$(mark_to_commit ":${1#:}")
+	else
+		sha1=$(git rev-parse --verify "$1")
+	fi
+
+	comment_for_reflog squash
+
+	# Hm, somehow I don't think --skip on a conflicting squash
+	# may be useful, but if someone wants to do it, it should
+	# do the obvious: skip what squash would do.
+	echo "$(git rev-parse HEAD)" >"$SEQ_DIR/skiphead"
+
+	mark_action_done
+
+	if test -n "$MESSAGE"
+	then
+		printf '%s\n' "$MESSAGE" >"$MSG"
+	else
+		if test -n "$from"
+		then
+			make_squash_message_multiple "$sha1" >"$MSG"
+		else
+			make_squash_message "$sha1" >"$MSG"
+		fi
+	fi
+
+	case "$(peek_next_command)" in
+	squash)
+		edit_commit=
+		use_perform=perform
+		cp "$MSG" "$squash_msg"
+		;;
+	*)
+		edit_commit=-e
+		use_perform=
+		rm -f "$squash_msg" || exit
+		;;
+	esac
+
+	test -n "$MESSAGE" && edit_commit=
+	test -n "$EDIT" && edit_commit=-e
+
+	# is --author (or equivalent) set?
+	if test -n "$AUTHOR"
+	then
+		# evaluate author script
+		get_current_author
+	else
+		# if --author is not given, we get the authorship
+		# information from the commit before.
+		eval "$(get_author_ident_from_commit HEAD)"
+		# but we do not write an author script
+	fi
+
+	# --from or not
+	failed=
+	if test -n "$from"
+	then
+		perform git reset --soft "$sha1"
+	else
+		perform git reset --soft HEAD^
+
+		pick_one cherry-pick -n "$sha1" || failed=t
+	fi
+
+	dashdash_signoff
+
+	if test -z "$failed"
+	then
+		# This is like --amend, but with a different message
+		with_author $use_perform git commit --no-verify \
+			-F "$MSG" $edit_commit || failed=t
+	else
+		cp "$MSG" "$GIT_DIR/MERGE_MSG"
+		warn
+		warn "Could not apply $sha1..."
+		die_with_patch $sha1 ""
+	fi
+
+	return 0
+}
+
+
+options_mark="\
+mark <mark>
+--
+"
+
+# Check the "mark" instruction
+check_mark () {
+	shift
+	test $# -eq 1 ||
+		todo_error "Wrong number of arguments. ($# given, 1 wanted)"
+	my_mark=$(expr "x${1#:}" : 'x0*\([0-9][0-9]*\)$')
+	test -n "$my_mark" ||
+		todo_error "Mark $1 not an integer."
+	expr "x$available_marks " : " $my_mark " >/dev/null &&
+		todo_error "Mark :$my_mark already defined. Choose another integer."
+	available_marks="$available_marks $my_mark"
+
+	return 0
+}
+
+# Realize the "mark" instruction
+insn_mark () {
+	shift
+	given="$1"
+
+	mark_action_done
+
+	mark=$(mark_to_ref ":${given#:}")
+	git update-ref "$mark" HEAD
+	return 0
+}
+
+
+options_merge="\
+merge [options] <commit-ish> ...
+--
+standard Generate default commit message
+s,strategy= Use merge strategy ...
+$OPTIONS_GENERAL
+"
+
+# Check the "merge" instruction
+check_merge () {
+	while test $# -gt 1
+	do
+		case "$1" in
+		--standard)
+			msg_opt="t$msg_opt"
+			;;
+		-s)
+			shift
+			;;
+		--)
+			shift
+			break
+			;;
+		-*)
+			check_general_option "$@" ||
+				todo_error "Unknown option $1"
+			;;
+		esac
+		shift $general_shift
+	done
+
+	test $# -gt 0 ||
+		todo_error 'What are my parents? Need new parents!'
+
+	while test $# -gt 0
+	do
+		arg_is_mark_or_commit_check "$1"
+		shift
+	done
+	return 0
+}
+
+# Realize the "merge" instruction
+insn_merge () {
+	comment_for_reflog merge
+
+	standard=
+
+	while test $# -gt 1
+	do
+		case "$1" in
+		--standard)
+			standard=t
+			;;
+		-s)
+			shift
+			strategy="-s $1"
+			;;
+		--)
+			shift
+			break
+			;;
+		-*)
+			handle_general_option "$@"
+			;;
+		esac
+		shift $general_shift
+	done
+
+	new_parents=
+	for p in "$@"
+	do
+		new_parents="$new_parents $(mark_to_ref $p)"
+	done
+	new_parents="${new_parents# }"
+
+	get_current_author
+
+	if test -n "$standard"
+	then
+		for cur_parent in $new_parents
+		do
+			printf '%s\t\t%s' \
+				"$(git rev-parse "$cur_parent")" "$cur_parent"
+		done | git fmt-merge-msg >"$MSG"
+	fi
+
+	test -n "$MESSAGE" &&
+		printf '%s\n' "$MESSAGE" >"$MSG"
+
+	dashdash_signoff
+	if ! has_message "$MSG" || test -n "$EDIT"
+	then
+		echo "
+# Please enter the merge commit message.
+# (Comment lines starting with '#' will not be included)" >>"$MSG"
+
+		git_editor "$MSG" ||
+			die_with_patch 'Editor returned error.'
+		has_message "$MSG" ||
+			die_with_patch 'No commit message given.'
+	fi
+
+	mark_action_done
+	if ! with_author perform git merge $strategy -m junk $new_parents
+	then
+		git rerere
+		cp "$MSG" "$GIT_DIR/MERGE_MSG"
+		die_to_continue 'Error merging'
+	fi
+	with_author perform git commit --amend -F "$MSG" --no-verify
+	return 0
+}
+
+
+options_reset="\
+reset <commit-ish>
+--
+"
+
+# Check the "reset" instruction
+check_reset () {
+	shift
+	test $# -eq 1 ||
+		todo_error "Wrong number of arguments. ($# given, 1 wanted)"
+	arg_is_mark_or_commit_check "$1"
+
+	return 0
+}
+
+# Realize the "reset" instruction
+insn_reset () {
+	shift
+	comment_for_reflog reset
+
+	mark_action_done
+	perform git reset --hard "$(mark_to_commit "$1")"
+}
+
+
+options_ref="\
+ref <ref>
+--
+"
+
+# Check the "ref" instruction
+check_ref () {
+	shift
+	test $# -eq 1 ||
+		todo_error "Wrong number of arguments. ($# given, 1 wanted)"
+	return 0
+}
+
+# Realize the "ref" instruction
+insn_ref () {
+	shift
+	comment_for_reflog ref
+
+	mark_action_done
+	perform git update-ref "$1" HEAD
+}
+
+
+### Instruction main loop
+
+# Run check_* or insn_* with massaged options
+# Usage: run_insn (check|do) <insn> <insn options>
+run_insn () {
+	c_or_i="$1"
+	insn="$2"
+	shift
+	shift
+	eval "option_spec=\"\$options_$insn\""
+	eval "$(printf "%s" "$option_spec" |
+		git rev-parse --parseopt -- "$@" ||
+		echo return 1)"
+	case "$c_or_i" in
+	check)
+		check_$insn "$@"
+		;;
+	do)
+		insn_$insn "$@"
+		;;
+	esac
+}
+
+# Execute the first line of the current TODO file
+execute_next () {
+	read -r command rol <"$TODO"
+	test "$VERBOSE" -gt 1 &&
+		printf 'Next line: %s\n' "$command $rol"
+
+	case "$command" in
+	'#'*|'')
+		mark_action_done
+		;;
+	*)
+		# reset general options
+		rm -f "$AUTHOR_SCRIPT" "$MSG"
+		echo 'HEAD' >"$SEQ_DIR/skiphead"
+		general_shift=1
+		AUTHOR=
+		EDIT=
+		MESSAGE=
+		SIGNOFF=
+		# XXX: eval is evil!
+		eval "run_insn do $command $rol" ||
+			die_to_continue 'An unexpected error occured.'
+		;;
+	esac
+}
+
+# Execute the rest of the TODO file and finish
+execute_rest () {
+	while has_action "$TODO"
+	do
+		execute_next
+	done
+
+	comment_for_reflog finish
+	if test -n "$ONTO"
+	then
+		git update-ref -m "$GIT_REFLOG_ACTION: $ONTO" "$ONTO" HEAD &&
+		git symbolic-ref HEAD "$ONTO"
+	fi &&
+	quit
+}
+
+# We don't need to check a todo file if it has not changed
+# since it has last been acknowledged to be sane.
+todo_has_changed () {
+	test -f "$1.sum" || return 0
+	test "$(git hash-object "$1")" != "$(cat "$1.sum")"
+}
+
+# acknowledge todo file to be sane
+todo_ack () {
+	git hash-object "$1" >"$1.sum"
+}
+
+# Main loop to check instructions
+todo_check () {
+	todo="$TODO"
+	test -n "$1" && todo="$1"
+	todo_has_changed "$todo" || return 0
+
+	test "$VERBOSE" -lt 1 || printf 'Checking...\r'
+	available_marks=' '
+	for cur_mark in $(git for-each-ref --format='%(refname)' "$MARK_PREFIX")
+	do
+		available_marks="$available_marks ${cur_mark##*/}"
+	done
+
+	retval=0
+	line=1
+	while read -r command rol
+	do
+		case "$command" in
+		'#'*|'')
+			;;
+		*)
+			eval 'test -n "$options_'"$command"'"' || {
+				retval=1
+				todo_error "Unknown $command instruction"
+				continue
+			}
+
+			general_shift=1
+			msg_opt=
+			author_opt=
+			eval "run_insn check $command $rol" ||
+				todo_error "Unknown option used"
+			expr "$msg_opt" : 'ttt*' >/dev/null &&
+				todo_error 'You can only provide one commit message option.'
+			expr "$author_opt" : 'ttt*' >/dev/null &&
+				todo_error 'You can only provide one author option.'
+			;;
+		esac
+		line=$(expr "$line" + 1)
+	done <"$todo"
+	test $retval -ne 0 || todo_ack "$todo"
+	return $retval
+}
+
+prepare_editable_todo () {
+	echo '# ALREADY DONE:'
+	sed -e 's/^/#  /' <"$DONE"
+	echo '# '
+	echo "$markline"
+	cat "$TODO"
+}
+
+# expand shortcuts in TODO file $1
+expand_shortcuts () {
+	sed -e '
+		s/^[ \t]*p\>/pick/;
+		s/^[ \t]*e\>/edit/;
+		s/^[ \t]*s\>/squash/;
+	' <"$1" >"$TODO.cut"
+	mv "$TODO.cut" "$1"
+}
+
+get_saved_options () {
+	read VERBOSE <"$SEQ_DIR/verbose"
+	read ADVICE <"$SEQ_DIR/advice"
+	read ONTO <"$SEQ_DIR/onto"
+	test -f "$WHY_FILE" &&
+		read WHY <"$WHY_FILE"
+	return 0
+}
+
+# Realize sequencer invocation
+do_startup () {
+	test -d "$SEQ_DIR" &&
+		die 'sequencer already started'
+
+	require_clean_work_tree
+
+	HEAD=$(git rev-parse --verify HEAD) ||
+		die 'No HEAD?'
+
+	mkdir "$SEQ_DIR" ||
+		die "Could not create temporary $SEQ_DIR"
+
+	# save options
+	echo "$VERBOSE" >"$SEQ_DIR/verbose"
+	echo "$ADVICE" >"$SEQ_DIR/advice"
+	test -n "$CALLERSTRING" &&
+		generate_caller_script "$CALLERSTRING"
+	# generate empty DONE and "onto" file
+	: >"$DONE"
+	: >"$SEQ_DIR/onto"
+
+	if test -n "$BATCHMODE"
+	then
+		GIT_CHERRY_PICK_HELP='  Aborting (batch mode)'
+		export GIT_CHERRY_PICK_HELP
+	else
+		assure_caller
+	fi
+
+	comment_for_reflog start
+
+	# save old head before checking out the given <branch>
+	git symbolic-ref HEAD >"$SEQ_DIR/head-name" 2>/dev/null ||
+		echo 'detached HEAD' >"$SEQ_DIR/head-name"
+	echo $HEAD >"$SEQ_DIR/head"
+	# do it here so that die_abort can work ;)
+
+	if test -n "$ONTO"
+	then
+		# if ONTO is a branch name, then keep it, otherwise
+		# we don't care anymore and erase ONTO.
+		if git-show-ref --quiet --verify -- "refs/heads/${ONTO##*/}"
+		then
+			ONTO="refs/heads/${ONTO##*/}"
+			echo "$ONTO" >"$SEQ_DIR/onto"
+			perform git checkout "$(git rev-parse "$ONTO")" ||
+				die_abort "Could not checkout branch $ONTO"
+		else
+			perform git checkout "$ONTO" ||
+				die_abort "Could not checkout commit $ONTO"
+			ONTO=
+		fi
+	fi
+
+	(git var GIT_AUTHOR_IDENT || git var COMMITTER_IDENT) |
+		make_author_script_from_string >"$ORIG_AUTHOR_SCRIPT"
+
+	# read from file or from stdin?
+	if test -z "$1"
+	then
+		: >"$TODO" ||
+			die_abort "Could not generate TODO file $TODO"
+		while read -r line
+		do
+			printf '%s\n' "$line" >>"$TODO" ||
+				die_abort "Could not append to TODO file $TODO"
+		done
+	else
+		cp "$1" "$TODO" ||
+			die_abort "Could not find TODO file $1."
+	fi
+	expand_shortcuts "$TODO"
+
+	has_action "$TODO" || die_abort 'Nothing to do'
+	todo_check || WHY=todo die_to_continue "TODO file contains errors."
+
+	execute_rest
+	exit
+}
+
+# Realize --continue.
+do_continue () {
+	test -d "$SEQ_DIR" || die 'No sequencer running'
+	test_caller 'continue'
+
+	expand_shortcuts "$TODO"
+	todo_check || WHY=todo die_to_continue "TODO file contains errors."
+
+	comment_for_reflog continue
+
+	# Sanity check
+	git rev-parse --verify HEAD >/dev/null ||
+		die_to_continue 'Cannot read HEAD'
+	git update-index --ignore-submodules --refresh &&
+		git diff-files --quiet --ignore-submodules ||
+		die_to_continue 'Working tree is dirty. (Use git add or git stash first?)'
+
+	get_saved_options
+
+	# do we have anything to commit? (staged changes)
+	if ! git diff-index --cached --quiet --ignore-submodules HEAD --
+	then
+		get_current_author
+
+		# After "pause", we should amend (merge-safe!).
+		# On conflict, we do not have a commit to amend, so we
+		# should just commit.
+		case "$WHY" in
+		pause|run)
+			with_author git commit --amend --no-verify -F "$MSG" -e ||
+				die_to_continue 'Could not commit staged changes.'
+			;;
+		conflict)
+			with_author git commit --no-verify -F "$MSG" -e ||
+				die_to_continue 'Could not commit staged changes.'
+			echo '# resolved CONFLICTS of the last instruction' >>"$DONE"
+			;;
+		*)
+			die_to_continue 'There are staged changes. Do not know what to do with them.'
+		esac
+	fi
+
+	require_clean_work_tree
+	clean_why
+	execute_rest
+	exit
+}
+
+# Realize --abort.
+do_abort () {
+	test -d "$SEQ_DIR" || die 'No sequencer running'
+	test_caller 'abort'
+
+	comment_for_reflog abort
+	git rerere clear
+	restore
+	quit
+}
+
+# Realize --skip.
+do_skip () {
+	test -d "$SEQ_DIR" || die 'No sequencer running'
+	test_caller 'skip'
+
+	expand_shortcuts "$TODO"
+	todo_check || WHY=todo die_to_continue "TODO file contains errors."
+	get_saved_options
+
+	comment_for_reflog skip
+	git rerere clear
+	clean_why
+
+	perform git reset --hard "$(cat "$SEQ_DIR/skiphead")" &&
+	echo '# SKIPPED the last instruction' >>"$DONE" &&
+		execute_rest
+	exit
+}
+
+# Realize --edit.
+do_edit () {
+	test -d "$SEQ_DIR" || die 'No sequencer running'
+
+	markline='### BEGIN EDITING BELOW THIS LINE ###'
+	prepare_editable_todo >"$TODO.new"
+	if todo_has_changed "$TODO"
+	then
+		sane=
+	else
+		sane=t
+		todo_ack "$TODO.new"
+	fi
+
+	# XXX: does not make sense
+	#      when input does not come from a terminal
+	git_editor "$TODO.new" ||
+		die 'Editor returned an error.'
+
+	if test -t 0 -a -t 1
+	then
+		sane=t
+		echo
+		# interactive:
+		until has_action "$TODO.new" && todo_check "$TODO.new"
+		do
+			has_action "$TODO.new" || echo 'TODO file empty.'
+			printf 'What to do with the file? [c]orrect/[e]dit again/[r]ewind/[s]ave/[?] '
+			read reply
+			case "$reply" in
+			[cC]*)
+				git_editor "$TODO.new"
+				expand_shortcuts "$TODO.new"
+				;;
+			[eE]*)
+				prepare_editable_todo >"$TODO.new"
+				git_editor "$TODO.new"
+				expand_shortcuts "$TODO.new"
+				;;
+			[rRxXqQ]*)
+				rm -f "$TODO.new" "$TODO.new.sum"
+				exit 0
+				;;
+			[sS]*)
+				test "$WHY" != 'pause' ||
+					echo 'todo' >"$WHY_FILE"
+				sane=
+				break
+				;;
+			[?hH]*)
+				cat <<EOF
+
+Help:
+s - save TODO file and exit
+c - respawn editor to correct TODO file
+e - drop changes and respawn editor on original TODO file
+r - drop changes and exit as if nothing happened
+? - print this help
+EOF
+				;;
+			esac
+			echo
+		done
+	else
+		# defaults:
+		has_action "$TODO.new" || quit "Nothing to do"
+		todo_check "$TODO.new" ||
+			die 'TODO file contains errors. Aborting.'
+	fi
+	cp "$TODO" "$TODO.old"
+	if grep "^$markline" "$TODO.new" >/dev/null
+	then
+		sed -e "1,/^$markline/d" <"$TODO.new" >"$TODO"
+	else
+		cp "$TODO.new" "$TODO"
+	fi
+	rm -f "$TODO.new" "$TODO.new.sum"
+	test -z "$sane" || todo_ack "$TODO"
+	echo
+	echo 'TODO file contains:'
+	echo
+	cat "$TODO"
+	exit 0
+}
+
+# Realize --status.
+do_status () {
+	test -d "$SEQ_DIR" || die 'No sequencer running.'
+	get_saved_options
+
+	if has_action "$DONE"
+	then
+		echo 'Already done (or tried):'
+		sed -e 's/^/  /' <"$DONE"
+		echo
+	fi
+	case "$WHY" in
+	pause)
+		echo 'Intentionally paused.'
+		;;
+	run)
+		echo 'Interrupted because running failed.'
+		;;
+	conflict)
+		echo 'Interrupted by conflict at'
+		sed -n -e 's/^/  /;$p' <"$DONE"
+		;;
+	todo)
+		echo 'Interrupted because of errors in the TODO file.'
+		;;
+	*)
+		echo 'Current state is broken.'
+	esac
+	test "$VERBOSE" -gt 1 && echo 'Running verbosely.'
+	test "$VERBOSE" -lt 1 && echo 'Running quietly.'
+	test -n "$ONTO" &&
+		echo "Sequencing on branch ${ONTO##*/}."
+	if has_action "$TODO"
+	then
+		echo
+
+		echo 'Still to do:'
+		sed -e 's/^/  /' <"$TODO"
+	fi
+	if test "$WHY" = todo
+	then
+		echo
+		echo 'But there are errors. To edit, run:'
+		echo '    git sequencer --edit'
+	else
+		echo
+		echo 'To abort & restore, invoke:'
+		echo "    $(print_caller --abort)"
+		echo 'To continue, invoke:'
+		echo "    $(print_caller --continue)"
+		test "$WHY" = 'pause' -o "$WHY" = 'run' || {
+			echo 'To skip the current instruction, invoke:'
+			echo "    $(print_caller --skip)"
+		}
+	fi
+	exit 0
+}
+
+is_standalone () {
+	test $# -eq 2 &&
+	test "$2" = '--' &&
+	test -z "$BATCHMODE" &&
+	test "$ADVICE" = t &&
+	test -z "$onto" &&
+	test "$VERBOSE" -eq 1
+}
+
+
+### Option handling and startup
+
+onto=
+BATCHMODE=
+CALLERSTRING=
+VERBOSE=1
+ADVICE=t
+CALLER=
+CALLER_ABRT=
+CALLER_CONT=
+CALLER_SKIP=
+while test $# -gt 0
+do
+	case "$1" in
+	--continue)
+		is_standalone "$@" || usage
+		assure_caller
+		do_continue
+		;;
+	--abort)
+		is_standalone "$@" || usage
+		assure_caller
+		do_abort
+		;;
+	--skip)
+		is_standalone "$@" || usage
+		assure_caller
+		do_skip
+		;;
+	--status)
+		is_standalone "$@" || usage
+		assure_caller
+		do_status
+		;;
+	--edit)
+		is_standalone "$@" || usage
+		do_edit
+		;;
+	--caller)
+		###############################################################
+		# This feature is for user scripts only. (Hence undocumented.)
+		# User scripts should pass an argument like:
+		# --caller="git foo|abrt|go|next"
+		# on every git sequencer call. (It is only ignored on
+		# --edit and --status.)
+		# So git sequencer knows that
+		# "git foo abrt" will abort,
+		# "git foo go" will continue and
+		# "git foo next" will skip the sequencing process.
+		# This is useful if your user script does some extra
+		# preparations or cleanup before/after calling
+		#   git sequencer --caller="..." --abort|--continue|--skip
+		#
+		# Running git-sequencer without the same --caller string
+		# fails then, until the sequencing process has finished or
+		# aborted.
+		#
+		# Btw, --caller="my_tiny_script.sh|-a||" will be
+		# interpreted, that users must invoke "my_tiny_script.sh -a"
+		# to abort, but can invoke "git sequencer --continue" and
+		# "git sequencer --skip" to continue or skip.
+		# And it is also possible to provide three different scripts
+		# by --caller="|script 1|tool 2|util 3".
+		#
+		# If your user script does not need any special
+		# abort/continue/skip behavior, then just do NOT pass
+		# the --caller option.
+		###############################################################
+		shift
+		expr "x$1" : 'x.*|.*|.*|.*$' >/dev/null ||
+			die 'Wrong --caller format.'
+		CALLERSTRING="$1"
+		;;
+	-B)
+		BATCHMODE=t
+		# XXX: we still have abort on editor invokations
+		;;
+	--no-advice)
+		ADVICE=f
+		;;
+	--onto)
+		shift
+		ONTO="$1"
+		test $(git cat-file -t "$ONTO") = 'commit' ||
+			die "$ONTO is no commit or branch."
+		;;
+	-q)
+		VERBOSE=0
+		ADVICE=f
+		# XXX: If there were no editors,
+		# we could just do exec >/dev/null
+		;;
+	-v)
+		VERBOSE=2
+		# or increment it if we have several verbosity steps
+		;;
+	--)
+		shift
+		break
+		;;
+	*)
+		die "$1 currently not implemented."
+		;;
+	esac
+	shift
+done
+test $# -gt 1 && usage
+
+do_startup "$1"
-- 
1.5.6.3.391.ge45b

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

* [PATCH 2/3] Add git-sequencer documentation
  2008-07-16 20:45 [PATCH 1/3] Add git-sequencer shell prototype Stephan Beyer
@ 2008-07-16 20:45 ` Stephan Beyer
  2008-07-16 20:45   ` [PATCH 3/3] Add git-sequencer test suite (t3350) Stephan Beyer
  0 siblings, 1 reply; 8+ messages in thread
From: Stephan Beyer @ 2008-07-16 20:45 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Daniel Barkalow, Christian Couder, Junio C Hamano

Mentored-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 Documentation/git-sequencer.txt |  673 +++++++++++++++++++++++++++++++++++++++
 1 files changed, 673 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/git-sequencer.txt

diff --git a/Documentation/git-sequencer.txt b/Documentation/git-sequencer.txt
new file mode 100644
index 0000000..8a701c4
--- /dev/null
+++ b/Documentation/git-sequencer.txt
@@ -0,0 +1,673 @@
+git-sequencer(1)
+================
+
+NAME
+----
+git-sequencer - Execute a sequence of git instructions
+
+SYNOPSIS
+--------
+[verse]
+'git sequencer' [--batch] [--onto=<base>]
+		[--verbose | --no-advice | --quiet]
+		[--] [<file>]
+'git sequencer' --continue | --skip | --abort | --edit | --status
+
+
+DESCRIPTION
+-----------
+Executes a sequence of git instructions to HEAD or `<base>`.
+The sequence is given by `<file>` or standard input.
+Also see 'TODO FILE FORMAT' below.
+
+Before doing anything, the TODO file is checked for correct syntax
+and sanity.
+
+In case of a conflict or request in the TODO file, 'git-sequencer' will
+pause. On conflict you can use 'git-diff' to locate the markers (`<<<<<<<`)
+and make edits to resolve the conflict.
+
+For each file you edit, you need to tell git the changes by doing
+
+    git add <file>
+
+After resolving the conflict manually and updating the index with the
+desired resolution, you can continue the sequencing process with
+
+    git sequencer --continue
+
+Alternatively, you can undo the 'git-sequencer' progress with
+
+    git sequencer --abort
+
+or skip the current instruction with
+
+    git sequencer --skip
+
+or correct the TODO file with
+
+    git sequencer --edit
+
+During pauses or when finished with the sequencing task, the current
+HEAD will always be the result of the last processed instruction.
+
+
+OPTIONS
+-------
+<file>::
+	Filename of the TODO file.  If omitted, standard input is used.
+	See 'TODO FILE FORMAT' below.
+
+-B::
+--batch::
+	Run in batch mode. If unexpected user intervention is needed
+	(e.g. a conflict or the need to run an editor), 'git-sequencer' fails.
++
+Note that the sanity check fails, if you use this option
+and an instruction like `edit` or `pause` is in the TODO file.
+
+--onto=<base>::
+	Checkout given commit or branch before sequencing.
+	If you provide a branch, sequencer will make the provided
+	changes on the branch, i.e. the branch will be changed.
+
+--continue::
+	Restart the sequencing process after having resolved a merge conflict.
+
+--abort::
+	Restore the original branch and abort the sequence operation.
+
+--skip::
+	Restart the sequencing process by skipping the current instruction.
+
+--status::
+	Show the current status of 'git-sequencer' and what
+	operations can be done to change that status.
+
+--edit::
+	Invoke editor to edit the unprocessed part of the TODO file.
++
+The file is syntax- and sanity-checked afterwards, so that you can
+safely run `git sequencer --skip` or `--continue` after editing.
+If you nonetheless noticed that you made a mistake, you can
+overwrite `.git/sequencer/todo` with `.git/sequencer/todo.old` and
+rerun `git sequencer --edit`.
++
+If the check fails you are prompted if you want to correct your
+changes, edit again, cancel editing or really want to save.
+
+--no-advice::
+	Suppress advice on intentional and unintentional pauses.
+
+-q::
+--quiet::
+	Suppress output. Implies `--no-advice`.
+	(Not yet implemented.)
+
+-v::
+--verbose::
+	Be more verbose.
+
+
+NOTES
+-----
+
+When sequencing, it is possible, that you are changing the history of
+a branch in a way that can cause problems for anyone who already has
+a copy of the branch in their repository and tries to pull updates from
+you.  You should understand the implications of using 'git-sequencer' on
+a repository that you share.
+
+'git-sequencer' will usually be called by another git porcelain, like
+linkgit:git-am[1] or linkgit:git-rebase[1].
+
+
+TODO FILE FORMAT
+----------------
+
+The TODO file contains basically one instruction per line.
+
+Blank lines will be ignored.
+All characters after a `#` character will be ignored until the end of a line.
+
+The following instructions can be used:
+
+
+edit <commit>::
+	Pick a commit and pause the sequencer process to let you
+	make changes.
++
+This is a short form for `pick <commit> and `pause` on separate lines.
+
+
+mark <mark>::
+	Set a symbolic mark for the last commit.
+	`<mark>` is an unsigned integer starting at 1 and
+	prefixed with a colon, e.g. `:1`.
++
+The marks can help if you want to refer to commits that you
+created during the sequencer process, e.g. if you want to
+merge such a commit.
++
+The set marks are removed after the sequencer has completed.
+
+
+merge [options] <commit-ish1> <commit-ish2> ... <commit-ishN>::
+	Merge commits into HEAD.
++
+You can refer to a commit by a mark.
++
+If you do not provide a commit message (using `-F`, `-m`, `-C`, `-M`,
+or `--standard`), an editor will be invoked.
++
+See the following list and 'GENERAL OPTIONS' for values of `options`:
+
+	--standard;;
+		Generate a commit message like 'Merge ... into HEAD'.
+		See also linkgit:git-fmt-merge-msg[1].
+
+	-s <strategy>;;
+	--strategy=<strategy>;;
+		Use the given merge strategy.
+		See also linkgit:git-merge[1].
+
+
+pick [options] <commit>::
+	Pick (see linkgit:git-cherry-pick[1]) a commit.
+	Sequencer will pause on conflicts.
++
+See the following list and 'GENERAL OPTIONS' for values of `options`:
+
+	-R;;
+	--reverse;;
+		Revert the changes introduced by pick <commit>.
+
+	--mainline=<n>;;
+		Allow you to pick merge commits by specifying the
+		parent number (beginning from 1) to let sequencer
+		replay the changes relative to the specified parent.
+		+
+This option does not work together with `-R`.
+
+
+patch [options] <file>::
+	If file `<file>` is a pure (diff) patch, then apply the patch.
+	If no `--message` option is given, an editor will
+	be invoked to enter a commit message.
++
+If `<file>` is a linkgit:git-format-patch[1]-formatted patch,
+then the patch will be commited.
++
+See the following list and 'GENERAL OPTIONS' for values of `options`:
+
+	-3;;
+	--3way;;
+		When the patch does not apply cleanly, fall back on
+		3-way merge, if the patch records the identity of blobs
+		it is supposed to apply to, and we have those blobs
+		available locally.
+
+	-k;;
+		Pass `-k` flag to 'git-mailinfo' (see linkgit:git-mailinfo[1]).
+
+	-n;;
+		Pass `-n` flag to 'git-mailinfo' (see linkgit:git-mailinfo[1]).
+
+	--exclude=<path-pattern>;;
+		Do not apply changes to files matching the given path pattern.
+		This can be useful when importing patchsets, where you want to
+		exclude certain files or directories.
+
+	-R;;
+	--reverse;;
+		Apply the patch in reverse.
+
+	--no-add;;
+		When applying a patch, ignore additions made by the
+		patch.  This can be used to extract the common part between
+		two files by first running 'diff' on them and applying
+		the result with this option, which would apply the
+		deletion part but not addition part.
+
+	--whitespace=<action>;;
+		Specify behavior on whitespace errors.
+		See linkgit:git-apply[1] for a detailed description.
+
+	--context=<n>;;
+		Ensure at least <n> lines of surrounding context match before
+		and after each change.  When fewer lines of surrounding
+		context exist they all must match.  By default no context is
+		ever ignored.
+
+	--inaccurate-eof;;
+		Under certain circumstances, some versions of 'diff' do not
+		correctly detect a missing new-line at the end of the file.
+		As a result, patches created by such 'diff' programs do not
+		record incomplete lines correctly.
+		This option adds support for applying such patches by
+		working around this bug.
+
+	-p<n>;;
+		Remove <n> leading slashes from traditional diff paths.
+		The default is 1.
+
+	--unidiff-zero;;
+		By default, 'git-apply' expects that the patch being
+		applied is a unified diff with at least one line of context.
+		This provides good safety measures, but breaks down when
+		applying a diff generated with --unified=0. To bypass these
+		checks use this option.
+
+
+pause::
+	Pause the sequencer process to let you manually make changes.
+	For example, you can re-edit the done commit, split a commit,
+	fix bugs or typos, or make further commits on top of HEAD before
+	continuing.
++
+After you have finished your changes and added them to the index,
+invoke `git sequencer --continue`.
+If you only want to edit the last commit message with an editor,
+run `git commit --amend` (see linkgit:git-commit[1]) before saying
+`--continue`.
+
+
+ref <ref>::
+	Set ref `<ref>` to the current HEAD, see also
+	linkgit:git-update-ref[1].
+
+
+reset <commit-ish>::
+	Go back (see linkgit:git-reset[1] `--hard`) to commit `<commit-ish>`.
+	`<commit-ish>` can also be given by a mark, if prefixed with a colon.
+
+
+squash [options] <commit>::
+	Add the changes introduced by `<commit>` to the last commit.
++
+See 'GENERAL OPTIONS' for values of `options`.
+
+squash [options] --from <mark>::
+	Squash all commits from the given mark into one commit.
+	There must not be any `merge` instructions between the
+	`mark` instruction and this `squash --from` instruction.
++
+See the following list and 'GENERAL OPTIONS' for values of `options`:
+
+	--collect-signoffs;;
+		Collect the Signed-off-by: lines of each commit and
+		add them to the squashed commit message.
+		(Not yet implemented.)
+
+	--include-merges;;
+		Sanity check does not fail if you have merges
+		between HEAD and <mark>.
+
+
+run [--dir=<path>] [--] <cmd> <args>...::
+	Run command `<cmd>` with arguments `<args>`.
+	Pause (conflict-like) if exit status is non-zero.
++
+If `<path>` is set, sequencer will change directory to `<path>`
+before running the command and change back after exit.
+
+
+GENERAL OPTIONS
+---------------
+
+Besides some special options, the instructions
+`patch`, `merge`, `pick`, `squash` take the following general options:
+
+--author=<author>::
+	Override the author name and e-mail address used in the commit.
+	Use `A U Thor <author@example.com>` format.
+
+-C <commit-ish>::
+--reuse-commit=<commit-ish>::
+	Reuse message and authorship data from specified commit.
+
+-M <commit-ish>
+--reuse-message=<commit-ish>::
+	Reuse message from specified commit.
+	Note, that only the commit message is reused
+	and not the authorship information.
+
+-F <file>::
+--file=<file>::
+	Take the commit message from the given file.
+
+-m <msg>::
+--message=<msg>::
+	Use the given `<msg>` as the commit message.
+
+--signoff::
+	Add `Signed-off-by:` line to the commit message (if not yet there),
+	using the committer identity of yourself.
+
+-e::
+--edit::
+	Regardless what commit message options are given,
+	invoke the editor to allow editing of the commit message.
+
+
+RETURN VALUES
+-------------
+
+'git-sequencer' returns:
+
+* `0`, if 'git-sequencer' successfully completed all the instructions
+       in the TODO file or successfully aborted after
+       `git sequencer --abort`,
+* `2`, on user-requested pausing, e.g.
+       when using the `edit` instruction.
+* `3`, on pauses that are not requested, e.g.
+       when there are conflicts to resolve
+       or errors in the TODO file.
+* any other value on error, e.g.
+  running 'git-sequencer' on a bare repository.
+
+
+EXAMPLES
+--------
+
+Here are some examples that shall ease the start with the TODO
+file format.
+Make sure you have understood the `pick` and perhaps the `patch` command.
+Those will not be explained further.
+
+Manually editing and adding commits
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sequencer allows manual intervention in between.
+This can be useful to
+
+* check if everything has gone right so far,
+
+* split commits,
+
+* edit changes and/or commit messages, or
+
+* add further manual commits on top of the current one.
+
+If you want to do one of this, either change `pick` to `edit`, or
+add a `pause` line after the specific instruction.
+
+Note that if you only want to edit the commit message in an
+editor, just use the `--edit` option of your `pick` or `patch`
+instruction.
+
+`HEAD` refers to the last commit being done by sequencer.
+So if you want to split a commit, repeat something like
+
+------------
+$ git reset HEAD^   # Reset index to HEAD^, but keep working tree
+		    # HEAD is the last commit being done by sequencer
+$ git add -p        # Add changes interactively to the index, and/or
+$ git add file1     # Add changes from file1 to the index
+$ git commit        # Commit staged changes
+------------
+
+until you have no changes to commit, and then run
+
+------------
+$ git sequencer --continue   # Continue sequencer process
+------------
+
+Be aware that if there are still staged changes,
+'git-sequencer' will add those changes to the last commit being done.
+
+
+Squashing commits
+~~~~~~~~~~~~~~~~~
+
+Squashing commits means putting the changes of many commits into one.
+If you have two commits `abcdef1` and `fa1afe1` and you want to squash them,
+feed 'git-sequencer' with a TODO file like:
+
+------------
+pick abcdef1
+squash fa1afe1
+------------
+
+Squash will concatenate the commit messages of `abcdef1` and `fa1afe1` and
+invoke an editor so that you can edit them.
+Perhaps you just want to reuse the commit message of `abcdef1` and
+add a signoff.  Then use:
+
+------------
+pick abcdef1
+squash -C abcdef1 --signoff fa1afe1
+------------
+
+You can also squash more than two commits.
+Basically you can do:
+
+------------
+pick A
+squash B
+squash C
+squash D
+squash --message "Make indentation consistent" --signoff E
+------------
+
+If somebody sent you a patch that you have not yet applied and you want
+to apply it and squash it, or if you have a `pick <commit>` list generated
+with something like
+
+------------
+$ git rev-list --no-merges --reverse A^..E | sed -e 's/^/pick /'`
+------------
+
+you can use the `mark` and `squash --from` instructions to
+squash all commits between them into one:
+
+------------
+mark :0
+pick A
+pick B
+pick C
+pick D
+pick E
+squash --message "Make indentation consistent" --signoff --from :0
+------------
+
+
+Branching and Merging
+~~~~~~~~~~~~~~~~~~~~~
+
+Merging branches can easily be done using the `merge` instruction.
+For an example, it is more interesting to branch, pick some commits
+and merge.  Imagine you want to 'copy' this onto the current branch:
+
+------------
+ ...--A1--A2--A3--A4--A5---MA-A6  refs/heads/old
+       |       \          /
+       |        C1--MC--C2  refs/heads/topic
+	\          /
+	 B1--B2--B3
+------------
+
+You want the copy to look exactly like this, except that you
+are not on branch `old`, and you want to call the copy of `topic`
+simply `topic2`.
+Here is a way to achieve this:
+
+------------
+pick A1
+mark :0    # remember this to pick further commits on trunk A
+
+pick B1    # pick commits for trunk B
+pick B2
+pick B3
+mark :1    # remember this for merge
+# Why not just merge B3 later?
+# Then you would merge the original B3 and not the copy.
+# But in this example you want to merge the copy of B3.
+
+reset :0   # go back to the copy of A1
+pick A2    # go on picking the commits of trunk A
+pick A3
+mark :2    # remember this to pick further commits on trunk A
+
+pick C1    # pick commits for trunk C
+merge -C MC :1    # merge trunk B
+pick C2
+ref refs/heads/topic2    # create branch for the C trunk
+
+reset :2   # go back to last commit of trunk A (copy of A3)
+pick A4    # go on picking the commits of trunk A
+pick A5
+merge --standard topic2   # merge trunk C
+pick A6
+------------
+
+
+Proper handling of conflicts
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+First of all, you are encouraged to use linkgit:git-rerere[1]:
+
+------------
+$ git config rerere.enabled true    # enable rerere
+------------
+
+Sequencer invokes 'git-rerere' automatically on conflict.
+
+If you experience conflicts, try
+
+------------
+$ git diff     # Show conflicting code
+$ git status   # Show conflicting files
+------------
+
+Then fix these conflicts using your editor and run
+
+------------
+$ git add file1 file2 file3   # Add modified files to the index
+$ git status                  # Make sure working tree is clean
+$ git sequencer --continue    # Continue sequencer process
+------------
+
+Now assume a conflict happens because you have unproperly edited
+the TODO file.
+
+Imagine your initial TODO file was:
+
+------------
+pick A
+pick C
+pick D
+------------
+
+But you wanted to pick B before C, and now you have this conflict on
+picking C.  You may first have a look at:
+
+------------
+$ git sequencer --status
+------------
+
+This will show you, what has been done, in what step the conflict
+happened and what is still to do, like this:
+
+------------
+Already done (or tried):
+  pick A
+  pick C
+
+Interrupted by conflict at
+  pick C
+
+Still to do:
+  pick D
+
+To abort & restore, invoke:
+    git sequencer --abort
+To continue, invoke:
+    git sequencer --continue
+To skip the current instruction, invoke:
+    git sequencer --skip
+------------
+
+A good way to solve that situation is running
+
+------------
+$ git sequencer --edit
+------------
+
+and change the file to:
+
+------------
+pick B
+pick C
+pick D
+------------
+
+Save the file, and invoke:
+
+------------
+$ git sequencer --skip
+------------
+
+Then the conflict-ridden `pick C` will be skipped and B is picked,
+before C will again be picked.
+
+
+Running tests
+~~~~~~~~~~~~~
+
+Imagine you have test programs within a `tests/` directory in your working
+tree.  But before running your test programs, you have to invoke `make` in
+the root directory of the working tree to compile your project.
+
+If the commit policy of your project says that after every commit the
+software must be able to compile and the test suite must pass, you
+are required to check this after every pick.
+
+This example shows how 'git-sequencer' can assist you:
+
+------------
+pick A      # Fix foo
+run make
+run --dir=tests ./test-foo
+pick B      # Extend bar
+run make
+run --dir tests -- ./test-bar --expensive-tests
+pick C
+run make
+run --dir tests make tests
+------------
+
+Sequencer will be paused, when a run fails (i.e. on non-zero exit status).
+Then it is your turn to fix the problem and make the tests pass.
+
+Note, that on `git sequencer --continue`, 'git-sequencer' will not
+repeat the failed `run` instruction.
+
+
+SEE ALSO
+--------
+
+linkgit:git-add[1],
+linkgit:git-am[1],
+linkgit:git-cherry-pick[1],
+linkgit:git-commit[1],
+linkgit:git-fmt-merge-msg[1],
+linkgit:git-format-patch[1],
+linkgit:git-rebase[1],
+linkgit:git-rerere[1],
+linkgit:git-reset[1],
+linkgit:git-update-ref[1]
+
+
+Authors
+-------
+Written by Stephan Beyer <s-beyer@gmx.net>.
+
+
+Documentation
+-------------
+Documentation by Stephan Beyer and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the linkgit:git[1] suite
-- 
1.5.6.3.391.ge45b

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

* [PATCH 3/3] Add git-sequencer test suite (t3350)
  2008-07-16 20:45 ` [PATCH 2/3] Add git-sequencer documentation Stephan Beyer
@ 2008-07-16 20:45   ` Stephan Beyer
  2008-07-16 20:45     ` Sequencer migration patches Stephan Beyer
  0 siblings, 1 reply; 8+ messages in thread
From: Stephan Beyer @ 2008-07-16 20:45 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Daniel Barkalow, Christian Couder, Junio C Hamano

Mentored-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 t/t3350-sequencer.sh |  838 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 838 insertions(+), 0 deletions(-)
 create mode 100755 t/t3350-sequencer.sh

diff --git a/t/t3350-sequencer.sh b/t/t3350-sequencer.sh
new file mode 100755
index 0000000..3cc7da8
--- /dev/null
+++ b/t/t3350-sequencer.sh
@@ -0,0 +1,838 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Stephan Beyer
+#
+# `setup' is based on t3404* by Johannes Schindelin.
+
+test_description='git sequencer
+
+These are basic usage tests for git sequencer.
+'
+. ./test-lib.sh
+
+# set up two branches like this:
+#
+# A - B - C - D - E
+#   \
+#     F - G - H
+#       \
+#         I
+#
+# where B, D and G touch increment value in file1.
+# The others generate empty file[23456].
+
+SEQDIR=".git/sequencer"
+SEQMARK="refs/sequencer-marks"
+MARKDIR=".git/$SEQMARK"
+
+test_expect_success 'setup' '
+	: >file1 &&
+	git add file1 &&
+	test_tick &&
+	git commit -m "generate empty file1" &&
+	git tag A &&
+	echo 1 >file1 &&
+	test_tick &&
+	git commit -m "write 1 into file1" file1 &&
+	git tag B &&
+	: >file2 &&
+	git add file2 &&
+	test_tick &&
+	git commit -m "generate empty file2" &&
+	git tag C &&
+	echo 2 >file1 &&
+	test_tick &&
+	git commit -m "write 2 into file1" file1 &&
+	git tag D &&
+	: >file3 &&
+	git add file3 &&
+	test_tick &&
+	git commit -m "generate empty file3" &&
+	git tag E &&
+	git checkout -b branch1 A &&
+	: >file4 &&
+	git add file4 &&
+	test_tick &&
+	git commit -m "generate empty file4" &&
+	git tag F &&
+	echo 3 >file1 &&
+	test_tick &&
+	git commit -m "write 3 into file1" file1 &&
+	git tag G &&
+	: >file5 &&
+	git add file5 &&
+	test_tick &&
+	git commit -m "generate empty file5" &&
+	git tag H &&
+	git checkout -b branch2 F &&
+	: >file6 &&
+	git add file6 &&
+	test_tick &&
+	git commit -m "generate empty file6" &&
+	git tag I &&
+	git diff -p --raw C..D >patchD.raw &&
+	git diff -p --raw A..F >patchF.raw &&
+	git format-patch --stdout A..B >patchB &&
+	git format-patch --stdout B..C >patchC &&
+	git format-patch --stdout C..D >patchD &&
+	git format-patch --stdout A..F >patchF &&
+	git format-patch --stdout F..G >patchG
+'
+
+orig_author="$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>"
+
+# Functions to verify exit status of sequencer.
+# Do not just use "test_must_fail git sequencer ..."!
+expect_fail () {
+	"$@"
+	test $? -eq 1
+}
+expect_continue () {
+	"$@"
+	test $? -eq 2
+}
+expect_conflict () {
+	"$@"
+	test $? -eq 3
+}
+
+
+# Other test helpers:
+
+# Test if commit $1 has author $2
+expect_author () {
+	test "$2" = "$(git cat-file commit "$1" |
+		sed -n -e "s/^author \(.*\)> .*$/\1>/p")"
+}
+
+# Test if commit $1 has commit message in file $2
+# Side effect: overwrites actual
+expect_msg () {
+	git cat-file commit "$1" | sed -e "1,/^$/d" >actual &&
+	test_cmp "$2" actual
+}
+
+# Test that no marks are set.
+no_marks_set () {
+	if test -e "$MARKDIR"
+	then
+		rmdir "$MARKDIR"
+	fi
+}
+
+test_expect_success 'fail on empty TODO from stdin' '
+	expect_fail git sequencer <file6 &&
+	! test -d "$SEQDIR"
+'
+
+# Generate fake editor
+#
+# Simple and practical concept:
+#  We use only a small string identifier for "editor sessions".
+#  Each sessions knows what to do and perhaps defines
+#  which session to choose next.
+echo "#!$SHELL_PATH" >fake-editor.sh
+cat >>fake-editor.sh <<\EOF
+test -f fake-editor-session || exit 1
+#test -t 1 || exit 1
+# This test could be useful, but as the test-lib is not always
+# verbose, this will fail.
+next=ok
+read this <fake-editor-session
+case "$this" in
+commitmsg)
+	echo 'echo 2 >file1'
+	;;
+squashCE)
+	echo 'generate file2 and file3'
+	;;
+squashCI)
+	echo 'generate file2 and file6'
+	next=squashDCE
+	;;
+squashDCE)
+	echo 'generate file2 and file3 and write 2 into file1'
+	next=merge1
+	;;
+merge1)
+	echo 'A typed merge message.'
+	;;
+merge2)
+	test "$(sed -n -e 1p "$1")" = 'test merge' &&
+		echo 'cleanup merge' ||
+		echo error
+	sed -e 1d "$1"
+	;;
+editXXXXXXXXX)
+	printf 'last edited'
+	;;
+edit*)
+	printf 'edited: '
+	cat "$1"
+	next="${this}X"
+	;;
+nochange)
+	cat "$1"
+	;;
+ok|fail)
+	echo '-- THIS IS UNEXPECTED --'
+	next=fail
+	;;
+*)
+	echo 'I do not know.'
+	;;
+esac >"$1".tmp
+mv "$1".tmp "$1"
+echo $next >fake-editor-session
+exit 0
+EOF
+chmod a+x fake-editor.sh
+test_set_editor "$(pwd)/fake-editor.sh"
+
+next_session () {
+	echo "$1" >fake-editor-session
+}
+
+# check if fake-editor-session is ok.
+# If "$1" is set to anything, it will set the
+# next session to "ok", which is nice for
+# test_expect_failure.
+session_ok () {
+	test "ok" = $(cat fake-editor-session)
+	ret=$?
+	test -n "$1" && next_session ok
+	return $ret
+}
+
+
+cat >todotest1 <<EOF
+pick C
+squash E
+ref refs/tags/CE
+EOF
+
+test_expect_success '"pick", "squash", "ref" from stdin' '
+	next_session squashCE &&
+	git sequencer <todotest1 &&
+	! test -d "$SEQDIR" &&
+	session_ok &&
+	test -f file2 &&
+	test -f file3 &&
+	test $(git rev-parse CE) = $(git rev-parse HEAD) &&
+	test $(git rev-parse I) = $(git rev-parse HEAD^)
+'
+
+cat >todotest2 <<EOF
+# This is a test
+
+reset I  # go back to I
+
+EOF
+
+test_expect_success '"reset" from file with comments and blank lines' '
+	git sequencer todotest2 &&
+	session_ok &&
+	test $(git rev-parse I) = $(git rev-parse HEAD)
+'
+
+cat >todotest1 <<EOF
+pick C
+EOF
+
+test_expect_success '--onto <branch> keeps branch' '
+	git checkout -b test-branch A &&
+	git checkout master &&
+	git sequencer --onto test-branch <todotest1 &&
+	session_ok &&
+	test "$(git symbolic-ref -q HEAD)" = "refs/heads/test-branch" &&
+	test "$(git rev-parse test-branch^)" = "$(git rev-parse A)"
+'
+
+test_expect_success '--onto commit (detached HEAD) works' '
+	git sequencer --onto A <todotest1 &&
+	session_ok &&
+	test_must_fail git symbolic-ref -q HEAD &&
+	test "$(git rev-parse HEAD)" = "$(git rev-parse test-branch)"
+'
+
+echo 'pick -R C' >>todotest1
+
+test_expect_success 'pick -R works' '
+	git checkout A &&
+	git sequencer todotest1 &&
+	session_ok &&
+	! test -f file2
+'
+
+mkdir testdir
+cat >testdir/script <<EOF
+#!/bin/sh
+test -s ../file1
+EOF
+chmod 755 testdir/script
+cat >todotest1 <<EOF
+run -- test -s file1  # this will fail
+pick B
+run --dir testdir -- test -s ../file1
+pick D
+run --dir=testdir ./script
+EOF
+
+test_expect_success '"run" insn works' '
+	git checkout A &&
+	expect_conflict git sequencer todotest1 &&
+	: >newfile &&
+	git add newfile &&
+	next_session nochange &&
+	git sequencer --continue &&
+	session_ok &&
+	test -f newfile
+'
+
+echo thisdoesnotexist >>todotest1
+
+test_expect_success 'junk is conflict' '
+	git checkout A &&
+	expect_conflict git sequencer todotest1 &&
+	test -d "$SEQDIR" &&
+	git sequencer --abort &&
+	! test -d "$SEQDIR" &&
+	session_ok &&
+	test $(git rev-parse A) = $(git rev-parse HEAD)
+'
+
+GIT_AUTHOR_NAME="Another 'ant' Thor"
+GIT_AUTHOR_EMAIL="a.thor@example.com"
+GIT_COMMITTER_NAME="Co M Miter"
+GIT_COMMITTER_EMAIL="c.miter@example.com"
+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL
+yet_another="Max Min <mm@example.com>"
+
+cat >todotest1 <<EOF
+patch patchB      # write 1 into file1
+patch -k patchC   # generate file2
+patch patchD.raw  # write 2 into file1
+EOF
+
+echo 'write 1 into file1' >expected1
+echo '[PATCH] generate empty file2' >expected2
+echo 'echo 2 >file1' >expected3
+
+test_expect_success '"patch" insn works' '
+	git checkout A &&
+	next_session commitmsg &&
+	git sequencer todotest1 &&
+	! test -d "$SEQDIR" &&
+	session_ok &&
+	test "$(git rev-parse HEAD~3)" = "$(git rev-parse A)" &&
+	test "$(git show HEAD~2:file1)" = "1" &&
+	test -z "$(git show HEAD^:file2)" &&
+	test "$(git show HEAD:file1)" = "2" &&
+	expect_author HEAD~2 "$orig_author" &&
+	expect_author HEAD~1 "$orig_author" &&
+	expect_author HEAD~0 "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" &&
+	expect_msg HEAD~2 expected1 &&
+	expect_msg HEAD~1 expected2 &&
+	expect_msg HEAD~0 expected3
+'
+
+cat >todotest1 <<EOF
+pick B	# write 1 into file1
+pause
+pick C	# generate file2
+EOF
+
+echo 'generate empty file2' >expected1
+echo 'write 1 into file1' >expected2
+
+test_expect_success "pick ; pause insns and --continue works" '
+	git checkout A &&
+	expect_continue git sequencer todotest1 &&
+	session_ok &&
+	echo 5 >file1 &&
+	git add file1 &&
+	next_session nochange &&
+	git sequencer --continue &&
+	test "$(git show HEAD:file1)" = 5 &&
+	test -z "$(git show HEAD:file2)" &&
+	expect_msg HEAD expected1 &&
+	expect_msg HEAD^ expected2 &&
+	session_ok
+'
+
+cat >todotest1 <<EOF
+edit B	# write 1 into file1
+pick C	# generate file2
+EOF
+
+test_expect_success "edit insn and --continue works" '
+	git checkout A &&
+	expect_continue git sequencer todotest1 &&
+	session_ok &&
+	echo 5 >file1 &&
+	git add file1 &&
+	next_session nochange &&
+	git sequencer --continue &&
+	test "$(git show HEAD:file1)" = 5 &&
+	test -z "$(git show HEAD:file2)" &&
+	expect_msg HEAD expected1 &&
+	expect_msg HEAD^ expected2 &&
+	session_ok
+'
+
+cat >todotest1 <<EOF
+patch patchB	# write 1 into file1
+pick H		# generate file5
+mark :1
+patch patchC	# generate file2
+squash I	# generate file6
+patch patchD	# write 2 into file1
+ref refs/tags/CID
+mark :2
+reset :1	# reset to new H
+patch patchD	# write 2 into file1
+squash CE	# generate file2 and file3
+ref refs/tags/DCE
+merge :2	# merge :2 into HEAD
+patch patchF	# generate file4
+EOF
+
+test_expect_success 'all insns work without options' '
+	git checkout A &&
+	next_session squashCI &&
+	no_marks_set &&
+	git sequencer todotest1 &&
+	no_marks_set &&
+	test "$(git show HEAD:file1)" = "2" &&
+	test -z "$(git show HEAD:file2)" &&
+	test -z "$(git show HEAD:file3)" &&
+	test -z "$(git show HEAD:file4)" &&
+	test -z "$(git show HEAD:file5)" &&
+	test -z "$(git show HEAD:file6)" &&
+	echo "$(git rev-parse DCE)" >expected &&
+	echo "$(git rev-parse CID)" >>expected &&
+	git cat-file commit HEAD^ | sed -n -e "s/^parent //p" >actual &&
+	test_cmp expected actual &&
+	session_ok
+'
+
+cat >todotest1 <<EOF
+merge --standard DCE
+EOF
+
+echo "Merge DCE into HEAD" >expected1
+
+test_expect_success 'merge --standard works' '
+	git checkout CID &&
+	git sequencer todotest1 &&
+	expect_msg HEAD expected1 &&
+	session_ok
+'
+
+cat >todotest1 <<EOF
+merge --standard --message="foo" DCE
+EOF
+
+
+test_expect_success 'merge --standard --message="foo" is conflict' '
+	git checkout CID &&
+	expect_conflict git sequencer todotest1 &&
+	git sequencer --abort &&
+	session_ok
+'
+
+for command in 'pick ' 'patch patch' 'squash ' 'merge --standard '
+do
+	cat >todotest1 <<EOF
+patch patchB	# 1 into file1
+${command}G	# 3 into file1
+patch -3 patchF	# empty file4
+EOF
+
+	test_expect_success "conflict test: ${command%% *} and --abort" '
+		git checkout A &&
+		expect_conflict git sequencer todotest1 &&
+		session_ok &&
+		test -d "$SEQDIR" &&
+		git sequencer --abort &&
+		session_ok &&
+		test $(git rev-parse HEAD) = $(git rev-parse A)
+	'
+
+	test_expect_success "conflict test: ${command%% *} and --continue" '
+		git checkout A &&
+		expect_conflict git sequencer todotest1 &&
+		session_ok &&
+		test -d "$SEQDIR" &&
+		## XXX: It would be perfect if we could remove the if
+		{ if test "${command%% *}" != "patch"
+		then grep "^<<<<<<<" file1 ; fi } &&
+		echo 3 >file1 &&
+		git add file1 &&
+		next_session nochange &&
+		git sequencer --continue &&
+		session_ok &&
+		! test -d "$SEQDIR" &&
+		test "$(git show HEAD:file1)" = "3" &&
+		test -f file4
+	'
+
+	test_expect_success "conflict test: ${command%% *} and --skip" '
+		git checkout A &&
+		expect_conflict git sequencer todotest1 &&
+		session_ok &&
+		test -d "$SEQDIR" &&
+		git sequencer --skip &&
+		session_ok &&
+		! test -d "$SEQDIR" &&
+		test "$(git show HEAD:file1)" = "1" &&
+		test -f file4
+	'
+done
+
+echo 'file5-gen' >commitmsg
+
+cat >todotest1 <<EOF
+patch --signoff patchB
+pause
+pick --author="$yet_another" --file="commitmsg" --signoff H
+mark :1
+patch --message="file2-gen" patchC
+squash --signoff --author="$yet_another" I
+pause
+patch --message="echo 2 >file1" patchD
+mark :2
+reset :1
+patch --author="$yet_another" patchD
+squash --signoff --message="generate file[23]" CE
+merge --signoff --message="test merge" --author="$yet_another" :2
+pause
+ref refs/tags/a_merge
+patch --message="Generate file4 and write 23 into it" patchF.raw
+pause
+pick I
+EOF
+
+cat >expected1 <<EOF
+write 1 into file
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+EOF
+
+test_expect_success 'insns work with options and another author 1' '
+	git checkout A &&
+	no_marks_set &&
+
+	# patch --signoff patchB  # write 1 into file1
+	# pause
+	expect_continue git sequencer todotest1 &&
+	test "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" = \
+		"$(git cat-file commit HEAD | grep "^Signed-off-by")" &&
+	expect_author HEAD "$orig_author" &&
+	test -d "$SEQDIR" &&
+	session_ok
+'
+
+cat >expected1 <<EOF
+file5-gen
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+EOF
+
+cat >expected2 <<EOF
+file2-gen
+
+generate empty file6
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+EOF
+
+test_expect_success 'insns work with options and another author 2' '
+	: >file7 &&
+	git add file7 &&
+	next_session nochange &&
+	git commit --amend &&
+	session_ok &&
+
+	next_session nochange &&
+	expect_continue git sequencer --continue &&
+	session_ok &&
+
+	# amended commit
+	test "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" = \
+		"$(git cat-file commit HEAD^^ | grep "^Signed-off-by")" &&
+	expect_author HEAD^^ "$orig_author" &&
+	test -z "$(git show HEAD:file7)" &&
+
+	# pick --author="$yet_another" --file="commitmsg" --signoff H
+	expect_author HEAD^ "$yet_another" &&
+	expect_msg HEAD^ expected1 &&
+	test -z "$(git show HEAD:file5)" &&
+
+	# mark :1
+	test "$(git rev-parse "$SEQMARK/1")" = "$(git rev-parse HEAD^)" &&
+
+	# patch --message="file2-gen" patchC
+	# squash --signoff --author="$yet_another" I # generate file6
+	# pause
+	test -z "$(git show HEAD:file2)" &&
+	test -z "$(git show HEAD:file6)" &&
+	git ls-files | grep "^file2" &&
+	git ls-files | grep "^file6" &&
+	expect_msg HEAD expected2 &&
+	expect_author HEAD "$yet_another"
+'
+
+echo 'echo 2 >file1' >expected1
+
+cat >expected2 <<EOF
+generate file[23]
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+EOF
+
+cat >expected3 <<EOF
+test merge
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+EOF
+
+cat >expected4 <<EOF
+file1
+file2
+file3
+file5
+file6
+file7
+EOF
+
+test_expect_success 'insns work with options and another author 3' '
+	# do not change anything
+	expect_continue git sequencer --continue &&
+	session_ok &&
+
+	# patch --message="echo 2 >file1" patchD
+	# mark :2
+	commit="$(git rev-parse --verify "$SEQMARK/2")" &&
+	expect_author "$commit" "$orig_author" &&
+	expect_msg "$commit" expected1 &&
+
+	# reset :1
+	# patch --author="$yet_another" patchD # write 2 into file1
+	# squash --signoff --message="generate file[23]" CE
+	expect_author HEAD^ "$yet_another" &&
+	expect_msg HEAD^ expected2 &&
+
+	# merge --signoff --message="test merge" --author="$yet_another" :2
+	# pause
+	expect_author HEAD "$yet_another" &&
+	expect_msg HEAD expected3 &&
+	git ls-files >actual &&
+	test_cmp expected4 actual
+'
+
+cat >expected_merge <<EOF
+cleanup merge
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+EOF
+
+echo 'Generate file4 and write 23 into it' >expected2
+
+test_expect_success 'insns work with options and another author 4' '
+	git rm file5 file6 file7 &&
+	next_session merge2 &&
+	expect_continue git sequencer --continue &&
+	session_ok &&
+
+	# ref refs/tags/a_merge
+	expect_author a_merge "$yet_another" &&
+	expect_msg a_merge expected_merge &&
+
+	# patch --message="Generate file4 and write 23 into it" patchF.raw
+	# pause
+	git ls-files | grep "^file4" &&
+	echo 23 >file4 &&
+	git add file4 &&
+	next_session nochange &&
+	git sequencer --continue &&
+	session_ok &&
+	no_marks_set &&
+	test "$(git show HEAD:file1)" = "2" &&
+	test "$(git show HEAD:file4)" = "23" &&
+	expect_msg HEAD^ expected2 &&
+	expect_author HEAD^ "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" &&
+
+	# pick I
+	test -z "$(git show HEAD:file6)" &&
+	git ls-files | grep "^file6" &&
+	session_ok
+'
+
+# almost the same to test --quiet
+cat >todotest1 <<EOF
+patch patchB
+pick H
+mark :1
+patch patchC
+squash --message="a squash" I
+patch patchD
+mark :2
+reset :1
+patch patchD
+squash --message="another squash" CE
+merge --message="test merge" :2
+pause
+patch patchF
+EOF
+
+test_expect_failure '--quiet works' '
+	git checkout A &&
+	expect_continue git sequencer --quiet todotest1 >actual &&
+	session_ok &&
+	! test -s actual
+'
+
+test_expect_failure '--quiet works on continue' '
+	git sequencer --continue >>actual &&
+	session_ok &&
+	! test -s actual
+'
+
+echo 'merge --strategy=ours --reuse-commit=a_merge branch1 branch2 CE CID' >todotest1
+
+test_expect_success 'merge multiple branches and --reuse-commit works' '
+	git checkout -b merge-multiple master &&
+	git sequencer todotest1 &&
+	session_ok &&
+	expect_msg HEAD expected_merge &&
+	git rev-parse HEAD^ >expected &&
+	git rev-parse branch1 >>expected &&
+	git rev-parse branch2 >>expected &&
+	git rev-parse CE >>expected &&
+	git rev-parse CID >>expected &&
+	git cat-file commit HEAD | sed -n -e "s/^parent //p" >actual &&
+	test_cmp expected actual &&
+	! test -f file6
+'
+
+echo 'pick --mainline=5 merge-multiple' >todotest1
+
+test_expect_success 'pick --mainline works' '
+	git checkout -b mainline CID &&
+	git sequencer todotest1 &&
+	session_ok &&
+	expect_msg HEAD expected_merge &&
+	! test -f file6 &&
+	test -f file3 &&
+	test -f file2 &&
+	test "$(git show HEAD:file1)" = 2
+'
+
+cat >todotest1 <<EOF
+pick C		# file2
+mark :1
+patch patchB	# write 1 into file1
+patch patchD	# write 2 into file1
+pick I		# file6
+squash --message="2 in file1 and file6 exists" --signoff --from :1
+EOF
+
+cat >expected1 <<EOF
+2 in file1 and file6 exists
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+EOF
+
+test_expect_success 'squash --from works' '
+	git checkout A &&
+	git sequencer <todotest1 &&
+	session_ok &&
+	test "$(git rev-parse A)" = "$(git rev-parse HEAD~2)" &&
+	test "$(git show HEAD:file1)" = "2" &&
+	test -z "$(git show HEAD:file6)" &&
+	expect_msg HEAD expected1
+'
+
+cat >todotest1 <<EOF
+patch patchB	# write 1 into file1
+pick H		# generate file5
+mark :1
+patch patchC	# generate file2
+squash --message="file5" I	# generate file6
+patch patchD	# write 2 into file1
+mark :2
+reset :1	# reset to new H
+patch patchD	# write 2 into file1
+squash --message="CE" CE	# generate file2 and file3
+merge --standard :2	# merge :2 into HEAD
+patch patchF	# generate file4
+EOF
+cp todotest1 todotest2
+cat todotest1 | sed -e 's/^\(patch\|pick\|squash\|merge\) /&--edit /' >todotest3
+echo 'squash --message="doesnt work either" --from :1' >>todotest1
+echo 'squash --include-merges --message="stupid" --from :1' >>todotest2
+
+test_expect_success 'squash --from conflicts with merge in between' '
+	git checkout A &&
+	expect_conflict git sequencer todotest1 &&
+	git sequencer --abort &&
+	session_ok &&
+	! test -d "$SEQDIR"
+'
+
+test_expect_success 'squash --include-merges --from succeeds with merge in between' '
+	git checkout A &&
+	git sequencer todotest2 &&
+	session_ok &&
+	test "$(git rev-parse HEAD~3)" = "$(git rev-parse A)"
+'
+
+test_expect_success 'patch|pick|squash|merge --edit works' '
+	git checkout A &&
+	next_session editX &&
+	git sequencer todotest3 &&
+	session_ok
+'
+
+cat >todotest1 <<EOF
+patch patchB
+pause
+EOF
+
+test_expect_success 'batch mode fails on pause insn' '
+	git checkout A &&
+	expect_fail git sequencer --batch todotest1 &&
+	session_ok &&
+	! test -d "$SEQDIR"
+'
+
+cat >todotest1 <<EOF
+patch patchB
+pick G
+EOF
+
+test_expect_success 'batch mode fails on conflict' '
+	git checkout A &&
+	expect_fail git sequencer --batch <todotest1 &&
+	session_ok &&
+	! test -d "$SEQDIR" &&
+	test -z "$(git show HEAD:file1)"
+'
+
+cat >todotest1 <<EOF
+patch patchB
+pause
+EOF
+
+test_expect_success '--caller works' '
+	git checkout A &&
+	expect_continue git sequencer \
+		--caller="this works|abrt||skip" todotest1 &&
+	expect_fail git sequencer --abort &&
+	expect_fail git sequencer --skip &&
+	git sequencer --continue &&
+	session_ok
+'
+
+test_done
-- 
1.5.6.3.391.ge45b

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

* Sequencer migration patches
  2008-07-16 20:45   ` [PATCH 3/3] Add git-sequencer test suite (t3350) Stephan Beyer
@ 2008-07-16 20:45     ` Stephan Beyer
  2008-07-16 20:45       ` [PATCH 1/2] Migrate git-am to use git-sequencer Stephan Beyer
                         ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Stephan Beyer @ 2008-07-16 20:45 UTC (permalink / raw)
  To: git
  Cc: Stephan Beyer, Daniel Barkalow, Christian Couder, Junio C Hamano,
	Johannes Schindelin

Hi,

the following patches migrate git-am and git-rebase--interactive to
git-sequencer.

But there are some issues I want to mention.

I've compared performance of original rebase and am with the
sequencer-based ones and the sequencer-based ones perform fairly bad.
For example, applying 45 patches with git-am took 3 seconds using the
original and 8 seconds using the sequencer-based one.
Rebasing 100 commits takes 10.1 seconds instead of only 4.8 seconds
on my test machine.

I expect that the builtin-sequencer performs muuuch better.
But as long as there is no builtin-sequencer these patches should
perhaps not be applied.
Well, I could offer to provide patches to put sequencer-based git-am and
git-rebase-i scripts to contrib/examples/ (or contrib/sequencer-examples/
or something else). ;-)

Regards,
  Stephan


Stephan Beyer (2):
  Migrate git-am to use git-sequencer
  Introduce git am --abort

 Documentation/git-am.txt               |    5 +-
 Documentation/git-rerere.txt           |    2 +-
 contrib/completion/git-completion.bash |    2 +-
 git-am.sh                              |  617 +++++++++++---------------------
 git-rebase.sh                          |    7 +-
 t/t4150-am.sh                          |   27 +-
 6 files changed, 244 insertions(+), 416 deletions(-)


Stephan Beyer (1):
  Migrate rebase-i to sequencer

 git-rebase--interactive.sh    |  438 ++++++++++-------------------------------
 t/t3404-rebase-interactive.sh |    9 +-
 2 files changed, 113 insertions(+), 334 deletions(-)

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

* [PATCH 1/2] Migrate git-am to use git-sequencer
  2008-07-16 20:45     ` Sequencer migration patches Stephan Beyer
@ 2008-07-16 20:45       ` Stephan Beyer
  2008-07-16 20:45         ` [PATCH 2/2] Introduce git am --abort Stephan Beyer
  2008-07-16 20:45       ` [PATCH] Migrate rebase-i to sequencer Stephan Beyer
  2008-07-17 13:05       ` Sequencer migration patches Stephan Beyer
  2 siblings, 1 reply; 8+ messages in thread
From: Stephan Beyer @ 2008-07-16 20:45 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Daniel Barkalow, Christian Couder, Junio C Hamano

In principle a migration to git-sequencer is straightforward:
Put all the mail from the mbox or Maildir into .git/rebase and
let the "patch" instruction of sequencer do the rest of the
work.

The git am --interactive part is a little more tricky.
To get this working, "pause" instructions are put after every
"patch" instruction and then be_interactive() swoops in,
that allows the user to input his choice.

Also a slight behavior change, that can be seen in the diff of
the test cases, should be mentioned: If git-am has nothing to do,
the user does not have to remove .git/rebase or run git-am --skip
manually. It automatically aborts instead, which seems to be
an improvement.

Mentored-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 git-am.sh     |  612 ++++++++++++++++++++-------------------------------------
 git-rebase.sh |    1 +
 t/t4150-am.sh |    6 +-
 3 files changed, 214 insertions(+), 405 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index cc8787b..e36f22c 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -33,102 +33,143 @@ cd_to_toplevel
 git var GIT_COMMITTER_IDENT >/dev/null ||
 	die "You need to set your committer info first"
 
-stop_here () {
-    echo "$1" >"$dotest/next"
-    exit 1
+cleanup () {
+	git gc --auto
+	rm -fr "$dotest"
 }
 
-stop_here_user_resolve () {
-    if [ -n "$resolvemsg" ]; then
-	    printf '%s\n' "$resolvemsg"
-	    stop_here $1
-    fi
-    cmdline=$(basename $0)
-    if test '' != "$interactive"
-    then
-        cmdline="$cmdline -i"
-    fi
-    if test '' != "$threeway"
-    then
-        cmdline="$cmdline -3"
-    fi
-    echo "When you have resolved this problem run \"$cmdline --resolved\"."
-    echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
-
-    stop_here $1
+die_abort () {
+	cleanup
+	die "$1"
 }
 
-go_next () {
-	rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
-		"$dotest/patch" "$dotest/info"
-	echo "$next" >"$dotest/next"
-	this=$next
-}
+be_interactive () {
+	msg="$GIT_DIR/sequencer/message"
+	patch="$GIT_DIR/sequencer/patch"
+	author_script="$GIT_DIR/sequencer/author-script"
+	# we rely on sequencer here
+
+	test -t 0 ||
+		die "cannot be interactive without stdin connected to a terminal."
+	action=$(cat "$dotest/interactive")
+	while test "$action" = again
+	do
+		echo
+		echo "Commit Body is:"
+		echo "--------------------------"
+		cat "$msg"
+		echo "--------------------------"
+		if test -z "$1"
+		then
+			printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+		else
+			echo 'Patch does not apply cleanly!'
+			printf "Apply+fix? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+		fi
 
-cannot_fallback () {
-	echo "$1"
-	echo "Cannot fall back to three-way merge."
-	exit 1
+		read reply
+		case "$reply" in
+		[yY]*)
+			return 0
+			;;
+		[nN]*)
+			if test -z "$1"
+			then
+				git reset -q --hard HEAD^
+			else
+				git reset -q --hard HEAD
+			fi
+			return 1
+			;;
+		[eE]*)
+			git_editor "$msg"
+			git commit --amend --file="$msg" --no-verify >/dev/null
+			;;
+		[vV]*)
+			LESS=-S ${PAGER:-less} "$patch"
+			;;
+		[aA]*)
+			echo 'accept' >"$dotest/interactive"
+			return 0
+			;;
+		*)
+			:
+			;;
+		esac
+	done
+	test "$action" = accept &&
+		sed -n -e '1s/^/Applying &/p' <"$msg"
+	return 0
 }
 
-fall_back_3way () {
-    O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
-
-    rm -fr "$dotest"/patch-merge-*
-    mkdir "$dotest/patch-merge-tmp-dir"
-
-    # First see if the patch records the index info that we can use.
-    git apply --build-fake-ancestor "$dotest/patch-merge-tmp-index" \
-	"$dotest/patch" &&
-    GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
-    git write-tree >"$dotest/patch-merge-base+" ||
-    cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge."
-
-    echo Using index info to reconstruct a base tree...
-    if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
-	git apply $binary --cached <"$dotest/patch"
-    then
-	mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
-	mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
-    else
-        cannot_fallback "Did you hand edit your patch?
-It does not apply to blobs recorded in its index."
-    fi
-
-    test -f "$dotest/patch-merge-index" &&
-    his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git write-tree) &&
-    orig_tree=$(cat "$dotest/patch-merge-base") &&
-    rm -fr "$dotest"/patch-merge-* || exit 1
-
-    echo Falling back to patching base and 3-way merge...
+print_continue_info () {
+	echo 'When you have resolved this problem run "git am --resolved".'
+	echo 'If you would prefer to skip this patch, instead run "git am --skip".'
+}
 
-    # This is not so wrong.  Depending on which base we picked,
-    # orig_tree may be wildly different from ours, but his_tree
-    # has the same set of wildly different changes in parts the
-    # patch did not touch, so recursive ends up canceling them,
-    # saying that we reverted all those changes.
+run_sequencer () {
+	git sequencer $noadvice --caller='git am||--resolved|--skip' "$@"
+	case "$?" in
+	0)
+		cleanup
+		exit 0
+		;;
+	2|3)
+		ret=$?
+		print_continue_info
+		exit $(($ret-2))
+		;;
+	*)
+		die_abort 'git-sequencer died unexpected. Aborting.'
+		;;
+	esac
+}
 
-    eval GITHEAD_$his_tree='"$FIRSTLINE"'
-    export GITHEAD_$his_tree
-    git-merge-recursive $orig_tree -- HEAD $his_tree || {
-	    git rerere
-	    echo Failed to merge in the changes.
-	    exit 1
-    }
-    unset GITHEAD_$his_tree
+run_sequencer_i () {
+	command="$1"
+	while true
+	do
+		output=$(git sequencer $noadvice \
+			--caller='git am -i||--resolved|--skip' \
+			$command 2>&1 >/dev/null)
+		noadvice=
+		case "$?" in
+		0)
+			cleanup
+			exit 0
+			;;
+		2)
+			be_interactive
+			;;
+		3)
+			be_interactive conflict
+			if test $? -eq 0
+			then
+				printf '%s\n' "$output" 1>&2
+				print_continue_info
+				exit 1
+			fi
+			;;
+		*)
+			die_abort "$output"
+			;;
+		esac
+		command=--continue
+	done
 }
 
 prec=4
 dotest="$GIT_DIR/rebase"
+todofile="$dotest/todo"
 sign= utf8=t keep= skip= interactive= resolved= binary= rebasing=
-resolvemsg= resume=
-git_apply_opt=
+resolvemsg=
+opts=
 
 while test $# != 0
 do
 	case "$1" in
 	-i|--interactive)
-		interactive=t ;;
+		interactive=_i ;;
 	-b|--binary)
 		binary=t ;;
 	-3|--3way)
@@ -153,9 +194,9 @@ do
 	--resolvemsg)
 		shift; resolvemsg=$1 ;;
 	--whitespace)
-		git_apply_opt="$git_apply_opt $1=$2"; shift ;;
+		opts="$opts $1=$2"; shift ;;
 	-C|-p)
-		git_apply_opt="$git_apply_opt $1$2"; shift ;;
+		opts="$opts $1$2"; shift ;;
 	--)
 		shift; break ;;
 	*)
@@ -164,348 +205,117 @@ do
 	shift
 done
 
-# If the dotest directory exists, but we have finished applying all the
-# patches in them, clear it out.
-if test -d "$dotest" &&
-   last=$(cat "$dotest/last") &&
-   next=$(cat "$dotest/next") &&
-   test $# != 0 &&
-   test "$next" -gt "$last"
-then
-   rm -fr "$dotest"
-fi
-
 if test -d "$dotest"
 then
-	case "$#,$skip$resolved" in
-	0,*t*)
-		# Explicit resume command and we do not have file, so
-		# we are happy.
-		: ;;
-	0,)
-		# No file input but without resume parameters; catch
-		# user error to feed us a patch from standard input
-		# when there is already $dotest.  This is somewhat
-		# unreliable -- stdin could be /dev/null for example
-		# and the caller did not intend to feed us a patch but
-		# wanted to continue unattended.
-		tty -s
-		;;
-	*)
-		false
-		;;
-	esac ||
-	die "previous rebase directory $dotest still exists but mbox given."
-	resume=yes
-else
-	# Make sure we are not given --skip nor --resolved
-	test ",$skip,$resolved," = ,,, ||
-		die "Resolve operation not in progress, we are not resuming."
+	test "$#" != 0 &&
+		die "previous rebase directory $dotest still exists but mbox given."
+
+	test -f "$dotest/interactive" &&
+		interactive=_i action=$(cat "$dotest/interactive")
 
-	# Start afresh.
-	mkdir -p "$dotest" || exit
+	# No file input but without resume parameters; catch
+	# user error to feed us a patch from standard input
+	# when there is already $dotest.  This is somewhat
+	# unreliable -- stdin could be /dev/null for example
+	# and the caller did not intend to feed us a patch but
+	# wanted to continue unattended.
+	test -z "$resolved$skip" && tty -s
 
-	if test -n "$prefix" && test $# != 0
-	then
-		first=t
-		for arg
-		do
-			test -n "$first" && {
-				set x
-				first=
-			}
-			case "$arg" in
-			/*)
-				set "$@" "$arg" ;;
-			*)
-				set "$@" "$prefix$arg" ;;
-			esac
-		done
-		shift
-	fi
-	git mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" ||  {
-		rm -fr "$dotest"
-		exit 1
-	}
+	test -n "$resolved" && run_sequencer$interactive --continue
+	test -n "$skip" && run_sequencer$interactive --skip
 
-	# -b, -s, -u, -k and --whitespace flags are kept for the
-	# resuming session after a patch failure.
-	# -3 and -i can and must be given when resuming.
-	echo "$binary" >"$dotest/binary"
-	echo " $ws" >"$dotest/whitespace"
-	echo "$sign" >"$dotest/sign"
-	echo "$utf8" >"$dotest/utf8"
-	echo "$keep" >"$dotest/keep"
-	echo 1 >"$dotest/next"
-	if test -n "$rebasing"
-	then
-		: >"$dotest/rebasing"
-	else
-		: >"$dotest/applying"
-		git update-ref ORIG_HEAD HEAD
-	fi
+	die "$dotest still exists. Use git am --skip/--resolved."
 fi
 
-case "$resolved" in
-'')
-	files=$(git diff-index --cached --name-only HEAD --) || exit
-	if [ "$files" ]; then
-	   echo "Dirty index: cannot apply patches (dirty: $files)" >&2
-	   exit 1
-	fi
-esac
+# Make sure we are not given --skip nor --resolved
+test -z "$resolved$skip" ||
+	die 'git-am is not in progress. You cannot use --skip/--resolved then.'
 
-if test "$(cat "$dotest/binary")" = t
-then
-	binary=--allow-binary-replacement
-fi
-if test "$(cat "$dotest/utf8")" = t
-then
-	utf8=-u
-else
-	utf8=-n
-fi
-if test "$(cat "$dotest/keep")" = t
-then
-	keep=-k
-fi
-ws=`cat "$dotest/whitespace"`
-if test "$(cat "$dotest/sign")" = t
+# sequencer running?
+git sequencer --status >/dev/null 2>&1 &&
+	die "Sequencer already started. Cannot run git-am."
+
+# Start afresh.
+mkdir -p "$dotest" ||
+	die "Could not create $dotest directory."
+
+if test -n "$prefix" && test $# != 0
 then
-	SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
-			s/>.*/>/
-			s/^/Signed-off-by: /'
-		`
-else
-	SIGNOFF=
+	first=t
+	for arg
+	do
+		test -n "$first" && {
+			set x
+			first=
+		}
+		case "$arg" in
+		/*)
+			set "$@" "$arg" ;;
+		*)
+			set "$@" "$prefix$arg" ;;
+		esac
+	done
+	shift
 fi
+last=$(git mailsplit -d"$prec" -o"$dotest" -b -- "$@") ||  {
+	cleanup
+	exit 1
+}
+this=1
 
-last=`cat "$dotest/last"`
-this=`cat "$dotest/next"`
-if test "$skip" = t
-then
-	git rerere clear
-	this=`expr "$this" + 1`
-	resume=
+files=$(git diff-index --cached --name-only HEAD --) || exit
+if [ "$files" ]; then
+	echo "Dirty index: cannot apply patches (dirty: $files)" >&2
+	exit 1
 fi
 
-if test "$this" -gt "$last"
+test -n "$interactive" && echo 'again' >"$dotest/interactive"
+
+# converting our options to git-sequencer file insn options
+test -n "$binary" && opts="$opts --binary"
+test -n "$utf8" || opts="$opts -n"
+test -n "$keep" && opts="$opts -k"
+test -n "$sign" && opts="$opts --signoff"
+test -n "$threeway" && opts="$opts -3"
+
+# these files are created for tab completion scripts
+if test -n "$rebasing"
 then
-	echo Nothing to do.
-	rm -fr "$dotest"
-	exit
+	: >"$dotest/rebasing"
+else
+	: >"$dotest/applying"
+	git update-ref ORIG_HEAD HEAD
 fi
 
+# create todofile
+: > "$todofile" ||
+	die_abort "Cannot create $todofile"
 while test "$this" -le "$last"
 do
-	msgnum=`printf "%0${prec}d" $this`
-	next=`expr "$this" + 1`
-	test -f "$dotest/$msgnum" || {
-		resume=
-		go_next
-		continue
-	}
-
-	# If we are not resuming, parse and extract the patch information
-	# into separate files:
-	#  - info records the authorship and title
-	#  - msg is the rest of commit log message
-	#  - patch is the patch body.
-	#
-	# When we are resuming, these files are either already prepared
-	# by the user, or the user can tell us to do so by --resolved flag.
-	case "$resume" in
-	'')
-		git mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
-			<"$dotest/$msgnum" >"$dotest/info" ||
-			stop_here $this
-
-		# skip pine's internal folder data
-		grep '^Author: Mail System Internal Data$' \
-			<"$dotest"/info >/dev/null &&
-			go_next && continue
-
-		test -s "$dotest/patch" || {
-			echo "Patch is empty.  Was it split wrong?"
-			stop_here $this
-		}
-		if test -f "$dotest/rebasing" &&
-			commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
-				-e q "$dotest/$msgnum") &&
-			test "$(git cat-file -t "$commit")" = commit
-		then
-			git cat-file commit "$commit" |
-			sed -e '1,/^$/d' >"$dotest/msg-clean"
-		else
-			SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")"
-			case "$keep_subject" in -k)  SUBJECT="[PATCH] $SUBJECT" ;; esac
-
-			(printf '%s\n\n' "$SUBJECT"; cat "$dotest/msg") |
-				git stripspace > "$dotest/msg-clean"
-		fi
-		;;
-	esac
-
-	GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")"
-	GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
-	GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
+	msgnum=$(printf "%0${prec}d" $this)
+	this=$(($this+1))
 
-	if test -z "$GIT_AUTHOR_EMAIL"
-	then
-		echo "Patch does not have a valid e-mail address."
-		stop_here $this
-	fi
-
-	export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
-
-	case "$resume" in
-	'')
-	    if test '' != "$SIGNOFF"
-	    then
-		LAST_SIGNED_OFF_BY=`
-		    sed -ne '/^Signed-off-by: /p' \
-		    "$dotest/msg-clean" |
-		    sed -ne '$p'
-		`
-		ADD_SIGNOFF=`
-		    test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
-		    test '' = "$LAST_SIGNED_OFF_BY" && echo
-		    echo "$SIGNOFF"
-		}`
-	    else
-		ADD_SIGNOFF=
-	    fi
-	    {
-		if test -s "$dotest/msg-clean"
-		then
-			cat "$dotest/msg-clean"
-		fi
-		if test '' != "$ADD_SIGNOFF"
-		then
-			echo "$ADD_SIGNOFF"
-		fi
-	    } >"$dotest/final-commit"
-	    ;;
-	*)
-		case "$resolved$interactive" in
-		tt)
-			# This is used only for interactive view option.
-			git diff-index -p --cached HEAD -- >"$dotest/patch"
-			;;
-		esac
-	esac
-
-	resume=
-	if test "$interactive" = t
-	then
-	    test -t 0 ||
-	    die "cannot be interactive without stdin connected to a terminal."
-	    action=again
-	    while test "$action" = again
-	    do
-		echo "Commit Body is:"
-		echo "--------------------------"
-		cat "$dotest/final-commit"
-		echo "--------------------------"
-		printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
-		read reply
-		case "$reply" in
-		[yY]*) action=yes ;;
-		[aA]*) action=yes interactive= ;;
-		[nN]*) action=skip ;;
-		[eE]*) git_editor "$dotest/final-commit"
-		       action=again ;;
-		[vV]*) action=again
-		       LESS=-S ${PAGER:-less} "$dotest/patch" ;;
-		*)     action=again ;;
-		esac
-	    done
-	else
-	    action=yes
-	fi
-	FIRSTLINE=$(sed 1q "$dotest/final-commit")
-
-	if test $action = skip
-	then
-		go_next
+	# This ignores every mail that does not contain a patch.
+	grep '^diff' "$dotest/$msgnum" >/dev/null ||
 		continue
-	fi
 
-	if test -x "$GIT_DIR"/hooks/applypatch-msg
-	then
-		"$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
-		stop_here $this
-	fi
-
-	printf 'Applying %s\n' "$FIRSTLINE"
-
-	case "$resolved" in
-	'')
-		git apply $git_apply_opt $binary --index "$dotest/patch"
-		apply_status=$?
-		;;
-	t)
-		# Resolved means the user did all the hard work, and
-		# we do not have to do any patch application.  Just
-		# trust what the user has in the index file and the
-		# working tree.
-		resolved=
-		git diff-index --quiet --cached HEAD -- && {
-			echo "No changes - did you forget to use 'git add'?"
-			stop_here_user_resolve $this
-		}
-		unmerged=$(git ls-files -u)
-		if test -n "$unmerged"
-		then
-			echo "You still have unmerged paths in your index"
-			echo "did you forget to use 'git add'?"
-			stop_here_user_resolve $this
-		fi
-		apply_status=0
-		git rerere
-		;;
-	esac
-
-	if test $apply_status = 1 && test "$threeway" = t
-	then
-		if (fall_back_3way)
-		then
-		    # Applying the patch to an earlier tree and merging the
-		    # result may have produced the same tree as ours.
-		    git diff-index --quiet --cached HEAD -- && {
-			echo No changes -- Patch already applied.
-			go_next
-			continue
-		    }
-		    # clear apply_status -- we have successfully merged.
-		    apply_status=0
-		fi
-	fi
-	if test $apply_status != 0
-	then
-		echo Patch failed at $msgnum.
-		stop_here_user_resolve $this
-	fi
-
-	if test -x "$GIT_DIR"/hooks/pre-applypatch
-	then
-		"$GIT_DIR"/hooks/pre-applypatch || stop_here $this
-	fi
-
-	tree=$(git write-tree) &&
-	parent=$(git rev-parse --verify HEAD) &&
-	commit=$(git commit-tree $tree -p $parent <"$dotest/final-commit") &&
-	git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
-	stop_here $this
-
-	if test -x "$GIT_DIR"/hooks/post-applypatch
-	then
-		"$GIT_DIR"/hooks/post-applypatch
-	fi
-
-	go_next
+	extra=
+	test -n "$rebasing" &&
+		commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
+			-e q "$dotest/$msgnum") &&
+		test "$(git cat-file -t "$commit")" = commit &&
+		extra=" -C $commit"
+
+	subject=$(sed -n '1,/^Subject:/s/Subject: *\(\[.*\]\)\{0,1\} *//p' \
+		<"$dotest/$msgnum")
+	test -n "$interactive" ||
+		printf 'run -- printf '\''Applying "%%s"\\n'\'' '\''%s'\''\n' \
+			"$(printf '%s\n' "$subject" |
+				sed "s/'/'\\\\''/g")" >>"$todofile"
+	printf 'patch%s%s "%s" # %s\n' "$opts" "$extra" "$dotest/$msgnum" \
+		"$subject" >>"$todofile"
+	test -z "$interactive" || echo 'pause' >>"$todofile"
 done
 
-git gc --auto
-
-rm -fr "$dotest"
+noadvice=--no-advice
+run_sequencer$interactive "$todofile"
diff --git a/git-rebase.sh b/git-rebase.sh
index 56cf6f0..231c486 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -220,6 +220,7 @@ do
 			dotest="$GIT_DIR"/rebase
 			move_to_original_branch
 		fi
+		rm -rf "$GIT_DIR/sequencer"
 		git reset --hard $(cat "$dotest/orig-head")
 		rm -r "$dotest"
 		exit
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 5cbd5ef..e771806 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -212,14 +212,12 @@ test_expect_success 'am takes patches from a Pine mailbox' '
 '
 
 test_expect_success 'am fails on mail without patch' '
-	! git am <failmail &&
-	rm -r .git/rebase/
+	test_must_fail git am <failmail
 '
 
 test_expect_success 'am fails on empty patch' '
 	echo "---" >>failmail &&
-	! git am <failmail &&
-	git am --skip &&
+	test_must_fail git am <failmail &&
 	! test -d .git/rebase
 '
 
-- 
1.5.6.3.391.ge45b

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

* [PATCH 2/2] Introduce git am --abort
  2008-07-16 20:45       ` [PATCH 1/2] Migrate git-am to use git-sequencer Stephan Beyer
@ 2008-07-16 20:45         ` Stephan Beyer
  0 siblings, 0 replies; 8+ messages in thread
From: Stephan Beyer @ 2008-07-16 20:45 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Daniel Barkalow, Christian Couder, Junio C Hamano

Currently, aborting a git-am process during a conflict is done by
resetting to the HEAD before applying the patches and removing the
.git/rebase directory manually.

This patch introduces an --abort option for git-am to make this as
easy as in git-rebase.

Mentored-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 Documentation/git-am.txt               |    5 ++++-
 Documentation/git-rerere.txt           |    2 +-
 contrib/completion/git-completion.bash |    2 +-
 git-am.sh                              |   19 ++++++++++++-------
 git-rebase.sh                          |    8 +++-----
 t/t4150-am.sh                          |   21 +++++++++++++++++----
 6 files changed, 38 insertions(+), 19 deletions(-)

diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 2d7f162..df35cee 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -13,7 +13,7 @@ SYNOPSIS
          [--3way] [--interactive] [--binary]
          [--whitespace=<option>] [-C<n>] [-p<n>]
 	 [<mbox> | <Maildir>...]
-'git am' (--skip | --resolved)
+'git am' (--abort | --skip | --resolved)
 
 DESCRIPTION
 -----------
@@ -79,6 +79,9 @@ default.   You could use `--no-utf8` to override this.
 --interactive::
 	Run interactively.
 
+--abort::
+	Abort applying and rewind applied patches.
+
 --skip::
 	Skip the current patch.  This is only meaningful when
 	restarting an aborted patch.
diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt
index 678bfd3..ad81dbc 100644
--- a/Documentation/git-rerere.txt
+++ b/Documentation/git-rerere.txt
@@ -37,7 +37,7 @@ its working state.
 'clear'::
 
 This resets the metadata used by rerere if a merge resolution is to be
-is aborted.  Calling 'git-am --skip' or 'git-rebase [--skip|--abort]'
+is aborted.  Calling 'git-am [--skip|--abort]' or 'git-rebase [--skip|--abort]'
 will automatically invoke this command.
 
 'diff'::
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 29f6cd4..d271ba0 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -489,7 +489,7 @@ _git_am ()
 {
 	local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
 	if [ -d "$dir"/rebase ]; then
-		__gitcomp "--skip --resolved"
+		__gitcomp "--skip --resolved --abort"
 		return
 	fi
 	case "$cur" in
diff --git a/git-am.sh b/git-am.sh
index e36f22c..7301314 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -8,6 +8,7 @@ OPTIONS_SPEC="\
 git-am [options] [<mbox>|<Maildir>...]
 git-am [options] --resolved
 git-am [options] --skip
+git-am [options] --abort
 --
 d,dotest=       (removed -- do not use)
 i,interactive   run interactively
@@ -22,6 +23,7 @@ p=              pass it through git-apply
 resolvemsg=     override error message when patch failure occurs
 r,resolved      to be used after a patch failure
 skip            skip the current patch
+abort           abort patching and reset done patches
 rebasing        (internal use for git-rebase)"
 
 . git-sh-setup
@@ -108,7 +110,7 @@ print_continue_info () {
 }
 
 run_sequencer () {
-	git sequencer $noadvice --caller='git am||--resolved|--skip' "$@"
+	git sequencer $noadvice --caller='git am|--abort|--resolved|--skip' "$@"
 	case "$?" in
 	0)
 		cleanup
@@ -130,7 +132,7 @@ run_sequencer_i () {
 	while true
 	do
 		output=$(git sequencer $noadvice \
-			--caller='git am -i||--resolved|--skip' \
+			--caller='git am -i|--abort|--resolved|--skip' \
 			$command 2>&1 >/dev/null)
 		noadvice=
 		case "$?" in
@@ -170,6 +172,8 @@ do
 	case "$1" in
 	-i|--interactive)
 		interactive=_i ;;
+	--abort)
+		abort=t ;;
 	-b|--binary)
 		binary=t ;;
 	-3|--3way)
@@ -219,17 +223,18 @@ then
 	# unreliable -- stdin could be /dev/null for example
 	# and the caller did not intend to feed us a patch but
 	# wanted to continue unattended.
-	test -z "$resolved$skip" && tty -s
+	test -z "$abort$resolved$skip" && tty -s
 
+	test -n "$abort" && run_sequencer$interactive --abort
 	test -n "$resolved" && run_sequencer$interactive --continue
 	test -n "$skip" && run_sequencer$interactive --skip
 
-	die "$dotest still exists. Use git am --skip/--resolved."
+	die "$dotest still exists. Use git am --abort/--skip/--resolved."
 fi
 
-# Make sure we are not given --skip nor --resolved
-test -z "$resolved$skip" ||
-	die 'git-am is not in progress. You cannot use --skip/--resolved then.'
+# Make sure we are not given --skip nor --resolved nor --abort
+test -z "$abort$resolved$skip" ||
+	die 'git-am is not in progress. You cannot use --abort/--skip/--resolved then.'
 
 # sequencer running?
 git sequencer --status >/dev/null 2>&1 &&
diff --git a/git-rebase.sh b/git-rebase.sh
index 231c486..a83933b 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -216,13 +216,11 @@ do
 		if test -d "$dotest"
 		then
 			move_to_original_branch
+			git reset --hard $(cat "$dotest/orig-head")
+			rm -r "$dotest"
 		else
-			dotest="$GIT_DIR"/rebase
-			move_to_original_branch
+			git am --abort
 		fi
-		rm -rf "$GIT_DIR/sequencer"
-		git reset --hard $(cat "$dotest/orig-head")
-		rm -r "$dotest"
 		exit
 		;;
 	--onto)
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index e771806..152e2d9 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -83,6 +83,10 @@ test_expect_success setup '
 	git commit -m third &&
 	git format-patch --stdout first >patch2	&&
 	git checkout -b lorem &&
+	echo new >another &&
+	git add another &&
+	test_tick &&
+	git commit -m "added another file" &&
 	sed -n -e "11,\$p" msg >file &&
 	head -n 9 msg >>file &&
 	test_tick &&
@@ -181,8 +185,8 @@ test_expect_success 'am -3 falls back to 3-way merge' '
 '
 
 test_expect_success 'am pauses on conflict' '
-	git checkout lorem2^^ &&
-	! git am lorem-move.patch &&
+	git checkout lorem2~3 &&
+	test_must_fail git am lorem-move.patch &&
 	test -d .git/rebase
 '
 
@@ -193,9 +197,18 @@ test_expect_success 'am --skip works' '
 	test goodbye = "$(cat another)"
 '
 
+test_expect_success 'am --abort works' '
+	git checkout lorem2~3 &&
+	test_must_fail git am lorem-move.patch &&
+	test -d .git/rebase &&
+	git am --abort &&
+	test "$(git rev-parse HEAD)" = "$(git rev-parse lorem2~3)" &&
+	! test -f another
+'
+
 test_expect_success 'am --resolved works' '
-	git checkout lorem2^^ &&
-	! git am lorem-move.patch &&
+	git checkout lorem2~3 &&
+	test_must_fail git am lorem-move.patch &&
 	test -d .git/rebase &&
 	echo resolved >>file &&
 	git add file &&
-- 
1.5.6.3.391.g7ab7e

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

* [PATCH] Migrate rebase-i to sequencer
  2008-07-16 20:45     ` Sequencer migration patches Stephan Beyer
  2008-07-16 20:45       ` [PATCH 1/2] Migrate git-am to use git-sequencer Stephan Beyer
@ 2008-07-16 20:45       ` Stephan Beyer
  2008-07-17 13:05       ` Sequencer migration patches Stephan Beyer
  2 siblings, 0 replies; 8+ messages in thread
From: Stephan Beyer @ 2008-07-16 20:45 UTC (permalink / raw)
  To: git
  Cc: Stephan Beyer, Daniel Barkalow, Christian Couder, Junio C Hamano,
	Johannes Schindelin

The migration of pure rebase-i to sequencer is simply done by
generating the todo list, but with a comment marker (`#')
before the description, and then feed it to git sequencer.

For git-rebase-i -p (preserving merges) merges should be
rewritten. For this, the sequencer instructions "mark", "merge"
and "reset" are used.

Mentored-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 git-rebase--interactive.sh    |  438 ++++++++++-------------------------------
 t/t3404-rebase-interactive.sh |    9 +-
 2 files changed, 113 insertions(+), 334 deletions(-)

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index da79a24..2136e02 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -42,11 +42,6 @@ STRATEGY=
 ONTO=
 VERBOSE=
 
-GIT_CHERRY_PICK_HELP="  After resolving the conflicts,
-mark the corrected paths with 'git add <paths>', and
-run 'git rebase --continue'"
-export GIT_CHERRY_PICK_HELP
-
 warn () {
 	echo "$*" >&2
 }
@@ -74,48 +69,6 @@ require_clean_work_tree () {
 	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
-}
-
-last_count=
-mark_action_done () {
-	sed -e 1q < "$TODO" >> "$DONE"
-	sed -e 1d < "$TODO" >> "$TODO".new
-	mv -f "$TODO".new "$TODO"
-	count=$(grep -c '^[^#]' < "$DONE")
-	total=$(($count+$(grep -c '^[^#]' < "$TODO")))
-	if test "$last_count" != "$count"
-	then
-		last_count=$count
-		printf "Rebasing (%d/%d)\r" $count $total
-		test -z "$VERBOSE" || echo
-	fi
-}
-
-make_patch () {
-	parent_sha1=$(git rev-parse --verify "$1"^) ||
-		die "Cannot get patch for $1^"
-	git diff-tree -p "$parent_sha1".."$1" > "$DOTEST"/patch
-	test -f "$DOTEST"/message ||
-		git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message
-	test -f "$DOTEST"/author-script ||
-		get_author_ident_from_commit "$1" > "$DOTEST"/author-script
-}
-
-die_with_patch () {
-	make_patch "$1"
-	git rerere
-	die "$2"
-}
-
 die_abort () {
 	rm -rf "$DOTEST"
 	die "$1"
@@ -125,48 +78,20 @@ has_action () {
 	grep '^[^#]' "$1" >/dev/null
 }
 
-pick_one () {
-	no_ff=
-	case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac
-	output 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^) ||
-		die "Could not get the parent of $sha1"
-	current_sha1=$(git rev-parse --verify HEAD)
-	if test "$no_ff$current_sha1" = "$parent_sha1"; then
-		output git reset --hard $sha1
-		test "a$1" = a-n && output git reset --soft $current_sha1
-		sha1=$(git rev-parse --short $sha1)
-		output warn Fast forward to $sha1
-	else
-		output git cherry-pick "$@"
-	fi
-}
-
-pick_one_preserving_merges () {
-	case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
+create_todo_preserving_merges () {
+	shortsha1=$sha1
 	sha1=$(git rev-parse $sha1)
 
-	if test -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=
+	first_parent=
 	for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)
 	do
+		# check if we've already seen this parent
 		if test -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
@@ -174,186 +99,49 @@ pick_one_preserving_merges () {
 				new_parents="$new_parents $new_p"
 				;;
 			esac
+		else
+			new_parents="$new_parents $p"
 		fi
+		test -n "$first_parent" || first_parent=$p
 	done
-	case $fast_forward in
-	t)
-		output warn "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
-		output 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')"
-			# No point in merging the first parent, that's HEAD
-			new_parents=${new_parents# $first_parent}
-			if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
-				GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
-				GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
-				output git merge $STRATEGY -m "$msg" \
-					$new_parents
-			then
-				git rerere
-				printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
-				die Error redoing merge $sha1
-			fi
-			;;
-		*)
-			output git cherry-pick "$@" ||
-				die_with_patch $sha1 "Could not pick $sha1"
-			;;
-		esac
-		;;
-	esac
-}
+	# We do not have parent, so ignore this commit
+	test t = $preserve && return
 
-nth_string () {
-	case "$1" in
-	*1[0-9]|*[04-9]) echo "$1"th;;
-	*1) echo "$1"st;;
-	*2) echo "$1"nd;;
-	*3) echo "$1"rd;;
-	esac
-}
+	# We always write a mark, because we do not know if there will
+	# be a "reset" or "merge"
+	# Filter the unneeded marks out afterwards.
+	echo "mark :$mark"
+	mark=$(($mark+1))
 
-make_squash_message () {
-	if test -f "$SQUASH_MSG"; then
-		COUNT=$(($(sed -n "s/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p" \
-			< "$SQUASH_MSG" | sed -ne '$p')+1))
-		echo "# This is a combination of $COUNT commits."
-		sed -e 1d -e '2,/^./{
-			/^$/d
-		}' <"$SQUASH_MSG"
-	else
-		COUNT=2
-		echo "# This is a combination of two commits."
-		echo "# The first commit's message is:"
-		echo
-		git cat-file commit HEAD | sed -e '1,/^$/d'
-	fi
-	echo
-	echo "# This is the $(nth_string $COUNT) commit message:"
-	echo
-	git cat-file commit $1 | sed -e '1,/^$/d'
-}
+	new_first_parent=$(expr "$new_parents" : ' \([^ ]*\)')
 
-peek_next_command () {
-	sed -n "1s/ .*$//p" < "$TODO"
-}
+	# Reset if needed
+	test -z "$first_parent" -o "$first_parent" = $lastsha1 ||
+		echo "reset $new_first_parent"
 
-do_next () {
-	rm -f "$DOTEST"/message "$DOTEST"/author-script \
-		"$DOTEST"/amend || exit
-	read command sha1 rest < "$TODO"
-	case "$command" in
-	'#'*|'')
-		mark_action_done
-		;;
-	pick|p)
-		comment_for_reflog pick
+	echo ":$mark" > "$REWRITTEN"/$sha1
 
-		mark_action_done
-		pick_one $sha1 ||
-			die_with_patch $sha1 "Could not apply $sha1... $rest"
-		;;
-	edit|e)
-		comment_for_reflog edit
-
-		mark_action_done
-		pick_one $sha1 ||
-			die_with_patch $sha1 "Could not apply $sha1... $rest"
-		make_patch $sha1
-		: > "$DOTEST"/amend
-		warn
-		warn "You can amend the commit now, with"
-		warn
-		warn "	git commit --amend"
-		warn
-		warn "Once you are satisfied with your changes, run"
-		warn
-		warn "	git rebase --continue"
-		warn
-		exit 0
-		;;
-	squash|s)
-		comment_for_reflog squash
-
-		has_action "$DONE" ||
-			die "Cannot 'squash' without a previous commit"
-
-		mark_action_done
-		make_squash_message $sha1 > "$MSG"
-		case "$(peek_next_command)" in
-		squash|s)
-			EDIT_COMMIT=
-			USE_OUTPUT=output
-			cp "$MSG" "$SQUASH_MSG"
-			;;
-		*)
-			EDIT_COMMIT=-e
-			USE_OUTPUT=
-			rm -f "$SQUASH_MSG" || exit
-			;;
-		esac
-
-		failed=f
-		author_script=$(get_author_ident_from_commit HEAD)
-		output git reset --soft HEAD^
-		pick_one -n $sha1 || failed=t
-		echo "$author_script" > "$DOTEST"/author-script
-		if test $failed = f
-		then
-			# This is like --amend, but with a different message
-			eval "$author_script"
-			GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
-			GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
-			GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
-			$USE_OUTPUT git commit --no-verify -F "$MSG" $EDIT_COMMIT || failed=t
-		fi
-		if test $failed = t
-		then
-			cp "$MSG" "$GIT_DIR"/MERGE_MSG
-			warn
-			warn "Could not apply $sha1... $rest"
-			die_with_patch $sha1 ""
-		fi
+	# Merge or pick
+	case "$new_parents" in
+	' '*' '*)
+		new_parents=${new_parents# $new_first_parent}
+		printf 'merge%s -C %s%s\t%s\n' "$STRATEGY" \
+			"$shortsha1" "$new_parents" "$rest"
 		;;
 	*)
-		warn "Unknown command: $command $sha1 $rest"
-		die_with_patch $sha1 "Please fix this in the file $TODO."
+		printf 'pick %s\t%s\n' "$shortsha1" "$rest"
 		;;
 	esac
-	test -s "$TODO" && return
 
-	comment_for_reflog finish &&
+	lastsha1="$sha1"
+	return 0
+}
+
+update_refs_and_exit () {
 	HEADNAME=$(cat "$DOTEST"/head-name) &&
 	OLDHEAD=$(cat "$DOTEST"/head) &&
 	SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
-	if test -d "$REWRITTEN"
-	then
-		test -f "$DOTEST"/current-commit &&
-			current_commit=$(cat "$DOTEST"/current-commit) &&
-			git rev-parse HEAD > "$REWRITTEN"/$current_commit
-		if test -f "$REWRITTEN"/$OLDHEAD
-		then
-			NEWHEAD=$(cat "$REWRITTEN"/$OLDHEAD)
-		else
-			NEWHEAD=$OLDHEAD
-		fi
-	else
-		NEWHEAD=$(git rev-parse HEAD)
-	fi &&
+	NEWHEAD=$(git rev-parse HEAD) &&
 	case $HEADNAME in
 	refs/*)
 		message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
@@ -371,13 +159,6 @@ do_next () {
 	exit
 }
 
-do_rest () {
-	while :
-	do
-		do_next
-	done
-}
-
 # check if no other options are set
 is_standalone () {
 	test $# -eq 2 -a "$2" = '--' &&
@@ -393,80 +174,47 @@ get_saved_options () {
 	test -f "$DOTEST"/verbose && VERBOSE=t
 }
 
-while test $# != 0
-do
-	case "$1" in
-	--continue)
-		is_standalone "$@" || usage
-		get_saved_options
-		comment_for_reflog continue
-
-		test -d "$DOTEST" || die "No interactive rebase running"
-
-		# Sanity check
-		git rev-parse --verify HEAD >/dev/null ||
-			die "Cannot read HEAD"
-		git update-index --ignore-submodules --refresh &&
-			git diff-files --quiet --ignore-submodules ||
-			die "Working tree is dirty"
-
-		# do we have anything to commit?
-		if git diff-index --cached --quiet --ignore-submodules HEAD --
+run_sequencer () {
+	git sequencer --caller='git rebase -i|--abort|--continue|--skip' "$@"
+	case "$?" in
+	0)
+		if test "$1" = --abort
 		then
-			: Nothing to commit -- skip this
+			rm -rf "$DOTEST"
+			exit
 		else
-			. "$DOTEST"/author-script ||
-				die "Cannot find the author identity"
-			if test -f "$DOTEST"/amend
-			then
-				git reset --soft HEAD^ ||
-				die "Cannot rewind the HEAD"
-			fi
-			export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE &&
-			git commit --no-verify -F "$DOTEST"/message -e ||
-			die "Could not commit staged changes."
+			update_refs_and_exit
 		fi
-
-		require_clean_work_tree
-		do_rest
 		;;
-	--abort)
-		is_standalone "$@" || usage
-		get_saved_options
-		comment_for_reflog abort
-
-		git rerere clear
-		test -d "$DOTEST" || die "No interactive rebase running"
-
-		HEADNAME=$(cat "$DOTEST"/head-name)
-		HEAD=$(cat "$DOTEST"/head)
-		case $HEADNAME in
-		refs/*)
-			git symbolic-ref HEAD $HEADNAME
-			;;
-		esac &&
-		output git reset --hard $HEAD &&
-		rm -rf "$DOTEST"
-		exit
+	2)
+		# pause
+		exit 0
 		;;
-	--skip)
-		is_standalone "$@" || usage
-		get_saved_options
-		comment_for_reflog skip
-
-		git rerere clear
-		test -d "$DOTEST" || die "No interactive rebase running"
+	3)
+		# conflict
+		exit 1
+		;;
+	*)
+		die_abort 'git-sequencer died unexpected.'
+		;;
+	esac
+}
 
-		output git reset --hard && do_rest
+while test $# != 0
+do
+	case "$1" in
+	--abort|--continue|--skip)
+		is_standalone "$@" || usage
+		run_sequencer "$1"
 		;;
 	-s)
 		case "$#,$1" in
 		*,*=*)
-			STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
+			STRATEGY=" -s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
 		1,*)
 			usage ;;
 		*)
-			STRATEGY="-s $2"
+			STRATEGY=" -s $2"
 			shift ;;
 		esac
 		;;
@@ -492,12 +240,12 @@ do
 		test $# -eq 1 -o $# -eq 2 || usage
 		test -d "$DOTEST" &&
 			die "Interactive rebase already started"
+		git sequencer --status >/dev/null 2>&1 &&
+			die "Sequencer already started. Cannot run rebase."
 
 		git var GIT_COMMITTER_IDENT >/dev/null ||
 			die "You need to set your committer info first"
 
-		comment_for_reflog start
-
 		require_clean_work_tree
 
 		UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
@@ -514,42 +262,72 @@ do
 		HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
 		mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
 
-		: > "$DOTEST"/interactive || die "Could not mark as interactive"
+		: > "$DOTEST"/interactive || die_abort "Could not mark as interactive"
 		git symbolic-ref HEAD > "$DOTEST"/head-name 2> /dev/null ||
 			echo "detached HEAD" > "$DOTEST"/head-name
 
 		echo $HEAD > "$DOTEST"/head
-		echo $UPSTREAM > "$DOTEST"/upstream
 		echo $ONTO > "$DOTEST"/onto
 		test -z "$STRATEGY" || echo "$STRATEGY" > "$DOTEST"/strategy
 		test t = "$VERBOSE" && : > "$DOTEST"/verbose
 		if test t = "$PRESERVE_MERGES"
 		then
+			lastsha1=
 			# $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.)
+			# reachable on the way between $UPSTREAM and $HEAD.
+			# The filename is the SHA1 of the old value and the
+			# content is the SHA1 or :mark of the new one.
 			mkdir "$REWRITTEN" &&
 			for c in $(git merge-base --all $HEAD $UPSTREAM)
 			do
+				test -n "$lastsha1" || lastsha1=$c
 				echo $ONTO > "$REWRITTEN"/$c ||
 					die "Could not init rewritten commits"
 			done
-			MERGES_OPTION=
-		else
-			MERGES_OPTION=--no-merges
+			git rev-list --abbrev-commit --abbrev=7 \
+				--pretty=format:"%m%h	# %s" --topo-order \
+				--reverse --cherry-pick $UPSTREAM...$HEAD | \
+				sed -n -e "s/^>//p" > "$DOTEST"/commit-list
+
+			mark=0
+			while read -r sha1 rest
+			do
+				create_todo_preserving_merges
+			done < "$DOTEST"/commit-list > "$TODO"
+
+			# We now have more "mark :..." lines than needed.
+			# Remove the unused.  This is just a step to keep
+			# the list clean.
+			keep_marks=$(sed -e "/^mark :/d" <"$TODO" |
+				sed -n -e 's/^[^#]* :\([0-9][0-9]*\).*$/:\1:/p')
+			while read -r line
+			do
+				case "$line" in
+				'mark :'*)
+					case "$keep_marks " in
+					*${line#mark }:*)
+						echo "$line"
+						;;
+					esac
+					;;
+				*)
+					printf '%s\n' "$line"
+					;;
+				esac
+			done < "$TODO" > "$TODO".new
+			mv "$TODO".new "$TODO"
+		fi
+		if test -z "$PRESERVE_MERGES"
+		then
+			git rev-list --no-merges --abbrev-commit --abbrev=7 \
+				--pretty=format:"%mpick %h	# %s" \
+				--reverse --cherry-pick $UPSTREAM...$HEAD | \
+				sed -n -e "s/^>//p" > "$TODO"
 		fi
 
 		SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
 		SHORTHEAD=$(git rev-parse --short $HEAD)
 		SHORTONTO=$(git rev-parse --short $ONTO)
-		git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
-			--abbrev=7 --reverse --left-right --cherry-pick \
-			$UPSTREAM...$HEAD | \
-			sed -n "s/^>/pick /p" > "$TODO"
 		cat >> "$TODO" << EOF
 
 # Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
@@ -575,7 +353,7 @@ EOF
 			die_abort "Nothing to do"
 
 		git update-ref ORIG_HEAD $HEAD
-		output git checkout $ONTO && do_rest
+		run_sequencer --onto "$ONTO" "$TODO"
 		;;
 	esac
 	shift
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index ffe3dd9..64a28ef 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -159,19 +159,20 @@ test_expect_success 'stop on conflicting pick' '
 	git tag new-branch1 &&
 	test_must_fail git rebase -i master &&
 	test "$(git rev-parse HEAD~3)" = "$(git rev-parse master)" &&
-	test_cmp expect .git/rebase-merge/patch &&
+	test_cmp expect .git/sequencer/patch &&
 	test_cmp expect2 file1 &&
 	test "$(git-diff --name-status |
 		sed -n -e "/^U/s/^U[^a-z]*//p")" = file1 &&
-	test 4 = $(grep -v "^#" < .git/rebase-merge/done | wc -l) &&
-	test 0 = $(grep -c "^[^#]" < .git/rebase-merge/git-rebase-todo)
+	test 4 = $(grep -v "^#" < .git/sequencer/done | wc -l) &&
+	test 0 = $(grep -c "^[^#]" < .git/sequencer/todo) &&
+	test -d .git/rebase-merge
 '
 
 test_expect_success 'abort' '
 	git rebase --abort &&
 	test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
 	test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
-	! test -d .git/rebase-merge
+	! test -d .git/sequencer
 '
 
 test_expect_success 'retain authorship' '
-- 
1.5.6.3.391.ge45b

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

* Re: Sequencer migration patches
  2008-07-16 20:45     ` Sequencer migration patches Stephan Beyer
  2008-07-16 20:45       ` [PATCH 1/2] Migrate git-am to use git-sequencer Stephan Beyer
  2008-07-16 20:45       ` [PATCH] Migrate rebase-i to sequencer Stephan Beyer
@ 2008-07-17 13:05       ` Stephan Beyer
  2 siblings, 0 replies; 8+ messages in thread
From: Stephan Beyer @ 2008-07-17 13:05 UTC (permalink / raw)
  To: git; +Cc: Daniel Barkalow, Christian Couder, Junio C Hamano,
	Johannes Schindelin

Hi,

Stephan Beyer wrote:
> Rebasing 100 commits takes 10.1 seconds instead of only 4.8 seconds
> on my test machine.

Btw, the builtin-sequencer needs 1.7 seconds to pick that 100 commits
on my test machine.

Regards,
  Stephan

-- 
Stephan Beyer <s-beyer@gmx.net>, PGP 0x6EDDD207FCC5040F

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

end of thread, other threads:[~2008-07-17 13:06 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-07-16 20:45 [PATCH 1/3] Add git-sequencer shell prototype Stephan Beyer
2008-07-16 20:45 ` [PATCH 2/3] Add git-sequencer documentation Stephan Beyer
2008-07-16 20:45   ` [PATCH 3/3] Add git-sequencer test suite (t3350) Stephan Beyer
2008-07-16 20:45     ` Sequencer migration patches Stephan Beyer
2008-07-16 20:45       ` [PATCH 1/2] Migrate git-am to use git-sequencer Stephan Beyer
2008-07-16 20:45         ` [PATCH 2/2] Introduce git am --abort Stephan Beyer
2008-07-16 20:45       ` [PATCH] Migrate rebase-i to sequencer Stephan Beyer
2008-07-17 13:05       ` Sequencer migration patches Stephan Beyer

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 NNTP newsgroup(s).