summaryrefslogtreecommitdiff
path: root/git-rebase--interactive.sh
diff options
context:
space:
mode:
Diffstat (limited to 'git-rebase--interactive.sh')
-rw-r--r--git-rebase--interactive.sh258
1 files changed, 231 insertions, 27 deletions
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 56d99662f4..05f22e43cc 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -77,17 +77,18 @@ amend="$state_dir"/amend
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
+# Work around Git for Windows' Bash whose "read" does not strip CRLF
+# and leaves CR at the end instead.
+cr=$(printf "\015")
+
+strategy_args=${strategy:+--strategy=$strategy}
+test -n "$strategy_opts" &&
+eval '
+ for strategy_opt in '"$strategy_opts"'
+ do
+ strategy_args="$strategy_args -X$(git rev-parse --sq-quote "${strategy_opt#--}")"
+ done
+'
GIT_CHERRY_PICK_HELP="$resolvemsg"
export GIT_CHERRY_PICK_HELP
@@ -152,11 +153,21 @@ Commands:
s, squash = use commit, but meld into previous commit
f, fixup = like "squash", but discard this commit's log message
x, exec = run command (the rest of the line) using shell
+ d, drop = remove commit
These lines can be re-ordered; they are executed from top to bottom.
+EOF
+ if test $(get_missing_commit_check_level) = error
+ then
+ git stripspace --comment-lines >>"$todo" <<\EOF
+Do not remove any line. Use 'drop' explicitly to remove a commit.
+EOF
+ else
+ git stripspace --comment-lines >>"$todo" <<\EOF
If you remove a line here THAT COMMIT WILL BE LOST.
EOF
+ fi
}
make_patch () {
@@ -181,7 +192,6 @@ make_patch () {
die_with_patch () {
echo "$1" > "$state_dir"/stopped-sha
make_patch "$1"
- git rerere
die "$2"
}
@@ -505,7 +515,11 @@ do_next () {
rm -f "$msg" "$author_script" "$amend" "$state_dir"/stopped-sha || exit
read -r command sha1 rest < "$todo"
case "$command" in
- "$comment_char"*|''|noop)
+ "$comment_char"*|''|noop|drop|d)
+ mark_action_done
+ ;;
+ "$cr")
+ # Work around CR left by "read" (e.g. with Git for Windows' Bash).
mark_action_done
;;
pick|p)
@@ -534,7 +548,8 @@ do_next () {
mark_action_done
do_pick $sha1 "$rest"
- warn "Stopped at $sha1... $rest"
+ sha1_abbrev=$(git rev-parse --short $sha1)
+ warn "Stopped at $sha1_abbrev... $rest"
exit_with_patch $sha1 0
;;
squash|s|fixup|f)
@@ -592,7 +607,7 @@ do_next () {
read -r command rest < "$todo"
mark_action_done
printf 'Executing: %s\n' "$rest"
- ${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution
+ "${SHELL:-@SHELL_PATH@}" -c "$rest" # Actual execution
status=$?
# Run in subshell because require_clean_work_tree can die.
dirty=f
@@ -655,9 +670,9 @@ do_next () {
git notes copy --for-rewrite=rebase < "$rewritten_list" ||
true # we don't care if this copying failed
} &&
- if test -x "$GIT_DIR"/hooks/post-rewrite &&
- test -s "$rewritten_list"; then
- "$GIT_DIR"/hooks/post-rewrite rebase < "$rewritten_list"
+ hook="$(git rev-parse --git-path hooks/post-rewrite)"
+ if test -x "$hook" && test -s "$rewritten_list"; then
+ "$hook" rebase < "$rewritten_list"
true # we don't care if this hook failed
fi &&
warn "Successfully rebased and updated $head_name."
@@ -719,8 +734,8 @@ transform_todo_ids () {
# that do not have a SHA-1 at the beginning of $rest.
;;
*)
- sha1=$(git rev-parse --verify --quiet "$@" ${rest%% *}) &&
- rest="$sha1 ${rest#* }"
+ sha1=$(git rev-parse --verify --quiet "$@" ${rest%%[ ]*}) &&
+ rest="$sha1 ${rest#*[ ]}"
;;
esac
printf '%s\n' "$command${rest:+ }$rest"
@@ -740,10 +755,15 @@ collapse_todo_ids() {
# "pick sha1 fixup!/squash! msg" appears in it so that the latter
# comes immediately after the former, and change "pick" to
# "fixup"/"squash".
+#
+# Note that if the config has specified a custom instruction format
+# each log message will be re-retrieved in order to normalize the
+# autosquash arrangement
rearrange_squash () {
# extract fixup!/squash! lines and resolve any referenced sha1's
while read -r pick sha1 message
do
+ test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1})
case "$message" in
"squash! "*|"fixup! "*)
action="${message%%!*}"
@@ -785,6 +805,7 @@ rearrange_squash () {
*" $sha1 "*) continue ;;
esac
printf '%s\n' "$pick $sha1 $message"
+ test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1})
used="$used$sha1 "
while read -r squash action msg_prefix msg_content
do
@@ -802,8 +823,13 @@ rearrange_squash () {
case "$message" in "$msg_content"*) emit=1;; esac ;;
esac
if test $emit = 1; then
- real_prefix=$(echo "$msg_prefix" | sed "s/,/! /g")
- printf '%s\n' "$action $squash ${real_prefix}$msg_content"
+ if test -n "${format}"
+ then
+ msg_content=$(git log -n 1 --format="${format}" ${squash})
+ else
+ msg_content="$(echo "$msg_prefix" | sed "s/,/! /g")$msg_content"
+ fi
+ printf '%s\n' "$action $squash $msg_content"
used="$used$squash "
fi
done <"$1.sq"
@@ -833,6 +859,180 @@ add_exec_commands () {
mv "$1.new" "$1"
}
+# Check if the SHA-1 passed as an argument is a
+# correct one, if not then print $2 in "$todo".badsha
+# $1: the SHA-1 to test
+# $2: the line number of the input
+# $3: the input filename
+check_commit_sha () {
+ badsha=0
+ if test -z "$1"
+ then
+ badsha=1
+ else
+ sha1_verif="$(git rev-parse --verify --quiet $1^{commit})"
+ if test -z "$sha1_verif"
+ then
+ badsha=1
+ fi
+ fi
+
+ if test $badsha -ne 0
+ then
+ line="$(sed -n -e "${2}p" "$3")"
+ warn "Warning: the SHA-1 is missing or isn't" \
+ "a commit in the following line:"
+ warn " - $line"
+ warn
+ fi
+
+ return $badsha
+}
+
+# prints the bad commits and bad commands
+# from the todolist in stdin
+check_bad_cmd_and_sha () {
+ retval=0
+ lineno=0
+ while read -r command rest
+ do
+ lineno=$(( $lineno + 1 ))
+ case $command in
+ "$comment_char"*|''|noop|x|exec)
+ # Doesn't expect a SHA-1
+ ;;
+ "$cr")
+ # Work around CR left by "read" (e.g. with Git for
+ # Windows' Bash).
+ ;;
+ pick|p|drop|d|reword|r|edit|e|squash|s|fixup|f)
+ if ! check_commit_sha "${rest%%[ ]*}" "$lineno" "$1"
+ then
+ retval=1
+ fi
+ ;;
+ *)
+ line="$(sed -n -e "${lineno}p" "$1")"
+ warn "Warning: the command isn't recognized" \
+ "in the following line:"
+ warn " - $line"
+ warn
+ retval=1
+ ;;
+ esac
+ done <"$1"
+ return $retval
+}
+
+# Print the list of the SHA-1 of the commits
+# from stdin to stdout
+todo_list_to_sha_list () {
+ git stripspace --strip-comments |
+ while read -r command sha1 rest
+ do
+ case $command in
+ "$comment_char"*|''|noop|x|"exec")
+ ;;
+ *)
+ long_sha=$(git rev-list --no-walk "$sha1" 2>/dev/null)
+ printf "%s\n" "$long_sha"
+ ;;
+ esac
+ done
+}
+
+# Use warn for each line in stdin
+warn_lines () {
+ while read -r line
+ do
+ warn " - $line"
+ done
+}
+
+# Switch to the branch in $into and notify it in the reflog
+checkout_onto () {
+ 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
+}
+
+get_missing_commit_check_level () {
+ check_level=$(git config --get rebase.missingCommitsCheck)
+ check_level=${check_level:-ignore}
+ # Don't be case sensitive
+ printf '%s' "$check_level" | tr 'A-Z' 'a-z'
+}
+
+# Check if the user dropped some commits by mistake
+# Behaviour determined by rebase.missingCommitsCheck.
+# Check if there is an unrecognized command or a
+# bad SHA-1 in a command.
+check_todo_list () {
+ raise_error=f
+
+ check_level=$(get_missing_commit_check_level)
+
+ case "$check_level" in
+ warn|error)
+ # Get the SHA-1 of the commits
+ todo_list_to_sha_list <"$todo".backup >"$todo".oldsha1
+ todo_list_to_sha_list <"$todo" >"$todo".newsha1
+
+ # Sort the SHA-1 and compare them
+ sort -u "$todo".oldsha1 >"$todo".oldsha1+
+ mv "$todo".oldsha1+ "$todo".oldsha1
+ sort -u "$todo".newsha1 >"$todo".newsha1+
+ mv "$todo".newsha1+ "$todo".newsha1
+ comm -2 -3 "$todo".oldsha1 "$todo".newsha1 >"$todo".miss
+
+ # Warn about missing commits
+ if test -s "$todo".miss
+ then
+ test "$check_level" = error && raise_error=t
+
+ warn "Warning: some commits may have been dropped" \
+ "accidentally."
+ warn "Dropped commits (newer to older):"
+
+ # Make the list user-friendly and display
+ opt="--no-walk=sorted --format=oneline --abbrev-commit --stdin"
+ git rev-list $opt <"$todo".miss | warn_lines
+
+ warn "To avoid this message, use \"drop\" to" \
+ "explicitly remove a commit."
+ warn
+ warn "Use 'git config rebase.missingCommitsCheck' to change" \
+ "the level of warnings."
+ warn "The possible behaviours are: ignore, warn, error."
+ warn
+ fi
+ ;;
+ ignore)
+ ;;
+ *)
+ warn "Unrecognized setting $check_level for option" \
+ "rebase.missingCommitsCheck. Ignoring."
+ ;;
+ esac
+
+ if ! check_bad_cmd_and_sha "$todo"
+ then
+ raise_error=t
+ fi
+
+ if test $raise_error = t
+ then
+ # Checkout before the first commit of the
+ # rebase: this way git rebase --continue
+ # will work correctly as it expects HEAD to be
+ # placed before the commit of the next action
+ checkout_onto
+
+ warn "You can fix this with 'git rebase --edit-todo'."
+ die "Or you can abort the rebase with 'git rebase --abort'."
+ fi
+}
+
# 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
@@ -981,7 +1181,10 @@ else
revisions=$onto...$orig_head
shortrevisions=$shorthead
fi
-git rev-list $merges_option --pretty=oneline --reverse --left-right --topo-order \
+format=$(git config --get rebase.instructionFormat)
+# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
+git rev-list $merges_option --format="%m%H ${format:-%s}" \
+ --reverse --left-right --topo-order \
$revisions ${restrict_revision+^$restrict_revision} | \
sed -n "s/^>//p" |
while read -r sha1 rest
@@ -1031,7 +1234,8 @@ then
git rev-list $revisions |
while read rev
do
- if test -f "$rewritten"/$rev && test "$(sane_grep "$rev" "$state_dir"/not-cherry-picks)" = ""
+ if test -f "$rewritten"/$rev &&
+ ! sane_grep "$rev" "$state_dir"/not-cherry-picks >/dev/null
then
# Use -f2 because if rev-list is telling us this commit is
# not worthwhile, we don't want to track its multiple heads,
@@ -1080,13 +1284,13 @@ git_sequence_editor "$todo" ||
has_action "$todo" ||
return 2
+check_todo_list
+
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
+checkout_onto
do_rest
}