index 929d681c4716fa4f6b2947fdb40ad6fbb580bef3..c8b0861c085035c85002f6e2fe969369ea32016f 100755 (executable)
continue continue rebasing process
abort abort rebasing process and restore original branch
skip skip current patch and continue rebasing process
continue continue rebasing process
abort abort rebasing process and restore original branch
skip skip current patch and continue rebasing process
+no-verify override pre-rebase hook from stopping the operation
"
. git-sh-setup
"
. git-sh-setup
MSG="$DOTEST"/message
SQUASH_MSG="$DOTEST"/message-squash
REWRITTEN="$DOTEST"/rewritten
MSG="$DOTEST"/message
SQUASH_MSG="$DOTEST"/message-squash
REWRITTEN="$DOTEST"/rewritten
+DROPPED="$DOTEST"/dropped
PRESERVE_MERGES=
STRATEGY=
ONTO=
VERBOSE=
PRESERVE_MERGES=
STRATEGY=
ONTO=
VERBOSE=
+OK_TO_SKIP_PRE_REBASE=
GIT_CHERRY_PICK_HELP=" After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
GIT_CHERRY_PICK_HELP=" After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
esac
}
esac
}
+run_pre_rebase_hook () {
+ if test -z "$OK_TO_SKIP_PRE_REBASE" &&
+ test -x "$GIT_DIR/hooks/pre-rebase"
+ then
+ "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || {
+ echo >&2 "The pre-rebase hook refused to rebase."
+ exit 1
+ }
+ fi
+}
+
require_clean_work_tree () {
# test if working tree is dirty
git rev-parse --verify HEAD > /dev/null &&
require_clean_work_tree () {
# test if working tree is dirty
git rev-parse --verify HEAD > /dev/null &&
}
make_patch () {
}
make_patch () {
- parent_sha1=$(git rev-parse --verify "$1"^) ||
- die "Cannot get patch for $1^"
- git diff-tree -p "$parent_sha1".."$1" > "$DOTEST"/patch
+ sha1_and_parents="$(git rev-list --parents -1 "$1")"
+ case "$sha1_and_parents" in
+ ?*' '?*' '?*)
+ git diff --cc $sha1_and_parents
+ ;;
+ ?*' '?*)
+ git diff-tree -p "$1^!"
+ ;;
+ *)
+ echo "Root commit"
+ ;;
+ esac > "$DOTEST"/patch
test -f "$DOTEST"/message ||
git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message
test -f "$DOTEST"/author-script ||
test -f "$DOTEST"/message ||
git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message
test -f "$DOTEST"/author-script ||
if test -f "$DOTEST"/current-commit
then
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"
+ if test "$fast_forward" = t
+ then
+ cat "$DOTEST"/current-commit | while read current_commit
+ do
+ git rev-parse HEAD > "$REWRITTEN"/$current_commit
+ done
+ rm "$DOTEST"/current-commit ||
+ die "Cannot write current commit's replacement sha1"
+ fi
fi
fi
- echo $sha1 > "$DOTEST"/current-commit
+ echo $sha1 >> "$DOTEST"/current-commit
# rewrite parents; if none were rewritten, we can fast-forward.
new_parents=
# rewrite parents; if none were rewritten, we can fast-forward.
new_parents=
- for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)
+ pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)"
+ while [ "$pend" != "" ]
do
do
+ p=$(expr "$pend" : ' \([^ ]*\)')
+ pend="${pend# $p}"
+
if test -f "$REWRITTEN"/$p
then
new_p=$(cat "$REWRITTEN"/$p)
if test -f "$REWRITTEN"/$p
then
new_p=$(cat "$REWRITTEN"/$p)
+
+ # If the todo reordered commits, and our parent is marked for
+ # rewriting, but hasn't been gotten to yet, assume the user meant to
+ # drop it on top of the current HEAD
+ if test -z "$new_p"
+ then
+ new_p=$(git rev-parse HEAD)
+ fi
+
test $p != $new_p && fast_forward=f
case "$new_parents" in
*$new_p*)
test $p != $new_p && fast_forward=f
case "$new_parents" in
*$new_p*)
;;
esac
else
;;
esac
else
- new_parents="$new_parents $p"
+ if test -f "$DROPPED"/$p
+ then
+ fast_forward=f
+ pend=" $(cat "$DROPPED"/$p)$pend"
+ else
+ new_parents="$new_parents $p"
+ fi
fi
done
case $fast_forward in
fi
done
case $fast_forward in
die "Cannot fast forward to $sha1"
;;
f)
die "Cannot fast forward to $sha1"
;;
f)
- test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
-
first_parent=$(expr "$new_parents" : ' \([^ ]*\)')
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"
+
+ if [ "$1" != "-n" ]
+ then
+ # detach HEAD to current parent
+ output git checkout $first_parent 2> /dev/null ||
+ die "Cannot move HEAD to $first_parent"
+ fi
case "$new_parents" in
' '*' '*)
case "$new_parents" in
' '*' '*)
+ test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
+
# redo merge
author_script=$(get_author_ident_from_commit $sha1)
eval "$author_script"
# redo merge
author_script=$(get_author_ident_from_commit $sha1)
eval "$author_script"
output git merge $STRATEGY -m "$msg" \
$new_parents
then
output git merge $STRATEGY -m "$msg" \
$new_parents
then
- git rerere
printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
- die Error redoing merge $sha1
+ die_with_patch $sha1 "Error redoing merge $sha1"
fi
;;
*)
fi
;;
*)
"$DOTEST"/amend || exit
read command sha1 rest < "$TODO"
case "$command" in
"$DOTEST"/amend || exit
read command sha1 rest < "$TODO"
case "$command" in
- '#'*|'')
+ '#'*|''|noop)
mark_action_done
;;
pick|p)
mark_action_done
;;
pick|p)
pick_one $sha1 ||
die_with_patch $sha1 "Could not apply $sha1... $rest"
make_patch $sha1
pick_one $sha1 ||
die_with_patch $sha1 "Could not apply $sha1... $rest"
make_patch $sha1
- : > "$DOTEST"/amend
+ git rev-parse --verify HEAD > "$DOTEST"/amend
warn "Stopped at $sha1... $rest"
warn "You can amend the commit now, with"
warn
warn "Stopped at $sha1... $rest"
warn "You can amend the commit now, with"
warn
mark_action_done
make_squash_message $sha1 > "$MSG"
mark_action_done
make_squash_message $sha1 > "$MSG"
+ failed=f
+ author_script=$(get_author_ident_from_commit HEAD)
+ output git reset --soft HEAD^
+ pick_one -n $sha1 || failed=t
case "$(peek_next_command)" in
squash|s)
EDIT_COMMIT=
USE_OUTPUT=output
case "$(peek_next_command)" in
squash|s)
EDIT_COMMIT=
USE_OUTPUT=output
+ MSG_OPT=-F
+ MSG_FILE="$MSG"
cp "$MSG" "$SQUASH_MSG"
;;
*)
EDIT_COMMIT=-e
USE_OUTPUT=
cp "$MSG" "$SQUASH_MSG"
;;
*)
EDIT_COMMIT=-e
USE_OUTPUT=
+ MSG_OPT=
+ MSG_FILE=
rm -f "$SQUASH_MSG" || exit
rm -f "$SQUASH_MSG" || exit
+ cp "$MSG" "$GIT_DIR"/SQUASH_MSG
+ rm -f "$GIT_DIR"/MERGE_MSG || exit
;;
esac
;;
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
echo "$author_script" > "$DOTEST"/author-script
if test $failed = f
then
GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
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
+ $USE_OUTPUT git commit --no-verify $MSG_OPT "$MSG_FILE" $EDIT_COMMIT || failed=t
fi
if test $failed = t
then
fi
if test $failed = t
then
HEADNAME=$(cat "$DOTEST"/head-name) &&
OLDHEAD=$(cat "$DOTEST"/head) &&
SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
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 &&
+ NEWHEAD=$(git rev-parse HEAD) &&
case $HEADNAME in
refs/*)
message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
case $HEADNAME in
refs/*)
message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
while test $# != 0
do
case "$1" in
while test $# != 0
do
case "$1" in
+ --no-verify)
+ OK_TO_SKIP_PRE_REBASE=yes
+ ;;
+ --verify)
+ ;;
--continue)
is_standalone "$@" || usage
get_saved_options
--continue)
is_standalone "$@" || usage
get_saved_options
else
. "$DOTEST"/author-script ||
die "Cannot find the author identity"
else
. "$DOTEST"/author-script ||
die "Cannot find the author identity"
+ amend=
if test -f "$DOTEST"/amend
then
if test -f "$DOTEST"/amend
then
+ amend=$(git rev-parse --verify HEAD)
+ test "$amend" = $(cat "$DOTEST"/amend) ||
+ die "\
+You have uncommitted changes in your working tree. Please, commit them
+first and then run 'git rebase --continue' again."
git reset --soft HEAD^ ||
die "Cannot rewind the HEAD"
fi
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE &&
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."
+ git commit --no-verify -F "$DOTEST"/message -e || {
+ test -n "$amend" && git reset --soft $amend
+ die "Could not commit staged changes."
+ }
fi
require_clean_work_tree
fi
require_clean_work_tree
;;
--)
shift
;;
--)
shift
+ run_pre_rebase_hook ${1+"$@"}
test $# -eq 1 -o $# -eq 2 || usage
test -d "$DOTEST" &&
die "Interactive rebase already started"
test $# -eq 1 -o $# -eq 2 || usage
test -d "$DOTEST" &&
die "Interactive rebase already started"
echo $ONTO > "$REWRITTEN"/$c ||
die "Could not init rewritten commits"
done
echo $ONTO > "$REWRITTEN"/$c ||
die "Could not init rewritten commits"
done
+ # No cherry-pick because our first pass is to determine
+ # parents to rewrite and skipping dropped commits would
+ # prematurely end our probe
MERGES_OPTION=
MERGES_OPTION=
+ first_after_upstream="$(git rev-list --reverse --first-parent $UPSTREAM..$HEAD | head -n 1)"
else
else
- MERGES_OPTION=--no-merges
+ MERGES_OPTION="--no-merges --cherry-pick"
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 \
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 \
+ --abbrev=7 --reverse --left-right --topo-order \
$UPSTREAM...$HEAD | \
$UPSTREAM...$HEAD | \
- sed -n "s/^>/pick /p" > "$TODO"
+ sed -n "s/^>//p" | while read shortsha1 rest
+ do
+ if test t != "$PRESERVE_MERGES"
+ then
+ echo "pick $shortsha1 $rest" >> "$TODO"
+ else
+ sha1=$(git rev-parse $shortsha1)
+ preserve=t
+ for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)
+ do
+ if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \)
+ then
+ preserve=f
+ fi
+ done
+ if test f = "$preserve"
+ then
+ touch "$REWRITTEN"/$sha1
+ echo "pick $shortsha1 $rest" >> "$TODO"
+ fi
+ fi
+ done
+
+ # Watch for commits that been dropped by --cherry-pick
+ if test t = "$PRESERVE_MERGES"
+ then
+ mkdir "$DROPPED"
+ # Save all non-cherry-picked changes
+ git rev-list $UPSTREAM...$HEAD --left-right --cherry-pick | \
+ sed -n "s/^>//p" > "$DOTEST"/not-cherry-picks
+ # Now all commits and note which ones are missing in
+ # not-cherry-picks and hence being dropped
+ git rev-list $UPSTREAM..$HEAD |
+ while read rev
+ do
+ if test -f "$REWRITTEN"/$rev -a "$(grep "$rev" "$DOTEST"/not-cherry-picks)" = ""
+ then
+ # Use -f2 because if rev-list is telling us this commit is
+ # not worthwhile, we don't want to track its multiple heads,
+ # just the history of its first-parent for others that will
+ # be rebasing on top of it
+ git rev-list --parents -1 $rev | cut -d' ' -f2 > "$DROPPED"/$rev
+ short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev)
+ grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO"
+ rm "$REWRITTEN"/$rev
+ fi
+ done
+ fi
+ test -s "$TODO" || echo noop >> "$TODO"
cat >> "$TODO" << EOF
# Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
cat >> "$TODO" << EOF
# Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO