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