Skip to content

Commit 3707995

Browse files
LostRedSkygitster
authored andcommitted
git rebase -i: warn about removed commits
Check if commits were removed (i.e. a line was deleted) and print warnings or stop git rebase depending on the value of the configuration variable rebase.missingCommitsCheck. This patch gives the user the possibility to avoid silent loss of information (losing a commit through deleting the line in this case) if he wants. Add the configuration variable rebase.missingCommitsCheck. - When unset or set to "ignore", no checking is done. - When set to "warn", the commits are checked, warnings are displayed but git rebase still proceeds. - When set to "error", the commits are checked, warnings are displayed and the rebase is stopped. (The user can then use 'git rebase --edit-todo' and 'git rebase --continue', or 'git rebase --abort') rebase.missingCommitsCheck defaults to "ignore". Signed-off-by: Galan Rémi <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent c9266d5 commit 3707995

File tree

4 files changed

+197
-3
lines changed

4 files changed

+197
-3
lines changed

Documentation/config.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2160,6 +2160,17 @@ rebase.autoStash::
21602160
successful rebase might result in non-trivial conflicts.
21612161
Defaults to false.
21622162

2163+
rebase.missingCommitsCheck::
2164+
If set to "warn", git rebase -i will print a warning if some
2165+
commits are removed (e.g. a line was deleted), however the
2166+
rebase will still proceed. If set to "error", it will print
2167+
the previous warning and stop the rebase, 'git rebase
2168+
--edit-todo' can then be used to correct the error. If set to
2169+
"ignore", no checking is done.
2170+
To drop a commit without warning or error, use the `drop`
2171+
command in the todo-list.
2172+
Defaults to "ignore".
2173+
21632174
receive.advertiseAtomic::
21642175
By default, git-receive-pack will advertise the atomic push
21652176
capability to its clients. If you don't want to this capability

Documentation/git-rebase.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ rebase.autoSquash::
213213
rebase.autoStash::
214214
If set to true enable '--autostash' option by default.
215215

216+
rebase.missingCommitsCheck::
217+
If set to "warn", print warnings about removed commits in
218+
interactive mode. If set to "error", print the warnings and
219+
stop the rebase. If set to "ignore", no checking is
220+
done. "ignore" by default.
221+
216222
OPTIONS
217223
-------
218224
--onto <newbase>::

git-rebase--interactive.sh

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,17 @@ Commands:
156156
157157
These lines can be re-ordered; they are executed from top to bottom.
158158
159+
EOF
160+
if test $(get_missing_commit_check_level) = error
161+
then
162+
git stripspace --comment-lines >>"$todo" <<\EOF
163+
Do not remove any line. Use 'drop' explicitly to remove a commit.
164+
EOF
165+
else
166+
git stripspace --comment-lines >>"$todo" <<\EOF
159167
If you remove a line here THAT COMMIT WILL BE LOST.
160168
EOF
169+
fi
161170
}
162171

163172
make_patch () {
@@ -837,6 +846,108 @@ add_exec_commands () {
837846
mv "$1.new" "$1"
838847
}
839848

849+
# Print the list of the SHA-1 of the commits
850+
# from stdin to stdout
851+
todo_list_to_sha_list () {
852+
git stripspace --strip-comments |
853+
while read -r command sha1 rest
854+
do
855+
case $command in
856+
"$comment_char"*|''|noop|x|"exec")
857+
;;
858+
*)
859+
long_sha=$(git rev-list --no-walk "$sha1" 2>/dev/null)
860+
printf "%s\n" "$long_sha"
861+
;;
862+
esac
863+
done
864+
}
865+
866+
# Use warn for each line in stdin
867+
warn_lines () {
868+
while read -r line
869+
do
870+
warn " - $line"
871+
done
872+
}
873+
874+
# Switch to the branch in $into and notify it in the reflog
875+
checkout_onto () {
876+
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
877+
output git checkout $onto || die_abort "could not detach HEAD"
878+
git update-ref ORIG_HEAD $orig_head
879+
}
880+
881+
get_missing_commit_check_level () {
882+
check_level=$(git config --get rebase.missingCommitsCheck)
883+
check_level=${check_level:-ignore}
884+
# Don't be case sensitive
885+
printf '%s' "$check_level" | tr 'A-Z' 'a-z'
886+
}
887+
888+
# Check if the user dropped some commits by mistake
889+
# Behaviour determined by rebase.missingCommitsCheck.
890+
check_todo_list () {
891+
raise_error=f
892+
893+
check_level=$(get_missing_commit_check_level)
894+
895+
case "$check_level" in
896+
warn|error)
897+
# Get the SHA-1 of the commits
898+
todo_list_to_sha_list <"$todo".backup >"$todo".oldsha1
899+
todo_list_to_sha_list <"$todo" >"$todo".newsha1
900+
901+
# Sort the SHA-1 and compare them
902+
sort -u "$todo".oldsha1 >"$todo".oldsha1+
903+
mv "$todo".oldsha1+ "$todo".oldsha1
904+
sort -u "$todo".newsha1 >"$todo".newsha1+
905+
mv "$todo".newsha1+ "$todo".newsha1
906+
comm -2 -3 "$todo".oldsha1 "$todo".newsha1 >"$todo".miss
907+
908+
# Warn about missing commits
909+
if test -s "$todo".miss
910+
then
911+
test "$check_level" = error && raise_error=t
912+
913+
warn "Warning: some commits may have been dropped" \
914+
"accidentally."
915+
warn "Dropped commits (newer to older):"
916+
917+
# Make the list user-friendly and display
918+
opt="--no-walk=sorted --format=oneline --abbrev-commit --stdin"
919+
git rev-list $opt <"$todo".miss | warn_lines
920+
921+
warn "To avoid this message, use \"drop\" to" \
922+
"explicitly remove a commit."
923+
warn
924+
warn "Use 'git config rebase.missingCommitsCheck' to change" \
925+
"the level of warnings."
926+
warn "The possible behaviours are: ignore, warn, error."
927+
warn
928+
fi
929+
;;
930+
ignore)
931+
;;
932+
*)
933+
warn "Unrecognized setting $check_level for option" \
934+
"rebase.missingCommitsCheck. Ignoring."
935+
;;
936+
esac
937+
938+
if test $raise_error = t
939+
then
940+
# Checkout before the first commit of the
941+
# rebase: this way git rebase --continue
942+
# will work correctly as it expects HEAD to be
943+
# placed before the commit of the next action
944+
checkout_onto
945+
946+
warn "You can fix this with 'git rebase --edit-todo'."
947+
die "Or you can abort the rebase with 'git rebase --abort'."
948+
fi
949+
}
950+
840951
# The whole contents of this file is run by dot-sourcing it from
841952
# inside a shell function. It used to be that "return"s we see
842953
# below were not inside any function, and expected to return
@@ -1077,13 +1188,13 @@ git_sequence_editor "$todo" ||
10771188
has_action "$todo" ||
10781189
return 2
10791190

1191+
check_todo_list
1192+
10801193
expand_todo_ids
10811194

10821195
test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
10831196

1084-
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
1085-
output git checkout $onto || die_abort "could not detach HEAD"
1086-
git update-ref ORIG_HEAD $orig_head
1197+
checkout_onto
10871198
do_rest
10881199

10891200
}

t/t3404-rebase-interactive.sh

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,4 +1120,70 @@ test_expect_success 'drop' '
11201120
test A = $(git cat-file commit HEAD^^ | sed -ne \$p)
11211121
'
11221122

1123+
cat >expect <<EOF
1124+
Successfully rebased and updated refs/heads/missing-commit.
1125+
EOF
1126+
1127+
test_expect_success 'rebase -i respects rebase.missingCommitsCheck = ignore' '
1128+
test_config rebase.missingCommitsCheck ignore &&
1129+
rebase_setup_and_clean missing-commit &&
1130+
set_fake_editor &&
1131+
FAKE_LINES="1 2 3 4" \
1132+
git rebase -i --root 2>actual &&
1133+
test D = $(git cat-file commit HEAD | sed -ne \$p) &&
1134+
test_cmp expect actual
1135+
'
1136+
1137+
cat >expect <<EOF
1138+
Warning: some commits may have been dropped accidentally.
1139+
Dropped commits (newer to older):
1140+
- $(git rev-list --pretty=oneline --abbrev-commit -1 master)
1141+
To avoid this message, use "drop" to explicitly remove a commit.
1142+
1143+
Use 'git config rebase.missingCommitsCheck' to change the level of warnings.
1144+
The possible behaviours are: ignore, warn, error.
1145+
1146+
Successfully rebased and updated refs/heads/missing-commit.
1147+
EOF
1148+
1149+
test_expect_success 'rebase -i respects rebase.missingCommitsCheck = warn' '
1150+
test_config rebase.missingCommitsCheck warn &&
1151+
rebase_setup_and_clean missing-commit &&
1152+
set_fake_editor &&
1153+
FAKE_LINES="1 2 3 4" \
1154+
git rebase -i --root 2>actual &&
1155+
test_cmp expect actual &&
1156+
test D = $(git cat-file commit HEAD | sed -ne \$p)
1157+
'
1158+
1159+
cat >expect <<EOF
1160+
Warning: some commits may have been dropped accidentally.
1161+
Dropped commits (newer to older):
1162+
- $(git rev-list --pretty=oneline --abbrev-commit -1 master)
1163+
- $(git rev-list --pretty=oneline --abbrev-commit -1 master~2)
1164+
To avoid this message, use "drop" to explicitly remove a commit.
1165+
1166+
Use 'git config rebase.missingCommitsCheck' to change the level of warnings.
1167+
The possible behaviours are: ignore, warn, error.
1168+
1169+
You can fix this with 'git rebase --edit-todo'.
1170+
Or you can abort the rebase with 'git rebase --abort'.
1171+
EOF
1172+
1173+
test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '
1174+
test_config rebase.missingCommitsCheck error &&
1175+
rebase_setup_and_clean missing-commit &&
1176+
set_fake_editor &&
1177+
test_must_fail env FAKE_LINES="1 2 4" \
1178+
git rebase -i --root 2>actual &&
1179+
test_cmp expect actual &&
1180+
cp .git/rebase-merge/git-rebase-todo.backup \
1181+
.git/rebase-merge/git-rebase-todo &&
1182+
FAKE_LINES="1 2 drop 3 4 drop 5" \
1183+
git rebase --edit-todo &&
1184+
git rebase --continue &&
1185+
test D = $(git cat-file commit HEAD | sed -ne \$p) &&
1186+
test B = $(git cat-file commit HEAD^ | sed -ne \$p)
1187+
'
1188+
11231189
test_done

0 commit comments

Comments
 (0)