Merge branch 'ab/config-based-hooks-base' into seen
[git] / t / t6421-merge-partial-clone.sh
1 #!/bin/sh
2
3 test_description="limiting blob downloads when merging with partial clones"
4 # Uses a methodology similar to
5 #   t6042: corner cases with renames but not criss-cross merges
6 #   t6036: corner cases with both renames and criss-cross merges
7 #   t6423: directory rename detection
8 #
9 # The setup for all of them, pictorially, is:
10 #
11 #      A
12 #      o
13 #     / \
14 #  O o   ?
15 #     \ /
16 #      o
17 #      B
18 #
19 # To help make it easier to follow the flow of tests, they have been
20 # divided into sections and each test will start with a quick explanation
21 # of what commits O, A, and B contain.
22 #
23 # Notation:
24 #    z/{b,c}   means  files z/b and z/c both exist
25 #    x/d_1     means  file x/d exists with content d1.  (Purpose of the
26 #                     underscore notation is to differentiate different
27 #                     files that might be renamed into each other's paths.)
28
29 . ./test-lib.sh
30 . "$TEST_DIRECTORY"/lib-merge.sh
31
32 test_setup_repo () {
33         test -d server && return
34         test_create_repo server &&
35         (
36                 cd server &&
37
38                 git config uploadpack.allowfilter 1 &&
39                 git config uploadpack.allowanysha1inwant 1 &&
40
41                 mkdir -p general &&
42                 test_seq 2 9 >general/leap1 &&
43                 cp general/leap1 general/leap2 &&
44                 echo leap2 >>general/leap2 &&
45
46                 mkdir -p basename &&
47                 cp general/leap1 basename/numbers &&
48                 cp general/leap1 basename/sequence &&
49                 cp general/leap1 basename/values &&
50                 echo numbers >>basename/numbers &&
51                 echo sequence >>basename/sequence &&
52                 echo values >>basename/values &&
53
54                 mkdir -p dir/unchanged &&
55                 mkdir -p dir/subdir/tweaked &&
56                 echo a >dir/subdir/a &&
57                 echo b >dir/subdir/b &&
58                 echo c >dir/subdir/c &&
59                 echo d >dir/subdir/d &&
60                 echo e >dir/subdir/e &&
61                 cp general/leap1 dir/subdir/Makefile &&
62                 echo toplevel makefile >>dir/subdir/Makefile &&
63                 echo f >dir/subdir/tweaked/f &&
64                 echo g >dir/subdir/tweaked/g &&
65                 echo h >dir/subdir/tweaked/h &&
66                 echo subdirectory makefile >dir/subdir/tweaked/Makefile &&
67                 for i in `test_seq 1 88`; do
68                         echo content $i >dir/unchanged/file_$i
69                 done &&
70                 git add . &&
71                 git commit -m "O" &&
72
73                 git branch O &&
74                 git branch A &&
75                 git branch B-single &&
76                 git branch B-dir &&
77                 git branch B-many &&
78
79                 git switch A &&
80
81                 git rm general/leap* &&
82                 mkdir general/ &&
83                 test_seq 1 9 >general/jump1 &&
84                 cp general/jump1 general/jump2 &&
85                 echo leap2 >>general/jump2 &&
86
87                 rm basename/numbers basename/sequence basename/values &&
88                 mkdir -p basename/subdir/
89                 cp general/jump1 basename/subdir/numbers &&
90                 cp general/jump1 basename/subdir/sequence &&
91                 cp general/jump1 basename/subdir/values &&
92                 echo numbers >>basename/subdir/numbers &&
93                 echo sequence >>basename/subdir/sequence &&
94                 echo values >>basename/subdir/values &&
95
96                 git rm dir/subdir/tweaked/f &&
97                 echo more >>dir/subdir/e &&
98                 echo more >>dir/subdir/Makefile &&
99                 echo more >>dir/subdir/tweaked/Makefile &&
100                 mkdir dir/subdir/newsubdir &&
101                 echo rust code >dir/subdir/newsubdir/newfile.rs &&
102                 git mv dir/subdir/e dir/subdir/newsubdir/ &&
103                 git mv dir folder &&
104                 git add . &&
105                 git commit -m "A" &&
106
107                 git switch B-single &&
108                 echo new first line >dir/subdir/Makefile &&
109                 cat general/leap1 >>dir/subdir/Makefile &&
110                 echo toplevel makefile >>dir/subdir/Makefile &&
111                 echo perl code >general/newfile.pl &&
112                 git add . &&
113                 git commit -m "B-single" &&
114
115                 git switch B-dir &&
116                 echo java code >dir/subdir/newfile.java &&
117                 echo scala code >dir/subdir/newfile.scala &&
118                 echo groovy code >dir/subdir/newfile.groovy &&
119                 git add . &&
120                 git commit -m "B-dir" &&
121
122                 git switch B-many &&
123                 test_seq 2 10 >general/leap1 &&
124                 rm general/leap2 &&
125                 cp general/leap1 general/leap2 &&
126                 echo leap2 >>general/leap2 &&
127
128                 rm basename/numbers basename/sequence basename/values &&
129                 mkdir -p basename/subdir/
130                 cp general/leap1 basename/subdir/numbers &&
131                 cp general/leap1 basename/subdir/sequence &&
132                 cp general/leap1 basename/subdir/values &&
133                 echo numbers >>basename/subdir/numbers &&
134                 echo sequence >>basename/subdir/sequence &&
135                 echo values >>basename/subdir/values &&
136
137                 mkdir dir/subdir/newsubdir/ &&
138                 echo c code >dir/subdir/newfile.c &&
139                 echo python code >dir/subdir/newsubdir/newfile.py &&
140                 git add . &&
141                 git commit -m "B-many" &&
142
143                 git switch A
144         )
145 }
146
147 # Testcase: Objects downloaded for single relevant rename
148 #   Commit O:
149 #              general/{leap1_O, leap2_O}
150 #              basename/{numbers_O, sequence_O, values_O}
151 #              dir/subdir/{a,b,c,d,e_O,Makefile_TOP_O}
152 #              dir/subdir/tweaked/{f,g,h,Makefile_SUB_O}
153 #              dir/unchanged/<LOTS OF FILES>
154 #   Commit A:
155 #     (Rename leap->jump, rename basename/ -> basename/subdir/, rename dir/
156 #      -> folder/, move e into newsubdir, add newfile.rs, remove f, modify
157 #      both both Makefiles and jumps)
158 #              general/{jump1_A, jump2_A}
159 #              basename/subdir/{numbers_A, sequence_A, values_A}
160 #              folder/subdir/{a,b,c,d,Makefile_TOP_A}
161 #              folder/subdir/newsubdir/{e_A,newfile.rs}
162 #              folder/subdir/tweaked/{g,h,Makefile_SUB_A}
163 #              folder/unchanged/<LOTS OF FILES>
164 #   Commit B(-single):
165 #     (add newfile.pl, tweak Makefile_TOP)
166 #              general/{leap1_O, leap2_O,newfile.pl}
167 #              basename/{numbers_O, sequence_O, values_O}
168 #              dir/{a,b,c,d,e_O,Makefile_TOP_B}
169 #              dir/tweaked/{f,g,h,Makefile_SUB_O}
170 #              dir/unchanged/<LOTS OF FILES>
171 #   Expected:
172 #              general/{jump1_A, jump2_A,newfile.pl}
173 #              basename/subdir/{numbers_A, sequence_A, values_A}
174 #              folder/subdir/{a,b,c,d,Makefile_TOP_Merged}
175 #              folder/subdir/newsubdir/{e_A,newfile.rs}
176 #              folder/subdir/tweaked/{g,h,Makefile_SUB_A}
177 #              folder/unchanged/<LOTS OF FILES>
178 #
179 # Objects that need to be fetched:
180 #   Rename detection:
181 #     Side1 (O->A):
182 #       Basename-matches rename detection only needs to fetch these objects:
183 #         Makefile_TOP_O, Makefile_TOP_A
184 #         (Despite many renames, all others are content irrelevant.  They
185 #          are also location irrelevant because newfile.rs was added on
186 #          the side doing the directory rename, and newfile.pl was added to
187 #          a directory that was not renamed on either side.)
188 #       General rename detection only needs to fetch these objects:
189 #         <None>
190 #          (Even though newfile.rs, jump[12], basename/subdir/*, and e
191 #          could all be used as destinations in rename detection, the
192 #          basename detection for Makefile matches up all relevant
193 #          sources, so these other files never end up needing to be
194 #          used)
195 #     Side2 (O->B):
196 #       Basename-matches rename detection only needs to fetch these objects:
197 #         <None>
198 #         (there are no deleted files, so no possible sources)
199 #       General rename detection only needs to fetch these objects:
200 #         <None>
201 #         (there are no deleted files, so no possible sources)
202 #   Merge:
203 #     3-way content merge needs to grab these objects:
204 #       Makefile_TOP_B
205 #   Nothing else needs to fetch objects
206 #
207 #   Summary: 2 fetches (1 for 2 objects, 1 for 1 object)
208 #
209 test_expect_merge_algorithm failure success 'Objects downloaded for single relevant rename' '
210         test_setup_repo &&
211         git clone --sparse --filter=blob:none "file://$(pwd)/server" objects-single &&
212         (
213                 cd objects-single &&
214
215                 git rev-list --objects --all --missing=print |
216                         grep '\?' >missing-objects-before &&
217
218                 git checkout -q origin/A &&
219
220                 GIT_TRACE2_PERF="$(pwd)/trace.output" git \
221                         -c merge.directoryRenames=true merge --no-stat \
222                         --no-progress origin/B-single &&
223
224                 # Check the number of objects we reported we would fetch
225                 cat >expect <<-EOF &&
226                 fetch_count:2
227                 fetch_count:1
228                 EOF
229                 grep fetch_count trace.output | cut -d "|" -f 9 | tr -d " ." >actual &&
230                 test_cmp expect actual &&
231
232                 # Check the number of fetch commands exec-ed
233                 grep d0.*fetch.negotiationAlgorithm trace.output >fetches &&
234                 test_line_count = 2 fetches &&
235
236                 git rev-list --objects --all --missing=print |
237                         grep ^? >missing-objects-after &&
238                 test_cmp missing-objects-before missing-objects-after |
239                         grep "^[-+]?" >found-and-new-objects &&
240                 # We should not have any NEW missing objects
241                 ! grep ^+ found-and-new-objects &&
242                 # Fetched 2+1=3 objects, so should have 3 fewer missing objects
243                 test_line_count = 3 found-and-new-objects
244         )
245 '
246
247 # Testcase: Objects downloaded for directory rename
248 #   Commit O:
249 #              general/{leap1_O, leap2_O}
250 #              basename/{numbers_O, sequence_O, values_O}
251 #              dir/subdir/{a,b,c,d,e_O,Makefile_TOP_O}
252 #              dir/subdir/tweaked/{f,g,h,Makefile_SUB_O}
253 #              dir/unchanged/<LOTS OF FILES>
254 #   Commit A:
255 #     (Rename leap->jump, rename basename/ -> basename/subdir/, rename dir/ ->
256 #      folder/, move e into newsubdir, add newfile.rs, remove f, modify
257 #      both Makefiles and jumps)
258 #              general/{jump1_A, jump2_A}
259 #              basename/subdir/{numbers_A, sequence_A, values_A}
260 #              folder/subdir/{a,b,c,d,Makefile_TOP_A}
261 #              folder/subdir/newsubdir/{e_A,newfile.rs}
262 #              folder/subdir/tweaked/{g,h,Makefile_SUB_A}
263 #              folder/unchanged/<LOTS OF FILES>
264 #   Commit B(-dir):
265 #     (add dir/subdir/newfile.{java,scala,groovy}
266 #              general/{leap1_O, leap2_O}
267 #              basename/{numbers_O, sequence_O, values_O}
268 #              dir/subdir/{a,b,c,d,e_O,Makefile_TOP_O,
269 #                          newfile.java,newfile.scala,newfile.groovy}
270 #              dir/subdir/tweaked/{f,g,h,Makefile_SUB_O}
271 #              dir/unchanged/<LOTS OF FILES>
272 #   Expected:
273 #              general/{jump1_A, jump2_A}
274 #              basename/subdir/{numbers_A, sequence_A, values_A}
275 #              folder/subdir/{a,b,c,d,Makefile_TOP_A,
276 #                             newfile.java,newfile.scala,newfile.groovy}
277 #              folder/subdir/newsubdir/{e_A,newfile.rs}
278 #              folder/subdir/tweaked/{g,h,Makefile_SUB_A}
279 #              folder/unchanged/<LOTS OF FILES>
280 #
281 # Objects that need to be fetched:
282 #   Makefile_TOP_O, Makefile_TOP_A
283 #   Makefile_SUB_O, Makefile_SUB_A
284 #   e_O, e_A
285 #   * Despite A's rename of jump->leap, those renames are irrelevant.
286 #   * Despite A's rename of basename/ -> basename/subdir/, those renames are
287 #     irrelevant.
288 #   * Because of A's rename of dir/ -> folder/ and B-dir's addition of
289 #     newfile.* into dir/subdir/, we need to determine directory renames.
290 #     (Technically, there are enough exact renames to determine directory
291 #      rename detection, but the current implementation always does
292 #      basename searching before directory rename detection.  Running it
293 #      also before basename searching would mean doing directory rename
294 #      detection twice, but it's a bit expensive to do that and cases like
295 #      this are not all that common.)
296 #   Summary: 1 fetches for 6 objects
297 #
298 test_expect_merge_algorithm failure success 'Objects downloaded when a directory rename triggered' '
299         test_setup_repo &&
300         git clone --sparse --filter=blob:none "file://$(pwd)/server" objects-dir &&
301         (
302                 cd objects-dir &&
303
304                 git rev-list --objects --all --missing=print |
305                         grep '\?' >missing-objects-before &&
306
307                 git checkout -q origin/A &&
308
309                 GIT_TRACE2_PERF="$(pwd)/trace.output" git \
310                         -c merge.directoryRenames=true merge --no-stat \
311                         --no-progress origin/B-dir &&
312
313                 # Check the number of objects we reported we would fetch
314                 cat >expect <<-EOF &&
315                 fetch_count:6
316                 EOF
317                 grep fetch_count trace.output | cut -d "|" -f 9 | tr -d " ." >actual &&
318                 test_cmp expect actual &&
319
320                 # Check the number of fetch commands exec-ed
321                 grep d0.*fetch.negotiationAlgorithm trace.output >fetches &&
322                 test_line_count = 1 fetches &&
323
324                 git rev-list --objects --all --missing=print |
325                         grep ^? >missing-objects-after &&
326                 test_cmp missing-objects-before missing-objects-after |
327                         grep "^[-+]?" >found-and-new-objects &&
328                 # We should not have any NEW missing objects
329                 ! grep ^+ found-and-new-objects &&
330                 # Fetched 6 objects, so should have 6 fewer missing objects
331                 test_line_count = 6 found-and-new-objects
332         )
333 '
334
335 # Testcase: Objects downloaded with lots of renames and modifications
336 #   Commit O:
337 #              general/{leap1_O, leap2_O}
338 #              basename/{numbers_O, sequence_O, values_O}
339 #              dir/subdir/{a,b,c,d,e_O,Makefile_TOP_O}
340 #              dir/subdir/tweaked/{f,g,h,Makefile_SUB_O}
341 #              dir/unchanged/<LOTS OF FILES>
342 #   Commit A:
343 #     (Rename leap->jump, rename basename/ -> basename/subdir/, rename dir/
344 #      -> folder/, move e into newsubdir, add newfile.rs, remove f, modify
345 #      both both Makefiles and jumps)
346 #              general/{jump1_A, jump2_A}
347 #              basename/subdir/{numbers_A, sequence_A, values_A}
348 #              folder/subdir/{a,b,c,d,Makefile_TOP_A}
349 #              folder/subdir/newsubdir/{e_A,newfile.rs}
350 #              folder/subdir/tweaked/{g,h,Makefile_SUB_A}
351 #              folder/unchanged/<LOTS OF FILES>
352 #   Commit B(-minimal):
353 #     (modify both leaps, rename basename/ -> basename/subdir/, add
354 #      newfile.{c,py})
355 #              general/{leap1_B, leap2_B}
356 #              basename/subdir/{numbers_B, sequence_B, values_B}
357 #              dir/{a,b,c,d,e_O,Makefile_TOP_O,newfile.c}
358 #              dir/tweaked/{f,g,h,Makefile_SUB_O,newfile.py}
359 #              dir/unchanged/<LOTS OF FILES>
360 #   Expected:
361 #              general/{jump1_Merged, jump2_Merged}
362 #              basename/subdir/{numbers_Merged, sequence_Merged, values_Merged}
363 #              folder/subdir/{a,b,c,d,Makefile_TOP_A,newfile.c}
364 #              folder/subdir/newsubdir/e_A
365 #              folder/subdir/tweaked/{g,h,Makefile_SUB_A,newfile.py}
366 #              folder/unchanged/<LOTS OF FILES>
367 #
368 # Objects that need to be fetched:
369 #   Rename detection:
370 #     Side1 (O->A):
371 #       Basename-matches rename detection only needs to fetch these objects:
372 #         numbers_O, numbers_A
373 #         sequence_O, sequence_A
374 #         values_O, values_A
375 #         Makefile_TOP_O, Makefile_TOP_A
376 #         Makefile_SUB_O, Makefile_SUB_A
377 #         e_O, e_A
378 #       General rename detection only needs to fetch these objects:
379 #         leap1_O, leap2_O
380 #         jump1_A, jump2_A, newfile.rs
381 #         (only need remaining relevant sources, but any relevant sources need
382 #          to be matched against all possible unpaired destinations)
383 #     Side2 (O->B):
384 #       Basename-matches rename detection only needs to fetch these objects:
385 #         numbers_B
386 #         sequence_B
387 #         values_B
388 #       (because numbers_O, sequence_O, and values_O already fetched above)
389 #       General rename detection only needs to fetch these objects:
390 #         <None>
391 #   Merge:
392 #     3-way content merge needs to grab these objects:
393 #       leap1_B
394 #       leap2_B
395 #   Nothing else needs to fetch objects
396 #
397 #   Summary: 4 fetches (1 for 6 objects, 1 for 8, 1 for 3, 1 for 2)
398 #
399 test_expect_merge_algorithm failure success 'Objects downloaded with lots of renames and modifications' '
400         test_setup_repo &&
401         git clone --sparse --filter=blob:none "file://$(pwd)/server" objects-many &&
402         (
403                 cd objects-many &&
404
405                 git rev-list --objects --all --missing=print |
406                         grep '\?' >missing-objects-before &&
407
408                 git checkout -q origin/A &&
409
410                 GIT_TRACE2_PERF="$(pwd)/trace.output" git \
411                         -c merge.directoryRenames=true merge --no-stat \
412                         --no-progress origin/B-many &&
413
414                 # Check the number of objects we reported we would fetch
415                 cat >expect <<-EOF &&
416                 fetch_count:12
417                 fetch_count:5
418                 fetch_count:3
419                 fetch_count:2
420                 EOF
421                 grep fetch_count trace.output | cut -d "|" -f 9 | tr -d " ." >actual &&
422                 test_cmp expect actual &&
423
424                 # Check the number of fetch commands exec-ed
425                 grep d0.*fetch.negotiationAlgorithm trace.output >fetches &&
426                 test_line_count = 4 fetches &&
427
428                 git rev-list --objects --all --missing=print |
429                         grep ^? >missing-objects-after &&
430                 test_cmp missing-objects-before missing-objects-after |
431                         grep "^[-+]?" >found-and-new-objects &&
432                 # We should not have any NEW missing objects
433                 ! grep ^+ found-and-new-objects &&
434                 # Fetched 12 + 5 + 3 + 2 == 22 objects
435                 test_line_count = 22 found-and-new-objects
436         )
437 '
438
439 test_done