Skip to content

Commit be9a592

Browse files
committed
cherry-pick: refuse cherry-pick sequence if index is dirty
Cherry-pick, like merge or rebase, refuses to run when there are changes in the index. However, if a cherry-pick sequence is requested, this refusal happens "too late": when the cherry-pick sequence has already started, and an "--abort" or "--quit" is needed to resume normal operation. Normally, when an operation is "in-progress" and you want to go back to where you were before, "--abort" is the right thing to run. If you run "git cherry-pick --abort" in this specific situation, however, your staged changes are destroyed as part of the abort! Generally speaking, the abort process assumes any changes in the index are part of the operation to be aborted. Add an earlier check in the cherry-pick sequence process to ensure that the index is clean, introducing a new general "quit if index dirty" function derived from the existing worktree-level function used in rebase and pull. Also add a test. Signed-off-by: Tao Klerks <[email protected]>
1 parent 4a714b3 commit be9a592

File tree

4 files changed

+94
-35
lines changed

4 files changed

+94
-35
lines changed

sequencer.c

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3162,38 +3162,48 @@ static int walk_revs_populate_todo(struct todo_list *todo_list,
31623162
return 0;
31633163
}
31643164

3165-
static int create_seq_dir(struct repository *r)
3165+
static const char *cherry_pick_action_name(enum replay_action action) {
3166+
switch (action) {
3167+
case REPLAY_REVERT:
3168+
return "revert";
3169+
break;
3170+
case REPLAY_PICK:
3171+
return "cherry-pick";
3172+
break;
3173+
default:
3174+
BUG("unexpected action in cherry_pick_action_name");
3175+
}
3176+
}
3177+
3178+
static int create_seq_dir(struct repository *r, enum replay_action requested_action)
31663179
{
3167-
enum replay_action action;
3180+
enum replay_action in_progress_action;
3181+
const char *in_progress_action_name = NULL;
31683182
const char *in_progress_error = NULL;
31693183
const char *in_progress_advice = NULL;
3184+
const char *requested_action_name = NULL;
31703185
unsigned int advise_skip =
31713186
refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD") ||
31723187
refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD");
31733188

3174-
if (!sequencer_get_last_command(r, &action)) {
3175-
switch (action) {
3176-
case REPLAY_REVERT:
3177-
in_progress_error = _("revert is already in progress");
3178-
in_progress_advice =
3179-
_("try \"git revert (--continue | %s--abort | --quit)\"");
3180-
break;
3181-
case REPLAY_PICK:
3182-
in_progress_error = _("cherry-pick is already in progress");
3183-
in_progress_advice =
3184-
_("try \"git cherry-pick (--continue | %s--abort | --quit)\"");
3185-
break;
3186-
default:
3187-
BUG("unexpected action in create_seq_dir");
3188-
}
3189+
if (!sequencer_get_last_command(r, &in_progress_action)) {
3190+
in_progress_action_name = cherry_pick_action_name(in_progress_action);
3191+
in_progress_error = _("%s is already in progress");
3192+
in_progress_advice =
3193+
_("try \"git %s (--continue | %s--abort | --quit)\"");
31893194
}
31903195
if (in_progress_error) {
3191-
error("%s", in_progress_error);
3196+
error(in_progress_error, in_progress_action_name);
31923197
if (advice_enabled(ADVICE_SEQUENCER_IN_USE))
31933198
advise(in_progress_advice,
3199+
in_progress_action_name,
31943200
advise_skip ? "--skip | " : "");
31953201
return -1;
31963202
}
3203+
requested_action_name = cherry_pick_action_name(requested_action);
3204+
if (require_clean_index(r, requested_action_name,
3205+
_("Please commit or stash them."), 1, 1))
3206+
return -1;
31973207
if (mkdir(git_path_seq_dir(), 0777) < 0)
31983208
return error_errno(_("could not create sequencer directory '%s'"),
31993209
git_path_seq_dir());
@@ -5223,12 +5233,11 @@ int sequencer_pick_revisions(struct repository *r,
52235233

52245234
/*
52255235
* Start a new cherry-pick/ revert sequence; but
5226-
* first, make sure that an existing one isn't in
5227-
* progress
5236+
* first, make sure that the index is clean and that
5237+
* an existing one isn't in progress.
52285238
*/
5229-
52305239
if (walk_revs_populate_todo(&todo_list, opts) ||
5231-
create_seq_dir(r) < 0)
5240+
create_seq_dir(r, opts->action) < 0)
52325241
return -1;
52335242
if (repo_get_oid(r, "HEAD", &oid) && (opts->action == REPLAY_REVERT))
52345243
return error(_("can't revert as initial commit"));

t/t3510-cherry-pick-sequence.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ test_expect_success 'cherry-pick persists data on failure' '
4747
test_path_is_file .git/sequencer/opts
4848
'
4949

50+
test_expect_success 'cherry-pick sequence refuses to run on dirty index' '
51+
pristine_detach initial &&
52+
touch localindexchange &&
53+
git add localindexchange &&
54+
echo picking &&
55+
test_must_fail git cherry-pick initial..picked &&
56+
test_path_is_missing .git/sequencer &&
57+
test_must_fail git cherry-pick --abort
58+
'
59+
5060
test_expect_success 'cherry-pick mid-cherry-pick-sequence' '
5161
pristine_detach initial &&
5262
test_must_fail git cherry-pick base..anotherpick &&

wt-status.c

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2616,15 +2616,12 @@ int has_uncommitted_changes(struct repository *r,
26162616
return result;
26172617
}
26182618

2619-
/**
2620-
* If the work tree has unstaged or uncommitted changes, dies with the
2621-
* appropriate message.
2622-
*/
2623-
int require_clean_work_tree(struct repository *r,
2624-
const char *action,
2625-
const char *hint,
2626-
int ignore_submodules,
2627-
int gently)
2619+
static int require_clean_index_or_work_tree(struct repository *r,
2620+
const char *action,
2621+
const char *hint,
2622+
int ignore_submodules,
2623+
int check_index_only,
2624+
int gently)
26282625
{
26292626
struct lock_file lock_file = LOCK_INIT;
26302627
int err = 0, fd;
@@ -2635,10 +2632,12 @@ int require_clean_work_tree(struct repository *r,
26352632
repo_update_index_if_able(r, &lock_file);
26362633
rollback_lock_file(&lock_file);
26372634

2638-
if (has_unstaged_changes(r, ignore_submodules)) {
2639-
/* TRANSLATORS: the action is e.g. "pull with rebase" */
2640-
error(_("cannot %s: You have unstaged changes."), _(action));
2641-
err = 1;
2635+
if (!check_index_only) {
2636+
if (has_unstaged_changes(r, ignore_submodules)) {
2637+
/* TRANSLATORS: the action is e.g. "pull with rebase" */
2638+
error(_("cannot %s: You have unstaged changes."), _(action));
2639+
err = 1;
2640+
}
26422641
}
26432642

26442643
if (has_uncommitted_changes(r, ignore_submodules)) {
@@ -2659,3 +2658,39 @@ int require_clean_work_tree(struct repository *r,
26592658

26602659
return err;
26612660
}
2661+
2662+
/**
2663+
* If the work tree has unstaged or uncommitted changes, dies with the
2664+
* appropriate message.
2665+
*/
2666+
int require_clean_work_tree(struct repository *r,
2667+
const char *action,
2668+
const char *hint,
2669+
int ignore_submodules,
2670+
int gently)
2671+
{
2672+
return require_clean_index_or_work_tree(r,
2673+
action,
2674+
hint,
2675+
ignore_submodules,
2676+
0,
2677+
gently);
2678+
}
2679+
2680+
/**
2681+
* If the work tree has uncommitted changes, dies with the appropriate
2682+
* message.
2683+
*/
2684+
int require_clean_index(struct repository *r,
2685+
const char *action,
2686+
const char *hint,
2687+
int ignore_submodules,
2688+
int gently)
2689+
{
2690+
return require_clean_index_or_work_tree(r,
2691+
action,
2692+
hint,
2693+
ignore_submodules,
2694+
1,
2695+
gently);
2696+
}

wt-status.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,5 +181,10 @@ int require_clean_work_tree(struct repository *repo,
181181
const char *hint,
182182
int ignore_submodules,
183183
int gently);
184+
int require_clean_index(struct repository *repo,
185+
const char *action,
186+
const char *hint,
187+
int ignore_submodules,
188+
int gently);
184189

185190
#endif /* STATUS_H */

0 commit comments

Comments
 (0)