Merge branch 'ab/config-based-hooks-base' into seen
[git] / t / t1092-sparse-checkout-compatibility.sh
1 #!/bin/sh
2
3 test_description='compare full workdir to sparse workdir'
4
5 GIT_TEST_SPLIT_INDEX=0
6 GIT_TEST_SPARSE_INDEX=
7
8 . ./test-lib.sh
9
10 test_expect_success 'setup' '
11         git init initial-repo &&
12         (
13                 GIT_TEST_SPARSE_INDEX=0 &&
14                 cd initial-repo &&
15                 echo a >a &&
16                 echo "after deep" >e &&
17                 echo "after folder1" >g &&
18                 echo "after x" >z &&
19                 mkdir folder1 folder2 deep x &&
20                 mkdir deep/deeper1 deep/deeper2 deep/before deep/later &&
21                 mkdir deep/deeper1/deepest &&
22                 echo "after deeper1" >deep/e &&
23                 echo "after deepest" >deep/deeper1/e &&
24                 cp a folder1 &&
25                 cp a folder2 &&
26                 cp a x &&
27                 cp a deep &&
28                 cp a deep/before &&
29                 cp a deep/deeper1 &&
30                 cp a deep/deeper2 &&
31                 cp a deep/later &&
32                 cp a deep/deeper1/deepest &&
33                 cp -r deep/deeper1/deepest deep/deeper2 &&
34                 mkdir deep/deeper1/0 &&
35                 mkdir deep/deeper1/0/0 &&
36                 touch deep/deeper1/0/1 &&
37                 touch deep/deeper1/0/0/0 &&
38                 git add . &&
39                 git commit -m "initial commit" &&
40                 git checkout -b base &&
41                 for dir in folder1 folder2 deep
42                 do
43                         git checkout -b update-$dir &&
44                         echo "updated $dir" >$dir/a &&
45                         git commit -a -m "update $dir" || return 1
46                 done &&
47
48                 git checkout -b rename-base base &&
49                 echo >folder1/larger-content <<-\EOF &&
50                 matching
51                 lines
52                 help
53                 inexact
54                 renames
55                 EOF
56                 cp folder1/larger-content folder2/ &&
57                 cp folder1/larger-content deep/deeper1/ &&
58                 git add . &&
59                 git commit -m "add interesting rename content" &&
60
61                 git checkout -b rename-out-to-out rename-base &&
62                 mv folder1/a folder2/b &&
63                 mv folder1/larger-content folder2/edited-content &&
64                 echo >>folder2/edited-content &&
65                 git add . &&
66                 git commit -m "rename folder1/... to folder2/..." &&
67
68                 git checkout -b rename-out-to-in rename-base &&
69                 mv folder1/a deep/deeper1/b &&
70                 mv folder1/larger-content deep/deeper1/edited-content &&
71                 echo >>deep/deeper1/edited-content &&
72                 git add . &&
73                 git commit -m "rename folder1/... to deep/deeper1/..." &&
74
75                 git checkout -b rename-in-to-out rename-base &&
76                 mv deep/deeper1/a folder1/b &&
77                 mv deep/deeper1/larger-content folder1/edited-content &&
78                 echo >>folder1/edited-content &&
79                 git add . &&
80                 git commit -m "rename deep/deeper1/... to folder1/..." &&
81
82                 git checkout -b deepest base &&
83                 echo "updated deepest" >deep/deeper1/deepest/a &&
84                 git commit -a -m "update deepest" &&
85
86                 git checkout -f base &&
87                 git reset --hard
88         )
89 '
90
91 init_repos () {
92         rm -rf full-checkout sparse-checkout sparse-index &&
93
94         # create repos in initial state
95         cp -r initial-repo full-checkout &&
96         git -C full-checkout reset --hard &&
97
98         cp -r initial-repo sparse-checkout &&
99         git -C sparse-checkout reset --hard &&
100
101         cp -r initial-repo sparse-index &&
102         git -C sparse-index reset --hard &&
103
104         # initialize sparse-checkout definitions
105         git -C sparse-checkout sparse-checkout init --cone &&
106         git -C sparse-checkout sparse-checkout set deep &&
107         git -C sparse-index sparse-checkout init --cone --sparse-index &&
108         test_cmp_config -C sparse-index true index.sparse &&
109         git -C sparse-index sparse-checkout set deep
110 }
111
112 run_on_sparse () {
113         (
114                 cd sparse-checkout &&
115                 GIT_PROGRESS_DELAY=100000 "$@" >../sparse-checkout-out 2>../sparse-checkout-err
116         ) &&
117         (
118                 cd sparse-index &&
119                 GIT_PROGRESS_DELAY=100000 "$@" >../sparse-index-out 2>../sparse-index-err
120         )
121 }
122
123 run_on_all () {
124         (
125                 cd full-checkout &&
126                 GIT_PROGRESS_DELAY=100000 "$@" >../full-checkout-out 2>../full-checkout-err
127         ) &&
128         run_on_sparse "$@"
129 }
130
131 test_all_match () {
132         run_on_all "$@" &&
133         test_cmp full-checkout-out sparse-checkout-out &&
134         test_cmp full-checkout-out sparse-index-out &&
135         test_cmp full-checkout-err sparse-checkout-err &&
136         test_cmp full-checkout-err sparse-index-err
137 }
138
139 test_sparse_match () {
140         run_on_sparse "$@" &&
141         test_cmp sparse-checkout-out sparse-index-out &&
142         test_cmp sparse-checkout-err sparse-index-err
143 }
144
145 test_expect_success 'sparse-index contents' '
146         init_repos &&
147
148         test-tool -C sparse-index read-cache --table >cache &&
149         for dir in folder1 folder2 x
150         do
151                 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
152                 grep "040000 tree $TREE $dir/" cache \
153                         || return 1
154         done &&
155
156         git -C sparse-index sparse-checkout set folder1 &&
157
158         test-tool -C sparse-index read-cache --table >cache &&
159         for dir in deep folder2 x
160         do
161                 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
162                 grep "040000 tree $TREE $dir/" cache \
163                         || return 1
164         done &&
165
166         git -C sparse-index sparse-checkout set deep/deeper1 &&
167
168         test-tool -C sparse-index read-cache --table >cache &&
169         for dir in deep/deeper2 folder1 folder2 x
170         do
171                 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
172                 grep "040000 tree $TREE $dir/" cache \
173                         || return 1
174         done &&
175
176         # Disabling the sparse-index removes tree entries with full ones
177         git -C sparse-index sparse-checkout init --no-sparse-index &&
178
179         test-tool -C sparse-index read-cache --table >cache &&
180         ! grep "040000 tree" cache &&
181         test_sparse_match test-tool read-cache --table
182 '
183
184 test_expect_success 'expanded in-memory index matches full index' '
185         init_repos &&
186         test_sparse_match test-tool read-cache --expand --table
187 '
188
189 test_expect_success 'status with options' '
190         init_repos &&
191         test_sparse_match ls &&
192         test_all_match git status --porcelain=v2 &&
193         test_all_match git status --porcelain=v2 -z -u &&
194         test_all_match git status --porcelain=v2 -uno &&
195         run_on_all touch README.md &&
196         test_all_match git status --porcelain=v2 &&
197         test_all_match git status --porcelain=v2 -z -u &&
198         test_all_match git status --porcelain=v2 -uno &&
199         test_all_match git add README.md &&
200         test_all_match git status --porcelain=v2 &&
201         test_all_match git status --porcelain=v2 -z -u &&
202         test_all_match git status --porcelain=v2 -uno
203 '
204
205 test_expect_success 'status reports sparse-checkout' '
206         init_repos &&
207         git -C sparse-checkout status >full &&
208         git -C sparse-index status >sparse &&
209         test_i18ngrep "You are in a sparse checkout with " full &&
210         test_i18ngrep "You are in a sparse checkout." sparse
211 '
212
213 test_expect_success 'add, commit, checkout' '
214         init_repos &&
215
216         write_script edit-contents <<-\EOF &&
217         echo text >>$1
218         EOF
219         run_on_all ../edit-contents README.md &&
220
221         test_all_match git add README.md &&
222         test_all_match git status --porcelain=v2 &&
223         test_all_match git commit -m "Add README.md" &&
224
225         test_all_match git checkout HEAD~1 &&
226         test_all_match git checkout - &&
227
228         run_on_all ../edit-contents README.md &&
229
230         test_all_match git add -A &&
231         test_all_match git status --porcelain=v2 &&
232         test_all_match git commit -m "Extend README.md" &&
233
234         test_all_match git checkout HEAD~1 &&
235         test_all_match git checkout - &&
236
237         run_on_all ../edit-contents deep/newfile &&
238
239         test_all_match git status --porcelain=v2 -uno &&
240         test_all_match git status --porcelain=v2 &&
241         test_all_match git add . &&
242         test_all_match git status --porcelain=v2 &&
243         test_all_match git commit -m "add deep/newfile" &&
244
245         test_all_match git checkout HEAD~1 &&
246         test_all_match git checkout -
247 '
248
249 test_expect_success 'status/add: outside sparse cone' '
250         init_repos &&
251
252         # adding a "missing" file outside the cone should fail
253         test_sparse_match test_must_fail git add folder1/a &&
254
255         # folder1 is at HEAD, but outside the sparse cone
256         run_on_sparse mkdir folder1 &&
257         cp initial-repo/folder1/a sparse-checkout/folder1/a &&
258         cp initial-repo/folder1/a sparse-index/folder1/a &&
259
260         test_sparse_match git status &&
261
262         write_script edit-contents <<-\EOF &&
263         echo text >>$1
264         EOF
265         run_on_sparse ../edit-contents folder1/a &&
266         run_on_all ../edit-contents folder1/new &&
267
268         test_sparse_match git status --porcelain=v2 &&
269
270         # This "git add folder1/a" fails with a warning
271         # in the sparse repos, differing from the full
272         # repo. This is intentional.
273         test_sparse_match test_must_fail git add folder1/a &&
274         test_sparse_match test_must_fail git add --refresh folder1/a &&
275         test_all_match git status --porcelain=v2 &&
276
277         test_all_match git add . &&
278         test_all_match git status --porcelain=v2 &&
279         test_all_match git commit -m folder1/new &&
280
281         run_on_all ../edit-contents folder1/newer &&
282         test_all_match git add folder1/ &&
283         test_all_match git status --porcelain=v2 &&
284         test_all_match git commit -m folder1/newer
285 '
286
287 test_expect_success 'checkout and reset --hard' '
288         init_repos &&
289
290         test_all_match git checkout update-folder1 &&
291         test_all_match git status --porcelain=v2 &&
292
293         test_all_match git checkout update-deep &&
294         test_all_match git status --porcelain=v2 &&
295
296         test_all_match git checkout -b reset-test &&
297         test_all_match git reset --hard deepest &&
298         test_all_match git reset --hard update-folder1 &&
299         test_all_match git reset --hard update-folder2
300 '
301
302 test_expect_success 'diff --staged' '
303         init_repos &&
304
305         write_script edit-contents <<-\EOF &&
306         echo text >>README.md
307         EOF
308         run_on_all ../edit-contents &&
309
310         test_all_match git diff &&
311         test_all_match git diff --staged &&
312         test_all_match git add README.md &&
313         test_all_match git diff &&
314         test_all_match git diff --staged
315 '
316
317 test_expect_success 'diff with renames' '
318         init_repos &&
319
320         for branch in rename-out-to-out rename-out-to-in rename-in-to-out
321         do
322                 test_all_match git checkout rename-base &&
323                 test_all_match git checkout $branch -- . &&
324                 test_all_match git diff --staged --no-renames &&
325                 test_all_match git diff --staged --find-renames || return 1
326         done
327 '
328
329 test_expect_success 'log with pathspec outside sparse definition' '
330         init_repos &&
331
332         test_all_match git log -- a &&
333         test_all_match git log -- folder1/a &&
334         test_all_match git log -- folder2/a &&
335         test_all_match git log -- deep/a &&
336         test_all_match git log -- deep/deeper1/a &&
337         test_all_match git log -- deep/deeper1/deepest/a &&
338
339         test_all_match git checkout update-folder1 &&
340         test_all_match git log -- folder1/a
341 '
342
343 test_expect_success 'blame with pathspec inside sparse definition' '
344         init_repos &&
345
346         test_all_match git blame a &&
347         test_all_match git blame deep/a &&
348         test_all_match git blame deep/deeper1/a &&
349         test_all_match git blame deep/deeper1/deepest/a
350 '
351
352 # TODO: blame currently does not support blaming files outside of the
353 # sparse definition. It complains that the file doesn't exist locally.
354 test_expect_failure 'blame with pathspec outside sparse definition' '
355         init_repos &&
356
357         test_all_match git blame folder1/a &&
358         test_all_match git blame folder2/a &&
359         test_all_match git blame deep/deeper2/a &&
360         test_all_match git blame deep/deeper2/deepest/a
361 '
362
363 # TODO: reset currently does not behave as expected when in a
364 # sparse-checkout.
365 test_expect_failure 'checkout and reset (mixed)' '
366         init_repos &&
367
368         test_all_match git checkout -b reset-test update-deep &&
369         test_all_match git reset deepest &&
370         test_all_match git reset update-folder1 &&
371         test_all_match git reset update-folder2
372 '
373
374 # Ensure that sparse-index behaves identically to
375 # sparse-checkout with a full index.
376 test_expect_success 'checkout and reset (mixed) [sparse]' '
377         init_repos &&
378
379         test_sparse_match git checkout -b reset-test update-deep &&
380         test_sparse_match git reset deepest &&
381         test_sparse_match git reset update-folder1 &&
382         test_sparse_match git reset update-folder2
383 '
384
385 test_expect_success 'merge' '
386         init_repos &&
387
388         test_all_match git checkout -b merge update-deep &&
389         test_all_match git merge -m "folder1" update-folder1 &&
390         test_all_match git rev-parse HEAD^{tree} &&
391         test_all_match git merge -m "folder2" update-folder2 &&
392         test_all_match git rev-parse HEAD^{tree}
393 '
394
395 test_expect_success 'merge with outside renames' '
396         init_repos &&
397
398         for type in out-to-out out-to-in in-to-out
399         do
400                 test_all_match git reset --hard &&
401                 test_all_match git checkout -f -b merge-$type update-deep &&
402                 test_all_match git merge -m "$type" rename-$type &&
403                 test_all_match git rev-parse HEAD^{tree} || return 1
404         done
405 '
406
407 # Sparse-index fails to convert the index in the
408 # final 'git cherry-pick' command.
409 test_expect_success 'cherry-pick with conflicts' '
410         init_repos &&
411
412         write_script edit-conflict <<-\EOF &&
413         echo $1 >conflict
414         EOF
415
416         test_all_match git checkout -b to-cherry-pick &&
417         run_on_all ../edit-conflict ABC &&
418         test_all_match git add conflict &&
419         test_all_match git commit -m "conflict to pick" &&
420
421         test_all_match git checkout -B base HEAD~1 &&
422         run_on_all ../edit-conflict DEF &&
423         test_all_match git add conflict &&
424         test_all_match git commit -m "conflict in base" &&
425
426         test_all_match test_must_fail git cherry-pick to-cherry-pick
427 '
428
429 test_expect_success 'clean' '
430         init_repos &&
431
432         echo bogus >>.gitignore &&
433         run_on_all cp ../.gitignore . &&
434         test_all_match git add .gitignore &&
435         test_all_match git commit -m "ignore bogus files" &&
436
437         run_on_sparse mkdir folder1 &&
438         run_on_all touch folder1/bogus &&
439
440         test_all_match git status --porcelain=v2 &&
441         test_all_match git clean -f &&
442         test_all_match git status --porcelain=v2 &&
443         test_sparse_match ls &&
444         test_sparse_match ls folder1 &&
445
446         test_all_match git clean -xf &&
447         test_all_match git status --porcelain=v2 &&
448         test_sparse_match ls &&
449         test_sparse_match ls folder1 &&
450
451         test_all_match git clean -xdf &&
452         test_all_match git status --porcelain=v2 &&
453         test_sparse_match ls &&
454         test_sparse_match ls folder1 &&
455
456         test_sparse_match test_path_is_dir folder1
457 '
458
459 test_expect_success 'submodule handling' '
460         init_repos &&
461
462         test_all_match mkdir modules &&
463         test_all_match touch modules/a &&
464         test_all_match git add modules &&
465         test_all_match git commit -m "add modules directory" &&
466
467         run_on_all git submodule add "$(pwd)/initial-repo" modules/sub &&
468         test_all_match git commit -m "add submodule" &&
469
470         # having a submodule prevents "modules" from collapse
471         test-tool -C sparse-index read-cache --table >cache &&
472         grep "100644 blob .*    modules/a" cache &&
473         grep "160000 commit $(git -C initial-repo rev-parse HEAD)       modules/sub" cache
474 '
475
476 test_expect_success 'sparse-index is expanded and converted back' '
477         init_repos &&
478
479         GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
480                 git -C sparse-index -c core.fsmonitor="" reset --hard &&
481         test_region index convert_to_sparse trace2.txt &&
482         test_region index ensure_full_index trace2.txt
483 '
484
485 test_expect_success 'sparse-index is not expanded' '
486         init_repos &&
487
488         rm -f trace2.txt &&
489         echo >>sparse-index/untracked.txt &&
490         GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
491                 git -C sparse-index status &&
492         test_region ! index ensure_full_index trace2.txt
493 '
494
495 test_expect_success 'reset mixed and checkout orphan' '
496         init_repos &&
497
498         test_all_match git checkout rename-out-to-in &&
499         test_all_match git reset --mixed HEAD~1 &&
500         test_sparse_match test-tool read-cache --table --expand &&
501         test_all_match git status --porcelain=v2 &&
502         test_all_match git status --porcelain=v2 &&
503
504         # At this point, sparse-checkouts behave differently
505         # from the full-checkout.
506         test_sparse_match git checkout --orphan new-branch &&
507         test_sparse_match test-tool read-cache --table --expand &&
508         test_sparse_match git status --porcelain=v2 &&
509         test_sparse_match git status --porcelain=v2
510 '
511
512 test_expect_success 'add everything with deep new file' '
513         init_repos &&
514
515         run_on_sparse git sparse-checkout set deep/deeper1/deepest &&
516
517         run_on_all touch deep/deeper1/x &&
518         test_all_match git add . &&
519         test_all_match git status --porcelain=v2 &&
520         test_all_match git status --porcelain=v2
521 '
522
523 test_done