From c9266d589482544d588ccfd95f54d0bfdb277aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Galan=20R=C3=A9mi?= Date: Mon, 29 Jun 2015 22:20:30 +0200 Subject: [PATCH 1/3] git-rebase -i: add command "drop" to remove a commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of removing a line to remove the commit, you can use the command "drop" (just like "pick" or "edit"). It has the same effect as deleting the line (removing the commit) except that you keep a visual trace of your actions, allowing a better control and reducing the possibility of removing a commit by mistake. Signed-off-by: Galan Rémi Signed-off-by: Junio C Hamano --- Documentation/git-rebase.txt | 3 +++ git-rebase--interactive.sh | 3 ++- t/lib-rebase.sh | 4 ++-- t/t3404-rebase-interactive.sh | 18 ++++++++++++++++++ 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 1d01baa5fc..34bd070af8 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -514,6 +514,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 diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index bab0dccc04..2882276643 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -152,6 +152,7 @@ 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. @@ -505,7 +506,7 @@ do_next () { rm -f "$msg" "$author_script" "$amend" || exit read -r command sha1 rest < "$todo" case "$command" in - "$comment_char"*|''|noop) + "$comment_char"*|''|noop|drop|d) mark_action_done ;; pick|p) diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 6bd252212a..fdbc9007d0 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -14,7 +14,7 @@ # specified line. # # " " -- 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 () { 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";; diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index ac429a0bbb..3d059e5c09 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1102,4 +1102,22 @@ test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' ' test $(git cat-file commit HEAD | sed -ne \$p) = I ' +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) +' + test_done From 370799596081e1d1f862e42305ba8119183bde94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Galan=20R=C3=A9mi?= Date: Mon, 29 Jun 2015 22:20:31 +0200 Subject: [PATCH 2/3] git rebase -i: warn about removed commits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Junio C Hamano --- Documentation/config.txt | 11 ++++ Documentation/git-rebase.txt | 6 ++ git-rebase--interactive.sh | 117 +++++++++++++++++++++++++++++++++- t/t3404-rebase-interactive.sh | 66 +++++++++++++++++++ 4 files changed, 197 insertions(+), 3 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 5f76e8cf4e..8169308aad 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -2160,6 +2160,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". + receive.advertiseAtomic:: By default, git-receive-pack will advertise the atomic push capability to its clients. If you don't want to this capability diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 34bd070af8..2ca3b8d599 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -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. + OPTIONS ------- --onto :: diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 2882276643..c26a200a6c 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -156,8 +156,17 @@ Commands: 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 () { @@ -837,6 +846,108 @@ add_exec_commands () { mv "$1.new" "$1" } +# 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_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 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 @@ -1077,13 +1188,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 } diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 3d059e5c09..cfa741018d 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1120,4 +1120,70 @@ test_expect_success 'drop' ' test A = $(git cat-file commit HEAD^^ | sed -ne \$p) ' +cat >expect <actual && + test D = $(git cat-file commit HEAD | sed -ne \$p) && + test_cmp expect actual +' + +cat >expect <actual && + test_cmp expect actual && + test D = $(git cat-file commit HEAD | sed -ne \$p) +' + +cat >expect <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) +' + test_done From 804098bb30a5339cccb0be981a3e876245aa0ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Galan=20R=C3=A9mi?= Date: Mon, 29 Jun 2015 22:20:32 +0200 Subject: [PATCH 3/3] git rebase -i: add static check for commands and SHA-1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check before the start of the rebasing if the commands exists, and for the commands expecting a SHA-1, check if the SHA-1 is present and corresponds to a commit. In case of error, print the error, stop git rebase and prompt the user to fix with 'git rebase --edit-todo' or to abort. This allows to avoid doing half of a rebase before finding an error and giving back what's left of the todo list to the user and prompt him to fix when it might be too late for him to do so (he might have to abort and restart the rebase). Signed-off-by: Galan Rémi Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 72 +++++++++++++++++++++++++++++++++++ t/lib-rebase.sh | 5 +++ t/t3404-rebase-interactive.sh | 39 +++++++++++++++++++ 3 files changed, 116 insertions(+) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index c26a200a6c..dcc3401b5a 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -846,6 +846,71 @@ 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 () { @@ -887,6 +952,8 @@ get_missing_commit_check_level () { # 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 @@ -935,6 +1002,11 @@ check_todo_list () { ;; 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 diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index fdbc9007d0..9a96e1566d 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -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;; diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index cfa741018d..ebdab4b95d 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1186,4 +1186,43 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' ' test B = $(git cat-file commit HEAD^ | sed -ne \$p) ' +cat >expect <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 <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