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>
Harald Nordgren committed
Apr 28, 2026 at 18:39 UTC
13817db2746d48b6d3cfe68e3bf10a1be67865a9
4 files changed
+57
-12
Documentation/git-stash.adoc
+10
-1
@@ -12,7 +12,7 @@ git stash list [<log-options>]
12
git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]
13
git stash drop [-q | --quiet] [<stash>]
14
git stash pop [--index] [-q | --quiet] [<stash>]
15
-git stash apply [--index] [-q | --quiet] [<stash>]
15
+git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]
16
git stash branch <branchname> [<stash>]
17
git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]
18
[-u | --include-untracked] [-a | --all] [(-m | --message) <message>]
@@ -195,6 +195,15 @@ the index's ones. However, this can fail, when you have conflicts
195
(which are stored in the index, where you therefore can no longer
196
apply the changes as they were originally).
197
198
+`--label-ours=<label>`::
199
+`--label-theirs=<label>`::
200
+`--label-base=<label>`::
201
+ These options are only valid for the `apply` command.
202
++
203
+Use the given labels in conflict markers instead of the default
204
+"Updated upstream", "Stashed changes", and "Stash base".
205
+`--label-base` only has an effect with merge.conflictStyle=diff3.
206
+
207
`-k`::
208
`--keep-index`::
209
`--no-keep-index`::
builtin/stash.c
+20
-8
@@ -44,7 +44,7 @@
44
#define BUILTIN_STASH_POP_USAGE \
45
N_("git stash pop [--index] [-q | --quiet] [<stash>]")
46
#define BUILTIN_STASH_APPLY_USAGE \
47
- N_("git stash apply [--index] [-q | --quiet] [<stash>]")
47
+ N_("git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]")
48
#define BUILTIN_STASH_BRANCH_USAGE \
49
N_("git stash branch <branchname> [<stash>]")
50
#define BUILTIN_STASH_STORE_USAGE \
@@ -591,7 +591,9 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)
591
}
592
593
static int do_apply_stash(const char *prefix, struct stash_info *info,
594
- int index, int quiet)
594
+ int index, int quiet,
595
+ const char *label_ours, const char *label_theirs,
596
+ const char *label_base)
597
{
598
int clean, ret;
599
int has_index = index;
@@ -643,9 +645,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
645
646
init_ui_merge_options(&o, the_repository);
647
646
- o.branch1 = "Updated upstream";
647
- o.branch2 = "Stashed changes";
648
- o.ancestor = "Stash base";
648
+ o.branch1 = label_ours ? label_ours : "Updated upstream";
649
+ o.branch2 = label_theirs ? label_theirs : "Stashed changes";
650
+ o.ancestor = label_base ? label_base : "Stash base";
651
652
if (oideq(&info->b_tree, &c_tree))
653
o.branch1 = "Version stash was based on";
@@ -723,11 +725,18 @@ static int apply_stash(int argc, const char **argv, const char *prefix,
725
int ret = -1;
726
int quiet = 0;
727
int index = use_index;
728
+ const char *label_ours = NULL, *label_theirs = NULL, *label_base = NULL;
729
struct stash_info info = STASH_INFO_INIT;
730
struct option options[] = {
731
OPT__QUIET(&quiet, N_("be quiet, only report errors")),
732
OPT_BOOL(0, "index", &index,
733
N_("attempt to recreate the index")),
734
+ OPT_STRING(0, "label-ours", &label_ours, N_("label"),
735
+ N_("label for the upstream side in conflict markers")),
736
+ OPT_STRING(0, "label-theirs", &label_theirs, N_("label"),
737
+ N_("label for the stashed side in conflict markers")),
738
+ OPT_STRING(0, "label-base", &label_base, N_("label"),
739
+ N_("label for the base in diff3 conflict markers")),
740
OPT_END()
741
};
742
@@ -737,7 +746,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,
746
if (get_stash_info(&info, argc, argv))
747
goto cleanup;
748
740
- ret = do_apply_stash(prefix, &info, index, quiet);
749
+ ret = do_apply_stash(prefix, &info, index, quiet,
750
+ label_ours, label_theirs, label_base);
751
cleanup:
752
free_stash_info(&info);
753
return ret;
@@ -836,7 +846,8 @@ static int pop_stash(int argc, const char **argv, const char *prefix,
846
if (get_stash_info_assert(&info, argc, argv))
847
goto cleanup;
848
839
- if ((ret = do_apply_stash(prefix, &info, index, quiet)))
849
+ if ((ret = do_apply_stash(prefix, &info, index, quiet,
850
+ NULL, NULL, NULL)))
851
printf_ln(_("The stash entry is kept in case "
852
"you need it again."));
853
else
@@ -877,7 +888,8 @@ static int branch_stash(int argc, const char **argv, const char *prefix,
888
strvec_push(&cp.args, oid_to_hex(&info.b_commit));
889
ret = run_command(&cp);
890
if (!ret)
880
- ret = do_apply_stash(prefix, &info, 1, 0);
891
+ ret = do_apply_stash(prefix, &info, 1, 0,
892
+ NULL, NULL, NULL);
893
if (!ret && info.is_stash_ref)
894
ret = do_drop_stash(&info, 0);
895
t/t3903-stash.sh
+24
@@ -56,6 +56,7 @@ setup_stash() {
56
git add other-file &&
57
test_tick &&
58
git commit -m initial &&
59
+ git tag initial &&
60
echo 2 >file &&
61
git add file &&
62
echo 3 >file &&
@@ -1790,4 +1791,27 @@ test_expect_success 'stash.index=false overridden by --index' '
1791
test_cmp expect file
1792
'
1793
1794
+test_expect_success 'apply with custom conflict labels' '
1795
+ git reset --hard initial &&
1796
+ test_commit label-base conflict-file base-content &&
1797
+ echo stashed >conflict-file &&
1798
+ git stash push -m "stashed" &&
1799
+ test_commit label-upstream conflict-file upstream-content &&
1800
+ test_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&
1801
+ test_grep "^<<<<<<< UP" conflict-file &&
1802
+ test_grep "^||||||| Stash base" conflict-file &&
1803
+ test_grep "^>>>>>>> STASH" conflict-file
1804
+'
1805
+
1806
+test_expect_success 'apply with empty conflict labels' '
1807
+ git reset --hard initial &&
1808
+ test_commit empty-label-base conflict-file base-content &&
1809
+ echo stashed >conflict-file &&
1810
+ git stash push -m "stashed" &&
1811
+ test_commit empty-label-upstream conflict-file upstream-content &&
1812
+ test_must_fail git stash apply --label-ours= --label-theirs= &&
1813
+ test_grep "^<<<<<<<$" conflict-file &&
1814
+ test_grep "^>>>>>>>$" conflict-file
1815
+'
1816
+
1817
test_done
xdiff/xmerge.c
+3
-3
@@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
199
int size, int i, int style,
200
xdmerge_t *m, char *dest, int marker_size)
201
{
202
- int marker1_size = (name1 ? strlen(name1) + 1 : 0);
203
- int marker2_size = (name2 ? strlen(name2) + 1 : 0);
204
- int marker3_size = (name3 ? strlen(name3) + 1 : 0);
202
+ int marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);
203
+ int marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);
204
+ int marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);
205
int needs_cr = is_cr_needed(xe1, xe2, m);
206
207
if (marker_size <= 0)