git-apply: allow simultaneous --cached and --3way options

"git apply" does not allow "--cached" and "--3way" to be used together, since "--3way" writes conflict markers into the working tree. Allow "git apply" to accept "--cached" and "--3way" at the same time. When a single file auto-resolves cleanly, the result is placed in the index at stage #0 and the command exits with 0 status. For a file that has a conflict which cannot be cleanly auto-resolved, the original contents from common ancestor (stage conflict at the content level, and the command exists with non-zero status, because there is no place (like the working tree) to leave a half-resolved merge for the user to resolve. The user can use `git diff` to view the contents of the conflict, or `git checkout -m -- .` to regenerate the conflict markers in the working directory. Don't attempt rerere in this case since it depends on conflict markers written to file for its database storage and lookup. There would be two main changes required to get rerere working: 1. Allow the rerere api to accept in memory object rather than files, which would allow us to pass in the conflict markers contained in the result from ll_merge(). 2. Rerere can't write to the working directory, so it would have to apply the result to cache stage #0 directly. A flag would be needed to control this. Signed-off-by: Jerry Zhang <jerry@skydio.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jerry Zhang committed Apr 7, 2021 at 19:13 UTC c0c2a37ac2b9338c3a93340cbcbab69690da4df0
3 files changed +60 -5
Documentation/git-apply.txt
+4 -2
@@ -87,8 +87,10 @@ OPTIONS
87 Attempt 3-way merge if the patch records the identity of blobs it is supposed
88 to apply to and we have those blobs available locally, possibly leaving the
89 conflict markers in the files in the working tree for the user to
90 - resolve. This option implies the `--index` option, and is incompatible
91 - with the `--reject` and the `--cached` options.
90 + resolve. This option implies the `--index` option unless the
91 + `--cached` option is used, and is incompatible with the `--reject` option.
92 + When used with the `--cached` option, any conflicts are left at higher stages
93 + in the cache.
94
95 --build-fake-ancestor=<file>::
96 Newer 'git diff' output has embedded 'index information'
apply.c
+6 -3
@@ -134,8 +134,6 @@ int check_apply_state(struct apply_state *state, int force_apply)
134
135 if (state->apply_with_reject && state->threeway)
136 return error(_("--reject and --3way cannot be used together."));
137 - if (state->cached && state->threeway)
138 - return error(_("--cached and --3way cannot be used together."));
137 if (state->threeway) {
138 if (is_not_gitdir)
139 return error(_("--3way outside a repository"));
@@ -4646,7 +4644,12 @@ static int write_out_results(struct apply_state *state, struct patch *list)
4644 }
4645 string_list_clear(&cpath, 0);
4646
4649 - repo_rerere(state->repo, 0);
4647 + /*
4648 + * rerere relies on the partially merged result being in the working
4649 + * tree with conflict markers, but that isn't written with --cached.
4650 + */
4651 + if (!state->cached)
4652 + repo_rerere(state->repo, 0);
4653 }
4654
4655 return errs;
t/t4108-apply-threeway.sh
+50
@@ -180,4 +180,54 @@ test_expect_success 'apply -3 with ambiguous repeating file' '
180 test_cmp expect one_two_repeat
181 '
182
183 +test_expect_success 'apply with --3way --cached clean apply' '
184 + # Merging side should be similar to applying this patch
185 + git diff ...side >P.diff &&
186 +
187 + # The corresponding cleanly applied merge
188 + git reset --hard &&
189 + git checkout main~ &&
190 + git merge --no-commit side &&
191 + git ls-files -s >expect.ls &&
192 +
193 + # should succeed
194 + git reset --hard &&
195 + git checkout main~ &&
196 + git apply --cached --3way P.diff &&
197 + git ls-files -s >actual.ls &&
198 + print_sanitized_conflicted_diff >actual.diff &&
199 +
200 + # The cache should resemble the corresponding merge
201 + # (both files at stage #0)
202 + test_cmp expect.ls actual.ls &&
203 + # However the working directory should not change
204 + >expect.diff &&
205 + test_cmp expect.diff actual.diff
206 +'
207 +
208 +test_expect_success 'apply with --3way --cached and conflicts' '
209 + # Merging side should be similar to applying this patch
210 + git diff ...side >P.diff &&
211 +
212 + # The corresponding conflicted merge
213 + git reset --hard &&
214 + git checkout main^0 &&
215 + test_must_fail git merge --no-commit side &&
216 + git ls-files -s >expect.ls &&
217 +
218 + # should fail to apply
219 + git reset --hard &&
220 + git checkout main^0 &&
221 + test_must_fail git apply --cached --3way P.diff &&
222 + git ls-files -s >actual.ls &&
223 + print_sanitized_conflicted_diff >actual.diff &&
224 +
225 + # The cache should resemble the corresponding merge
226 + # (one file at stage #0, one file at stages #1 #2 #3)
227 + test_cmp expect.ls actual.ls &&
228 + # However the working directory should not change
229 + >expect.diff &&
230 + test_cmp expect.diff actual.diff
231 +'
232 +
233 test_done