|  | #!/bin/sh | 
|  |  | 
|  | test_description='test untracked cache' | 
|  |  | 
|  | GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main | 
|  | export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME | 
|  |  | 
|  | . ./test-lib.sh | 
|  |  | 
|  | # On some filesystems (e.g. FreeBSD's ext2 and ufs) directory mtime | 
|  | # is updated lazily after contents in the directory changes, which | 
|  | # forces the untracked cache code to take the slow path.  A test | 
|  | # that wants to make sure that the fast path works correctly should | 
|  | # call this helper to make mtime of the containing directory in sync | 
|  | # with the reality before checking the fast path behaviour. | 
|  | # | 
|  | # See <20160803174522.5571-1-pclouds@gmail.com> if you want to know | 
|  | # more. | 
|  |  | 
|  | GIT_FORCE_UNTRACKED_CACHE=true | 
|  | export GIT_FORCE_UNTRACKED_CACHE | 
|  |  | 
|  | sync_mtime () { | 
|  | find . -type d -exec ls -ld {} + >/dev/null | 
|  | } | 
|  |  | 
|  | avoid_racy() { | 
|  | sleep 1 | 
|  | } | 
|  |  | 
|  | status_is_clean() { | 
|  | git status --porcelain >../status.actual && | 
|  | test_must_be_empty ../status.actual | 
|  | } | 
|  |  | 
|  | # Ignore_Untracked_Cache, abbreviated to 3 letters because then people can | 
|  | # compare commands side-by-side, e.g. | 
|  | #    iuc status --porcelain >expect && | 
|  | #    git status --porcelain >actual && | 
|  | #    test_cmp expect actual | 
|  | iuc () { | 
|  | git ls-files -s >../current-index-entries | 
|  | git ls-files -t | sed -ne s/^S.//p >../current-sparse-entries | 
|  |  | 
|  | GIT_INDEX_FILE=.git/tmp_index | 
|  | export GIT_INDEX_FILE | 
|  | git update-index --index-info <../current-index-entries | 
|  | git update-index --skip-worktree $(cat ../current-sparse-entries) | 
|  |  | 
|  | git -c core.untrackedCache=false "$@" | 
|  | ret=$? | 
|  |  | 
|  | rm ../current-index-entries | 
|  | rm $GIT_INDEX_FILE | 
|  | unset GIT_INDEX_FILE | 
|  |  | 
|  | return $ret | 
|  | } | 
|  |  | 
|  | get_relevant_traces () { | 
|  | # From the GIT_TRACE2_PERF data of the form | 
|  | #    $TIME $FILE:$LINE | d0 | main | data | r1 | ? | ? | read_directo | $RELEVANT_STAT | 
|  | # extract the $RELEVANT_STAT fields.  We don't care about region_enter | 
|  | # or region_leave, or stats for things outside read_directory. | 
|  | INPUT_FILE=$1 | 
|  | OUTPUT_FILE=$2 | 
|  | grep data.*read_directo $INPUT_FILE | | 
|  | cut -d "|" -f 9 | | 
|  | grep -v visited \ | 
|  | >"$OUTPUT_FILE" | 
|  | } | 
|  |  | 
|  |  | 
|  | test_lazy_prereq UNTRACKED_CACHE ' | 
|  | { git update-index --test-untracked-cache; ret=$?; } && | 
|  | test $ret -ne 1 | 
|  | ' | 
|  |  | 
|  | if ! test_have_prereq UNTRACKED_CACHE; then | 
|  | skip_all='This system does not support untracked cache' | 
|  | test_done | 
|  | fi | 
|  |  | 
|  | test_expect_success 'core.untrackedCache is unset' ' | 
|  | test_must_fail git config --get core.untrackedCache | 
|  | ' | 
|  |  | 
|  | test_expect_success 'setup' ' | 
|  | git init --template= worktree && | 
|  | cd worktree && | 
|  | mkdir done dtwo dthree && | 
|  | touch one two three done/one dtwo/two dthree/three && | 
|  | test-tool chmtime =-300 one two three done/one dtwo/two dthree/three && | 
|  | test-tool chmtime =-300 done dtwo dthree && | 
|  | test-tool chmtime =-300 . && | 
|  | git add one two done/one && | 
|  | mkdir .git/info && | 
|  | : >.git/info/exclude && | 
|  | git update-index --untracked-cache && | 
|  | test_oid_cache <<-EOF | 
|  | root sha1:e6fcc8f2ee31bae321d66afd183fcb7237afae6e | 
|  | root sha256:b90c672088c015b9c83876e919da311bad4cd39639fb139f988af6a11493b974 | 
|  |  | 
|  | exclude sha1:13263c0978fb9fad16b2d580fb800b6d811c3ff0 | 
|  | exclude sha256:fe4aaa1bbbbce4cb8f73426748a14c5ad6026b26f90505a0bf2494b165a5b76c | 
|  |  | 
|  | done sha1:1946f0437f90c5005533cbe1736a6451ca301714 | 
|  | done sha256:7f079501d79f665b3acc50f5e0e9e94509084d5032ac20113a37dd5029b757cc | 
|  | EOF | 
|  | ' | 
|  |  | 
|  | test_expect_success 'untracked cache is empty' ' | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | cat >../expect-empty <<EOF && | 
|  | info/exclude $ZERO_OID | 
|  | core.excludesfile $ZERO_OID | 
|  | exclude_per_dir .gitignore | 
|  | flags 00000006 | 
|  | EOF | 
|  | test_cmp ../expect-empty ../actual | 
|  | ' | 
|  |  | 
|  | cat >../status.expect <<EOF && | 
|  | A  done/one | 
|  | A  one | 
|  | A  two | 
|  | ?? dthree/ | 
|  | ?? dtwo/ | 
|  | ?? three | 
|  | EOF | 
|  |  | 
|  | cat >../dump.expect <<EOF && | 
|  | info/exclude $EMPTY_BLOB | 
|  | core.excludesfile $ZERO_OID | 
|  | exclude_per_dir .gitignore | 
|  | flags 00000006 | 
|  | / $ZERO_OID recurse valid | 
|  | dthree/ | 
|  | dtwo/ | 
|  | three | 
|  | /done/ $ZERO_OID recurse valid | 
|  | /dthree/ $ZERO_OID recurse check_only valid | 
|  | three | 
|  | /dtwo/ $ZERO_OID recurse check_only valid | 
|  | two | 
|  | EOF | 
|  |  | 
|  | test_expect_success 'status first time (empty cache)' ' | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status --porcelain >../actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | ....node-creation:3 | 
|  | ....gitignore-invalidation:1 | 
|  | ....directory-invalidation:0 | 
|  | ....opendir:4 | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  | ' | 
|  |  | 
|  | test_expect_success 'untracked cache after first status' ' | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../dump.expect ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'status second time (fully populated cache)' ' | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status --porcelain >../actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | ....node-creation:0 | 
|  | ....gitignore-invalidation:0 | 
|  | ....directory-invalidation:0 | 
|  | ....opendir:0 | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  | ' | 
|  |  | 
|  | test_expect_success 'untracked cache after second status' ' | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../dump.expect ../actual | 
|  | ' | 
|  |  | 
|  | cat >../status_uall.expect <<EOF && | 
|  | A  done/one | 
|  | A  one | 
|  | A  two | 
|  | ?? dthree/three | 
|  | ?? dtwo/two | 
|  | ?? three | 
|  | EOF | 
|  |  | 
|  | # Bypassing the untracked cache here is not desirable from an | 
|  | # end-user perspective, but is expected in the current design. | 
|  | # The untracked cache data stored for a -unormal run cannot be | 
|  | # correctly used in a -uall run - it would yield incorrect output. | 
|  | test_expect_success 'untracked cache is bypassed with -uall' ' | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status -uall --porcelain >../actual && | 
|  | iuc status -uall --porcelain >../status.iuc && | 
|  | test_cmp ../status_uall.expect ../status.iuc && | 
|  | test_cmp ../status_uall.expect ../actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  | ' | 
|  |  | 
|  | test_expect_success 'untracked cache remains after bypass' ' | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../dump.expect ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'if -uall is configured, untracked cache gets populated by default' ' | 
|  | test_config status.showuntrackedfiles all && | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status --porcelain >../actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | test_cmp ../status_uall.expect ../status.iuc && | 
|  | test_cmp ../status_uall.expect ../actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | ....node-creation:3 | 
|  | ....gitignore-invalidation:1 | 
|  | ....directory-invalidation:0 | 
|  | ....opendir:4 | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  | ' | 
|  |  | 
|  | cat >../dump_uall.expect <<EOF && | 
|  | info/exclude $EMPTY_BLOB | 
|  | core.excludesfile $ZERO_OID | 
|  | exclude_per_dir .gitignore | 
|  | flags 00000000 | 
|  | / $ZERO_OID recurse valid | 
|  | three | 
|  | /done/ $ZERO_OID recurse valid | 
|  | /dthree/ $ZERO_OID recurse valid | 
|  | three | 
|  | /dtwo/ $ZERO_OID recurse valid | 
|  | two | 
|  | EOF | 
|  |  | 
|  | test_expect_success 'if -uall was configured, untracked cache is populated' ' | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../dump_uall.expect ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'if -uall is configured, untracked cache is used by default' ' | 
|  | test_config status.showuntrackedfiles all && | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status --porcelain >../actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | test_cmp ../status_uall.expect ../status.iuc && | 
|  | test_cmp ../status_uall.expect ../actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | ....node-creation:0 | 
|  | ....gitignore-invalidation:0 | 
|  | ....directory-invalidation:0 | 
|  | ....opendir:0 | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  | ' | 
|  |  | 
|  | # Bypassing the untracked cache here is not desirable from an | 
|  | # end-user perspective, but is expected in the current design. | 
|  | # The untracked cache data stored for a -all run cannot be | 
|  | # correctly used in a -unormal run - it would yield incorrect | 
|  | # output. | 
|  | test_expect_success 'if -uall is configured, untracked cache is bypassed with -unormal' ' | 
|  | test_config status.showuntrackedfiles all && | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status -unormal --porcelain >../actual && | 
|  | iuc status -unormal --porcelain >../status.iuc && | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  | ' | 
|  |  | 
|  | test_expect_success 'repopulate untracked cache for -unormal' ' | 
|  | git status --porcelain | 
|  | ' | 
|  |  | 
|  | test_expect_success 'modify in root directory, one dir invalidation' ' | 
|  | : >four && | 
|  | test-tool chmtime =-240 four && | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status --porcelain >../actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | cat >../status.expect <<EOF && | 
|  | A  done/one | 
|  | A  one | 
|  | A  two | 
|  | ?? dthree/ | 
|  | ?? dtwo/ | 
|  | ?? four | 
|  | ?? three | 
|  | EOF | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | ....node-creation:0 | 
|  | ....gitignore-invalidation:0 | 
|  | ....directory-invalidation:1 | 
|  | ....opendir:1 | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  |  | 
|  | ' | 
|  |  | 
|  | test_expect_success 'verify untracked cache dump' ' | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | cat >../expect <<EOF && | 
|  | info/exclude $EMPTY_BLOB | 
|  | core.excludesfile $ZERO_OID | 
|  | exclude_per_dir .gitignore | 
|  | flags 00000006 | 
|  | / $ZERO_OID recurse valid | 
|  | dthree/ | 
|  | dtwo/ | 
|  | four | 
|  | three | 
|  | /done/ $ZERO_OID recurse valid | 
|  | /dthree/ $ZERO_OID recurse check_only valid | 
|  | three | 
|  | /dtwo/ $ZERO_OID recurse check_only valid | 
|  | two | 
|  | EOF | 
|  | test_cmp ../expect ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'new .gitignore invalidates recursively' ' | 
|  | echo four >.gitignore && | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status --porcelain >../actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | cat >../status.expect <<EOF && | 
|  | A  done/one | 
|  | A  one | 
|  | A  two | 
|  | ?? .gitignore | 
|  | ?? dthree/ | 
|  | ?? dtwo/ | 
|  | ?? three | 
|  | EOF | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | ....node-creation:0 | 
|  | ....gitignore-invalidation:1 | 
|  | ....directory-invalidation:1 | 
|  | ....opendir:4 | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  |  | 
|  | ' | 
|  |  | 
|  | test_expect_success 'verify untracked cache dump' ' | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | cat >../expect <<EOF && | 
|  | info/exclude $EMPTY_BLOB | 
|  | core.excludesfile $ZERO_OID | 
|  | exclude_per_dir .gitignore | 
|  | flags 00000006 | 
|  | / $(test_oid root) recurse valid | 
|  | .gitignore | 
|  | dthree/ | 
|  | dtwo/ | 
|  | three | 
|  | /done/ $ZERO_OID recurse valid | 
|  | /dthree/ $ZERO_OID recurse check_only valid | 
|  | three | 
|  | /dtwo/ $ZERO_OID recurse check_only valid | 
|  | two | 
|  | EOF | 
|  | test_cmp ../expect ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'new info/exclude invalidates everything' ' | 
|  | echo three >>.git/info/exclude && | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status --porcelain >../actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | cat >../status.expect <<EOF && | 
|  | A  done/one | 
|  | A  one | 
|  | A  two | 
|  | ?? .gitignore | 
|  | ?? dtwo/ | 
|  | EOF | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | ....node-creation:0 | 
|  | ....gitignore-invalidation:1 | 
|  | ....directory-invalidation:0 | 
|  | ....opendir:4 | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  | ' | 
|  |  | 
|  | test_expect_success 'verify untracked cache dump' ' | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | cat >../expect <<EOF && | 
|  | info/exclude $(test_oid exclude) | 
|  | core.excludesfile $ZERO_OID | 
|  | exclude_per_dir .gitignore | 
|  | flags 00000006 | 
|  | / $(test_oid root) recurse valid | 
|  | .gitignore | 
|  | dtwo/ | 
|  | /done/ $ZERO_OID recurse valid | 
|  | /dthree/ $ZERO_OID recurse check_only valid | 
|  | /dtwo/ $ZERO_OID recurse check_only valid | 
|  | two | 
|  | EOF | 
|  | test_cmp ../expect ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'move two from tracked to untracked' ' | 
|  | git rm --cached two && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | cat >../expect <<EOF && | 
|  | info/exclude $(test_oid exclude) | 
|  | core.excludesfile $ZERO_OID | 
|  | exclude_per_dir .gitignore | 
|  | flags 00000006 | 
|  | / $(test_oid root) recurse | 
|  | /done/ $ZERO_OID recurse valid | 
|  | /dthree/ $ZERO_OID recurse check_only valid | 
|  | /dtwo/ $ZERO_OID recurse check_only valid | 
|  | two | 
|  | EOF | 
|  | test_cmp ../expect ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'status after the move' ' | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status --porcelain >../actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | cat >../status.expect <<EOF && | 
|  | A  done/one | 
|  | A  one | 
|  | ?? .gitignore | 
|  | ?? dtwo/ | 
|  | ?? two | 
|  | EOF | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | ....node-creation:0 | 
|  | ....gitignore-invalidation:0 | 
|  | ....directory-invalidation:0 | 
|  | ....opendir:1 | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  | ' | 
|  |  | 
|  | test_expect_success 'verify untracked cache dump' ' | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | cat >../expect <<EOF && | 
|  | info/exclude $(test_oid exclude) | 
|  | core.excludesfile $ZERO_OID | 
|  | exclude_per_dir .gitignore | 
|  | flags 00000006 | 
|  | / $(test_oid root) recurse valid | 
|  | .gitignore | 
|  | dtwo/ | 
|  | two | 
|  | /done/ $ZERO_OID recurse valid | 
|  | /dthree/ $ZERO_OID recurse check_only valid | 
|  | /dtwo/ $ZERO_OID recurse check_only valid | 
|  | two | 
|  | EOF | 
|  | test_cmp ../expect ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'move two from untracked to tracked' ' | 
|  | git add two && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | cat >../expect <<EOF && | 
|  | info/exclude $(test_oid exclude) | 
|  | core.excludesfile $ZERO_OID | 
|  | exclude_per_dir .gitignore | 
|  | flags 00000006 | 
|  | / $(test_oid root) recurse | 
|  | /done/ $ZERO_OID recurse valid | 
|  | /dthree/ $ZERO_OID recurse check_only valid | 
|  | /dtwo/ $ZERO_OID recurse check_only valid | 
|  | two | 
|  | EOF | 
|  | test_cmp ../expect ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'status after the move' ' | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status --porcelain >../actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | cat >../status.expect <<EOF && | 
|  | A  done/one | 
|  | A  one | 
|  | A  two | 
|  | ?? .gitignore | 
|  | ?? dtwo/ | 
|  | EOF | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | ....node-creation:0 | 
|  | ....gitignore-invalidation:0 | 
|  | ....directory-invalidation:0 | 
|  | ....opendir:1 | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  | ' | 
|  |  | 
|  | test_expect_success 'verify untracked cache dump' ' | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | cat >../expect <<EOF && | 
|  | info/exclude $(test_oid exclude) | 
|  | core.excludesfile $ZERO_OID | 
|  | exclude_per_dir .gitignore | 
|  | flags 00000006 | 
|  | / $(test_oid root) recurse valid | 
|  | .gitignore | 
|  | dtwo/ | 
|  | /done/ $ZERO_OID recurse valid | 
|  | /dthree/ $ZERO_OID recurse check_only valid | 
|  | /dtwo/ $ZERO_OID recurse check_only valid | 
|  | two | 
|  | EOF | 
|  | test_cmp ../expect ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'set up for sparse checkout testing' ' | 
|  | echo two >done/.gitignore && | 
|  | echo three >>done/.gitignore && | 
|  | echo two >done/two && | 
|  | git add -f done/two done/.gitignore && | 
|  | git commit -m "first commit" | 
|  | ' | 
|  |  | 
|  | test_expect_success 'status after commit' ' | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status --porcelain >../actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | cat >../status.expect <<EOF && | 
|  | ?? .gitignore | 
|  | ?? dtwo/ | 
|  | EOF | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | ....node-creation:0 | 
|  | ....gitignore-invalidation:0 | 
|  | ....directory-invalidation:0 | 
|  | ....opendir:2 | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  | ' | 
|  |  | 
|  | test_expect_success 'untracked cache correct after commit' ' | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | cat >../expect <<EOF && | 
|  | info/exclude $(test_oid exclude) | 
|  | core.excludesfile $ZERO_OID | 
|  | exclude_per_dir .gitignore | 
|  | flags 00000006 | 
|  | / $(test_oid root) recurse valid | 
|  | .gitignore | 
|  | dtwo/ | 
|  | /done/ $ZERO_OID recurse valid | 
|  | /dthree/ $ZERO_OID recurse check_only valid | 
|  | /dtwo/ $ZERO_OID recurse check_only valid | 
|  | two | 
|  | EOF | 
|  | test_cmp ../expect ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'set up sparse checkout' ' | 
|  | echo "done/[a-z]*" >.git/info/sparse-checkout && | 
|  | test_config core.sparsecheckout true && | 
|  | git checkout main && | 
|  | git update-index --force-untracked-cache && | 
|  | git status --porcelain >/dev/null && # prime the cache | 
|  | test_path_is_missing done/.gitignore && | 
|  | test_path_is_file done/one | 
|  | ' | 
|  |  | 
|  | test_expect_success 'create/modify files, some of which are gitignored' ' | 
|  | echo two bis >done/two && | 
|  | echo three >done/three && # three is gitignored | 
|  | echo four >done/four && # four is gitignored at a higher level | 
|  | echo five >done/five && # five is not gitignored | 
|  | test-tool chmtime =-180 done/two done/three done/four done/five done && | 
|  | # we need to ensure that the root dir is touched (in the past); | 
|  | test-tool chmtime =-180 . && | 
|  | sync_mtime | 
|  | ' | 
|  |  | 
|  | test_expect_success 'test sparse status with untracked cache' ' | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status --porcelain >../status.actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | cat >../status.expect <<EOF && | 
|  | M done/two | 
|  | ?? .gitignore | 
|  | ?? done/five | 
|  | ?? dtwo/ | 
|  | EOF | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../status.actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | ....node-creation:0 | 
|  | ....gitignore-invalidation:1 | 
|  | ....directory-invalidation:2 | 
|  | ....opendir:2 | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  | ' | 
|  |  | 
|  | test_expect_success 'untracked cache correct after status' ' | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | cat >../expect <<EOF && | 
|  | info/exclude $(test_oid exclude) | 
|  | core.excludesfile $ZERO_OID | 
|  | exclude_per_dir .gitignore | 
|  | flags 00000006 | 
|  | / $(test_oid root) recurse valid | 
|  | .gitignore | 
|  | dtwo/ | 
|  | /done/ $(test_oid done) recurse valid | 
|  | five | 
|  | /dthree/ $ZERO_OID recurse check_only valid | 
|  | /dtwo/ $ZERO_OID recurse check_only valid | 
|  | two | 
|  | EOF | 
|  | test_cmp ../expect ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'test sparse status again with untracked cache' ' | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status --porcelain >../status.actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | cat >../status.expect <<EOF && | 
|  | M done/two | 
|  | ?? .gitignore | 
|  | ?? done/five | 
|  | ?? dtwo/ | 
|  | EOF | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../status.actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | ....node-creation:0 | 
|  | ....gitignore-invalidation:0 | 
|  | ....directory-invalidation:0 | 
|  | ....opendir:0 | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  | ' | 
|  |  | 
|  | test_expect_success 'set up for test of subdir and sparse checkouts' ' | 
|  | mkdir done/sub && | 
|  | mkdir done/sub/sub && | 
|  | echo "sub" > done/sub/sub/file && | 
|  | test-tool chmtime =-120 done/sub/sub/file done/sub/sub done/sub done | 
|  | ' | 
|  |  | 
|  | test_expect_success 'test sparse status with untracked cache and subdir' ' | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status --porcelain >../status.actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | cat >../status.expect <<EOF && | 
|  | M done/two | 
|  | ?? .gitignore | 
|  | ?? done/five | 
|  | ?? done/sub/ | 
|  | ?? dtwo/ | 
|  | EOF | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../status.actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | ....node-creation:2 | 
|  | ....gitignore-invalidation:0 | 
|  | ....directory-invalidation:1 | 
|  | ....opendir:3 | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  | ' | 
|  |  | 
|  | test_expect_success 'verify untracked cache dump (sparse/subdirs)' ' | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | cat >../expect-from-test-dump <<EOF && | 
|  | info/exclude $(test_oid exclude) | 
|  | core.excludesfile $ZERO_OID | 
|  | exclude_per_dir .gitignore | 
|  | flags 00000006 | 
|  | / $(test_oid root) recurse valid | 
|  | .gitignore | 
|  | dtwo/ | 
|  | /done/ $(test_oid done) recurse valid | 
|  | five | 
|  | sub/ | 
|  | /done/sub/ $ZERO_OID recurse check_only valid | 
|  | sub/ | 
|  | /done/sub/sub/ $ZERO_OID recurse check_only valid | 
|  | file | 
|  | /dthree/ $ZERO_OID recurse check_only valid | 
|  | /dtwo/ $ZERO_OID recurse check_only valid | 
|  | two | 
|  | EOF | 
|  | test_cmp ../expect-from-test-dump ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'test sparse status again with untracked cache and subdir' ' | 
|  | : >../trace.output && | 
|  | GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ | 
|  | git status --porcelain >../status.actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../status.actual && | 
|  | get_relevant_traces ../trace.output ../trace.relevant && | 
|  | cat >../trace.expect <<EOF && | 
|  | ....path: | 
|  | ....node-creation:0 | 
|  | ....gitignore-invalidation:0 | 
|  | ....directory-invalidation:0 | 
|  | ....opendir:0 | 
|  | EOF | 
|  | test_cmp ../trace.expect ../trace.relevant | 
|  | ' | 
|  |  | 
|  | test_expect_success 'move entry in subdir from untracked to cached' ' | 
|  | git add dtwo/two && | 
|  | git status --porcelain >../status.actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | cat >../status.expect <<EOF && | 
|  | M done/two | 
|  | A  dtwo/two | 
|  | ?? .gitignore | 
|  | ?? done/five | 
|  | ?? done/sub/ | 
|  | EOF | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../status.actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'move entry in subdir from cached to untracked' ' | 
|  | git rm --cached dtwo/two && | 
|  | git status --porcelain >../status.actual && | 
|  | iuc status --porcelain >../status.iuc && | 
|  | cat >../status.expect <<EOF && | 
|  | M done/two | 
|  | ?? .gitignore | 
|  | ?? done/five | 
|  | ?? done/sub/ | 
|  | ?? dtwo/ | 
|  | EOF | 
|  | test_cmp ../status.expect ../status.iuc && | 
|  | test_cmp ../status.expect ../status.actual | 
|  | ' | 
|  |  | 
|  | test_expect_success '--no-untracked-cache removes the cache' ' | 
|  | git update-index --no-untracked-cache && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | echo "no untracked cache" >../expect-no-uc && | 
|  | test_cmp ../expect-no-uc ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'git status does not change anything' ' | 
|  | git status && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../expect-no-uc ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'setting core.untrackedCache to true and using git status creates the cache' ' | 
|  | git config core.untrackedCache true && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../expect-no-uc ../actual && | 
|  | git status && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../expect-from-test-dump ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'using --no-untracked-cache does not fail when core.untrackedCache is true' ' | 
|  | git update-index --no-untracked-cache && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../expect-no-uc ../actual && | 
|  | git update-index --untracked-cache && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../expect-empty ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'setting core.untrackedCache to false and using git status removes the cache' ' | 
|  | git config core.untrackedCache false && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../expect-empty ../actual && | 
|  | git status && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../expect-no-uc ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'using --untracked-cache does not fail when core.untrackedCache is false' ' | 
|  | git update-index --untracked-cache && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../expect-empty ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'setting core.untrackedCache to keep' ' | 
|  | git config core.untrackedCache keep && | 
|  | git update-index --untracked-cache && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../expect-empty ../actual && | 
|  | git status && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../expect-from-test-dump ../actual && | 
|  | git update-index --no-untracked-cache && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../expect-no-uc ../actual && | 
|  | git update-index --force-untracked-cache && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../expect-empty ../actual && | 
|  | git status && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | test_cmp ../expect-from-test-dump ../actual | 
|  | ' | 
|  |  | 
|  | test_expect_success 'test ident field is working' ' | 
|  | mkdir ../other_worktree && | 
|  | cp -R done dthree dtwo four three ../other_worktree && | 
|  | GIT_WORK_TREE=../other_worktree git status 2>../err && | 
|  | echo "warning: untracked cache is disabled on this system or location" >../expect && | 
|  | test_cmp ../expect ../err | 
|  | ' | 
|  |  | 
|  | test_expect_success 'untracked cache survives a checkout' ' | 
|  | git commit --allow-empty -m empty && | 
|  | test-tool dump-untracked-cache >../before && | 
|  | test_when_finished  "git checkout main" && | 
|  | git checkout -b other_branch && | 
|  | test-tool dump-untracked-cache >../after && | 
|  | test_cmp ../before ../after && | 
|  | test_commit test && | 
|  | test-tool dump-untracked-cache >../before && | 
|  | git checkout main && | 
|  | test-tool dump-untracked-cache >../after && | 
|  | test_cmp ../before ../after | 
|  | ' | 
|  |  | 
|  | test_expect_success 'untracked cache survives a commit' ' | 
|  | test-tool dump-untracked-cache >../before && | 
|  | git add done/two && | 
|  | git commit -m commit && | 
|  | test-tool dump-untracked-cache >../after && | 
|  | test_cmp ../before ../after | 
|  | ' | 
|  |  | 
|  | test_expect_success 'teardown worktree' ' | 
|  | cd .. | 
|  | ' | 
|  |  | 
|  | test_expect_success SYMLINKS 'setup worktree for symlink test' ' | 
|  | git init worktree-symlink && | 
|  | cd worktree-symlink && | 
|  | git config core.untrackedCache true && | 
|  | mkdir one two && | 
|  | touch one/file two/file && | 
|  | git add one/file two/file && | 
|  | git commit -m"first commit" && | 
|  | git rm -rf one && | 
|  | ln -s two one && | 
|  | git add one && | 
|  | git commit -m"second commit" | 
|  | ' | 
|  |  | 
|  | test_expect_success SYMLINKS '"status" after symlink replacement should be clean with UC=true' ' | 
|  | git checkout HEAD~ && | 
|  | status_is_clean && | 
|  | status_is_clean && | 
|  | git checkout main && | 
|  | avoid_racy && | 
|  | status_is_clean && | 
|  | status_is_clean | 
|  | ' | 
|  |  | 
|  | test_expect_success SYMLINKS '"status" after symlink replacement should be clean with UC=false' ' | 
|  | git config core.untrackedCache false && | 
|  | git checkout HEAD~ && | 
|  | status_is_clean && | 
|  | status_is_clean && | 
|  | git checkout main && | 
|  | avoid_racy && | 
|  | status_is_clean && | 
|  | status_is_clean | 
|  | ' | 
|  |  | 
|  | test_expect_success 'setup worktree for non-symlink test' ' | 
|  | git init worktree-non-symlink && | 
|  | cd worktree-non-symlink && | 
|  | git config core.untrackedCache true && | 
|  | mkdir one two && | 
|  | touch one/file two/file && | 
|  | git add one/file two/file && | 
|  | git commit -m"first commit" && | 
|  | git rm -rf one && | 
|  | cp two/file one && | 
|  | git add one && | 
|  | git commit -m"second commit" | 
|  | ' | 
|  |  | 
|  | test_expect_success '"status" after file replacement should be clean with UC=true' ' | 
|  | git checkout HEAD~ && | 
|  | status_is_clean && | 
|  | status_is_clean && | 
|  | git checkout main && | 
|  | avoid_racy && | 
|  | status_is_clean && | 
|  | test-tool dump-untracked-cache >../actual && | 
|  | grep -F "recurse valid" ../actual >../actual.grep && | 
|  | cat >../expect.grep <<EOF && | 
|  | / $ZERO_OID recurse valid | 
|  | /two/ $ZERO_OID recurse valid | 
|  | EOF | 
|  | status_is_clean && | 
|  | test_cmp ../expect.grep ../actual.grep | 
|  | ' | 
|  |  | 
|  | test_expect_success '"status" after file replacement should be clean with UC=false' ' | 
|  | git config core.untrackedCache false && | 
|  | git checkout HEAD~ && | 
|  | status_is_clean && | 
|  | status_is_clean && | 
|  | git checkout main && | 
|  | avoid_racy && | 
|  | status_is_clean && | 
|  | status_is_clean | 
|  | ' | 
|  |  | 
|  | test_expect_success 'empty repo (no index) and core.untrackedCache' ' | 
|  | git init emptyrepo && | 
|  | git -C emptyrepo -c core.untrackedCache=true write-tree | 
|  | ' | 
|  |  | 
|  | test_done |