Browse Source

Merge branch 'gr/rebase-i-drop-warn'

Add "drop commit-object-name subject" command as another way to
skip replaying of a commit in "rebase -i", and then punish those
who do not use it (and instead just remove the lines) by throwing
a warning.

* gr/rebase-i-drop-warn:
  git rebase -i: add static check for commands and SHA-1
  git rebase -i: warn about removed commits
  git-rebase -i: add command "drop" to remove a commit
maint
Junio C Hamano 10 years ago
parent
commit
3a760cad79
  1. 11
      Documentation/config.txt
  2. 9
      Documentation/git-rebase.txt
  3. 192
      git-rebase--interactive.sh
  4. 9
      t/lib-rebase.sh
  5. 123
      t/t3404-rebase-interactive.sh

11
Documentation/config.txt

@ -2180,6 +2180,17 @@ rebase.autoStash:: @@ -2180,6 +2180,17 @@ rebase.autoStash::
successful rebase might result in non-trivial conflicts.
Defaults to false.

rebase.missingCommitsCheck::
If set to "warn", git rebase -i will print a warning if some
commits are removed (e.g. a line was deleted), however the
rebase will still proceed. If set to "error", it will print
the previous warning and stop the rebase, 'git rebase
--edit-todo' can then be used to correct the error. If set to
"ignore", no checking is done.
To drop a commit without warning or error, use the `drop`
command in the todo-list.
Defaults to "ignore".

rebase.instructionFormat
A format string, as specified in linkgit:git-log[1], to be used for
the instruction list during an interactive rebase. The format will automatically

9
Documentation/git-rebase.txt

@ -213,6 +213,12 @@ rebase.autoSquash:: @@ -213,6 +213,12 @@ rebase.autoSquash::
rebase.autoStash::
If set to true enable '--autostash' option by default.

rebase.missingCommitsCheck::
If set to "warn", print warnings about removed commits in
interactive mode. If set to "error", print the warnings and
stop the rebase. If set to "ignore", no checking is
done. "ignore" by default.

rebase.instructionFormat::
Custom commit list format to use during an '--interactive' rebase.

@ -521,6 +527,9 @@ rebasing. @@ -521,6 +527,9 @@ rebasing.
If you just want to edit the commit message for a commit, replace the
command "pick" with the command "reword".

To drop a commit, replace the command "pick" with "drop", or just
delete the matching line.

If you want to fold two or more commits into one, replace the command
"pick" for the second and subsequent commits with "squash" or "fixup".
If the commits had different authors, the folded commit will be

192
git-rebase--interactive.sh

@ -152,11 +152,21 @@ Commands: @@ -152,11 +152,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 () {
@ -505,7 +515,7 @@ do_next () { @@ -505,7 +515,7 @@ 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
;;
pick|p)
@ -844,6 +854,180 @@ add_exec_commands () { @@ -844,6 +854,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 to display if incorrect SHA-1
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
warn "Warning: the SHA-1 is missing or isn't" \
"a commit in the following line:"
warn " - $2"
warn
fi

return $badsha
}

# prints the bad commits and bad commands
# from the todolist in stdin
check_bad_cmd_and_sha () {
retval=0
git stripspace --strip-comments |
(
while read -r line
do
IFS=' '
set -- $line
command=$1
sha1=$2

case $command in
''|noop|x|"exec")
# Doesn't expect a SHA-1
;;
pick|p|drop|d|reword|r|edit|e|squash|s|fixup|f)
if ! check_commit_sha $sha1 "$line"
then
retval=1
fi
;;
*)
warn "Warning: the command isn't recognized" \
"in the following line:"
warn " - $line"
warn
retval=1
;;
esac
done

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
@ -1094,13 +1278,13 @@ git_sequence_editor "$todo" || @@ -1094,13 +1278,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

}

9
t/lib-rebase.sh

@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
# specified line.
#
# "<cmd> <lineno>" -- add a line with the specified command
# ("squash", "fixup", "edit", or "reword") and the SHA1 taken
# ("squash", "fixup", "edit", "reword" or "drop") and the SHA1 taken
# from the specified line.
#
# "exec_cmd_with_args" -- add an "exec cmd with args" line.
@ -46,7 +46,7 @@ set_fake_editor () { @@ -46,7 +46,7 @@ set_fake_editor () {
action=pick
for line in $FAKE_LINES; do
case $line in
squash|fixup|edit|reword)
squash|fixup|edit|reword|drop)
action="$line";;
exec*)
echo "$line" | sed 's/_/ /g' >> "$1";;
@ -54,6 +54,11 @@ set_fake_editor () { @@ -54,6 +54,11 @@ set_fake_editor () {
echo '# comment' >> "$1";;
">")
echo >> "$1";;
bad)
action="badcmd";;
fakesha)
echo "$action XXXXXXX False commit" >> "$1"
action=pick;;
*)
sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
action=pick;;

123
t/t3404-rebase-interactive.sh

@ -1123,4 +1123,127 @@ test_expect_success 'rebase --continue removes CHERRY_PICK_HEAD' ' @@ -1123,4 +1123,127 @@ test_expect_success 'rebase --continue removes CHERRY_PICK_HEAD' '
test ! -f .git/CHERRY_PICK_HEAD
'

rebase_setup_and_clean () {
test_when_finished "
git checkout master &&
test_might_fail git branch -D $1 &&
test_might_fail git rebase --abort
" &&
git checkout -b $1 master
}

test_expect_success 'drop' '
rebase_setup_and_clean drop-test &&
set_fake_editor &&
FAKE_LINES="1 drop 2 3 drop 4 5" git rebase -i --root &&
test E = $(git cat-file commit HEAD | sed -ne \$p) &&
test C = $(git cat-file commit HEAD^ | sed -ne \$p) &&
test A = $(git cat-file commit HEAD^^ | sed -ne \$p)
'

cat >expect <<EOF
Successfully rebased and updated refs/heads/missing-commit.
EOF

test_expect_success 'rebase -i respects rebase.missingCommitsCheck = ignore' '
test_config rebase.missingCommitsCheck ignore &&
rebase_setup_and_clean missing-commit &&
set_fake_editor &&
FAKE_LINES="1 2 3 4" \
git rebase -i --root 2>actual &&
test D = $(git cat-file commit HEAD | sed -ne \$p) &&
test_cmp expect actual
'

cat >expect <<EOF
Warning: some commits may have been dropped accidentally.
Dropped commits (newer to older):
- $(git rev-list --pretty=oneline --abbrev-commit -1 master)
To avoid this message, use "drop" to explicitly remove a commit.

Use 'git config rebase.missingCommitsCheck' to change the level of warnings.
The possible behaviours are: ignore, warn, error.

Successfully rebased and updated refs/heads/missing-commit.
EOF

test_expect_success 'rebase -i respects rebase.missingCommitsCheck = warn' '
test_config rebase.missingCommitsCheck warn &&
rebase_setup_and_clean missing-commit &&
set_fake_editor &&
FAKE_LINES="1 2 3 4" \
git rebase -i --root 2>actual &&
test_cmp expect actual &&
test D = $(git cat-file commit HEAD | sed -ne \$p)
'

cat >expect <<EOF
Warning: some commits may have been dropped accidentally.
Dropped commits (newer to older):
- $(git rev-list --pretty=oneline --abbrev-commit -1 master)
- $(git rev-list --pretty=oneline --abbrev-commit -1 master~2)
To avoid this message, use "drop" to explicitly remove a commit.

Use 'git config rebase.missingCommitsCheck' to change the level of warnings.
The possible behaviours are: ignore, warn, error.

You can fix this with 'git rebase --edit-todo'.
Or you can abort the rebase with 'git rebase --abort'.
EOF

test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '
test_config rebase.missingCommitsCheck error &&
rebase_setup_and_clean missing-commit &&
set_fake_editor &&
test_must_fail env FAKE_LINES="1 2 4" \
git rebase -i --root 2>actual &&
test_cmp expect actual &&
cp .git/rebase-merge/git-rebase-todo.backup \
.git/rebase-merge/git-rebase-todo &&
FAKE_LINES="1 2 drop 3 4 drop 5" \
git rebase --edit-todo &&
git rebase --continue &&
test D = $(git cat-file commit HEAD | sed -ne \$p) &&
test B = $(git cat-file commit HEAD^ | sed -ne \$p)
'

cat >expect <<EOF
Warning: the command isn't recognized in the following line:
- badcmd $(git rev-list --oneline -1 master~1)

You can fix this with 'git rebase --edit-todo'.
Or you can abort the rebase with 'git rebase --abort'.
EOF

test_expect_success 'static check of bad command' '
rebase_setup_and_clean bad-cmd &&
set_fake_editor &&
test_must_fail env FAKE_LINES="1 2 3 bad 4 5" \
git rebase -i --root 2>actual &&
test_cmp expect actual &&
FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo &&
git rebase --continue &&
test E = $(git cat-file commit HEAD | sed -ne \$p) &&
test C = $(git cat-file commit HEAD^ | sed -ne \$p)
'

cat >expect <<EOF
Warning: the SHA-1 is missing or isn't a commit in the following line:
- edit XXXXXXX False commit

You can fix this with 'git rebase --edit-todo'.
Or you can abort the rebase with 'git rebase --abort'.
EOF

test_expect_success 'static check of bad SHA-1' '
rebase_setup_and_clean bad-sha &&
set_fake_editor &&
test_must_fail env FAKE_LINES="1 2 edit fakesha 3 4 5 #" \
git rebase -i --root 2>actual &&
test_cmp expect actual &&
FAKE_LINES="1 2 4 5 6" git rebase --edit-todo &&
git rebase --continue &&
test E = $(git cat-file commit HEAD | sed -ne \$p)
'

test_done

Loading…
Cancel
Save