1b1dce4bae
Don't you just hate the fact sometimes, that git-rebase just applies the patches, without any possibility to edit them, or rearrange them? With "--interactive", git-rebase now lets you edit the list of patches, so that you can reorder, edit and delete patches. Such a list will typically look like this: pick deadbee The oneline of this commit pick fa1afe1 The oneline of the next commit ... By replacing the command "pick" with the command "edit", you can amend that patch and/or its commit message, and by replacing it with "squash" you can tell rebase to fold that patch into the patch before that. It is derived from the script sent to the list in <Pine.LNX.4.63.0702252156190.22628@wbgn013.biozentrum.uni-wuerzburg.de> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
290 lines
6.1 KiB
Bash
Executable File
290 lines
6.1 KiB
Bash
Executable File
#!/bin/sh
|
|
#
|
|
# Copyright (c) 2006 Johannes E. Schindelin
|
|
|
|
# SHORT DESCRIPTION
|
|
#
|
|
# This script makes it easy to fix up commits in the middle of a series,
|
|
# and rearrange commits.
|
|
#
|
|
# The original idea comes from Eric W. Biederman, in
|
|
# http://article.gmane.org/gmane.comp.version-control.git/22407
|
|
|
|
USAGE='(--continue | --abort | --skip | [--onto <branch>] <upstream> [<branch>])'
|
|
|
|
. git-sh-setup
|
|
require_work_tree
|
|
|
|
DOTEST="$GIT_DIR/.dotest-merge"
|
|
TODO="$DOTEST"/todo
|
|
DONE="$DOTEST"/done
|
|
STRATEGY=
|
|
VERBOSE=
|
|
|
|
warn () {
|
|
echo "$*" >&2
|
|
}
|
|
|
|
require_clean_work_tree () {
|
|
# test if working tree is dirty
|
|
git rev-parse --verify HEAD > /dev/null &&
|
|
git update-index --refresh &&
|
|
git diff-files --quiet &&
|
|
git diff-index --cached --quiet HEAD ||
|
|
die "Working tree is dirty"
|
|
}
|
|
|
|
ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
|
|
|
|
comment_for_reflog () {
|
|
case "$ORIG_REFLOG_ACTION" in
|
|
''|rebase*)
|
|
GIT_REFLOG_ACTION="rebase -i ($1)"
|
|
export GIT_REFLOG_ACTION
|
|
esac
|
|
}
|
|
|
|
mark_action_done () {
|
|
sed -e 1q < "$TODO" >> "$DONE"
|
|
sed -e 1d < "$TODO" >> "$TODO".new
|
|
mv -f "$TODO".new "$TODO"
|
|
}
|
|
|
|
make_patch () {
|
|
parent_sha1=$(git rev-parse --verify "$1"^ 2> /dev/null)
|
|
git diff "$parent_sha1".."$1" > "$DOTEST"/patch
|
|
}
|
|
|
|
die_with_patch () {
|
|
make_patch "$1"
|
|
die "$2"
|
|
}
|
|
|
|
pick_one () {
|
|
case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
|
|
git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
|
|
parent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null)
|
|
current_sha1=$(git rev-parse --verify HEAD)
|
|
if [ $current_sha1 = $parent_sha1 ]; then
|
|
git reset --hard $sha1
|
|
sha1=$(git rev-parse --short $sha1)
|
|
warn Fast forward to $sha1
|
|
else
|
|
git cherry-pick $STRATEGY "$@"
|
|
fi
|
|
}
|
|
|
|
do_next () {
|
|
read command sha1 rest < "$TODO"
|
|
case "$command" in
|
|
\#|'')
|
|
mark_action_done
|
|
continue
|
|
;;
|
|
pick)
|
|
comment_for_reflog pick
|
|
|
|
mark_action_done
|
|
pick_one $sha1 ||
|
|
die_with_patch $sha1 "Could not apply $sha1... $rest"
|
|
;;
|
|
edit)
|
|
comment_for_reflog edit
|
|
|
|
mark_action_done
|
|
pick_one $sha1 ||
|
|
die_with_patch $sha1 "Could not apply $sha1... $rest"
|
|
make_patch $sha1
|
|
warn
|
|
warn "You can amend the commit now, with"
|
|
warn
|
|
warn " git commit --amend"
|
|
warn
|
|
exit 0
|
|
;;
|
|
squash)
|
|
comment_for_reflog squash
|
|
|
|
test -z "$(grep -ve '^$' -e '^#' < $DONE)" &&
|
|
die "Cannot 'squash' without a previous commit"
|
|
|
|
mark_action_done
|
|
failed=f
|
|
pick_one -n $sha1 || failed=t
|
|
MSG="$DOTEST"/message
|
|
echo "# This is a combination of two commits." > "$MSG"
|
|
echo "# The first commit's message is:" >> "$MSG"
|
|
echo >> "$MSG"
|
|
git cat-file commit HEAD | sed -e '1,/^$/d' >> "$MSG"
|
|
echo >> "$MSG"
|
|
echo "# And this is the 2nd commit message:" >> "$MSG"
|
|
echo >> "$MSG"
|
|
git cat-file commit $sha1 | sed -e '1,/^$/d' >> "$MSG"
|
|
git reset --soft HEAD^
|
|
author_script=$(get_author_ident_from_commit $sha1)
|
|
case $failed in
|
|
f)
|
|
# This is like --amend, but with a different message
|
|
eval "$author_script"
|
|
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
|
|
git commit -F "$MSG" -e
|
|
;;
|
|
t)
|
|
cp "$MSG" "$GIT_DIR"/MERGE_MSG
|
|
warn
|
|
warn "Could not apply $sha1... $rest"
|
|
warn "After you fixed that, commit the result with"
|
|
warn
|
|
warn " $(echo $author_script | tr '\012' ' ') \\"
|
|
warn " git commit -F \"$GIT_DIR\"/MERGE_MSG -e"
|
|
die_with_patch $sha1 ""
|
|
esac
|
|
;;
|
|
*)
|
|
warn "Unknown command: $command $sha1 $rest"
|
|
die_with_patch $sha1 "Please fix this in the file $TODO."
|
|
esac
|
|
test -s "$TODO" && return
|
|
|
|
HEAD=$(git rev-parse HEAD)
|
|
HEADNAME=$(cat "$DOTEST"/head-name)
|
|
rm -rf "$DOTEST" &&
|
|
warn "Successfully rebased and updated $HEADNAME."
|
|
|
|
exit
|
|
}
|
|
|
|
do_rest () {
|
|
while :
|
|
do
|
|
do_next
|
|
done
|
|
test -f "$DOTEST"/verbose &&
|
|
git diff --stat $(cat "$DOTEST"/head)..HEAD
|
|
exit
|
|
}
|
|
|
|
while case $# in 0) break ;; esac
|
|
do
|
|
case "$1" in
|
|
--continue)
|
|
comment_for_reflog continue
|
|
|
|
test -d "$DOTEST" || die "No interactive rebase running"
|
|
|
|
require_clean_work_tree
|
|
do_rest
|
|
;;
|
|
--abort)
|
|
comment_for_reflog abort
|
|
|
|
test -d "$DOTEST" || die "No interactive rebase running"
|
|
|
|
HEADNAME=$(cat "$DOTEST"/head-name)
|
|
HEAD=$(cat "$DOTEST"/head)
|
|
git symbolic-ref HEAD $HEADNAME &&
|
|
git reset --hard $HEAD &&
|
|
rm -rf "$DOTEST"
|
|
exit
|
|
;;
|
|
--skip)
|
|
comment_for_reflog skip
|
|
|
|
test -d "$DOTEST" || die "No interactive rebase running"
|
|
|
|
git reset --hard && do_rest
|
|
;;
|
|
-s|--strategy)
|
|
shift
|
|
case "$#,$1" in
|
|
*,*=*)
|
|
STRATEGY="-s `expr "z$1" : 'z-[^=]*=\(.*\)'`" ;;
|
|
1,*)
|
|
usage ;;
|
|
*)
|
|
STRATEGY="-s $2"
|
|
shift ;;
|
|
esac
|
|
;;
|
|
--merge)
|
|
# we use merge anyway
|
|
;;
|
|
-C*)
|
|
die "Interactive rebase uses merge, so $1 does not make sense"
|
|
;;
|
|
-v)
|
|
VERBOSE=t
|
|
;;
|
|
-i|--interactive)
|
|
# yeah, we know
|
|
;;
|
|
''|-h)
|
|
usage
|
|
;;
|
|
*)
|
|
test -d "$DOTEST" &&
|
|
die "Interactive rebase already started"
|
|
|
|
git var GIT_COMMITTER_IDENT >/dev/null ||
|
|
die "You need to set your committer info first"
|
|
|
|
comment_for_reflog start
|
|
|
|
ONTO=
|
|
case "$1" in
|
|
--onto)
|
|
ONTO=$(git rev-parse --verify "$2") ||
|
|
die "Does not point to a valid commit: $2"
|
|
shift; shift
|
|
;;
|
|
esac
|
|
|
|
require_clean_work_tree
|
|
|
|
if [ ! -z "$2"]
|
|
then
|
|
git show-ref --verify --quiet "refs/heads/$2" ||
|
|
die "Invalid branchname: $2"
|
|
git checkout "$2" ||
|
|
die "Could not checkout $2"
|
|
fi
|
|
|
|
HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
|
|
UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
|
|
|
|
test -z "$ONTO" && ONTO=$UPSTREAM
|
|
|
|
mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
|
|
: > "$DOTEST"/interactive || die "Could not mark as interactive"
|
|
git symbolic-ref HEAD > "$DOTEST"/head-name ||
|
|
die "Could not get HEAD"
|
|
|
|
echo $HEAD > "$DOTEST"/head
|
|
echo $UPSTREAM > "$DOTEST"/upstream
|
|
echo $ONTO > "$DOTEST"/onto
|
|
test t = "$VERBOSE" && : > "$DOTEST"/verbose
|
|
|
|
cat > "$TODO" << EOF
|
|
# Rebasing $UPSTREAM..$HEAD onto $ONTO
|
|
#
|
|
# Commands:
|
|
# pick = use commit
|
|
# edit = use commit, but stop for amending
|
|
# squash = use commit, but meld into previous commit
|
|
EOF
|
|
git rev-list --no-merges --pretty=oneline --abbrev-commit \
|
|
--abbrev=7 --reverse $UPSTREAM..$HEAD | \
|
|
sed "s/^/pick /" >> "$TODO"
|
|
|
|
test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
|
|
die "Nothing to do"
|
|
|
|
cp "$TODO" "$TODO".backup
|
|
${VISUAL:-${EDITOR:-vi}} "$TODO" ||
|
|
die "Could not execute editor"
|
|
|
|
git reset --hard $ONTO && do_rest
|
|
esac
|
|
shift
|
|
done
|