-#!/bin/sh
+# This shell script fragment is sourced by git-rebase to implement
+# its interactive mode. "git rebase --interactive" makes it easy
+# to fix up commits in the middle of a series and rearrange commits.
#
# 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
rewritten_list="$state_dir"/rewritten-list
rewritten_pending="$state_dir"/rewritten-pending
+strategy_args=
+if test -n "$do_merge"
+then
+ strategy_args=${strategy:+--strategy=$strategy}
+ eval '
+ for strategy_opt in '"$strategy_opts"'
+ do
+ strategy_args="$strategy_args -X$(git rev-parse --sq-quote "${strategy_opt#--}")"
+ done
+ '
+fi
+
GIT_CHERRY_PICK_HELP="$resolvemsg"
export GIT_CHERRY_PICK_HELP
echo "$1" > "$state_dir"/stopped-sha
make_patch $1
git rev-parse --verify HEAD > "$amend"
+ gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
warn "You can amend the commit now, with"
warn
- warn " git commit --amend"
+ warn " git commit --amend $gpg_sign_opt_quoted"
warn
warn "Once you are satisfied with your changes, run"
warn
test -d "$rewritten" &&
pick_one_preserving_merges "$@" && return
- output git cherry-pick $empty_args $ff "$@"
+ output eval git cherry-pick \
+ ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \
+ "$strategy_args" $empty_args $ff "$@"
}
pick_one_preserving_merges () {
msg_content="$(commit_message $sha1)"
# No point in merging the first parent, that's HEAD
new_parents=${new_parents# $first_parent}
- if ! do_with_author output \
- git merge --no-ff ${strategy:+-s $strategy} -m \
- "$msg_content" $new_parents
+ merge_args="--no-log --no-ff"
+ if ! do_with_author output eval \
+ 'git merge ${gpg_sign_opt:+"$gpg_sign_opt"} \
+ $merge_args $strategy_args -m "$msg_content" $new_parents'
then
printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG
die_with_patch $sha1 "Error redoing merge $sha1"
echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list"
;;
*)
- output git cherry-pick "$@" ||
+ output eval git cherry-pick \
+ ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \
+ "$strategy_args" "$@" ||
die_with_patch $sha1 "Could not pick $sha1"
;;
esac
--no-post-rewrite -n -q -C $1 &&
pick_one -n $1 &&
git commit --allow-empty --allow-empty-message \
- --amend --no-post-rewrite -n -q -C $1 ||
+ --amend --no-post-rewrite -n -q -C $1 \
+ ${gpg_sign_opt:+"$gpg_sign_opt"} ||
die_with_patch $1 "Could not apply $1... $2"
else
pick_one $1 ||
mark_action_done
do_pick $sha1 "$rest"
- git commit --amend --no-post-rewrite || {
+ git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || {
warn "Could not amend commit after successfully picking $sha1... $rest"
warn "This is most likely due to an empty commit message, or the pre-commit hook"
warn "failed. If the pre-commit hook failed, you may need to resolve the issue before"
squash|s|fixup|f)
# This is an intermediate commit; its message will only be
# used in case of trouble. So use the long version:
- do_with_author output git commit --amend --no-verify -F "$squash_msg" ||
+ do_with_author output git commit --amend --no-verify -F "$squash_msg" \
+ ${gpg_sign_opt:+"$gpg_sign_opt"} ||
die_failed_squash $sha1 "$rest"
;;
*)
# This is the final command of this squash/fixup group
if test -f "$fixup_msg"
then
- do_with_author git commit --amend --no-verify -F "$fixup_msg" ||
+ do_with_author git commit --amend --no-verify -F "$fixup_msg" \
+ ${gpg_sign_opt:+"$gpg_sign_opt"} ||
die_failed_squash $sha1 "$rest"
else
cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit
rm -f "$GIT_DIR"/MERGE_MSG
- do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e ||
+ do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \
+ ${gpg_sign_opt:+"$gpg_sign_opt"} ||
die_failed_squash $sha1 "$rest"
fi
rm -f "$squash_msg" "$fixup_msg"
;;
esac
;;
- 3,#*|3,)
+ 3,"$comment_char"*|3,)
# copy comments
;;
*)
die "Could not skip unnecessary pick commands"
}
+transform_todo_ids () {
+ while read -r command rest
+ do
+ case "$command" in
+ "$comment_char"* | exec)
+ # Be careful for oddball commands like 'exec'
+ # that do not have a SHA-1 at the beginning of $rest.
+ ;;
+ *)
+ sha1=$(git rev-parse --verify --quiet "$@" ${rest%% *}) &&
+ rest="$sha1 ${rest#* }"
+ ;;
+ esac
+ printf '%s\n' "$command${rest:+ }$rest"
+ done <"$todo" >"$todo.new" &&
+ mv -f "$todo.new" "$todo"
+}
+
+expand_todo_ids() {
+ transform_todo_ids
+}
+
+collapse_todo_ids() {
+ transform_todo_ids --short
+}
+
# Rearrange the todo list that has both "pick sha1 msg" and
# "pick sha1 fixup!/squash! msg" appears in it so that the latter
# comes immediately after the former, and change "pick" to
case "$message" in
"squash! "*|"fixup! "*)
action="${message%%!*}"
- rest="${message#*! }"
- echo "$sha1 $action $rest"
+ rest=$message
+ prefix=
+ # skip all squash! or fixup! (but save for later)
+ while :
+ do
+ case "$rest" in
+ "squash! "*|"fixup! "*)
+ prefix="$prefix${rest%%!*},"
+ rest="${rest#*! }"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+ printf '%s %s %s %s\n' "$sha1" "$action" "$prefix" "$rest"
# if it's a single word, try to resolve to a full sha1 and
# emit a second copy. This allows us to match on both message
# and on sha1 prefix
if test -n "$fullsha"; then
# prefix the action to uniquely identify this line as
# intended for full sha1 match
- echo "$sha1 +$action $fullsha"
+ echo "$sha1 +$action $prefix $fullsha"
fi
fi
esac
esac
printf '%s\n' "$pick $sha1 $message"
used="$used$sha1 "
- while read -r squash action msg_content
+ while read -r squash action msg_prefix msg_content
do
case " $used" in
*" $squash "*) continue ;;
case "$message" in "$msg_content"*) emit=1;; esac ;;
esac
if test $emit = 1; then
- printf '%s\n' "$action $squash $action! $msg_content"
+ real_prefix=$(echo "$msg_prefix" | sed "s/,/! /g")
+ printf '%s\n' "$action $squash ${real_prefix}$msg_content"
used="$used$squash "
fi
done <"$1.sq"
mv "$1.new" "$1"
}
+# The whole contents of this file is run by dot-sourcing it from
+# inside a shell function. It used to be that "return"s we see
+# below were not inside any function, and expected to return
+# to the function that dot-sourced us.
+#
+# However, FreeBSD /bin/sh misbehaves on such a construct and
+# continues to run the statements that follow such a "return".
+# As a work-around, we introduce an extra layer of a function
+# here, and immediately call it after defining it.
+git_rebase__interactive () {
+
case "$action" in
continue)
# do we have anything to commit?
else
if ! test -f "$author_script"
then
+ gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
die "You have staged changes in your working tree. If these changes are meant to be
squashed into the previous commit, run:
- git commit --amend
+ git commit --amend $gpg_sign_opt_quoted
If they are meant to go into a new commit, run:
- git commit
+ git commit $gpg_sign_opt_quoted
In both case, once you're done, continue with:
die "\
You have uncommitted changes in your working tree. Please, commit them
first and then run 'git rebase --continue' again."
- do_with_author git commit --amend --no-verify -F "$msg" -e ||
+ do_with_author git commit --amend --no-verify -F "$msg" -e \
+ ${gpg_sign_opt:+"$gpg_sign_opt"} ||
die "Could not commit staged changes."
else
- do_with_author git commit --no-verify -F "$msg" -e ||
+ do_with_author git commit --no-verify -F "$msg" -e \
+ ${gpg_sign_opt:+"$gpg_sign_opt"} ||
die "Could not commit staged changes."
fi
fi
edit-todo)
git stripspace --strip-comments <"$todo" >"$todo".new
mv -f "$todo".new "$todo"
+ collapse_todo_ids
append_todo_help
git stripspace --comment-lines >>"$todo" <<\EOF
git_sequence_editor "$todo" ||
die "Could not execute editor"
+ expand_todo_ids
exit
;;
if test ! -z "$switch_to"
then
+ GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to"
output git checkout "$switch_to" -- ||
- die "Could not checkout $switch_to"
+ die "Could not checkout $switch_to"
+
+ comment_for_reflog start
fi
orig_head=$(git rev-parse --verify HEAD) || die "No HEAD?"
has_action "$todo" ||
- die_abort "Nothing to do"
+ return 2
cp "$todo" "$todo".backup
git_sequence_editor "$todo" ||
die_abort "Could not execute editor"
has_action "$todo" ||
- die_abort "Nothing to do"
+ return 2
+expand_todo_ids
+
test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
+GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
output git checkout $onto || die_abort "could not detach HEAD"
git update-ref ORIG_HEAD $orig_head
do_rest
+
+}
+# ... and then we call the whole thing.
+git_rebase__interactive
SUBDIRECTORY_OK=Yes
OPTIONS_KEEPDASHDASH=
+OPTIONS_STUCKLONG=t
OPTIONS_SPEC="\
git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>]
git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] --root [<branch>]
v,verbose! display a diffstat of what changed upstream
q,quiet! be quiet. implies --no-stat
autostash! automatically stash/stash pop before and after
+fork-point use 'merge-base --fork-point' to refine upstream
onto=! rebase onto given branch instead of upstream
p,preserve-merges! try to recreate merges instead of ignoring them
s,strategy=! use the given merge strategy
whitespace=! passed to 'git apply'
ignore-whitespace! passed to 'git apply'
C=! passed to 'git apply'
+S,gpg-sign? GPG-sign commits
Actions:
continue! continue
abort! abort and check out the original branch
diffstat=
test "$(git config --bool rebase.stat)" = true && diffstat=t
autostash="$(git config --bool rebase.autostash || echo false)"
+fork_point=auto
git_am_opt=
rebase_root=
force_rebase=
autosquash=
keep_empty=
test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
+gpg_sign_opt=
read_basic_state () {
test -f "$state_dir/head-name" &&
strategy_opts="$(cat "$state_dir"/strategy_opts)"
test -f "$state_dir"/allow_rerere_autoupdate &&
allow_rerere_autoupdate="$(cat "$state_dir"/allow_rerere_autoupdate)"
+ test -f "$state_dir"/gpg_sign_opt &&
+ gpg_sign_opt="$(cat "$state_dir"/gpg_sign_opt)"
}
write_basic_state () {
"$state_dir"/strategy_opts
test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \
"$state_dir"/allow_rerere_autoupdate
+ test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt
}
output () {
esac
}
- finish_rebase () {
+ apply_autostash () {
if test -f "$state_dir/autostash"
then
stash_sha1=$(cat "$state_dir/autostash")
then
echo "$(gettext 'Applied autostash.')"
else
- ref_stash=refs/stash &&
- >>"$GIT_DIR/logs/$ref_stash" &&
- git update-ref -m "autostash" $ref_stash $stash_sha1 ||
- die "$(eval_gettext 'Cannot store $stash_sha1')"
-
+ git stash store -m "autostash" -q $stash_sha1 ||
+ die "$(eval_gettext "Cannot store \$stash_sha1")"
gettext 'Applying autostash resulted in conflicts.
Your changes are safe in the stash.
You can run "git stash pop" or "git stash drop" at any time.
'
fi
fi
+ }
+
+ finish_rebase () {
+ apply_autostash &&
git gc --auto &&
rm -rf "$state_dir"
}
if test $ret -eq 0
then
finish_rebase
+ elif test $ret -eq 2 # special exit status for rebase -i
+ then
+ apply_autostash &&
+ rm -rf "$state_dir" &&
+ die "Nothing to do"
fi
exit $ret
}
test $total_argc -eq 2 || usage
action=${1##--}
;;
- --onto)
- test 2 -le "$#" || usage
- onto="$2"
- shift
+ --onto=*)
+ onto="${1#--onto=}"
;;
- -x)
- test 2 -le "$#" || usage
- cmd="${cmd}exec $2${LF}"
- shift
+ --exec=*)
+ cmd="${cmd}exec ${1#--exec=}${LF}"
;;
- -i)
+ --interactive)
interactive_rebase=explicit
;;
- -k)
+ --keep-empty)
keep_empty=yes
;;
- -p)
+ --preserve-merges)
preserve_merges=t
test -z "$interactive_rebase" && interactive_rebase=implied
;;
--no-autosquash)
autosquash=
;;
- -M|-m)
+ --fork-point)
+ fork_point=t
+ ;;
+ --no-fork-point)
+ fork_point=
+ ;;
+ --merge)
do_merge=t
;;
- -X)
- shift
- strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$1")"
+ --strategy-option=*)
+ strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--${1#--strategy-option=}")"
do_merge=t
test -z "$strategy" && strategy=recursive
;;
- -s)
- shift
- strategy="$1"
+ --strategy=*)
+ strategy="${1#--strategy=}"
do_merge=t
;;
- -n)
+ --no-stat)
diffstat=
;;
--stat)
--autostash)
autostash=true
;;
- -v)
+ --verbose)
verbose=t
diffstat=t
GIT_QUIET=
;;
- -q)
+ --quiet)
GIT_QUIET=t
git_am_opt="$git_am_opt -q"
verbose=
diffstat=
;;
- --whitespace)
- shift
- git_am_opt="$git_am_opt --whitespace=$1"
- case "$1" in
+ --whitespace=*)
+ git_am_opt="$git_am_opt --whitespace=${1#--whitespace=}"
+ case "${1#--whitespace=}" in
fix|strip)
force_rebase=t
;;
git_am_opt="$git_am_opt $1"
force_rebase=t
;;
- -C)
- shift
- git_am_opt="$git_am_opt -C$1"
+ -C*)
+ git_am_opt="$git_am_opt $1"
;;
--root)
rebase_root=t
;;
- -f|--no-ff)
+ --force-rebase|--no-ff)
force_rebase=t
;;
--rerere-autoupdate|--no-rerere-autoupdate)
allow_rerere_autoupdate="$1"
;;
+ --gpg-sign)
+ gpg_sign_opt=-S
+ ;;
+ --gpg-sign=*)
+ gpg_sign_opt="-S${1#--gpg-sign=}"
+ ;;
--)
shift
break
error_on_missing_default_upstream "rebase" "rebase" \
"against" "git rebase <branch>"
fi
+
+ test "$fork_point" = auto && fork_point=t
;;
*) upstream_name="$1"
+ if test "$upstream_name" = "-"
+ then
+ upstream_name="@{-1}"
+ fi
shift
;;
esac
- upstream=`git rev-parse --verify "${upstream_name}^0"` ||
+ upstream=$(peel_committish "${upstream_name}") ||
die "$(eval_gettext "invalid upstream \$upstream_name")"
upstream_arg="$upstream_name"
else
if test -z "$onto"
then
- empty_tree=`git hash-object -t tree /dev/null`
- onto=`git commit-tree $empty_tree </dev/null`
+ empty_tree=$(git hash-object -t tree /dev/null)
+ onto=$(git commit-tree $empty_tree </dev/null)
squash_onto="$onto"
fi
unset upstream_name
fi
;;
*)
- onto=$(git rev-parse --verify "${onto_name}^0") ||
+ onto=$(peel_committish "$onto_name") ||
die "$(eval_gettext "Does not point to a valid commit: \$onto_name")"
;;
esac
;;
0)
# Do not need to switch branches, we are already on it.
- if branch_name=`git symbolic-ref -q HEAD`
+ if branch_name=$(git symbolic-ref -q HEAD)
then
head_name=$branch_name
- branch_name=`expr "z$branch_name" : 'zrefs/heads/\(.*\)'`
+ branch_name=$(expr "z$branch_name" : 'zrefs/heads/\(.*\)')
else
head_name="detached HEAD"
branch_name=HEAD ;# detached
;;
esac
+if test "$fork_point" = t
+then
+ new_upstream=$(git merge-base --fork-point "$upstream_name" \
+ "${switch_to:-HEAD}")
+ if test -n "$new_upstream"
+ then
+ upstream=$new_upstream
+ fi
+fi
+
if test "$autostash" = true && ! (require_clean_work_tree) 2>/dev/null
then
stash_sha1=$(git stash create "autostash") ||
if test -z "$force_rebase"
then
# Lazily switch to the target branch if needed...
- test -z "$switch_to" || git checkout "$switch_to" --
+ test -z "$switch_to" ||
+ GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to" \
+ git checkout "$switch_to" --
say "$(eval_gettext "Current branch \$branch_name is up to date.")"
finish_rebase
exit 0
# Detach HEAD and reset the tree
say "$(gettext "First, rewinding head to replay your work on top of it...")"
-git checkout -q "$onto^0" || die "could not detach HEAD"
+
+GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" \
+ git checkout -q "$onto^0" || die "could not detach HEAD"
git update-ref ORIG_HEAD $orig_head
# If the $onto is a proper descendant of the tip of the branch, then