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>
This commit is contained in:
Harald Nordgren
2026-04-10 21:01:10 +00:00
committed by Junio C Hamano
parent b15384c06f
commit 1fcc199bd8
4 changed files with 67 additions and 11 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 \
@@ -590,8 +590,11 @@ 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)
static int do_apply_stash_with_labels(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;
@@ -643,9 +646,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";
@@ -717,17 +720,31 @@ restore_untracked:
return ret;
}
static int do_apply_stash(const char *prefix, struct stash_info *info,
int index, int quiet)
{
return do_apply_stash_with_labels(prefix, info, index, quiet,
NULL, NULL, NULL);
}
static int apply_stash(int argc, const char **argv, const char *prefix,
struct repository *repo UNUSED)
{
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 +754,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_with_labels(prefix, &info, index, quiet,
label_ours, label_theirs, label_base);
cleanup:
free_stash_info(&info);
return ret;

View File

@@ -1666,6 +1666,35 @@ test_expect_success 'restore untracked files even when we hit conflicts' '
)
'
test_expect_success 'apply with custom conflict labels' '
git init conflict_labels &&
(
cd conflict_labels &&
test_commit base file &&
echo stashed >file &&
git stash push -m "stashed" &&
test_commit upstream file &&
test_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&
test_grep "^<<<<<<< UP" file &&
test_grep "^||||||| Stash base" file &&
test_grep "^>>>>>>> STASH" file
)
'
test_expect_success 'apply with empty conflict labels' '
git init empty_labels &&
(
cd empty_labels &&
test_commit base file &&
echo stashed >file &&
git stash push -m "stashed" &&
test_commit upstream file &&
test_must_fail git stash apply --label-ours= --label-theirs= &&
test_grep "^<<<<<<<$" file &&
test_grep "^>>>>>>>$" file
)
'
test_expect_success 'stash create reports a locked index' '
test_when_finished "rm -rf repo" &&
git init repo &&

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)