Subject: Re: [PATCH v15 1/5] stash: add --label-ours, --label-theirs, --label-base for apply

From: Phillip Wood <[email-masked]>
Date: Tue, 28 Apr 2026 10:32:10 +0100

Hi Harald

On 24/04/2026 22:10, Harald Nordgren via GitGitGadget wrote:
> From: Harald Nordgren <[email-masked]>
> 
> 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.

This all looks good, just one small comment below

> -	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";

This uses the existing label which is sensible, but I wonder if "Stash 
HEAD" would be a better choice as the merge base is always HEAD commit 
that the stash is based on.

We can always change that later

Thanks

Phillip

>   
>   	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);
>   
> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
> index 70879941c2..bdaad22e1f 100755
> --- a/t/t3903-stash.sh
> +++ b/t/t3903-stash.sh
> @@ -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
> diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c
> index 29dad98c49..659ad4ec97 100644
> --- a/xdiff/xmerge.c
> +++ b/xdiff/xmerge.c
> @@ -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)