Merge branch 'hn/checkout-m-autostash-refine' into jch
The autostash fallback in 'git checkout -m' has been refined to only retry when there are local changes. Additionally, a blank line now visually separates autostash conflict advice from the subsequent branch-switch message. * hn/checkout-m-autostash-refine: checkout: separate autostash conflict advice from branch-switch message stash: reserve exit status 1 for conflicts
commit
89009a6f16
|
|
@ -426,6 +426,15 @@ include::includes/cmd-config-section-all.adoc[]
|
|||
:git-stash: 1
|
||||
include::config/stash.adoc[]
|
||||
|
||||
EXIT STATUS
|
||||
-----------
|
||||
|
||||
The `git stash` subcommands exit with status 0 on success. The
|
||||
subcommands that apply a stash entry, i.e. `apply`, `pop` and `branch`,
|
||||
exit with status 1 when applying the stash entry resulted in conflicts,
|
||||
in which case the stash entry is left in place, and with a non-zero
|
||||
status other than 1 when they fail for other reasons.
|
||||
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
|
|
|
|||
|
|
@ -1166,6 +1166,7 @@ static int switch_branches(const struct checkout_opts *opts,
|
|||
int flag, writeout_error = 0;
|
||||
int do_merge = 1;
|
||||
int created_autostash = 0;
|
||||
enum stash_apply_result autostash_res = STASH_APPLY_CLEAN;
|
||||
struct strbuf old_commit_shortname = STRBUF_INIT;
|
||||
struct strbuf autostash_msg = STRBUF_INIT;
|
||||
const char *stash_label_base = NULL;
|
||||
|
|
@ -1237,12 +1238,12 @@ static int switch_branches(const struct checkout_opts *opts,
|
|||
git_config_push_parameter(cfg.buf);
|
||||
strbuf_release(&cfg);
|
||||
}
|
||||
apply_autostash_ref(the_repository,
|
||||
"CHECKOUT_AUTOSTASH_HEAD",
|
||||
new_branch_info->name,
|
||||
"local",
|
||||
stash_label_base,
|
||||
autostash_msg.buf);
|
||||
autostash_res = apply_autostash_ref(the_repository,
|
||||
"CHECKOUT_AUTOSTASH_HEAD",
|
||||
new_branch_info->name,
|
||||
"local",
|
||||
stash_label_base,
|
||||
autostash_msg.buf);
|
||||
}
|
||||
if (ret) {
|
||||
branch_info_release(&old_branch_info);
|
||||
|
|
@ -1255,6 +1256,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_res == STASH_APPLY_CONFLICT && !opts->quiet)
|
||||
fputc('\n', stderr);
|
||||
update_refs_for_switch(opts, &old_branch_info, new_branch_info);
|
||||
|
||||
if (created_autostash) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "object-name.h"
|
||||
#include "parse-options.h"
|
||||
#include "refs.h"
|
||||
#include "stash.h"
|
||||
#include "lockfile.h"
|
||||
#include "cache-tree.h"
|
||||
#include "unpack-trees.h"
|
||||
|
|
@ -640,10 +641,12 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)
|
|||
die(_("could not write index"));
|
||||
}
|
||||
|
||||
static int do_apply_stash(const char *prefix, struct stash_info *info,
|
||||
int index, int quiet,
|
||||
const char *label_ours, const char *label_theirs,
|
||||
const char *label_base)
|
||||
static enum stash_apply_result do_apply_stash(const char *prefix,
|
||||
struct stash_info *info,
|
||||
int index, int quiet,
|
||||
const char *label_ours,
|
||||
const char *label_theirs,
|
||||
const char *label_base)
|
||||
{
|
||||
int clean, ret;
|
||||
int has_index = index;
|
||||
|
|
@ -717,8 +720,8 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
|
|||
|
||||
/*
|
||||
* If 'clean' >= 0, reverse the value for 'ret' so 'ret' is 0 when the
|
||||
* merge was clean, and nonzero if the merge was unclean or encountered
|
||||
* an error.
|
||||
* merge was clean, and 1 if the merge was unclean or a negative value
|
||||
* if it encountered an error.
|
||||
*/
|
||||
ret = clean >= 0 ? !clean : clean;
|
||||
|
||||
|
|
@ -2492,9 +2495,20 @@ int cmd_stash(int argc,
|
|||
strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file,
|
||||
(uintmax_t)pid);
|
||||
|
||||
if (fn)
|
||||
return !!fn(argc, argv, prefix, repo);
|
||||
else if (!argc)
|
||||
if (fn) {
|
||||
ret = fn(argc, argv, prefix, repo);
|
||||
|
||||
/*
|
||||
* The subcommand implementations return 0 on success, a
|
||||
* negative value on failure, and STASH_APPLY_CONFLICT
|
||||
* when applying a stash entry resulted in conflicts.
|
||||
* Map failures to 128, the status die() uses, so that
|
||||
* exit status 1 unambiguously indicates conflicts.
|
||||
*/
|
||||
if (ret < 0)
|
||||
return 128;
|
||||
return ret;
|
||||
} else if (!argc)
|
||||
return !!push_stash_unassumed(0, NULL, prefix, repo);
|
||||
|
||||
/* Assume 'stash push' */
|
||||
|
|
|
|||
66
sequencer.c
66
sequencer.c
|
|
@ -19,6 +19,7 @@
|
|||
#include "commit.h"
|
||||
#include "sequencer.h"
|
||||
#include "run-command.h"
|
||||
#include "stash.h"
|
||||
#include "hook.h"
|
||||
#include "utf8.h"
|
||||
#include "cache-tree.h"
|
||||
|
|
@ -4794,13 +4795,15 @@ void create_autostash_ref(struct repository *r, const char *refname,
|
|||
create_autostash_internal(r, NULL, refname, message, silent);
|
||||
}
|
||||
|
||||
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)
|
||||
static enum stash_apply_result 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)
|
||||
{
|
||||
struct child_process child = CHILD_PROCESS_INIT;
|
||||
int ret = 0;
|
||||
enum stash_apply_result ret = STASH_APPLY_CLEAN;
|
||||
|
||||
if (attempt_apply) {
|
||||
child.git_cmd = 1;
|
||||
|
|
@ -4816,9 +4819,11 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,
|
|||
strvec_pushf(&child.args, "--label-base=%s", label_base);
|
||||
strvec_push(&child.args, stash_oid);
|
||||
ret = run_command(&child);
|
||||
if (ret > 1)
|
||||
ret = STASH_APPLY_ERROR;
|
||||
}
|
||||
|
||||
if (attempt_apply && !ret)
|
||||
if (attempt_apply && ret == STASH_APPLY_CLEAN)
|
||||
fprintf(stderr, _("Applied autostash.\n"));
|
||||
else {
|
||||
struct child_process store = CHILD_PROCESS_INIT;
|
||||
|
|
@ -4832,13 +4837,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 && ret == STASH_APPLY_CONFLICT)
|
||||
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 if (attempt_apply)
|
||||
ret = error(_("could not apply autostash; "
|
||||
"your changes are safe in the stash"));
|
||||
else
|
||||
fprintf(stderr,
|
||||
_("Autostash exists; creating a new stash entry.\n"
|
||||
|
|
@ -4850,15 +4858,16 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int apply_save_autostash(const char *path, int attempt_apply)
|
||||
static enum stash_apply_result apply_save_autostash(const char *path,
|
||||
int attempt_apply)
|
||||
{
|
||||
struct strbuf stash_oid = STRBUF_INIT;
|
||||
int ret = 0;
|
||||
enum stash_apply_result ret = STASH_APPLY_CLEAN;
|
||||
|
||||
if (!read_oneliner(&stash_oid, path,
|
||||
READ_ONELINER_SKIP_IF_EMPTY)) {
|
||||
strbuf_release(&stash_oid);
|
||||
return 0;
|
||||
return STASH_APPLY_CLEAN;
|
||||
}
|
||||
strbuf_trim(&stash_oid);
|
||||
|
||||
|
|
@ -4870,37 +4879,40 @@ static int apply_save_autostash(const char *path, int attempt_apply)
|
|||
return ret;
|
||||
}
|
||||
|
||||
int save_autostash(const char *path)
|
||||
enum stash_apply_result save_autostash(const char *path)
|
||||
{
|
||||
return apply_save_autostash(path, 0);
|
||||
}
|
||||
|
||||
int apply_autostash(const char *path)
|
||||
enum stash_apply_result apply_autostash(const char *path)
|
||||
{
|
||||
return apply_save_autostash(path, 1);
|
||||
}
|
||||
|
||||
int apply_autostash_oid(const char *stash_oid)
|
||||
enum stash_apply_result apply_autostash_oid(const char *stash_oid)
|
||||
{
|
||||
return apply_save_autostash_oid(stash_oid, 1, 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)
|
||||
static enum stash_apply_result 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)
|
||||
{
|
||||
struct object_id stash_oid;
|
||||
char stash_oid_hex[GIT_MAX_HEXSZ + 1];
|
||||
int flag, ret;
|
||||
int flag;
|
||||
enum stash_apply_result ret;
|
||||
|
||||
if (!refs_ref_exists(get_main_ref_store(r), refname))
|
||||
return 0;
|
||||
return STASH_APPLY_CLEAN;
|
||||
|
||||
if (!refs_resolve_ref_unsafe(get_main_ref_store(r), refname,
|
||||
RESOLVE_REF_READING, &stash_oid, &flag))
|
||||
return -1;
|
||||
return STASH_APPLY_ERROR;
|
||||
if (flag & REF_ISSYMREF)
|
||||
return error(_("autostash reference is a symref"));
|
||||
|
||||
|
|
@ -4915,15 +4927,19 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,
|
|||
return ret;
|
||||
}
|
||||
|
||||
int save_autostash_ref(struct repository *r, const char *refname)
|
||||
enum stash_apply_result save_autostash_ref(struct repository *r,
|
||||
const char *refname)
|
||||
{
|
||||
return apply_save_autostash_ref(r, refname, 0,
|
||||
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)
|
||||
enum stash_apply_result 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)
|
||||
{
|
||||
return apply_save_autostash_ref(r, refname, 1,
|
||||
label_ours, label_theirs, label_base,
|
||||
|
|
|
|||
19
sequencer.h
19
sequencer.h
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "strbuf.h"
|
||||
#include "strvec.h"
|
||||
#include "stash.h"
|
||||
#include "wt-status.h"
|
||||
|
||||
struct commit;
|
||||
|
|
@ -231,13 +232,17 @@ void commit_post_rewrite(struct repository *r,
|
|||
void create_autostash(struct repository *r, const char *path);
|
||||
void create_autostash_ref(struct repository *r, const char *refname,
|
||||
const char *message, bool silent);
|
||||
int save_autostash(const char *path);
|
||||
int save_autostash_ref(struct repository *r, const char *refname);
|
||||
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);
|
||||
enum stash_apply_result save_autostash(const char *path);
|
||||
enum stash_apply_result save_autostash_ref(struct repository *r,
|
||||
const char *refname);
|
||||
enum stash_apply_result apply_autostash(const char *path);
|
||||
enum stash_apply_result apply_autostash_oid(const char *stash_oid);
|
||||
enum stash_apply_result 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);
|
||||
|
||||
#define SUMMARY_INITIAL_COMMIT (1 << 0)
|
||||
#define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef STASH_H
|
||||
#define STASH_H
|
||||
|
||||
enum stash_apply_result {
|
||||
/* The stash was applied cleanly, or there was nothing to apply. */
|
||||
STASH_APPLY_CLEAN = 0,
|
||||
|
||||
/*
|
||||
* The stash could not be applied because it resulted in
|
||||
* conflicts. The stash entry is left in place. The "git stash
|
||||
* apply", "pop" and "branch" subcommands exit with this status
|
||||
* in this case, mirroring the convention of "git merge-tree" and
|
||||
* the merge strategies.
|
||||
*/
|
||||
STASH_APPLY_CONFLICT = 1,
|
||||
|
||||
/* Something went wrong. */
|
||||
STASH_APPLY_ERROR = -1,
|
||||
};
|
||||
|
||||
#endif /* STASH_H */
|
||||
|
|
@ -1808,13 +1808,13 @@ test_expect_success 'stash.index=false overridden by --index' '
|
|||
test_cmp expect file
|
||||
'
|
||||
|
||||
test_expect_success 'apply with custom conflict labels' '
|
||||
test_expect_success 'apply exits 1 on conflicts' '
|
||||
git reset --hard initial &&
|
||||
test_commit label-base conflict-file base-content &&
|
||||
echo stashed >conflict-file &&
|
||||
git stash push -m "stashed" &&
|
||||
test_commit label-upstream conflict-file upstream-content &&
|
||||
test_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&
|
||||
test_expect_code 1 git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&
|
||||
test_grep "^<<<<<<< UP" conflict-file &&
|
||||
test_grep "^||||||| Stash base" conflict-file &&
|
||||
test_grep "^>>>>>>> STASH" conflict-file
|
||||
|
|
@ -1826,11 +1826,30 @@ test_expect_success 'apply with empty conflict labels' '
|
|||
echo stashed >conflict-file &&
|
||||
git stash push -m "stashed" &&
|
||||
test_commit empty-label-upstream conflict-file upstream-content &&
|
||||
test_must_fail git stash apply --label-ours= --label-theirs= &&
|
||||
test_expect_code 1 git stash apply --label-ours= --label-theirs= &&
|
||||
test_grep "^<<<<<<<$" conflict-file &&
|
||||
test_grep "^>>>>>>>$" conflict-file
|
||||
'
|
||||
|
||||
test_expect_success 'pop exits 1 on conflicts and keeps the stash entry' '
|
||||
git reset --hard initial &&
|
||||
echo stashed >file &&
|
||||
git stash push -m pop-stashed &&
|
||||
test_commit pop-upstream file upstream-content &&
|
||||
test_expect_code 1 git stash pop &&
|
||||
git stash list >list &&
|
||||
test_grep pop-stashed list
|
||||
'
|
||||
|
||||
test_expect_success 'stash branch exits with a non-1 status on errors' '
|
||||
git reset --hard initial &&
|
||||
echo stashed >file &&
|
||||
git stash push -m branch-stashed &&
|
||||
test_expect_code 128 git stash branch conflicting-branch refs/heads/does-not-exist &&
|
||||
git stash list >list &&
|
||||
test_grep branch-stashed list
|
||||
'
|
||||
|
||||
test_expect_success 'stash show --include-untracked includes untracked files' '
|
||||
git reset --hard &&
|
||||
|
||||
|
|
|
|||
|
|
@ -236,10 +236,18 @@ test_expect_success 'checkout -m creates a recoverable stash on conflict' '
|
|||
test_must_fail git checkout side 2>stderr &&
|
||||
test_grep "Your local changes" stderr &&
|
||||
git checkout -m side >actual 2>&1 &&
|
||||
test_grep "resulted in conflicts" actual &&
|
||||
test_grep "git stash drop" actual &&
|
||||
test_grep "git stash pop" actual &&
|
||||
test_grep "The following paths have local changes" actual &&
|
||||
cat >expect <<-EOF &&
|
||||
Your local changes are stashed, however applying them
|
||||
resulted in conflicts. You can either resolve the conflicts
|
||||
and then discard the stash with "git stash drop", or, if you
|
||||
do not want to resolve them now, run "git reset --hard" and
|
||||
apply the local changes later by running "git stash pop".
|
||||
|
||||
Switched to branch ${SQ}side${SQ}
|
||||
The following paths have local changes:
|
||||
M one
|
||||
EOF
|
||||
test_cmp expect 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 &&
|
||||
|
|
|
|||
Loading…
Reference in New Issue