Updated version, was Re: RFC: Patch editing
- From
Johannes Schindelin <johannes.schindelin@gmx.de>
- Date
- Mar 1, 2007, 23:30 UTC
- Message-ID
- <Pine.LNX.4.63.0703020020470.22628@wbgn013.biozentrum.uni-wuerzburg.de>
- In-Reply-To
- <Pine.LNX.4.63.0702252156190.22628@wbgn013.biozentrum.uni-wuerzburg.de>
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 pickmark_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
figit 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