Updated version, was Re: RFC: Patch editing

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

this is an updated version, which got even less testing than the first 
version :-( However, the changes were small:

- added an attribution to Eric Biederman for the idea,
- if you mark a commit as "edit", it will _not_ be committed, so that
  you do not need --amend _both_ in case of conflicts and no conflicts 
  (noticed by Shawn Pearce),
- a "merge" is refused when it is the _first_ in the list (the first 
  version erroneously checked the opposite...)
- for a failed "merge", like for an "edit", you do not need --amend both 
  in case of conflicts and no conflicts, and
- the wrong usage of the "--reedit-message" flag was replaced by the 
  appropriate "-F .msg -e".

My plans are _not_ to turn this into git-rebase--interactive.sh, as I 
originally planned. Instead, I will try to make git-rebase a builtin, and 
add a "-i" flag which does the equivalent of this script.

BTW would people be mad at me if I did _not_ imitate git-rebase.sh (call 
format-patch and feed that into apply), but rather used cherry-pick 
throughout?

Ciao,
Dscho

-- snipsnap --
#!/bin/sh
#
# Copyright (c) 2006 Johannes E. Schindelin

# SHORT DESCRIPTION
#
# This is a really stupid script to arrange a patch series for submission.
# It does not care for authorship or dates, all it does is make 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
#
# MOTIVATION
#
# It is extremely useful for this type of workflow:
#
#	1. have a wonderful idea
#	2. hack on the code
#	3. prepare a series for submission
#	4. submit
#
# where the point (2) consists of several instances of
#
#	A.1. finish something worthy of a commit
#	A.2. commit
#
# and/or
#
#	B.1. realize that something does not work
#	B.2. fix that
#	B.3. commit it
#
# Sometimes the thing fixed in B.2. cannot be amended to the not-quite
# perfect commit it fixes, because that commit is buried deeply in a
# patch series.
#
# Use this script after plenty of "A"s and "B"s, by rearranging, and
# possibly editing and merging commits.
#
# USAGE:
#
# The subcommand "start" will make a list of the commits in the patch
# series <base>..HEAD, where <base> is the commit identiefied by the
# parameter.
#
# It will automatically start an editor, where you can rearrange the
# commits. The list looks more or less like this:
#
#	pick deadbee The oneline of this commit
#	pick fa1afe1 The oneline of the next commit
#	...
#
# By replacing the command "pick" with "edit" you can tell
# this script to stop the loop so you can fix up the commit by editing
# files and/or the commit message.
#
# When replacing the command "pick" with "merge, the script will merge
# this commit's changes into the previous commit, munge the commit
# messages of both, and open the commit message editor.
#
# In both cases, or when a "pick" does not succeed (because of merge
# errors), the loop will stop to let you fix things, and you can continue
# the loop with `git edit-patch-series continue`.

USAGE='(start <basecommit>| continue | reset [--force])'

. git-sh-setup
require_work_tree

SERIESDIR="$(pwd)/.series"
TODO="$SERIESDIR"/todo
DONE="$SERIESDIR"/done

warn () {
	echo "$@" >&2
}

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

ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"

comment_for_reflog () {
	if test -z "$ORIG_REFLOG_ACTION"; then
		GIT_REFLOG_ACTION="edit-patch-series($1)"
		export GIT_REFLOG_ACTION
	fi
}

mark_action_done () {
	sed -n 1p < "$TODO" >> "$DONE"
	sed -n '2,$p' < "$TODO" >> "$TODO".new
	mv -f "$TODO".new "$TODO"
}

do_next () {
	read command sha1 rest < "$TODO"
	case "$command" in
	\#)
		mark_action_done
		continue
	;;
	pick)
		comment_for_reflog pick

		mark_action_done
		git cherry-pick "$sha1" || \
			die "Could not apply $sha1... $rest"
	;;
	edit)
		comment_for_reflog edit

		mark_action_done
		git cherry-pick -n "$sha1" || \
			die "Could not apply $sha1... $rest"
		warn
		warn "After you edited the files, commit the result with"
		warn
		warn "	git commit -F .msg -e"
		warn
		exit 0
	;;
	merge)
		comment_for_reflog merge

		test -s "$DONE" ||
			die "Cannot 'merge' without a previous commit"

		mark_action_done
		failed=f
		git cherry-pick -n "$sha1" || failed=t
		MSG="$SERIESDIR"/message
		echo "# This is a combination of two commits." > "$MSG"
		echo "# The first commit's message is:" >> "$MSG"
		git cat-file commit HEAD | sed -n '/^$/,$p' >> "$MSG"
		echo >> "$MSG"
		echo "# And this is the 2nd commit message:" >> "$MSG"
		echo >> "$MSG"
		cat .msg >> "$MSG"
		git reset --soft HEAD^
		case $failed in
		f)
			# Usually, I would use --amend here, but -F forbids it
			git commit -F "$MSG" -e
		;;
		t)
			cp "$MSG" .msg
			warn
			warn "Could not apply $sha1... $rest"
			warn "After you fixed that, commit the result with"
			warn
			warn "	git commit -F .msg -e"
			warn
			exit 1
		esac
	;;
	*)
		warn "Unknown command: $command $sha1 $rest"
		warn "Please fix this in the file $TODO."
		exit 1
	esac
	test -s "$TODO" && continue

	HEAD=$(git rev-parse HEAD)
	HEADNAME=$(cat "$SERIESDIR"/head-name)
	git update-ref $HEADNAME $HEAD &&
	git symbolic-ref HEAD $HEADNAME &&
	rm -rf "$SERIESDIR" &&
	warn "Successfully edited patch series and updated $HEADNAME."

	exit $?
}

case "$1" in
start)
	comment_for_reflog start

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

	# DWIM: use the merge base
	MERGEBASE=$(git merge-base $HEAD $BASE)
	if test "$BASE" != "$MERGEBASE"
	then
		CANDIDATES="$(git merge-base --all $HEAD $BASE)"
		if test "$MERGEBASE" != "$CANDIDATES"
		then
			warn "Error: Multiple merge bases found:"
			warn "$CANDIDATES"
			rm -rf "$SERIESDIR"
			exit 1
		fi
		BASE=$MERGEBASE
	fi

	require_clean_work_tree

	git var GIT_COMMITTER_IDENT >/dev/null || exit

	test -d "$SERIESDIR" &&
		die "Edit-patch-series already started"

	test "z$(git rev-list --parents $BASE..$HEAD | grep " .* ")" != z &&
		die "Cannot edit a series including merges"

	mkdir "$SERIESDIR" || die "Could not create temporary $SERIESDIR"
	git symbolic-ref HEAD > "$SERIESDIR"/head-name || die "Could not get HEAD"

	echo $HEAD > "$SERIESDIR"/head
	echo $BASE > "$SERIESDIR"/base

	cat > "$TODO" << EOF
# Edit the patch series by exchanging lines.  The commits will be
# arranged in the given order.  If you want to edit a commit, replace
# the "pick" command with "edit".  If you want to merge the changes of
# a commit A into a commit B, place A directly after B, and replace the
# "pick" command with "merge".
EOF
	git rev-list --reverse --pretty=oneline --abbrev-commit --abbrev=7 \
		$BASE..$HEAD | sed "s/^/pick /" >> "$TODO"

	cp "$TODO" "$TODO".backup
	${VISUAL:-${EDITOR:-vi}} "$TODO" || warn "Could not execute editor"
	if git diff --no-index "$TODO" "$TODO".backup > /dev/null
	then
		rm -rf "$SERIESDIR"
		warn "No changes"
		exit 0
	fi

	git checkout $BASE || die "Could not checkout $BASE"

	while :
	do
		do_next
	done
;;
continue)
	comment_for_reflog continue

	test -d "$SERIESDIR" || die "No edit-patch-series running"

	require_clean_work_tree

	while :
	do
		do_next
	done
;;
reset)
	comment_for_reflog reset

	test -d "$SERIESDIR" || die "No edit-patch-series running"

	HEADNAME=$(cat "$SERIESDIR"/head-name)
	force=
	case "$2" in
	-f|--force)
		git symbolic-ref HEAD $HEADNAME &&
		git reset --hard
	;;
	*)
		git checkout $HEADNAME
	esac &&
	rm -rf "$SERIESDIR"
;;
*)
	usage
esac
-
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]