apply: register conflicted stages to the index

Now we have all the necessary logic to fall back on three-way merge when the patch does not cleanly apply, insert the conflicted entries to the index as appropriate. This obviously triggers only when the "--index" option is used. When we fall back to three-way merge and some of the merges fail, just like the case where the "--reject" option was specified and we had to write some "*.rej" files out for unapplicable patches, exit the command with non-zero status without showing the diffstat and summary. Otherwise they would make the list of problematic paths scroll off the display. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed May 9, 2012 at 16:50 UTC 4f4a6cb9882516d75f345772c9bf00b891753f10
2 files changed +138 -6
builtin/apply.c
+60 -6
@@ -3288,7 +3288,10 @@ static int try_threeway(struct image *image, struct patch *patch,
3288
3289 if (status) {
3290 patch->conflicted_threeway = 1;
3291 - hashcpy(patch->threeway_stage[0], pre_sha1);
3291 + if (patch->is_new)
3292 + hashclr(patch->threeway_stage[0]);
3293 + else
3294 + hashcpy(patch->threeway_stage[0], pre_sha1);
3295 hashcpy(patch->threeway_stage[1], our_sha1);
3296 hashcpy(patch->threeway_stage[2], post_sha1);
3297 fprintf(stderr, "Applied patch to '%s' with conflicts.\n", patch->new_name);
@@ -3852,6 +3855,32 @@ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned
3855 die_errno(_("unable to write file '%s' mode %o"), path, mode);
3856 }
3857
3858 +static void add_conflicted_stages_file(struct patch *patch)
3859 +{
3860 + int stage, namelen;
3861 + unsigned ce_size, mode;
3862 + struct cache_entry *ce;
3863 +
3864 + if (!update_index)
3865 + return;
3866 + namelen = strlen(patch->new_name);
3867 + ce_size = cache_entry_size(namelen);
3868 + mode = patch->new_mode ? patch->new_mode : (S_IFREG | 0644);
3869 +
3870 + remove_file_from_cache(patch->new_name);
3871 + for (stage = 1; stage < 4; stage++) {
3872 + if (is_null_sha1(patch->threeway_stage[stage - 1]))
3873 + continue;
3874 + ce = xcalloc(1, ce_size);
3875 + memcpy(ce->name, patch->new_name, namelen);
3876 + ce->ce_mode = create_ce_mode(mode);
3877 + ce->ce_flags = create_ce_flags(namelen, stage);
3878 + hashcpy(ce->sha1, patch->threeway_stage[stage - 1]);
3879 + if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
3880 + die(_("unable to add cache entry for %s"), patch->new_name);
3881 + }
3882 +}
3883 +
3884 static void create_file(struct patch *patch)
3885 {
3886 char *path = patch->new_name;
@@ -3862,7 +3891,11 @@ static void create_file(struct patch *patch)
3891 if (!mode)
3892 mode = S_IFREG | 0644;
3893 create_one_file(path, mode, buf, size);
3865 - add_index_file(path, mode, buf, size);
3894 +
3895 + if (patch->conflicted_threeway)
3896 + add_conflicted_stages_file(patch);
3897 + else
3898 + add_index_file(path, mode, buf, size);
3899 }
3900
3901 /* phase zero is to remove, phase one is to create */
@@ -3964,6 +3997,7 @@ static int write_out_results(struct patch *list)
3997 int phase;
3998 int errs = 0;
3999 struct patch *l;
4000 + struct string_list cpath = STRING_LIST_INIT_DUP;
4001
4002 for (phase = 0; phase < 2; phase++) {
4003 l = list;
@@ -3972,12 +4006,28 @@ static int write_out_results(struct patch *list)
4006 errs = 1;
4007 else {
4008 write_out_one_result(l, phase);
3975 - if (phase == 1 && write_out_one_reject(l))
3976 - errs = 1;
4009 + if (phase == 1) {
4010 + if (write_out_one_reject(l))
4011 + errs = 1;
4012 + if (l->conflicted_threeway) {
4013 + string_list_append(&cpath, l->new_name);
4014 + errs = 1;
4015 + }
4016 + }
4017 }
4018 l = l->next;
4019 }
4020 }
4021 +
4022 + if (cpath.nr) {
4023 + struct string_list_item *item;
4024 +
4025 + sort_string_list(&cpath);
4026 + for_each_string_list_item(item, &cpath)
4027 + fprintf(stderr, "U %s\n", item->string);
4028 + string_list_clear(&cpath, 0);
4029 + }
4030 +
4031 return errs;
4032 }
4033
@@ -4100,8 +4150,12 @@ static int apply_patch(int fd, const char *filename, int options)
4150 !apply_with_reject)
4151 exit(1);
4152
4103 - if (apply && write_out_results(list))
4104 - exit(1);
4153 + if (apply && write_out_results(list)) {
4154 + if (apply_with_reject)
4155 + exit(1);
4156 + /* with --3way, we still need to write the index out */
4157 + return 1;
4158 + }
4159
4160 if (fake_ancestor)
4161 build_fake_ancestor(list, fake_ancestor);
t/t4108-apply-threeway.sh new
+78
@@ -0,0 +1,78 @@
1 +#!/bin/sh
2 +
3 +test_description='git apply --3way'
4 +
5 +. ./test-lib.sh
6 +
7 +create_file () {
8 + for i
9 + do
10 + echo "$i"
11 + done
12 +}
13 +
14 +sanitize_conflicted_diff () {
15 + sed -e '
16 + /^index /d
17 + s/^\(+[<>][<>][<>][<>]*\) .*/\1/
18 + '
19 +}
20 +
21 +test_expect_success setup '
22 + test_tick &&
23 + create_file >one 1 2 3 4 5 6 7 &&
24 + cat one >two &&
25 + git add one two &&
26 + git commit -m initial &&
27 +
28 + git branch side &&
29 +
30 + test_tick &&
31 + create_file >one 1 two 3 4 5 six 7 &&
32 + create_file >two 1 two 3 4 5 6 7 &&
33 + git commit -a -m master &&
34 +
35 + git checkout side &&
36 + create_file >one 1 2 3 4 five 6 7 &&
37 + create_file >two 1 2 3 4 five 6 7 &&
38 + git commit -a -m side &&
39 +
40 + git checkout master
41 +'
42 +
43 +test_expect_success 'apply without --3way' '
44 + git diff side^ side >P.diff &&
45 +
46 + # should fail to apply
47 + git reset --hard &&
48 + git checkout master^0 &&
49 + test_must_fail git apply --index P.diff &&
50 + # should leave things intact
51 + git diff-files --exit-code &&
52 + git diff-index --exit-code --cached HEAD
53 +'
54 +
55 +test_expect_success 'apply with --3way' '
56 + # Merging side should be similar to applying this patch
57 + git diff ...side >P.diff &&
58 +
59 + # The corresponding conflicted merge
60 + git reset --hard &&
61 + git checkout master^0 &&
62 + test_must_fail git merge --no-commit side &&
63 + git ls-files -s >expect.ls &&
64 + git diff HEAD | sanitize_conflicted_diff >expect.diff &&
65 +
66 + # should fail to apply
67 + git reset --hard &&
68 + git checkout master^0 &&
69 + test_must_fail git apply --index --3way P.diff &&
70 + git ls-files -s >actual.ls &&
71 + git diff HEAD | sanitize_conflicted_diff >actual.diff &&
72 +
73 + # The result should resemble the corresponding merge
74 + test_cmp expect.ls actual.ls &&
75 + test_cmp expect.diff actual.diff
76 +'
77 +
78 +test_done