diff --git a/builtin/checkout.c b/builtin/checkout.c index 55e3a89a85..09b3c87e2a 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -838,10 +838,11 @@ static void init_topts(struct unpack_trees_options *topts, static int merge_working_tree(const struct checkout_opts *opts, struct branch_info *old_branch_info, struct branch_info *new_branch_info, - bool quiet, + bool allow_autostash, int *writeout_error) { int ret; + bool can_autostash = false; struct lock_file lock_file = LOCK_INIT; struct tree *new_tree; @@ -888,9 +889,13 @@ static int merge_working_tree(const struct checkout_opts *opts, return 1; } + if (allow_autostash) + can_autostash = has_unstaged_changes(the_repository, 1) || + has_uncommitted_changes(the_repository, 1); + /* 2-way merge to the new branch */ init_topts(&topts, opts->show_progress, - opts->overwrite_ignore, quiet); + opts->overwrite_ignore, can_autostash); init_checkout_metadata(&topts.meta, new_branch_info->refname, new_branch_info->commit ? &new_branch_info->commit->object.oid : @@ -917,7 +922,8 @@ static int merge_working_tree(const struct checkout_opts *opts, clear_unpack_trees_porcelain(&topts); if (ret == -1) { rollback_lock_file(&lock_file); - return MERGE_WORKING_TREE_UNPACK_FAILED; + return can_autostash ? + MERGE_WORKING_TREE_UNPACK_FAILED : 1; } } @@ -1166,6 +1172,7 @@ static int switch_branches(const struct checkout_opts *opts, int flag, writeout_error = 0; int do_merge = 1; int created_autostash = 0; + bool autostash_conflicted = false; struct strbuf old_commit_shortname = STRBUF_INIT; struct strbuf autostash_msg = STRBUF_INIT; const char *stash_label_base = NULL; @@ -1242,7 +1249,8 @@ static int switch_branches(const struct checkout_opts *opts, new_branch_info->name, "local", stash_label_base, - autostash_msg.buf); + autostash_msg.buf, + &autostash_conflicted); } if (ret) { branch_info_release(&old_branch_info); @@ -1255,6 +1263,8 @@ static int switch_branches(const struct checkout_opts *opts, if (!opts->quiet && !old_branch_info.path && old_branch_info.commit && new_branch_info->commit != old_branch_info.commit) orphaned_commit_warning(old_branch_info.commit, new_branch_info->commit); + if (autostash_conflicted && !opts->quiet) + fputc('\n', stderr); update_refs_for_switch(opts, &old_branch_info, new_branch_info); if (created_autostash) { diff --git a/builtin/commit.c b/builtin/commit.c index 28f6174503..d678a81865 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1980,7 +1980,7 @@ int cmd_commit(int argc, } apply_autostash_ref(the_repository, "MERGE_AUTOSTASH", - NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL); cleanup: free_commit_extra_headers(extra); diff --git a/builtin/merge.c b/builtin/merge.c index 5b4eb23a83..e21b0d8ffb 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -538,7 +538,7 @@ static void finish(struct commit *head_commit, if (new_head) apply_autostash_ref(the_repository, "MERGE_AUTOSTASH", - NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL); strbuf_release(&reflog_message); } @@ -1682,7 +1682,7 @@ int cmd_merge(int argc, &commit->object.oid, overwrite_ignore)) { apply_autostash_ref(the_repository, "MERGE_AUTOSTASH", - NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL); ret = 1; goto done; } @@ -1846,7 +1846,7 @@ int cmd_merge(int argc, fprintf(stderr, _("Merge with strategy %s failed.\n"), use_strategies[0]->name); apply_autostash_ref(the_repository, "MERGE_AUTOSTASH", - NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL); ret = 2; goto done; } else if (best_strategy == wt_strategy) diff --git a/sequencer.c b/sequencer.c index 83c3849205..002e1c95bd 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4796,7 +4796,8 @@ void create_autostash_ref(struct repository *r, const char *refname, static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply, const char *label_ours, const char *label_theirs, const char *label_base, - const char *stash_msg) + const char *stash_msg, + bool *conflicted) { struct child_process child = CHILD_PROCESS_INIT; int ret = 0; @@ -4831,14 +4832,16 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply, strvec_push(&store.args, stash_oid); if (run_command(&store)) ret = error(_("cannot store %s"), stash_oid); - else if (attempt_apply) + else if (attempt_apply) { + if (conflicted) + *conflicted = true; fprintf(stderr, _("Your local changes are stashed, however applying them\n" "resulted in conflicts. You can either resolve the conflicts\n" "and then discard the stash with \"git stash drop\", or, if you\n" "do not want to resolve them now, run \"git reset --hard\" and\n" "apply the local changes later by running \"git stash pop\".\n")); - else + } else fprintf(stderr, _("Autostash exists; creating a new stash entry.\n" "Your changes are safe in the stash.\n" @@ -4862,7 +4865,7 @@ static int apply_save_autostash(const char *path, int attempt_apply) strbuf_trim(&stash_oid); ret = apply_save_autostash_oid(stash_oid.buf, attempt_apply, - NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL); unlink(path); strbuf_release(&stash_oid); @@ -4881,19 +4884,24 @@ int apply_autostash(const char *path) int apply_autostash_oid(const char *stash_oid) { - return apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL); + return apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL, + NULL); } static int apply_save_autostash_ref(struct repository *r, const char *refname, int attempt_apply, const char *label_ours, const char *label_theirs, const char *label_base, - const char *stash_msg) + const char *stash_msg, + bool *conflicted) { struct object_id stash_oid; char stash_oid_hex[GIT_MAX_HEXSZ + 1]; int flag, ret; + if (conflicted) + *conflicted = false; + if (!refs_ref_exists(get_main_ref_store(r), refname)) return 0; @@ -4906,7 +4914,7 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname, oid_to_hex_r(stash_oid_hex, &stash_oid); ret = apply_save_autostash_oid(stash_oid_hex, attempt_apply, label_ours, label_theirs, label_base, - stash_msg); + stash_msg, conflicted); refs_delete_ref(get_main_ref_store(r), "", refname, &stash_oid, REF_NO_DEREF); @@ -4917,16 +4925,17 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname, int save_autostash_ref(struct repository *r, const char *refname) { return apply_save_autostash_ref(r, refname, 0, - NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL); } int apply_autostash_ref(struct repository *r, const char *refname, const char *label_ours, const char *label_theirs, - const char *label_base, const char *stash_msg) + const char *label_base, const char *stash_msg, + bool *conflicted) { return apply_save_autostash_ref(r, refname, 1, label_ours, label_theirs, label_base, - stash_msg); + stash_msg, conflicted); } static int checkout_onto(struct repository *r, struct replay_opts *opts, diff --git a/sequencer.h b/sequencer.h index 64a9c7fb1b..b39528b6d0 100644 --- a/sequencer.h +++ b/sequencer.h @@ -237,7 +237,8 @@ int apply_autostash(const char *path); int apply_autostash_oid(const char *stash_oid); int apply_autostash_ref(struct repository *r, const char *refname, const char *label_ours, const char *label_theirs, - const char *label_base, const char *stash_msg); + const char *label_base, const char *stash_msg, + bool *conflicted); #define SUMMARY_INITIAL_COMMIT (1 << 0) #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1) diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 0ddd1ad7aa..f9696dab36 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -240,6 +240,14 @@ test_expect_success 'checkout -m creates a recoverable stash on conflict' ' test_grep "git stash drop" actual && test_grep "git stash pop" actual && test_grep "The following paths have local changes" actual && + sed -n "/apply the local changes later/,/Switched to branch/p" \ + actual >separator.actual && + cat >separator.expect <<-EOF && + apply the local changes later by running "git stash pop". + + Switched to branch ${SQ}side${SQ} + EOF + test_cmp separator.expect separator.actual && git log -p -1 --format="%gs%n%B" -g --diff-merges=1 refs/stash >actual && sed /^index/d actual >actual.trimmed && cat >expect <<-EOF && @@ -262,11 +270,18 @@ test_expect_success 'checkout -m creates a recoverable stash on conflict' ' git reset --hard ' -test_expect_success 'checkout -m which would overwrite untracked file' ' +test_expect_success 'checkout -m only retries untracked-file failure with local changes' ' git checkout -f --detach main && test_commit another-file && git checkout HEAD^ && >another-file.t && + test_must_fail env GIT_TRACE2_EVENT="$(pwd)/trace" \ + git checkout -m @{-1} 2>err && + test_grep "untracked working tree files" err && + grep "\"region_enter\".*\"category\":\"index\",\"label\":\"refresh\"" \ + trace >refresh.events && + test_line_count = 1 refresh.events && + fill 1 2 3 4 5 >one && test_must_fail git checkout -m @{-1} 2>err && q_to_tab >expect <<-\EOF &&