[PATCH 12/14] revert: Introduce --reset to cleanup sequencer data
From: Ramkumar Ramachandra <hidden>
Date: 2016-06-15 22:51:33
Subsystem:
the rest · Maintainer:
Linus Torvalds
When the sequencer data is persisted after a failed cherry-pick, don't allow subsequent calls to cherry-pick to clobber this state: instead, error out with the complaint that an existing cherry-pick is in progress. To fix existing tests and the "rebase -i" script, introduce a new "--reset" command-line option to call after every failed cherry-pick; it essentially clears out the sequencer data, thereby allowing subsequent calls. Helped-by: Jonathan Nieder [off-list ref] Signed-off-by: Ramkumar Ramachandra <redacted> --- This is perfect but for the fact that 'git reset --hard' doesn't blow away the sequencer state. Why I haven't implemented that yet: should ONLY a hard reset blow away the state? builtin/revert.c | 54 ++++++++++++++++++++++++++++++----- git-rebase--interactive.sh | 25 +++++++++++++--- t/t3032-merge-recursive-options.sh | 2 + t/t3501-revert-cherry-pick.sh | 1 + t/t3502-cherry-pick-merge.sh | 9 ++++- t/t3504-cherry-pick-rerere.sh | 2 + t/t3505-cherry-pick-empty.sh | 14 ++++----- t/t3506-cherry-pick-ff.sh | 3 ++ t/t3507-cherry-pick-conflict.sh | 24 ++++++++++++--- t/t3510-cherry-pick-sequence.sh | 14 +++++++++ t/t7502-commit.sh | 1 + 11 files changed, 121 insertions(+), 28 deletions(-)
diff --git a/builtin/revert.c b/builtin/revert.c
index 8cdcdb6..9e18d64 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c@@ -46,6 +46,8 @@ enum replay_action { REVERT, CHERRY_PICK }; struct replay_opts { enum replay_action action; + int reset; + /* Boolean options */ int edit; int record_origin;
@@ -108,6 +110,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) const char * const * usage_str = revert_or_cherry_pick_usage(opts); int noop; struct option options[] = { + OPT_BOOLEAN(0, "reset", &opts->reset, "forget the current operation"), OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"), OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"), { OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)",
@@ -136,7 +139,21 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) opts->commit_argc = parse_options(argc, argv, NULL, options, usage_str, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN); - if (opts->commit_argc < 2) + + /* Check for incompatible command line arguments */ + if (opts->reset) { + verify_opt_compatible(me, "--reset", + "--no-commit", opts->no_commit, + "--signoff", opts->signoff, + "--mainline", opts->mainline, + "--strategy", opts->strategy ? 1 : 0, + "--strategy-option", opts->xopts ? 1 : 0, + "-x", opts->record_origin, + "--ff", opts->allow_ff, + NULL); + } + + else if (opts->commit_argc < 2) usage_with_options(usage_str, options); if (opts->allow_ff)
@@ -625,8 +642,11 @@ static void walk_revs_populate_todo(struct commit_list **todo_list, static void create_seq_dir(void) { if (file_exists(git_path(SEQ_DIR))) { - if (!is_directory(git_path(SEQ_DIR)) && remove_path(git_path(SEQ_DIR)) < 0) - die(_("Could not remove %s"), git_path(SEQ_DIR)); + error(_("%s already exists."), git_path(SEQ_DIR)); + advise(_("This usually means that a %s operation is in progress."), me); + advise(_("Use %s --continue to continue the operation"), me); + advise(_("or use %s --reset to forget about it"), me); + die(_("%s failed"), me); } else if (mkdir(git_path(SEQ_DIR), 0777) < 0) die_errno(_("Could not create sequencer directory '%s'."), git_path(SEQ_DIR)); }
@@ -709,13 +729,31 @@ static int process_continuation(struct replay_opts *opts) read_and_refresh_cache(me, opts); - walk_revs_populate_todo(&todo_list, opts); - create_seq_dir(); - if (!get_sha1("HEAD", sha1)) - persist_head(sha1_to_hex(sha1)); - persist_todo(todo_list, opts); + if (opts->reset) { + if (!file_exists(git_path(SEQ_TODO_FILE))) + goto error; + return cleanup_sequencer_data(); + } else { + /* + * Start a new cherry-pick/ revert sequence; but + * first, make sure that an existing one isn't in + * progress + */ + if (file_exists(git_path(SEQ_TODO_FILE))) { + error(_("A %s is already in progress"), me); + advise(_("Use %s --reset to forget about it"), me); + return -1; + } + walk_revs_populate_todo(&todo_list, opts); + create_seq_dir(); + if (!get_sha1("HEAD", sha1)) + save_head(sha1_to_hex(sha1)); + save_todo(todo_list, opts); + } return pick_commits(todo_list, opts); +error: + return error(_("No %s in progress"), me); } int cmd_revert(int argc, const char **argv, const char *prefix)
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 65690af..14c57dd 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh@@ -136,6 +136,10 @@ make_patch () { get_author_ident_from_commit "$1" > "$author_script" } +clear_cherry_pick_state () { + git cherry-pick --reset +} + die_with_patch () { echo "$1" > "$state_dir"/stopped-sha make_patch "$1"
@@ -279,8 +283,10 @@ pick_one_preserving_merges () { echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list" ;; *) - output git cherry-pick "$@" || + output git cherry-pick "$@" || { + clear_cherry_pick_state die_with_patch $sha1 "Could not pick $sha1" + } ;; esac ;;
@@ -385,16 +391,20 @@ do_next () { comment_for_reflog pick mark_action_done - pick_one $sha1 || + pick_one $sha1 || { + clear_cherry_pick_state die_with_patch $sha1 "Could not apply $sha1... $rest" + } record_in_rewritten $sha1 ;; reword|r) comment_for_reflog reword mark_action_done - pick_one $sha1 || + pick_one $sha1 || { + clear_cherry_pick_state die_with_patch $sha1 "Could not apply $sha1... $rest" + } git commit --amend --no-post-rewrite record_in_rewritten $sha1 ;;
@@ -402,8 +412,10 @@ do_next () { comment_for_reflog edit mark_action_done - pick_one $sha1 || + pick_one $sha1 || { + clear_cherry_pick_state die_with_patch $sha1 "Could not apply $sha1... $rest" + } echo "$sha1" > "$state_dir"/stopped-sha make_patch $sha1 git rev-parse --verify HEAD > "$amend"
@@ -438,7 +450,10 @@ do_next () { echo "$author_script_content" > "$author_script" eval "$author_script_content" output git reset --soft HEAD^ - pick_one -n $sha1 || die_failed_squash $sha1 "$rest" + pick_one -n $sha1 || { + clear_cherry_pick_state + die_failed_squash $sha1 "$rest" + } case "$(peek_next_command)" in squash|s|fixup|f) # This is an intermediate commit; its message will only be
diff --git a/t/t3032-merge-recursive-options.sh b/t/t3032-merge-recursive-options.sh
index 2b17311..81191ae 100755
--- a/t/t3032-merge-recursive-options.sh
+++ b/t/t3032-merge-recursive-options.sh@@ -113,8 +113,10 @@ test_expect_success '--ignore-space-change makes merge succeed' ' test_expect_success 'naive cherry-pick fails' ' git read-tree --reset -u HEAD && test_must_fail git cherry-pick --no-commit remote && + git cherry-pick --reset && git read-tree --reset -u HEAD && test_must_fail git cherry-pick remote && + git cherry-pick --reset && test_must_fail git update-index --refresh && grep "<<<<<<" text.txt '
diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
index 595d2ff..e0c805d 100755
--- a/t/t3501-revert-cherry-pick.sh
+++ b/t/t3501-revert-cherry-pick.sh@@ -96,6 +96,7 @@ test_expect_success 'revert forbidden on dirty working tree' ' echo content >extra_file && git add extra_file && test_must_fail git revert HEAD 2>errors && + git revert --reset && test_i18ngrep "Your local changes would be overwritten by " errors '
diff --git a/t/t3502-cherry-pick-merge.sh b/t/t3502-cherry-pick-merge.sh
index 0ab52da..db21e47 100755
--- a/t/t3502-cherry-pick-merge.sh
+++ b/t/t3502-cherry-pick-merge.sh@@ -36,6 +36,7 @@ test_expect_success 'cherry-pick a non-merge with -m should fail' ' git reset --hard && git checkout a^0 && test_must_fail git cherry-pick -m 1 b && + git cherry-pick --reset && git diff --exit-code a -- '
@@ -45,6 +46,7 @@ test_expect_success 'cherry pick a merge without -m should fail' ' git reset --hard && git checkout a^0 && test_must_fail git cherry-pick c && + git cherry-pick --reset && git diff --exit-code a -- '
@@ -71,8 +73,8 @@ test_expect_success 'cherry pick a merge relative to nonexistent parent should f git reset --hard && git checkout b^0 && - test_must_fail git cherry-pick -m 3 c - + test_must_fail git cherry-pick -m 3 c && + git cherry-pick --reset ' test_expect_success 'revert a non-merge with -m should fail' '
@@ -80,6 +82,7 @@ test_expect_success 'revert a non-merge with -m should fail' ' git reset --hard && git checkout c^0 && test_must_fail git revert -m 1 b && + git cherry-pick --reset && git diff --exit-code c '
@@ -89,6 +92,7 @@ test_expect_success 'revert a merge without -m should fail' ' git reset --hard && git checkout c^0 && test_must_fail git revert c && + git cherry-pick --reset && git diff --exit-code c '
@@ -116,6 +120,7 @@ test_expect_success 'revert a merge relative to nonexistent parent should fail' git reset --hard && git checkout c^0 && test_must_fail git revert -m 3 c && + git cherry-pick --reset && git diff --exit-code c '
diff --git a/t/t3504-cherry-pick-rerere.sh b/t/t3504-cherry-pick-rerere.sh
index e6a6481..85d5fc1 100755
--- a/t/t3504-cherry-pick-rerere.sh
+++ b/t/t3504-cherry-pick-rerere.sh@@ -29,6 +29,7 @@ test_expect_success 'fixup' ' test_expect_success 'cherry-pick conflict' ' test_must_fail git cherry-pick master && + git cherry-pick --reset && test_cmp expect foo '
@@ -39,6 +40,7 @@ test_expect_success 'reconfigure' ' test_expect_success 'cherry-pick conflict without rerere' ' test_must_fail git cherry-pick master && + git cherry-pick --reset && test_must_fail test_cmp expect foo '
diff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh
index c10b28c..dc02227 100755
--- a/t/t3505-cherry-pick-empty.sh
+++ b/t/t3505-cherry-pick-empty.sh@@ -23,10 +23,9 @@ test_expect_success setup ' ' test_expect_success 'cherry-pick an empty commit' ' - git checkout master && { - git cherry-pick empty-branch^ - test "$?" = 1 - } + git checkout master && + test_expect_code 1 git cherry-pick empty-branch^ + git cherry-pick --reset ' test_expect_success 'index lockfile was removed' '
@@ -36,10 +35,9 @@ test_expect_success 'index lockfile was removed' ' ' test_expect_success 'cherry-pick a commit with an empty message' ' - git checkout master && { - git cherry-pick empty-branch - test "$?" = 1 - } + git checkout master && + test_expect_code 1 git cherry-pick empty-branch && + git cherry-pick --reset ' test_expect_success 'index lockfile was removed' '
diff --git a/t/t3506-cherry-pick-ff.sh b/t/t3506-cherry-pick-ff.sh
index 51ca391..abd63a5 100755
--- a/t/t3506-cherry-pick-ff.sh
+++ b/t/t3506-cherry-pick-ff.sh@@ -67,12 +67,14 @@ test_expect_success 'merge setup' ' test_expect_success 'cherry-pick a non-merge with --ff and -m should fail' ' git reset --hard A -- && test_must_fail git cherry-pick --ff -m 1 B && + git cherry-pick --reset && git diff --exit-code A -- ' test_expect_success 'cherry pick a merge with --ff but without -m should fail' ' git reset --hard A -- && test_must_fail git cherry-pick --ff C && + git cherry-pick --reset && git diff --exit-code A -- '
@@ -93,6 +95,7 @@ test_expect_success 'cherry pick with --ff a merge (2)' ' test_expect_success 'cherry pick a merge relative to nonexistent parent with --ff should fail' ' git reset --hard B -- && test_must_fail git cherry-pick --ff -m 3 C + git cherry-pick --reset ' test_expect_success 'cherry pick a root commit with --ff' '
diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh
index 212ec54..86f8626 100755
--- a/t/t3507-cherry-pick-conflict.sh
+++ b/t/t3507-cherry-pick-conflict.sh@@ -39,6 +39,7 @@ test_expect_success 'failed cherry-pick does not advance HEAD' ' head=$(git rev-parse HEAD) && test_must_fail git cherry-pick picked && + git cherry-pick --reset && newhead=$(git rev-parse HEAD) && test "$head" = "$newhead"
@@ -55,6 +56,7 @@ test_expect_success 'advice from failed cherry-pick' " hint: and commit the result with 'git commit' EOF test_must_fail git cherry-pick picked 2>actual && + git cherry-pick --reset && test_i18ncmp expected actual "
@@ -62,7 +64,8 @@ test_expect_success 'advice from failed cherry-pick' " test_expect_success 'failed cherry-pick sets CHERRY_PICK_HEAD' ' pristine_detach initial && test_must_fail git cherry-pick picked && - test_cmp_rev picked CHERRY_PICK_HEAD + test_cmp_rev picked CHERRY_PICK_HEAD && + git cherry-pick --reset ' test_expect_success 'successful cherry-pick does not set CHERRY_PICK_HEAD' '
@@ -84,13 +87,15 @@ test_expect_success 'GIT_CHERRY_PICK_HELP suppresses CHERRY_PICK_HEAD' ' export GIT_CHERRY_PICK_HELP && test_must_fail git cherry-pick picked ) && - test_must_fail git rev-parse --verify CHERRY_PICK_HEAD + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD && + git cherry-pick --reset ' test_expect_success 'git reset clears CHERRY_PICK_HEAD' ' pristine_detach initial && test_must_fail git cherry-pick picked && + git cherry-pick --reset && git reset && test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
@@ -102,7 +107,8 @@ test_expect_success 'failed commit does not clear CHERRY_PICK_HEAD' ' test_must_fail git cherry-pick picked && test_must_fail git commit && - test_cmp_rev picked CHERRY_PICK_HEAD + test_cmp_rev picked CHERRY_PICK_HEAD && + git cherry-pick --reset ' test_expect_success 'cancelled commit does not clear CHERRY_PICK_HEAD' '
@@ -119,7 +125,8 @@ test_expect_success 'cancelled commit does not clear CHERRY_PICK_HEAD' ' test_must_fail git commit ) && - test_cmp_rev picked CHERRY_PICK_HEAD + test_cmp_rev picked CHERRY_PICK_HEAD && + git cherry-pick --reset ' test_expect_success 'successful commit clears CHERRY_PICK_HEAD' '
@@ -130,13 +137,15 @@ test_expect_success 'successful commit clears CHERRY_PICK_HEAD' ' git add foo && git commit && - test_must_fail git rev-parse --verify CHERRY_PICK_HEAD + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD && + git cherry-pick --reset ' test_expect_success 'failed cherry-pick produces dirty index' ' pristine_detach initial && test_must_fail git cherry-pick picked && + git cherry-pick --reset && test_must_fail git update-index --refresh -q && test_must_fail git diff-index --exit-code HEAD
@@ -160,6 +169,7 @@ test_expect_success 'failed cherry-pick registers participants in index' ' git read-tree -u --reset HEAD && test_must_fail git cherry-pick picked && + git cherry-pick --reset && git ls-files --stage --unmerged > actual && test_cmp expected actual
@@ -176,6 +186,7 @@ test_expect_success 'failed cherry-pick describes conflict in work tree' ' EOF test_must_fail git cherry-pick picked && + git cherry-pick --reset && sed "s/[a-f0-9]*\.\.\./objid/" foo > actual && test_cmp expected actual
@@ -195,6 +206,7 @@ test_expect_success 'diff3 -m style' ' EOF test_must_fail git cherry-pick picked && + git cherry-pick --reset && sed "s/[a-f0-9]*\.\.\./objid/" foo > actual && test_cmp expected actual
@@ -227,6 +239,7 @@ test_expect_success 'revert also handles conflicts sanely' ' head=$(git rev-parse HEAD) && test_must_fail git revert picked && + git revert --reset && newhead=$(git rev-parse HEAD) && git ls-files --stage --unmerged > actual-stages &&
@@ -252,6 +265,7 @@ test_expect_success 'revert conflict, diff3 -m style' ' EOF test_must_fail git revert picked && + git revert --reset && sed "s/[a-f0-9]*\.\.\./objid/" foo > actual && test_cmp expected actual
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index 83e2722..9e97a7b 100644
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh@@ -34,4 +34,18 @@ test_expect_success 'cherry-pick cleans up sequencer directory upon success' ' test_path_is_missing .git/sequencer ' +test_expect_success '--reset complains when no cherry-pick is in progress' ' + pristine_detach initial && + test_must_fail git cherry-pick --reset >actual 2>&1 && + test_i18ngrep "error" actual +' + +test_expect_success '--reset cleans up sequencer directory' ' + pristine_detach initial && + head=$(git rev-parse HEAD) && + test_must_fail git cherry-pick base..picked && + git cherry-pick --reset && + test_path_is_missing .git/sequencer +' + test_done
diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh
index 3f3adc3..0d54027 100755
--- a/t/t7502-commit.sh
+++ b/t/t7502-commit.sh@@ -291,6 +291,7 @@ test_expect_success 'do not fire editor in the presence of conflicts' ' git commit -m second && # Must fail due to conflict test_must_fail git cherry-pick -n master && + git cherry-pick --reset && echo "editor not started" >.git/result && ( GIT_EDITOR="$(pwd)/.git/FAKE_EDITOR" &&
--
1.7.5.GIT