Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

revert: introduce --abort to cancel a failed cherry-pick

After running some ill-advised command like "git cherry-pick
HEAD..linux-next", the bewildered novice may want to return to more
familiar territory.  Introduce a "git cherry-pick --abort" command
that rolls back the entire cherry-pick sequence and places the
repository back on solid ground.

Just like "git merge --abort", this internally uses "git reset
--merge", so local changes not involved in the conflict resolution are
preserved.

Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information...
commit 539047c19ec040819b6f6af2d55714195b812abb 1 parent 82433cd
jrn jrn authored gitster committed
1  Documentation/git-cherry-pick.txt
@@ -11,6 +11,7 @@ SYNOPSIS
11 11 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...
12 12 'git cherry-pick' --continue
13 13 'git cherry-pick' --quit
  14 +'git cherry-pick' --abort
14 15
15 16 DESCRIPTION
16 17 -----------
1  Documentation/git-revert.txt
@@ -11,6 +11,7 @@ SYNOPSIS
11 11 'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>...
12 12 'git revert' --continue
13 13 'git revert' --quit
  14 +'git revert' --abort
14 15
15 16 DESCRIPTION
16 17 -----------
3  Documentation/sequencer.txt
@@ -7,3 +7,6 @@
7 7 Forget about the current operation in progress. Can be used
8 8 to clear the sequencer state after a failed cherry-pick or
9 9 revert.
  10 +
  11 +--abort::
  12 + Cancel the operation and return to the pre-sequence state.
87 builtin/revert.c
@@ -40,7 +40,12 @@ static const char * const cherry_pick_usage[] = {
40 40 };
41 41
42 42 enum replay_action { REVERT, CHERRY_PICK };
43   -enum replay_subcommand { REPLAY_NONE, REPLAY_REMOVE_STATE, REPLAY_CONTINUE };
  43 +enum replay_subcommand {
  44 + REPLAY_NONE,
  45 + REPLAY_REMOVE_STATE,
  46 + REPLAY_CONTINUE,
  47 + REPLAY_ROLLBACK
  48 +};
44 49
45 50 struct replay_opts {
46 51 enum replay_action action;
@@ -135,9 +140,11 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
135 140 const char *me = action_name(opts);
136 141 int remove_state = 0;
137 142 int contin = 0;
  143 + int rollback = 0;
138 144 struct option options[] = {
139 145 OPT_BOOLEAN(0, "quit", &remove_state, "end revert or cherry-pick sequence"),
140 146 OPT_BOOLEAN(0, "continue", &contin, "resume revert or cherry-pick sequence"),
  147 + OPT_BOOLEAN(0, "abort", &rollback, "cancel revert or cherry-pick sequence"),
141 148 OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"),
142 149 OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"),
143 150 OPT_NOOP_NOARG('r', NULL),
@@ -173,6 +180,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
173 180 verify_opt_mutually_compatible(me,
174 181 "--quit", remove_state,
175 182 "--continue", contin,
  183 + "--abort", rollback,
176 184 NULL);
177 185
178 186 /* Set the subcommand */
@@ -180,6 +188,8 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
180 188 opts->subcommand = REPLAY_REMOVE_STATE;
181 189 else if (contin)
182 190 opts->subcommand = REPLAY_CONTINUE;
  191 + else if (rollback)
  192 + opts->subcommand = REPLAY_ROLLBACK;
183 193 else
184 194 opts->subcommand = REPLAY_NONE;
185 195
@@ -188,8 +198,12 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
188 198 char *this_operation;
189 199 if (opts->subcommand == REPLAY_REMOVE_STATE)
190 200 this_operation = "--quit";
191   - else
  201 + else if (opts->subcommand == REPLAY_CONTINUE)
192 202 this_operation = "--continue";
  203 + else {
  204 + assert(opts->subcommand == REPLAY_ROLLBACK);
  205 + this_operation = "--abort";
  206 + }
193 207
194 208 verify_opt_compatible(me, this_operation,
195 209 "--no-commit", opts->no_commit,
@@ -850,7 +864,7 @@ static int create_seq_dir(void)
850 864
851 865 if (file_exists(seq_dir)) {
852 866 error(_("a cherry-pick or revert is already in progress"));
853   - advise(_("try \"git cherry-pick (--continue | --quit)\""));
  867 + advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
854 868 return -1;
855 869 }
856 870 else if (mkdir(seq_dir, 0777) < 0)
@@ -873,6 +887,71 @@ static void save_head(const char *head)
873 887 die(_("Error wrapping up %s."), head_file);
874 888 }
875 889
  890 +static int reset_for_rollback(const unsigned char *sha1)
  891 +{
  892 + const char *argv[4]; /* reset --merge <arg> + NULL */
  893 + argv[0] = "reset";
  894 + argv[1] = "--merge";
  895 + argv[2] = sha1_to_hex(sha1);
  896 + argv[3] = NULL;
  897 + return run_command_v_opt(argv, RUN_GIT_CMD);
  898 +}
  899 +
  900 +static int rollback_single_pick(void)
  901 +{
  902 + unsigned char head_sha1[20];
  903 +
  904 + if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
  905 + !file_exists(git_path("REVERT_HEAD")))
  906 + return error(_("no cherry-pick or revert in progress"));
  907 + if (!resolve_ref("HEAD", head_sha1, 0, NULL))
  908 + return error(_("cannot resolve HEAD"));
  909 + if (is_null_sha1(head_sha1))
  910 + return error(_("cannot abort from a branch yet to be born"));
  911 + return reset_for_rollback(head_sha1);
  912 +}
  913 +
  914 +static int sequencer_rollback(struct replay_opts *opts)
  915 +{
  916 + const char *filename;
  917 + FILE *f;
  918 + unsigned char sha1[20];
  919 + struct strbuf buf = STRBUF_INIT;
  920 +
  921 + filename = git_path(SEQ_HEAD_FILE);
  922 + f = fopen(filename, "r");
  923 + if (!f && errno == ENOENT) {
  924 + /*
  925 + * There is no multiple-cherry-pick in progress.
  926 + * If CHERRY_PICK_HEAD or REVERT_HEAD indicates
  927 + * a single-cherry-pick in progress, abort that.
  928 + */
  929 + return rollback_single_pick();
  930 + }
  931 + if (!f)
  932 + return error(_("cannot open %s: %s"), filename,
  933 + strerror(errno));
  934 + if (strbuf_getline(&buf, f, '\n')) {
  935 + error(_("cannot read %s: %s"), filename, ferror(f) ?
  936 + strerror(errno) : _("unexpected end of file"));
  937 + goto fail;
  938 + }
  939 + if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') {
  940 + error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"),
  941 + filename);
  942 + goto fail;
  943 + }
  944 + if (reset_for_rollback(sha1))
  945 + goto fail;
  946 + strbuf_release(&buf);
  947 + fclose(f);
  948 + return 0;
  949 +fail:
  950 + strbuf_release(&buf);
  951 + fclose(f);
  952 + return -1;
  953 +}
  954 +
876 955 static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
877 956 {
878 957 const char *todo_file = git_path(SEQ_TODO_FILE);
@@ -977,6 +1056,8 @@ static int pick_revisions(struct replay_opts *opts)
977 1056 remove_sequencer_state(1);
978 1057 return 0;
979 1058 }
  1059 + if (opts->subcommand == REPLAY_ROLLBACK)
  1060 + return sequencer_rollback(opts);
980 1061 if (opts->subcommand == REPLAY_CONTINUE) {
981 1062 if (!file_exists(git_path(SEQ_TODO_FILE)))
982 1063 return error(_("No %s in progress"), action_name(opts));
96 t/t3510-cherry-pick-sequence.sh
@@ -2,6 +2,7 @@
2 2
3 3 test_description='Test cherry-pick continuation features
4 4
  5 + + yetanotherpick: rewrites foo to e
5 6 + anotherpick: rewrites foo to d
6 7 + picked: rewrites foo to c
7 8 + unrelatedpick: rewrites unrelated to reallyunrelated
@@ -19,6 +20,12 @@ pristine_detach () {
19 20 git clean -d -f -f -q -x
20 21 }
21 22
  23 +test_cmp_rev () {
  24 + git rev-parse --verify "$1" >expect.rev &&
  25 + git rev-parse --verify "$2" >actual.rev &&
  26 + test_cmp expect.rev actual.rev
  27 +}
  28 +
22 29 test_expect_success setup '
23 30 echo unrelated >unrelated &&
24 31 git add unrelated &&
@@ -27,6 +34,7 @@ test_expect_success setup '
27 34 test_commit unrelatedpick unrelated reallyunrelated &&
28 35 test_commit picked foo c &&
29 36 test_commit anotherpick foo d &&
  37 + test_commit yetanotherpick foo e &&
30 38 git config advice.detachedhead false
31 39
32 40 '
@@ -75,6 +83,11 @@ test_expect_success '--quit does not complain when no cherry-pick is in progress
75 83 git cherry-pick --quit
76 84 '
77 85
  86 +test_expect_success '--abort requires cherry-pick in progress' '
  87 + pristine_detach initial &&
  88 + test_must_fail git cherry-pick --abort
  89 +'
  90 +
78 91 test_expect_success '--quit cleans up sequencer state' '
79 92 pristine_detach initial &&
80 93 test_must_fail git cherry-pick base..picked &&
@@ -103,6 +116,79 @@ test_expect_success 'cherry-pick --reset (another name for --quit)' '
103 116 test_cmp expect actual
104 117 '
105 118
  119 +test_expect_success '--abort to cancel multiple cherry-pick' '
  120 + pristine_detach initial &&
  121 + test_must_fail git cherry-pick base..anotherpick &&
  122 + git cherry-pick --abort &&
  123 + test_path_is_missing .git/sequencer &&
  124 + test_cmp_rev initial HEAD &&
  125 + git update-index --refresh &&
  126 + git diff-index --exit-code HEAD
  127 +'
  128 +
  129 +test_expect_success '--abort to cancel single cherry-pick' '
  130 + pristine_detach initial &&
  131 + test_must_fail git cherry-pick picked &&
  132 + git cherry-pick --abort &&
  133 + test_path_is_missing .git/sequencer &&
  134 + test_cmp_rev initial HEAD &&
  135 + git update-index --refresh &&
  136 + git diff-index --exit-code HEAD
  137 +'
  138 +
  139 +test_expect_success 'cherry-pick --abort to cancel multiple revert' '
  140 + pristine_detach anotherpick &&
  141 + test_must_fail git revert base..picked &&
  142 + git cherry-pick --abort &&
  143 + test_path_is_missing .git/sequencer &&
  144 + test_cmp_rev anotherpick HEAD &&
  145 + git update-index --refresh &&
  146 + git diff-index --exit-code HEAD
  147 +'
  148 +
  149 +test_expect_success 'revert --abort works, too' '
  150 + pristine_detach anotherpick &&
  151 + test_must_fail git revert base..picked &&
  152 + git revert --abort &&
  153 + test_path_is_missing .git/sequencer &&
  154 + test_cmp_rev anotherpick HEAD
  155 +'
  156 +
  157 +test_expect_success '--abort to cancel single revert' '
  158 + pristine_detach anotherpick &&
  159 + test_must_fail git revert picked &&
  160 + git revert --abort &&
  161 + test_path_is_missing .git/sequencer &&
  162 + test_cmp_rev anotherpick HEAD &&
  163 + git update-index --refresh &&
  164 + git diff-index --exit-code HEAD
  165 +'
  166 +
  167 +test_expect_success '--abort keeps unrelated change, easy case' '
  168 + pristine_detach unrelatedpick &&
  169 + echo changed >expect &&
  170 + test_must_fail git cherry-pick picked..yetanotherpick &&
  171 + echo changed >unrelated &&
  172 + git cherry-pick --abort &&
  173 + test_cmp expect unrelated
  174 +'
  175 +
  176 +test_expect_success '--abort refuses to clobber unrelated change, harder case' '
  177 + pristine_detach initial &&
  178 + echo changed >expect &&
  179 + test_must_fail git cherry-pick base..anotherpick &&
  180 + echo changed >unrelated &&
  181 + test_must_fail git cherry-pick --abort &&
  182 + test_cmp expect unrelated &&
  183 + git rev-list HEAD >log &&
  184 + test_line_count = 2 log &&
  185 + test_must_fail git update-index --refresh &&
  186 +
  187 + git checkout unrelated &&
  188 + git cherry-pick --abort &&
  189 + test_cmp_rev initial HEAD
  190 +'
  191 +
106 192 test_expect_success 'cherry-pick cleans up sequencer state when one commit is left' '
107 193 pristine_detach initial &&
108 194 test_must_fail git cherry-pick base..picked &&
@@ -127,6 +213,16 @@ test_expect_success 'cherry-pick cleans up sequencer state when one commit is le
127 213 test_cmp expect actual
128 214 '
129 215
  216 +test_expect_failure '--abort after last commit in sequence' '
  217 + pristine_detach initial &&
  218 + test_must_fail git cherry-pick base..picked &&
  219 + git cherry-pick --abort &&
  220 + test_path_is_missing .git/sequencer &&
  221 + test_cmp_rev initial HEAD &&
  222 + git update-index --refresh &&
  223 + git diff-index --exit-code HEAD
  224 +'
  225 +
130 226 test_expect_success 'cherry-pick does not implicitly stomp an existing operation' '
131 227 pristine_detach initial &&
132 228 test_must_fail git cherry-pick base..anotherpick &&

0 comments on commit 539047c

Please sign in to comment.
Something went wrong with that request. Please try again.