Staging
v0.8.1
Revision ff58b9aaf8bf4ce7471a21baa502cb9ddaa9873a authored by Jeff King on 12 February 2008, 05:45:18 UTC, committed by Junio C Hamano on 13 February 2008, 21:54:58 UTC
It makes no sense to suggest "git reset HEAD" since we have
no HEAD commit. This actually used to work but regressed in
f26a0012.

wt_status_print_cached_header was updated to take the whole
wt_status struct rather than just the reference field.
Previously the various code paths were sometimes sending in
s->reference and sometimes sending in NULL, making the
decision on whether this was an initial commit before we
even got to this function. Now we must check the initial
flag here.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 077b725
Raw File
git-rebase--interactive.sh
#!/bin/sh
#
# Copyright (c) 2006 Johannes E. Schindelin

# SHORT DESCRIPTION
#
# This script makes it easy to fix up commits in the middle of a series,
# and rearrange commits.
#
# The original idea comes from Eric W. Biederman, in
# http://article.gmane.org/gmane.comp.version-control.git/22407

USAGE='(--continue | --abort | --skip | [--preserve-merges] [--verbose]
	[--onto <branch>] <upstream> [<branch>])'

OPTIONS_SPEC=
. git-sh-setup
require_work_tree

DOTEST="$GIT_DIR/.dotest-merge"
TODO="$DOTEST"/git-rebase-todo
DONE="$DOTEST"/done
MSG="$DOTEST"/message
SQUASH_MSG="$DOTEST"/message-squash
REWRITTEN="$DOTEST"/rewritten
PRESERVE_MERGES=
STRATEGY=
VERBOSE=
test -d "$REWRITTEN" && PRESERVE_MERGES=t
test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
test -f "$DOTEST"/verbose && VERBOSE=t

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
}

output () {
	case "$VERBOSE" in
	'')
		output=$("$@" 2>&1 )
		status=$?
		test $status != 0 && printf "%s\n" "$output"
		return $status
		;;
	*)
		"$@"
		;;
	esac
}

require_clean_work_tree () {
	# test if working tree is dirty
	git rev-parse --verify HEAD > /dev/null &&
	git update-index --refresh &&
	git diff-files --quiet &&
	git diff-index --cached --quiet HEAD -- ||
	die "Working tree is dirty"
}

ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"

comment_for_reflog () {
	case "$ORIG_REFLOG_ACTION" in
	''|rebase*)
		GIT_REFLOG_ACTION="rebase -i ($1)"
		export GIT_REFLOG_ACTION
		;;
	esac
}

last_count=
mark_action_done () {
	sed -e 1q < "$TODO" >> "$DONE"
	sed -e 1d < "$TODO" >> "$TODO".new
	mv -f "$TODO".new "$TODO"
	count=$(($(grep -ve '^$' -e '^#' < "$DONE" | wc -l)))
	total=$(($count+$(grep -ve '^$' -e '^#' < "$TODO" | wc -l)))
	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"
}

has_action () {
	grep -vqe '^$' -e '^#' "$1"
}

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
	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=
	for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)
	do
		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
			*)
				new_parents="$new_parents $new_p"
				;;
			esac
		fi
	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
}

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
}

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" | tail -n 1)+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'
}

peek_next_command () {
	sed -n "1s/ .*$//p" < "$TODO"
}

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

		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
		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
		;;
	*)
		warn "Unknown command: $command $sha1 $rest"
		die_with_patch $sha1 "Please fix this in the file $TODO."
		;;
	esac
	test -s "$TODO" && return

	comment_for_reflog finish &&
	HEADNAME=$(cat "$DOTEST"/head-name) &&
	OLDHEAD=$(cat "$DOTEST"/head) &&
	SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
	if 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 &&
	case $HEADNAME in
	refs/*)
		message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
		git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD &&
		git symbolic-ref HEAD $HEADNAME
		;;
	esac && {
		test ! -f "$DOTEST"/verbose ||
			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
	} &&
	rm -rf "$DOTEST" &&
	git gc --auto &&
	warn "Successfully rebased and updated $HEADNAME."

	exit
}

do_rest () {
	while :
	do
		do_next
	done
}

while test $# != 0
do
	case "$1" in
	--continue)
		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 --refresh && git diff-files --quiet ||
			die "Working tree is dirty"

		# do we have anything to commit?
		if git diff-index --cached --quiet HEAD --
		then
			: Nothing to commit -- skip this
		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."
		fi

		require_clean_work_tree
		do_rest
		;;
	--abort)
		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
		;;
	--skip)
		comment_for_reflog skip

		git rerere clear
		test -d "$DOTEST" || die "No interactive rebase running"

		output git reset --hard && do_rest
		;;
	-s|--strategy)
		case "$#,$1" in
		*,*=*)
			STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
		1,*)
			usage ;;
		*)
			STRATEGY="-s $2"
			shift ;;
		esac
		;;
	-m|--merge)
		# we use merge anyway
		;;
	-C*)
		die "Interactive rebase uses merge, so $1 does not make sense"
		;;
	-v|--verbose)
		VERBOSE=t
		;;
	-p|--preserve-merges)
		PRESERVE_MERGES=t
		;;
	-i|--interactive)
		# yeah, we know
		;;
	''|-h)
		usage
		;;
	*)
		test -d "$DOTEST" &&
			die "Interactive rebase already started"

		git var GIT_COMMITTER_IDENT >/dev/null ||
			die "You need to set your committer info first"

		comment_for_reflog start

		ONTO=
		case "$1" in
		--onto)
			ONTO=$(git rev-parse --verify "$2") ||
				die "Does not point to a valid commit: $2"
			shift; shift
			;;
		esac

		require_clean_work_tree

		if test ! -z "$2"
		then
			output git show-ref --verify --quiet "refs/heads/$2" ||
				die "Invalid branchname: $2"
			output git checkout "$2" ||
				die "Could not checkout $2"
		fi

		HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
		UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"

		mkdir "$DOTEST" || die "Could not create temporary $DOTEST"

		test -z "$ONTO" && ONTO=$UPSTREAM

		: > "$DOTEST"/interactive || die "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
			# $REWRITTEN contains files for each commit that is
			# reachable by at least one merge base of $HEAD and
			# $UPSTREAM. They are not necessarily rewritten, but
			# their children might be.
			# This ensures that commits on merged, but otherwise
			# unrelated side branches are left alone. (Think "X"
			# in the man page's example.)
			mkdir "$REWRITTEN" &&
			for c in $(git merge-base --all $HEAD $UPSTREAM)
			do
				echo $ONTO > "$REWRITTEN"/$c ||
					die "Could not init rewritten commits"
			done
			MERGES_OPTION=
		else
			MERGES_OPTION=--no-merges
		fi

		SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
		SHORTHEAD=$(git rev-parse --short $HEAD)
		SHORTONTO=$(git rev-parse --short $ONTO)
		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
#
# Commands:
#  pick = use commit
#  edit = use commit, but stop for amending
#  squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
EOF

		has_action "$TODO" ||
			die_abort "Nothing to do"

		cp "$TODO" "$TODO".backup
		git_editor "$TODO" ||
			die "Could not execute editor"

		has_action "$TODO" ||
			die_abort "Nothing to do"

		output git checkout $ONTO && do_rest
		;;
	esac
	shift
done
back to top