Skip to content

Commit de81ca3

Browse files
r1walzgitster
authored andcommitted
cherry-pick/revert: add --skip option
git am or rebase have a --skip flag to skip the current commit if the user wishes to do so. During a cherry-pick or revert a user could likewise skip a commit, but needs to use 'git reset' (or in the case of conflicts 'git reset --merge'), followed by 'git (cherry-pick | revert) --continue' to skip the commit. This is more annoying and sometimes confusing on the users' part. Add a `--skip` option to make skipping commits easier for the user and to make the commands more consistent. In the next commit, we will change the advice messages hence finishing the process of teaching revert and cherry-pick "how to skip commits". Signed-off-by: Rohit Ashiwal <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 265ab48 commit de81ca3

File tree

7 files changed

+187
-6
lines changed

7 files changed

+187
-6
lines changed

Documentation/git-cherry-pick.txt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ SYNOPSIS
1010
[verse]
1111
'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff]
1212
[-S[<keyid>]] <commit>...
13-
'git cherry-pick' --continue
14-
'git cherry-pick' --quit
15-
'git cherry-pick' --abort
13+
'git cherry-pick' (--continue | --skip | --abort | --quit)
1614

1715
DESCRIPTION
1816
-----------

Documentation/git-revert.txt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ SYNOPSIS
99
--------
1010
[verse]
1111
'git revert' [--[no-]edit] [-n] [-m parent-number] [-s] [-S[<keyid>]] <commit>...
12-
'git revert' --continue
13-
'git revert' --quit
14-
'git revert' --abort
12+
'git revert' (--continue | --skip | --abort | --quit)
1513

1614
DESCRIPTION
1715
-----------

Documentation/sequencer.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
`.git/sequencer`. Can be used to continue after resolving
44
conflicts in a failed cherry-pick or revert.
55

6+
--skip::
7+
Skip the current commit and continue with the rest of the
8+
sequence.
9+
610
--quit::
711
Forget about the current operation in progress. Can be used
812
to clear the sequencer state after a failed cherry-pick or

builtin/revert.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
102102
OPT_CMDMODE(0, "quit", &cmd, N_("end revert or cherry-pick sequence"), 'q'),
103103
OPT_CMDMODE(0, "continue", &cmd, N_("resume revert or cherry-pick sequence"), 'c'),
104104
OPT_CMDMODE(0, "abort", &cmd, N_("cancel revert or cherry-pick sequence"), 'a'),
105+
OPT_CMDMODE(0, "skip", &cmd, N_("skip current commit and continue"), 's'),
105106
OPT_CLEANUP(&cleanup_arg),
106107
OPT_BOOL('n', "no-commit", &opts->no_commit, N_("don't automatically commit")),
107108
OPT_BOOL('e', "edit", &opts->edit, N_("edit the commit message")),
@@ -151,6 +152,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
151152
this_operation = "--quit";
152153
else if (cmd == 'c')
153154
this_operation = "--continue";
155+
else if (cmd == 's')
156+
this_operation = "--skip";
154157
else {
155158
assert(cmd == 'a');
156159
this_operation = "--abort";
@@ -210,6 +213,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
210213
return sequencer_continue(the_repository, opts);
211214
if (cmd == 'a')
212215
return sequencer_rollback(the_repository, opts);
216+
if (cmd == 's')
217+
return sequencer_skip(the_repository, opts);
213218
return sequencer_pick_revisions(the_repository, opts);
214219
}
215220

sequencer.c

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2762,6 +2762,15 @@ static int rollback_single_pick(struct repository *r)
27622762
return reset_merge(&head_oid);
27632763
}
27642764

2765+
static int skip_single_pick(void)
2766+
{
2767+
struct object_id head;
2768+
2769+
if (read_ref_full("HEAD", 0, &head, NULL))
2770+
return error(_("cannot resolve HEAD"));
2771+
return reset_merge(&head);
2772+
}
2773+
27652774
int sequencer_rollback(struct repository *r, struct replay_opts *opts)
27662775
{
27672776
FILE *f;
@@ -2811,6 +2820,70 @@ int sequencer_rollback(struct repository *r, struct replay_opts *opts)
28112820
return -1;
28122821
}
28132822

2823+
int sequencer_skip(struct repository *r, struct replay_opts *opts)
2824+
{
2825+
enum replay_action action = -1;
2826+
sequencer_get_last_command(r, &action);
2827+
2828+
/*
2829+
* Check whether the subcommand requested to skip the commit is actually
2830+
* in progress and that it's safe to skip the commit.
2831+
*
2832+
* opts->action tells us which subcommand requested to skip the commit.
2833+
* If the corresponding .git/<ACTION>_HEAD exists, we know that the
2834+
* action is in progress and we can skip the commit.
2835+
*
2836+
* Otherwise we check that the last instruction was related to the
2837+
* particular subcommand we're trying to execute and barf if that's not
2838+
* the case.
2839+
*
2840+
* Finally we check that the rollback is "safe", i.e., has the HEAD
2841+
* moved? In this case, it doesn't make sense to "reset the merge" and
2842+
* "skip the commit" as the user already handled this by committing. But
2843+
* we'd not want to barf here, instead give advice on how to proceed. We
2844+
* only need to check that when .git/<ACTION>_HEAD doesn't exist because
2845+
* it gets removed when the user commits, so if it still exists we're
2846+
* sure the user can't have committed before.
2847+
*/
2848+
switch (opts->action) {
2849+
case REPLAY_REVERT:
2850+
if (!file_exists(git_path_revert_head(r))) {
2851+
if (action != REPLAY_REVERT)
2852+
return error(_("no revert in progress"));
2853+
if (!rollback_is_safe())
2854+
goto give_advice;
2855+
}
2856+
break;
2857+
case REPLAY_PICK:
2858+
if (!file_exists(git_path_cherry_pick_head(r))) {
2859+
if (action != REPLAY_PICK)
2860+
return error(_("no cherry-pick in progress"));
2861+
if (!rollback_is_safe())
2862+
goto give_advice;
2863+
}
2864+
break;
2865+
default:
2866+
BUG("unexpected action in sequencer_skip");
2867+
}
2868+
2869+
if (skip_single_pick())
2870+
return error(_("failed to skip the commit"));
2871+
if (!is_directory(git_path_seq_dir()))
2872+
return 0;
2873+
2874+
return sequencer_continue(r, opts);
2875+
2876+
give_advice:
2877+
error(_("there is nothing to skip"));
2878+
2879+
if (advice_resolve_conflict) {
2880+
advise(_("have you committed already?\n"
2881+
"try \"git %s --continue\""),
2882+
action == REPLAY_REVERT ? "revert" : "cherry-pick");
2883+
}
2884+
return -1;
2885+
}
2886+
28142887
static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
28152888
{
28162889
struct lock_file todo_lock = LOCK_INIT;

sequencer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ int sequencer_pick_revisions(struct repository *repo,
129129
struct replay_opts *opts);
130130
int sequencer_continue(struct repository *repo, struct replay_opts *opts);
131131
int sequencer_rollback(struct repository *repo, struct replay_opts *opts);
132+
int sequencer_skip(struct repository *repo, struct replay_opts *opts);
132133
int sequencer_remove_state(struct replay_opts *opts);
133134

134135
#define TODO_LIST_KEEP_EMPTY (1U << 0)

t/t3510-cherry-pick-sequence.sh

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,108 @@ test_expect_success 'cherry-pick cleans up sequencer state upon success' '
9393
test_path_is_missing .git/sequencer
9494
'
9595

96+
test_expect_success 'cherry-pick --skip requires cherry-pick in progress' '
97+
pristine_detach initial &&
98+
test_must_fail git cherry-pick --skip
99+
'
100+
101+
test_expect_success 'revert --skip requires revert in progress' '
102+
pristine_detach initial &&
103+
test_must_fail git revert --skip
104+
'
105+
106+
test_expect_success 'cherry-pick --skip to skip commit' '
107+
pristine_detach initial &&
108+
test_must_fail git cherry-pick anotherpick &&
109+
test_must_fail git revert --skip &&
110+
git cherry-pick --skip &&
111+
test_cmp_rev initial HEAD &&
112+
test_path_is_missing .git/CHERRY_PICK_HEAD
113+
'
114+
115+
test_expect_success 'revert --skip to skip commit' '
116+
pristine_detach anotherpick &&
117+
test_must_fail git revert anotherpick~1 &&
118+
test_must_fail git cherry-pick --skip &&
119+
git revert --skip &&
120+
test_cmp_rev anotherpick HEAD
121+
'
122+
123+
test_expect_success 'skip "empty" commit' '
124+
pristine_detach picked &&
125+
test_commit dummy foo d &&
126+
test_must_fail git cherry-pick anotherpick &&
127+
git cherry-pick --skip &&
128+
test_cmp_rev dummy HEAD
129+
'
130+
131+
test_expect_success 'skip a commit and check if rest of sequence is correct' '
132+
pristine_detach initial &&
133+
echo e >expect &&
134+
cat >expect.log <<-EOF &&
135+
OBJID
136+
:100644 100644 OBJID OBJID M foo
137+
OBJID
138+
:100644 100644 OBJID OBJID M foo
139+
OBJID
140+
:100644 100644 OBJID OBJID M unrelated
141+
OBJID
142+
:000000 100644 OBJID OBJID A foo
143+
:000000 100644 OBJID OBJID A unrelated
144+
EOF
145+
test_must_fail git cherry-pick base..yetanotherpick &&
146+
test_must_fail git cherry-pick --skip &&
147+
echo d >foo &&
148+
git add foo &&
149+
git cherry-pick --continue &&
150+
{
151+
git rev-list HEAD |
152+
git diff-tree --root --stdin |
153+
sed "s/$OID_REGEX/OBJID/g"
154+
} >actual.log &&
155+
test_cmp expect foo &&
156+
test_cmp expect.log actual.log
157+
'
158+
159+
test_expect_success 'check advice when we move HEAD by committing' '
160+
pristine_detach initial &&
161+
cat >expect <<-EOF &&
162+
error: there is nothing to skip
163+
hint: have you committed already?
164+
hint: try "git cherry-pick --continue"
165+
fatal: cherry-pick failed
166+
EOF
167+
test_must_fail git cherry-pick base..yetanotherpick &&
168+
echo c >foo &&
169+
git commit -a &&
170+
test_path_is_missing .git/CHERRY_PICK_HEAD &&
171+
test_must_fail git cherry-pick --skip 2>advice &&
172+
test_i18ncmp expect advice
173+
'
174+
175+
test_expect_success 'allow skipping commit but not abort for a new history' '
176+
pristine_detach initial &&
177+
cat >expect <<-EOF &&
178+
error: cannot abort from a branch yet to be born
179+
fatal: cherry-pick failed
180+
EOF
181+
git checkout --orphan new_disconnected &&
182+
git reset --hard &&
183+
test_must_fail git cherry-pick anotherpick &&
184+
test_must_fail git cherry-pick --abort 2>advice &&
185+
git cherry-pick --skip &&
186+
test_i18ncmp expect advice
187+
'
188+
189+
test_expect_success 'allow skipping stopped cherry-pick because of untracked file modifications' '
190+
pristine_detach initial &&
191+
git rm --cached unrelated &&
192+
git commit -m "untrack unrelated" &&
193+
test_must_fail git cherry-pick initial base &&
194+
test_path_is_missing .git/CHERRY_PICK_HEAD &&
195+
git cherry-pick --skip
196+
'
197+
96198
test_expect_success '--quit does not complain when no cherry-pick is in progress' '
97199
pristine_detach initial &&
98200
git cherry-pick --quit

0 commit comments

Comments
 (0)