stash: add --label-ours, --label-theirs, --label-base for apply

Allow callers of "git stash apply" to pass custom labels for conflict
markers instead of the default "Updated upstream" and "Stashed changes".
Document the new options and add a test.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
main
Harald Nordgren 2026-04-28 18:39:08 +00:00 committed by Junio C Hamano
parent b15384c06f
commit 13817db274
4 changed files with 57 additions and 12 deletions

View File

@ -12,7 +12,7 @@ git stash list [<log-options>]
git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]
git stash drop [-q | --quiet] [<stash>]
git stash pop [--index] [-q | --quiet] [<stash>]
git stash apply [--index] [-q | --quiet] [<stash>]
git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]
git stash branch <branchname> [<stash>]
git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]
[-u | --include-untracked] [-a | --all] [(-m | --message) <message>]
@ -195,6 +195,15 @@ the index's ones. However, this can fail, when you have conflicts
(which are stored in the index, where you therefore can no longer
apply the changes as they were originally).

`--label-ours=<label>`::
`--label-theirs=<label>`::
`--label-base=<label>`::
These options are only valid for the `apply` command.
+
Use the given labels in conflict markers instead of the default
"Updated upstream", "Stashed changes", and "Stash base".
`--label-base` only has an effect with merge.conflictStyle=diff3.

`-k`::
`--keep-index`::
`--no-keep-index`::

View File

@ -44,7 +44,7 @@
#define BUILTIN_STASH_POP_USAGE \
N_("git stash pop [--index] [-q | --quiet] [<stash>]")
#define BUILTIN_STASH_APPLY_USAGE \
N_("git stash apply [--index] [-q | --quiet] [<stash>]")
N_("git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]")
#define BUILTIN_STASH_BRANCH_USAGE \
N_("git stash branch <branchname> [<stash>]")
#define BUILTIN_STASH_STORE_USAGE \
@ -591,7 +591,9 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)
}

static int do_apply_stash(const char *prefix, struct stash_info *info,
int index, int quiet)
int index, int quiet,
const char *label_ours, const char *label_theirs,
const char *label_base)
{
int clean, ret;
int has_index = index;
@ -643,9 +645,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,

init_ui_merge_options(&o, the_repository);

o.branch1 = "Updated upstream";
o.branch2 = "Stashed changes";
o.ancestor = "Stash base";
o.branch1 = label_ours ? label_ours : "Updated upstream";
o.branch2 = label_theirs ? label_theirs : "Stashed changes";
o.ancestor = label_base ? label_base : "Stash base";

if (oideq(&info->b_tree, &c_tree))
o.branch1 = "Version stash was based on";
@ -723,11 +725,18 @@ static int apply_stash(int argc, const char **argv, const char *prefix,
int ret = -1;
int quiet = 0;
int index = use_index;
const char *label_ours = NULL, *label_theirs = NULL, *label_base = NULL;
struct stash_info info = STASH_INFO_INIT;
struct option options[] = {
OPT__QUIET(&quiet, N_("be quiet, only report errors")),
OPT_BOOL(0, "index", &index,
N_("attempt to recreate the index")),
OPT_STRING(0, "label-ours", &label_ours, N_("label"),
N_("label for the upstream side in conflict markers")),
OPT_STRING(0, "label-theirs", &label_theirs, N_("label"),
N_("label for the stashed side in conflict markers")),
OPT_STRING(0, "label-base", &label_base, N_("label"),
N_("label for the base in diff3 conflict markers")),
OPT_END()
};

@ -737,7 +746,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,
if (get_stash_info(&info, argc, argv))
goto cleanup;

ret = do_apply_stash(prefix, &info, index, quiet);
ret = do_apply_stash(prefix, &info, index, quiet,
label_ours, label_theirs, label_base);
cleanup:
free_stash_info(&info);
return ret;
@ -836,7 +846,8 @@ static int pop_stash(int argc, const char **argv, const char *prefix,
if (get_stash_info_assert(&info, argc, argv))
goto cleanup;

if ((ret = do_apply_stash(prefix, &info, index, quiet)))
if ((ret = do_apply_stash(prefix, &info, index, quiet,
NULL, NULL, NULL)))
printf_ln(_("The stash entry is kept in case "
"you need it again."));
else
@ -877,7 +888,8 @@ static int branch_stash(int argc, const char **argv, const char *prefix,
strvec_push(&cp.args, oid_to_hex(&info.b_commit));
ret = run_command(&cp);
if (!ret)
ret = do_apply_stash(prefix, &info, 1, 0);
ret = do_apply_stash(prefix, &info, 1, 0,
NULL, NULL, NULL);
if (!ret && info.is_stash_ref)
ret = do_drop_stash(&info, 0);


View File

@ -56,6 +56,7 @@ setup_stash() {
git add other-file &&
test_tick &&
git commit -m initial &&
git tag initial &&
echo 2 >file &&
git add file &&
echo 3 >file &&
@ -1790,4 +1791,27 @@ test_expect_success 'stash.index=false overridden by --index' '
test_cmp expect file
'

test_expect_success 'apply with custom conflict labels' '
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_grep "^<<<<<<< UP" conflict-file &&
test_grep "^||||||| Stash base" conflict-file &&
test_grep "^>>>>>>> STASH" conflict-file
'

test_expect_success 'apply with empty conflict labels' '
git reset --hard initial &&
test_commit empty-label-base conflict-file base-content &&
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_grep "^<<<<<<<$" conflict-file &&
test_grep "^>>>>>>>$" conflict-file
'

test_done

View File

@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
int size, int i, int style,
xdmerge_t *m, char *dest, int marker_size)
{
int marker1_size = (name1 ? strlen(name1) + 1 : 0);
int marker2_size = (name2 ? strlen(name2) + 1 : 0);
int marker3_size = (name3 ? strlen(name3) + 1 : 0);
int marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);
int marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);
int marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);
int needs_cr = is_cr_needed(xe1, xe2, m);

if (marker_size <= 0)