{"thread":{"id":"54525","subject":"[PATCH v3 19/19] ci: run test round with parallel-checkout enabled","startedAt":"2020-10-29T02:16:41Z","lastAt":"2021-03-23T17:35:58Z","messageCount":93,"participants":["Matheus Tavares","Junio C Hamano","Jeff Hostetler","Matheus Tavares Bernardino","Christian Couder","Emily Shaffer"],"isPatch":true,"patchVersion":3,"patchTotal":19},"messages":[{"id":"408618","messageId":"641c61f9b65ee99be1f8ed3031b53ece90ec187c.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 19/19] ci: run test round with parallel-checkout enabled","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:56Z","receivedAt":"2020-10-29T02:16:41Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"We already have tests for the basic parallel-checkout operations. But\nthis code can also run in other commands, such as git-read-tree and\ngit-sparse-checkout, which are currently not tested with multiple\nworkers. To promote a wider test coverage without duplicating tests:\n\n1. Add the GIT_TEST_CHECKOUT_WORKERS environment variable, to optionally\n   force parallel-checkout execution during the whole test suite.\n\n2. Include this variable in the second test round of the linux-gcc job\n   of our ci scripts. This round runs `make test` again with some\n   optional GIT_TEST_* variables enabled, so there is no additional\n   overhead in exercising the parallel-checkout code here.\n\nNote: the specific parallel-checkout tests t208* cannot be used in\ncombination with GIT_TEST_CHECKOUT_WORKERS as they need to set and check\nthe number of workers by themselves. So skip those tests when this flag\nis set.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n ci/run-build-and-tests.sh  |  1 +\n parallel-checkout.c        | 14 ++++++++++++++\n t/README                   |  4 ++++\n t/lib-parallel-checkout.sh |  6 ++++++\n 4 files changed, 25 insertions(+)\n\ndiff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh\nindex 6c27b886b8..aa32ddc361 100755\n--- a/ci/run-build-and-tests.sh\n+++ b/ci/run-build-and-tests.sh\n@@ -22,6 +22,7 @@ linux-gcc)\n \texport GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=1\n \texport GIT_TEST_MULTI_PACK_INDEX=1\n \texport GIT_TEST_ADD_I_USE_BUILTIN=1\n+\texport GIT_TEST_CHECKOUT_WORKERS=2\n \tmake test\n \t;;\n linux-clang)\ndiff --git a/parallel-checkout.c b/parallel-checkout.c\nindex c5c449d224..7482447f2d 100644\n--- a/parallel-checkout.c\n+++ b/parallel-checkout.c\n@@ -32,6 +32,20 @@ enum pc_status parallel_checkout_status(void)\n \n void get_parallel_checkout_configs(int *num_workers, int *threshold)\n {\n+\tchar *env_workers = getenv(\"GIT_TEST_CHECKOUT_WORKERS\");\n+\n+\tif (env_workers && *env_workers) {\n+\t\tif (strtol_i(env_workers, 10, num_workers)) {\n+\t\t\tdie(\"invalid value for GIT_TEST_CHECKOUT_WORKERS: '%s'\",\n+\t\t\t    env_workers);\n+\t\t}\n+\t\tif (*num_workers < 1)\n+\t\t\t*num_workers = online_cpus();\n+\n+\t\t*threshold = 0;\n+\t\treturn;\n+\t}\n+\n \tif (git_config_get_int(\"checkout.workers\", num_workers))\n \t\t*num_workers = 1;\n \telse if (*num_workers < 1)\ndiff --git a/t/README b/t/README\nindex 2adaf7c2d2..cd1b15c55a 100644\n--- a/t/README\n+++ b/t/README\n@@ -425,6 +425,10 @@ GIT_TEST_DEFAULT_HASH=<hash-algo> specifies which hash algorithm to\n use in the test scripts. Recognized values for <hash-algo> are \"sha1\"\n and \"sha256\".\n \n+GIT_TEST_CHECKOUT_WORKERS=<n> overrides the 'checkout.workers' setting\n+to <n> and 'checkout.thresholdForParallelism' to 0, forcing the\n+execution of the parallel-checkout code.\n+\n Naming Tests\n ------------\n \ndiff --git a/t/lib-parallel-checkout.sh b/t/lib-parallel-checkout.sh\nindex e62a433eb1..7b454da375 100644\n--- a/t/lib-parallel-checkout.sh\n+++ b/t/lib-parallel-checkout.sh\n@@ -1,5 +1,11 @@\n # Helpers for t208* tests\n \n+if ! test -z \"$GIT_TEST_CHECKOUT_WORKERS\"\n+then\n+\tskip_all=\"skipping test, GIT_TEST_CHECKOUT_WORKERS is set\"\n+\ttest_done\n+fi\n+\n # Runs `git -c checkout.workers=$1 -c checkout.thesholdForParallelism=$2 ${@:4}`\n # and checks that the number of workers spawned is equal to $3.\n #\n-- \n2.28.0\n\n"},{"id":"408619","messageId":"b26f676caecba8bbecb21833c3e7ce988025100d.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 18/19] parallel-checkout: add tests related to .gitattributes","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:55Z","receivedAt":"2020-10-29T02:16:42Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Add tests to confirm that `struct conv_attrs` data is correctly passed\nfrom the main process to the workers, and that they properly smudge\nfiles before writing to the working tree. Also check that\nnon-parallel-eligible entries, such as regular files that require\nexternal filters, are correctly smudge and written when\nparallel-checkout is enabled.\n\nNote: to avoid repeating code, some helper functions are extracted from\nt0028 into a common lib file.\n\nOriginal-patch-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n t/lib-encoding.sh                       |  25 ++++\n t/t0028-working-tree-encoding.sh        |  25 +---\n t/t2082-parallel-checkout-attributes.sh | 174 ++++++++++++++++++++++++\n 3 files changed, 200 insertions(+), 24 deletions(-)\n create mode 100644 t/lib-encoding.sh\n create mode 100755 t/t2082-parallel-checkout-attributes.sh\n\ndiff --git a/t/lib-encoding.sh b/t/lib-encoding.sh\nnew file mode 100644\nindex 0000000000..c52ffbbed5\n--- /dev/null\n+++ b/t/lib-encoding.sh\n@@ -0,0 +1,25 @@\n+# Encoding helpers used by t0028 and t2082\n+\n+test_lazy_prereq NO_UTF16_BOM '\n+\ttest $(printf abc | iconv -f UTF-8 -t UTF-16 | wc -c) = 6\n+'\n+\n+test_lazy_prereq NO_UTF32_BOM '\n+\ttest $(printf abc | iconv -f UTF-8 -t UTF-32 | wc -c) = 12\n+'\n+\n+write_utf16 () {\n+\tif test_have_prereq NO_UTF16_BOM\n+\tthen\n+\t\tprintf '\\376\\377'\n+\tfi &&\n+\ticonv -f UTF-8 -t UTF-16\n+}\n+\n+write_utf32 () {\n+\tif test_have_prereq NO_UTF32_BOM\n+\tthen\n+\t\tprintf '\\0\\0\\376\\377'\n+\tfi &&\n+\ticonv -f UTF-8 -t UTF-32\n+}\ndiff --git a/t/t0028-working-tree-encoding.sh b/t/t0028-working-tree-encoding.sh\nindex bfc4fb9af5..4fffc3a639 100755\n--- a/t/t0028-working-tree-encoding.sh\n+++ b/t/t0028-working-tree-encoding.sh\n@@ -3,33 +3,10 @@\n test_description='working-tree-encoding conversion via gitattributes'\n \n . ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-encoding.sh\"\n \n GIT_TRACE_WORKING_TREE_ENCODING=1 && export GIT_TRACE_WORKING_TREE_ENCODING\n \n-test_lazy_prereq NO_UTF16_BOM '\n-\ttest $(printf abc | iconv -f UTF-8 -t UTF-16 | wc -c) = 6\n-'\n-\n-test_lazy_prereq NO_UTF32_BOM '\n-\ttest $(printf abc | iconv -f UTF-8 -t UTF-32 | wc -c) = 12\n-'\n-\n-write_utf16 () {\n-\tif test_have_prereq NO_UTF16_BOM\n-\tthen\n-\t\tprintf '\\376\\377'\n-\tfi &&\n-\ticonv -f UTF-8 -t UTF-16\n-}\n-\n-write_utf32 () {\n-\tif test_have_prereq NO_UTF32_BOM\n-\tthen\n-\t\tprintf '\\0\\0\\376\\377'\n-\tfi &&\n-\ticonv -f UTF-8 -t UTF-32\n-}\n-\n test_expect_success 'setup test files' '\n \tgit config core.eol lf &&\n \ndiff --git a/t/t2082-parallel-checkout-attributes.sh b/t/t2082-parallel-checkout-attributes.sh\nnew file mode 100755\nindex 0000000000..6800574588\n--- /dev/null\n+++ b/t/t2082-parallel-checkout-attributes.sh\n@@ -0,0 +1,174 @@\n+#!/bin/sh\n+\n+test_description='parallel-checkout: attributes\n+\n+Verify that parallel-checkout correctly creates files that require\n+conversions, as specified in .gitattributes. The main point here is\n+to check that the conv_attr data is correctly sent to the workers\n+and that it contains sufficient information to smudge files\n+properly (without access to the index or attribute stack).\n+'\n+\n+TEST_NO_CREATE_REPO=1\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-parallel-checkout.sh\"\n+. \"$TEST_DIRECTORY/lib-encoding.sh\"\n+\n+test_expect_success 'parallel-checkout with ident' '\n+\tgit init ident &&\n+\t(\n+\t\tcd ident &&\n+\t\techo \"A ident\" >.gitattributes &&\n+\t\techo \"\\$Id\\$\" >A &&\n+\t\techo \"\\$Id\\$\" >B &&\n+\t\tgit add -A &&\n+\t\tgit commit -m id &&\n+\n+\t\trm A B &&\n+\t\tgit_pc 2 0 2 reset --hard &&\n+\t\thexsz=$(test_oid hexsz) &&\n+\t\tgrep -E \"\\\\\\$Id: [0-9a-f]{$hexsz} \\\\\\$\" A &&\n+\t\tgrep \"\\\\\\$Id\\\\\\$\" B\n+\t)\n+'\n+\n+test_expect_success 'parallel-checkout with re-encoding' '\n+\tgit init encoding &&\n+\t(\n+\t\tcd encoding &&\n+\t\techo text >utf8-text &&\n+\t\tcat utf8-text | write_utf16 >utf16-text &&\n+\n+\t\techo \"A working-tree-encoding=UTF-16\" >.gitattributes &&\n+\t\tcp utf16-text A &&\n+\t\tcp utf16-text B &&\n+\t\tgit add A B .gitattributes &&\n+\t\tgit commit -m encoding &&\n+\n+\t\t# Check that A (and only A) is stored in UTF-8\n+\t\tgit cat-file -p :A >A.internal &&\n+\t\ttest_cmp_bin utf8-text A.internal &&\n+\t\tgit cat-file -p :B >B.internal &&\n+\t\ttest_cmp_bin utf16-text B.internal &&\n+\n+\t\t# Check that A is re-encoded during checkout\n+\t\trm A B &&\n+\t\tgit_pc 2 0 2 checkout A B &&\n+\t\ttest_cmp_bin utf16-text A\n+\t)\n+'\n+\n+test_expect_success 'parallel-checkout with eol conversions' '\n+\tgit init eol &&\n+\t(\n+\t\tcd eol &&\n+\t\tgit config core.autocrlf false &&\n+\t\tprintf \"multi\\r\\nline\\r\\ntext\" >crlf-text &&\n+\t\tprintf \"multi\\nline\\ntext\" >lf-text &&\n+\n+\t\techo \"A text eol=crlf\" >.gitattributes &&\n+\t\techo \"B -text\" >>.gitattributes &&\n+\t\tcp crlf-text A &&\n+\t\tcp crlf-text B &&\n+\t\tgit add A B .gitattributes &&\n+\t\tgit commit -m eol &&\n+\n+\t\t# Check that A (and only A) is stored with LF format\n+\t\tgit cat-file -p :A >A.internal &&\n+\t\ttest_cmp_bin lf-text A.internal &&\n+\t\tgit cat-file -p :B >B.internal &&\n+\t\ttest_cmp_bin crlf-text B.internal &&\n+\n+\t\t# Check that A is converted to CRLF during checkout\n+\t\trm A B &&\n+\t\tgit_pc 2 0 2 checkout A B &&\n+\t\ttest_cmp_bin crlf-text A\n+\t)\n+'\n+\n+test_cmp_str()\n+{\n+\techo \"$1\" >tmp &&\n+\ttest_cmp tmp \"$2\"\n+}\n+\n+# Entries that require an external filter are not eligible for parallel\n+# checkout. Check that both the parallel-eligible and non-eligible entries are\n+# properly writen in a single checkout process.\n+#\n+test_expect_success 'parallel-checkout and external filter' '\n+\tgit init filter &&\n+\t(\n+\t\tcd filter &&\n+\t\tgit config filter.x2y.clean \"tr x y\" &&\n+\t\tgit config filter.x2y.smudge \"tr y x\" &&\n+\t\tgit config filter.x2y.required true &&\n+\n+\t\techo \"A filter=x2y\" >.gitattributes &&\n+\t\techo x >A &&\n+\t\techo x >B &&\n+\t\techo x >C &&\n+\t\tgit add -A &&\n+\t\tgit commit -m filter &&\n+\n+\t\t# Check that A (and only A) was cleaned\n+\t\tgit cat-file -p :A >A.internal &&\n+\t\ttest_cmp_str y A.internal &&\n+\t\tgit cat-file -p :B >B.internal &&\n+\t\ttest_cmp_str x B.internal &&\n+\t\tgit cat-file -p :C >C.internal &&\n+\t\ttest_cmp_str x C.internal &&\n+\n+\t\trm A B C *.internal &&\n+\t\tgit_pc 2 0 2 checkout A B C &&\n+\t\ttest_cmp_str x A &&\n+\t\ttest_cmp_str x B &&\n+\t\ttest_cmp_str x C\n+\t)\n+'\n+\n+# The delayed queue is independent from the parallel queue, and they should be\n+# able to work together in the same checkout process.\n+#\n+test_expect_success PERL 'parallel-checkout and delayed checkout' '\n+\twrite_script rot13-filter.pl \"$PERL_PATH\" \\\n+\t\t<\"$TEST_DIRECTORY\"/t0021/rot13-filter.pl &&\n+\ttest_config_global filter.delay.process \\\n+\t\t\"\\\"$(pwd)/rot13-filter.pl\\\" \\\"$(pwd)/delayed.log\\\" clean smudge delay\" &&\n+\ttest_config_global filter.delay.required true &&\n+\n+\techo \"a b c\" >delay-content &&\n+\techo \"n o p\" >delay-rot13-content &&\n+\n+\tgit init delayed &&\n+\t(\n+\t\tcd delayed &&\n+\t\techo \"*.a filter=delay\" >.gitattributes &&\n+\t\tcp ../delay-content test-delay10.a &&\n+\t\tcp ../delay-content test-delay11.a &&\n+\t\techo parallel >parallel1.b &&\n+\t\techo parallel >parallel2.b &&\n+\t\tgit add -A &&\n+\t\tgit commit -m delayed &&\n+\n+\t\t# Check that the stored data was cleaned\n+\t\tgit cat-file -p :test-delay10.a > delay10.internal &&\n+\t\ttest_cmp delay10.internal ../delay-rot13-content &&\n+\t\tgit cat-file -p :test-delay11.a > delay11.internal &&\n+\t\ttest_cmp delay11.internal ../delay-rot13-content &&\n+\t\trm *.internal &&\n+\n+\t\trm *.a *.b\n+\t) &&\n+\n+\tgit_pc 2 0 2 -C delayed checkout -f &&\n+\tverify_checkout delayed &&\n+\n+\t# Check that the *.a files got to the delay queue and were filtered\n+\tgrep \"smudge test-delay10.a .* \\[DELAYED\\]\" delayed.log &&\n+\tgrep \"smudge test-delay11.a .* \\[DELAYED\\]\" delayed.log &&\n+\ttest_cmp delayed/test-delay10.a delay-content &&\n+\ttest_cmp delayed/test-delay11.a delay-content\n+'\n+\n+test_done\n-- \n2.28.0\n\n"},{"id":"408620","messageId":"3d140dcacbd7fd49ea2dfb7bc0839e57b11427de.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 17/19] parallel-checkout: add tests related to clone collisions","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:54Z","receivedAt":"2020-10-29T02:16:44Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Add tests to confirm that path collisions are properly reported during a\nclone operation using parallel-checkout.\n\nOriginal-patch-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n t/lib-parallel-checkout.sh              |  4 +-\n t/t2081-parallel-checkout-collisions.sh | 98 +++++++++++++++++++++++++\n 2 files changed, 100 insertions(+), 2 deletions(-)\n create mode 100755 t/t2081-parallel-checkout-collisions.sh\n\ndiff --git a/t/lib-parallel-checkout.sh b/t/lib-parallel-checkout.sh\nindex 4dad9043fb..e62a433eb1 100644\n--- a/t/lib-parallel-checkout.sh\n+++ b/t/lib-parallel-checkout.sh\n@@ -18,7 +18,7 @@ git_pc()\n \t\t-c checkout.workers=$workers \\\n \t\t-c checkout.thresholdForParallelism=$threshold \\\n \t\t-c advice.detachedHead=0 \\\n-\t\t\"$@\" &&\n+\t\t\"$@\" 2>&8 &&\n \n \t# Check that the expected number of workers has been used. Note that it\n \t# can be different from the requested number in two cases: when the\n@@ -28,7 +28,7 @@ git_pc()\n \tlocal workers_in_trace=$(grep \"child_start\\[..*\\] git checkout--helper\" trace | wc -l) &&\n \ttest $workers_in_trace -eq $expected_workers &&\n \trm -f trace\n-}\n+} 8>&2 2>&4\n \n # Verify that both the working tree and the index were created correctly\n verify_checkout()\ndiff --git a/t/t2081-parallel-checkout-collisions.sh b/t/t2081-parallel-checkout-collisions.sh\nnew file mode 100755\nindex 0000000000..5cab2dcd2c\n--- /dev/null\n+++ b/t/t2081-parallel-checkout-collisions.sh\n@@ -0,0 +1,98 @@\n+#!/bin/sh\n+\n+test_description='parallel-checkout collisions\n+\n+When there are path collisions during a clone, Git should report a warning\n+listing all of the colliding entries. The sequential code detects a collision\n+by calling lstat() before trying to open(O_CREAT) the file. Then, to find the\n+colliding pair of an item k, it searches cache_entry[0, k-1].\n+\n+This is not sufficient in parallel checkout since:\n+\n+- A colliding file may be created between the lstat() and open() calls;\n+- A colliding entry might appear in the second half of the cache_entry array.\n+\n+The tests in this file make sure that the collision detection code is extended\n+for parallel checkout.\n+'\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-parallel-checkout.sh\"\n+\n+TEST_ROOT=\"$PWD\"\n+\n+test_expect_success CASE_INSENSITIVE_FS 'setup' '\n+\tfile_x_hex=$(git hash-object -w --stdin </dev/null) &&\n+\tfile_x_oct=$(echo $file_x_hex | hex2oct) &&\n+\n+\tattr_hex=$(echo \"file_x filter=logger\" | git hash-object -w --stdin) &&\n+\tattr_oct=$(echo $attr_hex | hex2oct) &&\n+\n+\tprintf \"100644 FILE_X\\0${file_x_oct}\" >tree &&\n+\tprintf \"100644 FILE_x\\0${file_x_oct}\" >>tree &&\n+\tprintf \"100644 file_X\\0${file_x_oct}\" >>tree &&\n+\tprintf \"100644 file_x\\0${file_x_oct}\" >>tree &&\n+\tprintf \"100644 .gitattributes\\0${attr_oct}\" >>tree &&\n+\n+\ttree_hex=$(git hash-object -w -t tree --stdin <tree) &&\n+\tcommit_hex=$(git commit-tree -m collisions $tree_hex) &&\n+\tgit update-ref refs/heads/collisions $commit_hex &&\n+\n+\twrite_script \"$TEST_ROOT\"/logger_script <<-\\EOF\n+\techo \"$@\" >>filter.log\n+\tEOF\n+'\n+\n+for mode in parallel sequential-fallback\n+do\n+\n+\tcase $mode in\n+\tparallel)\t\tworkers=2 threshold=0 expected_workers=2 ;;\n+\tsequential-fallback)\tworkers=2 threshold=100 expected_workers=0 ;;\n+\tesac\n+\n+\ttest_expect_success CASE_INSENSITIVE_FS \"collision detection on $mode clone\" '\n+\t\tgit_pc $workers $threshold $expected_workers \\\n+\t\t\tclone --branch=collisions . $mode 2>$mode.stderr &&\n+\n+\t\tgrep FILE_X $mode.stderr &&\n+\t\tgrep FILE_x $mode.stderr &&\n+\t\tgrep file_X $mode.stderr &&\n+\t\tgrep file_x $mode.stderr &&\n+\t\ttest_i18ngrep \"the following paths have collided\" $mode.stderr\n+\t'\n+\n+\t# The following test ensures that the collision detection code is\n+\t# correctly looking for colliding peers in the second half of the\n+\t# cache_entry array. This is done by defining a smudge command for the\n+\t# *last* array entry, which makes it non-eligible for parallel-checkout.\n+\t# The last entry is then checked out *before* any worker is spawned,\n+\t# making it succeed and the workers' entries collide.\n+\t#\n+\t# Note: this test don't work on Windows because, on this system,\n+\t# collision detection uses strcmp() when core.ignoreCase=false. And we\n+\t# have to set core.ignoreCase=false so that only 'file_x' matches the\n+\t# pattern of the filter attribute. But it works on OSX, where collision\n+\t# detection uses inode.\n+\t#\n+\ttest_expect_success CASE_INSENSITIVE_FS,!MINGW,!CYGWIN \"collision detection on $mode clone w/ filter\" '\n+\t\tgit_pc $workers $threshold $expected_workers \\\n+\t\t\t-c core.ignoreCase=false \\\n+\t\t\t-c filter.logger.smudge=\"\\\"$TEST_ROOT/logger_script\\\" %f\" \\\n+\t\t\tclone --branch=collisions . ${mode}_with_filter \\\n+\t\t\t2>${mode}_with_filter.stderr &&\n+\n+\t\tgrep FILE_X ${mode}_with_filter.stderr &&\n+\t\tgrep FILE_x ${mode}_with_filter.stderr &&\n+\t\tgrep file_X ${mode}_with_filter.stderr &&\n+\t\tgrep file_x ${mode}_with_filter.stderr &&\n+\t\ttest_i18ngrep \"the following paths have collided\" ${mode}_with_filter.stderr &&\n+\n+\t\t# Make sure only \"file_x\" was filtered\n+\t\ttest_path_is_file ${mode}_with_filter/filter.log &&\n+\t\techo file_x >expected.filter.log &&\n+\t\ttest_cmp ${mode}_with_filter/filter.log expected.filter.log\n+\t'\n+done\n+\n+test_done\n-- \n2.28.0\n\n"},{"id":"408621","messageId":"05299a3cc0ae8ebb55d17ba35adb953aeb003dca.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 16/19] parallel-checkout: add tests for basic operations","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:53Z","receivedAt":"2020-10-29T02:16:45Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Add tests to populate the working tree during clone and checkout using\nthe sequential and parallel modes, to confirm that they produce\nidentical results. Also test basic checkout mechanics, such as checking\nfor symlinks in the leading directories and the abidance to --force.\n\nNote: some helper functions are added to a common lib file which is only\nincluded by t2080 for now. But it will also be used by other\nparallel-checkout tests in the following patches.\n\nOriginal-patch-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n t/lib-parallel-checkout.sh          |  40 +++++++\n t/t2080-parallel-checkout-basics.sh | 170 ++++++++++++++++++++++++++++\n 2 files changed, 210 insertions(+)\n create mode 100644 t/lib-parallel-checkout.sh\n create mode 100755 t/t2080-parallel-checkout-basics.sh\n\ndiff --git a/t/lib-parallel-checkout.sh b/t/lib-parallel-checkout.sh\nnew file mode 100644\nindex 0000000000..4dad9043fb\n--- /dev/null\n+++ b/t/lib-parallel-checkout.sh\n@@ -0,0 +1,40 @@\n+# Helpers for t208* tests\n+\n+# Runs `git -c checkout.workers=$1 -c checkout.thesholdForParallelism=$2 ${@:4}`\n+# and checks that the number of workers spawned is equal to $3.\n+#\n+git_pc()\n+{\n+\tif test $# -lt 4\n+\tthen\n+\t\tBUG \"too few arguments to git_pc()\"\n+\tfi &&\n+\n+\tworkers=$1 threshold=$2 expected_workers=$3 &&\n+\tshift 3 &&\n+\n+\trm -f trace &&\n+\tGIT_TRACE2=\"$(pwd)/trace\" git \\\n+\t\t-c checkout.workers=$workers \\\n+\t\t-c checkout.thresholdForParallelism=$threshold \\\n+\t\t-c advice.detachedHead=0 \\\n+\t\t\"$@\" &&\n+\n+\t# Check that the expected number of workers has been used. Note that it\n+\t# can be different from the requested number in two cases: when the\n+\t# threshold is not reached; and when there are not enough\n+\t# parallel-eligible entries for all workers.\n+\t#\n+\tlocal workers_in_trace=$(grep \"child_start\\[..*\\] git checkout--helper\" trace | wc -l) &&\n+\ttest $workers_in_trace -eq $expected_workers &&\n+\trm -f trace\n+}\n+\n+# Verify that both the working tree and the index were created correctly\n+verify_checkout()\n+{\n+\tgit -C \"$1\" diff-index --quiet HEAD -- &&\n+\tgit -C \"$1\" diff-index --quiet --cached HEAD -- &&\n+\tgit -C \"$1\" status --porcelain >\"$1\".status &&\n+\ttest_must_be_empty \"$1\".status\n+}\ndiff --git a/t/t2080-parallel-checkout-basics.sh b/t/t2080-parallel-checkout-basics.sh\nnew file mode 100755\nindex 0000000000..edea88f14f\n--- /dev/null\n+++ b/t/t2080-parallel-checkout-basics.sh\n@@ -0,0 +1,170 @@\n+#!/bin/sh\n+\n+test_description='parallel-checkout basics\n+\n+Ensure that parallel-checkout basically works on clone and checkout, spawning\n+the required number of workers and correctly populating both the index and\n+working tree.\n+'\n+\n+TEST_NO_CREATE_REPO=1\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-parallel-checkout.sh\"\n+\n+# Test parallel-checkout with different operations (creation, deletion,\n+# modification) and entry types. A branch switch from B1 to B2 will contain:\n+#\n+# - a (file):      modified\n+# - e/x (file):    deleted\n+# - b (symlink):   deleted\n+# - b/f (file):    created\n+# - e (symlink):   created\n+# - d (submodule): created\n+#\n+test_expect_success SYMLINKS 'setup repo for checkout with various operations' '\n+\tgit init various &&\n+\t(\n+\t\tcd various &&\n+\t\tgit checkout -b B1 &&\n+\t\techo a>a &&\n+\t\tmkdir e &&\n+\t\techo e/x >e/x &&\n+\t\tln -s e b &&\n+\t\tgit add -A &&\n+\t\tgit commit -m B1 &&\n+\n+\t\tgit checkout -b B2 &&\n+\t\techo modified >a &&\n+\t\trm -rf e &&\n+\t\trm b &&\n+\t\tmkdir b &&\n+\t\techo b/f >b/f &&\n+\t\tln -s b e &&\n+\t\tgit init d &&\n+\t\ttest_commit -C d f &&\n+\t\tgit submodule add ./d &&\n+\t\tgit add -A &&\n+\t\tgit commit -m B2 &&\n+\n+\t\tgit checkout --recurse-submodules B1\n+\t)\n+'\n+\n+test_expect_success SYMLINKS 'sequential checkout' '\n+\tcp -R various various_sequential &&\n+\tgit_pc 1 0 0 -C various_sequential checkout --recurse-submodules B2 &&\n+\tverify_checkout various_sequential\n+'\n+\n+test_expect_success SYMLINKS 'parallel checkout' '\n+\tcp -R various various_parallel &&\n+\tgit_pc 2 0 2 -C various_parallel checkout --recurse-submodules B2 &&\n+\tverify_checkout various_parallel\n+'\n+\n+test_expect_success SYMLINKS 'fallback to sequential checkout (threshold)' '\n+\tcp -R various various_sequential_fallback &&\n+\tgit_pc 2 100 0 -C various_sequential_fallback checkout --recurse-submodules B2 &&\n+\tverify_checkout various_sequential_fallback\n+'\n+\n+test_expect_success SYMLINKS 'parallel checkout on clone' '\n+\tgit -C various checkout --recurse-submodules B2 &&\n+\tgit_pc 2 0 2 clone --recurse-submodules various various_parallel_clone  &&\n+\tverify_checkout various_parallel_clone\n+'\n+\n+test_expect_success SYMLINKS 'fallback to sequential checkout on clone (threshold)' '\n+\tgit -C various checkout --recurse-submodules B2 &&\n+\tgit_pc 2 100 0 clone --recurse-submodules various various_sequential_fallback_clone &&\n+\tverify_checkout various_sequential_fallback_clone\n+'\n+\n+# Just to be paranoid, actually compare the working trees' contents directly.\n+test_expect_success SYMLINKS 'compare the working trees' '\n+\trm -rf various_*/.git &&\n+\trm -rf various_*/d/.git &&\n+\n+\tdiff -r various_sequential various_parallel &&\n+\tdiff -r various_sequential various_sequential_fallback &&\n+\tdiff -r various_sequential various_parallel_clone &&\n+\tdiff -r various_sequential various_sequential_fallback_clone\n+'\n+\n+test_cmp_str()\n+{\n+\techo \"$1\" >tmp &&\n+\ttest_cmp tmp \"$2\"\n+}\n+\n+test_expect_success 'parallel checkout respects --[no]-force' '\n+\tgit init dirty &&\n+\t(\n+\t\tcd dirty &&\n+\t\tmkdir D &&\n+\t\ttest_commit D/F &&\n+\t\ttest_commit F &&\n+\n+\t\techo changed >F.t &&\n+\t\trm -rf D &&\n+\t\techo changed >D &&\n+\n+\t\t# We expect 0 workers because there is nothing to be updated\n+\t\tgit_pc 2 0 0 checkout HEAD &&\n+\t\ttest_path_is_file D &&\n+\t\ttest_cmp_str changed D &&\n+\t\ttest_cmp_str changed F.t &&\n+\n+\t\tgit_pc 2 0 2 checkout --force HEAD &&\n+\t\ttest_path_is_dir D &&\n+\t\ttest_cmp_str D/F D/F.t &&\n+\t\ttest_cmp_str F F.t\n+\t)\n+'\n+\n+test_expect_success SYMLINKS 'parallel checkout checks for symlinks in leading dirs' '\n+\tgit init symlinks &&\n+\t(\n+\t\tcd symlinks &&\n+\t\tmkdir D E &&\n+\n+\t\t# Create two entries in D to have enough work for 2 parallel\n+\t\t# workers\n+\t\ttest_commit D/A &&\n+\t\ttest_commit D/B &&\n+\t\ttest_commit E/C &&\n+\t\trm -rf D &&\n+\t\tln -s E D &&\n+\n+\t\tgit_pc 2 0 2 checkout --force HEAD &&\n+\t\t! test -L D &&\n+\t\ttest_cmp_str D/A D/A.t &&\n+\t\ttest_cmp_str D/B D/B.t\n+\t)\n+'\n+\n+test_expect_success SYMLINKS,CASE_INSENSITIVE_FS 'symlink colliding with leading dir' '\n+\tgit init colliding-symlink &&\n+\t(\n+\t\tcd colliding-symlink &&\n+\t\tfile_hex=$(git hash-object -w --stdin </dev/null) &&\n+\t\tfile_oct=$(echo $file_hex | hex2oct) &&\n+\n+\t\tsym_hex=$(echo \"./D\" | git hash-object -w --stdin) &&\n+\t\tsym_oct=$(echo $sym_hex | hex2oct) &&\n+\n+\t\tprintf \"100644 D/A\\0${file_oct}\" >tree &&\n+\t\tprintf \"100644 E/B\\0${file_oct}\" >>tree &&\n+\t\tprintf \"120000 e\\0${sym_oct}\" >>tree &&\n+\n+\t\ttree_hex=$(git hash-object -w -t tree --stdin <tree) &&\n+\t\tcommit_hex=$(git commit-tree -m collisions $tree_hex) &&\n+\t\tgit update-ref refs/heads/colliding-symlink $commit_hex &&\n+\n+\t\tgit_pc 2 0 2 checkout colliding-symlink &&\n+\t\ttest_path_is_dir D &&\n+\t\ttest_path_is_missing D/B\n+\t)\n+'\n+\n+test_done\n-- \n2.28.0\n\n"},{"id":"408622","messageId":"ee34c6e1497852e52b19a1b1ff4c70468080885e.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 15/19] checkout-index: add parallel checkout support","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:52Z","receivedAt":"2020-10-29T02:16:46Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n builtin/checkout-index.c | 21 ++++++++++++++++++---\n 1 file changed, 18 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/checkout-index.c b/builtin/checkout-index.c\nindex 9276ed0258..9a2e255f58 100644\n--- a/builtin/checkout-index.c\n+++ b/builtin/checkout-index.c\n@@ -12,6 +12,7 @@\n #include \"cache-tree.h\"\n #include \"parse-options.h\"\n #include \"entry.h\"\n+#include \"parallel-checkout.h\"\n \n #define CHECKOUT_ALL 4\n static int nul_term_line;\n@@ -169,6 +170,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)\n \tint force = 0, quiet = 0, not_new = 0;\n \tint index_opt = 0;\n \tint err = 0;\n+\tint pc_workers, pc_threshold;\n \tstruct option builtin_checkout_index_options[] = {\n \t\tOPT_BOOL('a', \"all\", &all,\n \t\t\tN_(\"check out all files in the index\")),\n@@ -223,6 +225,14 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)\n \t\thold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);\n \t}\n \n+\tif (!to_tempfile)\n+\t\tget_parallel_checkout_configs(&pc_workers, &pc_threshold);\n+\telse\n+\t\tpc_workers = 1;\n+\n+\tif (pc_workers > 1)\n+\t\tinit_parallel_checkout();\n+\n \t/* Check out named files first */\n \tfor (i = 0; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n@@ -262,12 +272,17 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_release(&buf);\n \t}\n \n-\tif (err)\n-\t\treturn 1;\n-\n \tif (all)\n \t\tcheckout_all(prefix, prefix_length);\n \n+\tif (pc_workers > 1) {\n+\t\terr |= run_parallel_checkout(&state, pc_workers, pc_threshold,\n+\t\t\t\t\t     NULL, NULL);\n+\t}\n+\n+\tif (err)\n+\t\treturn 1;\n+\n \tif (is_lock_file_locked(&lock_file) &&\n \t    write_locked_index(&the_index, &lock_file, COMMIT_LOCK))\n \t\tdie(\"Unable to write new index file\");\n-- \n2.28.0\n\n"},{"id":"408623","messageId":"da99b671e6dfcdc0f36ed0277154d63dd7f23e70.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 13/19] make_transient_cache_entry(): optionally alloc from mem_pool","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:50Z","receivedAt":"2020-10-29T02:16:46Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Allow make_transient_cache_entry() to optionally receive a mem_pool\nstruct in which it should allocate the entry. This will be used in the\nfollowing patch, to store some transient entries which should persist\nuntil parallel checkout finishes.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n builtin/checkout--helper.c |  2 +-\n builtin/checkout.c         |  2 +-\n builtin/difftool.c         |  2 +-\n cache.h                    | 10 +++++-----\n read-cache.c               | 12 ++++++++----\n unpack-trees.c             |  2 +-\n 6 files changed, 17 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/checkout--helper.c b/builtin/checkout--helper.c\nindex 67fe37cf11..9646ed9eeb 100644\n--- a/builtin/checkout--helper.c\n+++ b/builtin/checkout--helper.c\n@@ -38,7 +38,7 @@ static void packet_to_pc_item(char *line, int len,\n \t}\n \n \tmemset(pc_item, 0, sizeof(*pc_item));\n-\tpc_item->ce = make_empty_transient_cache_entry(fixed_portion->name_len);\n+\tpc_item->ce = make_empty_transient_cache_entry(fixed_portion->name_len, NULL);\n \tpc_item->ce->ce_namelen = fixed_portion->name_len;\n \tpc_item->ce->ce_mode = fixed_portion->ce_mode;\n \tmemcpy(pc_item->ce->name, variant, pc_item->ce->ce_namelen);\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b18b9d6f3c..c0bf5e6711 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -291,7 +291,7 @@ static int checkout_merged(int pos, const struct checkout *state, int *nr_checko\n \tif (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid))\n \t\tdie(_(\"Unable to add merge result for '%s'\"), path);\n \tfree(result_buf.ptr);\n-\tce = make_transient_cache_entry(mode, &oid, path, 2);\n+\tce = make_transient_cache_entry(mode, &oid, path, 2, NULL);\n \tif (!ce)\n \t\tdie(_(\"make_cache_entry failed for path '%s'\"), path);\n \tstatus = checkout_entry(ce, state, NULL, nr_checkouts);\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex dfa22b67eb..5e7a57c8c2 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -323,7 +323,7 @@ static int checkout_path(unsigned mode, struct object_id *oid,\n \tstruct cache_entry *ce;\n \tint ret;\n \n-\tce = make_transient_cache_entry(mode, oid, path, 0);\n+\tce = make_transient_cache_entry(mode, oid, path, 0, NULL);\n \tret = checkout_entry(ce, state, NULL, NULL);\n \n \tdiscard_cache_entry(ce);\ndiff --git a/cache.h b/cache.h\nindex ccfeb9ba2b..b5074b2cb2 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -355,16 +355,16 @@ struct cache_entry *make_empty_cache_entry(struct index_state *istate,\n \t\t\t\t\t   size_t name_len);\n \n /*\n- * Create a cache_entry that is not intended to be added to an index.\n- * Caller is responsible for discarding the cache_entry\n- * with `discard_cache_entry`.\n+ * Create a cache_entry that is not intended to be added to an index. If mp is\n+ * not NULL, the entry is allocated within the given memory pool. Caller is\n+ * responsible for discarding the cache_entry with `discard_cache_entry`.\n  */\n struct cache_entry *make_transient_cache_entry(unsigned int mode,\n \t\t\t\t\t       const struct object_id *oid,\n \t\t\t\t\t       const char *path,\n-\t\t\t\t\t       int stage);\n+\t\t\t\t\t       int stage, struct mem_pool *mp);\n \n-struct cache_entry *make_empty_transient_cache_entry(size_t name_len);\n+struct cache_entry *make_empty_transient_cache_entry(size_t len, struct mem_pool *mp);\n \n /*\n  * Discard cache entry.\ndiff --git a/read-cache.c b/read-cache.c\nindex ecf6f68994..f9bac760af 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -813,8 +813,10 @@ struct cache_entry *make_empty_cache_entry(struct index_state *istate, size_t le\n \treturn mem_pool__ce_calloc(find_mem_pool(istate), len);\n }\n \n-struct cache_entry *make_empty_transient_cache_entry(size_t len)\n+struct cache_entry *make_empty_transient_cache_entry(size_t len, struct mem_pool *mp)\n {\n+\tif (mp)\n+\t\treturn mem_pool__ce_calloc(mp, len);\n \treturn xcalloc(1, cache_entry_size(len));\n }\n \n@@ -848,8 +850,10 @@ struct cache_entry *make_cache_entry(struct index_state *istate,\n \treturn ret;\n }\n \n-struct cache_entry *make_transient_cache_entry(unsigned int mode, const struct object_id *oid,\n-\t\t\t\t\t       const char *path, int stage)\n+struct cache_entry *make_transient_cache_entry(unsigned int mode,\n+\t\t\t\t\t       const struct object_id *oid,\n+\t\t\t\t\t       const char *path, int stage,\n+\t\t\t\t\t       struct mem_pool *mp)\n {\n \tstruct cache_entry *ce;\n \tint len;\n@@ -860,7 +864,7 @@ struct cache_entry *make_transient_cache_entry(unsigned int mode, const struct o\n \t}\n \n \tlen = strlen(path);\n-\tce = make_empty_transient_cache_entry(len);\n+\tce = make_empty_transient_cache_entry(len, mp);\n \n \toidcpy(&ce->oid, oid);\n \tmemcpy(ce->name, path, len);\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex e05e6ceff2..dcb40dc8fa 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -1031,7 +1031,7 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info,\n \tsize_t len = traverse_path_len(info, tree_entry_len(n));\n \tstruct cache_entry *ce =\n \t\tis_transient ?\n-\t\tmake_empty_transient_cache_entry(len) :\n+\t\tmake_empty_transient_cache_entry(len, NULL) :\n \t\tmake_empty_cache_entry(istate, len);\n \n \tce->ce_mode = create_ce_mode(n->mode);\n-- \n2.28.0\n\n"},{"id":"408624","messageId":"d3d561754a4d5599a5e14560c9e0da55ae627ffb.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 14/19] builtin/checkout.c: complete parallel checkout support","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:51Z","receivedAt":"2020-10-29T02:16:47Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"There is one code path in builtin/checkout.c which still doesn't benefit\nfrom parallel checkout because it calls checkout_entry() directly,\ninstead of unpack_trees(). Let's add parallel support for this missing\nspot as well. Note: the transient cache entries allocated in\ncheckout_merged() are now allocated in a mem_pool which is only\ndiscarded after parallel checkout finishes. This is done because the\nentries need to be valid when run_parallel_checkout() is called.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n builtin/checkout.c | 20 ++++++++++++++++----\n 1 file changed, 16 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex c0bf5e6711..ddc4079b85 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -27,6 +27,7 @@\n #include \"wt-status.h\"\n #include \"xdiff-interface.h\"\n #include \"entry.h\"\n+#include \"parallel-checkout.h\"\n \n static const char * const checkout_usage[] = {\n \tN_(\"git checkout [<options>] <branch>\"),\n@@ -230,7 +231,8 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos,\n \t\treturn error(_(\"path '%s' does not have their version\"), ce->name);\n }\n \n-static int checkout_merged(int pos, const struct checkout *state, int *nr_checkouts)\n+static int checkout_merged(int pos, const struct checkout *state,\n+\t\t\t   int *nr_checkouts, struct mem_pool *ce_mem_pool)\n {\n \tstruct cache_entry *ce = active_cache[pos];\n \tconst char *path = ce->name;\n@@ -291,11 +293,10 @@ static int checkout_merged(int pos, const struct checkout *state, int *nr_checko\n \tif (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid))\n \t\tdie(_(\"Unable to add merge result for '%s'\"), path);\n \tfree(result_buf.ptr);\n-\tce = make_transient_cache_entry(mode, &oid, path, 2, NULL);\n+\tce = make_transient_cache_entry(mode, &oid, path, 2, ce_mem_pool);\n \tif (!ce)\n \t\tdie(_(\"make_cache_entry failed for path '%s'\"), path);\n \tstatus = checkout_entry(ce, state, NULL, nr_checkouts);\n-\tdiscard_cache_entry(ce);\n \treturn status;\n }\n \n@@ -359,16 +360,22 @@ static int checkout_worktree(const struct checkout_opts *opts,\n \tint nr_checkouts = 0, nr_unmerged = 0;\n \tint errs = 0;\n \tint pos;\n+\tint pc_workers, pc_threshold;\n+\tstruct mem_pool ce_mem_pool;\n \n \tstate.force = 1;\n \tstate.refresh_cache = 1;\n \tstate.istate = &the_index;\n \n+\tmem_pool_init(&ce_mem_pool, 0);\n+\tget_parallel_checkout_configs(&pc_workers, &pc_threshold);\n \tinit_checkout_metadata(&state.meta, info->refname,\n \t\t\t       info->commit ? &info->commit->object.oid : &info->oid,\n \t\t\t       NULL);\n \n \tenable_delayed_checkout(&state);\n+\tif (pc_workers > 1)\n+\t\tinit_parallel_checkout();\n \tfor (pos = 0; pos < active_nr; pos++) {\n \t\tstruct cache_entry *ce = active_cache[pos];\n \t\tif (ce->ce_flags & CE_MATCHED) {\n@@ -384,10 +391,15 @@ static int checkout_worktree(const struct checkout_opts *opts,\n \t\t\t\t\t\t       &nr_checkouts, opts->overlay_mode);\n \t\t\telse if (opts->merge)\n \t\t\t\terrs |= checkout_merged(pos, &state,\n-\t\t\t\t\t\t\t&nr_unmerged);\n+\t\t\t\t\t\t\t&nr_unmerged,\n+\t\t\t\t\t\t\t&ce_mem_pool);\n \t\t\tpos = skip_same_name(ce, pos) - 1;\n \t\t}\n \t}\n+\tif (pc_workers > 1)\n+\t\terrs |= run_parallel_checkout(&state, pc_workers, pc_threshold,\n+\t\t\t\t\t      NULL, NULL);\n+\tmem_pool_discard(&ce_mem_pool, should_validate_cache_entries());\n \tremove_marked_cache_entries(&the_index, 1);\n \tremove_scheduled_dirs();\n \terrs |= finish_delayed_checkout(&state, &nr_checkouts);\n-- \n2.28.0\n\n"},{"id":"408626","messageId":"2bdc13664e65a25607b8ecb4c0ea54fb2dad482c.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 10/19] unpack-trees: add basic support for parallel checkout","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:47Z","receivedAt":"2020-10-29T02:16:50Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"This new interface allows us to enqueue some of the entries being\nchecked out to later call write_entry() for them in parallel. For now,\nthe parallel checkout machinery is enabled by default and there is no\nuser configuration, but run_parallel_checkout() just writes the queued\nentries in sequence (without spawning additional workers). The next\npatch will actually implement the parallelism and, later, we will make\nit configurable.\n\nWhen there are path collisions among the entries being written (which\ncan happen e.g. with case-sensitive files in case-insensitive file\nsystems), the parallel checkout code detects the problem and marks the\nitem with PC_ITEM_COLLIDED. Later, these items are sequentially fed to\ncheckout_entry() again. This is similar to the way the sequential code\ndeals with collisions, overwriting the previously checked out entries\nwith the subsequent ones. The only difference is that, when we start\nwriting the entries in parallel, we won't be able to determine which of\nthe colliding entries will survive on disk (for the sequential\nalgorithm, it is always the last one).\n\nI also experimented with the idea of not overwriting colliding entries,\nand it seemed to work well in my simple tests. However, because just one\nentry of each colliding group would be actually written, the others\nwould have null lstat() fields on the index. This might not be a problem\nby itself, but it could cause performance penalties for subsequent\ncommands that need to refresh the index: when the st_size value cached\nis 0, read-cache.c:ie_modified() will go to the filesystem to see if the\ncontents match. As mentioned in the function:\n\n    * Immediately after read-tree or update-index --cacheinfo,\n    * the length field is zero, as we have never even read the\n    * lstat(2) information once, and we cannot trust DATA_CHANGED\n    * returned by ie_match_stat() which in turn was returned by\n    * ce_match_stat_basic() to signal that the filesize of the\n    * blob changed.  We have to actually go to the filesystem to\n    * see if the contents match, and if so, should answer \"unchanged\".\n\nSo, if we have N entries in a colliding group and we decide to write and\nlstat() only one of them, every subsequent git-status will have to read,\nconvert, and hash the written file N - 1 times, to check that the N - 1\nunwritten entries are dirty. By checking out all colliding entries (like\nthe sequential code does), we only pay the overhead once.\n\nCo-authored-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nCo-authored-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n Makefile            |   1 +\n entry.c             |  17 +-\n parallel-checkout.c | 368 ++++++++++++++++++++++++++++++++++++++++++++\n parallel-checkout.h |  27 ++++\n unpack-trees.c      |   6 +-\n 5 files changed, 416 insertions(+), 3 deletions(-)\n create mode 100644 parallel-checkout.c\n create mode 100644 parallel-checkout.h\n\ndiff --git a/Makefile b/Makefile\nindex 1fb0ec1705..10ee5e709b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -945,6 +945,7 @@ LIB_OBJS += pack-revindex.o\n LIB_OBJS += pack-write.o\n LIB_OBJS += packfile.o\n LIB_OBJS += pager.o\n+LIB_OBJS += parallel-checkout.o\n LIB_OBJS += parse-options-cb.o\n LIB_OBJS += parse-options.o\n LIB_OBJS += patch-delta.o\ndiff --git a/entry.c b/entry.c\nindex 9d79a5671f..6676954431 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -7,6 +7,7 @@\n #include \"progress.h\"\n #include \"fsmonitor.h\"\n #include \"entry.h\"\n+#include \"parallel-checkout.h\"\n \n static void create_directories(const char *path, int path_len,\n \t\t\t       const struct checkout *state)\n@@ -426,8 +427,17 @@ static void mark_colliding_entries(const struct checkout *state,\n \tfor (i = 0; i < state->istate->cache_nr; i++) {\n \t\tstruct cache_entry *dup = state->istate->cache[i];\n \n-\t\tif (dup == ce)\n-\t\t\tbreak;\n+\t\tif (dup == ce) {\n+\t\t\t/*\n+\t\t\t * Parallel checkout creates the files in no particular\n+\t\t\t * order. So the other side of the collision may appear\n+\t\t\t * after the given cache_entry in the array.\n+\t\t\t */\n+\t\t\tif (parallel_checkout_status() == PC_RUNNING)\n+\t\t\t\tcontinue;\n+\t\t\telse\n+\t\t\t\tbreak;\n+\t\t}\n \n \t\tif (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))\n \t\t\tcontinue;\n@@ -536,6 +546,9 @@ int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,\n \t\tca = &ca_buf;\n \t}\n \n+\tif (!enqueue_checkout(ce, ca))\n+\t\treturn 0;\n+\n \treturn write_entry(ce, path.buf, ca, state, 0);\n }\n \ndiff --git a/parallel-checkout.c b/parallel-checkout.c\nnew file mode 100644\nindex 0000000000..981dbe6ff3\n--- /dev/null\n+++ b/parallel-checkout.c\n@@ -0,0 +1,368 @@\n+#include \"cache.h\"\n+#include \"entry.h\"\n+#include \"parallel-checkout.h\"\n+#include \"streaming.h\"\n+\n+enum pc_item_status {\n+\tPC_ITEM_PENDING = 0,\n+\tPC_ITEM_WRITTEN,\n+\t/*\n+\t * The entry could not be written because there was another file\n+\t * already present in its path or leading directories. Since\n+\t * checkout_entry_ca() removes such files from the working tree before\n+\t * enqueueing the entry for parallel checkout, it means that there was\n+\t * a path collision among the entries being written.\n+\t */\n+\tPC_ITEM_COLLIDED,\n+\tPC_ITEM_FAILED,\n+};\n+\n+struct parallel_checkout_item {\n+\t/* pointer to a istate->cache[] entry. Not owned by us. */\n+\tstruct cache_entry *ce;\n+\tstruct conv_attrs ca;\n+\tstruct stat st;\n+\tenum pc_item_status status;\n+};\n+\n+struct parallel_checkout {\n+\tenum pc_status status;\n+\tstruct parallel_checkout_item *items;\n+\tsize_t nr, alloc;\n+};\n+\n+static struct parallel_checkout parallel_checkout = { 0 };\n+\n+enum pc_status parallel_checkout_status(void)\n+{\n+\treturn parallel_checkout.status;\n+}\n+\n+void init_parallel_checkout(void)\n+{\n+\tif (parallel_checkout.status != PC_UNINITIALIZED)\n+\t\tBUG(\"parallel checkout already initialized\");\n+\n+\tparallel_checkout.status = PC_ACCEPTING_ENTRIES;\n+}\n+\n+static void finish_parallel_checkout(void)\n+{\n+\tif (parallel_checkout.status == PC_UNINITIALIZED)\n+\t\tBUG(\"cannot finish parallel checkout: not initialized yet\");\n+\n+\tfree(parallel_checkout.items);\n+\tmemset(&parallel_checkout, 0, sizeof(parallel_checkout));\n+}\n+\n+static int is_eligible_for_parallel_checkout(const struct cache_entry *ce,\n+\t\t\t\t\t     const struct conv_attrs *ca)\n+{\n+\tenum conv_attrs_classification c;\n+\n+\tif (!S_ISREG(ce->ce_mode))\n+\t\treturn 0;\n+\n+\tc = classify_conv_attrs(ca);\n+\tswitch (c) {\n+\tcase CA_CLASS_INCORE:\n+\t\treturn 1;\n+\n+\tcase CA_CLASS_INCORE_FILTER:\n+\t\t/*\n+\t\t * It would be safe to allow concurrent instances of\n+\t\t * single-file smudge filters, like rot13, but we should not\n+\t\t * assume that all filters are parallel-process safe. So we\n+\t\t * don't allow this.\n+\t\t */\n+\t\treturn 0;\n+\n+\tcase CA_CLASS_INCORE_PROCESS:\n+\t\t/*\n+\t\t * The parallel queue and the delayed queue are not compatible,\n+\t\t * so they must be kept completely separated. And we can't tell\n+\t\t * if a long-running process will delay its response without\n+\t\t * actually asking it to perform the filtering. Therefore, this\n+\t\t * type of filter is not allowed in parallel checkout.\n+\t\t *\n+\t\t * Furthermore, there should only be one instance of the\n+\t\t * long-running process filter as we don't know how it is\n+\t\t * managing its own concurrency. So, spreading the entries that\n+\t\t * requisite such a filter among the parallel workers would\n+\t\t * require a lot more inter-process communication. We would\n+\t\t * probably have to designate a single process to interact with\n+\t\t * the filter and send all the necessary data to it, for each\n+\t\t * entry.\n+\t\t */\n+\t\treturn 0;\n+\n+\tcase CA_CLASS_STREAMABLE:\n+\t\treturn 1;\n+\n+\tdefault:\n+\t\tBUG(\"unsupported conv_attrs classification '%d'\", c);\n+\t}\n+}\n+\n+int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca)\n+{\n+\tstruct parallel_checkout_item *pc_item;\n+\n+\tif (parallel_checkout.status != PC_ACCEPTING_ENTRIES ||\n+\t    !is_eligible_for_parallel_checkout(ce, ca))\n+\t\treturn -1;\n+\n+\tALLOC_GROW(parallel_checkout.items, parallel_checkout.nr + 1,\n+\t\t   parallel_checkout.alloc);\n+\n+\tpc_item = &parallel_checkout.items[parallel_checkout.nr++];\n+\tpc_item->ce = ce;\n+\tmemcpy(&pc_item->ca, ca, sizeof(pc_item->ca));\n+\tpc_item->status = PC_ITEM_PENDING;\n+\n+\treturn 0;\n+}\n+\n+static int handle_results(struct checkout *state)\n+{\n+\tint ret = 0;\n+\tsize_t i;\n+\tint have_pending = 0;\n+\n+\t/*\n+\t * We first update the successfully written entries with the collected\n+\t * stat() data, so that they can be found by mark_colliding_entries(),\n+\t * in the next loop, when necessary.\n+\t */\n+\tfor (i = 0; i < parallel_checkout.nr; ++i) {\n+\t\tstruct parallel_checkout_item *pc_item = &parallel_checkout.items[i];\n+\t\tif (pc_item->status == PC_ITEM_WRITTEN)\n+\t\t\tupdate_ce_after_write(state, pc_item->ce, &pc_item->st);\n+\t}\n+\n+\tfor (i = 0; i < parallel_checkout.nr; ++i) {\n+\t\tstruct parallel_checkout_item *pc_item = &parallel_checkout.items[i];\n+\n+\t\tswitch(pc_item->status) {\n+\t\tcase PC_ITEM_WRITTEN:\n+\t\t\t/* Already handled */\n+\t\t\tbreak;\n+\t\tcase PC_ITEM_COLLIDED:\n+\t\t\t/*\n+\t\t\t * The entry could not be checked out due to a path\n+\t\t\t * collision with another entry. Since there can only\n+\t\t\t * be one entry of each colliding group on the disk, we\n+\t\t\t * could skip trying to check out this one and move on.\n+\t\t\t * However, this would leave the unwritten entries with\n+\t\t\t * null stat() fields on the index, which could\n+\t\t\t * potentially slow down subsequent operations that\n+\t\t\t * require refreshing it: git would not be able to\n+\t\t\t * trust st_size and would have to go to the filesystem\n+\t\t\t * to see if the contents match (see ie_modified()).\n+\t\t\t *\n+\t\t\t * Instead, let's pay the overhead only once, now, and\n+\t\t\t * call checkout_entry_ca() again for this file, to\n+\t\t\t * have it's stat() data stored in the index. This also\n+\t\t\t * has the benefit of adding this entry and its\n+\t\t\t * colliding pair to the collision report message.\n+\t\t\t * Additionally, this overwriting behavior is consistent\n+\t\t\t * with what the sequential checkout does, so it doesn't\n+\t\t\t * add any extra overhead.\n+\t\t\t */\n+\t\t\tret |= checkout_entry_ca(pc_item->ce, &pc_item->ca,\n+\t\t\t\t\t\t state, NULL, NULL);\n+\t\t\tbreak;\n+\t\tcase PC_ITEM_PENDING:\n+\t\t\thave_pending = 1;\n+\t\t\t/* fall through */\n+\t\tcase PC_ITEM_FAILED:\n+\t\t\tret = -1;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tBUG(\"unknown checkout item status in parallel checkout\");\n+\t\t}\n+\t}\n+\n+\tif (have_pending)\n+\t\terror(_(\"parallel checkout finished with pending entries\"));\n+\n+\treturn ret;\n+}\n+\n+static int reset_fd(int fd, const char *path)\n+{\n+\tif (lseek(fd, 0, SEEK_SET) != 0)\n+\t\treturn error_errno(\"failed to rewind descriptor of %s\", path);\n+\tif (ftruncate(fd, 0))\n+\t\treturn error_errno(\"failed to truncate file %s\", path);\n+\treturn 0;\n+}\n+\n+static int write_pc_item_to_fd(struct parallel_checkout_item *pc_item, int fd,\n+\t\t\t       const char *path)\n+{\n+\tint ret;\n+\tstruct stream_filter *filter;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tchar *new_blob;\n+\tunsigned long size;\n+\tsize_t newsize = 0;\n+\tssize_t wrote;\n+\n+\t/* Sanity check */\n+\tassert(is_eligible_for_parallel_checkout(pc_item->ce, &pc_item->ca));\n+\n+\tfilter = get_stream_filter_ca(&pc_item->ca, &pc_item->ce->oid);\n+\tif (filter) {\n+\t\tif (stream_blob_to_fd(fd, &pc_item->ce->oid, filter, 1)) {\n+\t\t\t/* On error, reset fd to try writing without streaming */\n+\t\t\tif (reset_fd(fd, path))\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\tnew_blob = read_blob_entry(pc_item->ce, &size);\n+\tif (!new_blob)\n+\t\treturn error(\"unable to read sha1 file of %s (%s)\", path,\n+\t\t\t     oid_to_hex(&pc_item->ce->oid));\n+\n+\t/*\n+\t * checkout metadata is used to give context for external process\n+\t * filters. Files requiring such filters are not eligible for parallel\n+\t * checkout, so pass NULL.\n+\t */\n+\tret = convert_to_working_tree_ca(&pc_item->ca, pc_item->ce->name,\n+\t\t\t\t\t new_blob, size, &buf, NULL);\n+\n+\tif (ret) {\n+\t\tfree(new_blob);\n+\t\tnew_blob = strbuf_detach(&buf, &newsize);\n+\t\tsize = newsize;\n+\t}\n+\n+\twrote = write_in_full(fd, new_blob, size);\n+\tfree(new_blob);\n+\tif (wrote < 0)\n+\t\treturn error(\"unable to write file %s\", path);\n+\n+\treturn 0;\n+}\n+\n+static int close_and_clear(int *fd)\n+{\n+\tint ret = 0;\n+\n+\tif (*fd >= 0) {\n+\t\tret = close(*fd);\n+\t\t*fd = -1;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static int check_leading_dirs(const char *path, int len, int prefix_len)\n+{\n+\tconst char *slash = path + len;\n+\n+\twhile (slash > path && *slash != '/')\n+\t\tslash--;\n+\n+\treturn has_dirs_only_path(path, slash - path, prefix_len);\n+}\n+\n+static void write_pc_item(struct parallel_checkout_item *pc_item,\n+\t\t\t  struct checkout *state)\n+{\n+\tunsigned int mode = (pc_item->ce->ce_mode & 0100) ? 0777 : 0666;\n+\tint fd = -1, fstat_done = 0;\n+\tstruct strbuf path = STRBUF_INIT;\n+\n+\tstrbuf_add(&path, state->base_dir, state->base_dir_len);\n+\tstrbuf_add(&path, pc_item->ce->name, pc_item->ce->ce_namelen);\n+\n+\t/*\n+\t * At this point, leading dirs should have already been created. But if\n+\t * a symlink being checked out has collided with one of the dirs, due to\n+\t * file system folding rules, it's possible that the dirs are no longer\n+\t * present. So we have to check again, and report any path collisions.\n+\t */\n+\tif (!check_leading_dirs(path.buf, path.len, state->base_dir_len)) {\n+\t\tpc_item->status = PC_ITEM_COLLIDED;\n+\t\tgoto out;\n+\t}\n+\n+\tfd = open(path.buf, O_WRONLY | O_CREAT | O_EXCL, mode);\n+\n+\tif (fd < 0) {\n+\t\tif (errno == EEXIST || errno == EISDIR) {\n+\t\t\t/*\n+\t\t\t * Errors which probably represent a path collision.\n+\t\t\t * Suppress the error message and mark the item to be\n+\t\t\t * retried later, sequentially. ENOTDIR and ENOENT are\n+\t\t\t * also interesting, but check_leading_dirs() should\n+\t\t\t * have already caught these cases.\n+\t\t\t */\n+\t\t\tpc_item->status = PC_ITEM_COLLIDED;\n+\t\t} else {\n+\t\t\terror_errno(\"failed to open file %s\", path.buf);\n+\t\t\tpc_item->status = PC_ITEM_FAILED;\n+\t\t}\n+\t\tgoto out;\n+\t}\n+\n+\tif (write_pc_item_to_fd(pc_item, fd, path.buf)) {\n+\t\t/* Error was already reported. */\n+\t\tpc_item->status = PC_ITEM_FAILED;\n+\t\tgoto out;\n+\t}\n+\n+\tfstat_done = fstat_checkout_output(fd, state, &pc_item->st);\n+\n+\tif (close_and_clear(&fd)) {\n+\t\terror_errno(\"unable to close file %s\", path.buf);\n+\t\tpc_item->status = PC_ITEM_FAILED;\n+\t\tgoto out;\n+\t}\n+\n+\tif (state->refresh_cache && !fstat_done && lstat(path.buf, &pc_item->st) < 0) {\n+\t\terror_errno(\"unable to stat just-written file %s\",  path.buf);\n+\t\tpc_item->status = PC_ITEM_FAILED;\n+\t\tgoto out;\n+\t}\n+\n+\tpc_item->status = PC_ITEM_WRITTEN;\n+\n+out:\n+\t/*\n+\t * No need to check close() return. At this point, either fd is already\n+\t * closed, or we are on an error path, that has already been reported.\n+\t */\n+\tclose_and_clear(&fd);\n+\tstrbuf_release(&path);\n+}\n+\n+static void write_items_sequentially(struct checkout *state)\n+{\n+\tsize_t i;\n+\n+\tfor (i = 0; i < parallel_checkout.nr; ++i)\n+\t\twrite_pc_item(&parallel_checkout.items[i], state);\n+}\n+\n+int run_parallel_checkout(struct checkout *state)\n+{\n+\tint ret;\n+\n+\tif (parallel_checkout.status != PC_ACCEPTING_ENTRIES)\n+\t\tBUG(\"cannot run parallel checkout: uninitialized or already running\");\n+\n+\tparallel_checkout.status = PC_RUNNING;\n+\n+\twrite_items_sequentially(state);\n+\tret = handle_results(state);\n+\n+\tfinish_parallel_checkout();\n+\treturn ret;\n+}\ndiff --git a/parallel-checkout.h b/parallel-checkout.h\nnew file mode 100644\nindex 0000000000..e6d6fc01ea\n--- /dev/null\n+++ b/parallel-checkout.h\n@@ -0,0 +1,27 @@\n+#ifndef PARALLEL_CHECKOUT_H\n+#define PARALLEL_CHECKOUT_H\n+\n+struct cache_entry;\n+struct checkout;\n+struct conv_attrs;\n+\n+enum pc_status {\n+\tPC_UNINITIALIZED = 0,\n+\tPC_ACCEPTING_ENTRIES,\n+\tPC_RUNNING,\n+};\n+\n+enum pc_status parallel_checkout_status(void);\n+void init_parallel_checkout(void);\n+\n+/*\n+ * Return -1 if parallel checkout is currently not enabled or if the entry is\n+ * not eligible for parallel checkout. Otherwise, enqueue the entry for later\n+ * write and return 0.\n+ */\n+int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca);\n+\n+/* Write all the queued entries, returning 0 on success.*/\n+int run_parallel_checkout(struct checkout *state);\n+\n+#endif /* PARALLEL_CHECKOUT_H */\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex a511fadd89..1b1da7485a 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -17,6 +17,7 @@\n #include \"object-store.h\"\n #include \"promisor-remote.h\"\n #include \"entry.h\"\n+#include \"parallel-checkout.h\"\n \n /*\n  * Error messages expected by scripts out of plumbing commands such as\n@@ -438,7 +439,6 @@ static int check_updates(struct unpack_trees_options *o,\n \tif (should_update_submodules())\n \t\tload_gitmodules_file(index, &state);\n \n-\tenable_delayed_checkout(&state);\n \tif (has_promisor_remote()) {\n \t\t/*\n \t\t * Prefetch the objects that are to be checked out in the loop\n@@ -461,6 +461,9 @@ static int check_updates(struct unpack_trees_options *o,\n \t\t\t\t\t   to_fetch.oid, to_fetch.nr);\n \t\toid_array_clear(&to_fetch);\n \t}\n+\n+\tenable_delayed_checkout(&state);\n+\tinit_parallel_checkout();\n \tfor (i = 0; i < index->cache_nr; i++) {\n \t\tstruct cache_entry *ce = index->cache[i];\n \n@@ -474,6 +477,7 @@ static int check_updates(struct unpack_trees_options *o,\n \t\t}\n \t}\n \tstop_progress(&progress);\n+\terrs |= run_parallel_checkout(&state);\n \terrs |= finish_delayed_checkout(&state, NULL);\n \tgit_attr_set_direction(GIT_ATTR_CHECKIN);\n \n-- \n2.28.0\n\n"},{"id":"408625","messageId":"e1b886f82320db6fd1b389eb4d63ac45bd5af7b1.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 09/19] entry: add checkout_entry_ca() which takes preloaded conv_attrs","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:46Z","receivedAt":"2020-10-29T02:16:51Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"The parallel checkout machinery will call checkout_entry() for entries\nthat could not be written in parallel due to path collisions. At this\npoint, we will already be holding the conversion attributes for each\nentry, and it would be wasteful to let checkout_entry() load these\nagain. Instead, let's add the checkout_entry_ca() variant, which\noptionally takes a preloaded conv_attrs struct.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 23 ++++++++++++-----------\n entry.h | 13 +++++++++++--\n 2 files changed, 23 insertions(+), 13 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex 8237859b12..9d79a5671f 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -440,12 +440,13 @@ static void mark_colliding_entries(const struct checkout *state,\n \t}\n }\n \n-int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n-\t\t   char *topath, int *nr_checkouts)\n+int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,\n+\t\t      const struct checkout *state, char *topath,\n+\t\t      int *nr_checkouts)\n {\n \tstatic struct strbuf path = STRBUF_INIT;\n \tstruct stat st;\n-\tstruct conv_attrs ca;\n+\tstruct conv_attrs ca_buf;\n \n \tif (ce->ce_flags & CE_WT_REMOVE) {\n \t\tif (topath)\n@@ -459,11 +460,11 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t}\n \n \tif (topath) {\n-\t\tif (S_ISREG(ce->ce_mode)) {\n-\t\t\tconvert_attrs(state->istate, &ca, ce->name);\n-\t\t\treturn write_entry(ce, topath, &ca, state, 1);\n+\t\tif (S_ISREG(ce->ce_mode) && !ca) {\n+\t\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n+\t\t\tca = &ca_buf;\n \t\t}\n-\t\treturn write_entry(ce, topath, NULL, state, 1);\n+\t\treturn write_entry(ce, topath, ca, state, 1);\n \t}\n \n \tstrbuf_reset(&path);\n@@ -530,12 +531,12 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \tif (nr_checkouts)\n \t\t(*nr_checkouts)++;\n \n-\tif (S_ISREG(ce->ce_mode)) {\n-\t\tconvert_attrs(state->istate, &ca, ce->name);\n-\t\treturn write_entry(ce, path.buf, &ca, state, 0);\n+\tif (S_ISREG(ce->ce_mode) && !ca) {\n+\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n+\t\tca = &ca_buf;\n \t}\n \n-\treturn write_entry(ce, path.buf, NULL, state, 0);\n+\treturn write_entry(ce, path.buf, ca, state, 0);\n }\n \n void unlink_entry(const struct cache_entry *ce)\ndiff --git a/entry.h b/entry.h\nindex 664aed1576..2081fbbbab 100644\n--- a/entry.h\n+++ b/entry.h\n@@ -27,9 +27,18 @@ struct checkout {\n  * file named by ce, a temporary file is created by this function and\n  * its name is returned in topath[], which must be able to hold at\n  * least TEMPORARY_FILENAME_LENGTH bytes long.\n+ *\n+ * With checkout_entry_ca(), callers can optionally pass a preloaded\n+ * conv_attrs struct (to avoid reloading it), when ce refers to a\n+ * regular file. If ca is NULL, the attributes will be loaded\n+ * internally when (and if) needed.\n  */\n-int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n-\t\t   char *topath, int *nr_checkouts);\n+#define checkout_entry(ce, state, topath, nr_checkouts) \\\n+\t\tcheckout_entry_ca(ce, NULL, state, topath, nr_checkouts)\n+int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,\n+\t\t      const struct checkout *state, char *topath,\n+\t\t      int *nr_checkouts);\n+\n void enable_delayed_checkout(struct checkout *state);\n int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n /*\n-- \n2.28.0\n\n"},{"id":"408627","messageId":"096e543fd208d18cae875d3ea1a0ecfacf10fa08.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 11/19] parallel-checkout: make it truly parallel","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:48Z","receivedAt":"2020-10-29T02:16:53Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Use multiple worker processes to distribute the queued entries and call\nwrite_checkout_item() in parallel for them. The items are distributed\nuniformly in contiguous chunks. This minimizes the chances of two\nworkers writing to the same directory simultaneously, which could\naffect performance due to lock contention in the kernel. Work stealing\n(or any other format of re-distribution) is not implemented yet.\n\nThe parallel version was benchmarked during three operations in the\nlinux repo, with cold cache: cloning v5.8, checking out v5.8 from\nv2.6.15 (checkout I) and checking out v5.8 from v5.7 (checkout II). The\nfour tables below show the mean run times and standard deviations for\n5 runs in: a local file system with SSD, a local file system with HDD, a\nLinux NFS server, and Amazon EFS. The numbers of workers were chosen\nbased on what produces the best result for each case.\n\nLocal SSD:\n\n            Clone                  Checkout I             Checkout II\nSequential  8.171 s ± 0.206 s      8.735 s ± 0.230 s      4.166 s ± 0.246 s\n10 workers  3.277 s ± 0.138 s      3.774 s ± 0.188 s      2.561 s ± 0.120 s\nSpeedup     2.49 ± 0.12            2.31 ± 0.13            1.63 ± 0.12\n\nLocal HDD:\n\n            Clone                  Checkout I             Checkout II\nSequential  35.157 s ± 0.205 s     48.835 s ± 0.407 s     47.302 s ± 1.435 s\n8 workers   35.538 s ± 0.325 s     49.353 s ± 0.826 s     48.919 s ± 0.416 s\nSpeedup     0.99 ± 0.01            0.99 ± 0.02            0.97 ± 0.03\n\nLinux NFS server (v4.1, on EBS, single availability zone):\n\n            Clone                  Checkout I             Checkout II\nSequential  216.070 s ± 3.611 s    211.169 s ± 3.147 s    57.446 s ± 1.301 s\n32 workers  67.997 s ± 0.740 s     66.563 s ± 0.457 s     23.708 s ± 0.622 s\nSpeedup     3.18 ± 0.06            3.17 ± 0.05            2.42 ± 0.08\n\nEFS (v4.1, replicated over multiple availability zones):\n\n            Clone                  Checkout I             Checkout II\nSequential  1249.329 s ± 13.857 s  1438.979 s ± 78.792 s  543.919 s ± 18.745 s\n64 workers  225.864 s ± 12.433 s   316.345 s ± 1.887 s    183.648 s ± 10.095 s\nSpeedup     5.53 ± 0.31            4.55 ± 0.25            2.96 ± 0.19\n\nThe above benchmarks show that parallel checkout is most effective on\nrepositories located on an SSD or over a distributed file system. For\nlocal file systems on spinning disks, and/or older machines, the\nparallelism does not always bring a good performance. In fact, it can\neven increase the run time. For this reason, the sequential code is\nstill the default. Two settings are added to optionally enable and\nconfigure the new parallel version as desired.\n\nLocal SSD tests were executed in an i7-7700HQ (4 cores with\nhyper-threading) running Manjaro Linux. Local HDD tests were executed in\nan i7-2600 (also 4 cores with hyper-threading), HDD Seagate Barracuda\n7200 rpm SATA 3.0, running Debian 9.13. NFS and EFS tests were\nexecuted in an Amazon EC2 c5n.large instance, with 2 vCPUs. The Linux\nNFS server was running on a m6g.large instance with 1 TB, EBS GP2\nvolume. Before each timing, the linux repository was removed (or checked\nout back), and `sync && sysctl vm.drop_caches=3` was executed.\n\nCo-authored-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nCo-authored-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n .gitignore                        |   1 +\n Documentation/config/checkout.txt |  21 +++\n Makefile                          |   1 +\n builtin.h                         |   1 +\n builtin/checkout--helper.c        | 142 +++++++++++++++\n git.c                             |   2 +\n parallel-checkout.c               | 280 +++++++++++++++++++++++++++---\n parallel-checkout.h               |  84 ++++++++-\n unpack-trees.c                    |  10 +-\n 9 files changed, 508 insertions(+), 34 deletions(-)\n create mode 100644 builtin/checkout--helper.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 6232d33924..1a341ea184 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -33,6 +33,7 @@\n /git-check-mailmap\n /git-check-ref-format\n /git-checkout\n+/git-checkout--helper\n /git-checkout-index\n /git-cherry\n /git-cherry-pick\ndiff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt\nindex 6b646813ab..23e8f7cde0 100644\n--- a/Documentation/config/checkout.txt\n+++ b/Documentation/config/checkout.txt\n@@ -16,3 +16,24 @@ will checkout the '<something>' branch on another remote,\n and by linkgit:git-worktree[1] when 'git worktree add' refers to a\n remote branch. This setting might be used for other checkout-like\n commands or functionality in the future.\n+\n+checkout.workers::\n+\tThe number of parallel workers to use when updating the working tree.\n+\tThe default is one, i.e. sequential execution. If set to a value less\n+\tthan one, Git will use as many workers as the number of logical cores\n+\tavailable. This setting and `checkout.thresholdForParallelism` affect\n+\tall commands that perform checkout. E.g. checkout, clone, reset,\n+\tsparse-checkout, etc.\n++\n+Note: parallel checkout usually delivers better performance for repositories\n+located on SSDs or over NFS. For repositories on spinning disks and/or machines\n+with a small number of cores, the default sequential checkout often performs\n+better. The size and compression level of a repository might also influence how\n+well the parallel version performs.\n+\n+checkout.thresholdForParallelism::\n+\tWhen running parallel checkout with a small number of files, the cost\n+\tof subprocess spawning and inter-process communication might outweigh\n+\tthe parallelization gains. This setting allows to define the minimum\n+\tnumber of files for which parallel checkout should be attempted. The\n+\tdefault is 100.\ndiff --git a/Makefile b/Makefile\nindex 10ee5e709b..535e6e94aa 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1063,6 +1063,7 @@ BUILTIN_OBJS += builtin/check-attr.o\n BUILTIN_OBJS += builtin/check-ignore.o\n BUILTIN_OBJS += builtin/check-mailmap.o\n BUILTIN_OBJS += builtin/check-ref-format.o\n+BUILTIN_OBJS += builtin/checkout--helper.o\n BUILTIN_OBJS += builtin/checkout-index.o\n BUILTIN_OBJS += builtin/checkout.o\n BUILTIN_OBJS += builtin/clean.o\ndiff --git a/builtin.h b/builtin.h\nindex 53fb290963..2abbe14b0b 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -123,6 +123,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix);\n int cmd_bundle(int argc, const char **argv, const char *prefix);\n int cmd_cat_file(int argc, const char **argv, const char *prefix);\n int cmd_checkout(int argc, const char **argv, const char *prefix);\n+int cmd_checkout__helper(int argc, const char **argv, const char *prefix);\n int cmd_checkout_index(int argc, const char **argv, const char *prefix);\n int cmd_check_attr(int argc, const char **argv, const char *prefix);\n int cmd_check_ignore(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/checkout--helper.c b/builtin/checkout--helper.c\nnew file mode 100644\nindex 0000000000..67fe37cf11\n--- /dev/null\n+++ b/builtin/checkout--helper.c\n@@ -0,0 +1,142 @@\n+#include \"builtin.h\"\n+#include \"config.h\"\n+#include \"entry.h\"\n+#include \"parallel-checkout.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+\n+static void packet_to_pc_item(char *line, int len,\n+\t\t\t      struct parallel_checkout_item *pc_item)\n+{\n+\tstruct pc_item_fixed_portion *fixed_portion;\n+\tchar *encoding, *variant;\n+\n+\tif (len < sizeof(struct pc_item_fixed_portion))\n+\t\tBUG(\"checkout worker received too short item (got %dB, exp %dB)\",\n+\t\t    len, (int)sizeof(struct pc_item_fixed_portion));\n+\n+\tfixed_portion = (struct pc_item_fixed_portion *)line;\n+\n+\tif (len - sizeof(struct pc_item_fixed_portion) !=\n+\t\tfixed_portion->name_len + fixed_portion->working_tree_encoding_len)\n+\t\tBUG(\"checkout worker received corrupted item\");\n+\n+\tvariant = line + sizeof(struct pc_item_fixed_portion);\n+\n+\t/*\n+\t * Note: the main process uses zero length to communicate that the\n+\t * encoding is NULL. There is no use case in actually sending an empty\n+\t * string since it's considered as NULL when ca.working_tree_encoding\n+\t * is set at git_path_check_encoding().\n+\t */\n+\tif (fixed_portion->working_tree_encoding_len) {\n+\t\tencoding = xmemdupz(variant,\n+\t\t\t\t    fixed_portion->working_tree_encoding_len);\n+\t\tvariant += fixed_portion->working_tree_encoding_len;\n+\t} else {\n+\t\tencoding = NULL;\n+\t}\n+\n+\tmemset(pc_item, 0, sizeof(*pc_item));\n+\tpc_item->ce = make_empty_transient_cache_entry(fixed_portion->name_len);\n+\tpc_item->ce->ce_namelen = fixed_portion->name_len;\n+\tpc_item->ce->ce_mode = fixed_portion->ce_mode;\n+\tmemcpy(pc_item->ce->name, variant, pc_item->ce->ce_namelen);\n+\toidcpy(&pc_item->ce->oid, &fixed_portion->oid);\n+\n+\tpc_item->id = fixed_portion->id;\n+\tpc_item->ca.crlf_action = fixed_portion->crlf_action;\n+\tpc_item->ca.ident = fixed_portion->ident;\n+\tpc_item->ca.working_tree_encoding = encoding;\n+}\n+\n+static void report_result(struct parallel_checkout_item *pc_item)\n+{\n+\tstruct pc_item_result res = { 0 };\n+\tsize_t size;\n+\n+\tres.id = pc_item->id;\n+\tres.status = pc_item->status;\n+\n+\tif (pc_item->status == PC_ITEM_WRITTEN) {\n+\t\tres.st = pc_item->st;\n+\t\tsize = sizeof(res);\n+\t} else {\n+\t\tsize = PC_ITEM_RESULT_BASE_SIZE;\n+\t}\n+\n+\tpacket_write(1, (const char *)&res, size);\n+}\n+\n+/* Free the worker-side malloced data, but not pc_item itself. */\n+static void release_pc_item_data(struct parallel_checkout_item *pc_item)\n+{\n+\tfree((char *)pc_item->ca.working_tree_encoding);\n+\tdiscard_cache_entry(pc_item->ce);\n+}\n+\n+static void worker_loop(struct checkout *state)\n+{\n+\tstruct parallel_checkout_item *items = NULL;\n+\tsize_t i, nr = 0, alloc = 0;\n+\n+\twhile (1) {\n+\t\tint len;\n+\t\tchar *line = packet_read_line(0, &len);\n+\n+\t\tif (!line)\n+\t\t\tbreak;\n+\n+\t\tALLOC_GROW(items, nr + 1, alloc);\n+\t\tpacket_to_pc_item(line, len, &items[nr++]);\n+\t}\n+\n+\tfor (i = 0; i < nr; ++i) {\n+\t\tstruct parallel_checkout_item *pc_item = &items[i];\n+\t\twrite_pc_item(pc_item, state);\n+\t\treport_result(pc_item);\n+\t\trelease_pc_item_data(pc_item);\n+\t}\n+\n+\tpacket_flush(1);\n+\n+\tfree(items);\n+}\n+\n+static const char * const checkout_helper_usage[] = {\n+\tN_(\"git checkout--helper [<options>]\"),\n+\tNULL\n+};\n+\n+int cmd_checkout__helper(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct checkout state = CHECKOUT_INIT;\n+\tstruct option checkout_helper_options[] = {\n+\t\tOPT_STRING(0, \"prefix\", &state.base_dir, N_(\"string\"),\n+\t\t\tN_(\"when creating files, prepend <string>\")),\n+\t\tOPT_END()\n+\t};\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n+\t\tusage_with_options(checkout_helper_usage,\n+\t\t\t\t   checkout_helper_options);\n+\n+\tgit_config(git_default_config, NULL);\n+\targc = parse_options(argc, argv, prefix, checkout_helper_options,\n+\t\t\t     checkout_helper_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_with_options(checkout_helper_usage, checkout_helper_options);\n+\n+\tif (state.base_dir)\n+\t\tstate.base_dir_len = strlen(state.base_dir);\n+\n+\t/*\n+\t * Setting this on worker won't actually update the index. We just need\n+\t * to pretend so to induce the checkout machinery to stat() the written\n+\t * entries.\n+\t */\n+\tstate.refresh_cache = 1;\n+\n+\tworker_loop(&state);\n+\treturn 0;\n+}\ndiff --git a/git.c b/git.c\nindex 4bdcdad2cc..384f144593 100644\n--- a/git.c\n+++ b/git.c\n@@ -487,6 +487,8 @@ static struct cmd_struct commands[] = {\n \t{ \"check-mailmap\", cmd_check_mailmap, RUN_SETUP },\n \t{ \"check-ref-format\", cmd_check_ref_format, NO_PARSEOPT  },\n \t{ \"checkout\", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },\n+\t{ \"checkout--helper\", cmd_checkout__helper,\n+\t\tRUN_SETUP | NEED_WORK_TREE | SUPPORT_SUPER_PREFIX },\n \t{ \"checkout-index\", cmd_checkout_index,\n \t\tRUN_SETUP | NEED_WORK_TREE},\n \t{ \"cherry\", cmd_cherry, RUN_SETUP },\ndiff --git a/parallel-checkout.c b/parallel-checkout.c\nindex 981dbe6ff3..a5508e27c2 100644\n--- a/parallel-checkout.c\n+++ b/parallel-checkout.c\n@@ -1,28 +1,15 @@\n #include \"cache.h\"\n #include \"entry.h\"\n #include \"parallel-checkout.h\"\n+#include \"pkt-line.h\"\n+#include \"run-command.h\"\n #include \"streaming.h\"\n+#include \"thread-utils.h\"\n+#include \"config.h\"\n \n-enum pc_item_status {\n-\tPC_ITEM_PENDING = 0,\n-\tPC_ITEM_WRITTEN,\n-\t/*\n-\t * The entry could not be written because there was another file\n-\t * already present in its path or leading directories. Since\n-\t * checkout_entry_ca() removes such files from the working tree before\n-\t * enqueueing the entry for parallel checkout, it means that there was\n-\t * a path collision among the entries being written.\n-\t */\n-\tPC_ITEM_COLLIDED,\n-\tPC_ITEM_FAILED,\n-};\n-\n-struct parallel_checkout_item {\n-\t/* pointer to a istate->cache[] entry. Not owned by us. */\n-\tstruct cache_entry *ce;\n-\tstruct conv_attrs ca;\n-\tstruct stat st;\n-\tenum pc_item_status status;\n+struct pc_worker {\n+\tstruct child_process cp;\n+\tsize_t next_to_complete, nr_to_complete;\n };\n \n struct parallel_checkout {\n@@ -38,6 +25,19 @@ enum pc_status parallel_checkout_status(void)\n \treturn parallel_checkout.status;\n }\n \n+#define DEFAULT_THRESHOLD_FOR_PARALLELISM 100\n+\n+void get_parallel_checkout_configs(int *num_workers, int *threshold)\n+{\n+\tif (git_config_get_int(\"checkout.workers\", num_workers))\n+\t\t*num_workers = 1;\n+\telse if (*num_workers < 1)\n+\t\t*num_workers = online_cpus();\n+\n+\tif (git_config_get_int(\"checkout.thresholdForParallelism\", threshold))\n+\t\t*threshold = DEFAULT_THRESHOLD_FOR_PARALLELISM;\n+}\n+\n void init_parallel_checkout(void)\n {\n \tif (parallel_checkout.status != PC_UNINITIALIZED)\n@@ -115,10 +115,12 @@ int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca)\n \tALLOC_GROW(parallel_checkout.items, parallel_checkout.nr + 1,\n \t\t   parallel_checkout.alloc);\n \n-\tpc_item = &parallel_checkout.items[parallel_checkout.nr++];\n+\tpc_item = &parallel_checkout.items[parallel_checkout.nr];\n \tpc_item->ce = ce;\n \tmemcpy(&pc_item->ca, ca, sizeof(pc_item->ca));\n \tpc_item->status = PC_ITEM_PENDING;\n+\tpc_item->id = parallel_checkout.nr;\n+\tparallel_checkout.nr++;\n \n \treturn 0;\n }\n@@ -231,7 +233,8 @@ static int write_pc_item_to_fd(struct parallel_checkout_item *pc_item, int fd,\n \t/*\n \t * checkout metadata is used to give context for external process\n \t * filters. Files requiring such filters are not eligible for parallel\n-\t * checkout, so pass NULL.\n+\t * checkout, so pass NULL. Note: if that changes, the metadata must also\n+\t * be passed from the main process to the workers.\n \t */\n \tret = convert_to_working_tree_ca(&pc_item->ca, pc_item->ce->name,\n \t\t\t\t\t new_blob, size, &buf, NULL);\n@@ -272,8 +275,8 @@ static int check_leading_dirs(const char *path, int len, int prefix_len)\n \treturn has_dirs_only_path(path, slash - path, prefix_len);\n }\n \n-static void write_pc_item(struct parallel_checkout_item *pc_item,\n-\t\t\t  struct checkout *state)\n+void write_pc_item(struct parallel_checkout_item *pc_item,\n+\t\t   struct checkout *state)\n {\n \tunsigned int mode = (pc_item->ce->ce_mode & 0100) ? 0777 : 0666;\n \tint fd = -1, fstat_done = 0;\n@@ -343,6 +346,221 @@ static void write_pc_item(struct parallel_checkout_item *pc_item,\n \tstrbuf_release(&path);\n }\n \n+static void send_one_item(int fd, struct parallel_checkout_item *pc_item)\n+{\n+\tsize_t len_data;\n+\tchar *data, *variant;\n+\tstruct pc_item_fixed_portion *fixed_portion;\n+\tconst char *working_tree_encoding = pc_item->ca.working_tree_encoding;\n+\tsize_t name_len = pc_item->ce->ce_namelen;\n+\tsize_t working_tree_encoding_len = working_tree_encoding ?\n+\t\t\t\t\t   strlen(working_tree_encoding) : 0;\n+\n+\tlen_data = sizeof(struct pc_item_fixed_portion) + name_len +\n+\t\t   working_tree_encoding_len;\n+\n+\tdata = xcalloc(1, len_data);\n+\n+\tfixed_portion = (struct pc_item_fixed_portion *)data;\n+\tfixed_portion->id = pc_item->id;\n+\tfixed_portion->ce_mode = pc_item->ce->ce_mode;\n+\tfixed_portion->crlf_action = pc_item->ca.crlf_action;\n+\tfixed_portion->ident = pc_item->ca.ident;\n+\tfixed_portion->name_len = name_len;\n+\tfixed_portion->working_tree_encoding_len = working_tree_encoding_len;\n+\t/*\n+\t * We use hashcpy() instead of oidcpy() because the hash[] positions\n+\t * after `the_hash_algo->rawsz` might not be initialized. And Valgrind\n+\t * would complain about passing uninitialized bytes to a syscall\n+\t * (write(2)). There is no real harm in this case, but the warning could\n+\t * hinder the detection of actual errors.\n+\t */\n+\thashcpy(fixed_portion->oid.hash, pc_item->ce->oid.hash);\n+\n+\tvariant = data + sizeof(*fixed_portion);\n+\tif (working_tree_encoding_len) {\n+\t\tmemcpy(variant, working_tree_encoding, working_tree_encoding_len);\n+\t\tvariant += working_tree_encoding_len;\n+\t}\n+\tmemcpy(variant, pc_item->ce->name, name_len);\n+\n+\tpacket_write(fd, data, len_data);\n+\n+\tfree(data);\n+}\n+\n+static void send_batch(int fd, size_t start, size_t nr)\n+{\n+\tsize_t i;\n+\tfor (i = 0; i < nr; ++i)\n+\t\tsend_one_item(fd, &parallel_checkout.items[start + i]);\n+\tpacket_flush(fd);\n+}\n+\n+static struct pc_worker *setup_workers(struct checkout *state, int num_workers)\n+{\n+\tstruct pc_worker *workers;\n+\tint i, workers_with_one_extra_item;\n+\tsize_t base_batch_size, next_to_assign = 0;\n+\n+\tALLOC_ARRAY(workers, num_workers);\n+\n+\tfor (i = 0; i < num_workers; ++i) {\n+\t\tstruct child_process *cp = &workers[i].cp;\n+\n+\t\tchild_process_init(cp);\n+\t\tcp->git_cmd = 1;\n+\t\tcp->in = -1;\n+\t\tcp->out = -1;\n+\t\tcp->clean_on_exit = 1;\n+\t\tstrvec_push(&cp->args, \"checkout--helper\");\n+\t\tif (state->base_dir_len)\n+\t\t\tstrvec_pushf(&cp->args, \"--prefix=%s\", state->base_dir);\n+\t\tif (start_command(cp))\n+\t\t\tdie(_(\"failed to spawn checkout worker\"));\n+\t}\n+\n+\tbase_batch_size = parallel_checkout.nr / num_workers;\n+\tworkers_with_one_extra_item = parallel_checkout.nr % num_workers;\n+\n+\tfor (i = 0; i < num_workers; ++i) {\n+\t\tstruct pc_worker *worker = &workers[i];\n+\t\tsize_t batch_size = base_batch_size;\n+\n+\t\t/* distribute the extra work evenly */\n+\t\tif (i < workers_with_one_extra_item)\n+\t\t\tbatch_size++;\n+\n+\t\tsend_batch(worker->cp.in, next_to_assign, batch_size);\n+\t\tworker->next_to_complete = next_to_assign;\n+\t\tworker->nr_to_complete = batch_size;\n+\n+\t\tnext_to_assign += batch_size;\n+\t}\n+\n+\treturn workers;\n+}\n+\n+static void finish_workers(struct pc_worker *workers, int num_workers)\n+{\n+\tint i;\n+\n+\t/*\n+\t * Close pipes before calling finish_command() to let the workers\n+\t * exit asynchronously and avoid spending extra time on wait().\n+\t */\n+\tfor (i = 0; i < num_workers; ++i) {\n+\t\tstruct child_process *cp = &workers[i].cp;\n+\t\tif (cp->in >= 0)\n+\t\t\tclose(cp->in);\n+\t\tif (cp->out >= 0)\n+\t\t\tclose(cp->out);\n+\t}\n+\n+\tfor (i = 0; i < num_workers; ++i) {\n+\t\tif (finish_command(&workers[i].cp))\n+\t\t\terror(_(\"checkout worker %d finished with error\"), i);\n+\t}\n+\n+\tfree(workers);\n+}\n+\n+#define ASSERT_PC_ITEM_RESULT_SIZE(got, exp) \\\n+{ \\\n+\tif (got != exp) \\\n+\t\tBUG(\"corrupted result from checkout worker (got %dB, exp %dB)\", \\\n+\t\t    got, exp); \\\n+} while(0)\n+\n+static void parse_and_save_result(const char *line, int len,\n+\t\t\t\t  struct pc_worker *worker)\n+{\n+\tstruct pc_item_result *res;\n+\tstruct parallel_checkout_item *pc_item;\n+\tstruct stat *st = NULL;\n+\n+\tif (len < PC_ITEM_RESULT_BASE_SIZE)\n+\t\tBUG(\"too short result from checkout worker (got %dB, exp %dB)\",\n+\t\t    len, (int)PC_ITEM_RESULT_BASE_SIZE);\n+\n+\tres = (struct pc_item_result *)line;\n+\n+\t/*\n+\t * Worker should send either the full result struct on success, or\n+\t * just the base (i.e. no stat data), otherwise.\n+\t */\n+\tif (res->status == PC_ITEM_WRITTEN) {\n+\t\tASSERT_PC_ITEM_RESULT_SIZE(len, (int)sizeof(struct pc_item_result));\n+\t\tst = &res->st;\n+\t} else {\n+\t\tASSERT_PC_ITEM_RESULT_SIZE(len, (int)PC_ITEM_RESULT_BASE_SIZE);\n+\t}\n+\n+\tif (!worker->nr_to_complete || res->id != worker->next_to_complete)\n+\t\tBUG(\"checkout worker sent unexpected item id\");\n+\n+\tworker->next_to_complete++;\n+\tworker->nr_to_complete--;\n+\n+\tpc_item = &parallel_checkout.items[res->id];\n+\tpc_item->status = res->status;\n+\tif (st)\n+\t\tpc_item->st = *st;\n+}\n+\n+\n+static void gather_results_from_workers(struct pc_worker *workers,\n+\t\t\t\t\tint num_workers)\n+{\n+\tint i, active_workers = num_workers;\n+\tstruct pollfd *pfds;\n+\n+\tCALLOC_ARRAY(pfds, num_workers);\n+\tfor (i = 0; i < num_workers; ++i) {\n+\t\tpfds[i].fd = workers[i].cp.out;\n+\t\tpfds[i].events = POLLIN;\n+\t}\n+\n+\twhile (active_workers) {\n+\t\tint nr = poll(pfds, num_workers, -1);\n+\n+\t\tif (nr < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\tdie_errno(\"failed to poll checkout workers\");\n+\t\t}\n+\n+\t\tfor (i = 0; i < num_workers && nr > 0; ++i) {\n+\t\t\tstruct pc_worker *worker = &workers[i];\n+\t\t\tstruct pollfd *pfd = &pfds[i];\n+\n+\t\t\tif (!pfd->revents)\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (pfd->revents & POLLIN) {\n+\t\t\t\tint len;\n+\t\t\t\tconst char *line = packet_read_line(pfd->fd, &len);\n+\n+\t\t\t\tif (!line) {\n+\t\t\t\t\tpfd->fd = -1;\n+\t\t\t\t\tactive_workers--;\n+\t\t\t\t} else {\n+\t\t\t\t\tparse_and_save_result(line, len, worker);\n+\t\t\t\t}\n+\t\t\t} else if (pfd->revents & POLLHUP) {\n+\t\t\t\tpfd->fd = -1;\n+\t\t\t\tactive_workers--;\n+\t\t\t} else if (pfd->revents & (POLLNVAL | POLLERR)) {\n+\t\t\t\tdie(_(\"error polling from checkout worker\"));\n+\t\t\t}\n+\n+\t\t\tnr--;\n+\t\t}\n+\t}\n+\n+\tfree(pfds);\n+}\n+\n static void write_items_sequentially(struct checkout *state)\n {\n \tsize_t i;\n@@ -351,7 +569,7 @@ static void write_items_sequentially(struct checkout *state)\n \t\twrite_pc_item(&parallel_checkout.items[i], state);\n }\n \n-int run_parallel_checkout(struct checkout *state)\n+int run_parallel_checkout(struct checkout *state, int num_workers, int threshold)\n {\n \tint ret;\n \n@@ -360,7 +578,17 @@ int run_parallel_checkout(struct checkout *state)\n \n \tparallel_checkout.status = PC_RUNNING;\n \n-\twrite_items_sequentially(state);\n+\tif (parallel_checkout.nr < num_workers)\n+\t\tnum_workers = parallel_checkout.nr;\n+\n+\tif (num_workers <= 1 || parallel_checkout.nr < threshold) {\n+\t\twrite_items_sequentially(state);\n+\t} else {\n+\t\tstruct pc_worker *workers = setup_workers(state, num_workers);\n+\t\tgather_results_from_workers(workers, num_workers);\n+\t\tfinish_workers(workers, num_workers);\n+\t}\n+\n \tret = handle_results(state);\n \n \tfinish_parallel_checkout();\ndiff --git a/parallel-checkout.h b/parallel-checkout.h\nindex e6d6fc01ea..0c9984584e 100644\n--- a/parallel-checkout.h\n+++ b/parallel-checkout.h\n@@ -1,9 +1,12 @@\n #ifndef PARALLEL_CHECKOUT_H\n #define PARALLEL_CHECKOUT_H\n \n-struct cache_entry;\n-struct checkout;\n-struct conv_attrs;\n+#include \"entry.h\"\n+#include \"convert.h\"\n+\n+/****************************************************************\n+ * Users of parallel checkout\n+ ****************************************************************/\n \n enum pc_status {\n \tPC_UNINITIALIZED = 0,\n@@ -12,6 +15,7 @@ enum pc_status {\n };\n \n enum pc_status parallel_checkout_status(void);\n+void get_parallel_checkout_configs(int *num_workers, int *threshold);\n void init_parallel_checkout(void);\n \n /*\n@@ -21,7 +25,77 @@ void init_parallel_checkout(void);\n  */\n int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca);\n \n-/* Write all the queued entries, returning 0 on success.*/\n-int run_parallel_checkout(struct checkout *state);\n+/*\n+ * Write all the queued entries, returning 0 on success. If the number of\n+ * entries is smaller than the specified threshold, the operation is performed\n+ * sequentially.\n+ */\n+int run_parallel_checkout(struct checkout *state, int num_workers, int threshold);\n+\n+/****************************************************************\n+ * Interface with checkout--helper\n+ ****************************************************************/\n+\n+enum pc_item_status {\n+\tPC_ITEM_PENDING = 0,\n+\tPC_ITEM_WRITTEN,\n+\t/*\n+\t * The entry could not be written because there was another file\n+\t * already present in its path or leading directories. Since\n+\t * checkout_entry_ca() removes such files from the working tree before\n+\t * enqueueing the entry for parallel checkout, it means that there was\n+\t * a path collision among the entries being written.\n+\t */\n+\tPC_ITEM_COLLIDED,\n+\tPC_ITEM_FAILED,\n+};\n+\n+struct parallel_checkout_item {\n+\t/*\n+\t * In main process ce points to a istate->cache[] entry. Thus, it's not\n+\t * owned by us. In workers they own the memory, which *must be* released.\n+\t */\n+\tstruct cache_entry *ce;\n+\tstruct conv_attrs ca;\n+\tsize_t id; /* position in parallel_checkout.items[] of main process */\n+\n+\t/* Output fields, sent from workers. */\n+\tenum pc_item_status status;\n+\tstruct stat st;\n+};\n+\n+/*\n+ * The fixed-size portion of `struct parallel_checkout_item` that is sent to the\n+ * workers. Following this will be 2 strings: ca.working_tree_encoding and\n+ * ce.name; These are NOT null terminated, since we have the size in the fixed\n+ * portion.\n+ *\n+ * Note that not all fields of conv_attrs and cache_entry are passed, only the\n+ * ones that will be required by the workers to smudge and write the entry.\n+ */\n+struct pc_item_fixed_portion {\n+\tsize_t id;\n+\tstruct object_id oid;\n+\tunsigned int ce_mode;\n+\tenum crlf_action crlf_action;\n+\tint ident;\n+\tsize_t working_tree_encoding_len;\n+\tsize_t name_len;\n+};\n+\n+/*\n+ * The fields of `struct parallel_checkout_item` that are returned by the\n+ * workers. Note: `st` must be the last one, as it is omitted on error.\n+ */\n+struct pc_item_result {\n+\tsize_t id;\n+\tenum pc_item_status status;\n+\tstruct stat st;\n+};\n+\n+#define PC_ITEM_RESULT_BASE_SIZE offsetof(struct pc_item_result, st)\n+\n+void write_pc_item(struct parallel_checkout_item *pc_item,\n+\t\t   struct checkout *state);\n \n #endif /* PARALLEL_CHECKOUT_H */\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 1b1da7485a..117ed42370 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -399,7 +399,7 @@ static int check_updates(struct unpack_trees_options *o,\n \tint errs = 0;\n \tstruct progress *progress;\n \tstruct checkout state = CHECKOUT_INIT;\n-\tint i;\n+\tint i, pc_workers, pc_threshold;\n \n \ttrace_performance_enter();\n \tstate.force = 1;\n@@ -462,8 +462,11 @@ static int check_updates(struct unpack_trees_options *o,\n \t\toid_array_clear(&to_fetch);\n \t}\n \n+\tget_parallel_checkout_configs(&pc_workers, &pc_threshold);\n+\n \tenable_delayed_checkout(&state);\n-\tinit_parallel_checkout();\n+\tif (pc_workers > 1)\n+\t\tinit_parallel_checkout();\n \tfor (i = 0; i < index->cache_nr; i++) {\n \t\tstruct cache_entry *ce = index->cache[i];\n \n@@ -477,7 +480,8 @@ static int check_updates(struct unpack_trees_options *o,\n \t\t}\n \t}\n \tstop_progress(&progress);\n-\terrs |= run_parallel_checkout(&state);\n+\tif (pc_workers > 1)\n+\t\terrs |= run_parallel_checkout(&state, pc_workers, pc_threshold);\n \terrs |= finish_delayed_checkout(&state, NULL);\n \tgit_attr_set_direction(GIT_ATTR_CHECKIN);\n \n-- \n2.28.0\n\n"},{"id":"408629","messageId":"9cfeb4821ca88fe25122798239316c4524f28c92.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 12/19] parallel-checkout: support progress displaying","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:49Z","receivedAt":"2020-10-29T02:16:53Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Original-patch-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n parallel-checkout.c | 34 +++++++++++++++++++++++++++++++---\n parallel-checkout.h |  4 +++-\n unpack-trees.c      | 11 ++++++++---\n 3 files changed, 42 insertions(+), 7 deletions(-)\n\ndiff --git a/parallel-checkout.c b/parallel-checkout.c\nindex a5508e27c2..c5c449d224 100644\n--- a/parallel-checkout.c\n+++ b/parallel-checkout.c\n@@ -2,6 +2,7 @@\n #include \"entry.h\"\n #include \"parallel-checkout.h\"\n #include \"pkt-line.h\"\n+#include \"progress.h\"\n #include \"run-command.h\"\n #include \"streaming.h\"\n #include \"thread-utils.h\"\n@@ -16,6 +17,8 @@ struct parallel_checkout {\n \tenum pc_status status;\n \tstruct parallel_checkout_item *items;\n \tsize_t nr, alloc;\n+\tstruct progress *progress;\n+\tunsigned int *progress_cnt;\n };\n \n static struct parallel_checkout parallel_checkout = { 0 };\n@@ -125,6 +128,20 @@ int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca)\n \treturn 0;\n }\n \n+size_t pc_queue_size(void)\n+{\n+\treturn parallel_checkout.nr;\n+}\n+\n+static void advance_progress_meter(void)\n+{\n+\tif (parallel_checkout.progress) {\n+\t\t(*parallel_checkout.progress_cnt)++;\n+\t\tdisplay_progress(parallel_checkout.progress,\n+\t\t\t\t *parallel_checkout.progress_cnt);\n+\t}\n+}\n+\n static int handle_results(struct checkout *state)\n {\n \tint ret = 0;\n@@ -173,6 +190,7 @@ static int handle_results(struct checkout *state)\n \t\t\t */\n \t\t\tret |= checkout_entry_ca(pc_item->ce, &pc_item->ca,\n \t\t\t\t\t\t state, NULL, NULL);\n+\t\t\tadvance_progress_meter();\n \t\t\tbreak;\n \t\tcase PC_ITEM_PENDING:\n \t\t\thave_pending = 1;\n@@ -506,6 +524,9 @@ static void parse_and_save_result(const char *line, int len,\n \tpc_item->status = res->status;\n \tif (st)\n \t\tpc_item->st = *st;\n+\n+\tif (res->status != PC_ITEM_COLLIDED)\n+\t\tadvance_progress_meter();\n }\n \n \n@@ -565,11 +586,16 @@ static void write_items_sequentially(struct checkout *state)\n {\n \tsize_t i;\n \n-\tfor (i = 0; i < parallel_checkout.nr; ++i)\n-\t\twrite_pc_item(&parallel_checkout.items[i], state);\n+\tfor (i = 0; i < parallel_checkout.nr; ++i) {\n+\t\tstruct parallel_checkout_item *pc_item = &parallel_checkout.items[i];\n+\t\twrite_pc_item(pc_item, state);\n+\t\tif (pc_item->status != PC_ITEM_COLLIDED)\n+\t\t\tadvance_progress_meter();\n+\t}\n }\n \n-int run_parallel_checkout(struct checkout *state, int num_workers, int threshold)\n+int run_parallel_checkout(struct checkout *state, int num_workers, int threshold,\n+\t\t\t  struct progress *progress, unsigned int *progress_cnt)\n {\n \tint ret;\n \n@@ -577,6 +603,8 @@ int run_parallel_checkout(struct checkout *state, int num_workers, int threshold\n \t\tBUG(\"cannot run parallel checkout: uninitialized or already running\");\n \n \tparallel_checkout.status = PC_RUNNING;\n+\tparallel_checkout.progress = progress;\n+\tparallel_checkout.progress_cnt = progress_cnt;\n \n \tif (parallel_checkout.nr < num_workers)\n \t\tnum_workers = parallel_checkout.nr;\ndiff --git a/parallel-checkout.h b/parallel-checkout.h\nindex 0c9984584e..6c3a016c0b 100644\n--- a/parallel-checkout.h\n+++ b/parallel-checkout.h\n@@ -24,13 +24,15 @@ void init_parallel_checkout(void);\n  * write and return 0.\n  */\n int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca);\n+size_t pc_queue_size(void);\n \n /*\n  * Write all the queued entries, returning 0 on success. If the number of\n  * entries is smaller than the specified threshold, the operation is performed\n  * sequentially.\n  */\n-int run_parallel_checkout(struct checkout *state, int num_workers, int threshold);\n+int run_parallel_checkout(struct checkout *state, int num_workers, int threshold,\n+\t\t\t  struct progress *progress, unsigned int *progress_cnt);\n \n /****************************************************************\n  * Interface with checkout--helper\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 117ed42370..e05e6ceff2 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -471,17 +471,22 @@ static int check_updates(struct unpack_trees_options *o,\n \t\tstruct cache_entry *ce = index->cache[i];\n \n \t\tif (ce->ce_flags & CE_UPDATE) {\n+\t\t\tsize_t last_pc_queue_size = pc_queue_size();\n+\n \t\t\tif (ce->ce_flags & CE_WT_REMOVE)\n \t\t\t\tBUG(\"both update and delete flags are set on %s\",\n \t\t\t\t    ce->name);\n-\t\t\tdisplay_progress(progress, ++cnt);\n \t\t\tce->ce_flags &= ~CE_UPDATE;\n \t\t\terrs |= checkout_entry(ce, &state, NULL, NULL);\n+\n+\t\t\tif (last_pc_queue_size == pc_queue_size())\n+\t\t\t\tdisplay_progress(progress, ++cnt);\n \t\t}\n \t}\n-\tstop_progress(&progress);\n \tif (pc_workers > 1)\n-\t\terrs |= run_parallel_checkout(&state, pc_workers, pc_threshold);\n+\t\terrs |= run_parallel_checkout(&state, pc_workers, pc_threshold,\n+\t\t\t\t\t      progress, &cnt);\n+\tstop_progress(&progress);\n \terrs |= finish_delayed_checkout(&state, NULL);\n \tgit_attr_set_direction(GIT_ATTR_CHECKIN);\n \n-- \n2.28.0\n\n"},{"id":"408628","messageId":"81e03baab1dd7e28262e1d721eac1646c5908b5a.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 08/19] entry: move conv_attrs lookup up to checkout_entry()","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:45Z","receivedAt":"2020-10-29T02:16:56Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"In a following patch, checkout_entry() will use conv_attrs to decide\nwhether an entry should be enqueued for parallel checkout or not. But\nthe attributes lookup only happens lower in this call stack. To avoid\nthe unnecessary work of loading the attributes twice, let's move it up\nto checkout_entry(), and pass the loaded struct down to write_entry().\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 38 +++++++++++++++++++++++++++-----------\n 1 file changed, 27 insertions(+), 11 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex 1d2df188e5..8237859b12 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -263,8 +263,9 @@ void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,\n \t}\n }\n \n-static int write_entry(struct cache_entry *ce,\n-\t\t       char *path, const struct checkout *state, int to_tempfile)\n+/* Note: ca is used (and required) iff the entry refers to a regular file. */\n+static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca,\n+\t\t       const struct checkout *state, int to_tempfile)\n {\n \tunsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;\n \tstruct delayed_checkout *dco = state->delayed_checkout;\n@@ -281,8 +282,7 @@ static int write_entry(struct cache_entry *ce,\n \tclone_checkout_metadata(&meta, &state->meta, &ce->oid);\n \n \tif (ce_mode_s_ifmt == S_IFREG) {\n-\t\tstruct stream_filter *filter = get_stream_filter(state->istate, ce->name,\n-\t\t\t\t\t\t\t\t &ce->oid);\n+\t\tstruct stream_filter *filter = get_stream_filter_ca(ca, &ce->oid);\n \t\tif (filter &&\n \t\t    !streaming_write_entry(ce, path, filter,\n \t\t\t\t\t   state, to_tempfile,\n@@ -329,14 +329,17 @@ static int write_entry(struct cache_entry *ce,\n \t\t * Convert from git internal format to working tree format\n \t\t */\n \t\tif (dco && dco->state != CE_NO_DELAY) {\n-\t\t\tret = async_convert_to_working_tree(state->istate, ce->name, new_blob,\n-\t\t\t\t\t\t\t    size, &buf, &meta, dco);\n+\t\t\tret = async_convert_to_working_tree_ca(ca, ce->name,\n+\t\t\t\t\t\t\t       new_blob, size,\n+\t\t\t\t\t\t\t       &buf, &meta, dco);\n \t\t\tif (ret && string_list_has_string(&dco->paths, ce->name)) {\n \t\t\t\tfree(new_blob);\n \t\t\t\tgoto delayed;\n \t\t\t}\n-\t\t} else\n-\t\t\tret = convert_to_working_tree(state->istate, ce->name, new_blob, size, &buf, &meta);\n+\t\t} else {\n+\t\t\tret = convert_to_working_tree_ca(ca, ce->name, new_blob,\n+\t\t\t\t\t\t\t size, &buf, &meta);\n+\t\t}\n \n \t\tif (ret) {\n \t\t\tfree(new_blob);\n@@ -442,6 +445,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n {\n \tstatic struct strbuf path = STRBUF_INIT;\n \tstruct stat st;\n+\tstruct conv_attrs ca;\n \n \tif (ce->ce_flags & CE_WT_REMOVE) {\n \t\tif (topath)\n@@ -454,8 +458,13 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t\treturn 0;\n \t}\n \n-\tif (topath)\n-\t\treturn write_entry(ce, topath, state, 1);\n+\tif (topath) {\n+\t\tif (S_ISREG(ce->ce_mode)) {\n+\t\t\tconvert_attrs(state->istate, &ca, ce->name);\n+\t\t\treturn write_entry(ce, topath, &ca, state, 1);\n+\t\t}\n+\t\treturn write_entry(ce, topath, NULL, state, 1);\n+\t}\n \n \tstrbuf_reset(&path);\n \tstrbuf_add(&path, state->base_dir, state->base_dir_len);\n@@ -517,9 +526,16 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t\treturn 0;\n \n \tcreate_directories(path.buf, path.len, state);\n+\n \tif (nr_checkouts)\n \t\t(*nr_checkouts)++;\n-\treturn write_entry(ce, path.buf, state, 0);\n+\n+\tif (S_ISREG(ce->ce_mode)) {\n+\t\tconvert_attrs(state->istate, &ca, ce->name);\n+\t\treturn write_entry(ce, path.buf, &ca, state, 0);\n+\t}\n+\n+\treturn write_entry(ce, path.buf, NULL, state, 0);\n }\n \n void unlink_entry(const struct cache_entry *ce)\n-- \n2.28.0\n\n"},{"id":"408631","messageId":"2caa2c4345d524e9e3bb0c388f8dc0b99236d166.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 05/19] entry: extract a header file for entry.c functions","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:42Z","receivedAt":"2020-10-29T02:16:58Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"The declarations of entry.c's public functions and structures currently\nreside in cache.h. Although not many, they contribute to the size of\ncache.h and, when changed, cause the unnecessary recompilation of\nmodules that don't really use these functions. So let's move them to a\nnew entry.h header.\n\nOriginal-patch-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n apply.c                  |  1 +\n builtin/checkout-index.c |  1 +\n builtin/checkout.c       |  1 +\n builtin/difftool.c       |  1 +\n cache.h                  | 24 -----------------------\n entry.c                  |  9 +--------\n entry.h                  | 41 ++++++++++++++++++++++++++++++++++++++++\n unpack-trees.c           |  1 +\n 8 files changed, 47 insertions(+), 32 deletions(-)\n create mode 100644 entry.h\n\ndiff --git a/apply.c b/apply.c\nindex 76dba93c97..ddec80b4b0 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -21,6 +21,7 @@\n #include \"quote.h\"\n #include \"rerere.h\"\n #include \"apply.h\"\n+#include \"entry.h\"\n \n struct gitdiff_data {\n \tstruct strbuf *root;\ndiff --git a/builtin/checkout-index.c b/builtin/checkout-index.c\nindex 4bbfc92dce..9276ed0258 100644\n--- a/builtin/checkout-index.c\n+++ b/builtin/checkout-index.c\n@@ -11,6 +11,7 @@\n #include \"quote.h\"\n #include \"cache-tree.h\"\n #include \"parse-options.h\"\n+#include \"entry.h\"\n \n #define CHECKOUT_ALL 4\n static int nul_term_line;\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 0951f8fee5..b18b9d6f3c 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -26,6 +26,7 @@\n #include \"unpack-trees.h\"\n #include \"wt-status.h\"\n #include \"xdiff-interface.h\"\n+#include \"entry.h\"\n \n static const char * const checkout_usage[] = {\n \tN_(\"git checkout [<options>] <branch>\"),\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 7ac432b881..dfa22b67eb 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -23,6 +23,7 @@\n #include \"lockfile.h\"\n #include \"object-store.h\"\n #include \"dir.h\"\n+#include \"entry.h\"\n \n static int trust_exit_code;\n \ndiff --git a/cache.h b/cache.h\nindex c0072d43b1..ccfeb9ba2b 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1706,30 +1706,6 @@ const char *show_ident_date(const struct ident_split *id,\n  */\n int ident_cmp(const struct ident_split *, const struct ident_split *);\n \n-struct checkout {\n-\tstruct index_state *istate;\n-\tconst char *base_dir;\n-\tint base_dir_len;\n-\tstruct delayed_checkout *delayed_checkout;\n-\tstruct checkout_metadata meta;\n-\tunsigned force:1,\n-\t\t quiet:1,\n-\t\t not_new:1,\n-\t\t clone:1,\n-\t\t refresh_cache:1;\n-};\n-#define CHECKOUT_INIT { NULL, \"\" }\n-\n-#define TEMPORARY_FILENAME_LENGTH 25\n-int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath, int *nr_checkouts);\n-void enable_delayed_checkout(struct checkout *state);\n-int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n-/*\n- * Unlink the last component and schedule the leading directories for\n- * removal, such that empty directories get removed.\n- */\n-void unlink_entry(const struct cache_entry *ce);\n-\n struct cache_def {\n \tstruct strbuf path;\n \tint flags;\ndiff --git a/entry.c b/entry.c\nindex a0532f1f00..b0b8099699 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -6,6 +6,7 @@\n #include \"submodule.h\"\n #include \"progress.h\"\n #include \"fsmonitor.h\"\n+#include \"entry.h\"\n \n static void create_directories(const char *path, int path_len,\n \t\t\t       const struct checkout *state)\n@@ -429,14 +430,6 @@ static void mark_colliding_entries(const struct checkout *state,\n \t}\n }\n \n-/*\n- * Write the contents from ce out to the working tree.\n- *\n- * When topath[] is not NULL, instead of writing to the working tree\n- * file named by ce, a temporary file is created by this function and\n- * its name is returned in topath[], which must be able to hold at\n- * least TEMPORARY_FILENAME_LENGTH bytes long.\n- */\n int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t\t   char *topath, int *nr_checkouts)\n {\ndiff --git a/entry.h b/entry.h\nnew file mode 100644\nindex 0000000000..2d69185448\n--- /dev/null\n+++ b/entry.h\n@@ -0,0 +1,41 @@\n+#ifndef ENTRY_H\n+#define ENTRY_H\n+\n+#include \"cache.h\"\n+#include \"convert.h\"\n+\n+struct checkout {\n+\tstruct index_state *istate;\n+\tconst char *base_dir;\n+\tint base_dir_len;\n+\tstruct delayed_checkout *delayed_checkout;\n+\tstruct checkout_metadata meta;\n+\tunsigned force:1,\n+\t\t quiet:1,\n+\t\t not_new:1,\n+\t\t clone:1,\n+\t\t refresh_cache:1;\n+};\n+#define CHECKOUT_INIT { NULL, \"\" }\n+\n+#define TEMPORARY_FILENAME_LENGTH 25\n+\n+/*\n+ * Write the contents from ce out to the working tree.\n+ *\n+ * When topath[] is not NULL, instead of writing to the working tree\n+ * file named by ce, a temporary file is created by this function and\n+ * its name is returned in topath[], which must be able to hold at\n+ * least TEMPORARY_FILENAME_LENGTH bytes long.\n+ */\n+int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n+\t\t   char *topath, int *nr_checkouts);\n+void enable_delayed_checkout(struct checkout *state);\n+int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n+/*\n+ * Unlink the last component and schedule the leading directories for\n+ * removal, such that empty directories get removed.\n+ */\n+void unlink_entry(const struct cache_entry *ce);\n+\n+#endif /* ENTRY_H */\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 323280dd48..a511fadd89 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -16,6 +16,7 @@\n #include \"fsmonitor.h\"\n #include \"object-store.h\"\n #include \"promisor-remote.h\"\n+#include \"entry.h\"\n \n /*\n  * Error messages expected by scripts out of plumbing commands such as\n-- \n2.28.0\n\n"},{"id":"408630","messageId":"91ef17f533e6ed8ba2410ca6b966f06ca40973bb.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 07/19] entry: extract cache_entry update from write_entry()","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:44Z","receivedAt":"2020-10-29T02:16:59Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"This code will be used by the parallel checkout functions, outside\nentry.c, so extract it to a public function.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 25 ++++++++++++++++---------\n entry.h |  2 ++\n 2 files changed, 18 insertions(+), 9 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex b36071a610..1d2df188e5 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -251,6 +251,18 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts)\n \treturn errs;\n }\n \n+void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,\n+\t\t\t   struct stat *st)\n+{\n+\tif (state->refresh_cache) {\n+\t\tassert(state->istate);\n+\t\tfill_stat_cache_info(state->istate, ce, st);\n+\t\tce->ce_flags |= CE_UPDATE_IN_BASE;\n+\t\tmark_fsmonitor_invalid(state->istate, ce);\n+\t\tstate->istate->cache_changed |= CE_ENTRY_CHANGED;\n+\t}\n+}\n+\n static int write_entry(struct cache_entry *ce,\n \t\t       char *path, const struct checkout *state, int to_tempfile)\n {\n@@ -371,15 +383,10 @@ static int write_entry(struct cache_entry *ce,\n \n finish:\n \tif (state->refresh_cache) {\n-\t\tassert(state->istate);\n-\t\tif (!fstat_done)\n-\t\t\tif (lstat(ce->name, &st) < 0)\n-\t\t\t\treturn error_errno(\"unable to stat just-written file %s\",\n-\t\t\t\t\t\t   ce->name);\n-\t\tfill_stat_cache_info(state->istate, ce, &st);\n-\t\tce->ce_flags |= CE_UPDATE_IN_BASE;\n-\t\tmark_fsmonitor_invalid(state->istate, ce);\n-\t\tstate->istate->cache_changed |= CE_ENTRY_CHANGED;\n+\t\tif (!fstat_done && lstat(ce->name, &st) < 0)\n+\t\t\treturn error_errno(\"unable to stat just-written file %s\",\n+\t\t\t\t\t   ce->name);\n+\t\tupdate_ce_after_write(state, ce , &st);\n \t}\n delayed:\n \treturn 0;\ndiff --git a/entry.h b/entry.h\nindex f860e60846..664aed1576 100644\n--- a/entry.h\n+++ b/entry.h\n@@ -39,5 +39,7 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n void unlink_entry(const struct cache_entry *ce);\n void *read_blob_entry(const struct cache_entry *ce, unsigned long *size);\n int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st);\n+void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,\n+\t\t\t   struct stat *st);\n \n #endif /* ENTRY_H */\n-- \n2.28.0\n\n"},{"id":"408632","messageId":"bfa52df9e2a51c62cf75ac18603f1bbf4f7c3081.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 06/19] entry: make fstat_output() and read_blob_entry() public","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:43Z","receivedAt":"2020-10-29T02:16:59Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"These two functions will be used by the parallel checkout code, so let's\nmake them public. Note: fstat_output() is renamed to\nfstat_checkout_output(), now that it has become public, seeking to avoid\nfuture name collisions.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 8 ++++----\n entry.h | 2 ++\n 2 files changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex b0b8099699..b36071a610 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -84,7 +84,7 @@ static int create_file(const char *path, unsigned int mode)\n \treturn open(path, O_WRONLY | O_CREAT | O_EXCL, mode);\n }\n \n-static void *read_blob_entry(const struct cache_entry *ce, unsigned long *size)\n+void *read_blob_entry(const struct cache_entry *ce, unsigned long *size)\n {\n \tenum object_type type;\n \tvoid *blob_data = read_object_file(&ce->oid, &type, size);\n@@ -109,7 +109,7 @@ static int open_output_fd(char *path, const struct cache_entry *ce, int to_tempf\n \t}\n }\n \n-static int fstat_output(int fd, const struct checkout *state, struct stat *st)\n+int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st)\n {\n \t/* use fstat() only when path == ce->name */\n \tif (fstat_is_reliable() &&\n@@ -132,7 +132,7 @@ static int streaming_write_entry(const struct cache_entry *ce, char *path,\n \t\treturn -1;\n \n \tresult |= stream_blob_to_fd(fd, &ce->oid, filter, 1);\n-\t*fstat_done = fstat_output(fd, state, statbuf);\n+\t*fstat_done = fstat_checkout_output(fd, state, statbuf);\n \tresult |= close(fd);\n \n \tif (result)\n@@ -346,7 +346,7 @@ static int write_entry(struct cache_entry *ce,\n \n \t\twrote = write_in_full(fd, new_blob, size);\n \t\tif (!to_tempfile)\n-\t\t\tfstat_done = fstat_output(fd, state, &st);\n+\t\t\tfstat_done = fstat_checkout_output(fd, state, &st);\n \t\tclose(fd);\n \t\tfree(new_blob);\n \t\tif (wrote < 0)\ndiff --git a/entry.h b/entry.h\nindex 2d69185448..f860e60846 100644\n--- a/entry.h\n+++ b/entry.h\n@@ -37,5 +37,7 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n  * removal, such that empty directories get removed.\n  */\n void unlink_entry(const struct cache_entry *ce);\n+void *read_blob_entry(const struct cache_entry *ce, unsigned long *size);\n+int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st);\n \n #endif /* ENTRY_H */\n-- \n2.28.0\n\n"},{"id":"408633","messageId":"c77b16f694cfe8076e66f604c0bbe053f58a2e9e.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 03/19] convert: add get_stream_filter_ca() variant","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:40Z","receivedAt":"2020-10-29T02:17:02Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nLike the previous patch, we will also need to call get_stream_filter()\nwith a precomputed `struct conv_attrs`, when we add support for parallel\ncheckout workers. So add the _ca() variant which takes the conversion\nattributes struct as a parameter.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n[matheus.bernardino: move header comment to ca() variant and reword msg]\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 28 +++++++++++++++++-----------\n convert.h |  2 ++\n 2 files changed, 19 insertions(+), 11 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex 191a42a0ae..bd4d3f01cd 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1960,34 +1960,31 @@ static struct stream_filter *ident_filter(const struct object_id *oid)\n }\n \n /*\n- * Return an appropriately constructed filter for the path, or NULL if\n+ * Return an appropriately constructed filter for the given ca, or NULL if\n  * the contents cannot be filtered without reading the whole thing\n  * in-core.\n  *\n  * Note that you would be crazy to set CRLF, smudge/clean or ident to a\n  * large binary blob you would want us not to slurp into the memory!\n  */\n-struct stream_filter *get_stream_filter(const struct index_state *istate,\n-\t\t\t\t\tconst char *path,\n-\t\t\t\t\tconst struct object_id *oid)\n+struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n+\t\t\t\t\t   const struct object_id *oid)\n {\n-\tstruct conv_attrs ca;\n \tstruct stream_filter *filter = NULL;\n \n-\tconvert_attrs(istate, &ca, path);\n-\tif (ca.drv && (ca.drv->process || ca.drv->smudge || ca.drv->clean))\n+\tif (ca->drv && (ca->drv->process || ca->drv->smudge || ca->drv->clean))\n \t\treturn NULL;\n \n-\tif (ca.working_tree_encoding)\n+\tif (ca->working_tree_encoding)\n \t\treturn NULL;\n \n-\tif (ca.crlf_action == CRLF_AUTO || ca.crlf_action == CRLF_AUTO_CRLF)\n+\tif (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF)\n \t\treturn NULL;\n \n-\tif (ca.ident)\n+\tif (ca->ident)\n \t\tfilter = ident_filter(oid);\n \n-\tif (output_eol(ca.crlf_action) == EOL_CRLF)\n+\tif (output_eol(ca->crlf_action) == EOL_CRLF)\n \t\tfilter = cascade_filter(filter, lf_to_crlf_filter());\n \telse\n \t\tfilter = cascade_filter(filter, &null_filter_singleton);\n@@ -1995,6 +1992,15 @@ struct stream_filter *get_stream_filter(const struct index_state *istate,\n \treturn filter;\n }\n \n+struct stream_filter *get_stream_filter(const struct index_state *istate,\n+\t\t\t\t\tconst char *path,\n+\t\t\t\t\tconst struct object_id *oid)\n+{\n+\tstruct conv_attrs ca;\n+\tconvert_attrs(istate, &ca, path);\n+\treturn get_stream_filter_ca(&ca, oid);\n+}\n+\n void free_stream_filter(struct stream_filter *filter)\n {\n \tfilter->vtbl->free(filter);\ndiff --git a/convert.h b/convert.h\nindex 46d537d1ae..262c1a1d46 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -169,6 +169,8 @@ struct stream_filter; /* opaque */\n struct stream_filter *get_stream_filter(const struct index_state *istate,\n \t\t\t\t\tconst char *path,\n \t\t\t\t\tconst struct object_id *);\n+struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n+\t\t\t\t\t   const struct object_id *oid);\n void free_stream_filter(struct stream_filter *);\n int is_null_stream_filter(struct stream_filter *);\n \n-- \n2.28.0\n\n"},{"id":"408634","messageId":"18c3f4247e717a7766f13b4b33a0bbe31aee6b69.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 04/19] convert: add conv_attrs classification","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:41Z","receivedAt":"2020-10-29T02:17:02Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate `enum conv_attrs_classification` to express the different ways\nthat attributes are handled for a blob during checkout.\n\nThis will be used in a later commit when deciding whether to add a file\nto the parallel or delayed queue during checkout. For now, we can also\nuse it in get_stream_filter_ca() to simplify the function (as the\nclassifying logic is the same).\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n[matheus.bernardino: use classification in get_stream_filter_ca()]\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 26 +++++++++++++++++++-------\n convert.h | 33 +++++++++++++++++++++++++++++++++\n 2 files changed, 52 insertions(+), 7 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex bd4d3f01cd..c0b45149b5 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1972,13 +1972,7 @@ struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n {\n \tstruct stream_filter *filter = NULL;\n \n-\tif (ca->drv && (ca->drv->process || ca->drv->smudge || ca->drv->clean))\n-\t\treturn NULL;\n-\n-\tif (ca->working_tree_encoding)\n-\t\treturn NULL;\n-\n-\tif (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF)\n+\tif (classify_conv_attrs(ca) != CA_CLASS_STREAMABLE)\n \t\treturn NULL;\n \n \tif (ca->ident)\n@@ -2034,3 +2028,21 @@ void clone_checkout_metadata(struct checkout_metadata *dst,\n \tif (blob)\n \t\toidcpy(&dst->blob, blob);\n }\n+\n+enum conv_attrs_classification classify_conv_attrs(const struct conv_attrs *ca)\n+{\n+\tif (ca->drv) {\n+\t\tif (ca->drv->process)\n+\t\t\treturn CA_CLASS_INCORE_PROCESS;\n+\t\tif (ca->drv->smudge || ca->drv->clean)\n+\t\t\treturn CA_CLASS_INCORE_FILTER;\n+\t}\n+\n+\tif (ca->working_tree_encoding)\n+\t\treturn CA_CLASS_INCORE;\n+\n+\tif (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF)\n+\t\treturn CA_CLASS_INCORE;\n+\n+\treturn CA_CLASS_STREAMABLE;\n+}\ndiff --git a/convert.h b/convert.h\nindex 262c1a1d46..523ba9b140 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -190,4 +190,37 @@ int stream_filter(struct stream_filter *,\n \t\t  const char *input, size_t *isize_p,\n \t\t  char *output, size_t *osize_p);\n \n+enum conv_attrs_classification {\n+\t/*\n+\t * The blob must be loaded into a buffer before it can be\n+\t * smudged. All smudging is done in-proc.\n+\t */\n+\tCA_CLASS_INCORE,\n+\n+\t/*\n+\t * The blob must be loaded into a buffer, but uses a\n+\t * single-file driver filter, such as rot13.\n+\t */\n+\tCA_CLASS_INCORE_FILTER,\n+\n+\t/*\n+\t * The blob must be loaded into a buffer, but uses a\n+\t * long-running driver process, such as LFS. This might or\n+\t * might not use delayed operations. (The important thing is\n+\t * that there is a single subordinate long-running process\n+\t * handling all associated blobs and in case of delayed\n+\t * operations, may hold per-blob state.)\n+\t */\n+\tCA_CLASS_INCORE_PROCESS,\n+\n+\t/*\n+\t * The blob can be streamed and smudged without needing to\n+\t * completely read it into a buffer.\n+\t */\n+\tCA_CLASS_STREAMABLE,\n+};\n+\n+enum conv_attrs_classification classify_conv_attrs(\n+\tconst struct conv_attrs *ca);\n+\n #endif /* CONVERT_H */\n-- \n2.28.0\n\n"},{"id":"408635","messageId":"c5fbd1e16d616a4c238d98a35f3671238300c17f.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 02/19] convert: add [async_]convert_to_working_tree_ca() variants","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:39Z","receivedAt":"2020-10-29T02:17:05Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSeparate the attribute gathering from the actual conversion by adding\n_ca() variants of the conversion functions. These variants receive a\nprecomputed 'struct conv_attrs', not relying, thus, on a index state.\nThey will be used in a future patch adding parallel checkout support,\nfor two reasons:\n\n- We will already load the conversion attributes in checkout_entry(),\n  before conversion, to decide whether a path is eligible for parallel\n  checkout. Therefore, it would be wasteful to load them again later,\n  for the actual conversion.\n\n- The parallel workers will be responsible for reading, converting and\n  writing blobs to the working tree. They won't have access to the main\n  process' index state, so they cannot load the attributes. Instead,\n  they will receive the preloaded ones and call the _ca() variant of\n  the conversion functions. Furthermore, the attributes machinery is\n  optimized to handle paths in sequential order, so it's better to leave\n  it for the main process, anyway.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n[matheus.bernardino: squash, remove one function definition and reword]\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 50 ++++++++++++++++++++++++++++++++++++--------------\n convert.h |  9 +++++++++\n 2 files changed, 45 insertions(+), 14 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex eb14714979..191a42a0ae 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1447,7 +1447,7 @@ void convert_to_git_filter_fd(const struct index_state *istate,\n \tident_to_git(dst->buf, dst->len, dst, ca.ident);\n }\n \n-static int convert_to_working_tree_internal(const struct index_state *istate,\n+static int convert_to_working_tree_internal(const struct conv_attrs *ca,\n \t\t\t\t\t    const char *path, const char *src,\n \t\t\t\t\t    size_t len, struct strbuf *dst,\n \t\t\t\t\t    int normalizing,\n@@ -1455,11 +1455,8 @@ static int convert_to_working_tree_internal(const struct index_state *istate,\n \t\t\t\t\t    struct delayed_checkout *dco)\n {\n \tint ret = 0, ret_filter = 0;\n-\tstruct conv_attrs ca;\n-\n-\tconvert_attrs(istate, &ca, path);\n \n-\tret |= ident_to_worktree(src, len, dst, ca.ident);\n+\tret |= ident_to_worktree(src, len, dst, ca->ident);\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\n@@ -1469,24 +1466,24 @@ static int convert_to_working_tree_internal(const struct index_state *istate,\n \t * is a smudge or process filter (even if the process filter doesn't\n \t * support smudge).  The filters might expect CRLFs.\n \t */\n-\tif ((ca.drv && (ca.drv->smudge || ca.drv->process)) || !normalizing) {\n-\t\tret |= crlf_to_worktree(src, len, dst, ca.crlf_action);\n+\tif ((ca->drv && (ca->drv->smudge || ca->drv->process)) || !normalizing) {\n+\t\tret |= crlf_to_worktree(src, len, dst, ca->crlf_action);\n \t\tif (ret) {\n \t\t\tsrc = dst->buf;\n \t\t\tlen = dst->len;\n \t\t}\n \t}\n \n-\tret |= encode_to_worktree(path, src, len, dst, ca.working_tree_encoding);\n+\tret |= encode_to_worktree(path, src, len, dst, ca->working_tree_encoding);\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\n \t}\n \n \tret_filter = apply_filter(\n-\t\tpath, src, len, -1, dst, ca.drv, CAP_SMUDGE, meta, dco);\n-\tif (!ret_filter && ca.drv && ca.drv->required)\n-\t\tdie(_(\"%s: smudge filter %s failed\"), path, ca.drv->name);\n+\t\tpath, src, len, -1, dst, ca->drv, CAP_SMUDGE, meta, dco);\n+\tif (!ret_filter && ca->drv && ca->drv->required)\n+\t\tdie(_(\"%s: smudge filter %s failed\"), path, ca->drv->name);\n \n \treturn ret | ret_filter;\n }\n@@ -1497,7 +1494,9 @@ int async_convert_to_working_tree(const struct index_state *istate,\n \t\t\t\t  const struct checkout_metadata *meta,\n \t\t\t\t  void *dco)\n {\n-\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco);\n+\tstruct conv_attrs ca;\n+\tconvert_attrs(istate, &ca, path);\n+\treturn convert_to_working_tree_internal(&ca, path, src, len, dst, 0, meta, dco);\n }\n \n int convert_to_working_tree(const struct index_state *istate,\n@@ -1505,13 +1504,36 @@ int convert_to_working_tree(const struct index_state *istate,\n \t\t\t    size_t len, struct strbuf *dst,\n \t\t\t    const struct checkout_metadata *meta)\n {\n-\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL);\n+\tstruct conv_attrs ca;\n+\tconvert_attrs(istate, &ca, path);\n+\treturn convert_to_working_tree_internal(&ca, path, src, len, dst, 0, meta, NULL);\n+}\n+\n+int async_convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t\t     const char *path, const char *src,\n+\t\t\t\t     size_t len, struct strbuf *dst,\n+\t\t\t\t     const struct checkout_metadata *meta,\n+\t\t\t\t     void *dco)\n+{\n+\treturn convert_to_working_tree_internal(ca, path, src, len, dst, 0, meta, dco);\n+}\n+\n+int convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t       const char *path, const char *src,\n+\t\t\t       size_t len, struct strbuf *dst,\n+\t\t\t       const struct checkout_metadata *meta)\n+{\n+\treturn convert_to_working_tree_internal(ca, path, src, len, dst, 0, meta, NULL);\n }\n \n int renormalize_buffer(const struct index_state *istate, const char *path,\n \t\t       const char *src, size_t len, struct strbuf *dst)\n {\n-\tint ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL, NULL);\n+\tstruct conv_attrs ca;\n+\tint ret;\n+\n+\tconvert_attrs(istate, &ca, path);\n+\tret = convert_to_working_tree_internal(&ca, path, src, len, dst, 1, NULL, NULL);\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\ndiff --git a/convert.h b/convert.h\nindex aeb4a1be9a..46d537d1ae 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -100,11 +100,20 @@ int convert_to_working_tree(const struct index_state *istate,\n \t\t\t    const char *path, const char *src,\n \t\t\t    size_t len, struct strbuf *dst,\n \t\t\t    const struct checkout_metadata *meta);\n+int convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t       const char *path, const char *src,\n+\t\t\t       size_t len, struct strbuf *dst,\n+\t\t\t       const struct checkout_metadata *meta);\n int async_convert_to_working_tree(const struct index_state *istate,\n \t\t\t\t  const char *path, const char *src,\n \t\t\t\t  size_t len, struct strbuf *dst,\n \t\t\t\t  const struct checkout_metadata *meta,\n \t\t\t\t  void *dco);\n+int async_convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t\t     const char *path, const char *src,\n+\t\t\t\t     size_t len, struct strbuf *dst,\n+\t\t\t\t     const struct checkout_metadata *meta,\n+\t\t\t\t     void *dco);\n int async_query_available_blobs(const char *cmd,\n \t\t\t\tstruct string_list *available_paths);\n int renormalize_buffer(const struct index_state *istate,\n-- \n2.28.0\n\n"},{"id":"408636","messageId":"dfc3e0fd62bcf0223dc51485521116e1aca2ee07.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v3 01/19] convert: make convert_attrs() and convert structs public","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:38Z","receivedAt":"2020-10-29T02:17:06Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nMove convert_attrs() declaration from convert.c to convert.h, together\nwith the conv_attrs struct and the crlf_action enum. This function and\nthe data structures will be used outside convert.c in the upcoming\nparallel checkout implementation.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n[matheus.bernardino: squash and reword msg]\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 23 ++---------------------\n convert.h | 24 ++++++++++++++++++++++++\n 2 files changed, 26 insertions(+), 21 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex ee360c2f07..eb14714979 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -24,17 +24,6 @@\n #define CONVERT_STAT_BITS_TXT_CRLF  0x2\n #define CONVERT_STAT_BITS_BIN       0x4\n \n-enum crlf_action {\n-\tCRLF_UNDEFINED,\n-\tCRLF_BINARY,\n-\tCRLF_TEXT,\n-\tCRLF_TEXT_INPUT,\n-\tCRLF_TEXT_CRLF,\n-\tCRLF_AUTO,\n-\tCRLF_AUTO_INPUT,\n-\tCRLF_AUTO_CRLF\n-};\n-\n struct text_stat {\n \t/* NUL, CR, LF and CRLF counts */\n \tunsigned nul, lonecr, lonelf, crlf;\n@@ -1297,18 +1286,10 @@ static int git_path_check_ident(struct attr_check_item *check)\n \treturn !!ATTR_TRUE(value);\n }\n \n-struct conv_attrs {\n-\tstruct convert_driver *drv;\n-\tenum crlf_action attr_action; /* What attr says */\n-\tenum crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n-\tint ident;\n-\tconst char *working_tree_encoding; /* Supported encoding or default encoding if NULL */\n-};\n-\n static struct attr_check *check;\n \n-static void convert_attrs(const struct index_state *istate,\n-\t\t\t  struct conv_attrs *ca, const char *path)\n+void convert_attrs(const struct index_state *istate,\n+\t\t   struct conv_attrs *ca, const char *path)\n {\n \tstruct attr_check_item *ccheck = NULL;\n \ndiff --git a/convert.h b/convert.h\nindex e29d1026a6..aeb4a1be9a 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -37,6 +37,27 @@ enum eol {\n #endif\n };\n \n+enum crlf_action {\n+\tCRLF_UNDEFINED,\n+\tCRLF_BINARY,\n+\tCRLF_TEXT,\n+\tCRLF_TEXT_INPUT,\n+\tCRLF_TEXT_CRLF,\n+\tCRLF_AUTO,\n+\tCRLF_AUTO_INPUT,\n+\tCRLF_AUTO_CRLF\n+};\n+\n+struct convert_driver;\n+\n+struct conv_attrs {\n+\tstruct convert_driver *drv;\n+\tenum crlf_action attr_action; /* What attr says */\n+\tenum crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n+\tint ident;\n+\tconst char *working_tree_encoding; /* Supported encoding or default encoding if NULL */\n+};\n+\n enum ce_delay_state {\n \tCE_NO_DELAY = 0,\n \tCE_CAN_DELAY = 1,\n@@ -102,6 +123,9 @@ void convert_to_git_filter_fd(const struct index_state *istate,\n int would_convert_to_git_filter_fd(const struct index_state *istate,\n \t\t\t\t   const char *path);\n \n+void convert_attrs(const struct index_state *istate,\n+\t\t   struct conv_attrs *ca, const char *path);\n+\n /*\n  * Initialize the checkout metadata with the given values.  Any argument may be\n  * NULL if it is not applicable.  The treeish should be a commit if that is\n-- \n2.28.0\n\n"},{"id":"408637","messageId":"cover.1603937110.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1600814153.git.matheus.bernardino@usp.br","subject":"[PATCH v3 00/19] Parallel Checkout (part I)","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-29T02:14:37Z","receivedAt":"2020-10-29T02:17:08Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"There was some semantic conflicts between this series and\njk/checkout-index-errors, so I rebased my series on top of that.\n\nAlso, I'd please ask reviewers to confirm that my descriptor\nredirection in git_pc() (patch 17) is correct, as I'm not very \nfamiliar with the test suite descriptors.\n\nMain changes since v2:\n\nPatch 10:\n  - Squashed Peff's patch removing an useless function parameter.\n\nPatch 11:\n  - Valgrind used to complain about send_one_item() passing\n    uninitialized bytes to a syscall (write(2)). The referred bytes come\n    from the unused positions on oid->hash[], when the hash is SHA-1.\n    Since the workers won't use these bytes, there is no real harm. But\n    the warning could cause confusion and even get in the way of\n    detecting real errors, so I replaced the oidcpy() call with\n    hashcpy().\n\nPatch 16:\n  - Replaced use of the non-portable '\\+' in grep with '..*' (in\n    t/lib-parallel-checkout.sh).\n\n  - Properly quoted function parameters in t/lib-parallel-checkout.sh,\n    as Jonathan pointed out.\n\n  - In t2080, dropped tests that used git.git as test data, and added\n    two more tests to check clone with parallel-checkout using the\n    artificial repo already created for other tests.\n\n  - No longer skip clone tests when GIT_TEST_DEFAULT_HASH is sha256. A\n    bug in clone used to make the tests fail with this configuration set\n    to this value, but the bug was fixed in 47ac970309 (\"builtin/clone:\n    avoid failure with GIT_DEFAULT_HASH\", 2020-09-20).\n\nPatch 17:\n  - The test t2081-parallel-checkout-collisions.sh had a bug in which\n    the filter options were being wrongly passed to git. These options\n    were conditionally defined through a shell variable, for which the\n    quoting was wrong. This should have made the test fail but, in fact,\n    another bug (using the arithmetic operator `-eq` for strings), was\n    preventing the problematic section from ever running. These bugs are\n    now fixed, and the test script was also simplified, by making use of\n    the lib-parallel-checkout.sh and eliminating the helper function.\n\n  - Use \"$TEST_ROOT/logger_script\" instead of \"../logger_script\", to be\n    on the safe side.\n\n\nJeff Hostetler (4):\n  convert: make convert_attrs() and convert structs public\n  convert: add [async_]convert_to_working_tree_ca() variants\n  convert: add get_stream_filter_ca() variant\n  convert: add conv_attrs classification\n\nMatheus Tavares (15):\n  entry: extract a header file for entry.c functions\n  entry: make fstat_output() and read_blob_entry() public\n  entry: extract cache_entry update from write_entry()\n  entry: move conv_attrs lookup up to checkout_entry()\n  entry: add checkout_entry_ca() which takes preloaded conv_attrs\n  unpack-trees: add basic support for parallel checkout\n  parallel-checkout: make it truly parallel\n  parallel-checkout: support progress displaying\n  make_transient_cache_entry(): optionally alloc from mem_pool\n  builtin/checkout.c: complete parallel checkout support\n  checkout-index: add parallel checkout support\n  parallel-checkout: add tests for basic operations\n  parallel-checkout: add tests related to clone collisions\n  parallel-checkout: add tests related to .gitattributes\n  ci: run test round with parallel-checkout enabled\n\n .gitignore                              |   1 +\n Documentation/config/checkout.txt       |  21 +\n Makefile                                |   2 +\n apply.c                                 |   1 +\n builtin.h                               |   1 +\n builtin/checkout--helper.c              | 142 ++++++\n builtin/checkout-index.c                |  22 +-\n builtin/checkout.c                      |  21 +-\n builtin/difftool.c                      |   3 +-\n cache.h                                 |  34 +-\n ci/run-build-and-tests.sh               |   1 +\n convert.c                               | 121 +++--\n convert.h                               |  68 +++\n entry.c                                 | 102 ++--\n entry.h                                 |  54 ++\n git.c                                   |   2 +\n parallel-checkout.c                     | 638 ++++++++++++++++++++++++\n parallel-checkout.h                     | 103 ++++\n read-cache.c                            |  12 +-\n t/README                                |   4 +\n t/lib-encoding.sh                       |  25 +\n t/lib-parallel-checkout.sh              |  46 ++\n t/t0028-working-tree-encoding.sh        |  25 +-\n t/t2080-parallel-checkout-basics.sh     | 170 +++++++\n t/t2081-parallel-checkout-collisions.sh |  98 ++++\n t/t2082-parallel-checkout-attributes.sh | 174 +++++++\n unpack-trees.c                          |  22 +-\n 27 files changed, 1758 insertions(+), 155 deletions(-)\n create mode 100644 builtin/checkout--helper.c\n create mode 100644 entry.h\n create mode 100644 parallel-checkout.c\n create mode 100644 parallel-checkout.h\n create mode 100644 t/lib-encoding.sh\n create mode 100644 t/lib-parallel-checkout.sh\n create mode 100755 t/t2080-parallel-checkout-basics.sh\n create mode 100755 t/t2081-parallel-checkout-collisions.sh\n create mode 100755 t/t2082-parallel-checkout-attributes.sh\n\nRange-diff against v2:\n 1:  b9d2a329d3 =  1:  dfc3e0fd62 convert: make convert_attrs() and convert structs public\n 2:  313c3bcbeb =  2:  c5fbd1e16d convert: add [async_]convert_to_working_tree_ca() variants\n 3:  29bbdb78e9 =  3:  c77b16f694 convert: add get_stream_filter_ca() variant\n 4:  a1cf5df961 =  4:  18c3f4247e convert: add conv_attrs classification\n 5:  25b311745a =  5:  2caa2c4345 entry: extract a header file for entry.c functions\n 6:  dbee09e936 =  6:  bfa52df9e2 entry: make fstat_output() and read_blob_entry() public\n 7:  b61b5c44f0 =  7:  91ef17f533 entry: extract cache_entry update from write_entry()\n 8:  667ad0dea7 =  8:  81e03baab1 entry: move conv_attrs lookup up to checkout_entry()\n 9:  4ddb34209e =  9:  e1b886f823 entry: add checkout_entry_ca() which takes preloaded conv_attrs\n10:  af0d790973 ! 10:  2bdc13664e unpack-trees: add basic support for parallel checkout\n    @@ parallel-checkout.c (new)\n     +}\n     +\n     +static int write_pc_item_to_fd(struct parallel_checkout_item *pc_item, int fd,\n    -+\t\t\t       const char *path, struct checkout *state)\n    ++\t\t\t       const char *path)\n     +{\n     +\tint ret;\n     +\tstruct stream_filter *filter;\n    @@ parallel-checkout.c (new)\n     +\t\tgoto out;\n     +\t}\n     +\n    -+\tif (write_pc_item_to_fd(pc_item, fd, path.buf, state)) {\n    ++\tif (write_pc_item_to_fd(pc_item, fd, path.buf)) {\n     +\t\t/* Error was already reported. */\n     +\t\tpc_item->status = PC_ITEM_FAILED;\n     +\t\tgoto out;\n11:  991169488b ! 11:  096e543fd2 parallel-checkout: make it truly parallel\n    @@ Documentation/config/checkout.txt: will checkout the '<something>' branch on ano\n     +\tThe number of parallel workers to use when updating the working tree.\n     +\tThe default is one, i.e. sequential execution. If set to a value less\n     +\tthan one, Git will use as many workers as the number of logical cores\n    -+\tavailable. This setting and checkout.thresholdForParallelism affect all\n    -+\tcommands that perform checkout. E.g. checkout, switch, clone, reset,\n    -+\tsparse-checkout, read-tree, etc.\n    ++\tavailable. This setting and `checkout.thresholdForParallelism` affect\n    ++\tall commands that perform checkout. E.g. checkout, clone, reset,\n    ++\tsparse-checkout, etc.\n     ++\n     +Note: parallel checkout usually delivers better performance for repositories\n     +located on SSDs or over NFS. For repositories on spinning disks and/or machines\n    @@ parallel-checkout.c: static void write_pc_item(struct parallel_checkout_item *pc\n     +\n     +\tfixed_portion = (struct pc_item_fixed_portion *)data;\n     +\tfixed_portion->id = pc_item->id;\n    -+\toidcpy(&fixed_portion->oid, &pc_item->ce->oid);\n     +\tfixed_portion->ce_mode = pc_item->ce->ce_mode;\n     +\tfixed_portion->crlf_action = pc_item->ca.crlf_action;\n     +\tfixed_portion->ident = pc_item->ca.ident;\n     +\tfixed_portion->name_len = name_len;\n     +\tfixed_portion->working_tree_encoding_len = working_tree_encoding_len;\n    ++\t/*\n    ++\t * We use hashcpy() instead of oidcpy() because the hash[] positions\n    ++\t * after `the_hash_algo->rawsz` might not be initialized. And Valgrind\n    ++\t * would complain about passing uninitialized bytes to a syscall\n    ++\t * (write(2)). There is no real harm in this case, but the warning could\n    ++\t * hinder the detection of actual errors.\n    ++\t */\n    ++\thashcpy(fixed_portion->oid.hash, pc_item->ce->oid.hash);\n     +\n     +\tvariant = data + sizeof(*fixed_portion);\n     +\tif (working_tree_encoding_len) {\n12:  7ceadf2427 = 12:  9cfeb4821c parallel-checkout: support progress displaying\n13:  f13b4c17f4 = 13:  da99b671e6 make_transient_cache_entry(): optionally alloc from mem_pool\n14:  d7885a1130 = 14:  d3d561754a builtin/checkout.c: complete parallel checkout support\n15:  1cf9b807f7 ! 15:  ee34c6e149 checkout-index: add parallel checkout support\n    @@ builtin/checkout-index.c\n      #define CHECKOUT_ALL 4\n      static int nul_term_line;\n     @@ builtin/checkout-index.c: int cmd_checkout_index(int argc, const char **argv, const char *prefix)\n    - \tint prefix_length;\n      \tint force = 0, quiet = 0, not_new = 0;\n      \tint index_opt = 0;\n    + \tint err = 0;\n     +\tint pc_workers, pc_threshold;\n      \tstruct option builtin_checkout_index_options[] = {\n      \t\tOPT_BOOL('a', \"all\", &all,\n    @@ builtin/checkout-index.c: int cmd_checkout_index(int argc, const char **argv, co\n      \tfor (i = 0; i < argc; i++) {\n      \t\tconst char *arg = argv[i];\n     @@ builtin/checkout-index.c: int cmd_checkout_index(int argc, const char **argv, const char *prefix)\n    + \t\tstrbuf_release(&buf);\n    + \t}\n    + \n    +-\tif (err)\n    +-\t\treturn 1;\n    +-\n      \tif (all)\n      \t\tcheckout_all(prefix, prefix_length);\n      \n     +\tif (pc_workers > 1) {\n    -+\t\t/* Errors were already reported */\n    -+\t\trun_parallel_checkout(&state, pc_workers, pc_threshold,\n    -+\t\t\t\t      NULL, NULL);\n    ++\t\terr |= run_parallel_checkout(&state, pc_workers, pc_threshold,\n    ++\t\t\t\t\t     NULL, NULL);\n     +\t}\n    ++\n    ++\tif (err)\n    ++\t\treturn 1;\n     +\n      \tif (is_lock_file_locked(&lock_file) &&\n      \t    write_locked_index(&the_index, &lock_file, COMMIT_LOCK))\n16:  64b41d537e ! 16:  05299a3cc0 parallel-checkout: add tests for basic operations\n    @@ Commit message\n         for symlinks in the leading directories and the abidance to --force.\n     \n         Note: some helper functions are added to a common lib file which is only\n    -    included by t2080 for now. But it will also be used by another\n    -    parallel-checkout test in a following patch.\n    +    included by t2080 for now. But it will also be used by other\n    +    parallel-checkout tests in the following patches.\n     \n         Original-patch-by: Jeff Hostetler <jeffhost@microsoft.com>\n         Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n    @@ t/lib-parallel-checkout.sh (new)\n     +\n     +# Runs `git -c checkout.workers=$1 -c checkout.thesholdForParallelism=$2 ${@:4}`\n     +# and checks that the number of workers spawned is equal to $3.\n    ++#\n     +git_pc()\n     +{\n     +\tif test $# -lt 4\n     +\tthen\n     +\t\tBUG \"too few arguments to git_pc()\"\n    -+\tfi\n    ++\tfi &&\n     +\n     +\tworkers=$1 threshold=$2 expected_workers=$3 &&\n    -+\tshift && shift && shift &&\n    ++\tshift 3 &&\n     +\n     +\trm -f trace &&\n     +\tGIT_TRACE2=\"$(pwd)/trace\" git \\\n     +\t\t-c checkout.workers=$workers \\\n     +\t\t-c checkout.thresholdForParallelism=$threshold \\\n     +\t\t-c advice.detachedHead=0 \\\n    -+\t\t$@ &&\n    ++\t\t\"$@\" &&\n     +\n     +\t# Check that the expected number of workers has been used. Note that it\n    -+\t# can be different than the requested number in two cases: when the\n    -+\t# quantity of entries to be checked out is less than the number of\n    -+\t# workers; and when the threshold has not been reached.\n    ++\t# can be different from the requested number in two cases: when the\n    ++\t# threshold is not reached; and when there are not enough\n    ++\t# parallel-eligible entries for all workers.\n     +\t#\n    -+\tlocal workers_in_trace=$(grep \"child_start\\[.\\+\\] git checkout--helper\" trace | wc -l) &&\n    ++\tlocal workers_in_trace=$(grep \"child_start\\[..*\\] git checkout--helper\" trace | wc -l) &&\n     +\ttest $workers_in_trace -eq $expected_workers &&\n     +\trm -f trace\n     +}\n    @@ t/lib-parallel-checkout.sh (new)\n     +# Verify that both the working tree and the index were created correctly\n     +verify_checkout()\n     +{\n    -+\tgit -C $1 diff-index --quiet HEAD -- &&\n    -+\tgit -C $1 diff-index --quiet --cached HEAD -- &&\n    -+\tgit -C $1 status --porcelain >$1.status &&\n    -+\ttest_must_be_empty $1.status\n    ++\tgit -C \"$1\" diff-index --quiet HEAD -- &&\n    ++\tgit -C \"$1\" diff-index --quiet --cached HEAD -- &&\n    ++\tgit -C \"$1\" status --porcelain >\"$1\".status &&\n    ++\ttest_must_be_empty \"$1\".status\n     +}\n     \n      ## t/t2080-parallel-checkout-basics.sh (new) ##\n    @@ t/t2080-parallel-checkout-basics.sh (new)\n     +. ./test-lib.sh\n     +. \"$TEST_DIRECTORY/lib-parallel-checkout.sh\"\n     +\n    -+# NEEDSWORK: cloning a SHA1 repo with GIT_TEST_DEFAULT_HASH set to \"sha256\"\n    -+# currently produces a wrong result (See\n    -+# https://lore.kernel.org/git/20200911151717.43475-1-matheus.bernardino@usp.br/).\n    -+# So we skip the \"parallel-checkout during clone\" tests when this test flag is\n    -+# set to \"sha256\". Remove this when the bug is fixed.\n    -+#\n    -+if test \"$GIT_TEST_DEFAULT_HASH\" = \"sha256\"\n    -+then\n    -+\tskip_all=\"t2080 currently don't work with GIT_TEST_DEFAULT_HASH=sha256\"\n    -+\ttest_done\n    -+fi\n    -+\n    -+R_BASE=$GIT_BUILD_DIR\n    -+\n    -+test_expect_success 'sequential clone' '\n    -+\tgit_pc 1 0 0 clone --quiet -- $R_BASE r_sequential &&\n    -+\tverify_checkout r_sequential\n    -+'\n    -+\n    -+test_expect_success 'parallel clone' '\n    -+\tgit_pc 2 0 2 clone --quiet -- $R_BASE r_parallel &&\n    -+\tverify_checkout r_parallel\n    -+'\n    -+\n    -+test_expect_success 'fallback to sequential clone (threshold)' '\n    -+\tgit -C $R_BASE ls-files >files &&\n    -+\tnr_files=$(wc -l <files) &&\n    -+\tthreshold=$(($nr_files + 1)) &&\n    -+\n    -+\tgit_pc 2 $threshold 0 clone --quiet -- $R_BASE r_sequential_fallback &&\n    -+\tverify_checkout r_sequential_fallback\n    -+'\n    -+\n    -+# Just to be paranoid, actually compare the contents of the worktrees directly.\n    -+test_expect_success 'compare working trees from clones' '\n    -+\trm -rf r_sequential/.git &&\n    -+\trm -rf r_parallel/.git &&\n    -+\trm -rf r_sequential_fallback/.git &&\n    -+\tdiff -qr r_sequential r_parallel &&\n    -+\tdiff -qr r_sequential r_sequential_fallback\n    -+'\n    -+\n     +# Test parallel-checkout with different operations (creation, deletion,\n     +# modification) and entry types. A branch switch from B1 to B2 will contain:\n     +#\n    @@ t/t2080-parallel-checkout-basics.sh (new)\n     +\tverify_checkout various_sequential_fallback\n     +'\n     +\n    -+test_expect_success SYMLINKS 'compare working trees from checkouts' '\n    -+\trm -rf various_sequential/.git &&\n    -+\trm -rf various_parallel/.git &&\n    -+\trm -rf various_sequential_fallback/.git &&\n    -+\tdiff -qr various_sequential various_parallel &&\n    -+\tdiff -qr various_sequential various_sequential_fallback\n    ++test_expect_success SYMLINKS 'parallel checkout on clone' '\n    ++\tgit -C various checkout --recurse-submodules B2 &&\n    ++\tgit_pc 2 0 2 clone --recurse-submodules various various_parallel_clone  &&\n    ++\tverify_checkout various_parallel_clone\n    ++'\n    ++\n    ++test_expect_success SYMLINKS 'fallback to sequential checkout on clone (threshold)' '\n    ++\tgit -C various checkout --recurse-submodules B2 &&\n    ++\tgit_pc 2 100 0 clone --recurse-submodules various various_sequential_fallback_clone &&\n    ++\tverify_checkout various_sequential_fallback_clone\n    ++'\n    ++\n    ++# Just to be paranoid, actually compare the working trees' contents directly.\n    ++test_expect_success SYMLINKS 'compare the working trees' '\n    ++\trm -rf various_*/.git &&\n    ++\trm -rf various_*/d/.git &&\n    ++\n    ++\tdiff -r various_sequential various_parallel &&\n    ++\tdiff -r various_sequential various_sequential_fallback &&\n    ++\tdiff -r various_sequential various_parallel_clone &&\n    ++\tdiff -r various_sequential various_sequential_fallback_clone\n     +'\n     +\n     +test_cmp_str()\n17:  70708d3e31 ! 17:  3d140dcacb parallel-checkout: add tests related to clone collisions\n    @@ Commit message\n         Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n         Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n     \n    + ## t/lib-parallel-checkout.sh ##\n    +@@ t/lib-parallel-checkout.sh: git_pc()\n    + \t\t-c checkout.workers=$workers \\\n    + \t\t-c checkout.thresholdForParallelism=$threshold \\\n    + \t\t-c advice.detachedHead=0 \\\n    +-\t\t\"$@\" &&\n    ++\t\t\"$@\" 2>&8 &&\n    + \n    + \t# Check that the expected number of workers has been used. Note that it\n    + \t# can be different from the requested number in two cases: when the\n    +@@ t/lib-parallel-checkout.sh: git_pc()\n    + \tlocal workers_in_trace=$(grep \"child_start\\[..*\\] git checkout--helper\" trace | wc -l) &&\n    + \ttest $workers_in_trace -eq $expected_workers &&\n    + \trm -f trace\n    +-}\n    ++} 8>&2 2>&4\n    + \n    + # Verify that both the working tree and the index were created correctly\n    + verify_checkout()\n    +\n      ## t/t2081-parallel-checkout-collisions.sh (new) ##\n     @@\n     +#!/bin/sh\n     +\n    -+test_description='parallel-checkout collisions'\n    ++test_description='parallel-checkout collisions\n    ++\n    ++When there are path collisions during a clone, Git should report a warning\n    ++listing all of the colliding entries. The sequential code detects a collision\n    ++by calling lstat() before trying to open(O_CREAT) the file. Then, to find the\n    ++colliding pair of an item k, it searches cache_entry[0, k-1].\n    ++\n    ++This is not sufficient in parallel checkout since:\n    ++\n    ++- A colliding file may be created between the lstat() and open() calls;\n    ++- A colliding entry might appear in the second half of the cache_entry array.\n    ++\n    ++The tests in this file make sure that the collision detection code is extended\n    ++for parallel checkout.\n    ++'\n     +\n     +. ./test-lib.sh\n    ++. \"$TEST_DIRECTORY/lib-parallel-checkout.sh\"\n     +\n    -+# When there are pathname collisions during a clone, Git should report a warning\n    -+# listing all of the colliding entries. The sequential code detects a collision\n    -+# by calling lstat() before trying to open(O_CREAT) the file. Then, to find the\n    -+# colliding pair of an item k, it searches cache_entry[0, k-1].\n    -+#\n    -+# This is not sufficient in parallel-checkout mode since colliding files may be\n    -+# created in a racy order. The tests in this file make sure the collision\n    -+# detection code is extended for parallel-checkout. This is done in two parts:\n    -+#\n    -+# - First, two parallel workers create four colliding files racily.\n    -+# - Then this exercise is repeated but forcing the colliding pair to appear in\n    -+#   the second half of the cache_entry's array.\n    -+#\n    -+# The second item uses the fact that files with clean/smudge filters are not\n    -+# parallel-eligible; and that they are processed sequentially *before* any\n    -+# worker is spawned. We set a filter attribute to the last entry in the\n    -+# cache_entry[] array, making it non-eligible, so that it is populated first.\n    -+# This way, we can test if the collision detection code is correctly looking\n    -+# for collision pairs in the second half of the array.\n    ++TEST_ROOT=\"$PWD\"\n     +\n     +test_expect_success CASE_INSENSITIVE_FS 'setup' '\n    -+\tfile_hex=$(git hash-object -w --stdin </dev/null) &&\n    -+\tfile_oct=$(echo $file_hex | hex2oct) &&\n    ++\tfile_x_hex=$(git hash-object -w --stdin </dev/null) &&\n    ++\tfile_x_oct=$(echo $file_x_hex | hex2oct) &&\n     +\n     +\tattr_hex=$(echo \"file_x filter=logger\" | git hash-object -w --stdin) &&\n     +\tattr_oct=$(echo $attr_hex | hex2oct) &&\n     +\n    -+\tprintf \"100644 FILE_X\\0${file_oct}\" >tree &&\n    -+\tprintf \"100644 FILE_x\\0${file_oct}\" >>tree &&\n    -+\tprintf \"100644 file_X\\0${file_oct}\" >>tree &&\n    -+\tprintf \"100644 file_x\\0${file_oct}\" >>tree &&\n    ++\tprintf \"100644 FILE_X\\0${file_x_oct}\" >tree &&\n    ++\tprintf \"100644 FILE_x\\0${file_x_oct}\" >>tree &&\n    ++\tprintf \"100644 file_X\\0${file_x_oct}\" >>tree &&\n    ++\tprintf \"100644 file_x\\0${file_x_oct}\" >>tree &&\n     +\tprintf \"100644 .gitattributes\\0${attr_oct}\" >>tree &&\n     +\n     +\ttree_hex=$(git hash-object -w -t tree --stdin <tree) &&\n     +\tcommit_hex=$(git commit-tree -m collisions $tree_hex) &&\n     +\tgit update-ref refs/heads/collisions $commit_hex &&\n     +\n    -+\twrite_script logger_script <<-\\EOF\n    ++\twrite_script \"$TEST_ROOT\"/logger_script <<-\\EOF\n     +\techo \"$@\" >>filter.log\n     +\tEOF\n     +'\n     +\n    -+clone_and_check_collision()\n    -+{\n    -+\tid=$1 workers=$2 threshold=$3 expected_workers=$4 filter=$5 &&\n    -+\n    -+\tfilter_opts=\n    -+\tif test \"$filter\" -eq \"use_filter\"\n    -+\tthen\n    -+\t\t# We use `core.ignoreCase=0` so that only `file_x`\n    -+\t\t# matches the pattern in .gitattributes.\n    -+\t\t#\n    -+\t\tfilter_opts='-c filter.logger.smudge=\"../logger_script %f\" -c core.ignoreCase=0'\n    -+\tfi &&\n    -+\n    -+\ttest_path_is_missing $id.trace &&\n    -+\tGIT_TRACE2=\"$(pwd)/$id.trace\" git \\\n    -+\t\t-c checkout.workers=$workers \\\n    -+\t\t-c checkout.thresholdForParallelism=$threshold \\\n    -+\t\t$filter_opts clone --branch=collisions -- . r_$id 2>$id.warning &&\n    -+\n    -+\t# Check that checkout spawned the right number of workers\n    -+\tworkers_in_trace=$(grep \"child_start\\[.\\] git checkout--helper\" $id.trace | wc -l) &&\n    -+\ttest $workers_in_trace -eq $expected_workers &&\n    -+\n    -+\tif test $filter -eq \"use_filter\"\n    -+\tthen\n    -+\t\t#  Make sure only 'file_x' was filtered\n    -+\t\ttest_path_is_file r_$id/filter.log &&\n    ++for mode in parallel sequential-fallback\n    ++do\n    ++\n    ++\tcase $mode in\n    ++\tparallel)\t\tworkers=2 threshold=0 expected_workers=2 ;;\n    ++\tsequential-fallback)\tworkers=2 threshold=100 expected_workers=0 ;;\n    ++\tesac\n    ++\n    ++\ttest_expect_success CASE_INSENSITIVE_FS \"collision detection on $mode clone\" '\n    ++\t\tgit_pc $workers $threshold $expected_workers \\\n    ++\t\t\tclone --branch=collisions . $mode 2>$mode.stderr &&\n    ++\n    ++\t\tgrep FILE_X $mode.stderr &&\n    ++\t\tgrep FILE_x $mode.stderr &&\n    ++\t\tgrep file_X $mode.stderr &&\n    ++\t\tgrep file_x $mode.stderr &&\n    ++\t\ttest_i18ngrep \"the following paths have collided\" $mode.stderr\n    ++\t'\n    ++\n    ++\t# The following test ensures that the collision detection code is\n    ++\t# correctly looking for colliding peers in the second half of the\n    ++\t# cache_entry array. This is done by defining a smudge command for the\n    ++\t# *last* array entry, which makes it non-eligible for parallel-checkout.\n    ++\t# The last entry is then checked out *before* any worker is spawned,\n    ++\t# making it succeed and the workers' entries collide.\n    ++\t#\n    ++\t# Note: this test don't work on Windows because, on this system,\n    ++\t# collision detection uses strcmp() when core.ignoreCase=false. And we\n    ++\t# have to set core.ignoreCase=false so that only 'file_x' matches the\n    ++\t# pattern of the filter attribute. But it works on OSX, where collision\n    ++\t# detection uses inode.\n    ++\t#\n    ++\ttest_expect_success CASE_INSENSITIVE_FS,!MINGW,!CYGWIN \"collision detection on $mode clone w/ filter\" '\n    ++\t\tgit_pc $workers $threshold $expected_workers \\\n    ++\t\t\t-c core.ignoreCase=false \\\n    ++\t\t\t-c filter.logger.smudge=\"\\\"$TEST_ROOT/logger_script\\\" %f\" \\\n    ++\t\t\tclone --branch=collisions . ${mode}_with_filter \\\n    ++\t\t\t2>${mode}_with_filter.stderr &&\n    ++\n    ++\t\tgrep FILE_X ${mode}_with_filter.stderr &&\n    ++\t\tgrep FILE_x ${mode}_with_filter.stderr &&\n    ++\t\tgrep file_X ${mode}_with_filter.stderr &&\n    ++\t\tgrep file_x ${mode}_with_filter.stderr &&\n    ++\t\ttest_i18ngrep \"the following paths have collided\" ${mode}_with_filter.stderr &&\n    ++\n    ++\t\t# Make sure only \"file_x\" was filtered\n    ++\t\ttest_path_is_file ${mode}_with_filter/filter.log &&\n     +\t\techo file_x >expected.filter.log &&\n    -+\t\ttest_cmp r_$id/filter.log expected.filter.log\n    -+\telse\n    -+\t\ttest_path_is_missing r_$id/filter.log\n    -+\tfi &&\n    -+\n    -+\tgrep FILE_X $id.warning &&\n    -+\tgrep FILE_x $id.warning &&\n    -+\tgrep file_X $id.warning &&\n    -+\tgrep file_x $id.warning &&\n    -+\ttest_i18ngrep \"the following paths have collided\" $id.warning\n    -+}\n    -+\n    -+test_expect_success CASE_INSENSITIVE_FS 'collision detection on parallel clone' '\n    -+\tclone_and_check_collision parallel 2 0 2\n    -+'\n    -+\n    -+test_expect_success CASE_INSENSITIVE_FS 'collision detection on fallback to sequential clone' '\n    -+\tgit ls-tree --name-only -r collisions >files &&\n    -+\tnr_files=$(wc -l <files) &&\n    -+\tthreshold=$(($nr_files + 1)) &&\n    -+\tclone_and_check_collision sequential 2 $threshold 0\n    -+'\n    -+\n    -+# The next two tests don't work on Windows because, on this system, collision\n    -+# detection uses strcmp() (when core.ignoreCase=0) to find the colliding pair.\n    -+# But they work on OSX, where collision detection uses inode.\n    -+\n    -+test_expect_success CASE_INSENSITIVE_FS,!MINGW,!CYGWIN 'collision detection on parallel clone w/ filter' '\n    -+\tclone_and_check_collision parallel-with-filter 2 0 2 use_filter\n    -+'\n    -+\n    -+test_expect_success CASE_INSENSITIVE_FS,!MINGW,!CYGWIN 'collision detection on fallback to sequential clone w/ filter' '\n    -+\tgit ls-tree --name-only -r collisions >files &&\n    -+\tnr_files=$(wc -l <files) &&\n    -+\tthreshold=$(($nr_files + 1)) &&\n    -+\tclone_and_check_collision sequential-with-filter 2 $threshold 0 use_filter\n    -+'\n    ++\t\ttest_cmp ${mode}_with_filter/filter.log expected.filter.log\n    ++\t'\n    ++done\n     +\n     +test_done\n18:  ece38f0483 = 18:  b26f676cae parallel-checkout: add tests related to .gitattributes\n19:  b4cb5905d2 ! 19:  641c61f9b6 ci: run test round with parallel-checkout enabled\n    @@ t/lib-parallel-checkout.sh\n     +\n      # Runs `git -c checkout.workers=$1 -c checkout.thesholdForParallelism=$2 ${@:4}`\n      # and checks that the number of workers spawned is equal to $3.\n    - git_pc()\n    -\n    - ## t/t2081-parallel-checkout-collisions.sh ##\n    -@@\n    - test_description='parallel-checkout collisions'\n    - \n    - . ./test-lib.sh\n    -+. \"$TEST_DIRECTORY/lib-parallel-checkout.sh\"\n    - \n    - # When there are pathname collisions during a clone, Git should report a warning\n    - # listing all of the colliding entries. The sequential code detects a collision\n    + #\n-- \n2.28.0\n\n"},{"id":"408655","messageId":"xmqqlffo7qzo.fsf@gitster.c.googlers.com","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v3 00/19] Parallel Checkout (part I)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-29T19:48:43Z","receivedAt":"2020-10-29T19:49:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matheus Tavares <matheus.bernardino@usp.br> writes:\n\n> There was some semantic conflicts between this series and\n> jk/checkout-index-errors, so I rebased my series on top of that.\n\nThat is sensible, as you'd want to be able to rely on the exit\nstatus from the command while testing.\n\nWill replace what has been queued.\n"},{"id":"408684","messageId":"xmqq361w4n4x.fsf@gitster.c.googlers.com","threadId":"54525","inReplyTo":"dfc3e0fd62bcf0223dc51485521116e1aca2ee07.1603937110.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v3 01/19] convert: make convert_attrs() and convert structs public","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-29T23:40:14Z","receivedAt":"2020-10-29T23:40:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matheus Tavares <matheus.bernardino@usp.br> writes:\n\n> diff --git a/convert.h b/convert.h\n> index e29d1026a6..aeb4a1be9a 100644\n> --- a/convert.h\n> +++ b/convert.h\n> @@ -37,6 +37,27 @@ enum eol {\n>  #endif\n>  };\n>  \n> +enum crlf_action {\n> +\tCRLF_UNDEFINED,\n> +\tCRLF_BINARY,\n> +\tCRLF_TEXT,\n> +\tCRLF_TEXT_INPUT,\n> +\tCRLF_TEXT_CRLF,\n> +\tCRLF_AUTO,\n> +\tCRLF_AUTO_INPUT,\n> +\tCRLF_AUTO_CRLF\n> +};\n> +\n> +struct convert_driver;\n> +\n> +struct conv_attrs {\n> +\tstruct convert_driver *drv;\n> +\tenum crlf_action attr_action; /* What attr says */\n> +\tenum crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n> +\tint ident;\n> +\tconst char *working_tree_encoding; /* Supported encoding or default encoding if NULL */\n> +};\n> +\n>  enum ce_delay_state {\n>  \tCE_NO_DELAY = 0,\n>  \tCE_CAN_DELAY = 1,\n> @@ -102,6 +123,9 @@ void convert_to_git_filter_fd(const struct index_state *istate,\n>  int would_convert_to_git_filter_fd(const struct index_state *istate,\n>  \t\t\t\t   const char *path);\n>  \n> +void convert_attrs(const struct index_state *istate,\n> +\t\t   struct conv_attrs *ca, const char *path);\n> +\n>  /*\n>   * Initialize the checkout metadata with the given values.  Any argument may be\n>   * NULL if it is not applicable.  The treeish should be a commit if that is\n\nThe new global symbols are reasonable, I would think, with a\npossible exception of \"crlf_action\", which may want to also have\n\"conv\" or \"convert\" somewhere in its name.\n\n"},{"id":"408685","messageId":"xmqqy2jo386z.fsf@gitster.c.googlers.com","threadId":"54525","inReplyTo":"c5fbd1e16d616a4c238d98a35f3671238300c17f.1603937110.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v3 02/19] convert: add [async_]convert_to_working_tree_ca() variants","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-29T23:48:20Z","receivedAt":"2020-10-29T23:48:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matheus Tavares <matheus.bernardino@usp.br> writes:\n\n> -static int convert_to_working_tree_internal(const struct index_state *istate,\n> +static int convert_to_working_tree_internal(const struct conv_attrs *ca,\n\nMakes sense.  Once we know conv_attrs, we do not need the istate to\nconvert the contents.\n\n> @@ -1497,7 +1494,9 @@ int async_convert_to_working_tree(const struct index_state *istate,\n>  \t\t\t\t  const struct checkout_metadata *meta,\n>  \t\t\t\t  void *dco)\n>  {\n> -\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco);\n> +\tstruct conv_attrs ca;\n> +\tconvert_attrs(istate, &ca, path);\n> +\treturn convert_to_working_tree_internal(&ca, path, src, len, dst, 0, meta, dco);\n>  }\n>\n> @@ -1505,13 +1504,36 @@ int convert_to_working_tree(const struct index_state *istate,\n>  \t\t\t    size_t len, struct strbuf *dst,\n>  \t\t\t    const struct checkout_metadata *meta)\n>  {\n> -\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL);\n> +\tstruct conv_attrs ca;\n> +\tconvert_attrs(istate, &ca, path);\n> +\treturn convert_to_working_tree_internal(&ca, path, src, len, dst, 0, meta, NULL);\n> +}\n\nOK, these naturally implement \"let's lift convert_attrs() out of the\ncallee and move it to the callers\".  However...\n\n> +int async_convert_to_working_tree_ca(const struct conv_attrs *ca,\n> +\t\t\t\t     const char *path, const char *src,\n> +\t\t\t\t     size_t len, struct strbuf *dst,\n> +\t\t\t\t     const struct checkout_metadata *meta,\n> +\t\t\t\t     void *dco)\n> +{\n> +\treturn convert_to_working_tree_internal(ca, path, src, len, dst, 0, meta, dco);\n> +}\n> +\n> +int convert_to_working_tree_ca(const struct conv_attrs *ca,\n> +\t\t\t       const char *path, const char *src,\n> +\t\t\t       size_t len, struct strbuf *dst,\n> +\t\t\t       const struct checkout_metadata *meta)\n> +{\n> +\treturn convert_to_working_tree_internal(ca, path, src, len, dst, 0, meta, NULL);\n>  }\n\n... shouldn't they be implemented as thin wrappers around these new\n*_ca() variants of the API functions?  Otherwise, the *_ca()\nvariants are not yet used by anybody yet at this step, are they?\n"},{"id":"408686","messageId":"xmqqtuuc382h.fsf@gitster.c.googlers.com","threadId":"54525","inReplyTo":"c77b16f694cfe8076e66f604c0bbe053f58a2e9e.1603937110.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v3 03/19] convert: add get_stream_filter_ca() variant","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-29T23:51:02Z","receivedAt":"2020-10-29T23:51:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matheus Tavares <matheus.bernardino@usp.br> writes:\n\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n>\n> Like the previous patch, we will also need to call get_stream_filter()\n> with a precomputed `struct conv_attrs`, when we add support for parallel\n> checkout workers. So add the _ca() variant which takes the conversion\n> attributes struct as a parameter.\n>\n> Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n> [matheus.bernardino: move header comment to ca() variant and reword msg]\n> Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n> ---\n>  convert.c | 28 +++++++++++++++++-----------\n>  convert.h |  2 ++\n>  2 files changed, 19 insertions(+), 11 deletions(-)\n\nSame idea as 02/19, which is sound.\n\nIt makes readers wonder why this one is separate, while\nconvert_to_working_tree(), async_convert_to_working_tree(), and\nrenormalize_buffer() were done in a single patch in a single step,\nthough.\n\n\n"},{"id":"408688","messageId":"xmqqpn5037xv.fsf@gitster.c.googlers.com","threadId":"54525","inReplyTo":"18c3f4247e717a7766f13b4b33a0bbe31aee6b69.1603937110.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v3 04/19] convert: add conv_attrs classification","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-29T23:53:48Z","receivedAt":"2020-10-29T23:53:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matheus Tavares <matheus.bernardino@usp.br> writes:\n\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n>\n> Create `enum conv_attrs_classification` to express the different ways\n> that attributes are handled for a blob during checkout.\n>\n> This will be used in a later commit when deciding whether to add a file\n> to the parallel or delayed queue during checkout. For now, we can also\n> use it in get_stream_filter_ca() to simplify the function (as the\n> classifying logic is the same).\n>\n> Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n> [matheus.bernardino: use classification in get_stream_filter_ca()]\n> Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n> ---\n>  convert.c | 26 +++++++++++++++++++-------\n>  convert.h | 33 +++++++++++++++++++++++++++++++++\n>  2 files changed, 52 insertions(+), 7 deletions(-)\n\nYup, having an actual user of the new layer of abstraction in the\nsame patch makes it more easily understandable.  If only the new\nfunction and enum were presented without anybody using, it would\nhave been much harder to swallow, without visible and immediate\nbenefit.\n\nLooking good.\n"},{"id":"408757","messageId":"a6365d1a-9baa-2488-ca1e-e4e3354bccf5@jeffhostetler.com","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v3 00/19] Parallel Checkout (part I)","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2020-10-30T15:58:46Z","receivedAt":"2020-10-30T16:05:12Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 10/28/20 10:14 PM, Matheus Tavares wrote:\n > ...\n\nLooks good to me.\nThanks for pushing this forward.\n\nJeff\n\n"},{"id":"408760","messageId":"CAHd-oW44tFRnpDWsQfF80gZn42mvgwZ0rT-ocQZ92zBTrQL-ZA@mail.gmail.com","threadId":"54525","inReplyTo":"xmqq361w4n4x.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v3 01/19] convert: make convert_attrs() and convert structs public","fromName":"Matheus Tavares Bernardino","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-10-30T17:01:11Z","receivedAt":"2020-10-30T17:01:28Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"On Thu, Oct 29, 2020 at 8:40 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Matheus Tavares <matheus.bernardino@usp.br> writes:\n>\n> > diff --git a/convert.h b/convert.h\n> > index e29d1026a6..aeb4a1be9a 100644\n> > --- a/convert.h\n> > +++ b/convert.h\n> > @@ -37,6 +37,27 @@ enum eol {\n> >  #endif\n> >  };\n> >\n> > +enum crlf_action {\n> > +     CRLF_UNDEFINED,\n> > +     CRLF_BINARY,\n> > +     CRLF_TEXT,\n> > +     CRLF_TEXT_INPUT,\n> > +     CRLF_TEXT_CRLF,\n> > +     CRLF_AUTO,\n> > +     CRLF_AUTO_INPUT,\n> > +     CRLF_AUTO_CRLF\n> > +};\n> > +\n> > +struct convert_driver;\n> > +\n> > +struct conv_attrs {\n> > +     struct convert_driver *drv;\n> > +     enum crlf_action attr_action; /* What attr says */\n> > +     enum crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n> > +     int ident;\n> > +     const char *working_tree_encoding; /* Supported encoding or default encoding if NULL */\n> > +};\n> > +\n> >  enum ce_delay_state {\n> >       CE_NO_DELAY = 0,\n> >       CE_CAN_DELAY = 1,\n> > @@ -102,6 +123,9 @@ void convert_to_git_filter_fd(const struct index_state *istate,\n> >  int would_convert_to_git_filter_fd(const struct index_state *istate,\n> >                                  const char *path);\n> >\n> > +void convert_attrs(const struct index_state *istate,\n> > +                struct conv_attrs *ca, const char *path);\n> > +\n> >  /*\n> >   * Initialize the checkout metadata with the given values.  Any argument may be\n> >   * NULL if it is not applicable.  The treeish should be a commit if that is\n>\n> The new global symbols are reasonable, I would think, with a\n> possible exception of \"crlf_action\", which may want to also have\n> \"conv\" or \"convert\" somewhere in its name.\n\nOK. Maybe `enum crlf_conv_action`? In this case, should I also change\nthe prefix of the enum values? I'm not sure if it's worth it, though,\nsince there are about 52 occurrences of them.\n"},{"id":"408764","messageId":"xmqqd00z397m.fsf@gitster.c.googlers.com","threadId":"54525","inReplyTo":"CAHd-oW44tFRnpDWsQfF80gZn42mvgwZ0rT-ocQZ92zBTrQL-ZA@mail.gmail.com","subject":"Re: [PATCH v3 01/19] convert: make convert_attrs() and convert structs public","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-30T17:38:37Z","receivedAt":"2020-10-30T17:44:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matheus Tavares Bernardino <matheus.bernardino@usp.br> writes:\n\n>> > +enum crlf_action {\n>> > +     CRLF_UNDEFINED,\n>> > +     CRLF_BINARY,\n>> > +     CRLF_TEXT,\n>> > +     CRLF_TEXT_INPUT,\n>> > +     CRLF_TEXT_CRLF,\n>> > +     CRLF_AUTO,\n>> > +     CRLF_AUTO_INPUT,\n>> > +     CRLF_AUTO_CRLF\n>> > +};\n>> > +\n>> > +struct convert_driver;\n>> > +\n>> > +struct conv_attrs {\n>> > +     struct convert_driver *drv;\n>> ...\n>> > +void convert_attrs(const struct index_state *istate,\n>> > +                struct conv_attrs *ca, const char *path);\n>> > +\n>> >  /*\n>> >   * Initialize the checkout metadata with the given values.  Any argument may be\n>> >   * NULL if it is not applicable.  The treeish should be a commit if that is\n>>\n>> The new global symbols are reasonable, I would think, with a\n>> possible exception of \"crlf_action\", which may want to also have\n>> \"conv\" or \"convert\" somewhere in its name.\n>\n> OK. Maybe `enum crlf_conv_action`? In this case, should I also change\n\nEither that, or \"conv_crlf_action\" (or even use the fully spelled\n\"convert_\" as the prefix common to the global symbols from the\nsubsystem).\n\n> In this case, should I also change\n> the prefix of the enum values? I'm not sure if it's worth it, though,\n> since there are about 52 occurrences of them.\n\nAt the use sites, these constants will be passed to, or compared\nwith values returned by, the API functions whose names make it clear\nthat they are from the \"convert_\" family, so I think it is OK to\nleave the values as-is, as long as there is no unrelated symbol\nwhose name starts with \"CRLF_\" (and \"git grep '^CRLF_'\" tells me\nthat there is not any).\n\nThanks.\n"},{"id":"408771","messageId":"xmqqk0v75rbt.fsf@gitster.c.googlers.com","threadId":"54525","inReplyTo":"2caa2c4345d524e9e3bb0c388f8dc0b99236d166.1603937110.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v3 05/19] entry: extract a header file for entry.c functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-30T21:36:38Z","receivedAt":"2020-10-30T21:36:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matheus Tavares <matheus.bernardino@usp.br> writes:\n\n> The declarations of entry.c's public functions and structures currently\n> reside in cache.h. Although not many, they contribute to the size of\n> cache.h and, when changed, cause the unnecessary recompilation of\n> modules that don't really use these functions. So let's move them to a\n> new entry.h header.\n\nGood idea.  This is mostly moving things around, so there are only a\nfew minor nits.\n\n> diff --git a/entry.h b/entry.h\n> new file mode 100644\n> index 0000000000..2d69185448\n> --- /dev/null\n> +++ b/entry.h\n> @@ -0,0 +1,41 @@\n> +#ifndef ENTRY_H\n> +#define ENTRY_H\n> +\n> +#include \"cache.h\"\n> +#include \"convert.h\"\n> +\n> +struct checkout {\n> +\tstruct index_state *istate;\n> +\tconst char *base_dir;\n> +\tint base_dir_len;\n> +\tstruct delayed_checkout *delayed_checkout;\n> +\tstruct checkout_metadata meta;\n> +\tunsigned force:1,\n> +\t\t quiet:1,\n> +\t\t not_new:1,\n> +\t\t clone:1,\n> +\t\t refresh_cache:1;\n> +};\n> +#define CHECKOUT_INIT { NULL, \"\" }\n> +\n\nIt makes sense to have a blank here, like you did, as we just\ncompleted the definition of \"struct checkout\" and things directly\nrelated to it.\n\n> +#define TEMPORARY_FILENAME_LENGTH 25\n> +\n> +/*\n> + * Write the contents from ce out to the working tree.\n> + *\n> + * When topath[] is not NULL, instead of writing to the working tree\n> + * file named by ce, a temporary file is created by this function and\n> + * its name is returned in topath[], which must be able to hold at\n> + * least TEMPORARY_FILENAME_LENGTH bytes long.\n> + */\n> +int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n> +\t\t   char *topath, int *nr_checkouts);\n\nThe comment before the above block applies to both the function and\nto the TEMPORARY_FILENAME_LENGTH preprocessor macro.  And this is\nwhere we conclude the definition related to the function so it is a\ngood idea to have a blank line here....\n\n> +void enable_delayed_checkout(struct checkout *state);\n> +int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n\n....and here, as we have finished talking about the \"delayed\" stuff.\n\n> +/*\n> + * Unlink the last component and schedule the leading directories for\n> + * removal, such that empty directories get removed.\n> + */\n> +void unlink_entry(const struct cache_entry *ce);\n> +\n> +#endif /* ENTRY_H */\n> diff --git a/unpack-trees.c b/unpack-trees.c\n> index 323280dd48..a511fadd89 100644\n> --- a/unpack-trees.c\n> +++ b/unpack-trees.c\n> @@ -16,6 +16,7 @@\n>  #include \"fsmonitor.h\"\n>  #include \"object-store.h\"\n>  #include \"promisor-remote.h\"\n> +#include \"entry.h\"\n>  \n>  /*\n>   * Error messages expected by scripts out of plumbing commands such as\nn\n"},{"id":"408772","messageId":"xmqqft5v5qbf.fsf@gitster.c.googlers.com","threadId":"54525","inReplyTo":"81e03baab1dd7e28262e1d721eac1646c5908b5a.1603937110.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v3 08/19] entry: move conv_attrs lookup up to checkout_entry()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-30T21:58:28Z","receivedAt":"2020-10-30T21:58:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matheus Tavares <matheus.bernardino@usp.br> writes:\n\n> +/* Note: ca is used (and required) iff the entry refers to a regular file. */\n\nThis reflects how the current code happens to work, and it is\nunlikely to change (in other words, I offhand do not think of a\nreason why attributes may affect checking out a symlink or a\nsubmodule), so that's probably OK.  I mention this specifically\nbecause ...\n\n> +static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca,\n> +\t\t       const struct checkout *state, int to_tempfile)\n>  {\n>  \tunsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;\n>  \tstruct delayed_checkout *dco = state->delayed_checkout;\n> @@ -281,8 +282,7 @@ static int write_entry(struct cache_entry *ce,\n>  \tclone_checkout_metadata(&meta, &state->meta, &ce->oid);\n>  \n>  \tif (ce_mode_s_ifmt == S_IFREG) {\n> -\t\tstruct stream_filter *filter = get_stream_filter(state->istate, ce->name,\n> -\t\t\t\t\t\t\t\t &ce->oid);\n> +\t\tstruct stream_filter *filter = get_stream_filter_ca(ca, &ce->oid);\n>  \t\tif (filter &&\n>  \t\t    !streaming_write_entry(ce, path, filter,\n>  \t\t\t\t\t   state, to_tempfile,\n> @@ -329,14 +329,17 @@ static int write_entry(struct cache_entry *ce,\n>  \t\t * Convert from git internal format to working tree format\n>  \t\t */\n>  \t\tif (dco && dco->state != CE_NO_DELAY) {\n> -\t\t\tret = async_convert_to_working_tree(state->istate, ce->name, new_blob,\n> -\t\t\t\t\t\t\t    size, &buf, &meta, dco);\n> +\t\t\tret = async_convert_to_working_tree_ca(ca, ce->name,\n> +\t\t\t\t\t\t\t       new_blob, size,\n> +\t\t\t\t\t\t\t       &buf, &meta, dco);\n>  \t\t\tif (ret && string_list_has_string(&dco->paths, ce->name)) {\n>  \t\t\t\tfree(new_blob);\n>  \t\t\t\tgoto delayed;\n>  \t\t\t}\n> -\t\t} else\n> -\t\t\tret = convert_to_working_tree(state->istate, ce->name, new_blob, size, &buf, &meta);\n> +\t\t} else {\n> +\t\t\tret = convert_to_working_tree_ca(ca, ce->name, new_blob,\n> +\t\t\t\t\t\t\t size, &buf, &meta);\n> +\t\t}\n>  \n>  \t\tif (ret) {\n>  \t\t\tfree(new_blob);\n> @@ -442,6 +445,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n>  {\n>  \tstatic struct strbuf path = STRBUF_INIT;\n>  \tstruct stat st;\n> +\tstruct conv_attrs ca;\n>  \n>  \tif (ce->ce_flags & CE_WT_REMOVE) {\n>  \t\tif (topath)\n> @@ -454,8 +458,13 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n>  \t\treturn 0;\n>  \t}\n>  \n> -\tif (topath)\n> -\t\treturn write_entry(ce, topath, state, 1);\n> +\tif (topath) {\n> +\t\tif (S_ISREG(ce->ce_mode)) {\n> +\t\t\tconvert_attrs(state->istate, &ca, ce->name);\n> +\t\t\treturn write_entry(ce, topath, &ca, state, 1);\n> +\t\t}\n> +\t\treturn write_entry(ce, topath, NULL, state, 1);\n> +\t}\n\n... it looked somewhat upside-down at the first glance that we\ndecide if lower level routines are allowed to use the ca at this\nhigh level in the callchain.  But it is the point of this change\nto lift the point to make the decision to use attributes higher in\nthe callchain, so it would be OK (or \"unavoidable\").\n\nI wonder if it is worth to avoid early return from the inner block,\nlike this:\n\n\tstruct conv_attrs *use_ca = NULL;\n\t...\n\tif (topath) {\n\t\tstruct conv_attrs ca;\n\t\tif (S_ISREG(...)) {\n\t\t\tconvert_attrs(... &ca ...);\n\t\t\tuse_ca = &ca;\n \t\t}\n\t\treturn write_entry(ce, topath, use_ca, state, 1);\n\t}\n\nwhich would make it easier to further add code that is common to\nboth regular file and other things before we call write_entry().\n\nThe same comment applies to the codepath where a new file gets\ncreated in the next hunk.\n\n> @@ -517,9 +526,16 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n>  \t\treturn 0;\n>  \n>  \tcreate_directories(path.buf, path.len, state);\n> +\n>  \tif (nr_checkouts)\n>  \t\t(*nr_checkouts)++;\n> -\treturn write_entry(ce, path.buf, state, 0);\n> +\n> +\tif (S_ISREG(ce->ce_mode)) {\n> +\t\tconvert_attrs(state->istate, &ca, ce->name);\n> +\t\treturn write_entry(ce, path.buf, &ca, state, 0);\n> +\t}\n> +\n> +\treturn write_entry(ce, path.buf, NULL, state, 0);\n>  }\n>  \n>  void unlink_entry(const struct cache_entry *ce)\n"},{"id":"408773","messageId":"xmqqblgj5q5f.fsf@gitster.c.googlers.com","threadId":"54525","inReplyTo":"e1b886f82320db6fd1b389eb4d63ac45bd5af7b1.1603937110.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v3 09/19] entry: add checkout_entry_ca() which takes preloaded conv_attrs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-30T22:02:04Z","receivedAt":"2020-10-30T22:02:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matheus Tavares <matheus.bernardino@usp.br> writes:\n\n> The parallel checkout machinery will call checkout_entry() for entries\n> that could not be written in parallel due to path collisions. At this\n> point, we will already be holding the conversion attributes for each\n> entry, and it would be wasteful to let checkout_entry() load these\n> again. Instead, let's add the checkout_entry_ca() variant, which\n> optionally takes a preloaded conv_attrs struct.\n>\n> Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n> ---\n\nI think my review comment to 08/19 is partly taken care of with this\nstep.  Perhaps the progression will become simpler to understand if\nwe add this new helper first?  I dunno.  In either case, the end\nresult of applying both 08 and 09 looks quite nicer than the state\nafter up to 07 are applied.\n\n"},{"id":"408900","messageId":"xmqq361rv9fa.fsf@gitster.c.googlers.com","threadId":"54525","inReplyTo":"2bdc13664e65a25607b8ecb4c0ea54fb2dad482c.1603937110.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v3 10/19] unpack-trees: add basic support for parallel checkout","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-02T19:35:37Z","receivedAt":"2020-11-02T19:35:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matheus Tavares <matheus.bernardino@usp.br> writes:\n\n> This new interface allows us to enqueue some of the entries being\n> checked out to later call write_entry() for them in parallel. For now,\n> the parallel checkout machinery is enabled by default and there is no\n> user configuration, but run_parallel_checkout() just writes the queued\n> entries in sequence (without spawning additional workers).\n\nIn other words, this would show the worst case overhead caused by\nthe framework to allow parallel checkout, relative to the current\ncode.  Which is quite a sensible and separate step to have in the\nseries.  I like it.\n\n> The next\n> patch will actually implement the parallelism and, later, we will make\n> it configurable.\n\nOK.\n\n> When there are path collisions among the entries being written (which\n> can happen e.g. with case-sensitive files in case-insensitive file\n> systems), the parallel checkout code detects the problem and marks the\n> item with PC_ITEM_COLLIDED. Later, these items are sequentially fed to\n> checkout_entry() again. This is similar to the way the sequential code\n> deals with collisions, overwriting the previously checked out entries\n> with the subsequent ones. The only difference is that, when we start\n> writing the entries in parallel, we won't be able to determine which of\n> the colliding entries will survive on disk (for the sequential\n> algorithm, it is always the last one).\n\nSure.  \"The last one\" determinism does not buy us very much, but it\nis prudent to keep such a behavioural difference in mind.\n\n> I also experimented with the idea of not overwriting colliding entries,\n> and it seemed to work well in my simple tests. However, because just one\n> entry of each colliding group would be actually written, the others\n> would have null lstat() fields on the index. This might not be a problem\n> by itself, but it could cause performance penalties for subsequent\n> commands that need to refresh the index: when the st_size value cached\n> is 0, read-cache.c:ie_modified() will go to the filesystem to see if the\n> contents match. As mentioned in the function:\n>\n>     * Immediately after read-tree or update-index --cacheinfo,\n>     * the length field is zero, as we have never even read the\n>     * lstat(2) information once, and we cannot trust DATA_CHANGED\n>     * returned by ie_match_stat() which in turn was returned by\n>     * ce_match_stat_basic() to signal that the filesize of the\n>     * blob changed.  We have to actually go to the filesystem to\n>     * see if the contents match, and if so, should answer \"unchanged\".\n>\n> So, if we have N entries in a colliding group and we decide to write and\n> lstat() only one of them, every subsequent git-status will have to read,\n> convert, and hash the written file N - 1 times, to check that the N - 1\n> unwritten entries are dirty. By checking out all colliding entries (like\n> the sequential code does), we only pay the overhead once.\n\nAnd the cost is to writing them out N times is not free, either, I\npresume?\n\nBut I do not see the point of wasting engineering effort by trying\nto make it more efficient to create a corrupt working tree that is\nunusable because some paths that ought to exist are missing, so I\nthink it is OK.\n\n> Co-authored-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> Co-authored-by: Jeff Hostetler <jeffhost@microsoft.com>\n> Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n> ---\n>  Makefile            |   1 +\n>  entry.c             |  17 +-\n>  parallel-checkout.c | 368 ++++++++++++++++++++++++++++++++++++++++++++\n>  parallel-checkout.h |  27 ++++\n>  unpack-trees.c      |   6 +-\n>  5 files changed, 416 insertions(+), 3 deletions(-)\n>  create mode 100644 parallel-checkout.c\n>  create mode 100644 parallel-checkout.h\n>\n> diff --git a/Makefile b/Makefile\n> index 1fb0ec1705..10ee5e709b 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -945,6 +945,7 @@ LIB_OBJS += pack-revindex.o\n>  LIB_OBJS += pack-write.o\n>  LIB_OBJS += packfile.o\n>  LIB_OBJS += pager.o\n> +LIB_OBJS += parallel-checkout.o\n>  LIB_OBJS += parse-options-cb.o\n>  LIB_OBJS += parse-options.o\n>  LIB_OBJS += patch-delta.o\n> diff --git a/entry.c b/entry.c\n> index 9d79a5671f..6676954431 100644\n> --- a/entry.c\n> +++ b/entry.c\n> @@ -7,6 +7,7 @@\n>  #include \"progress.h\"\n>  #include \"fsmonitor.h\"\n>  #include \"entry.h\"\n> +#include \"parallel-checkout.h\"\n>  \n>  static void create_directories(const char *path, int path_len,\n>  \t\t\t       const struct checkout *state)\n> @@ -426,8 +427,17 @@ static void mark_colliding_entries(const struct checkout *state,\n>  \tfor (i = 0; i < state->istate->cache_nr; i++) {\n>  \t\tstruct cache_entry *dup = state->istate->cache[i];\n>  \n> -\t\tif (dup == ce)\n> -\t\t\tbreak;\n> +\t\tif (dup == ce) {\n> +\t\t\t/*\n> +\t\t\t * Parallel checkout creates the files in no particular\n> +\t\t\t * order. So the other side of the collision may appear\n> +\t\t\t * after the given cache_entry in the array.\n> +\t\t\t */\n> +\t\t\tif (parallel_checkout_status() == PC_RUNNING)\n> +\t\t\t\tcontinue;\n> +\t\t\telse\n> +\t\t\t\tbreak;\n> +\t\t}\n>  \n>  \t\tif (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))\n>  \t\t\tcontinue;\n> @@ -536,6 +546,9 @@ int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,\n>  \t\tca = &ca_buf;\n>  \t}\n>  \n> +\tif (!enqueue_checkout(ce, ca))\n> +\t\treturn 0;\n> +\n>  \treturn write_entry(ce, path.buf, ca, state, 0);\n\nIt it is not wrong but feels strange that paths that cannot be\nhandled by parallel codepath for whatever reason are written using\nthe fallback code, but the fallback actually touches the disk before\nthe queued paths for parallel writeout ;-) What's the reason why\nsome paths cannot be handled by the new codepath again?  Also, can a\npath that is handled by the fallback code collide with other paths\nthat are handled by the parallel codepath, and what happens for\nthese paths?\n\n>  }\n>  \n> diff --git a/parallel-checkout.c b/parallel-checkout.c\n> new file mode 100644\n> index 0000000000..981dbe6ff3\n> --- /dev/null\n> +++ b/parallel-checkout.c\n> @@ -0,0 +1,368 @@\n> +#include \"cache.h\"\n> +#include \"entry.h\"\n> +#include \"parallel-checkout.h\"\n> +#include \"streaming.h\"\n> +\n> +enum pc_item_status {\n> +\tPC_ITEM_PENDING = 0,\n> +\tPC_ITEM_WRITTEN,\n> +\t/*\n> +\t * The entry could not be written because there was another file\n> +\t * already present in its path or leading directories. Since\n> +\t * checkout_entry_ca() removes such files from the working tree before\n> +\t * enqueueing the entry for parallel checkout, it means that there was\n> +\t * a path collision among the entries being written.\n> +\t */\n> +\tPC_ITEM_COLLIDED,\n> +\tPC_ITEM_FAILED,\n> +};\n> +\n> +struct parallel_checkout_item {\n> +\t/* pointer to a istate->cache[] entry. Not owned by us. */\n> +\tstruct cache_entry *ce;\n> +\tstruct conv_attrs ca;\n> +\tstruct stat st;\n> +\tenum pc_item_status status;\n> +};\n> +\n> +struct parallel_checkout {\n> +\tenum pc_status status;\n> +\tstruct parallel_checkout_item *items;\n> +\tsize_t nr, alloc;\n> +};\n> +\n> +static struct parallel_checkout parallel_checkout = { 0 };\n\nCan't we let this handled by BSS by not explicitly giving an initial\nvalue?\n\n> +enum pc_status parallel_checkout_status(void)\n> +{\n> +\treturn parallel_checkout.status;\n> +}\n> +\n> +void init_parallel_checkout(void)\n> +{\n> +\tif (parallel_checkout.status != PC_UNINITIALIZED)\n> +\t\tBUG(\"parallel checkout already initialized\");\n> +\n> +\tparallel_checkout.status = PC_ACCEPTING_ENTRIES;\n> +}\n> +\n> +static void finish_parallel_checkout(void)\n> +{\n> +\tif (parallel_checkout.status == PC_UNINITIALIZED)\n> +\t\tBUG(\"cannot finish parallel checkout: not initialized yet\");\n> +\n> +\tfree(parallel_checkout.items);\n> +\tmemset(&parallel_checkout, 0, sizeof(parallel_checkout));\n> +}\n> +\n> +static int is_eligible_for_parallel_checkout(const struct cache_entry *ce,\n> +\t\t\t\t\t     const struct conv_attrs *ca)\n> +{\n> +\tenum conv_attrs_classification c;\n> +\n> +\tif (!S_ISREG(ce->ce_mode))\n> +\t\treturn 0;\n> +\n> +\tc = classify_conv_attrs(ca);\n> +\tswitch (c) {\n> +\tcase CA_CLASS_INCORE:\n> +\t\treturn 1;\n> +\n> +\tcase CA_CLASS_INCORE_FILTER:\n> +\t\t/*\n> +\t\t * It would be safe to allow concurrent instances of\n> +\t\t * single-file smudge filters, like rot13, but we should not\n> +\t\t * assume that all filters are parallel-process safe. So we\n> +\t\t * don't allow this.\n> +\t\t */\n> +\t\treturn 0;\n> +\n> +\tcase CA_CLASS_INCORE_PROCESS:\n> +\t\t/*\n> +\t\t * The parallel queue and the delayed queue are not compatible,\n> +\t\t * so they must be kept completely separated. And we can't tell\n> +\t\t * if a long-running process will delay its response without\n> +\t\t * actually asking it to perform the filtering. Therefore, this\n> +\t\t * type of filter is not allowed in parallel checkout.\n> +\t\t *\n> +\t\t * Furthermore, there should only be one instance of the\n> +\t\t * long-running process filter as we don't know how it is\n> +\t\t * managing its own concurrency. So, spreading the entries that\n> +\t\t * requisite such a filter among the parallel workers would\n> +\t\t * require a lot more inter-process communication. We would\n> +\t\t * probably have to designate a single process to interact with\n> +\t\t * the filter and send all the necessary data to it, for each\n> +\t\t * entry.\n> +\t\t */\n> +\t\treturn 0;\n> +\n> +\tcase CA_CLASS_STREAMABLE:\n> +\t\treturn 1;\n> +\n> +\tdefault:\n> +\t\tBUG(\"unsupported conv_attrs classification '%d'\", c);\n> +\t}\n> +}\n\nOK, the comments fairly clearly explain the reason for each case.\nGood.\n\n> +static int handle_results(struct checkout *state)\n> +{\n> +\tint ret = 0;\n> +\tsize_t i;\n> +\tint have_pending = 0;\n> +\n> +\t/*\n> +\t * We first update the successfully written entries with the collected\n> +\t * stat() data, so that they can be found by mark_colliding_entries(),\n> +\t * in the next loop, when necessary.\n> +\t */\n> +\tfor (i = 0; i < parallel_checkout.nr; ++i) {\n\nWe encourage post_increment++ when there is no particular reason to\ndo otherwise in this codebase (I won't repeat in the remainder of\nthis review).\n\n> +static int reset_fd(int fd, const char *path)\n> +{\n> +\tif (lseek(fd, 0, SEEK_SET) != 0)\n> +\t\treturn error_errno(\"failed to rewind descriptor of %s\", path);\n> +\tif (ftruncate(fd, 0))\n> +\t\treturn error_errno(\"failed to truncate file %s\", path);\n> +\treturn 0;\n> +}\n\nThis is in the error codepath when streaming fails, and we'll later\nattempt the normal \"read object in-core, write it out\" codepath, but\nis it enough to just ftruncate() it?  I am wondering why it is OK\nnot to unlink() the failed one---is it the caller who is responsible\nfor opening the file descriptor to write to, and at the layer of the\ncaller of this helper there is no way to re-open it, or something\nlike that?\n\n\t... /me looks ahead and it seems the answer is \"yes\".\n\n> +static int write_pc_item_to_fd(struct parallel_checkout_item *pc_item, int fd,\n> +\t\t\t       const char *path)\n> ...\n> +\tif (filter) {\n> +\t\tif (stream_blob_to_fd(fd, &pc_item->ce->oid, filter, 1)) {\n> +\t\t\t/* On error, reset fd to try writing without streaming */\n> +\t\t\tif (reset_fd(fd, path))\n> +\t\t\t\treturn -1;\n> +\t\t} else {\n> +\t\t\treturn 0;\n> +\t\t}\n> +\t}\n> +\n> +\tnew_blob = read_blob_entry(pc_item->ce, &size);\n> ...\n> +\twrote = write_in_full(fd, new_blob, size);\n\n> +static int check_leading_dirs(const char *path, int len, int prefix_len)\n> +{\n> +\tconst char *slash = path + len;\n> +\n> +\twhile (slash > path && *slash != '/')\n> +\t\tslash--;\n\nIt is kind of surprising that we do not give us an easy-to-use\nhelper to find the separtor between dirname and basename.  If there\nwere, we do not even need this helper function with an unclear name\n(i.e. \"check\" does not mean much to those who are trying to\nunderstand the caller---\"leading directories are checked for\nwhat???\" will be their question).\n\nPerhaps create or find such a helper to remove this function and use\nhas_dirs_only_path() directly in the caller?\n\n> +\treturn has_dirs_only_path(path, slash - path, prefix_len);\n> +}\n\n> +static void write_pc_item(struct parallel_checkout_item *pc_item,\n> +\t\t\t  struct checkout *state)\n> +{\n> +\tunsigned int mode = (pc_item->ce->ce_mode & 0100) ? 0777 : 0666;\n> +\tint fd = -1, fstat_done = 0;\n> +\tstruct strbuf path = STRBUF_INIT;\n> +\n> +\tstrbuf_add(&path, state->base_dir, state->base_dir_len);\n> +\tstrbuf_add(&path, pc_item->ce->name, pc_item->ce->ce_namelen);\n> +\n> +\t/*\n> +\t * At this point, leading dirs should have already been created. But if\n> +\t * a symlink being checked out has collided with one of the dirs, due to\n> +\t * file system folding rules, it's possible that the dirs are no longer\n\nIs \"file system folding rule\" clear to readers of the code after\nthis patch lands?  It isn't at least to me.\n\n> +\t * present. So we have to check again, and report any path collisions.\n> +\t */\n> +\tif (!check_leading_dirs(path.buf, path.len, state->base_dir_len)) {\n> +\t\tpc_item->status = PC_ITEM_COLLIDED;\n> +\t\tgoto out;\n> +\t}\n\nThanks.\n"},{"id":"408981","messageId":"CAHd-oW5H_LLLkbhe+ffAMkpuuuGrNcOYF4sxUV55qyDomOHRug@mail.gmail.com","threadId":"54525","inReplyTo":"xmqq361rv9fa.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v3 10/19] unpack-trees: add basic support for parallel checkout","fromName":"Matheus Tavares Bernardino","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-03T03:48:01Z","receivedAt":"2020-11-03T03:48:20Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"On Mon, Nov 2, 2020 at 4:35 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Matheus Tavares <matheus.bernardino@usp.br> writes:\n[...]\n> >\n> > @@ -536,6 +546,9 @@ int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,\n> >               ca = &ca_buf;\n> >       }\n> >\n> > +     if (!enqueue_checkout(ce, ca))\n> > +             return 0;\n> > +\n> >       return write_entry(ce, path.buf, ca, state, 0);\n>\n> It it is not wrong but feels strange that paths that cannot be\n> handled by parallel codepath for whatever reason are written using\n> the fallback code, but the fallback actually touches the disk before\n> the queued paths for parallel writeout ;-)\n\nYeah... I also considered having a second \"sequential_checkout_item\"\nqueue, and iterating it after the parallel-eligible entries. But I\nthought that it might be better to write the ineligible entries right\naway and save a little memory (especially for the regular files, for\nwhich we would also have to hold the conversion attributes).\n\nWith that said, I ended up adding a second queue in part 2, just for\nsymlinks. By postponing the checkout of symlinks we can avoid the\ncheck_leading_dirs() function and the additional lstat() calls in the\nworkers. This also makes it possible to create the leading directories\nin parallel (in part 3) with raceproof_create_file(), which is quite\nnice as it only calls stat() when open() fails. And since symlinks\nprobably appear in smaller numbers than regular files, this second\nqueue should never get too long.\n\n> What's the reason why\n> some paths cannot be handled by the new codepath again?\n\nSubmodules and symlinks are not eligible for parallel checkout mainly\nbecause it would be hard to detect collisions when they are involved.\nFor symlinks, one worker could create the symlink a/b => d right\nbefore another worker tries to open() and write() a/b/c, which would\nthen produce the wrong a/d/c file. And for submodules, we could have a\nworker checking out a submodule S while another worker writes the\ncolliding regular file s/f.\n\nAs for regular files, we don't parallelize the checkout of entries\nwhich require external filters, mainly because we cannot guarantee\nthat such filters are parallel-process safe. But also, the\ndelayed-checkout queue is incompatible with the parallel-checkout\nqueue (in the sense that each entry should only be present in one of\nthe two queues).\n\n> Also, can a\n> path that is handled by the fallback code collide with other paths\n> that are handled by the parallel codepath, and what happens for\n> these paths?\n\nYes, it can happen. But the parallel-checkout machinery should be\nready for it. There are two cases:\n\n1. Both paths collide in the basename (e.g. a/b and a/B)\n2. One path collide in the dirname (e.g. a/b and a/B/c)\n\nFor both cases, the collision will happen when trying to write the\nparallel-eligible path. This happens because, for now, all paths that\nare ineligible for parallel-checkout are checked out first. So, in the\nfirst case, we will detect the collision when open() fails in\nwrite_pc_item().\n\nThe second case is a little trickier, since [in part 1] we create the\nleading directories right before enqueueing an entry for\nparallel-checkout. An ineligible entry could then collide with the\ndirname of an already enqueued parallel-eligible entry, removing (and\nreplacing) the created dirs. Also, the ineligible entry could be a\nsymlink, and we want to avoid the case of workers writing the entry\na/b/c at a/d/c due to a symlink in b. These collisions with the\ndirname are detected when has_dirs_only_path() fails in\ncheck_leading_dirs().\n\nFurthermore, there is no risk that has_dirs_only_path() succeeds, but\nthen another entry collides with the leading directories before the\nactual checkout. Because, when we start the workers, no file or\ndirectory is ever removed.\n\n> >  }\n> >\n> > diff --git a/parallel-checkout.c b/parallel-checkout.c\n> > new file mode 100644\n> > index 0000000000..981dbe6ff3\n> > --- /dev/null\n> > +++ b/parallel-checkout.c\n> > @@ -0,0 +1,368 @@\n> > +#include \"cache.h\"\n> > +#include \"entry.h\"\n> > +#include \"parallel-checkout.h\"\n> > +#include \"streaming.h\"\n> > +\n> > +enum pc_item_status {\n> > +     PC_ITEM_PENDING = 0,\n> > +     PC_ITEM_WRITTEN,\n> > +     /*\n> > +      * The entry could not be written because there was another file\n> > +      * already present in its path or leading directories. Since\n> > +      * checkout_entry_ca() removes such files from the working tree before\n> > +      * enqueueing the entry for parallel checkout, it means that there was\n> > +      * a path collision among the entries being written.\n> > +      */\n> > +     PC_ITEM_COLLIDED,\n> > +     PC_ITEM_FAILED,\n> > +};\n> > +\n> > +struct parallel_checkout_item {\n> > +     /* pointer to a istate->cache[] entry. Not owned by us. */\n> > +     struct cache_entry *ce;\n> > +     struct conv_attrs ca;\n> > +     struct stat st;\n> > +     enum pc_item_status status;\n> > +};\n> > +\n> > +struct parallel_checkout {\n> > +     enum pc_status status;\n> > +     struct parallel_checkout_item *items;\n> > +     size_t nr, alloc;\n> > +};\n> > +\n> > +static struct parallel_checkout parallel_checkout = { 0 };\n>\n> Can't we let this handled by BSS by not explicitly giving an initial\n> value?\n\nGood catch, thanks.\n\n> > +enum pc_status parallel_checkout_status(void)\n> > +{\n> > +     return parallel_checkout.status;\n> > +}\n> > +\n> > +void init_parallel_checkout(void)\n> > +{\n> > +     if (parallel_checkout.status != PC_UNINITIALIZED)\n> > +             BUG(\"parallel checkout already initialized\");\n> > +\n> > +     parallel_checkout.status = PC_ACCEPTING_ENTRIES;\n> > +}\n> > +\n> > +static void finish_parallel_checkout(void)\n> > +{\n> > +     if (parallel_checkout.status == PC_UNINITIALIZED)\n> > +             BUG(\"cannot finish parallel checkout: not initialized yet\");\n> > +\n> > +     free(parallel_checkout.items);\n> > +     memset(&parallel_checkout, 0, sizeof(parallel_checkout));\n> > +}\n> > +\n> > +static int is_eligible_for_parallel_checkout(const struct cache_entry *ce,\n> > +                                          const struct conv_attrs *ca)\n> > +{\n> > +     enum conv_attrs_classification c;\n> > +\n> > +     if (!S_ISREG(ce->ce_mode))\n> > +             return 0;\n> > +\n> > +     c = classify_conv_attrs(ca);\n> > +     switch (c) {\n> > +     case CA_CLASS_INCORE:\n> > +             return 1;\n> > +\n> > +     case CA_CLASS_INCORE_FILTER:\n> > +             /*\n> > +              * It would be safe to allow concurrent instances of\n> > +              * single-file smudge filters, like rot13, but we should not\n> > +              * assume that all filters are parallel-process safe. So we\n> > +              * don't allow this.\n> > +              */\n> > +             return 0;\n> > +\n> > +     case CA_CLASS_INCORE_PROCESS:\n> > +             /*\n> > +              * The parallel queue and the delayed queue are not compatible,\n> > +              * so they must be kept completely separated. And we can't tell\n> > +              * if a long-running process will delay its response without\n> > +              * actually asking it to perform the filtering. Therefore, this\n> > +              * type of filter is not allowed in parallel checkout.\n> > +              *\n> > +              * Furthermore, there should only be one instance of the\n> > +              * long-running process filter as we don't know how it is\n> > +              * managing its own concurrency. So, spreading the entries that\n> > +              * requisite such a filter among the parallel workers would\n> > +              * require a lot more inter-process communication. We would\n> > +              * probably have to designate a single process to interact with\n> > +              * the filter and send all the necessary data to it, for each\n> > +              * entry.\n> > +              */\n> > +             return 0;\n> > +\n> > +     case CA_CLASS_STREAMABLE:\n> > +             return 1;\n> > +\n> > +     default:\n> > +             BUG(\"unsupported conv_attrs classification '%d'\", c);\n> > +     }\n> > +}\n>\n> OK, the comments fairly clearly explain the reason for each case.\n> Good.\n>\n> > +static int handle_results(struct checkout *state)\n> > +{\n> > +     int ret = 0;\n> > +     size_t i;\n> > +     int have_pending = 0;\n> > +\n> > +     /*\n> > +      * We first update the successfully written entries with the collected\n> > +      * stat() data, so that they can be found by mark_colliding_entries(),\n> > +      * in the next loop, when necessary.\n> > +      */\n> > +     for (i = 0; i < parallel_checkout.nr; ++i) {\n>\n> We encourage post_increment++ when there is no particular reason to\n> do otherwise in this codebase (I won't repeat in the remainder of\n> this review).\n\nOK, I will fix the pre-increments, thanks.\n\n> > +static int reset_fd(int fd, const char *path)\n> > +{\n> > +     if (lseek(fd, 0, SEEK_SET) != 0)\n> > +             return error_errno(\"failed to rewind descriptor of %s\", path);\n> > +     if (ftruncate(fd, 0))\n> > +             return error_errno(\"failed to truncate file %s\", path);\n> > +     return 0;\n> > +}\n>\n> This is in the error codepath when streaming fails, and we'll later\n> attempt the normal \"read object in-core, write it out\" codepath, but\n> is it enough to just ftruncate() it?  I am wondering why it is OK\n> not to unlink() the failed one---is it the caller who is responsible\n> for opening the file descriptor to write to, and at the layer of the\n> caller of this helper there is no way to re-open it, or something\n> like that?\n\nRight. We also avoid unlinking the failed one to keep the invariant\nthat the first worker to successfully open(O_CREAT | O_EXCL) a file\nhas the \"ownership\" for that path. So other workers that try to open\nthe same path will know that there is a collision and can immediately\nabort checking out their entry.\n\n>         ... /me looks ahead and it seems the answer is \"yes\".\n>\n> > +static int write_pc_item_to_fd(struct parallel_checkout_item *pc_item, int fd,\n> > +                            const char *path)\n> > ...\n> > +     if (filter) {\n> > +             if (stream_blob_to_fd(fd, &pc_item->ce->oid, filter, 1)) {\n> > +                     /* On error, reset fd to try writing without streaming */\n> > +                     if (reset_fd(fd, path))\n> > +                             return -1;\n> > +             } else {\n> > +                     return 0;\n> > +             }\n> > +     }\n> > +\n> > +     new_blob = read_blob_entry(pc_item->ce, &size);\n> > ...\n> > +     wrote = write_in_full(fd, new_blob, size);\n>\n> > +static int check_leading_dirs(const char *path, int len, int prefix_len)\n> > +{\n> > +     const char *slash = path + len;\n> > +\n> > +     while (slash > path && *slash != '/')\n> > +             slash--;\n>\n> It is kind of surprising that we do not give us an easy-to-use\n> helper to find the separtor between dirname and basename.  If there\n> were, we do not even need this helper function with an unclear name\n> (i.e. \"check\" does not mean much to those who are trying to\n> understand the caller---\"leading directories are checked for\n> what???\" will be their question).\n>\n> Perhaps create or find such a helper to remove this function and use\n> has_dirs_only_path() directly in the caller?\n\nOK, I'll look into it. It would be better if we can reuse an already\npresent helper, since this call to has_dirs_only_path() will be\nremoved in part 2.\n\n> > +     return has_dirs_only_path(path, slash - path, prefix_len);\n> > +}\n>\n> > +static void write_pc_item(struct parallel_checkout_item *pc_item,\n> > +                       struct checkout *state)\n> > +{\n> > +     unsigned int mode = (pc_item->ce->ce_mode & 0100) ? 0777 : 0666;\n> > +     int fd = -1, fstat_done = 0;\n> > +     struct strbuf path = STRBUF_INIT;\n> > +\n> > +     strbuf_add(&path, state->base_dir, state->base_dir_len);\n> > +     strbuf_add(&path, pc_item->ce->name, pc_item->ce->ce_namelen);\n> > +\n> > +     /*\n> > +      * At this point, leading dirs should have already been created. But if\n> > +      * a symlink being checked out has collided with one of the dirs, due to\n> > +      * file system folding rules, it's possible that the dirs are no longer\n>\n> Is \"file system folding rule\" clear to readers of the code after\n> this patch lands?  It isn't at least to me.\n\nOK, I will rephrase this paragraph to make it clearer.\n"},{"id":"409142","messageId":"cover.1604521275.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1603937110.git.matheus.bernardino@usp.br","subject":"[PATCH v4 00/19] Parallel Checkout (part I)","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:32:59Z","receivedAt":"2020-11-04T20:33:33Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Changes since v3:\n\nPatch 1:\n  - Renamed 'enum crlf_action' to 'enum convert_crlf_action', since it's\n    now public and the latter suits better the global namespace.\n\nPatch 2:\n  - Implemented the regular [async_]convert_to_working_tree() functions\n    as thin wrappers around the new _ca() variants.\n\nPatches 5 and 6:\n  - Properly added blank lines to separate declaration blocks in entry.h.\n\nPatch 8:\n  - Used a `struct conv_attrs ca_buf` (together with the `ca` pointer)\n    to avoid the early return in checkout_entry() when\n    S_ISREG(ce->ce_mode). I think this makes the patch a little easier\n    to parse and also simplifies the next patch.\n\nPatch 10:\n  - Removed explicit zero initialization of the static struct parallel_checkout.\n  - Removed the check_leading_dirs() function, which had a quite generic\n    name, and integrated the code into write_pc_item().\n\n    Note: for this change, I used the find_last_dir_sep() helper, which\n    is slightly slower since it doesn't take the path's length, and\n    thus, it has to iterate the whole string. Alternatively, We could\n    add an strbuf_find_last_dir_sep() variant which takes an strbuf and\n    starts the search from the end, to save some iterations per path.\n    But this snippet will be removed in part 2, so I thought it wouldn't\n    be worth adding a new helper now.\n\n  - Rephrased comment about the has_dirs_only_path() call in workers, for\n    better clarity.\n\n- Changed all unnecessary uses of ++pre_increment in the series to\n  post_increment++ (patches 10 and 11).\n\n\nJeff Hostetler (4):\n  convert: make convert_attrs() and convert structs public\n  convert: add [async_]convert_to_working_tree_ca() variants\n  convert: add get_stream_filter_ca() variant\n  convert: add conv_attrs classification\n\nMatheus Tavares (15):\n  entry: extract a header file for entry.c functions\n  entry: make fstat_output() and read_blob_entry() public\n  entry: extract cache_entry update from write_entry()\n  entry: move conv_attrs lookup up to checkout_entry()\n  entry: add checkout_entry_ca() which takes preloaded conv_attrs\n  unpack-trees: add basic support for parallel checkout\n  parallel-checkout: make it truly parallel\n  parallel-checkout: support progress displaying\n  make_transient_cache_entry(): optionally alloc from mem_pool\n  builtin/checkout.c: complete parallel checkout support\n  checkout-index: add parallel checkout support\n  parallel-checkout: add tests for basic operations\n  parallel-checkout: add tests related to clone collisions\n  parallel-checkout: add tests related to .gitattributes\n  ci: run test round with parallel-checkout enabled\n\n .gitignore                              |   1 +\n Documentation/config/checkout.txt       |  21 +\n Makefile                                |   2 +\n apply.c                                 |   1 +\n builtin.h                               |   1 +\n builtin/checkout--helper.c              | 142 ++++++\n builtin/checkout-index.c                |  22 +-\n builtin/checkout.c                      |  21 +-\n builtin/difftool.c                      |   3 +-\n cache.h                                 |  34 +-\n ci/run-build-and-tests.sh               |   1 +\n convert.c                               | 130 ++---\n convert.h                               |  96 +++-\n entry.c                                 | 102 ++--\n entry.h                                 |  55 +++\n git.c                                   |   2 +\n parallel-checkout.c                     | 632 ++++++++++++++++++++++++\n parallel-checkout.h                     | 103 ++++\n read-cache.c                            |  12 +-\n t/README                                |   4 +\n t/lib-encoding.sh                       |  25 +\n t/lib-parallel-checkout.sh              |  46 ++\n t/t0028-working-tree-encoding.sh        |  25 +-\n t/t2080-parallel-checkout-basics.sh     | 170 +++++++\n t/t2081-parallel-checkout-collisions.sh |  98 ++++\n t/t2082-parallel-checkout-attributes.sh | 174 +++++++\n unpack-trees.c                          |  22 +-\n 27 files changed, 1766 insertions(+), 179 deletions(-)\n create mode 100644 builtin/checkout--helper.c\n create mode 100644 entry.h\n create mode 100644 parallel-checkout.c\n create mode 100644 parallel-checkout.h\n create mode 100644 t/lib-encoding.sh\n create mode 100644 t/lib-parallel-checkout.sh\n create mode 100755 t/t2080-parallel-checkout-basics.sh\n create mode 100755 t/t2081-parallel-checkout-collisions.sh\n create mode 100755 t/t2082-parallel-checkout-attributes.sh\n\nRange-diff against v3:\n 1:  dfc3e0fd62 !  1:  2726f6dc05 convert: make convert_attrs() and convert structs public\n    @@ Commit message\n         Move convert_attrs() declaration from convert.c to convert.h, together\n         with the conv_attrs struct and the crlf_action enum. This function and\n         the data structures will be used outside convert.c in the upcoming\n    -    parallel checkout implementation.\n    +    parallel checkout implementation. Note that crlf_action is renamed to\n    +    convert_crlf_action, which is more appropriate for the global namespace.\n     \n         Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n         [matheus.bernardino: squash and reword msg]\n    @@ convert.c\n      struct text_stat {\n      \t/* NUL, CR, LF and CRLF counts */\n      \tunsigned nul, lonecr, lonelf, crlf;\n    +@@ convert.c: static int text_eol_is_crlf(void)\n    + \treturn 0;\n    + }\n    + \n    +-static enum eol output_eol(enum crlf_action crlf_action)\n    ++static enum eol output_eol(enum convert_crlf_action crlf_action)\n    + {\n    + \tswitch (crlf_action) {\n    + \tcase CRLF_BINARY:\n    +@@ convert.c: static int has_crlf_in_index(const struct index_state *istate, const char *path)\n    + }\n    + \n    + static int will_convert_lf_to_crlf(struct text_stat *stats,\n    +-\t\t\t\t   enum crlf_action crlf_action)\n    ++\t\t\t\t   enum convert_crlf_action crlf_action)\n    + {\n    + \tif (output_eol(crlf_action) != EOL_CRLF)\n    + \t\treturn 0;\n    +@@ convert.c: static int encode_to_worktree(const char *path, const char *src, size_t src_len,\n    + static int crlf_to_git(const struct index_state *istate,\n    + \t\t       const char *path, const char *src, size_t len,\n    + \t\t       struct strbuf *buf,\n    +-\t\t       enum crlf_action crlf_action, int conv_flags)\n    ++\t\t       enum convert_crlf_action crlf_action, int conv_flags)\n    + {\n    + \tstruct text_stat stats;\n    + \tchar *dst;\n    +@@ convert.c: static int crlf_to_git(const struct index_state *istate,\n    + \treturn 1;\n    + }\n    + \n    +-static int crlf_to_worktree(const char *src, size_t len,\n    +-\t\t\t    struct strbuf *buf, enum crlf_action crlf_action)\n    ++static int crlf_to_worktree(const char *src, size_t len, struct strbuf *buf,\n    ++\t\t\t    enum convert_crlf_action crlf_action)\n    + {\n    + \tchar *to_free = NULL;\n    + \tstruct text_stat stats;\n    +@@ convert.c: static const char *git_path_check_encoding(struct attr_check_item *check)\n    + \treturn value;\n    + }\n    + \n    +-static enum crlf_action git_path_check_crlf(struct attr_check_item *check)\n    ++static enum convert_crlf_action git_path_check_crlf(struct attr_check_item *check)\n    + {\n    + \tconst char *value = check->value;\n    + \n     @@ convert.c: static int git_path_check_ident(struct attr_check_item *check)\n      \treturn !!ATTR_TRUE(value);\n      }\n    @@ convert.c: static int git_path_check_ident(struct attr_check_item *check)\n      \n     \n      ## convert.h ##\n    -@@ convert.h: enum eol {\n    - #endif\n    +@@ convert.h: struct checkout_metadata {\n    + \tstruct object_id blob;\n      };\n      \n    -+enum crlf_action {\n    ++enum convert_crlf_action {\n     +\tCRLF_UNDEFINED,\n     +\tCRLF_BINARY,\n     +\tCRLF_TEXT,\n    @@ convert.h: enum eol {\n     +\n     +struct conv_attrs {\n     +\tstruct convert_driver *drv;\n    -+\tenum crlf_action attr_action; /* What attr says */\n    -+\tenum crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n    ++\tenum convert_crlf_action attr_action; /* What attr says */\n    ++\tenum convert_crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n     +\tint ident;\n     +\tconst char *working_tree_encoding; /* Supported encoding or default encoding if NULL */\n     +};\n     +\n    - enum ce_delay_state {\n    - \tCE_NO_DELAY = 0,\n    - \tCE_CAN_DELAY = 1,\n    -@@ convert.h: void convert_to_git_filter_fd(const struct index_state *istate,\n    - int would_convert_to_git_filter_fd(const struct index_state *istate,\n    - \t\t\t\t   const char *path);\n    - \n     +void convert_attrs(const struct index_state *istate,\n     +\t\t   struct conv_attrs *ca, const char *path);\n     +\n    - /*\n    -  * Initialize the checkout metadata with the given values.  Any argument may be\n    -  * NULL if it is not applicable.  The treeish should be a commit if that is\n    + extern enum eol core_eol;\n    + extern char *check_roundtrip_encoding;\n    + const char *get_cached_convert_stats_ascii(const struct index_state *istate,\n 2:  c5fbd1e16d !  2:  fc03417592 convert: add [async_]convert_to_working_tree_ca() variants\n    @@ convert.c: static int convert_to_working_tree_internal(const struct index_state\n      \n      \treturn ret | ret_filter;\n      }\n    -@@ convert.c: int async_convert_to_working_tree(const struct index_state *istate,\n    - \t\t\t\t  const struct checkout_metadata *meta,\n    - \t\t\t\t  void *dco)\n    - {\n    --\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco);\n    -+\tstruct conv_attrs ca;\n    -+\tconvert_attrs(istate, &ca, path);\n    -+\treturn convert_to_working_tree_internal(&ca, path, src, len, dst, 0, meta, dco);\n    - }\n      \n    - int convert_to_working_tree(const struct index_state *istate,\n    -@@ convert.c: int convert_to_working_tree(const struct index_state *istate,\n    - \t\t\t    size_t len, struct strbuf *dst,\n    - \t\t\t    const struct checkout_metadata *meta)\n    - {\n    --\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL);\n    -+\tstruct conv_attrs ca;\n    -+\tconvert_attrs(istate, &ca, path);\n    -+\treturn convert_to_working_tree_internal(&ca, path, src, len, dst, 0, meta, NULL);\n    -+}\n    -+\n    +-int async_convert_to_working_tree(const struct index_state *istate,\n    +-\t\t\t\t  const char *path, const char *src,\n    +-\t\t\t\t  size_t len, struct strbuf *dst,\n    +-\t\t\t\t  const struct checkout_metadata *meta,\n    +-\t\t\t\t  void *dco)\n     +int async_convert_to_working_tree_ca(const struct conv_attrs *ca,\n     +\t\t\t\t     const char *path, const char *src,\n     +\t\t\t\t     size_t len, struct strbuf *dst,\n     +\t\t\t\t     const struct checkout_metadata *meta,\n     +\t\t\t\t     void *dco)\n    -+{\n    + {\n    +-\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco);\n     +\treturn convert_to_working_tree_internal(ca, path, src, len, dst, 0, meta, dco);\n    -+}\n    -+\n    + }\n    + \n    +-int convert_to_working_tree(const struct index_state *istate,\n    +-\t\t\t    const char *path, const char *src,\n    +-\t\t\t    size_t len, struct strbuf *dst,\n    +-\t\t\t    const struct checkout_metadata *meta)\n     +int convert_to_working_tree_ca(const struct conv_attrs *ca,\n     +\t\t\t       const char *path, const char *src,\n     +\t\t\t       size_t len, struct strbuf *dst,\n     +\t\t\t       const struct checkout_metadata *meta)\n    -+{\n    + {\n    +-\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL);\n     +\treturn convert_to_working_tree_internal(ca, path, src, len, dst, 0, meta, NULL);\n      }\n      \n    @@ convert.c: int convert_to_working_tree(const struct index_state *istate,\n      \t\tlen = dst->len;\n     \n      ## convert.h ##\n    -@@ convert.h: int convert_to_working_tree(const struct index_state *istate,\n    - \t\t\t    const char *path, const char *src,\n    - \t\t\t    size_t len, struct strbuf *dst,\n    - \t\t\t    const struct checkout_metadata *meta);\n    +@@ convert.h: const char *get_convert_attr_ascii(const struct index_state *istate,\n    + int convert_to_git(const struct index_state *istate,\n    + \t\t   const char *path, const char *src, size_t len,\n    + \t\t   struct strbuf *dst, int conv_flags);\n    +-int convert_to_working_tree(const struct index_state *istate,\n    +-\t\t\t    const char *path, const char *src,\n    +-\t\t\t    size_t len, struct strbuf *dst,\n    +-\t\t\t    const struct checkout_metadata *meta);\n    +-int async_convert_to_working_tree(const struct index_state *istate,\n    +-\t\t\t\t  const char *path, const char *src,\n    +-\t\t\t\t  size_t len, struct strbuf *dst,\n    +-\t\t\t\t  const struct checkout_metadata *meta,\n    +-\t\t\t\t  void *dco);\n     +int convert_to_working_tree_ca(const struct conv_attrs *ca,\n     +\t\t\t       const char *path, const char *src,\n     +\t\t\t       size_t len, struct strbuf *dst,\n     +\t\t\t       const struct checkout_metadata *meta);\n    - int async_convert_to_working_tree(const struct index_state *istate,\n    - \t\t\t\t  const char *path, const char *src,\n    - \t\t\t\t  size_t len, struct strbuf *dst,\n    - \t\t\t\t  const struct checkout_metadata *meta,\n    - \t\t\t\t  void *dco);\n     +int async_convert_to_working_tree_ca(const struct conv_attrs *ca,\n     +\t\t\t\t     const char *path, const char *src,\n     +\t\t\t\t     size_t len, struct strbuf *dst,\n     +\t\t\t\t     const struct checkout_metadata *meta,\n     +\t\t\t\t     void *dco);\n    ++static inline int convert_to_working_tree(const struct index_state *istate,\n    ++\t\t\t\t\t  const char *path, const char *src,\n    ++\t\t\t\t\t  size_t len, struct strbuf *dst,\n    ++\t\t\t\t\t  const struct checkout_metadata *meta)\n    ++{\n    ++\tstruct conv_attrs ca;\n    ++\tconvert_attrs(istate, &ca, path);\n    ++\treturn convert_to_working_tree_ca(&ca, path, src, len, dst, meta);\n    ++}\n    ++static inline int async_convert_to_working_tree(const struct index_state *istate,\n    ++\t\t\t\t\t\tconst char *path, const char *src,\n    ++\t\t\t\t\t\tsize_t len, struct strbuf *dst,\n    ++\t\t\t\t\t\tconst struct checkout_metadata *meta,\n    ++\t\t\t\t\t\tvoid *dco)\n    ++{\n    ++\tstruct conv_attrs ca;\n    ++\tconvert_attrs(istate, &ca, path);\n    ++\treturn async_convert_to_working_tree_ca(&ca, path, src, len, dst, meta, dco);\n    ++}\n      int async_query_available_blobs(const char *cmd,\n      \t\t\t\tstruct string_list *available_paths);\n      int renormalize_buffer(const struct index_state *istate,\n 3:  c77b16f694 =  3:  8ce20f1031 convert: add get_stream_filter_ca() variant\n 4:  18c3f4247e =  4:  aa1eb461f4 convert: add conv_attrs classification\n 5:  2caa2c4345 !  5:  cb3dea224b entry: extract a header file for entry.c functions\n    @@ entry.h (new)\n     +#define CHECKOUT_INIT { NULL, \"\" }\n     +\n     +#define TEMPORARY_FILENAME_LENGTH 25\n    -+\n     +/*\n     + * Write the contents from ce out to the working tree.\n     + *\n    @@ entry.h (new)\n     + */\n     +int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n     +\t\t   char *topath, int *nr_checkouts);\n    ++\n     +void enable_delayed_checkout(struct checkout *state);\n     +int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n    ++\n     +/*\n     + * Unlink the last component and schedule the leading directories for\n     + * removal, such that empty directories get removed.\n 6:  bfa52df9e2 !  6:  46ed6274d7 entry: make fstat_output() and read_blob_entry() public\n    @@ entry.c: static int write_entry(struct cache_entry *ce,\n     \n      ## entry.h ##\n     @@ entry.h: int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n    -  * removal, such that empty directories get removed.\n       */\n      void unlink_entry(const struct cache_entry *ce);\n    + \n     +void *read_blob_entry(const struct cache_entry *ce, unsigned long *size);\n     +int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st);\n    - \n    ++\n      #endif /* ENTRY_H */\n 7:  91ef17f533 !  7:  a0479d02ff entry: extract cache_entry update from write_entry()\n    @@ entry.c: static int write_entry(struct cache_entry *ce,\n      \treturn 0;\n     \n      ## entry.h ##\n    -@@ entry.h: int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n    - void unlink_entry(const struct cache_entry *ce);\n    +@@ entry.h: void unlink_entry(const struct cache_entry *ce);\n    + \n      void *read_blob_entry(const struct cache_entry *ce, unsigned long *size);\n      int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st);\n     +void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,\n 8:  81e03baab1 !  8:  5c993cc27f entry: move conv_attrs lookup up to checkout_entry()\n    @@ entry.c: int checkout_entry(struct cache_entry *ce, const struct checkout *state\n      {\n      \tstatic struct strbuf path = STRBUF_INIT;\n      \tstruct stat st;\n    -+\tstruct conv_attrs ca;\n    ++\tstruct conv_attrs ca_buf, *ca = NULL;\n      \n      \tif (ce->ce_flags & CE_WT_REMOVE) {\n      \t\tif (topath)\n    @@ entry.c: int checkout_entry(struct cache_entry *ce, const struct checkout *state\n     -\t\treturn write_entry(ce, topath, state, 1);\n     +\tif (topath) {\n     +\t\tif (S_ISREG(ce->ce_mode)) {\n    -+\t\t\tconvert_attrs(state->istate, &ca, ce->name);\n    -+\t\t\treturn write_entry(ce, topath, &ca, state, 1);\n    ++\t\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n    ++\t\t\tca = &ca_buf;\n     +\t\t}\n    -+\t\treturn write_entry(ce, topath, NULL, state, 1);\n    ++\t\treturn write_entry(ce, topath, ca, state, 1);\n     +\t}\n      \n      \tstrbuf_reset(&path);\n    @@ entry.c: int checkout_entry(struct cache_entry *ce, const struct checkout *state\n     -\treturn write_entry(ce, path.buf, state, 0);\n     +\n     +\tif (S_ISREG(ce->ce_mode)) {\n    -+\t\tconvert_attrs(state->istate, &ca, ce->name);\n    -+\t\treturn write_entry(ce, path.buf, &ca, state, 0);\n    ++\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n    ++\t\tca = &ca_buf;\n     +\t}\n     +\n     +\treturn write_entry(ce, path.buf, NULL, state, 0);\n 9:  e1b886f823 !  9:  aa635bda21 entry: add checkout_entry_ca() which takes preloaded conv_attrs\n    @@ entry.c: static void mark_colliding_entries(const struct checkout *state,\n      {\n      \tstatic struct strbuf path = STRBUF_INIT;\n      \tstruct stat st;\n    --\tstruct conv_attrs ca;\n    +-\tstruct conv_attrs ca_buf, *ca = NULL;\n     +\tstruct conv_attrs ca_buf;\n      \n      \tif (ce->ce_flags & CE_WT_REMOVE) {\n    @@ entry.c: int checkout_entry(struct cache_entry *ce, const struct checkout *state\n      \n      \tif (topath) {\n     -\t\tif (S_ISREG(ce->ce_mode)) {\n    --\t\t\tconvert_attrs(state->istate, &ca, ce->name);\n    --\t\t\treturn write_entry(ce, topath, &ca, state, 1);\n     +\t\tif (S_ISREG(ce->ce_mode) && !ca) {\n    -+\t\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n    -+\t\t\tca = &ca_buf;\n    + \t\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n    + \t\t\tca = &ca_buf;\n      \t\t}\n    --\t\treturn write_entry(ce, topath, NULL, state, 1);\n    -+\t\treturn write_entry(ce, topath, ca, state, 1);\n    - \t}\n    - \n    - \tstrbuf_reset(&path);\n     @@ entry.c: int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n      \tif (nr_checkouts)\n      \t\t(*nr_checkouts)++;\n      \n     -\tif (S_ISREG(ce->ce_mode)) {\n    --\t\tconvert_attrs(state->istate, &ca, ce->name);\n    --\t\treturn write_entry(ce, path.buf, &ca, state, 0);\n     +\tif (S_ISREG(ce->ce_mode) && !ca) {\n    -+\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n    -+\t\tca = &ca_buf;\n    + \t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n    + \t\tca = &ca_buf;\n      \t}\n      \n     -\treturn write_entry(ce, path.buf, NULL, state, 0);\n    @@ entry.h: struct checkout {\n     +int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,\n     +\t\t      const struct checkout *state, char *topath,\n     +\t\t      int *nr_checkouts);\n    -+\n    + \n      void enable_delayed_checkout(struct checkout *state);\n      int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n    - /*\n10:  2bdc13664e ! 10:  bc8447cd9c unpack-trees: add basic support for parallel checkout\n    @@ parallel-checkout.c (new)\n     +\tsize_t nr, alloc;\n     +};\n     +\n    -+static struct parallel_checkout parallel_checkout = { 0 };\n    ++static struct parallel_checkout parallel_checkout;\n     +\n     +enum pc_status parallel_checkout_status(void)\n     +{\n    @@ parallel-checkout.c (new)\n     +\t * stat() data, so that they can be found by mark_colliding_entries(),\n     +\t * in the next loop, when necessary.\n     +\t */\n    -+\tfor (i = 0; i < parallel_checkout.nr; ++i) {\n    ++\tfor (i = 0; i < parallel_checkout.nr; i++) {\n     +\t\tstruct parallel_checkout_item *pc_item = &parallel_checkout.items[i];\n     +\t\tif (pc_item->status == PC_ITEM_WRITTEN)\n     +\t\t\tupdate_ce_after_write(state, pc_item->ce, &pc_item->st);\n     +\t}\n     +\n    -+\tfor (i = 0; i < parallel_checkout.nr; ++i) {\n    ++\tfor (i = 0; i < parallel_checkout.nr; i++) {\n     +\t\tstruct parallel_checkout_item *pc_item = &parallel_checkout.items[i];\n     +\n     +\t\tswitch(pc_item->status) {\n    @@ parallel-checkout.c (new)\n     +\treturn ret;\n     +}\n     +\n    -+static int check_leading_dirs(const char *path, int len, int prefix_len)\n    -+{\n    -+\tconst char *slash = path + len;\n    -+\n    -+\twhile (slash > path && *slash != '/')\n    -+\t\tslash--;\n    -+\n    -+\treturn has_dirs_only_path(path, slash - path, prefix_len);\n    -+}\n    -+\n     +static void write_pc_item(struct parallel_checkout_item *pc_item,\n     +\t\t\t  struct checkout *state)\n     +{\n     +\tunsigned int mode = (pc_item->ce->ce_mode & 0100) ? 0777 : 0666;\n     +\tint fd = -1, fstat_done = 0;\n     +\tstruct strbuf path = STRBUF_INIT;\n    ++\tconst char *dir_sep;\n     +\n     +\tstrbuf_add(&path, state->base_dir, state->base_dir_len);\n     +\tstrbuf_add(&path, pc_item->ce->name, pc_item->ce->ce_namelen);\n     +\n    ++\tdir_sep = find_last_dir_sep(path.buf);\n    ++\n     +\t/*\n    -+\t * At this point, leading dirs should have already been created. But if\n    -+\t * a symlink being checked out has collided with one of the dirs, due to\n    -+\t * file system folding rules, it's possible that the dirs are no longer\n    -+\t * present. So we have to check again, and report any path collisions.\n    ++\t * The leading dirs should have been already created by now. But, in\n    ++\t * case of path collisions, one of the dirs could have been replaced by\n    ++\t * a symlink (checked out after we enqueued this entry for parallel\n    ++\t * checkout). Thus, we must check the leading dirs again.\n     +\t */\n    -+\tif (!check_leading_dirs(path.buf, path.len, state->base_dir_len)) {\n    ++\tif (dir_sep && !has_dirs_only_path(path.buf, dir_sep - path.buf,\n    ++\t\t\t\t\t   state->base_dir_len)) {\n     +\t\tpc_item->status = PC_ITEM_COLLIDED;\n     +\t\tgoto out;\n     +\t}\n    @@ parallel-checkout.c (new)\n     +\t\t\t * Errors which probably represent a path collision.\n     +\t\t\t * Suppress the error message and mark the item to be\n     +\t\t\t * retried later, sequentially. ENOTDIR and ENOENT are\n    -+\t\t\t * also interesting, but check_leading_dirs() should\n    -+\t\t\t * have already caught these cases.\n    ++\t\t\t * also interesting, but the above has_dirs_only_path()\n    ++\t\t\t * call should have already caught these cases.\n     +\t\t\t */\n     +\t\t\tpc_item->status = PC_ITEM_COLLIDED;\n     +\t\t} else {\n    @@ parallel-checkout.c (new)\n     +{\n     +\tsize_t i;\n     +\n    -+\tfor (i = 0; i < parallel_checkout.nr; ++i)\n    ++\tfor (i = 0; i < parallel_checkout.nr; i++)\n     +\t\twrite_pc_item(&parallel_checkout.items[i], state);\n     +}\n     +\n11:  096e543fd2 ! 11:  815137685a parallel-checkout: make it truly parallel\n    @@ builtin/checkout--helper.c (new)\n     +\t\tpacket_to_pc_item(line, len, &items[nr++]);\n     +\t}\n     +\n    -+\tfor (i = 0; i < nr; ++i) {\n    ++\tfor (i = 0; i < nr; i++) {\n     +\t\tstruct parallel_checkout_item *pc_item = &items[i];\n     +\t\twrite_pc_item(pc_item, state);\n     +\t\treport_result(pc_item);\n    @@ parallel-checkout.c: static int write_pc_item_to_fd(struct parallel_checkout_ite\n      \t */\n      \tret = convert_to_working_tree_ca(&pc_item->ca, pc_item->ce->name,\n      \t\t\t\t\t new_blob, size, &buf, NULL);\n    -@@ parallel-checkout.c: static int check_leading_dirs(const char *path, int len, int prefix_len)\n    - \treturn has_dirs_only_path(path, slash - path, prefix_len);\n    +@@ parallel-checkout.c: static int close_and_clear(int *fd)\n    + \treturn ret;\n      }\n      \n     -static void write_pc_item(struct parallel_checkout_item *pc_item,\n    @@ parallel-checkout.c: static void write_pc_item(struct parallel_checkout_item *pc\n     +static void send_batch(int fd, size_t start, size_t nr)\n     +{\n     +\tsize_t i;\n    -+\tfor (i = 0; i < nr; ++i)\n    ++\tfor (i = 0; i < nr; i++)\n     +\t\tsend_one_item(fd, &parallel_checkout.items[start + i]);\n     +\tpacket_flush(fd);\n     +}\n    @@ parallel-checkout.c: static void write_pc_item(struct parallel_checkout_item *pc\n     +\n     +\tALLOC_ARRAY(workers, num_workers);\n     +\n    -+\tfor (i = 0; i < num_workers; ++i) {\n    ++\tfor (i = 0; i < num_workers; i++) {\n     +\t\tstruct child_process *cp = &workers[i].cp;\n     +\n     +\t\tchild_process_init(cp);\n    @@ parallel-checkout.c: static void write_pc_item(struct parallel_checkout_item *pc\n     +\tbase_batch_size = parallel_checkout.nr / num_workers;\n     +\tworkers_with_one_extra_item = parallel_checkout.nr % num_workers;\n     +\n    -+\tfor (i = 0; i < num_workers; ++i) {\n    ++\tfor (i = 0; i < num_workers; i++) {\n     +\t\tstruct pc_worker *worker = &workers[i];\n     +\t\tsize_t batch_size = base_batch_size;\n     +\n    @@ parallel-checkout.c: static void write_pc_item(struct parallel_checkout_item *pc\n     +\t * Close pipes before calling finish_command() to let the workers\n     +\t * exit asynchronously and avoid spending extra time on wait().\n     +\t */\n    -+\tfor (i = 0; i < num_workers; ++i) {\n    ++\tfor (i = 0; i < num_workers; i++) {\n     +\t\tstruct child_process *cp = &workers[i].cp;\n     +\t\tif (cp->in >= 0)\n     +\t\t\tclose(cp->in);\n    @@ parallel-checkout.c: static void write_pc_item(struct parallel_checkout_item *pc\n     +\t\t\tclose(cp->out);\n     +\t}\n     +\n    -+\tfor (i = 0; i < num_workers; ++i) {\n    ++\tfor (i = 0; i < num_workers; i++) {\n     +\t\tif (finish_command(&workers[i].cp))\n     +\t\t\terror(_(\"checkout worker %d finished with error\"), i);\n     +\t}\n    @@ parallel-checkout.c: static void write_pc_item(struct parallel_checkout_item *pc\n     +\tstruct pollfd *pfds;\n     +\n     +\tCALLOC_ARRAY(pfds, num_workers);\n    -+\tfor (i = 0; i < num_workers; ++i) {\n    ++\tfor (i = 0; i < num_workers; i++) {\n     +\t\tpfds[i].fd = workers[i].cp.out;\n     +\t\tpfds[i].events = POLLIN;\n     +\t}\n    @@ parallel-checkout.c: static void write_pc_item(struct parallel_checkout_item *pc\n     +\t\t\tdie_errno(\"failed to poll checkout workers\");\n     +\t\t}\n     +\n    -+\t\tfor (i = 0; i < num_workers && nr > 0; ++i) {\n    ++\t\tfor (i = 0; i < num_workers && nr > 0; i++) {\n     +\t\t\tstruct pc_worker *worker = &workers[i];\n     +\t\t\tstruct pollfd *pfd = &pfds[i];\n     +\n    @@ parallel-checkout.h: void init_parallel_checkout(void);\n     +\tsize_t id;\n     +\tstruct object_id oid;\n     +\tunsigned int ce_mode;\n    -+\tenum crlf_action crlf_action;\n    ++\tenum convert_crlf_action crlf_action;\n     +\tint ident;\n     +\tsize_t working_tree_encoding_len;\n     +\tsize_t name_len;\n12:  9cfeb4821c ! 12:  2b42621582 parallel-checkout: support progress displaying\n    @@ parallel-checkout.c: struct parallel_checkout {\n     +\tunsigned int *progress_cnt;\n      };\n      \n    - static struct parallel_checkout parallel_checkout = { 0 };\n    + static struct parallel_checkout parallel_checkout;\n     @@ parallel-checkout.c: int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca)\n      \treturn 0;\n      }\n    @@ parallel-checkout.c: static void write_items_sequentially(struct checkout *state\n      {\n      \tsize_t i;\n      \n    --\tfor (i = 0; i < parallel_checkout.nr; ++i)\n    +-\tfor (i = 0; i < parallel_checkout.nr; i++)\n     -\t\twrite_pc_item(&parallel_checkout.items[i], state);\n    -+\tfor (i = 0; i < parallel_checkout.nr; ++i) {\n    ++\tfor (i = 0; i < parallel_checkout.nr; i++) {\n     +\t\tstruct parallel_checkout_item *pc_item = &parallel_checkout.items[i];\n     +\t\twrite_pc_item(pc_item, state);\n     +\t\tif (pc_item->status != PC_ITEM_COLLIDED)\n13:  da99b671e6 = 13:  960116579a make_transient_cache_entry(): optionally alloc from mem_pool\n14:  d3d561754a = 14:  fb9f2f580c builtin/checkout.c: complete parallel checkout support\n15:  ee34c6e149 = 15:  a844451e58 checkout-index: add parallel checkout support\n16:  05299a3cc0 = 16:  3733857ffa parallel-checkout: add tests for basic operations\n17:  3d140dcacb = 17:  c8a2974f81 parallel-checkout: add tests related to clone collisions\n18:  b26f676cae = 18:  86fccd57d5 parallel-checkout: add tests related to .gitattributes\n19:  641c61f9b6 = 19:  7f3e23cc38 ci: run test round with parallel-checkout enabled\n-- \n2.28.0\n\n"},{"id":"409143","messageId":"2726f6dc05c319e2baaf76a05d62426e0695fe65.1604521275.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 01/19] convert: make convert_attrs() and convert structs public","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:00Z","receivedAt":"2020-11-04T20:33:39Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nMove convert_attrs() declaration from convert.c to convert.h, together\nwith the conv_attrs struct and the crlf_action enum. This function and\nthe data structures will be used outside convert.c in the upcoming\nparallel checkout implementation. Note that crlf_action is renamed to\nconvert_crlf_action, which is more appropriate for the global namespace.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n[matheus.bernardino: squash and reword msg]\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 35 ++++++++---------------------------\n convert.h | 24 ++++++++++++++++++++++++\n 2 files changed, 32 insertions(+), 27 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex ee360c2f07..f13b001273 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -24,17 +24,6 @@\n #define CONVERT_STAT_BITS_TXT_CRLF  0x2\n #define CONVERT_STAT_BITS_BIN       0x4\n \n-enum crlf_action {\n-\tCRLF_UNDEFINED,\n-\tCRLF_BINARY,\n-\tCRLF_TEXT,\n-\tCRLF_TEXT_INPUT,\n-\tCRLF_TEXT_CRLF,\n-\tCRLF_AUTO,\n-\tCRLF_AUTO_INPUT,\n-\tCRLF_AUTO_CRLF\n-};\n-\n struct text_stat {\n \t/* NUL, CR, LF and CRLF counts */\n \tunsigned nul, lonecr, lonelf, crlf;\n@@ -172,7 +161,7 @@ static int text_eol_is_crlf(void)\n \treturn 0;\n }\n \n-static enum eol output_eol(enum crlf_action crlf_action)\n+static enum eol output_eol(enum convert_crlf_action crlf_action)\n {\n \tswitch (crlf_action) {\n \tcase CRLF_BINARY:\n@@ -246,7 +235,7 @@ static int has_crlf_in_index(const struct index_state *istate, const char *path)\n }\n \n static int will_convert_lf_to_crlf(struct text_stat *stats,\n-\t\t\t\t   enum crlf_action crlf_action)\n+\t\t\t\t   enum convert_crlf_action crlf_action)\n {\n \tif (output_eol(crlf_action) != EOL_CRLF)\n \t\treturn 0;\n@@ -499,7 +488,7 @@ static int encode_to_worktree(const char *path, const char *src, size_t src_len,\n static int crlf_to_git(const struct index_state *istate,\n \t\t       const char *path, const char *src, size_t len,\n \t\t       struct strbuf *buf,\n-\t\t       enum crlf_action crlf_action, int conv_flags)\n+\t\t       enum convert_crlf_action crlf_action, int conv_flags)\n {\n \tstruct text_stat stats;\n \tchar *dst;\n@@ -585,8 +574,8 @@ static int crlf_to_git(const struct index_state *istate,\n \treturn 1;\n }\n \n-static int crlf_to_worktree(const char *src, size_t len,\n-\t\t\t    struct strbuf *buf, enum crlf_action crlf_action)\n+static int crlf_to_worktree(const char *src, size_t len, struct strbuf *buf,\n+\t\t\t    enum convert_crlf_action crlf_action)\n {\n \tchar *to_free = NULL;\n \tstruct text_stat stats;\n@@ -1247,7 +1236,7 @@ static const char *git_path_check_encoding(struct attr_check_item *check)\n \treturn value;\n }\n \n-static enum crlf_action git_path_check_crlf(struct attr_check_item *check)\n+static enum convert_crlf_action git_path_check_crlf(struct attr_check_item *check)\n {\n \tconst char *value = check->value;\n \n@@ -1297,18 +1286,10 @@ static int git_path_check_ident(struct attr_check_item *check)\n \treturn !!ATTR_TRUE(value);\n }\n \n-struct conv_attrs {\n-\tstruct convert_driver *drv;\n-\tenum crlf_action attr_action; /* What attr says */\n-\tenum crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n-\tint ident;\n-\tconst char *working_tree_encoding; /* Supported encoding or default encoding if NULL */\n-};\n-\n static struct attr_check *check;\n \n-static void convert_attrs(const struct index_state *istate,\n-\t\t\t  struct conv_attrs *ca, const char *path)\n+void convert_attrs(const struct index_state *istate,\n+\t\t   struct conv_attrs *ca, const char *path)\n {\n \tstruct attr_check_item *ccheck = NULL;\n \ndiff --git a/convert.h b/convert.h\nindex e29d1026a6..5678e99922 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -63,6 +63,30 @@ struct checkout_metadata {\n \tstruct object_id blob;\n };\n \n+enum convert_crlf_action {\n+\tCRLF_UNDEFINED,\n+\tCRLF_BINARY,\n+\tCRLF_TEXT,\n+\tCRLF_TEXT_INPUT,\n+\tCRLF_TEXT_CRLF,\n+\tCRLF_AUTO,\n+\tCRLF_AUTO_INPUT,\n+\tCRLF_AUTO_CRLF\n+};\n+\n+struct convert_driver;\n+\n+struct conv_attrs {\n+\tstruct convert_driver *drv;\n+\tenum convert_crlf_action attr_action; /* What attr says */\n+\tenum convert_crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n+\tint ident;\n+\tconst char *working_tree_encoding; /* Supported encoding or default encoding if NULL */\n+};\n+\n+void convert_attrs(const struct index_state *istate,\n+\t\t   struct conv_attrs *ca, const char *path);\n+\n extern enum eol core_eol;\n extern char *check_roundtrip_encoding;\n const char *get_cached_convert_stats_ascii(const struct index_state *istate,\n-- \n2.28.0\n\n"},{"id":"409144","messageId":"fc03417592c25a111fbf77f9e5b9e5468b9070ae.1604521275.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 02/19] convert: add [async_]convert_to_working_tree_ca() variants","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:01Z","receivedAt":"2020-11-04T20:33:42Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSeparate the attribute gathering from the actual conversion by adding\n_ca() variants of the conversion functions. These variants receive a\nprecomputed 'struct conv_attrs', not relying, thus, on a index state.\nThey will be used in a future patch adding parallel checkout support,\nfor two reasons:\n\n- We will already load the conversion attributes in checkout_entry(),\n  before conversion, to decide whether a path is eligible for parallel\n  checkout. Therefore, it would be wasteful to load them again later,\n  for the actual conversion.\n\n- The parallel workers will be responsible for reading, converting and\n  writing blobs to the working tree. They won't have access to the main\n  process' index state, so they cannot load the attributes. Instead,\n  they will receive the preloaded ones and call the _ca() variant of\n  the conversion functions. Furthermore, the attributes machinery is\n  optimized to handle paths in sequential order, so it's better to leave\n  it for the main process, anyway.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n[matheus.bernardino: squash, remove one function definition and reword]\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 47 ++++++++++++++++++++++++-----------------------\n convert.h | 37 ++++++++++++++++++++++++++++---------\n 2 files changed, 52 insertions(+), 32 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex f13b001273..ab3d517233 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1447,7 +1447,7 @@ void convert_to_git_filter_fd(const struct index_state *istate,\n \tident_to_git(dst->buf, dst->len, dst, ca.ident);\n }\n \n-static int convert_to_working_tree_internal(const struct index_state *istate,\n+static int convert_to_working_tree_internal(const struct conv_attrs *ca,\n \t\t\t\t\t    const char *path, const char *src,\n \t\t\t\t\t    size_t len, struct strbuf *dst,\n \t\t\t\t\t    int normalizing,\n@@ -1455,11 +1455,8 @@ static int convert_to_working_tree_internal(const struct index_state *istate,\n \t\t\t\t\t    struct delayed_checkout *dco)\n {\n \tint ret = 0, ret_filter = 0;\n-\tstruct conv_attrs ca;\n-\n-\tconvert_attrs(istate, &ca, path);\n \n-\tret |= ident_to_worktree(src, len, dst, ca.ident);\n+\tret |= ident_to_worktree(src, len, dst, ca->ident);\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\n@@ -1469,49 +1466,53 @@ static int convert_to_working_tree_internal(const struct index_state *istate,\n \t * is a smudge or process filter (even if the process filter doesn't\n \t * support smudge).  The filters might expect CRLFs.\n \t */\n-\tif ((ca.drv && (ca.drv->smudge || ca.drv->process)) || !normalizing) {\n-\t\tret |= crlf_to_worktree(src, len, dst, ca.crlf_action);\n+\tif ((ca->drv && (ca->drv->smudge || ca->drv->process)) || !normalizing) {\n+\t\tret |= crlf_to_worktree(src, len, dst, ca->crlf_action);\n \t\tif (ret) {\n \t\t\tsrc = dst->buf;\n \t\t\tlen = dst->len;\n \t\t}\n \t}\n \n-\tret |= encode_to_worktree(path, src, len, dst, ca.working_tree_encoding);\n+\tret |= encode_to_worktree(path, src, len, dst, ca->working_tree_encoding);\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\n \t}\n \n \tret_filter = apply_filter(\n-\t\tpath, src, len, -1, dst, ca.drv, CAP_SMUDGE, meta, dco);\n-\tif (!ret_filter && ca.drv && ca.drv->required)\n-\t\tdie(_(\"%s: smudge filter %s failed\"), path, ca.drv->name);\n+\t\tpath, src, len, -1, dst, ca->drv, CAP_SMUDGE, meta, dco);\n+\tif (!ret_filter && ca->drv && ca->drv->required)\n+\t\tdie(_(\"%s: smudge filter %s failed\"), path, ca->drv->name);\n \n \treturn ret | ret_filter;\n }\n \n-int async_convert_to_working_tree(const struct index_state *istate,\n-\t\t\t\t  const char *path, const char *src,\n-\t\t\t\t  size_t len, struct strbuf *dst,\n-\t\t\t\t  const struct checkout_metadata *meta,\n-\t\t\t\t  void *dco)\n+int async_convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t\t     const char *path, const char *src,\n+\t\t\t\t     size_t len, struct strbuf *dst,\n+\t\t\t\t     const struct checkout_metadata *meta,\n+\t\t\t\t     void *dco)\n {\n-\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco);\n+\treturn convert_to_working_tree_internal(ca, path, src, len, dst, 0, meta, dco);\n }\n \n-int convert_to_working_tree(const struct index_state *istate,\n-\t\t\t    const char *path, const char *src,\n-\t\t\t    size_t len, struct strbuf *dst,\n-\t\t\t    const struct checkout_metadata *meta)\n+int convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t       const char *path, const char *src,\n+\t\t\t       size_t len, struct strbuf *dst,\n+\t\t\t       const struct checkout_metadata *meta)\n {\n-\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL);\n+\treturn convert_to_working_tree_internal(ca, path, src, len, dst, 0, meta, NULL);\n }\n \n int renormalize_buffer(const struct index_state *istate, const char *path,\n \t\t       const char *src, size_t len, struct strbuf *dst)\n {\n-\tint ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL, NULL);\n+\tstruct conv_attrs ca;\n+\tint ret;\n+\n+\tconvert_attrs(istate, &ca, path);\n+\tret = convert_to_working_tree_internal(&ca, path, src, len, dst, 1, NULL, NULL);\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\ndiff --git a/convert.h b/convert.h\nindex 5678e99922..a4838b5e5c 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -99,15 +99,34 @@ const char *get_convert_attr_ascii(const struct index_state *istate,\n int convert_to_git(const struct index_state *istate,\n \t\t   const char *path, const char *src, size_t len,\n \t\t   struct strbuf *dst, int conv_flags);\n-int convert_to_working_tree(const struct index_state *istate,\n-\t\t\t    const char *path, const char *src,\n-\t\t\t    size_t len, struct strbuf *dst,\n-\t\t\t    const struct checkout_metadata *meta);\n-int async_convert_to_working_tree(const struct index_state *istate,\n-\t\t\t\t  const char *path, const char *src,\n-\t\t\t\t  size_t len, struct strbuf *dst,\n-\t\t\t\t  const struct checkout_metadata *meta,\n-\t\t\t\t  void *dco);\n+int convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t       const char *path, const char *src,\n+\t\t\t       size_t len, struct strbuf *dst,\n+\t\t\t       const struct checkout_metadata *meta);\n+int async_convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t\t     const char *path, const char *src,\n+\t\t\t\t     size_t len, struct strbuf *dst,\n+\t\t\t\t     const struct checkout_metadata *meta,\n+\t\t\t\t     void *dco);\n+static inline int convert_to_working_tree(const struct index_state *istate,\n+\t\t\t\t\t  const char *path, const char *src,\n+\t\t\t\t\t  size_t len, struct strbuf *dst,\n+\t\t\t\t\t  const struct checkout_metadata *meta)\n+{\n+\tstruct conv_attrs ca;\n+\tconvert_attrs(istate, &ca, path);\n+\treturn convert_to_working_tree_ca(&ca, path, src, len, dst, meta);\n+}\n+static inline int async_convert_to_working_tree(const struct index_state *istate,\n+\t\t\t\t\t\tconst char *path, const char *src,\n+\t\t\t\t\t\tsize_t len, struct strbuf *dst,\n+\t\t\t\t\t\tconst struct checkout_metadata *meta,\n+\t\t\t\t\t\tvoid *dco)\n+{\n+\tstruct conv_attrs ca;\n+\tconvert_attrs(istate, &ca, path);\n+\treturn async_convert_to_working_tree_ca(&ca, path, src, len, dst, meta, dco);\n+}\n int async_query_available_blobs(const char *cmd,\n \t\t\t\tstruct string_list *available_paths);\n int renormalize_buffer(const struct index_state *istate,\n-- \n2.28.0\n\n"},{"id":"409145","messageId":"8ce20f1031172bd6821392a562a3ecac65c6bc84.1604521275.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 03/19] convert: add get_stream_filter_ca() variant","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:02Z","receivedAt":"2020-11-04T20:33:47Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nLike the previous patch, we will also need to call get_stream_filter()\nwith a precomputed `struct conv_attrs`, when we add support for parallel\ncheckout workers. So add the _ca() variant which takes the conversion\nattributes struct as a parameter.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n[matheus.bernardino: move header comment to ca() variant and reword msg]\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 28 +++++++++++++++++-----------\n convert.h |  2 ++\n 2 files changed, 19 insertions(+), 11 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex ab3d517233..0a61e4e9bf 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1939,34 +1939,31 @@ static struct stream_filter *ident_filter(const struct object_id *oid)\n }\n \n /*\n- * Return an appropriately constructed filter for the path, or NULL if\n+ * Return an appropriately constructed filter for the given ca, or NULL if\n  * the contents cannot be filtered without reading the whole thing\n  * in-core.\n  *\n  * Note that you would be crazy to set CRLF, smudge/clean or ident to a\n  * large binary blob you would want us not to slurp into the memory!\n  */\n-struct stream_filter *get_stream_filter(const struct index_state *istate,\n-\t\t\t\t\tconst char *path,\n-\t\t\t\t\tconst struct object_id *oid)\n+struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n+\t\t\t\t\t   const struct object_id *oid)\n {\n-\tstruct conv_attrs ca;\n \tstruct stream_filter *filter = NULL;\n \n-\tconvert_attrs(istate, &ca, path);\n-\tif (ca.drv && (ca.drv->process || ca.drv->smudge || ca.drv->clean))\n+\tif (ca->drv && (ca->drv->process || ca->drv->smudge || ca->drv->clean))\n \t\treturn NULL;\n \n-\tif (ca.working_tree_encoding)\n+\tif (ca->working_tree_encoding)\n \t\treturn NULL;\n \n-\tif (ca.crlf_action == CRLF_AUTO || ca.crlf_action == CRLF_AUTO_CRLF)\n+\tif (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF)\n \t\treturn NULL;\n \n-\tif (ca.ident)\n+\tif (ca->ident)\n \t\tfilter = ident_filter(oid);\n \n-\tif (output_eol(ca.crlf_action) == EOL_CRLF)\n+\tif (output_eol(ca->crlf_action) == EOL_CRLF)\n \t\tfilter = cascade_filter(filter, lf_to_crlf_filter());\n \telse\n \t\tfilter = cascade_filter(filter, &null_filter_singleton);\n@@ -1974,6 +1971,15 @@ struct stream_filter *get_stream_filter(const struct index_state *istate,\n \treturn filter;\n }\n \n+struct stream_filter *get_stream_filter(const struct index_state *istate,\n+\t\t\t\t\tconst char *path,\n+\t\t\t\t\tconst struct object_id *oid)\n+{\n+\tstruct conv_attrs ca;\n+\tconvert_attrs(istate, &ca, path);\n+\treturn get_stream_filter_ca(&ca, oid);\n+}\n+\n void free_stream_filter(struct stream_filter *filter)\n {\n \tfilter->vtbl->free(filter);\ndiff --git a/convert.h b/convert.h\nindex a4838b5e5c..484b50965d 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -179,6 +179,8 @@ struct stream_filter; /* opaque */\n struct stream_filter *get_stream_filter(const struct index_state *istate,\n \t\t\t\t\tconst char *path,\n \t\t\t\t\tconst struct object_id *);\n+struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n+\t\t\t\t\t   const struct object_id *oid);\n void free_stream_filter(struct stream_filter *);\n int is_null_stream_filter(struct stream_filter *);\n \n-- \n2.28.0\n\n"},{"id":"409146","messageId":"aa1eb461f46d200f36b33c07ebc0a350829ab1f5.1604521275.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 04/19] convert: add conv_attrs classification","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:03Z","receivedAt":"2020-11-04T20:33:50Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate `enum conv_attrs_classification` to express the different ways\nthat attributes are handled for a blob during checkout.\n\nThis will be used in a later commit when deciding whether to add a file\nto the parallel or delayed queue during checkout. For now, we can also\nuse it in get_stream_filter_ca() to simplify the function (as the\nclassifying logic is the same).\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n[matheus.bernardino: use classification in get_stream_filter_ca()]\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 26 +++++++++++++++++++-------\n convert.h | 33 +++++++++++++++++++++++++++++++++\n 2 files changed, 52 insertions(+), 7 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex 0a61e4e9bf..3b2d626268 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1951,13 +1951,7 @@ struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n {\n \tstruct stream_filter *filter = NULL;\n \n-\tif (ca->drv && (ca->drv->process || ca->drv->smudge || ca->drv->clean))\n-\t\treturn NULL;\n-\n-\tif (ca->working_tree_encoding)\n-\t\treturn NULL;\n-\n-\tif (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF)\n+\tif (classify_conv_attrs(ca) != CA_CLASS_STREAMABLE)\n \t\treturn NULL;\n \n \tif (ca->ident)\n@@ -2013,3 +2007,21 @@ void clone_checkout_metadata(struct checkout_metadata *dst,\n \tif (blob)\n \t\toidcpy(&dst->blob, blob);\n }\n+\n+enum conv_attrs_classification classify_conv_attrs(const struct conv_attrs *ca)\n+{\n+\tif (ca->drv) {\n+\t\tif (ca->drv->process)\n+\t\t\treturn CA_CLASS_INCORE_PROCESS;\n+\t\tif (ca->drv->smudge || ca->drv->clean)\n+\t\t\treturn CA_CLASS_INCORE_FILTER;\n+\t}\n+\n+\tif (ca->working_tree_encoding)\n+\t\treturn CA_CLASS_INCORE;\n+\n+\tif (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF)\n+\t\treturn CA_CLASS_INCORE;\n+\n+\treturn CA_CLASS_STREAMABLE;\n+}\ndiff --git a/convert.h b/convert.h\nindex 484b50965d..43e567a59b 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -200,4 +200,37 @@ int stream_filter(struct stream_filter *,\n \t\t  const char *input, size_t *isize_p,\n \t\t  char *output, size_t *osize_p);\n \n+enum conv_attrs_classification {\n+\t/*\n+\t * The blob must be loaded into a buffer before it can be\n+\t * smudged. All smudging is done in-proc.\n+\t */\n+\tCA_CLASS_INCORE,\n+\n+\t/*\n+\t * The blob must be loaded into a buffer, but uses a\n+\t * single-file driver filter, such as rot13.\n+\t */\n+\tCA_CLASS_INCORE_FILTER,\n+\n+\t/*\n+\t * The blob must be loaded into a buffer, but uses a\n+\t * long-running driver process, such as LFS. This might or\n+\t * might not use delayed operations. (The important thing is\n+\t * that there is a single subordinate long-running process\n+\t * handling all associated blobs and in case of delayed\n+\t * operations, may hold per-blob state.)\n+\t */\n+\tCA_CLASS_INCORE_PROCESS,\n+\n+\t/*\n+\t * The blob can be streamed and smudged without needing to\n+\t * completely read it into a buffer.\n+\t */\n+\tCA_CLASS_STREAMABLE,\n+};\n+\n+enum conv_attrs_classification classify_conv_attrs(\n+\tconst struct conv_attrs *ca);\n+\n #endif /* CONVERT_H */\n-- \n2.28.0\n\n"},{"id":"409147","messageId":"cb3dea224b0776a332c38c6a251406314dfe87ba.1604521275.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 05/19] entry: extract a header file for entry.c functions","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:04Z","receivedAt":"2020-11-04T20:33:55Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"The declarations of entry.c's public functions and structures currently\nreside in cache.h. Although not many, they contribute to the size of\ncache.h and, when changed, cause the unnecessary recompilation of\nmodules that don't really use these functions. So let's move them to a\nnew entry.h header.\n\nOriginal-patch-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n apply.c                  |  1 +\n builtin/checkout-index.c |  1 +\n builtin/checkout.c       |  1 +\n builtin/difftool.c       |  1 +\n cache.h                  | 24 -----------------------\n entry.c                  |  9 +--------\n entry.h                  | 42 ++++++++++++++++++++++++++++++++++++++++\n unpack-trees.c           |  1 +\n 8 files changed, 48 insertions(+), 32 deletions(-)\n create mode 100644 entry.h\n\ndiff --git a/apply.c b/apply.c\nindex 76dba93c97..ddec80b4b0 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -21,6 +21,7 @@\n #include \"quote.h\"\n #include \"rerere.h\"\n #include \"apply.h\"\n+#include \"entry.h\"\n \n struct gitdiff_data {\n \tstruct strbuf *root;\ndiff --git a/builtin/checkout-index.c b/builtin/checkout-index.c\nindex 4bbfc92dce..9276ed0258 100644\n--- a/builtin/checkout-index.c\n+++ b/builtin/checkout-index.c\n@@ -11,6 +11,7 @@\n #include \"quote.h\"\n #include \"cache-tree.h\"\n #include \"parse-options.h\"\n+#include \"entry.h\"\n \n #define CHECKOUT_ALL 4\n static int nul_term_line;\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 0951f8fee5..b18b9d6f3c 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -26,6 +26,7 @@\n #include \"unpack-trees.h\"\n #include \"wt-status.h\"\n #include \"xdiff-interface.h\"\n+#include \"entry.h\"\n \n static const char * const checkout_usage[] = {\n \tN_(\"git checkout [<options>] <branch>\"),\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 7ac432b881..dfa22b67eb 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -23,6 +23,7 @@\n #include \"lockfile.h\"\n #include \"object-store.h\"\n #include \"dir.h\"\n+#include \"entry.h\"\n \n static int trust_exit_code;\n \ndiff --git a/cache.h b/cache.h\nindex c0072d43b1..ccfeb9ba2b 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1706,30 +1706,6 @@ const char *show_ident_date(const struct ident_split *id,\n  */\n int ident_cmp(const struct ident_split *, const struct ident_split *);\n \n-struct checkout {\n-\tstruct index_state *istate;\n-\tconst char *base_dir;\n-\tint base_dir_len;\n-\tstruct delayed_checkout *delayed_checkout;\n-\tstruct checkout_metadata meta;\n-\tunsigned force:1,\n-\t\t quiet:1,\n-\t\t not_new:1,\n-\t\t clone:1,\n-\t\t refresh_cache:1;\n-};\n-#define CHECKOUT_INIT { NULL, \"\" }\n-\n-#define TEMPORARY_FILENAME_LENGTH 25\n-int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath, int *nr_checkouts);\n-void enable_delayed_checkout(struct checkout *state);\n-int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n-/*\n- * Unlink the last component and schedule the leading directories for\n- * removal, such that empty directories get removed.\n- */\n-void unlink_entry(const struct cache_entry *ce);\n-\n struct cache_def {\n \tstruct strbuf path;\n \tint flags;\ndiff --git a/entry.c b/entry.c\nindex a0532f1f00..b0b8099699 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -6,6 +6,7 @@\n #include \"submodule.h\"\n #include \"progress.h\"\n #include \"fsmonitor.h\"\n+#include \"entry.h\"\n \n static void create_directories(const char *path, int path_len,\n \t\t\t       const struct checkout *state)\n@@ -429,14 +430,6 @@ static void mark_colliding_entries(const struct checkout *state,\n \t}\n }\n \n-/*\n- * Write the contents from ce out to the working tree.\n- *\n- * When topath[] is not NULL, instead of writing to the working tree\n- * file named by ce, a temporary file is created by this function and\n- * its name is returned in topath[], which must be able to hold at\n- * least TEMPORARY_FILENAME_LENGTH bytes long.\n- */\n int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t\t   char *topath, int *nr_checkouts)\n {\ndiff --git a/entry.h b/entry.h\nnew file mode 100644\nindex 0000000000..acbbb90220\n--- /dev/null\n+++ b/entry.h\n@@ -0,0 +1,42 @@\n+#ifndef ENTRY_H\n+#define ENTRY_H\n+\n+#include \"cache.h\"\n+#include \"convert.h\"\n+\n+struct checkout {\n+\tstruct index_state *istate;\n+\tconst char *base_dir;\n+\tint base_dir_len;\n+\tstruct delayed_checkout *delayed_checkout;\n+\tstruct checkout_metadata meta;\n+\tunsigned force:1,\n+\t\t quiet:1,\n+\t\t not_new:1,\n+\t\t clone:1,\n+\t\t refresh_cache:1;\n+};\n+#define CHECKOUT_INIT { NULL, \"\" }\n+\n+#define TEMPORARY_FILENAME_LENGTH 25\n+/*\n+ * Write the contents from ce out to the working tree.\n+ *\n+ * When topath[] is not NULL, instead of writing to the working tree\n+ * file named by ce, a temporary file is created by this function and\n+ * its name is returned in topath[], which must be able to hold at\n+ * least TEMPORARY_FILENAME_LENGTH bytes long.\n+ */\n+int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n+\t\t   char *topath, int *nr_checkouts);\n+\n+void enable_delayed_checkout(struct checkout *state);\n+int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n+\n+/*\n+ * Unlink the last component and schedule the leading directories for\n+ * removal, such that empty directories get removed.\n+ */\n+void unlink_entry(const struct cache_entry *ce);\n+\n+#endif /* ENTRY_H */\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 323280dd48..a511fadd89 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -16,6 +16,7 @@\n #include \"fsmonitor.h\"\n #include \"object-store.h\"\n #include \"promisor-remote.h\"\n+#include \"entry.h\"\n \n /*\n  * Error messages expected by scripts out of plumbing commands such as\n-- \n2.28.0\n\n"},{"id":"409148","messageId":"46ed6274d7bff1747a73f3a032fa907af2082782.1604521275.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 06/19] entry: make fstat_output() and read_blob_entry() public","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:05Z","receivedAt":"2020-11-04T20:33:59Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"These two functions will be used by the parallel checkout code, so let's\nmake them public. Note: fstat_output() is renamed to\nfstat_checkout_output(), now that it has become public, seeking to avoid\nfuture name collisions.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 8 ++++----\n entry.h | 3 +++\n 2 files changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex b0b8099699..b36071a610 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -84,7 +84,7 @@ static int create_file(const char *path, unsigned int mode)\n \treturn open(path, O_WRONLY | O_CREAT | O_EXCL, mode);\n }\n \n-static void *read_blob_entry(const struct cache_entry *ce, unsigned long *size)\n+void *read_blob_entry(const struct cache_entry *ce, unsigned long *size)\n {\n \tenum object_type type;\n \tvoid *blob_data = read_object_file(&ce->oid, &type, size);\n@@ -109,7 +109,7 @@ static int open_output_fd(char *path, const struct cache_entry *ce, int to_tempf\n \t}\n }\n \n-static int fstat_output(int fd, const struct checkout *state, struct stat *st)\n+int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st)\n {\n \t/* use fstat() only when path == ce->name */\n \tif (fstat_is_reliable() &&\n@@ -132,7 +132,7 @@ static int streaming_write_entry(const struct cache_entry *ce, char *path,\n \t\treturn -1;\n \n \tresult |= stream_blob_to_fd(fd, &ce->oid, filter, 1);\n-\t*fstat_done = fstat_output(fd, state, statbuf);\n+\t*fstat_done = fstat_checkout_output(fd, state, statbuf);\n \tresult |= close(fd);\n \n \tif (result)\n@@ -346,7 +346,7 @@ static int write_entry(struct cache_entry *ce,\n \n \t\twrote = write_in_full(fd, new_blob, size);\n \t\tif (!to_tempfile)\n-\t\t\tfstat_done = fstat_output(fd, state, &st);\n+\t\t\tfstat_done = fstat_checkout_output(fd, state, &st);\n \t\tclose(fd);\n \t\tfree(new_blob);\n \t\tif (wrote < 0)\ndiff --git a/entry.h b/entry.h\nindex acbbb90220..60df93ca78 100644\n--- a/entry.h\n+++ b/entry.h\n@@ -39,4 +39,7 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n  */\n void unlink_entry(const struct cache_entry *ce);\n \n+void *read_blob_entry(const struct cache_entry *ce, unsigned long *size);\n+int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st);\n+\n #endif /* ENTRY_H */\n-- \n2.28.0\n\n"},{"id":"409149","messageId":"a0479d02ffedccb65a167463228445a6d62c9f96.1604521275.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 07/19] entry: extract cache_entry update from write_entry()","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:06Z","receivedAt":"2020-11-04T20:34:01Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"This code will be used by the parallel checkout functions, outside\nentry.c, so extract it to a public function.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 25 ++++++++++++++++---------\n entry.h |  2 ++\n 2 files changed, 18 insertions(+), 9 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex b36071a610..1d2df188e5 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -251,6 +251,18 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts)\n \treturn errs;\n }\n \n+void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,\n+\t\t\t   struct stat *st)\n+{\n+\tif (state->refresh_cache) {\n+\t\tassert(state->istate);\n+\t\tfill_stat_cache_info(state->istate, ce, st);\n+\t\tce->ce_flags |= CE_UPDATE_IN_BASE;\n+\t\tmark_fsmonitor_invalid(state->istate, ce);\n+\t\tstate->istate->cache_changed |= CE_ENTRY_CHANGED;\n+\t}\n+}\n+\n static int write_entry(struct cache_entry *ce,\n \t\t       char *path, const struct checkout *state, int to_tempfile)\n {\n@@ -371,15 +383,10 @@ static int write_entry(struct cache_entry *ce,\n \n finish:\n \tif (state->refresh_cache) {\n-\t\tassert(state->istate);\n-\t\tif (!fstat_done)\n-\t\t\tif (lstat(ce->name, &st) < 0)\n-\t\t\t\treturn error_errno(\"unable to stat just-written file %s\",\n-\t\t\t\t\t\t   ce->name);\n-\t\tfill_stat_cache_info(state->istate, ce, &st);\n-\t\tce->ce_flags |= CE_UPDATE_IN_BASE;\n-\t\tmark_fsmonitor_invalid(state->istate, ce);\n-\t\tstate->istate->cache_changed |= CE_ENTRY_CHANGED;\n+\t\tif (!fstat_done && lstat(ce->name, &st) < 0)\n+\t\t\treturn error_errno(\"unable to stat just-written file %s\",\n+\t\t\t\t\t   ce->name);\n+\t\tupdate_ce_after_write(state, ce , &st);\n \t}\n delayed:\n \treturn 0;\ndiff --git a/entry.h b/entry.h\nindex 60df93ca78..ea7290bcd5 100644\n--- a/entry.h\n+++ b/entry.h\n@@ -41,5 +41,7 @@ void unlink_entry(const struct cache_entry *ce);\n \n void *read_blob_entry(const struct cache_entry *ce, unsigned long *size);\n int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st);\n+void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,\n+\t\t\t   struct stat *st);\n \n #endif /* ENTRY_H */\n-- \n2.28.0\n\n"},{"id":"409150","messageId":"5c993cc27f67109828390c7856d6c03d4a2cbb32.1604521275.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 08/19] entry: move conv_attrs lookup up to checkout_entry()","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:07Z","receivedAt":"2020-11-04T20:34:03Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"In a following patch, checkout_entry() will use conv_attrs to decide\nwhether an entry should be enqueued for parallel checkout or not. But\nthe attributes lookup only happens lower in this call stack. To avoid\nthe unnecessary work of loading the attributes twice, let's move it up\nto checkout_entry(), and pass the loaded struct down to write_entry().\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 38 +++++++++++++++++++++++++++-----------\n 1 file changed, 27 insertions(+), 11 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex 1d2df188e5..486712c3a9 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -263,8 +263,9 @@ void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,\n \t}\n }\n \n-static int write_entry(struct cache_entry *ce,\n-\t\t       char *path, const struct checkout *state, int to_tempfile)\n+/* Note: ca is used (and required) iff the entry refers to a regular file. */\n+static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca,\n+\t\t       const struct checkout *state, int to_tempfile)\n {\n \tunsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;\n \tstruct delayed_checkout *dco = state->delayed_checkout;\n@@ -281,8 +282,7 @@ static int write_entry(struct cache_entry *ce,\n \tclone_checkout_metadata(&meta, &state->meta, &ce->oid);\n \n \tif (ce_mode_s_ifmt == S_IFREG) {\n-\t\tstruct stream_filter *filter = get_stream_filter(state->istate, ce->name,\n-\t\t\t\t\t\t\t\t &ce->oid);\n+\t\tstruct stream_filter *filter = get_stream_filter_ca(ca, &ce->oid);\n \t\tif (filter &&\n \t\t    !streaming_write_entry(ce, path, filter,\n \t\t\t\t\t   state, to_tempfile,\n@@ -329,14 +329,17 @@ static int write_entry(struct cache_entry *ce,\n \t\t * Convert from git internal format to working tree format\n \t\t */\n \t\tif (dco && dco->state != CE_NO_DELAY) {\n-\t\t\tret = async_convert_to_working_tree(state->istate, ce->name, new_blob,\n-\t\t\t\t\t\t\t    size, &buf, &meta, dco);\n+\t\t\tret = async_convert_to_working_tree_ca(ca, ce->name,\n+\t\t\t\t\t\t\t       new_blob, size,\n+\t\t\t\t\t\t\t       &buf, &meta, dco);\n \t\t\tif (ret && string_list_has_string(&dco->paths, ce->name)) {\n \t\t\t\tfree(new_blob);\n \t\t\t\tgoto delayed;\n \t\t\t}\n-\t\t} else\n-\t\t\tret = convert_to_working_tree(state->istate, ce->name, new_blob, size, &buf, &meta);\n+\t\t} else {\n+\t\t\tret = convert_to_working_tree_ca(ca, ce->name, new_blob,\n+\t\t\t\t\t\t\t size, &buf, &meta);\n+\t\t}\n \n \t\tif (ret) {\n \t\t\tfree(new_blob);\n@@ -442,6 +445,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n {\n \tstatic struct strbuf path = STRBUF_INIT;\n \tstruct stat st;\n+\tstruct conv_attrs ca_buf, *ca = NULL;\n \n \tif (ce->ce_flags & CE_WT_REMOVE) {\n \t\tif (topath)\n@@ -454,8 +458,13 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t\treturn 0;\n \t}\n \n-\tif (topath)\n-\t\treturn write_entry(ce, topath, state, 1);\n+\tif (topath) {\n+\t\tif (S_ISREG(ce->ce_mode)) {\n+\t\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n+\t\t\tca = &ca_buf;\n+\t\t}\n+\t\treturn write_entry(ce, topath, ca, state, 1);\n+\t}\n \n \tstrbuf_reset(&path);\n \tstrbuf_add(&path, state->base_dir, state->base_dir_len);\n@@ -517,9 +526,16 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t\treturn 0;\n \n \tcreate_directories(path.buf, path.len, state);\n+\n \tif (nr_checkouts)\n \t\t(*nr_checkouts)++;\n-\treturn write_entry(ce, path.buf, state, 0);\n+\n+\tif (S_ISREG(ce->ce_mode)) {\n+\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n+\t\tca = &ca_buf;\n+\t}\n+\n+\treturn write_entry(ce, path.buf, NULL, state, 0);\n }\n \n void unlink_entry(const struct cache_entry *ce)\n-- \n2.28.0\n\n"},{"id":"409151","messageId":"aa635bda21c43e0d82ce21e791df280ee6231d43.1604521275.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 09/19] entry: add checkout_entry_ca() which takes preloaded conv_attrs","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:08Z","receivedAt":"2020-11-04T20:34:05Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"The parallel checkout machinery will call checkout_entry() for entries\nthat could not be written in parallel due to path collisions. At this\npoint, we will already be holding the conversion attributes for each\nentry, and it would be wasteful to let checkout_entry() load these\nagain. Instead, let's add the checkout_entry_ca() variant, which\noptionally takes a preloaded conv_attrs struct.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 13 +++++++------\n entry.h | 12 ++++++++++--\n 2 files changed, 17 insertions(+), 8 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex 486712c3a9..9d79a5671f 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -440,12 +440,13 @@ static void mark_colliding_entries(const struct checkout *state,\n \t}\n }\n \n-int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n-\t\t   char *topath, int *nr_checkouts)\n+int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,\n+\t\t      const struct checkout *state, char *topath,\n+\t\t      int *nr_checkouts)\n {\n \tstatic struct strbuf path = STRBUF_INIT;\n \tstruct stat st;\n-\tstruct conv_attrs ca_buf, *ca = NULL;\n+\tstruct conv_attrs ca_buf;\n \n \tif (ce->ce_flags & CE_WT_REMOVE) {\n \t\tif (topath)\n@@ -459,7 +460,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t}\n \n \tif (topath) {\n-\t\tif (S_ISREG(ce->ce_mode)) {\n+\t\tif (S_ISREG(ce->ce_mode) && !ca) {\n \t\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n \t\t\tca = &ca_buf;\n \t\t}\n@@ -530,12 +531,12 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \tif (nr_checkouts)\n \t\t(*nr_checkouts)++;\n \n-\tif (S_ISREG(ce->ce_mode)) {\n+\tif (S_ISREG(ce->ce_mode) && !ca) {\n \t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n \t\tca = &ca_buf;\n \t}\n \n-\treturn write_entry(ce, path.buf, NULL, state, 0);\n+\treturn write_entry(ce, path.buf, ca, state, 0);\n }\n \n void unlink_entry(const struct cache_entry *ce)\ndiff --git a/entry.h b/entry.h\nindex ea7290bcd5..d8244c5db2 100644\n--- a/entry.h\n+++ b/entry.h\n@@ -26,9 +26,17 @@ struct checkout {\n  * file named by ce, a temporary file is created by this function and\n  * its name is returned in topath[], which must be able to hold at\n  * least TEMPORARY_FILENAME_LENGTH bytes long.\n+ *\n+ * With checkout_entry_ca(), callers can optionally pass a preloaded\n+ * conv_attrs struct (to avoid reloading it), when ce refers to a\n+ * regular file. If ca is NULL, the attributes will be loaded\n+ * internally when (and if) needed.\n  */\n-int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n-\t\t   char *topath, int *nr_checkouts);\n+#define checkout_entry(ce, state, topath, nr_checkouts) \\\n+\t\tcheckout_entry_ca(ce, NULL, state, topath, nr_checkouts)\n+int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,\n+\t\t      const struct checkout *state, char *topath,\n+\t\t      int *nr_checkouts);\n \n void enable_delayed_checkout(struct checkout *state);\n int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n-- \n2.28.0\n\n"},{"id":"409152","messageId":"bc8447cd9c106055a715305ab506adc6abae7713.1604521275.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 10/19] unpack-trees: add basic support for parallel checkout","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:09Z","receivedAt":"2020-11-04T20:34:09Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"This new interface allows us to enqueue some of the entries being\nchecked out to later call write_entry() for them in parallel. For now,\nthe parallel checkout machinery is enabled by default and there is no\nuser configuration, but run_parallel_checkout() just writes the queued\nentries in sequence (without spawning additional workers). The next\npatch will actually implement the parallelism and, later, we will make\nit configurable.\n\nWhen there are path collisions among the entries being written (which\ncan happen e.g. with case-sensitive files in case-insensitive file\nsystems), the parallel checkout code detects the problem and marks the\nitem with PC_ITEM_COLLIDED. Later, these items are sequentially fed to\ncheckout_entry() again. This is similar to the way the sequential code\ndeals with collisions, overwriting the previously checked out entries\nwith the subsequent ones. The only difference is that, when we start\nwriting the entries in parallel, we won't be able to determine which of\nthe colliding entries will survive on disk (for the sequential\nalgorithm, it is always the last one).\n\nI also experimented with the idea of not overwriting colliding entries,\nand it seemed to work well in my simple tests. However, because just one\nentry of each colliding group would be actually written, the others\nwould have null lstat() fields on the index. This might not be a problem\nby itself, but it could cause performance penalties for subsequent\ncommands that need to refresh the index: when the st_size value cached\nis 0, read-cache.c:ie_modified() will go to the filesystem to see if the\ncontents match. As mentioned in the function:\n\n    * Immediately after read-tree or update-index --cacheinfo,\n    * the length field is zero, as we have never even read the\n    * lstat(2) information once, and we cannot trust DATA_CHANGED\n    * returned by ie_match_stat() which in turn was returned by\n    * ce_match_stat_basic() to signal that the filesize of the\n    * blob changed.  We have to actually go to the filesystem to\n    * see if the contents match, and if so, should answer \"unchanged\".\n\nSo, if we have N entries in a colliding group and we decide to write and\nlstat() only one of them, every subsequent git-status will have to read,\nconvert, and hash the written file N - 1 times, to check that the N - 1\nunwritten entries are dirty. By checking out all colliding entries (like\nthe sequential code does), we only pay the overhead once.\n\nCo-authored-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nCo-authored-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n Makefile            |   1 +\n entry.c             |  17 ++-\n parallel-checkout.c | 362 ++++++++++++++++++++++++++++++++++++++++++++\n parallel-checkout.h |  27 ++++\n unpack-trees.c      |   6 +-\n 5 files changed, 410 insertions(+), 3 deletions(-)\n create mode 100644 parallel-checkout.c\n create mode 100644 parallel-checkout.h\n\ndiff --git a/Makefile b/Makefile\nindex 1fb0ec1705..10ee5e709b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -945,6 +945,7 @@ LIB_OBJS += pack-revindex.o\n LIB_OBJS += pack-write.o\n LIB_OBJS += packfile.o\n LIB_OBJS += pager.o\n+LIB_OBJS += parallel-checkout.o\n LIB_OBJS += parse-options-cb.o\n LIB_OBJS += parse-options.o\n LIB_OBJS += patch-delta.o\ndiff --git a/entry.c b/entry.c\nindex 9d79a5671f..6676954431 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -7,6 +7,7 @@\n #include \"progress.h\"\n #include \"fsmonitor.h\"\n #include \"entry.h\"\n+#include \"parallel-checkout.h\"\n \n static void create_directories(const char *path, int path_len,\n \t\t\t       const struct checkout *state)\n@@ -426,8 +427,17 @@ static void mark_colliding_entries(const struct checkout *state,\n \tfor (i = 0; i < state->istate->cache_nr; i++) {\n \t\tstruct cache_entry *dup = state->istate->cache[i];\n \n-\t\tif (dup == ce)\n-\t\t\tbreak;\n+\t\tif (dup == ce) {\n+\t\t\t/*\n+\t\t\t * Parallel checkout creates the files in no particular\n+\t\t\t * order. So the other side of the collision may appear\n+\t\t\t * after the given cache_entry in the array.\n+\t\t\t */\n+\t\t\tif (parallel_checkout_status() == PC_RUNNING)\n+\t\t\t\tcontinue;\n+\t\t\telse\n+\t\t\t\tbreak;\n+\t\t}\n \n \t\tif (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))\n \t\t\tcontinue;\n@@ -536,6 +546,9 @@ int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,\n \t\tca = &ca_buf;\n \t}\n \n+\tif (!enqueue_checkout(ce, ca))\n+\t\treturn 0;\n+\n \treturn write_entry(ce, path.buf, ca, state, 0);\n }\n \ndiff --git a/parallel-checkout.c b/parallel-checkout.c\nnew file mode 100644\nindex 0000000000..fd871b09d3\n--- /dev/null\n+++ b/parallel-checkout.c\n@@ -0,0 +1,362 @@\n+#include \"cache.h\"\n+#include \"entry.h\"\n+#include \"parallel-checkout.h\"\n+#include \"streaming.h\"\n+\n+enum pc_item_status {\n+\tPC_ITEM_PENDING = 0,\n+\tPC_ITEM_WRITTEN,\n+\t/*\n+\t * The entry could not be written because there was another file\n+\t * already present in its path or leading directories. Since\n+\t * checkout_entry_ca() removes such files from the working tree before\n+\t * enqueueing the entry for parallel checkout, it means that there was\n+\t * a path collision among the entries being written.\n+\t */\n+\tPC_ITEM_COLLIDED,\n+\tPC_ITEM_FAILED,\n+};\n+\n+struct parallel_checkout_item {\n+\t/* pointer to a istate->cache[] entry. Not owned by us. */\n+\tstruct cache_entry *ce;\n+\tstruct conv_attrs ca;\n+\tstruct stat st;\n+\tenum pc_item_status status;\n+};\n+\n+struct parallel_checkout {\n+\tenum pc_status status;\n+\tstruct parallel_checkout_item *items;\n+\tsize_t nr, alloc;\n+};\n+\n+static struct parallel_checkout parallel_checkout;\n+\n+enum pc_status parallel_checkout_status(void)\n+{\n+\treturn parallel_checkout.status;\n+}\n+\n+void init_parallel_checkout(void)\n+{\n+\tif (parallel_checkout.status != PC_UNINITIALIZED)\n+\t\tBUG(\"parallel checkout already initialized\");\n+\n+\tparallel_checkout.status = PC_ACCEPTING_ENTRIES;\n+}\n+\n+static void finish_parallel_checkout(void)\n+{\n+\tif (parallel_checkout.status == PC_UNINITIALIZED)\n+\t\tBUG(\"cannot finish parallel checkout: not initialized yet\");\n+\n+\tfree(parallel_checkout.items);\n+\tmemset(&parallel_checkout, 0, sizeof(parallel_checkout));\n+}\n+\n+static int is_eligible_for_parallel_checkout(const struct cache_entry *ce,\n+\t\t\t\t\t     const struct conv_attrs *ca)\n+{\n+\tenum conv_attrs_classification c;\n+\n+\tif (!S_ISREG(ce->ce_mode))\n+\t\treturn 0;\n+\n+\tc = classify_conv_attrs(ca);\n+\tswitch (c) {\n+\tcase CA_CLASS_INCORE:\n+\t\treturn 1;\n+\n+\tcase CA_CLASS_INCORE_FILTER:\n+\t\t/*\n+\t\t * It would be safe to allow concurrent instances of\n+\t\t * single-file smudge filters, like rot13, but we should not\n+\t\t * assume that all filters are parallel-process safe. So we\n+\t\t * don't allow this.\n+\t\t */\n+\t\treturn 0;\n+\n+\tcase CA_CLASS_INCORE_PROCESS:\n+\t\t/*\n+\t\t * The parallel queue and the delayed queue are not compatible,\n+\t\t * so they must be kept completely separated. And we can't tell\n+\t\t * if a long-running process will delay its response without\n+\t\t * actually asking it to perform the filtering. Therefore, this\n+\t\t * type of filter is not allowed in parallel checkout.\n+\t\t *\n+\t\t * Furthermore, there should only be one instance of the\n+\t\t * long-running process filter as we don't know how it is\n+\t\t * managing its own concurrency. So, spreading the entries that\n+\t\t * requisite such a filter among the parallel workers would\n+\t\t * require a lot more inter-process communication. We would\n+\t\t * probably have to designate a single process to interact with\n+\t\t * the filter and send all the necessary data to it, for each\n+\t\t * entry.\n+\t\t */\n+\t\treturn 0;\n+\n+\tcase CA_CLASS_STREAMABLE:\n+\t\treturn 1;\n+\n+\tdefault:\n+\t\tBUG(\"unsupported conv_attrs classification '%d'\", c);\n+\t}\n+}\n+\n+int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca)\n+{\n+\tstruct parallel_checkout_item *pc_item;\n+\n+\tif (parallel_checkout.status != PC_ACCEPTING_ENTRIES ||\n+\t    !is_eligible_for_parallel_checkout(ce, ca))\n+\t\treturn -1;\n+\n+\tALLOC_GROW(parallel_checkout.items, parallel_checkout.nr + 1,\n+\t\t   parallel_checkout.alloc);\n+\n+\tpc_item = &parallel_checkout.items[parallel_checkout.nr++];\n+\tpc_item->ce = ce;\n+\tmemcpy(&pc_item->ca, ca, sizeof(pc_item->ca));\n+\tpc_item->status = PC_ITEM_PENDING;\n+\n+\treturn 0;\n+}\n+\n+static int handle_results(struct checkout *state)\n+{\n+\tint ret = 0;\n+\tsize_t i;\n+\tint have_pending = 0;\n+\n+\t/*\n+\t * We first update the successfully written entries with the collected\n+\t * stat() data, so that they can be found by mark_colliding_entries(),\n+\t * in the next loop, when necessary.\n+\t */\n+\tfor (i = 0; i < parallel_checkout.nr; i++) {\n+\t\tstruct parallel_checkout_item *pc_item = &parallel_checkout.items[i];\n+\t\tif (pc_item->status == PC_ITEM_WRITTEN)\n+\t\t\tupdate_ce_after_write(state, pc_item->ce, &pc_item->st);\n+\t}\n+\n+\tfor (i = 0; i < parallel_checkout.nr; i++) {\n+\t\tstruct parallel_checkout_item *pc_item = &parallel_checkout.items[i];\n+\n+\t\tswitch(pc_item->status) {\n+\t\tcase PC_ITEM_WRITTEN:\n+\t\t\t/* Already handled */\n+\t\t\tbreak;\n+\t\tcase PC_ITEM_COLLIDED:\n+\t\t\t/*\n+\t\t\t * The entry could not be checked out due to a path\n+\t\t\t * collision with another entry. Since there can only\n+\t\t\t * be one entry of each colliding group on the disk, we\n+\t\t\t * could skip trying to check out this one and move on.\n+\t\t\t * However, this would leave the unwritten entries with\n+\t\t\t * null stat() fields on the index, which could\n+\t\t\t * potentially slow down subsequent operations that\n+\t\t\t * require refreshing it: git would not be able to\n+\t\t\t * trust st_size and would have to go to the filesystem\n+\t\t\t * to see if the contents match (see ie_modified()).\n+\t\t\t *\n+\t\t\t * Instead, let's pay the overhead only once, now, and\n+\t\t\t * call checkout_entry_ca() again for this file, to\n+\t\t\t * have it's stat() data stored in the index. This also\n+\t\t\t * has the benefit of adding this entry and its\n+\t\t\t * colliding pair to the collision report message.\n+\t\t\t * Additionally, this overwriting behavior is consistent\n+\t\t\t * with what the sequential checkout does, so it doesn't\n+\t\t\t * add any extra overhead.\n+\t\t\t */\n+\t\t\tret |= checkout_entry_ca(pc_item->ce, &pc_item->ca,\n+\t\t\t\t\t\t state, NULL, NULL);\n+\t\t\tbreak;\n+\t\tcase PC_ITEM_PENDING:\n+\t\t\thave_pending = 1;\n+\t\t\t/* fall through */\n+\t\tcase PC_ITEM_FAILED:\n+\t\t\tret = -1;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tBUG(\"unknown checkout item status in parallel checkout\");\n+\t\t}\n+\t}\n+\n+\tif (have_pending)\n+\t\terror(_(\"parallel checkout finished with pending entries\"));\n+\n+\treturn ret;\n+}\n+\n+static int reset_fd(int fd, const char *path)\n+{\n+\tif (lseek(fd, 0, SEEK_SET) != 0)\n+\t\treturn error_errno(\"failed to rewind descriptor of %s\", path);\n+\tif (ftruncate(fd, 0))\n+\t\treturn error_errno(\"failed to truncate file %s\", path);\n+\treturn 0;\n+}\n+\n+static int write_pc_item_to_fd(struct parallel_checkout_item *pc_item, int fd,\n+\t\t\t       const char *path)\n+{\n+\tint ret;\n+\tstruct stream_filter *filter;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tchar *new_blob;\n+\tunsigned long size;\n+\tsize_t newsize = 0;\n+\tssize_t wrote;\n+\n+\t/* Sanity check */\n+\tassert(is_eligible_for_parallel_checkout(pc_item->ce, &pc_item->ca));\n+\n+\tfilter = get_stream_filter_ca(&pc_item->ca, &pc_item->ce->oid);\n+\tif (filter) {\n+\t\tif (stream_blob_to_fd(fd, &pc_item->ce->oid, filter, 1)) {\n+\t\t\t/* On error, reset fd to try writing without streaming */\n+\t\t\tif (reset_fd(fd, path))\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\tnew_blob = read_blob_entry(pc_item->ce, &size);\n+\tif (!new_blob)\n+\t\treturn error(\"unable to read sha1 file of %s (%s)\", path,\n+\t\t\t     oid_to_hex(&pc_item->ce->oid));\n+\n+\t/*\n+\t * checkout metadata is used to give context for external process\n+\t * filters. Files requiring such filters are not eligible for parallel\n+\t * checkout, so pass NULL.\n+\t */\n+\tret = convert_to_working_tree_ca(&pc_item->ca, pc_item->ce->name,\n+\t\t\t\t\t new_blob, size, &buf, NULL);\n+\n+\tif (ret) {\n+\t\tfree(new_blob);\n+\t\tnew_blob = strbuf_detach(&buf, &newsize);\n+\t\tsize = newsize;\n+\t}\n+\n+\twrote = write_in_full(fd, new_blob, size);\n+\tfree(new_blob);\n+\tif (wrote < 0)\n+\t\treturn error(\"unable to write file %s\", path);\n+\n+\treturn 0;\n+}\n+\n+static int close_and_clear(int *fd)\n+{\n+\tint ret = 0;\n+\n+\tif (*fd >= 0) {\n+\t\tret = close(*fd);\n+\t\t*fd = -1;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static void write_pc_item(struct parallel_checkout_item *pc_item,\n+\t\t\t  struct checkout *state)\n+{\n+\tunsigned int mode = (pc_item->ce->ce_mode & 0100) ? 0777 : 0666;\n+\tint fd = -1, fstat_done = 0;\n+\tstruct strbuf path = STRBUF_INIT;\n+\tconst char *dir_sep;\n+\n+\tstrbuf_add(&path, state->base_dir, state->base_dir_len);\n+\tstrbuf_add(&path, pc_item->ce->name, pc_item->ce->ce_namelen);\n+\n+\tdir_sep = find_last_dir_sep(path.buf);\n+\n+\t/*\n+\t * The leading dirs should have been already created by now. But, in\n+\t * case of path collisions, one of the dirs could have been replaced by\n+\t * a symlink (checked out after we enqueued this entry for parallel\n+\t * checkout). Thus, we must check the leading dirs again.\n+\t */\n+\tif (dir_sep && !has_dirs_only_path(path.buf, dir_sep - path.buf,\n+\t\t\t\t\t   state->base_dir_len)) {\n+\t\tpc_item->status = PC_ITEM_COLLIDED;\n+\t\tgoto out;\n+\t}\n+\n+\tfd = open(path.buf, O_WRONLY | O_CREAT | O_EXCL, mode);\n+\n+\tif (fd < 0) {\n+\t\tif (errno == EEXIST || errno == EISDIR) {\n+\t\t\t/*\n+\t\t\t * Errors which probably represent a path collision.\n+\t\t\t * Suppress the error message and mark the item to be\n+\t\t\t * retried later, sequentially. ENOTDIR and ENOENT are\n+\t\t\t * also interesting, but the above has_dirs_only_path()\n+\t\t\t * call should have already caught these cases.\n+\t\t\t */\n+\t\t\tpc_item->status = PC_ITEM_COLLIDED;\n+\t\t} else {\n+\t\t\terror_errno(\"failed to open file %s\", path.buf);\n+\t\t\tpc_item->status = PC_ITEM_FAILED;\n+\t\t}\n+\t\tgoto out;\n+\t}\n+\n+\tif (write_pc_item_to_fd(pc_item, fd, path.buf)) {\n+\t\t/* Error was already reported. */\n+\t\tpc_item->status = PC_ITEM_FAILED;\n+\t\tgoto out;\n+\t}\n+\n+\tfstat_done = fstat_checkout_output(fd, state, &pc_item->st);\n+\n+\tif (close_and_clear(&fd)) {\n+\t\terror_errno(\"unable to close file %s\", path.buf);\n+\t\tpc_item->status = PC_ITEM_FAILED;\n+\t\tgoto out;\n+\t}\n+\n+\tif (state->refresh_cache && !fstat_done && lstat(path.buf, &pc_item->st) < 0) {\n+\t\terror_errno(\"unable to stat just-written file %s\",  path.buf);\n+\t\tpc_item->status = PC_ITEM_FAILED;\n+\t\tgoto out;\n+\t}\n+\n+\tpc_item->status = PC_ITEM_WRITTEN;\n+\n+out:\n+\t/*\n+\t * No need to check close() return. At this point, either fd is already\n+\t * closed, or we are on an error path, that has already been reported.\n+\t */\n+\tclose_and_clear(&fd);\n+\tstrbuf_release(&path);\n+}\n+\n+static void write_items_sequentially(struct checkout *state)\n+{\n+\tsize_t i;\n+\n+\tfor (i = 0; i < parallel_checkout.nr; i++)\n+\t\twrite_pc_item(&parallel_checkout.items[i], state);\n+}\n+\n+int run_parallel_checkout(struct checkout *state)\n+{\n+\tint ret;\n+\n+\tif (parallel_checkout.status != PC_ACCEPTING_ENTRIES)\n+\t\tBUG(\"cannot run parallel checkout: uninitialized or already running\");\n+\n+\tparallel_checkout.status = PC_RUNNING;\n+\n+\twrite_items_sequentially(state);\n+\tret = handle_results(state);\n+\n+\tfinish_parallel_checkout();\n+\treturn ret;\n+}\ndiff --git a/parallel-checkout.h b/parallel-checkout.h\nnew file mode 100644\nindex 0000000000..e6d6fc01ea\n--- /dev/null\n+++ b/parallel-checkout.h\n@@ -0,0 +1,27 @@\n+#ifndef PARALLEL_CHECKOUT_H\n+#define PARALLEL_CHECKOUT_H\n+\n+struct cache_entry;\n+struct checkout;\n+struct conv_attrs;\n+\n+enum pc_status {\n+\tPC_UNINITIALIZED = 0,\n+\tPC_ACCEPTING_ENTRIES,\n+\tPC_RUNNING,\n+};\n+\n+enum pc_status parallel_checkout_status(void);\n+void init_parallel_checkout(void);\n+\n+/*\n+ * Return -1 if parallel checkout is currently not enabled or if the entry is\n+ * not eligible for parallel checkout. Otherwise, enqueue the entry for later\n+ * write and return 0.\n+ */\n+int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca);\n+\n+/* Write all the queued entries, returning 0 on success.*/\n+int run_parallel_checkout(struct checkout *state);\n+\n+#endif /* PARALLEL_CHECKOUT_H */\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex a511fadd89..1b1da7485a 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -17,6 +17,7 @@\n #include \"object-store.h\"\n #include \"promisor-remote.h\"\n #include \"entry.h\"\n+#include \"parallel-checkout.h\"\n \n /*\n  * Error messages expected by scripts out of plumbing commands such as\n@@ -438,7 +439,6 @@ static int check_updates(struct unpack_trees_options *o,\n \tif (should_update_submodules())\n \t\tload_gitmodules_file(index, &state);\n \n-\tenable_delayed_checkout(&state);\n \tif (has_promisor_remote()) {\n \t\t/*\n \t\t * Prefetch the objects that are to be checked out in the loop\n@@ -461,6 +461,9 @@ static int check_updates(struct unpack_trees_options *o,\n \t\t\t\t\t   to_fetch.oid, to_fetch.nr);\n \t\toid_array_clear(&to_fetch);\n \t}\n+\n+\tenable_delayed_checkout(&state);\n+\tinit_parallel_checkout();\n \tfor (i = 0; i < index->cache_nr; i++) {\n \t\tstruct cache_entry *ce = index->cache[i];\n \n@@ -474,6 +477,7 @@ static int check_updates(struct unpack_trees_options *o,\n \t\t}\n \t}\n \tstop_progress(&progress);\n+\terrs |= run_parallel_checkout(&state);\n \terrs |= finish_delayed_checkout(&state, NULL);\n \tgit_attr_set_direction(GIT_ATTR_CHECKIN);\n \n-- \n2.28.0\n\n"},{"id":"409153","messageId":"815137685ac3e41444201316f537db9797dcacd2.1604521276.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 11/19] parallel-checkout: make it truly parallel","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:10Z","receivedAt":"2020-11-04T20:34:13Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Use multiple worker processes to distribute the queued entries and call\nwrite_checkout_item() in parallel for them. The items are distributed\nuniformly in contiguous chunks. This minimizes the chances of two\nworkers writing to the same directory simultaneously, which could\naffect performance due to lock contention in the kernel. Work stealing\n(or any other format of re-distribution) is not implemented yet.\n\nThe parallel version was benchmarked during three operations in the\nlinux repo, with cold cache: cloning v5.8, checking out v5.8 from\nv2.6.15 (checkout I) and checking out v5.8 from v5.7 (checkout II). The\nfour tables below show the mean run times and standard deviations for\n5 runs in: a local file system with SSD, a local file system with HDD, a\nLinux NFS server, and Amazon EFS. The numbers of workers were chosen\nbased on what produces the best result for each case.\n\nLocal SSD:\n\n            Clone                  Checkout I             Checkout II\nSequential  8.171 s ± 0.206 s      8.735 s ± 0.230 s      4.166 s ± 0.246 s\n10 workers  3.277 s ± 0.138 s      3.774 s ± 0.188 s      2.561 s ± 0.120 s\nSpeedup     2.49 ± 0.12            2.31 ± 0.13            1.63 ± 0.12\n\nLocal HDD:\n\n            Clone                  Checkout I             Checkout II\nSequential  35.157 s ± 0.205 s     48.835 s ± 0.407 s     47.302 s ± 1.435 s\n8 workers   35.538 s ± 0.325 s     49.353 s ± 0.826 s     48.919 s ± 0.416 s\nSpeedup     0.99 ± 0.01            0.99 ± 0.02            0.97 ± 0.03\n\nLinux NFS server (v4.1, on EBS, single availability zone):\n\n            Clone                  Checkout I             Checkout II\nSequential  216.070 s ± 3.611 s    211.169 s ± 3.147 s    57.446 s ± 1.301 s\n32 workers  67.997 s ± 0.740 s     66.563 s ± 0.457 s     23.708 s ± 0.622 s\nSpeedup     3.18 ± 0.06            3.17 ± 0.05            2.42 ± 0.08\n\nEFS (v4.1, replicated over multiple availability zones):\n\n            Clone                  Checkout I             Checkout II\nSequential  1249.329 s ± 13.857 s  1438.979 s ± 78.792 s  543.919 s ± 18.745 s\n64 workers  225.864 s ± 12.433 s   316.345 s ± 1.887 s    183.648 s ± 10.095 s\nSpeedup     5.53 ± 0.31            4.55 ± 0.25            2.96 ± 0.19\n\nThe above benchmarks show that parallel checkout is most effective on\nrepositories located on an SSD or over a distributed file system. For\nlocal file systems on spinning disks, and/or older machines, the\nparallelism does not always bring a good performance. In fact, it can\neven increase the run time. For this reason, the sequential code is\nstill the default. Two settings are added to optionally enable and\nconfigure the new parallel version as desired.\n\nLocal SSD tests were executed in an i7-7700HQ (4 cores with\nhyper-threading) running Manjaro Linux. Local HDD tests were executed in\nan i7-2600 (also 4 cores with hyper-threading), HDD Seagate Barracuda\n7200 rpm SATA 3.0, running Debian 9.13. NFS and EFS tests were\nexecuted in an Amazon EC2 c5n.large instance, with 2 vCPUs. The Linux\nNFS server was running on a m6g.large instance with 1 TB, EBS GP2\nvolume. Before each timing, the linux repository was removed (or checked\nout back), and `sync && sysctl vm.drop_caches=3` was executed.\n\nCo-authored-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nCo-authored-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n .gitignore                        |   1 +\n Documentation/config/checkout.txt |  21 +++\n Makefile                          |   1 +\n builtin.h                         |   1 +\n builtin/checkout--helper.c        | 142 +++++++++++++++\n git.c                             |   2 +\n parallel-checkout.c               | 280 +++++++++++++++++++++++++++---\n parallel-checkout.h               |  84 ++++++++-\n unpack-trees.c                    |  10 +-\n 9 files changed, 508 insertions(+), 34 deletions(-)\n create mode 100644 builtin/checkout--helper.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 6232d33924..1a341ea184 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -33,6 +33,7 @@\n /git-check-mailmap\n /git-check-ref-format\n /git-checkout\n+/git-checkout--helper\n /git-checkout-index\n /git-cherry\n /git-cherry-pick\ndiff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt\nindex 6b646813ab..23e8f7cde0 100644\n--- a/Documentation/config/checkout.txt\n+++ b/Documentation/config/checkout.txt\n@@ -16,3 +16,24 @@ will checkout the '<something>' branch on another remote,\n and by linkgit:git-worktree[1] when 'git worktree add' refers to a\n remote branch. This setting might be used for other checkout-like\n commands or functionality in the future.\n+\n+checkout.workers::\n+\tThe number of parallel workers to use when updating the working tree.\n+\tThe default is one, i.e. sequential execution. If set to a value less\n+\tthan one, Git will use as many workers as the number of logical cores\n+\tavailable. This setting and `checkout.thresholdForParallelism` affect\n+\tall commands that perform checkout. E.g. checkout, clone, reset,\n+\tsparse-checkout, etc.\n++\n+Note: parallel checkout usually delivers better performance for repositories\n+located on SSDs or over NFS. For repositories on spinning disks and/or machines\n+with a small number of cores, the default sequential checkout often performs\n+better. The size and compression level of a repository might also influence how\n+well the parallel version performs.\n+\n+checkout.thresholdForParallelism::\n+\tWhen running parallel checkout with a small number of files, the cost\n+\tof subprocess spawning and inter-process communication might outweigh\n+\tthe parallelization gains. This setting allows to define the minimum\n+\tnumber of files for which parallel checkout should be attempted. The\n+\tdefault is 100.\ndiff --git a/Makefile b/Makefile\nindex 10ee5e709b..535e6e94aa 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1063,6 +1063,7 @@ BUILTIN_OBJS += builtin/check-attr.o\n BUILTIN_OBJS += builtin/check-ignore.o\n BUILTIN_OBJS += builtin/check-mailmap.o\n BUILTIN_OBJS += builtin/check-ref-format.o\n+BUILTIN_OBJS += builtin/checkout--helper.o\n BUILTIN_OBJS += builtin/checkout-index.o\n BUILTIN_OBJS += builtin/checkout.o\n BUILTIN_OBJS += builtin/clean.o\ndiff --git a/builtin.h b/builtin.h\nindex 53fb290963..2abbe14b0b 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -123,6 +123,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix);\n int cmd_bundle(int argc, const char **argv, const char *prefix);\n int cmd_cat_file(int argc, const char **argv, const char *prefix);\n int cmd_checkout(int argc, const char **argv, const char *prefix);\n+int cmd_checkout__helper(int argc, const char **argv, const char *prefix);\n int cmd_checkout_index(int argc, const char **argv, const char *prefix);\n int cmd_check_attr(int argc, const char **argv, const char *prefix);\n int cmd_check_ignore(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/checkout--helper.c b/builtin/checkout--helper.c\nnew file mode 100644\nindex 0000000000..a61ed76f0d\n--- /dev/null\n+++ b/builtin/checkout--helper.c\n@@ -0,0 +1,142 @@\n+#include \"builtin.h\"\n+#include \"config.h\"\n+#include \"entry.h\"\n+#include \"parallel-checkout.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+\n+static void packet_to_pc_item(char *line, int len,\n+\t\t\t      struct parallel_checkout_item *pc_item)\n+{\n+\tstruct pc_item_fixed_portion *fixed_portion;\n+\tchar *encoding, *variant;\n+\n+\tif (len < sizeof(struct pc_item_fixed_portion))\n+\t\tBUG(\"checkout worker received too short item (got %dB, exp %dB)\",\n+\t\t    len, (int)sizeof(struct pc_item_fixed_portion));\n+\n+\tfixed_portion = (struct pc_item_fixed_portion *)line;\n+\n+\tif (len - sizeof(struct pc_item_fixed_portion) !=\n+\t\tfixed_portion->name_len + fixed_portion->working_tree_encoding_len)\n+\t\tBUG(\"checkout worker received corrupted item\");\n+\n+\tvariant = line + sizeof(struct pc_item_fixed_portion);\n+\n+\t/*\n+\t * Note: the main process uses zero length to communicate that the\n+\t * encoding is NULL. There is no use case in actually sending an empty\n+\t * string since it's considered as NULL when ca.working_tree_encoding\n+\t * is set at git_path_check_encoding().\n+\t */\n+\tif (fixed_portion->working_tree_encoding_len) {\n+\t\tencoding = xmemdupz(variant,\n+\t\t\t\t    fixed_portion->working_tree_encoding_len);\n+\t\tvariant += fixed_portion->working_tree_encoding_len;\n+\t} else {\n+\t\tencoding = NULL;\n+\t}\n+\n+\tmemset(pc_item, 0, sizeof(*pc_item));\n+\tpc_item->ce = make_empty_transient_cache_entry(fixed_portion->name_len);\n+\tpc_item->ce->ce_namelen = fixed_portion->name_len;\n+\tpc_item->ce->ce_mode = fixed_portion->ce_mode;\n+\tmemcpy(pc_item->ce->name, variant, pc_item->ce->ce_namelen);\n+\toidcpy(&pc_item->ce->oid, &fixed_portion->oid);\n+\n+\tpc_item->id = fixed_portion->id;\n+\tpc_item->ca.crlf_action = fixed_portion->crlf_action;\n+\tpc_item->ca.ident = fixed_portion->ident;\n+\tpc_item->ca.working_tree_encoding = encoding;\n+}\n+\n+static void report_result(struct parallel_checkout_item *pc_item)\n+{\n+\tstruct pc_item_result res = { 0 };\n+\tsize_t size;\n+\n+\tres.id = pc_item->id;\n+\tres.status = pc_item->status;\n+\n+\tif (pc_item->status == PC_ITEM_WRITTEN) {\n+\t\tres.st = pc_item->st;\n+\t\tsize = sizeof(res);\n+\t} else {\n+\t\tsize = PC_ITEM_RESULT_BASE_SIZE;\n+\t}\n+\n+\tpacket_write(1, (const char *)&res, size);\n+}\n+\n+/* Free the worker-side malloced data, but not pc_item itself. */\n+static void release_pc_item_data(struct parallel_checkout_item *pc_item)\n+{\n+\tfree((char *)pc_item->ca.working_tree_encoding);\n+\tdiscard_cache_entry(pc_item->ce);\n+}\n+\n+static void worker_loop(struct checkout *state)\n+{\n+\tstruct parallel_checkout_item *items = NULL;\n+\tsize_t i, nr = 0, alloc = 0;\n+\n+\twhile (1) {\n+\t\tint len;\n+\t\tchar *line = packet_read_line(0, &len);\n+\n+\t\tif (!line)\n+\t\t\tbreak;\n+\n+\t\tALLOC_GROW(items, nr + 1, alloc);\n+\t\tpacket_to_pc_item(line, len, &items[nr++]);\n+\t}\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\tstruct parallel_checkout_item *pc_item = &items[i];\n+\t\twrite_pc_item(pc_item, state);\n+\t\treport_result(pc_item);\n+\t\trelease_pc_item_data(pc_item);\n+\t}\n+\n+\tpacket_flush(1);\n+\n+\tfree(items);\n+}\n+\n+static const char * const checkout_helper_usage[] = {\n+\tN_(\"git checkout--helper [<options>]\"),\n+\tNULL\n+};\n+\n+int cmd_checkout__helper(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct checkout state = CHECKOUT_INIT;\n+\tstruct option checkout_helper_options[] = {\n+\t\tOPT_STRING(0, \"prefix\", &state.base_dir, N_(\"string\"),\n+\t\t\tN_(\"when creating files, prepend <string>\")),\n+\t\tOPT_END()\n+\t};\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n+\t\tusage_with_options(checkout_helper_usage,\n+\t\t\t\t   checkout_helper_options);\n+\n+\tgit_config(git_default_config, NULL);\n+\targc = parse_options(argc, argv, prefix, checkout_helper_options,\n+\t\t\t     checkout_helper_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_with_options(checkout_helper_usage, checkout_helper_options);\n+\n+\tif (state.base_dir)\n+\t\tstate.base_dir_len = strlen(state.base_dir);\n+\n+\t/*\n+\t * Setting this on worker won't actually update the index. We just need\n+\t * to pretend so to induce the checkout machinery to stat() the written\n+\t * entries.\n+\t */\n+\tstate.refresh_cache = 1;\n+\n+\tworker_loop(&state);\n+\treturn 0;\n+}\ndiff --git a/git.c b/git.c\nindex 4bdcdad2cc..384f144593 100644\n--- a/git.c\n+++ b/git.c\n@@ -487,6 +487,8 @@ static struct cmd_struct commands[] = {\n \t{ \"check-mailmap\", cmd_check_mailmap, RUN_SETUP },\n \t{ \"check-ref-format\", cmd_check_ref_format, NO_PARSEOPT  },\n \t{ \"checkout\", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },\n+\t{ \"checkout--helper\", cmd_checkout__helper,\n+\t\tRUN_SETUP | NEED_WORK_TREE | SUPPORT_SUPER_PREFIX },\n \t{ \"checkout-index\", cmd_checkout_index,\n \t\tRUN_SETUP | NEED_WORK_TREE},\n \t{ \"cherry\", cmd_cherry, RUN_SETUP },\ndiff --git a/parallel-checkout.c b/parallel-checkout.c\nindex fd871b09d3..2d77998f46 100644\n--- a/parallel-checkout.c\n+++ b/parallel-checkout.c\n@@ -1,28 +1,15 @@\n #include \"cache.h\"\n #include \"entry.h\"\n #include \"parallel-checkout.h\"\n+#include \"pkt-line.h\"\n+#include \"run-command.h\"\n #include \"streaming.h\"\n+#include \"thread-utils.h\"\n+#include \"config.h\"\n \n-enum pc_item_status {\n-\tPC_ITEM_PENDING = 0,\n-\tPC_ITEM_WRITTEN,\n-\t/*\n-\t * The entry could not be written because there was another file\n-\t * already present in its path or leading directories. Since\n-\t * checkout_entry_ca() removes such files from the working tree before\n-\t * enqueueing the entry for parallel checkout, it means that there was\n-\t * a path collision among the entries being written.\n-\t */\n-\tPC_ITEM_COLLIDED,\n-\tPC_ITEM_FAILED,\n-};\n-\n-struct parallel_checkout_item {\n-\t/* pointer to a istate->cache[] entry. Not owned by us. */\n-\tstruct cache_entry *ce;\n-\tstruct conv_attrs ca;\n-\tstruct stat st;\n-\tenum pc_item_status status;\n+struct pc_worker {\n+\tstruct child_process cp;\n+\tsize_t next_to_complete, nr_to_complete;\n };\n \n struct parallel_checkout {\n@@ -38,6 +25,19 @@ enum pc_status parallel_checkout_status(void)\n \treturn parallel_checkout.status;\n }\n \n+#define DEFAULT_THRESHOLD_FOR_PARALLELISM 100\n+\n+void get_parallel_checkout_configs(int *num_workers, int *threshold)\n+{\n+\tif (git_config_get_int(\"checkout.workers\", num_workers))\n+\t\t*num_workers = 1;\n+\telse if (*num_workers < 1)\n+\t\t*num_workers = online_cpus();\n+\n+\tif (git_config_get_int(\"checkout.thresholdForParallelism\", threshold))\n+\t\t*threshold = DEFAULT_THRESHOLD_FOR_PARALLELISM;\n+}\n+\n void init_parallel_checkout(void)\n {\n \tif (parallel_checkout.status != PC_UNINITIALIZED)\n@@ -115,10 +115,12 @@ int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca)\n \tALLOC_GROW(parallel_checkout.items, parallel_checkout.nr + 1,\n \t\t   parallel_checkout.alloc);\n \n-\tpc_item = &parallel_checkout.items[parallel_checkout.nr++];\n+\tpc_item = &parallel_checkout.items[parallel_checkout.nr];\n \tpc_item->ce = ce;\n \tmemcpy(&pc_item->ca, ca, sizeof(pc_item->ca));\n \tpc_item->status = PC_ITEM_PENDING;\n+\tpc_item->id = parallel_checkout.nr;\n+\tparallel_checkout.nr++;\n \n \treturn 0;\n }\n@@ -231,7 +233,8 @@ static int write_pc_item_to_fd(struct parallel_checkout_item *pc_item, int fd,\n \t/*\n \t * checkout metadata is used to give context for external process\n \t * filters. Files requiring such filters are not eligible for parallel\n-\t * checkout, so pass NULL.\n+\t * checkout, so pass NULL. Note: if that changes, the metadata must also\n+\t * be passed from the main process to the workers.\n \t */\n \tret = convert_to_working_tree_ca(&pc_item->ca, pc_item->ce->name,\n \t\t\t\t\t new_blob, size, &buf, NULL);\n@@ -262,8 +265,8 @@ static int close_and_clear(int *fd)\n \treturn ret;\n }\n \n-static void write_pc_item(struct parallel_checkout_item *pc_item,\n-\t\t\t  struct checkout *state)\n+void write_pc_item(struct parallel_checkout_item *pc_item,\n+\t\t   struct checkout *state)\n {\n \tunsigned int mode = (pc_item->ce->ce_mode & 0100) ? 0777 : 0666;\n \tint fd = -1, fstat_done = 0;\n@@ -337,6 +340,221 @@ static void write_pc_item(struct parallel_checkout_item *pc_item,\n \tstrbuf_release(&path);\n }\n \n+static void send_one_item(int fd, struct parallel_checkout_item *pc_item)\n+{\n+\tsize_t len_data;\n+\tchar *data, *variant;\n+\tstruct pc_item_fixed_portion *fixed_portion;\n+\tconst char *working_tree_encoding = pc_item->ca.working_tree_encoding;\n+\tsize_t name_len = pc_item->ce->ce_namelen;\n+\tsize_t working_tree_encoding_len = working_tree_encoding ?\n+\t\t\t\t\t   strlen(working_tree_encoding) : 0;\n+\n+\tlen_data = sizeof(struct pc_item_fixed_portion) + name_len +\n+\t\t   working_tree_encoding_len;\n+\n+\tdata = xcalloc(1, len_data);\n+\n+\tfixed_portion = (struct pc_item_fixed_portion *)data;\n+\tfixed_portion->id = pc_item->id;\n+\tfixed_portion->ce_mode = pc_item->ce->ce_mode;\n+\tfixed_portion->crlf_action = pc_item->ca.crlf_action;\n+\tfixed_portion->ident = pc_item->ca.ident;\n+\tfixed_portion->name_len = name_len;\n+\tfixed_portion->working_tree_encoding_len = working_tree_encoding_len;\n+\t/*\n+\t * We use hashcpy() instead of oidcpy() because the hash[] positions\n+\t * after `the_hash_algo->rawsz` might not be initialized. And Valgrind\n+\t * would complain about passing uninitialized bytes to a syscall\n+\t * (write(2)). There is no real harm in this case, but the warning could\n+\t * hinder the detection of actual errors.\n+\t */\n+\thashcpy(fixed_portion->oid.hash, pc_item->ce->oid.hash);\n+\n+\tvariant = data + sizeof(*fixed_portion);\n+\tif (working_tree_encoding_len) {\n+\t\tmemcpy(variant, working_tree_encoding, working_tree_encoding_len);\n+\t\tvariant += working_tree_encoding_len;\n+\t}\n+\tmemcpy(variant, pc_item->ce->name, name_len);\n+\n+\tpacket_write(fd, data, len_data);\n+\n+\tfree(data);\n+}\n+\n+static void send_batch(int fd, size_t start, size_t nr)\n+{\n+\tsize_t i;\n+\tfor (i = 0; i < nr; i++)\n+\t\tsend_one_item(fd, &parallel_checkout.items[start + i]);\n+\tpacket_flush(fd);\n+}\n+\n+static struct pc_worker *setup_workers(struct checkout *state, int num_workers)\n+{\n+\tstruct pc_worker *workers;\n+\tint i, workers_with_one_extra_item;\n+\tsize_t base_batch_size, next_to_assign = 0;\n+\n+\tALLOC_ARRAY(workers, num_workers);\n+\n+\tfor (i = 0; i < num_workers; i++) {\n+\t\tstruct child_process *cp = &workers[i].cp;\n+\n+\t\tchild_process_init(cp);\n+\t\tcp->git_cmd = 1;\n+\t\tcp->in = -1;\n+\t\tcp->out = -1;\n+\t\tcp->clean_on_exit = 1;\n+\t\tstrvec_push(&cp->args, \"checkout--helper\");\n+\t\tif (state->base_dir_len)\n+\t\t\tstrvec_pushf(&cp->args, \"--prefix=%s\", state->base_dir);\n+\t\tif (start_command(cp))\n+\t\t\tdie(_(\"failed to spawn checkout worker\"));\n+\t}\n+\n+\tbase_batch_size = parallel_checkout.nr / num_workers;\n+\tworkers_with_one_extra_item = parallel_checkout.nr % num_workers;\n+\n+\tfor (i = 0; i < num_workers; i++) {\n+\t\tstruct pc_worker *worker = &workers[i];\n+\t\tsize_t batch_size = base_batch_size;\n+\n+\t\t/* distribute the extra work evenly */\n+\t\tif (i < workers_with_one_extra_item)\n+\t\t\tbatch_size++;\n+\n+\t\tsend_batch(worker->cp.in, next_to_assign, batch_size);\n+\t\tworker->next_to_complete = next_to_assign;\n+\t\tworker->nr_to_complete = batch_size;\n+\n+\t\tnext_to_assign += batch_size;\n+\t}\n+\n+\treturn workers;\n+}\n+\n+static void finish_workers(struct pc_worker *workers, int num_workers)\n+{\n+\tint i;\n+\n+\t/*\n+\t * Close pipes before calling finish_command() to let the workers\n+\t * exit asynchronously and avoid spending extra time on wait().\n+\t */\n+\tfor (i = 0; i < num_workers; i++) {\n+\t\tstruct child_process *cp = &workers[i].cp;\n+\t\tif (cp->in >= 0)\n+\t\t\tclose(cp->in);\n+\t\tif (cp->out >= 0)\n+\t\t\tclose(cp->out);\n+\t}\n+\n+\tfor (i = 0; i < num_workers; i++) {\n+\t\tif (finish_command(&workers[i].cp))\n+\t\t\terror(_(\"checkout worker %d finished with error\"), i);\n+\t}\n+\n+\tfree(workers);\n+}\n+\n+#define ASSERT_PC_ITEM_RESULT_SIZE(got, exp) \\\n+{ \\\n+\tif (got != exp) \\\n+\t\tBUG(\"corrupted result from checkout worker (got %dB, exp %dB)\", \\\n+\t\t    got, exp); \\\n+} while(0)\n+\n+static void parse_and_save_result(const char *line, int len,\n+\t\t\t\t  struct pc_worker *worker)\n+{\n+\tstruct pc_item_result *res;\n+\tstruct parallel_checkout_item *pc_item;\n+\tstruct stat *st = NULL;\n+\n+\tif (len < PC_ITEM_RESULT_BASE_SIZE)\n+\t\tBUG(\"too short result from checkout worker (got %dB, exp %dB)\",\n+\t\t    len, (int)PC_ITEM_RESULT_BASE_SIZE);\n+\n+\tres = (struct pc_item_result *)line;\n+\n+\t/*\n+\t * Worker should send either the full result struct on success, or\n+\t * just the base (i.e. no stat data), otherwise.\n+\t */\n+\tif (res->status == PC_ITEM_WRITTEN) {\n+\t\tASSERT_PC_ITEM_RESULT_SIZE(len, (int)sizeof(struct pc_item_result));\n+\t\tst = &res->st;\n+\t} else {\n+\t\tASSERT_PC_ITEM_RESULT_SIZE(len, (int)PC_ITEM_RESULT_BASE_SIZE);\n+\t}\n+\n+\tif (!worker->nr_to_complete || res->id != worker->next_to_complete)\n+\t\tBUG(\"checkout worker sent unexpected item id\");\n+\n+\tworker->next_to_complete++;\n+\tworker->nr_to_complete--;\n+\n+\tpc_item = &parallel_checkout.items[res->id];\n+\tpc_item->status = res->status;\n+\tif (st)\n+\t\tpc_item->st = *st;\n+}\n+\n+\n+static void gather_results_from_workers(struct pc_worker *workers,\n+\t\t\t\t\tint num_workers)\n+{\n+\tint i, active_workers = num_workers;\n+\tstruct pollfd *pfds;\n+\n+\tCALLOC_ARRAY(pfds, num_workers);\n+\tfor (i = 0; i < num_workers; i++) {\n+\t\tpfds[i].fd = workers[i].cp.out;\n+\t\tpfds[i].events = POLLIN;\n+\t}\n+\n+\twhile (active_workers) {\n+\t\tint nr = poll(pfds, num_workers, -1);\n+\n+\t\tif (nr < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\tdie_errno(\"failed to poll checkout workers\");\n+\t\t}\n+\n+\t\tfor (i = 0; i < num_workers && nr > 0; i++) {\n+\t\t\tstruct pc_worker *worker = &workers[i];\n+\t\t\tstruct pollfd *pfd = &pfds[i];\n+\n+\t\t\tif (!pfd->revents)\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (pfd->revents & POLLIN) {\n+\t\t\t\tint len;\n+\t\t\t\tconst char *line = packet_read_line(pfd->fd, &len);\n+\n+\t\t\t\tif (!line) {\n+\t\t\t\t\tpfd->fd = -1;\n+\t\t\t\t\tactive_workers--;\n+\t\t\t\t} else {\n+\t\t\t\t\tparse_and_save_result(line, len, worker);\n+\t\t\t\t}\n+\t\t\t} else if (pfd->revents & POLLHUP) {\n+\t\t\t\tpfd->fd = -1;\n+\t\t\t\tactive_workers--;\n+\t\t\t} else if (pfd->revents & (POLLNVAL | POLLERR)) {\n+\t\t\t\tdie(_(\"error polling from checkout worker\"));\n+\t\t\t}\n+\n+\t\t\tnr--;\n+\t\t}\n+\t}\n+\n+\tfree(pfds);\n+}\n+\n static void write_items_sequentially(struct checkout *state)\n {\n \tsize_t i;\n@@ -345,7 +563,7 @@ static void write_items_sequentially(struct checkout *state)\n \t\twrite_pc_item(&parallel_checkout.items[i], state);\n }\n \n-int run_parallel_checkout(struct checkout *state)\n+int run_parallel_checkout(struct checkout *state, int num_workers, int threshold)\n {\n \tint ret;\n \n@@ -354,7 +572,17 @@ int run_parallel_checkout(struct checkout *state)\n \n \tparallel_checkout.status = PC_RUNNING;\n \n-\twrite_items_sequentially(state);\n+\tif (parallel_checkout.nr < num_workers)\n+\t\tnum_workers = parallel_checkout.nr;\n+\n+\tif (num_workers <= 1 || parallel_checkout.nr < threshold) {\n+\t\twrite_items_sequentially(state);\n+\t} else {\n+\t\tstruct pc_worker *workers = setup_workers(state, num_workers);\n+\t\tgather_results_from_workers(workers, num_workers);\n+\t\tfinish_workers(workers, num_workers);\n+\t}\n+\n \tret = handle_results(state);\n \n \tfinish_parallel_checkout();\ndiff --git a/parallel-checkout.h b/parallel-checkout.h\nindex e6d6fc01ea..54314ccdc5 100644\n--- a/parallel-checkout.h\n+++ b/parallel-checkout.h\n@@ -1,9 +1,12 @@\n #ifndef PARALLEL_CHECKOUT_H\n #define PARALLEL_CHECKOUT_H\n \n-struct cache_entry;\n-struct checkout;\n-struct conv_attrs;\n+#include \"entry.h\"\n+#include \"convert.h\"\n+\n+/****************************************************************\n+ * Users of parallel checkout\n+ ****************************************************************/\n \n enum pc_status {\n \tPC_UNINITIALIZED = 0,\n@@ -12,6 +15,7 @@ enum pc_status {\n };\n \n enum pc_status parallel_checkout_status(void);\n+void get_parallel_checkout_configs(int *num_workers, int *threshold);\n void init_parallel_checkout(void);\n \n /*\n@@ -21,7 +25,77 @@ void init_parallel_checkout(void);\n  */\n int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca);\n \n-/* Write all the queued entries, returning 0 on success.*/\n-int run_parallel_checkout(struct checkout *state);\n+/*\n+ * Write all the queued entries, returning 0 on success. If the number of\n+ * entries is smaller than the specified threshold, the operation is performed\n+ * sequentially.\n+ */\n+int run_parallel_checkout(struct checkout *state, int num_workers, int threshold);\n+\n+/****************************************************************\n+ * Interface with checkout--helper\n+ ****************************************************************/\n+\n+enum pc_item_status {\n+\tPC_ITEM_PENDING = 0,\n+\tPC_ITEM_WRITTEN,\n+\t/*\n+\t * The entry could not be written because there was another file\n+\t * already present in its path or leading directories. Since\n+\t * checkout_entry_ca() removes such files from the working tree before\n+\t * enqueueing the entry for parallel checkout, it means that there was\n+\t * a path collision among the entries being written.\n+\t */\n+\tPC_ITEM_COLLIDED,\n+\tPC_ITEM_FAILED,\n+};\n+\n+struct parallel_checkout_item {\n+\t/*\n+\t * In main process ce points to a istate->cache[] entry. Thus, it's not\n+\t * owned by us. In workers they own the memory, which *must be* released.\n+\t */\n+\tstruct cache_entry *ce;\n+\tstruct conv_attrs ca;\n+\tsize_t id; /* position in parallel_checkout.items[] of main process */\n+\n+\t/* Output fields, sent from workers. */\n+\tenum pc_item_status status;\n+\tstruct stat st;\n+};\n+\n+/*\n+ * The fixed-size portion of `struct parallel_checkout_item` that is sent to the\n+ * workers. Following this will be 2 strings: ca.working_tree_encoding and\n+ * ce.name; These are NOT null terminated, since we have the size in the fixed\n+ * portion.\n+ *\n+ * Note that not all fields of conv_attrs and cache_entry are passed, only the\n+ * ones that will be required by the workers to smudge and write the entry.\n+ */\n+struct pc_item_fixed_portion {\n+\tsize_t id;\n+\tstruct object_id oid;\n+\tunsigned int ce_mode;\n+\tenum convert_crlf_action crlf_action;\n+\tint ident;\n+\tsize_t working_tree_encoding_len;\n+\tsize_t name_len;\n+};\n+\n+/*\n+ * The fields of `struct parallel_checkout_item` that are returned by the\n+ * workers. Note: `st` must be the last one, as it is omitted on error.\n+ */\n+struct pc_item_result {\n+\tsize_t id;\n+\tenum pc_item_status status;\n+\tstruct stat st;\n+};\n+\n+#define PC_ITEM_RESULT_BASE_SIZE offsetof(struct pc_item_result, st)\n+\n+void write_pc_item(struct parallel_checkout_item *pc_item,\n+\t\t   struct checkout *state);\n \n #endif /* PARALLEL_CHECKOUT_H */\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 1b1da7485a..117ed42370 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -399,7 +399,7 @@ static int check_updates(struct unpack_trees_options *o,\n \tint errs = 0;\n \tstruct progress *progress;\n \tstruct checkout state = CHECKOUT_INIT;\n-\tint i;\n+\tint i, pc_workers, pc_threshold;\n \n \ttrace_performance_enter();\n \tstate.force = 1;\n@@ -462,8 +462,11 @@ static int check_updates(struct unpack_trees_options *o,\n \t\toid_array_clear(&to_fetch);\n \t}\n \n+\tget_parallel_checkout_configs(&pc_workers, &pc_threshold);\n+\n \tenable_delayed_checkout(&state);\n-\tinit_parallel_checkout();\n+\tif (pc_workers > 1)\n+\t\tinit_parallel_checkout();\n \tfor (i = 0; i < index->cache_nr; i++) {\n \t\tstruct cache_entry *ce = index->cache[i];\n \n@@ -477,7 +480,8 @@ static int check_updates(struct unpack_trees_options *o,\n \t\t}\n \t}\n \tstop_progress(&progress);\n-\terrs |= run_parallel_checkout(&state);\n+\tif (pc_workers > 1)\n+\t\terrs |= run_parallel_checkout(&state, pc_workers, pc_threshold);\n \terrs |= finish_delayed_checkout(&state, NULL);\n \tgit_attr_set_direction(GIT_ATTR_CHECKIN);\n \n-- \n2.28.0\n\n"},{"id":"409154","messageId":"2b426215827cddc46f908feb3de38e67c95b5efa.1604521276.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 12/19] parallel-checkout: support progress displaying","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:11Z","receivedAt":"2020-11-04T20:34:15Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Original-patch-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n parallel-checkout.c | 34 +++++++++++++++++++++++++++++++---\n parallel-checkout.h |  4 +++-\n unpack-trees.c      | 11 ++++++++---\n 3 files changed, 42 insertions(+), 7 deletions(-)\n\ndiff --git a/parallel-checkout.c b/parallel-checkout.c\nindex 2d77998f46..72ac93d541 100644\n--- a/parallel-checkout.c\n+++ b/parallel-checkout.c\n@@ -2,6 +2,7 @@\n #include \"entry.h\"\n #include \"parallel-checkout.h\"\n #include \"pkt-line.h\"\n+#include \"progress.h\"\n #include \"run-command.h\"\n #include \"streaming.h\"\n #include \"thread-utils.h\"\n@@ -16,6 +17,8 @@ struct parallel_checkout {\n \tenum pc_status status;\n \tstruct parallel_checkout_item *items;\n \tsize_t nr, alloc;\n+\tstruct progress *progress;\n+\tunsigned int *progress_cnt;\n };\n \n static struct parallel_checkout parallel_checkout;\n@@ -125,6 +128,20 @@ int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca)\n \treturn 0;\n }\n \n+size_t pc_queue_size(void)\n+{\n+\treturn parallel_checkout.nr;\n+}\n+\n+static void advance_progress_meter(void)\n+{\n+\tif (parallel_checkout.progress) {\n+\t\t(*parallel_checkout.progress_cnt)++;\n+\t\tdisplay_progress(parallel_checkout.progress,\n+\t\t\t\t *parallel_checkout.progress_cnt);\n+\t}\n+}\n+\n static int handle_results(struct checkout *state)\n {\n \tint ret = 0;\n@@ -173,6 +190,7 @@ static int handle_results(struct checkout *state)\n \t\t\t */\n \t\t\tret |= checkout_entry_ca(pc_item->ce, &pc_item->ca,\n \t\t\t\t\t\t state, NULL, NULL);\n+\t\t\tadvance_progress_meter();\n \t\t\tbreak;\n \t\tcase PC_ITEM_PENDING:\n \t\t\thave_pending = 1;\n@@ -500,6 +518,9 @@ static void parse_and_save_result(const char *line, int len,\n \tpc_item->status = res->status;\n \tif (st)\n \t\tpc_item->st = *st;\n+\n+\tif (res->status != PC_ITEM_COLLIDED)\n+\t\tadvance_progress_meter();\n }\n \n \n@@ -559,11 +580,16 @@ static void write_items_sequentially(struct checkout *state)\n {\n \tsize_t i;\n \n-\tfor (i = 0; i < parallel_checkout.nr; i++)\n-\t\twrite_pc_item(&parallel_checkout.items[i], state);\n+\tfor (i = 0; i < parallel_checkout.nr; i++) {\n+\t\tstruct parallel_checkout_item *pc_item = &parallel_checkout.items[i];\n+\t\twrite_pc_item(pc_item, state);\n+\t\tif (pc_item->status != PC_ITEM_COLLIDED)\n+\t\t\tadvance_progress_meter();\n+\t}\n }\n \n-int run_parallel_checkout(struct checkout *state, int num_workers, int threshold)\n+int run_parallel_checkout(struct checkout *state, int num_workers, int threshold,\n+\t\t\t  struct progress *progress, unsigned int *progress_cnt)\n {\n \tint ret;\n \n@@ -571,6 +597,8 @@ int run_parallel_checkout(struct checkout *state, int num_workers, int threshold\n \t\tBUG(\"cannot run parallel checkout: uninitialized or already running\");\n \n \tparallel_checkout.status = PC_RUNNING;\n+\tparallel_checkout.progress = progress;\n+\tparallel_checkout.progress_cnt = progress_cnt;\n \n \tif (parallel_checkout.nr < num_workers)\n \t\tnum_workers = parallel_checkout.nr;\ndiff --git a/parallel-checkout.h b/parallel-checkout.h\nindex 54314ccdc5..8377b179d5 100644\n--- a/parallel-checkout.h\n+++ b/parallel-checkout.h\n@@ -24,13 +24,15 @@ void init_parallel_checkout(void);\n  * write and return 0.\n  */\n int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca);\n+size_t pc_queue_size(void);\n \n /*\n  * Write all the queued entries, returning 0 on success. If the number of\n  * entries is smaller than the specified threshold, the operation is performed\n  * sequentially.\n  */\n-int run_parallel_checkout(struct checkout *state, int num_workers, int threshold);\n+int run_parallel_checkout(struct checkout *state, int num_workers, int threshold,\n+\t\t\t  struct progress *progress, unsigned int *progress_cnt);\n \n /****************************************************************\n  * Interface with checkout--helper\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 117ed42370..e05e6ceff2 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -471,17 +471,22 @@ static int check_updates(struct unpack_trees_options *o,\n \t\tstruct cache_entry *ce = index->cache[i];\n \n \t\tif (ce->ce_flags & CE_UPDATE) {\n+\t\t\tsize_t last_pc_queue_size = pc_queue_size();\n+\n \t\t\tif (ce->ce_flags & CE_WT_REMOVE)\n \t\t\t\tBUG(\"both update and delete flags are set on %s\",\n \t\t\t\t    ce->name);\n-\t\t\tdisplay_progress(progress, ++cnt);\n \t\t\tce->ce_flags &= ~CE_UPDATE;\n \t\t\terrs |= checkout_entry(ce, &state, NULL, NULL);\n+\n+\t\t\tif (last_pc_queue_size == pc_queue_size())\n+\t\t\t\tdisplay_progress(progress, ++cnt);\n \t\t}\n \t}\n-\tstop_progress(&progress);\n \tif (pc_workers > 1)\n-\t\terrs |= run_parallel_checkout(&state, pc_workers, pc_threshold);\n+\t\terrs |= run_parallel_checkout(&state, pc_workers, pc_threshold,\n+\t\t\t\t\t      progress, &cnt);\n+\tstop_progress(&progress);\n \terrs |= finish_delayed_checkout(&state, NULL);\n \tgit_attr_set_direction(GIT_ATTR_CHECKIN);\n \n-- \n2.28.0\n\n"},{"id":"409155","messageId":"960116579abc0b5a805a1af2565fb6060afaa68c.1604521276.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 13/19] make_transient_cache_entry(): optionally alloc from mem_pool","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:12Z","receivedAt":"2020-11-04T20:34:18Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Allow make_transient_cache_entry() to optionally receive a mem_pool\nstruct in which it should allocate the entry. This will be used in the\nfollowing patch, to store some transient entries which should persist\nuntil parallel checkout finishes.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n builtin/checkout--helper.c |  2 +-\n builtin/checkout.c         |  2 +-\n builtin/difftool.c         |  2 +-\n cache.h                    | 10 +++++-----\n read-cache.c               | 12 ++++++++----\n unpack-trees.c             |  2 +-\n 6 files changed, 17 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/checkout--helper.c b/builtin/checkout--helper.c\nindex a61ed76f0d..5d6f3e71d0 100644\n--- a/builtin/checkout--helper.c\n+++ b/builtin/checkout--helper.c\n@@ -38,7 +38,7 @@ static void packet_to_pc_item(char *line, int len,\n \t}\n \n \tmemset(pc_item, 0, sizeof(*pc_item));\n-\tpc_item->ce = make_empty_transient_cache_entry(fixed_portion->name_len);\n+\tpc_item->ce = make_empty_transient_cache_entry(fixed_portion->name_len, NULL);\n \tpc_item->ce->ce_namelen = fixed_portion->name_len;\n \tpc_item->ce->ce_mode = fixed_portion->ce_mode;\n \tmemcpy(pc_item->ce->name, variant, pc_item->ce->ce_namelen);\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b18b9d6f3c..c0bf5e6711 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -291,7 +291,7 @@ static int checkout_merged(int pos, const struct checkout *state, int *nr_checko\n \tif (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid))\n \t\tdie(_(\"Unable to add merge result for '%s'\"), path);\n \tfree(result_buf.ptr);\n-\tce = make_transient_cache_entry(mode, &oid, path, 2);\n+\tce = make_transient_cache_entry(mode, &oid, path, 2, NULL);\n \tif (!ce)\n \t\tdie(_(\"make_cache_entry failed for path '%s'\"), path);\n \tstatus = checkout_entry(ce, state, NULL, nr_checkouts);\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex dfa22b67eb..5e7a57c8c2 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -323,7 +323,7 @@ static int checkout_path(unsigned mode, struct object_id *oid,\n \tstruct cache_entry *ce;\n \tint ret;\n \n-\tce = make_transient_cache_entry(mode, oid, path, 0);\n+\tce = make_transient_cache_entry(mode, oid, path, 0, NULL);\n \tret = checkout_entry(ce, state, NULL, NULL);\n \n \tdiscard_cache_entry(ce);\ndiff --git a/cache.h b/cache.h\nindex ccfeb9ba2b..b5074b2cb2 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -355,16 +355,16 @@ struct cache_entry *make_empty_cache_entry(struct index_state *istate,\n \t\t\t\t\t   size_t name_len);\n \n /*\n- * Create a cache_entry that is not intended to be added to an index.\n- * Caller is responsible for discarding the cache_entry\n- * with `discard_cache_entry`.\n+ * Create a cache_entry that is not intended to be added to an index. If mp is\n+ * not NULL, the entry is allocated within the given memory pool. Caller is\n+ * responsible for discarding the cache_entry with `discard_cache_entry`.\n  */\n struct cache_entry *make_transient_cache_entry(unsigned int mode,\n \t\t\t\t\t       const struct object_id *oid,\n \t\t\t\t\t       const char *path,\n-\t\t\t\t\t       int stage);\n+\t\t\t\t\t       int stage, struct mem_pool *mp);\n \n-struct cache_entry *make_empty_transient_cache_entry(size_t name_len);\n+struct cache_entry *make_empty_transient_cache_entry(size_t len, struct mem_pool *mp);\n \n /*\n  * Discard cache entry.\ndiff --git a/read-cache.c b/read-cache.c\nindex ecf6f68994..f9bac760af 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -813,8 +813,10 @@ struct cache_entry *make_empty_cache_entry(struct index_state *istate, size_t le\n \treturn mem_pool__ce_calloc(find_mem_pool(istate), len);\n }\n \n-struct cache_entry *make_empty_transient_cache_entry(size_t len)\n+struct cache_entry *make_empty_transient_cache_entry(size_t len, struct mem_pool *mp)\n {\n+\tif (mp)\n+\t\treturn mem_pool__ce_calloc(mp, len);\n \treturn xcalloc(1, cache_entry_size(len));\n }\n \n@@ -848,8 +850,10 @@ struct cache_entry *make_cache_entry(struct index_state *istate,\n \treturn ret;\n }\n \n-struct cache_entry *make_transient_cache_entry(unsigned int mode, const struct object_id *oid,\n-\t\t\t\t\t       const char *path, int stage)\n+struct cache_entry *make_transient_cache_entry(unsigned int mode,\n+\t\t\t\t\t       const struct object_id *oid,\n+\t\t\t\t\t       const char *path, int stage,\n+\t\t\t\t\t       struct mem_pool *mp)\n {\n \tstruct cache_entry *ce;\n \tint len;\n@@ -860,7 +864,7 @@ struct cache_entry *make_transient_cache_entry(unsigned int mode, const struct o\n \t}\n \n \tlen = strlen(path);\n-\tce = make_empty_transient_cache_entry(len);\n+\tce = make_empty_transient_cache_entry(len, mp);\n \n \toidcpy(&ce->oid, oid);\n \tmemcpy(ce->name, path, len);\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex e05e6ceff2..dcb40dc8fa 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -1031,7 +1031,7 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info,\n \tsize_t len = traverse_path_len(info, tree_entry_len(n));\n \tstruct cache_entry *ce =\n \t\tis_transient ?\n-\t\tmake_empty_transient_cache_entry(len) :\n+\t\tmake_empty_transient_cache_entry(len, NULL) :\n \t\tmake_empty_cache_entry(istate, len);\n \n \tce->ce_mode = create_ce_mode(n->mode);\n-- \n2.28.0\n\n"},{"id":"409156","messageId":"a844451e580b6eb2c7c90b00e6a958855a1f85b0.1604521276.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 15/19] checkout-index: add parallel checkout support","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:14Z","receivedAt":"2020-11-04T20:34:28Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n builtin/checkout-index.c | 21 ++++++++++++++++++---\n 1 file changed, 18 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/checkout-index.c b/builtin/checkout-index.c\nindex 9276ed0258..9a2e255f58 100644\n--- a/builtin/checkout-index.c\n+++ b/builtin/checkout-index.c\n@@ -12,6 +12,7 @@\n #include \"cache-tree.h\"\n #include \"parse-options.h\"\n #include \"entry.h\"\n+#include \"parallel-checkout.h\"\n \n #define CHECKOUT_ALL 4\n static int nul_term_line;\n@@ -169,6 +170,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)\n \tint force = 0, quiet = 0, not_new = 0;\n \tint index_opt = 0;\n \tint err = 0;\n+\tint pc_workers, pc_threshold;\n \tstruct option builtin_checkout_index_options[] = {\n \t\tOPT_BOOL('a', \"all\", &all,\n \t\t\tN_(\"check out all files in the index\")),\n@@ -223,6 +225,14 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)\n \t\thold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);\n \t}\n \n+\tif (!to_tempfile)\n+\t\tget_parallel_checkout_configs(&pc_workers, &pc_threshold);\n+\telse\n+\t\tpc_workers = 1;\n+\n+\tif (pc_workers > 1)\n+\t\tinit_parallel_checkout();\n+\n \t/* Check out named files first */\n \tfor (i = 0; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n@@ -262,12 +272,17 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_release(&buf);\n \t}\n \n-\tif (err)\n-\t\treturn 1;\n-\n \tif (all)\n \t\tcheckout_all(prefix, prefix_length);\n \n+\tif (pc_workers > 1) {\n+\t\terr |= run_parallel_checkout(&state, pc_workers, pc_threshold,\n+\t\t\t\t\t     NULL, NULL);\n+\t}\n+\n+\tif (err)\n+\t\treturn 1;\n+\n \tif (is_lock_file_locked(&lock_file) &&\n \t    write_locked_index(&the_index, &lock_file, COMMIT_LOCK))\n \t\tdie(\"Unable to write new index file\");\n-- \n2.28.0\n\n"},{"id":"409157","messageId":"c8a2974f814a2d0709c4628933fc240e43a1bfb6.1604521276.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 17/19] parallel-checkout: add tests related to clone collisions","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:16Z","receivedAt":"2020-11-04T20:34:33Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Add tests to confirm that path collisions are properly reported during a\nclone operation using parallel-checkout.\n\nOriginal-patch-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n t/lib-parallel-checkout.sh              |  4 +-\n t/t2081-parallel-checkout-collisions.sh | 98 +++++++++++++++++++++++++\n 2 files changed, 100 insertions(+), 2 deletions(-)\n create mode 100755 t/t2081-parallel-checkout-collisions.sh\n\ndiff --git a/t/lib-parallel-checkout.sh b/t/lib-parallel-checkout.sh\nindex 4dad9043fb..e62a433eb1 100644\n--- a/t/lib-parallel-checkout.sh\n+++ b/t/lib-parallel-checkout.sh\n@@ -18,7 +18,7 @@ git_pc()\n \t\t-c checkout.workers=$workers \\\n \t\t-c checkout.thresholdForParallelism=$threshold \\\n \t\t-c advice.detachedHead=0 \\\n-\t\t\"$@\" &&\n+\t\t\"$@\" 2>&8 &&\n \n \t# Check that the expected number of workers has been used. Note that it\n \t# can be different from the requested number in two cases: when the\n@@ -28,7 +28,7 @@ git_pc()\n \tlocal workers_in_trace=$(grep \"child_start\\[..*\\] git checkout--helper\" trace | wc -l) &&\n \ttest $workers_in_trace -eq $expected_workers &&\n \trm -f trace\n-}\n+} 8>&2 2>&4\n \n # Verify that both the working tree and the index were created correctly\n verify_checkout()\ndiff --git a/t/t2081-parallel-checkout-collisions.sh b/t/t2081-parallel-checkout-collisions.sh\nnew file mode 100755\nindex 0000000000..5cab2dcd2c\n--- /dev/null\n+++ b/t/t2081-parallel-checkout-collisions.sh\n@@ -0,0 +1,98 @@\n+#!/bin/sh\n+\n+test_description='parallel-checkout collisions\n+\n+When there are path collisions during a clone, Git should report a warning\n+listing all of the colliding entries. The sequential code detects a collision\n+by calling lstat() before trying to open(O_CREAT) the file. Then, to find the\n+colliding pair of an item k, it searches cache_entry[0, k-1].\n+\n+This is not sufficient in parallel checkout since:\n+\n+- A colliding file may be created between the lstat() and open() calls;\n+- A colliding entry might appear in the second half of the cache_entry array.\n+\n+The tests in this file make sure that the collision detection code is extended\n+for parallel checkout.\n+'\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-parallel-checkout.sh\"\n+\n+TEST_ROOT=\"$PWD\"\n+\n+test_expect_success CASE_INSENSITIVE_FS 'setup' '\n+\tfile_x_hex=$(git hash-object -w --stdin </dev/null) &&\n+\tfile_x_oct=$(echo $file_x_hex | hex2oct) &&\n+\n+\tattr_hex=$(echo \"file_x filter=logger\" | git hash-object -w --stdin) &&\n+\tattr_oct=$(echo $attr_hex | hex2oct) &&\n+\n+\tprintf \"100644 FILE_X\\0${file_x_oct}\" >tree &&\n+\tprintf \"100644 FILE_x\\0${file_x_oct}\" >>tree &&\n+\tprintf \"100644 file_X\\0${file_x_oct}\" >>tree &&\n+\tprintf \"100644 file_x\\0${file_x_oct}\" >>tree &&\n+\tprintf \"100644 .gitattributes\\0${attr_oct}\" >>tree &&\n+\n+\ttree_hex=$(git hash-object -w -t tree --stdin <tree) &&\n+\tcommit_hex=$(git commit-tree -m collisions $tree_hex) &&\n+\tgit update-ref refs/heads/collisions $commit_hex &&\n+\n+\twrite_script \"$TEST_ROOT\"/logger_script <<-\\EOF\n+\techo \"$@\" >>filter.log\n+\tEOF\n+'\n+\n+for mode in parallel sequential-fallback\n+do\n+\n+\tcase $mode in\n+\tparallel)\t\tworkers=2 threshold=0 expected_workers=2 ;;\n+\tsequential-fallback)\tworkers=2 threshold=100 expected_workers=0 ;;\n+\tesac\n+\n+\ttest_expect_success CASE_INSENSITIVE_FS \"collision detection on $mode clone\" '\n+\t\tgit_pc $workers $threshold $expected_workers \\\n+\t\t\tclone --branch=collisions . $mode 2>$mode.stderr &&\n+\n+\t\tgrep FILE_X $mode.stderr &&\n+\t\tgrep FILE_x $mode.stderr &&\n+\t\tgrep file_X $mode.stderr &&\n+\t\tgrep file_x $mode.stderr &&\n+\t\ttest_i18ngrep \"the following paths have collided\" $mode.stderr\n+\t'\n+\n+\t# The following test ensures that the collision detection code is\n+\t# correctly looking for colliding peers in the second half of the\n+\t# cache_entry array. This is done by defining a smudge command for the\n+\t# *last* array entry, which makes it non-eligible for parallel-checkout.\n+\t# The last entry is then checked out *before* any worker is spawned,\n+\t# making it succeed and the workers' entries collide.\n+\t#\n+\t# Note: this test don't work on Windows because, on this system,\n+\t# collision detection uses strcmp() when core.ignoreCase=false. And we\n+\t# have to set core.ignoreCase=false so that only 'file_x' matches the\n+\t# pattern of the filter attribute. But it works on OSX, where collision\n+\t# detection uses inode.\n+\t#\n+\ttest_expect_success CASE_INSENSITIVE_FS,!MINGW,!CYGWIN \"collision detection on $mode clone w/ filter\" '\n+\t\tgit_pc $workers $threshold $expected_workers \\\n+\t\t\t-c core.ignoreCase=false \\\n+\t\t\t-c filter.logger.smudge=\"\\\"$TEST_ROOT/logger_script\\\" %f\" \\\n+\t\t\tclone --branch=collisions . ${mode}_with_filter \\\n+\t\t\t2>${mode}_with_filter.stderr &&\n+\n+\t\tgrep FILE_X ${mode}_with_filter.stderr &&\n+\t\tgrep FILE_x ${mode}_with_filter.stderr &&\n+\t\tgrep file_X ${mode}_with_filter.stderr &&\n+\t\tgrep file_x ${mode}_with_filter.stderr &&\n+\t\ttest_i18ngrep \"the following paths have collided\" ${mode}_with_filter.stderr &&\n+\n+\t\t# Make sure only \"file_x\" was filtered\n+\t\ttest_path_is_file ${mode}_with_filter/filter.log &&\n+\t\techo file_x >expected.filter.log &&\n+\t\ttest_cmp ${mode}_with_filter/filter.log expected.filter.log\n+\t'\n+done\n+\n+test_done\n-- \n2.28.0\n\n"},{"id":"409158","messageId":"86fccd57d50dc97203f677a420a553d3f776fee0.1604521276.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 18/19] parallel-checkout: add tests related to .gitattributes","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:17Z","receivedAt":"2020-11-04T20:34:39Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Add tests to confirm that `struct conv_attrs` data is correctly passed\nfrom the main process to the workers, and that they properly smudge\nfiles before writing to the working tree. Also check that\nnon-parallel-eligible entries, such as regular files that require\nexternal filters, are correctly smudge and written when\nparallel-checkout is enabled.\n\nNote: to avoid repeating code, some helper functions are extracted from\nt0028 into a common lib file.\n\nOriginal-patch-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n t/lib-encoding.sh                       |  25 ++++\n t/t0028-working-tree-encoding.sh        |  25 +---\n t/t2082-parallel-checkout-attributes.sh | 174 ++++++++++++++++++++++++\n 3 files changed, 200 insertions(+), 24 deletions(-)\n create mode 100644 t/lib-encoding.sh\n create mode 100755 t/t2082-parallel-checkout-attributes.sh\n\ndiff --git a/t/lib-encoding.sh b/t/lib-encoding.sh\nnew file mode 100644\nindex 0000000000..c52ffbbed5\n--- /dev/null\n+++ b/t/lib-encoding.sh\n@@ -0,0 +1,25 @@\n+# Encoding helpers used by t0028 and t2082\n+\n+test_lazy_prereq NO_UTF16_BOM '\n+\ttest $(printf abc | iconv -f UTF-8 -t UTF-16 | wc -c) = 6\n+'\n+\n+test_lazy_prereq NO_UTF32_BOM '\n+\ttest $(printf abc | iconv -f UTF-8 -t UTF-32 | wc -c) = 12\n+'\n+\n+write_utf16 () {\n+\tif test_have_prereq NO_UTF16_BOM\n+\tthen\n+\t\tprintf '\\376\\377'\n+\tfi &&\n+\ticonv -f UTF-8 -t UTF-16\n+}\n+\n+write_utf32 () {\n+\tif test_have_prereq NO_UTF32_BOM\n+\tthen\n+\t\tprintf '\\0\\0\\376\\377'\n+\tfi &&\n+\ticonv -f UTF-8 -t UTF-32\n+}\ndiff --git a/t/t0028-working-tree-encoding.sh b/t/t0028-working-tree-encoding.sh\nindex bfc4fb9af5..4fffc3a639 100755\n--- a/t/t0028-working-tree-encoding.sh\n+++ b/t/t0028-working-tree-encoding.sh\n@@ -3,33 +3,10 @@\n test_description='working-tree-encoding conversion via gitattributes'\n \n . ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-encoding.sh\"\n \n GIT_TRACE_WORKING_TREE_ENCODING=1 && export GIT_TRACE_WORKING_TREE_ENCODING\n \n-test_lazy_prereq NO_UTF16_BOM '\n-\ttest $(printf abc | iconv -f UTF-8 -t UTF-16 | wc -c) = 6\n-'\n-\n-test_lazy_prereq NO_UTF32_BOM '\n-\ttest $(printf abc | iconv -f UTF-8 -t UTF-32 | wc -c) = 12\n-'\n-\n-write_utf16 () {\n-\tif test_have_prereq NO_UTF16_BOM\n-\tthen\n-\t\tprintf '\\376\\377'\n-\tfi &&\n-\ticonv -f UTF-8 -t UTF-16\n-}\n-\n-write_utf32 () {\n-\tif test_have_prereq NO_UTF32_BOM\n-\tthen\n-\t\tprintf '\\0\\0\\376\\377'\n-\tfi &&\n-\ticonv -f UTF-8 -t UTF-32\n-}\n-\n test_expect_success 'setup test files' '\n \tgit config core.eol lf &&\n \ndiff --git a/t/t2082-parallel-checkout-attributes.sh b/t/t2082-parallel-checkout-attributes.sh\nnew file mode 100755\nindex 0000000000..6800574588\n--- /dev/null\n+++ b/t/t2082-parallel-checkout-attributes.sh\n@@ -0,0 +1,174 @@\n+#!/bin/sh\n+\n+test_description='parallel-checkout: attributes\n+\n+Verify that parallel-checkout correctly creates files that require\n+conversions, as specified in .gitattributes. The main point here is\n+to check that the conv_attr data is correctly sent to the workers\n+and that it contains sufficient information to smudge files\n+properly (without access to the index or attribute stack).\n+'\n+\n+TEST_NO_CREATE_REPO=1\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-parallel-checkout.sh\"\n+. \"$TEST_DIRECTORY/lib-encoding.sh\"\n+\n+test_expect_success 'parallel-checkout with ident' '\n+\tgit init ident &&\n+\t(\n+\t\tcd ident &&\n+\t\techo \"A ident\" >.gitattributes &&\n+\t\techo \"\\$Id\\$\" >A &&\n+\t\techo \"\\$Id\\$\" >B &&\n+\t\tgit add -A &&\n+\t\tgit commit -m id &&\n+\n+\t\trm A B &&\n+\t\tgit_pc 2 0 2 reset --hard &&\n+\t\thexsz=$(test_oid hexsz) &&\n+\t\tgrep -E \"\\\\\\$Id: [0-9a-f]{$hexsz} \\\\\\$\" A &&\n+\t\tgrep \"\\\\\\$Id\\\\\\$\" B\n+\t)\n+'\n+\n+test_expect_success 'parallel-checkout with re-encoding' '\n+\tgit init encoding &&\n+\t(\n+\t\tcd encoding &&\n+\t\techo text >utf8-text &&\n+\t\tcat utf8-text | write_utf16 >utf16-text &&\n+\n+\t\techo \"A working-tree-encoding=UTF-16\" >.gitattributes &&\n+\t\tcp utf16-text A &&\n+\t\tcp utf16-text B &&\n+\t\tgit add A B .gitattributes &&\n+\t\tgit commit -m encoding &&\n+\n+\t\t# Check that A (and only A) is stored in UTF-8\n+\t\tgit cat-file -p :A >A.internal &&\n+\t\ttest_cmp_bin utf8-text A.internal &&\n+\t\tgit cat-file -p :B >B.internal &&\n+\t\ttest_cmp_bin utf16-text B.internal &&\n+\n+\t\t# Check that A is re-encoded during checkout\n+\t\trm A B &&\n+\t\tgit_pc 2 0 2 checkout A B &&\n+\t\ttest_cmp_bin utf16-text A\n+\t)\n+'\n+\n+test_expect_success 'parallel-checkout with eol conversions' '\n+\tgit init eol &&\n+\t(\n+\t\tcd eol &&\n+\t\tgit config core.autocrlf false &&\n+\t\tprintf \"multi\\r\\nline\\r\\ntext\" >crlf-text &&\n+\t\tprintf \"multi\\nline\\ntext\" >lf-text &&\n+\n+\t\techo \"A text eol=crlf\" >.gitattributes &&\n+\t\techo \"B -text\" >>.gitattributes &&\n+\t\tcp crlf-text A &&\n+\t\tcp crlf-text B &&\n+\t\tgit add A B .gitattributes &&\n+\t\tgit commit -m eol &&\n+\n+\t\t# Check that A (and only A) is stored with LF format\n+\t\tgit cat-file -p :A >A.internal &&\n+\t\ttest_cmp_bin lf-text A.internal &&\n+\t\tgit cat-file -p :B >B.internal &&\n+\t\ttest_cmp_bin crlf-text B.internal &&\n+\n+\t\t# Check that A is converted to CRLF during checkout\n+\t\trm A B &&\n+\t\tgit_pc 2 0 2 checkout A B &&\n+\t\ttest_cmp_bin crlf-text A\n+\t)\n+'\n+\n+test_cmp_str()\n+{\n+\techo \"$1\" >tmp &&\n+\ttest_cmp tmp \"$2\"\n+}\n+\n+# Entries that require an external filter are not eligible for parallel\n+# checkout. Check that both the parallel-eligible and non-eligible entries are\n+# properly writen in a single checkout process.\n+#\n+test_expect_success 'parallel-checkout and external filter' '\n+\tgit init filter &&\n+\t(\n+\t\tcd filter &&\n+\t\tgit config filter.x2y.clean \"tr x y\" &&\n+\t\tgit config filter.x2y.smudge \"tr y x\" &&\n+\t\tgit config filter.x2y.required true &&\n+\n+\t\techo \"A filter=x2y\" >.gitattributes &&\n+\t\techo x >A &&\n+\t\techo x >B &&\n+\t\techo x >C &&\n+\t\tgit add -A &&\n+\t\tgit commit -m filter &&\n+\n+\t\t# Check that A (and only A) was cleaned\n+\t\tgit cat-file -p :A >A.internal &&\n+\t\ttest_cmp_str y A.internal &&\n+\t\tgit cat-file -p :B >B.internal &&\n+\t\ttest_cmp_str x B.internal &&\n+\t\tgit cat-file -p :C >C.internal &&\n+\t\ttest_cmp_str x C.internal &&\n+\n+\t\trm A B C *.internal &&\n+\t\tgit_pc 2 0 2 checkout A B C &&\n+\t\ttest_cmp_str x A &&\n+\t\ttest_cmp_str x B &&\n+\t\ttest_cmp_str x C\n+\t)\n+'\n+\n+# The delayed queue is independent from the parallel queue, and they should be\n+# able to work together in the same checkout process.\n+#\n+test_expect_success PERL 'parallel-checkout and delayed checkout' '\n+\twrite_script rot13-filter.pl \"$PERL_PATH\" \\\n+\t\t<\"$TEST_DIRECTORY\"/t0021/rot13-filter.pl &&\n+\ttest_config_global filter.delay.process \\\n+\t\t\"\\\"$(pwd)/rot13-filter.pl\\\" \\\"$(pwd)/delayed.log\\\" clean smudge delay\" &&\n+\ttest_config_global filter.delay.required true &&\n+\n+\techo \"a b c\" >delay-content &&\n+\techo \"n o p\" >delay-rot13-content &&\n+\n+\tgit init delayed &&\n+\t(\n+\t\tcd delayed &&\n+\t\techo \"*.a filter=delay\" >.gitattributes &&\n+\t\tcp ../delay-content test-delay10.a &&\n+\t\tcp ../delay-content test-delay11.a &&\n+\t\techo parallel >parallel1.b &&\n+\t\techo parallel >parallel2.b &&\n+\t\tgit add -A &&\n+\t\tgit commit -m delayed &&\n+\n+\t\t# Check that the stored data was cleaned\n+\t\tgit cat-file -p :test-delay10.a > delay10.internal &&\n+\t\ttest_cmp delay10.internal ../delay-rot13-content &&\n+\t\tgit cat-file -p :test-delay11.a > delay11.internal &&\n+\t\ttest_cmp delay11.internal ../delay-rot13-content &&\n+\t\trm *.internal &&\n+\n+\t\trm *.a *.b\n+\t) &&\n+\n+\tgit_pc 2 0 2 -C delayed checkout -f &&\n+\tverify_checkout delayed &&\n+\n+\t# Check that the *.a files got to the delay queue and were filtered\n+\tgrep \"smudge test-delay10.a .* \\[DELAYED\\]\" delayed.log &&\n+\tgrep \"smudge test-delay11.a .* \\[DELAYED\\]\" delayed.log &&\n+\ttest_cmp delayed/test-delay10.a delay-content &&\n+\ttest_cmp delayed/test-delay11.a delay-content\n+'\n+\n+test_done\n-- \n2.28.0\n\n"},{"id":"409159","messageId":"7f3e23cc386292e24c80f1b38a109c13632fb03f.1604521276.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 19/19] ci: run test round with parallel-checkout enabled","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:18Z","receivedAt":"2020-11-04T20:34:40Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"We already have tests for the basic parallel-checkout operations. But\nthis code can also run in other commands, such as git-read-tree and\ngit-sparse-checkout, which are currently not tested with multiple\nworkers. To promote a wider test coverage without duplicating tests:\n\n1. Add the GIT_TEST_CHECKOUT_WORKERS environment variable, to optionally\n   force parallel-checkout execution during the whole test suite.\n\n2. Include this variable in the second test round of the linux-gcc job\n   of our ci scripts. This round runs `make test` again with some\n   optional GIT_TEST_* variables enabled, so there is no additional\n   overhead in exercising the parallel-checkout code here.\n\nNote: the specific parallel-checkout tests t208* cannot be used in\ncombination with GIT_TEST_CHECKOUT_WORKERS as they need to set and check\nthe number of workers by themselves. So skip those tests when this flag\nis set.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n ci/run-build-and-tests.sh  |  1 +\n parallel-checkout.c        | 14 ++++++++++++++\n t/README                   |  4 ++++\n t/lib-parallel-checkout.sh |  6 ++++++\n 4 files changed, 25 insertions(+)\n\ndiff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh\nindex 6c27b886b8..aa32ddc361 100755\n--- a/ci/run-build-and-tests.sh\n+++ b/ci/run-build-and-tests.sh\n@@ -22,6 +22,7 @@ linux-gcc)\n \texport GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=1\n \texport GIT_TEST_MULTI_PACK_INDEX=1\n \texport GIT_TEST_ADD_I_USE_BUILTIN=1\n+\texport GIT_TEST_CHECKOUT_WORKERS=2\n \tmake test\n \t;;\n linux-clang)\ndiff --git a/parallel-checkout.c b/parallel-checkout.c\nindex 72ac93d541..33f36fb1bf 100644\n--- a/parallel-checkout.c\n+++ b/parallel-checkout.c\n@@ -32,6 +32,20 @@ enum pc_status parallel_checkout_status(void)\n \n void get_parallel_checkout_configs(int *num_workers, int *threshold)\n {\n+\tchar *env_workers = getenv(\"GIT_TEST_CHECKOUT_WORKERS\");\n+\n+\tif (env_workers && *env_workers) {\n+\t\tif (strtol_i(env_workers, 10, num_workers)) {\n+\t\t\tdie(\"invalid value for GIT_TEST_CHECKOUT_WORKERS: '%s'\",\n+\t\t\t    env_workers);\n+\t\t}\n+\t\tif (*num_workers < 1)\n+\t\t\t*num_workers = online_cpus();\n+\n+\t\t*threshold = 0;\n+\t\treturn;\n+\t}\n+\n \tif (git_config_get_int(\"checkout.workers\", num_workers))\n \t\t*num_workers = 1;\n \telse if (*num_workers < 1)\ndiff --git a/t/README b/t/README\nindex 2adaf7c2d2..cd1b15c55a 100644\n--- a/t/README\n+++ b/t/README\n@@ -425,6 +425,10 @@ GIT_TEST_DEFAULT_HASH=<hash-algo> specifies which hash algorithm to\n use in the test scripts. Recognized values for <hash-algo> are \"sha1\"\n and \"sha256\".\n \n+GIT_TEST_CHECKOUT_WORKERS=<n> overrides the 'checkout.workers' setting\n+to <n> and 'checkout.thresholdForParallelism' to 0, forcing the\n+execution of the parallel-checkout code.\n+\n Naming Tests\n ------------\n \ndiff --git a/t/lib-parallel-checkout.sh b/t/lib-parallel-checkout.sh\nindex e62a433eb1..7b454da375 100644\n--- a/t/lib-parallel-checkout.sh\n+++ b/t/lib-parallel-checkout.sh\n@@ -1,5 +1,11 @@\n # Helpers for t208* tests\n \n+if ! test -z \"$GIT_TEST_CHECKOUT_WORKERS\"\n+then\n+\tskip_all=\"skipping test, GIT_TEST_CHECKOUT_WORKERS is set\"\n+\ttest_done\n+fi\n+\n # Runs `git -c checkout.workers=$1 -c checkout.thesholdForParallelism=$2 ${@:4}`\n # and checks that the number of workers spawned is equal to $3.\n #\n-- \n2.28.0\n\n"},{"id":"409160","messageId":"3733857ffa47c4d16fba91c2ca02ad0cce278dd7.1604521276.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 16/19] parallel-checkout: add tests for basic operations","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:15Z","receivedAt":"2020-11-04T20:34:40Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Add tests to populate the working tree during clone and checkout using\nthe sequential and parallel modes, to confirm that they produce\nidentical results. Also test basic checkout mechanics, such as checking\nfor symlinks in the leading directories and the abidance to --force.\n\nNote: some helper functions are added to a common lib file which is only\nincluded by t2080 for now. But it will also be used by other\nparallel-checkout tests in the following patches.\n\nOriginal-patch-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n t/lib-parallel-checkout.sh          |  40 +++++++\n t/t2080-parallel-checkout-basics.sh | 170 ++++++++++++++++++++++++++++\n 2 files changed, 210 insertions(+)\n create mode 100644 t/lib-parallel-checkout.sh\n create mode 100755 t/t2080-parallel-checkout-basics.sh\n\ndiff --git a/t/lib-parallel-checkout.sh b/t/lib-parallel-checkout.sh\nnew file mode 100644\nindex 0000000000..4dad9043fb\n--- /dev/null\n+++ b/t/lib-parallel-checkout.sh\n@@ -0,0 +1,40 @@\n+# Helpers for t208* tests\n+\n+# Runs `git -c checkout.workers=$1 -c checkout.thesholdForParallelism=$2 ${@:4}`\n+# and checks that the number of workers spawned is equal to $3.\n+#\n+git_pc()\n+{\n+\tif test $# -lt 4\n+\tthen\n+\t\tBUG \"too few arguments to git_pc()\"\n+\tfi &&\n+\n+\tworkers=$1 threshold=$2 expected_workers=$3 &&\n+\tshift 3 &&\n+\n+\trm -f trace &&\n+\tGIT_TRACE2=\"$(pwd)/trace\" git \\\n+\t\t-c checkout.workers=$workers \\\n+\t\t-c checkout.thresholdForParallelism=$threshold \\\n+\t\t-c advice.detachedHead=0 \\\n+\t\t\"$@\" &&\n+\n+\t# Check that the expected number of workers has been used. Note that it\n+\t# can be different from the requested number in two cases: when the\n+\t# threshold is not reached; and when there are not enough\n+\t# parallel-eligible entries for all workers.\n+\t#\n+\tlocal workers_in_trace=$(grep \"child_start\\[..*\\] git checkout--helper\" trace | wc -l) &&\n+\ttest $workers_in_trace -eq $expected_workers &&\n+\trm -f trace\n+}\n+\n+# Verify that both the working tree and the index were created correctly\n+verify_checkout()\n+{\n+\tgit -C \"$1\" diff-index --quiet HEAD -- &&\n+\tgit -C \"$1\" diff-index --quiet --cached HEAD -- &&\n+\tgit -C \"$1\" status --porcelain >\"$1\".status &&\n+\ttest_must_be_empty \"$1\".status\n+}\ndiff --git a/t/t2080-parallel-checkout-basics.sh b/t/t2080-parallel-checkout-basics.sh\nnew file mode 100755\nindex 0000000000..edea88f14f\n--- /dev/null\n+++ b/t/t2080-parallel-checkout-basics.sh\n@@ -0,0 +1,170 @@\n+#!/bin/sh\n+\n+test_description='parallel-checkout basics\n+\n+Ensure that parallel-checkout basically works on clone and checkout, spawning\n+the required number of workers and correctly populating both the index and\n+working tree.\n+'\n+\n+TEST_NO_CREATE_REPO=1\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-parallel-checkout.sh\"\n+\n+# Test parallel-checkout with different operations (creation, deletion,\n+# modification) and entry types. A branch switch from B1 to B2 will contain:\n+#\n+# - a (file):      modified\n+# - e/x (file):    deleted\n+# - b (symlink):   deleted\n+# - b/f (file):    created\n+# - e (symlink):   created\n+# - d (submodule): created\n+#\n+test_expect_success SYMLINKS 'setup repo for checkout with various operations' '\n+\tgit init various &&\n+\t(\n+\t\tcd various &&\n+\t\tgit checkout -b B1 &&\n+\t\techo a>a &&\n+\t\tmkdir e &&\n+\t\techo e/x >e/x &&\n+\t\tln -s e b &&\n+\t\tgit add -A &&\n+\t\tgit commit -m B1 &&\n+\n+\t\tgit checkout -b B2 &&\n+\t\techo modified >a &&\n+\t\trm -rf e &&\n+\t\trm b &&\n+\t\tmkdir b &&\n+\t\techo b/f >b/f &&\n+\t\tln -s b e &&\n+\t\tgit init d &&\n+\t\ttest_commit -C d f &&\n+\t\tgit submodule add ./d &&\n+\t\tgit add -A &&\n+\t\tgit commit -m B2 &&\n+\n+\t\tgit checkout --recurse-submodules B1\n+\t)\n+'\n+\n+test_expect_success SYMLINKS 'sequential checkout' '\n+\tcp -R various various_sequential &&\n+\tgit_pc 1 0 0 -C various_sequential checkout --recurse-submodules B2 &&\n+\tverify_checkout various_sequential\n+'\n+\n+test_expect_success SYMLINKS 'parallel checkout' '\n+\tcp -R various various_parallel &&\n+\tgit_pc 2 0 2 -C various_parallel checkout --recurse-submodules B2 &&\n+\tverify_checkout various_parallel\n+'\n+\n+test_expect_success SYMLINKS 'fallback to sequential checkout (threshold)' '\n+\tcp -R various various_sequential_fallback &&\n+\tgit_pc 2 100 0 -C various_sequential_fallback checkout --recurse-submodules B2 &&\n+\tverify_checkout various_sequential_fallback\n+'\n+\n+test_expect_success SYMLINKS 'parallel checkout on clone' '\n+\tgit -C various checkout --recurse-submodules B2 &&\n+\tgit_pc 2 0 2 clone --recurse-submodules various various_parallel_clone  &&\n+\tverify_checkout various_parallel_clone\n+'\n+\n+test_expect_success SYMLINKS 'fallback to sequential checkout on clone (threshold)' '\n+\tgit -C various checkout --recurse-submodules B2 &&\n+\tgit_pc 2 100 0 clone --recurse-submodules various various_sequential_fallback_clone &&\n+\tverify_checkout various_sequential_fallback_clone\n+'\n+\n+# Just to be paranoid, actually compare the working trees' contents directly.\n+test_expect_success SYMLINKS 'compare the working trees' '\n+\trm -rf various_*/.git &&\n+\trm -rf various_*/d/.git &&\n+\n+\tdiff -r various_sequential various_parallel &&\n+\tdiff -r various_sequential various_sequential_fallback &&\n+\tdiff -r various_sequential various_parallel_clone &&\n+\tdiff -r various_sequential various_sequential_fallback_clone\n+'\n+\n+test_cmp_str()\n+{\n+\techo \"$1\" >tmp &&\n+\ttest_cmp tmp \"$2\"\n+}\n+\n+test_expect_success 'parallel checkout respects --[no]-force' '\n+\tgit init dirty &&\n+\t(\n+\t\tcd dirty &&\n+\t\tmkdir D &&\n+\t\ttest_commit D/F &&\n+\t\ttest_commit F &&\n+\n+\t\techo changed >F.t &&\n+\t\trm -rf D &&\n+\t\techo changed >D &&\n+\n+\t\t# We expect 0 workers because there is nothing to be updated\n+\t\tgit_pc 2 0 0 checkout HEAD &&\n+\t\ttest_path_is_file D &&\n+\t\ttest_cmp_str changed D &&\n+\t\ttest_cmp_str changed F.t &&\n+\n+\t\tgit_pc 2 0 2 checkout --force HEAD &&\n+\t\ttest_path_is_dir D &&\n+\t\ttest_cmp_str D/F D/F.t &&\n+\t\ttest_cmp_str F F.t\n+\t)\n+'\n+\n+test_expect_success SYMLINKS 'parallel checkout checks for symlinks in leading dirs' '\n+\tgit init symlinks &&\n+\t(\n+\t\tcd symlinks &&\n+\t\tmkdir D E &&\n+\n+\t\t# Create two entries in D to have enough work for 2 parallel\n+\t\t# workers\n+\t\ttest_commit D/A &&\n+\t\ttest_commit D/B &&\n+\t\ttest_commit E/C &&\n+\t\trm -rf D &&\n+\t\tln -s E D &&\n+\n+\t\tgit_pc 2 0 2 checkout --force HEAD &&\n+\t\t! test -L D &&\n+\t\ttest_cmp_str D/A D/A.t &&\n+\t\ttest_cmp_str D/B D/B.t\n+\t)\n+'\n+\n+test_expect_success SYMLINKS,CASE_INSENSITIVE_FS 'symlink colliding with leading dir' '\n+\tgit init colliding-symlink &&\n+\t(\n+\t\tcd colliding-symlink &&\n+\t\tfile_hex=$(git hash-object -w --stdin </dev/null) &&\n+\t\tfile_oct=$(echo $file_hex | hex2oct) &&\n+\n+\t\tsym_hex=$(echo \"./D\" | git hash-object -w --stdin) &&\n+\t\tsym_oct=$(echo $sym_hex | hex2oct) &&\n+\n+\t\tprintf \"100644 D/A\\0${file_oct}\" >tree &&\n+\t\tprintf \"100644 E/B\\0${file_oct}\" >>tree &&\n+\t\tprintf \"120000 e\\0${sym_oct}\" >>tree &&\n+\n+\t\ttree_hex=$(git hash-object -w -t tree --stdin <tree) &&\n+\t\tcommit_hex=$(git commit-tree -m collisions $tree_hex) &&\n+\t\tgit update-ref refs/heads/colliding-symlink $commit_hex &&\n+\n+\t\tgit_pc 2 0 2 checkout colliding-symlink &&\n+\t\ttest_path_is_dir D &&\n+\t\ttest_path_is_missing D/B\n+\t)\n+'\n+\n+test_done\n-- \n2.28.0\n\n"},{"id":"409161","messageId":"fb9f2f580c5ae02bbeb8117c85bc24307320aa59.1604521276.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v4 14/19] builtin/checkout.c: complete parallel checkout support","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-11-04T20:33:13Z","receivedAt":"2020-11-04T20:34:45Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"There is one code path in builtin/checkout.c which still doesn't benefit\nfrom parallel checkout because it calls checkout_entry() directly,\ninstead of unpack_trees(). Let's add parallel support for this missing\nspot as well. Note: the transient cache entries allocated in\ncheckout_merged() are now allocated in a mem_pool which is only\ndiscarded after parallel checkout finishes. This is done because the\nentries need to be valid when run_parallel_checkout() is called.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n builtin/checkout.c | 20 ++++++++++++++++----\n 1 file changed, 16 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex c0bf5e6711..ddc4079b85 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -27,6 +27,7 @@\n #include \"wt-status.h\"\n #include \"xdiff-interface.h\"\n #include \"entry.h\"\n+#include \"parallel-checkout.h\"\n \n static const char * const checkout_usage[] = {\n \tN_(\"git checkout [<options>] <branch>\"),\n@@ -230,7 +231,8 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos,\n \t\treturn error(_(\"path '%s' does not have their version\"), ce->name);\n }\n \n-static int checkout_merged(int pos, const struct checkout *state, int *nr_checkouts)\n+static int checkout_merged(int pos, const struct checkout *state,\n+\t\t\t   int *nr_checkouts, struct mem_pool *ce_mem_pool)\n {\n \tstruct cache_entry *ce = active_cache[pos];\n \tconst char *path = ce->name;\n@@ -291,11 +293,10 @@ static int checkout_merged(int pos, const struct checkout *state, int *nr_checko\n \tif (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid))\n \t\tdie(_(\"Unable to add merge result for '%s'\"), path);\n \tfree(result_buf.ptr);\n-\tce = make_transient_cache_entry(mode, &oid, path, 2, NULL);\n+\tce = make_transient_cache_entry(mode, &oid, path, 2, ce_mem_pool);\n \tif (!ce)\n \t\tdie(_(\"make_cache_entry failed for path '%s'\"), path);\n \tstatus = checkout_entry(ce, state, NULL, nr_checkouts);\n-\tdiscard_cache_entry(ce);\n \treturn status;\n }\n \n@@ -359,16 +360,22 @@ static int checkout_worktree(const struct checkout_opts *opts,\n \tint nr_checkouts = 0, nr_unmerged = 0;\n \tint errs = 0;\n \tint pos;\n+\tint pc_workers, pc_threshold;\n+\tstruct mem_pool ce_mem_pool;\n \n \tstate.force = 1;\n \tstate.refresh_cache = 1;\n \tstate.istate = &the_index;\n \n+\tmem_pool_init(&ce_mem_pool, 0);\n+\tget_parallel_checkout_configs(&pc_workers, &pc_threshold);\n \tinit_checkout_metadata(&state.meta, info->refname,\n \t\t\t       info->commit ? &info->commit->object.oid : &info->oid,\n \t\t\t       NULL);\n \n \tenable_delayed_checkout(&state);\n+\tif (pc_workers > 1)\n+\t\tinit_parallel_checkout();\n \tfor (pos = 0; pos < active_nr; pos++) {\n \t\tstruct cache_entry *ce = active_cache[pos];\n \t\tif (ce->ce_flags & CE_MATCHED) {\n@@ -384,10 +391,15 @@ static int checkout_worktree(const struct checkout_opts *opts,\n \t\t\t\t\t\t       &nr_checkouts, opts->overlay_mode);\n \t\t\telse if (opts->merge)\n \t\t\t\terrs |= checkout_merged(pos, &state,\n-\t\t\t\t\t\t\t&nr_unmerged);\n+\t\t\t\t\t\t\t&nr_unmerged,\n+\t\t\t\t\t\t\t&ce_mem_pool);\n \t\t\tpos = skip_same_name(ce, pos) - 1;\n \t\t}\n \t}\n+\tif (pc_workers > 1)\n+\t\terrs |= run_parallel_checkout(&state, pc_workers, pc_threshold,\n+\t\t\t\t\t      NULL, NULL);\n+\tmem_pool_discard(&ce_mem_pool, should_validate_cache_entries());\n \tremove_marked_cache_entries(&the_index, 1);\n \tremove_scheduled_dirs();\n \terrs |= finish_delayed_checkout(&state, &nr_checkouts);\n-- \n2.28.0\n\n"},{"id":"411500","messageId":"CAP8UFD0Z-EU0=9qz8wYt4MSFqqmoDKnMxTbqgUJNVOR+qUZ3rA@mail.gmail.com","threadId":"54525","inReplyTo":"2726f6dc05c319e2baaf76a05d62426e0695fe65.1604521275.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v4 01/19] convert: make convert_attrs() and convert structs public","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2020-12-05T10:40:18Z","receivedAt":"2020-12-05T10:44:23Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Nov 4, 2020 at 9:33 PM Matheus Tavares\n<matheus.bernardino@usp.br> wrote:\n>\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n>\n> Move convert_attrs() declaration from convert.c to convert.h, together\n> with the conv_attrs struct and the crlf_action enum. This function and\n> the data structures will be used outside convert.c in the upcoming\n> parallel checkout implementation. Note that crlf_action is renamed to\n> convert_crlf_action, which is more appropriate for the global namespace.\n\nIt annoys me a bit that some things are called \"conv_*\" and others\n\"convert_*\". Maybe we could standardize everything, but it could be a\nseparate patch series.\n\n> Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n> [matheus.bernardino: squash and reword msg]\n\nNot sure we want the above line, which could actually not be\ncompletely true if you rework the patch before it gets merged.\n\n> Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n\n> --- a/convert.h\n> +++ b/convert.h\n> @@ -63,6 +63,30 @@ struct checkout_metadata {\n>         struct object_id blob;\n>  };\n>\n> +enum convert_crlf_action {\n> +       CRLF_UNDEFINED,\n> +       CRLF_BINARY,\n> +       CRLF_TEXT,\n> +       CRLF_TEXT_INPUT,\n> +       CRLF_TEXT_CRLF,\n> +       CRLF_AUTO,\n> +       CRLF_AUTO_INPUT,\n> +       CRLF_AUTO_CRLF\n> +};\n\nMaybe we should also prepend \"CONVERT_\" to the values?\n"},{"id":"411501","messageId":"CAP8UFD398EON0OpfQ0=bgJhO-0r9f1Z7LQRj+rKLp9pEwUnxHA@mail.gmail.com","threadId":"54525","inReplyTo":"fc03417592c25a111fbf77f9e5b9e5468b9070ae.1604521275.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v4 02/19] convert: add [async_]convert_to_working_tree_ca() variants","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2020-12-05T11:10:41Z","receivedAt":"2020-12-05T11:13:56Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Nov 4, 2020 at 9:33 PM Matheus Tavares\n<matheus.bernardino@usp.br> wrote:\n>\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n>\n> Separate the attribute gathering from the actual conversion by adding\n> _ca() variants of the conversion functions. These variants receive a\n> precomputed 'struct conv_attrs', not relying, thus, on a index state.\n\ns/a/an/\n\n> They will be used in a future patch adding parallel checkout support,\n> for two reasons:\n>\n> - We will already load the conversion attributes in checkout_entry(),\n>   before conversion, to decide whether a path is eligible for parallel\n>   checkout. Therefore, it would be wasteful to load them again later,\n>   for the actual conversion.\n>\n> - The parallel workers will be responsible for reading, converting and\n>   writing blobs to the working tree. They won't have access to the main\n>   process' index state, so they cannot load the attributes. Instead,\n>   they will receive the preloaded ones and call the _ca() variant of\n>   the conversion functions. Furthermore, the attributes machinery is\n>   optimized to handle paths in sequential order, so it's better to leave\n>   it for the main process, anyway.\n\nWell explained.\n\n> Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n> [matheus.bernardino: squash, remove one function definition and reword]\n\n<rant++>I'd rather have \"Reworked-by: Matheus Tavares\n<matheus.bernardino@usp.br>\" or \"Improved-by: Matheus Tavares\n<matheus.bernardino@usp.br>\" than lines such as the above\none.</rant++>\n\n> Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n\n> @@ -1447,7 +1447,7 @@ void convert_to_git_filter_fd(const struct index_state *istate,\n>         ident_to_git(dst->buf, dst->len, dst, ca.ident);\n>  }\n>\n> -static int convert_to_working_tree_internal(const struct index_state *istate,\n> +static int convert_to_working_tree_internal(const struct conv_attrs *ca,\n\nIt is still internal, but for consistency it might be better to add\n\"_ca\" to the name of this function too, as we now pass it a \"ca\"\ninstead of an \"istate\".\n"},{"id":"411503","messageId":"CAP8UFD2v1JrWm7o7yx9FPf0mj+b_PZGJJn9uyBeWR4SLNMoWxg@mail.gmail.com","threadId":"54525","inReplyTo":"8ce20f1031172bd6821392a562a3ecac65c6bc84.1604521275.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v4 03/19] convert: add get_stream_filter_ca() variant","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2020-12-05T11:45:59Z","receivedAt":"2020-12-05T14:03:57Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Nov 4, 2020 at 9:33 PM Matheus Tavares\n<matheus.bernardino@usp.br> wrote:\n\n> -struct stream_filter *get_stream_filter(const struct index_state *istate,\n> -                                       const char *path,\n> -                                       const struct object_id *oid)\n> +struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n> +                                          const struct object_id *oid)\n\nNice that the `path` argument could be removed here.\n"},{"id":"411511","messageId":"CAP8UFD30JOhFxxSU=bVY9YfSHAjVJAKabLEv4x28JotOdj8xRA@mail.gmail.com","threadId":"54525","inReplyTo":"aa1eb461f46d200f36b33c07ebc0a350829ab1f5.1604521275.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v4 04/19] convert: add conv_attrs classification","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2020-12-05T12:07:07Z","receivedAt":"2020-12-05T18:44:19Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Nov 4, 2020 at 9:33 PM Matheus Tavares\n<matheus.bernardino@usp.br> wrote:\n>\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n>\n> Create `enum conv_attrs_classification` to express the different ways\n> that attributes are handled for a blob during checkout.\n\nMicronit: the subject of the patch might want to be a bit more\nexplicit like \"convert: add conv_attrs_classification enum\". Otherwise\nit could make one wonder if it is missing an underscore between\n\"conv_attrs\" and \"classification\".\n\n> This will be used in a later commit when deciding whether to add a file\n> to the parallel or delayed queue during checkout. For now, we can also\n> use it in get_stream_filter_ca() to simplify the function (as the\n> classifying logic is the same).\n>\n> Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n> [matheus.bernardino: use classification in get_stream_filter_ca()]\n\nMaybe \"Co-authored-by: Matheus Tavares <matheus.bernardino@usp.br>\" instead?\n\n> Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n"},{"id":"411535","messageId":"CAHd-oW6RNVP68gXHGsMS9ky+DdcRBbTxJt--TRNszi9v+xOh-Q@mail.gmail.com","threadId":"54525","inReplyTo":"CAP8UFD0Z-EU0=9qz8wYt4MSFqqmoDKnMxTbqgUJNVOR+qUZ3rA@mail.gmail.com","subject":"Re: [PATCH v4 01/19] convert: make convert_attrs() and convert structs public","fromName":"Matheus Tavares Bernardino","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-05T21:53:19Z","receivedAt":"2020-12-05T21:54:38Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"On Sat, Dec 5, 2020 at 7:40 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Wed, Nov 4, 2020 at 9:33 PM Matheus Tavares\n> <matheus.bernardino@usp.br> wrote:\n> >\n> > From: Jeff Hostetler <jeffhost@microsoft.com>\n> >\n> > Move convert_attrs() declaration from convert.c to convert.h, together\n> > with the conv_attrs struct and the crlf_action enum. This function and\n> > the data structures will be used outside convert.c in the upcoming\n> > parallel checkout implementation. Note that crlf_action is renamed to\n> > convert_crlf_action, which is more appropriate for the global namespace.\n>\n> It annoys me a bit that some things are called \"conv_*\" and others\n> \"convert_*\". Maybe we could standardize everything, but it could be a\n> separate patch series.\n>\n> > Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n> > [matheus.bernardino: squash and reword msg]\n>\n> Not sure we want the above line, which could actually not be\n> completely true if you rework the patch before it gets merged.\n\nRight, I'll remove this line from this and the next patches. Thanks.\n\n> > Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n>\n> > --- a/convert.h\n> > +++ b/convert.h\n> > @@ -63,6 +63,30 @@ struct checkout_metadata {\n> >         struct object_id blob;\n> >  };\n> >\n> > +enum convert_crlf_action {\n> > +       CRLF_UNDEFINED,\n> > +       CRLF_BINARY,\n> > +       CRLF_TEXT,\n> > +       CRLF_TEXT_INPUT,\n> > +       CRLF_TEXT_CRLF,\n> > +       CRLF_AUTO,\n> > +       CRLF_AUTO_INPUT,\n> > +       CRLF_AUTO_CRLF\n> > +};\n>\n> Maybe we should also prepend \"CONVERT_\" to the values?\n\nYeah, I also wondered about that. But I wasn't sure if it was worth\nthe change since there are about 52 occurrences of them. Junio later\nmentioned [1] that it might be OK to leave them as-is since the use\nsites will always pass these values to the API functions, which would\nmake it clear that they are from the \"convert_\" family.\n\n[1]: https://lore.kernel.org/git/xmqqd00z397m.fsf@gitster.c.googlers.com/\n"},{"id":"411536","messageId":"CAHd-oW6yvPH24kTedX1LzBvZw3zMi4=QN6iXh5myHkC-bqKcZA@mail.gmail.com","threadId":"54525","inReplyTo":"CAP8UFD30JOhFxxSU=bVY9YfSHAjVJAKabLEv4x28JotOdj8xRA@mail.gmail.com","subject":"Re: [PATCH v4 04/19] convert: add conv_attrs classification","fromName":"Matheus Tavares Bernardino","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-05T22:08:09Z","receivedAt":"2020-12-05T22:09:04Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"On Sat, Dec 5, 2020 at 9:07 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Wed, Nov 4, 2020 at 9:33 PM Matheus Tavares\n> <matheus.bernardino@usp.br> wrote:\n> >\n> > From: Jeff Hostetler <jeffhost@microsoft.com>\n> >\n> > Create `enum conv_attrs_classification` to express the different ways\n> > that attributes are handled for a blob during checkout.\n>\n> Micronit: the subject of the patch might want to be a bit more\n> explicit like \"convert: add conv_attrs_classification enum\". Otherwise\n> it could make one wonder if it is missing an underscore between\n> \"conv_attrs\" and \"classification\".\n\nMakes sense, thanks!\n"},{"id":"411537","messageId":"CAHd-oW5rFL9i-1GMkyNtLs-w2KDhA8gVsyUsy+2ANvatJDeOEw@mail.gmail.com","threadId":"54525","inReplyTo":"CAP8UFD398EON0OpfQ0=bgJhO-0r9f1Z7LQRj+rKLp9pEwUnxHA@mail.gmail.com","subject":"Re: [PATCH v4 02/19] convert: add [async_]convert_to_working_tree_ca() variants","fromName":"Matheus Tavares Bernardino","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-05T22:20:43Z","receivedAt":"2020-12-05T22:21:58Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"On Sat, Dec 5, 2020 at 8:10 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Wed, Nov 4, 2020 at 9:33 PM Matheus Tavares\n> <matheus.bernardino@usp.br> wrote:\n> >\n> > From: Jeff Hostetler <jeffhost@microsoft.com>\n> >\n> > Separate the attribute gathering from the actual conversion by adding\n> > _ca() variants of the conversion functions. These variants receive a\n> > precomputed 'struct conv_attrs', not relying, thus, on a index state.\n>\n> s/a/an/\n\nGood catch, thanks.\n\n> > They will be used in a future patch adding parallel checkout support,\n> > for two reasons:\n> >\n> > - We will already load the conversion attributes in checkout_entry(),\n> >   before conversion, to decide whether a path is eligible for parallel\n> >   checkout. Therefore, it would be wasteful to load them again later,\n> >   for the actual conversion.\n> >\n> > - The parallel workers will be responsible for reading, converting and\n> >   writing blobs to the working tree. They won't have access to the main\n> >   process' index state, so they cannot load the attributes. Instead,\n> >   they will receive the preloaded ones and call the _ca() variant of\n> >   the conversion functions. Furthermore, the attributes machinery is\n> >   optimized to handle paths in sequential order, so it's better to leave\n> >   it for the main process, anyway.\n>\n> Well explained.\n>\n> > Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n> > [matheus.bernardino: squash, remove one function definition and reword]\n>\n> <rant++>I'd rather have \"Reworked-by: Matheus Tavares\n> <matheus.bernardino@usp.br>\" or \"Improved-by: Matheus Tavares\n> <matheus.bernardino@usp.br>\" than lines such as the above\n> one.</rant++>\n>\n> > Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n>\n> > @@ -1447,7 +1447,7 @@ void convert_to_git_filter_fd(const struct index_state *istate,\n> >         ident_to_git(dst->buf, dst->len, dst, ca.ident);\n> >  }\n> >\n> > -static int convert_to_working_tree_internal(const struct index_state *istate,\n> > +static int convert_to_working_tree_internal(const struct conv_attrs *ca,\n>\n> It is still internal, but for consistency it might be better to add\n> \"_ca\" to the name of this function too, as we now pass it a \"ca\"\n> instead of an \"istate\".\n\nRight, will do.\n"},{"id":"411554","messageId":"CAP8UFD1txMfR9KY5U-s+XyROOYGJfrqVRt1DmJn+_7Pa1EXKwQ@mail.gmail.com","threadId":"54525","inReplyTo":"cb3dea224b0776a332c38c6a251406314dfe87ba.1604521275.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v4 05/19] entry: extract a header file for entry.c functions","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2020-12-06T08:31:11Z","receivedAt":"2020-12-06T08:32:51Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Nov 4, 2020 at 9:33 PM Matheus Tavares\n<matheus.bernardino@usp.br> wrote:\n\n> --- a/entry.c\n> +++ b/entry.c\n> @@ -6,6 +6,7 @@\n>  #include \"submodule.h\"\n>  #include \"progress.h\"\n>  #include \"fsmonitor.h\"\n> +#include \"entry.h\"\n>\n>  static void create_directories(const char *path, int path_len,\n>                                const struct checkout *state)\n> @@ -429,14 +430,6 @@ static void mark_colliding_entries(const struct checkout *state,\n>         }\n>  }\n>\n> -/*\n> - * Write the contents from ce out to the working tree.\n> - *\n> - * When topath[] is not NULL, instead of writing to the working tree\n> - * file named by ce, a temporary file is created by this function and\n> - * its name is returned in topath[], which must be able to hold at\n> - * least TEMPORARY_FILENAME_LENGTH bytes long.\n> - */\n\nAbout the above change, the commit message might want to say something like:\n\n\"While at it let's also move a comment related to checkout_entry()\nfrom entry.c to entry.h as it's more useful to describe the function\nthere.\"\n"},{"id":"411555","messageId":"CAP8UFD1w7Sgh5KYApHy034mRA7Ls47Wb9kcecyeQM6vYcH-acA@mail.gmail.com","threadId":"54525","inReplyTo":"a0479d02ffedccb65a167463228445a6d62c9f96.1604521275.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v4 07/19] entry: extract cache_entry update from write_entry()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2020-12-06T08:53:42Z","receivedAt":"2020-12-06T08:55:12Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Nov 4, 2020 at 9:33 PM Matheus Tavares\n<matheus.bernardino@usp.br> wrote:\n>\n> This code will be used by the parallel checkout functions, outside\n> entry.c, so extract it to a public function.\n\nMaybe the title could be a bit more explicit like: \"entry: extract\nupdate_ce_after_write() from write_entry()\"\n\nAnd: s/This code/This new function/\n\n> @@ -371,15 +383,10 @@ static int write_entry(struct cache_entry *ce,\n>\n>  finish:\n>         if (state->refresh_cache) {\n\nHere we check state->refresh_cache ...\n\n> -               assert(state->istate);\n> -               if (!fstat_done)\n> -                       if (lstat(ce->name, &st) < 0)\n> -                               return error_errno(\"unable to stat just-written file %s\",\n> -                                                  ce->name);\n> -               fill_stat_cache_info(state->istate, ce, &st);\n> -               ce->ce_flags |= CE_UPDATE_IN_BASE;\n> -               mark_fsmonitor_invalid(state->istate, ce);\n> -               state->istate->cache_changed |= CE_ENTRY_CHANGED;\n> +               if (!fstat_done && lstat(ce->name, &st) < 0)\n> +                       return error_errno(\"unable to stat just-written file %s\",\n> +                                          ce->name);\n> +               update_ce_after_write(state, ce , &st);\n\n... and inside update_ce_after_write() we check state->refresh_cache\nagain. So the check is now done twice.\n\nNot a big issue though.\n"},{"id":"411556","messageId":"CAP8UFD1-3YxT42K77Rd-XSRsoXYr_RM8Dj_r8-2GQ0noQw58iQ@mail.gmail.com","threadId":"54525","inReplyTo":"5c993cc27f67109828390c7856d6c03d4a2cbb32.1604521275.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v4 08/19] entry: move conv_attrs lookup up to checkout_entry()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2020-12-06T09:35:48Z","receivedAt":"2020-12-06T09:36:50Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Nov 4, 2020 at 9:34 PM Matheus Tavares\n<matheus.bernardino@usp.br> wrote:\n\n> +       if (topath) {\n> +               if (S_ISREG(ce->ce_mode)) {\n> +                       convert_attrs(state->istate, &ca_buf, ce->name);\n> +                       ca = &ca_buf;\n> +               }\n> +               return write_entry(ce, topath, ca, state, 1);\n\nWe pass `ca` here instead of `&ca_buf` because ca is NULL if we are\nnot dealing with a regular file. Ok, I think it's indeed better to\npass NULL in this case.\n\n> @@ -517,9 +526,16 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n>                 return 0;\n>\n>         create_directories(path.buf, path.len, state);\n> +\n>         if (nr_checkouts)\n>                 (*nr_checkouts)++;\n> -       return write_entry(ce, path.buf, state, 0);\n> +\n> +       if (S_ISREG(ce->ce_mode)) {\n> +               convert_attrs(state->istate, &ca_buf, ce->name);\n> +               ca = &ca_buf;\n> +       }\n> +\n> +       return write_entry(ce, path.buf, NULL, state, 0);\n\nI am not sure why NULL is passed here though instead of `ca`.\n\nThe following comment is added in front of write_entry():\n\n+/* Note: ca is used (and required) iff the entry refers to a regular file. */\n\nSo I would think that `ca` should be passed.\n"},{"id":"411557","messageId":"CAP8UFD3SpzGbkeBas+yDEjDgtDiTdztD=6fMfG=8rHKN5B6kCQ@mail.gmail.com","threadId":"54525","inReplyTo":"aa635bda21c43e0d82ce21e791df280ee6231d43.1604521275.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v4 09/19] entry: add checkout_entry_ca() which takes preloaded conv_attrs","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2020-12-06T10:02:51Z","receivedAt":"2020-12-06T10:03:54Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Nov 4, 2020 at 9:34 PM Matheus Tavares\n<matheus.bernardino@usp.br> wrote:\n\nThe title might be a bit shorter with: s/which takes/taking/ or\ns/which takes/using/\n\n> @@ -530,12 +531,12 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n>         if (nr_checkouts)\n>                 (*nr_checkouts)++;\n>\n> -       if (S_ISREG(ce->ce_mode)) {\n> +       if (S_ISREG(ce->ce_mode) && !ca) {\n>                 convert_attrs(state->istate, &ca_buf, ce->name);\n>                 ca = &ca_buf;\n>         }\n\nI wonder if it's possible to avoid repeating the above 4 lines twice\nin this function.\n\n> -       return write_entry(ce, path.buf, NULL, state, 0);\n> +       return write_entry(ce, path.buf, ca, state, 0);\n\nIt's good that ca is eventually passed here, but it might belong to\nthe previous patch.\n\n> -int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n> -                  char *topath, int *nr_checkouts);\n> +#define checkout_entry(ce, state, topath, nr_checkouts) \\\n> +               checkout_entry_ca(ce, NULL, state, topath, nr_checkouts)\n\nI thought that we prefer inline functions over macros when possible.\n"},{"id":"411558","messageId":"CAP8UFD2+6DYpqM5d1-OCMr4hnGksng4R3e23hcQErT4-ff77=Q@mail.gmail.com","threadId":"54525","inReplyTo":"bc8447cd9c106055a715305ab506adc6abae7713.1604521275.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v4 10/19] unpack-trees: add basic support for parallel checkout","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2020-12-06T11:36:00Z","receivedAt":"2020-12-06T11:36:55Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Nov 4, 2020 at 9:34 PM Matheus Tavares\n<matheus.bernardino@usp.br> wrote:\n>\n> This new interface allows us to enqueue some of the entries being\n> checked out to later call write_entry() for them in parallel. For now,\n> the parallel checkout machinery is enabled by default and there is no\n> user configuration, but run_parallel_checkout() just writes the queued\n> entries in sequence (without spawning additional workers). The next\n> patch will actually implement the parallelism and, later, we will make\n> it configurable.\n\nI would think that it might be more logical to first add a\nconfiguration that does nothing, then add writing the queued entries\nin sequence without parallelism, and then add actual parallelism.\n\n> When there are path collisions among the entries being written (which\n> can happen e.g. with case-sensitive files in case-insensitive file\n> systems), the parallel checkout code detects the problem and marks the\n> item with PC_ITEM_COLLIDED.\n\nIs this needed in this step that only writes the queued entries in\nsequence without parallelism, or could this be added later, before the\nstep that adds actual parallelism?\n\n> Later, these items are sequentially fed to\n> checkout_entry() again. This is similar to the way the sequential code\n> deals with collisions, overwriting the previously checked out entries\n> with the subsequent ones. The only difference is that, when we start\n> writing the entries in parallel, we won't be able to determine which of\n> the colliding entries will survive on disk (for the sequential\n> algorithm, it is always the last one).\n\nSo I guess that PC_ITEM_COLLIDED will then be used to decide which\nentries will not be checked out in parallel?\n\n> I also experimented with the idea of not overwriting colliding entries,\n> and it seemed to work well in my simple tests.\n\nThere are a number of co-author of this patch, so it's not very clear\nwho \"I\" is. Maybe:\n\n\"The idea of not overwriting colliding entries seemed to work well in\nsimple tests, however ...\"\n\n> However, because just one\n> entry of each colliding group would be actually written, the others\n> would have null lstat() fields on the index. This might not be a problem\n> by itself, but it could cause performance penalties for subsequent\n> commands that need to refresh the index: when the st_size value cached\n> is 0, read-cache.c:ie_modified() will go to the filesystem to see if the\n> contents match. As mentioned in the function:\n>\n>     * Immediately after read-tree or update-index --cacheinfo,\n>     * the length field is zero, as we have never even read the\n>     * lstat(2) information once, and we cannot trust DATA_CHANGED\n>     * returned by ie_match_stat() which in turn was returned by\n>     * ce_match_stat_basic() to signal that the filesize of the\n>     * blob changed.  We have to actually go to the filesystem to\n>     * see if the contents match, and if so, should answer \"unchanged\".\n>\n> So, if we have N entries in a colliding group and we decide to write and\n> lstat() only one of them, every subsequent git-status will have to read,\n> convert, and hash the written file N - 1 times, to check that the N - 1\n> unwritten entries are dirty. By checking out all colliding entries (like\n> the sequential code does), we only pay the overhead once.\n\nOk.\n\n>  5 files changed, 410 insertions(+), 3 deletions(-)\n\nIt looks like a lot of new code in one patch/commit, which is why it\nmight be interesting to split it.\n\n> @@ -7,6 +7,7 @@\n>  #include \"progress.h\"\n>  #include \"fsmonitor.h\"\n>  #include \"entry.h\"\n> +#include \"parallel-checkout.h\"\n>\n>  static void create_directories(const char *path, int path_len,\n>                                const struct checkout *state)\n> @@ -426,8 +427,17 @@ static void mark_colliding_entries(const struct checkout *state,\n>         for (i = 0; i < state->istate->cache_nr; i++) {\n>                 struct cache_entry *dup = state->istate->cache[i];\n>\n> -               if (dup == ce)\n> -                       break;\n> +               if (dup == ce) {\n> +                       /*\n> +                        * Parallel checkout creates the files in no particular\n> +                        * order. So the other side of the collision may appear\n> +                        * after the given cache_entry in the array.\n> +                        */\n\nIs it really the case right now that the code creates files in no\nparticular order or will that be the case later when actual\nparallelism is implemented?\n\n> +                       if (parallel_checkout_status() == PC_RUNNING)\n> +                               continue;\n> +                       else\n> +                               break;\n> +               }\n\n> +struct parallel_checkout_item {\n> +       /* pointer to a istate->cache[] entry. Not owned by us. */\n> +       struct cache_entry *ce;\n> +       struct conv_attrs ca;\n> +       struct stat st;\n> +       enum pc_item_status status;\n> +};\n\n\"item\" seems not very clear to me. If there is only one\nparallel_checkout_item for each cache_entry then it might be better to\nuse \"parallel_checkout_entry\" instead of \"parallel_checkout_item\".\n\n> +enum pc_status {\n> +       PC_UNINITIALIZED = 0,\n> +       PC_ACCEPTING_ENTRIES,\n> +       PC_RUNNING,\n> +};\n> +\n> +enum pc_status parallel_checkout_status(void);\n> +void init_parallel_checkout(void);\n\nMaybe a comment to tell what the above function does could be helpful.\nIf I had to guess, I would write something like:\n\n/*\n * Put parallel checkout into the PC_ACCEPTING_ENTRIES state.\n * Should be used only when in the PC_UNINITIALIZED state.\n */\n\n> +/*\n> + * Return -1 if parallel checkout is currently not enabled\n\nIs it \"enabled\" or \"initialized\" or \"configured\" here? Does it refer\nto `enum pc_status` or a config option or something else? Looking at\nthe code, it is testing if the status PC_ACCEPTING_ENTRIES, so\nperhaps: s/not enabled/not accepting entries/\n\n> or if the entry is\n> + * not eligible for parallel checkout. Otherwise, enqueue the entry for later\n> + * write and return 0.\n> + */\n> +int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca);\n"},{"id":"411602","messageId":"CAHd-oW5Mj13_4rgHrQ-Awtzg0i0Wkbeb78z466FO=_d72New=A@mail.gmail.com","threadId":"54525","inReplyTo":"CAP8UFD1-3YxT42K77Rd-XSRsoXYr_RM8Dj_r8-2GQ0noQw58iQ@mail.gmail.com","subject":"Re: [PATCH v4 08/19] entry: move conv_attrs lookup up to checkout_entry()","fromName":"Matheus Tavares Bernardino","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-07T13:52:29Z","receivedAt":"2020-12-07T13:53:35Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"On Sun, Dec 6, 2020 at 6:36 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Wed, Nov 4, 2020 at 9:34 PM Matheus Tavares\n> <matheus.bernardino@usp.br> wrote:\n>\n> > +       if (topath) {\n> > +               if (S_ISREG(ce->ce_mode)) {\n> > +                       convert_attrs(state->istate, &ca_buf, ce->name);\n> > +                       ca = &ca_buf;\n> > +               }\n> > +               return write_entry(ce, topath, ca, state, 1);\n>\n> We pass `ca` here instead of `&ca_buf` because ca is NULL if we are\n> not dealing with a regular file. Ok, I think it's indeed better to\n> pass NULL in this case.\n>\n> > @@ -517,9 +526,16 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n> >                 return 0;\n> >\n> >         create_directories(path.buf, path.len, state);\n> > +\n> >         if (nr_checkouts)\n> >                 (*nr_checkouts)++;\n> > -       return write_entry(ce, path.buf, state, 0);\n> > +\n> > +       if (S_ISREG(ce->ce_mode)) {\n> > +               convert_attrs(state->istate, &ca_buf, ce->name);\n> > +               ca = &ca_buf;\n> > +       }\n> > +\n> > +       return write_entry(ce, path.buf, NULL, state, 0);\n>\n> I am not sure why NULL is passed here though instead of `ca`.\n\nOops, this is indeed wrong. I think I forgot to modify this line while\napplying the changes from the last review round. Thanks for catching\nit!\n"},{"id":"411606","messageId":"CAHd-oW5beC6bM-0SabCAry93S8tDL6uXD8-C6MAWj67Ss20xbQ@mail.gmail.com","threadId":"54525","inReplyTo":"CAP8UFD3SpzGbkeBas+yDEjDgtDiTdztD=6fMfG=8rHKN5B6kCQ@mail.gmail.com","subject":"Re: [PATCH v4 09/19] entry: add checkout_entry_ca() which takes preloaded conv_attrs","fromName":"Matheus Tavares Bernardino","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-07T16:47:16Z","receivedAt":"2020-12-07T16:48:36Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"On Sun, Dec 6, 2020 at 7:03 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Wed, Nov 4, 2020 at 9:34 PM Matheus Tavares\n> <matheus.bernardino@usp.br> wrote:\n>\n> The title might be a bit shorter with: s/which takes/taking/ or\n> s/which takes/using/\n>\n> > @@ -530,12 +531,12 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n> >         if (nr_checkouts)\n> >                 (*nr_checkouts)++;\n> >\n> > -       if (S_ISREG(ce->ce_mode)) {\n> > +       if (S_ISREG(ce->ce_mode) && !ca) {\n> >                 convert_attrs(state->istate, &ca_buf, ce->name);\n> >                 ca = &ca_buf;\n> >         }\n>\n> I wonder if it's possible to avoid repeating the above 4 lines twice\n> in this function.\n\nYeah, I was thinking about that as well. But the only way I found to\navoid the repetition would be to place this block before check_path().\nThe problem is that we would end up calling convert_attrs() even for\nthe cases where we later decide not to checkout the entry (because,\nfor example, state->not_new is true or the file is already up to\ndate).\n\n> > -       return write_entry(ce, path.buf, NULL, state, 0);\n> > +       return write_entry(ce, path.buf, ca, state, 0);\n>\n> It's good that ca is eventually passed here, but it might belong to\n> the previous patch.\n\nRight, I'll fix the previous patch.\n\n> > -int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n> > -                  char *topath, int *nr_checkouts);\n> > +#define checkout_entry(ce, state, topath, nr_checkouts) \\\n> > +               checkout_entry_ca(ce, NULL, state, topath, nr_checkouts)\n>\n> I thought that we prefer inline functions over macros when possible.\n\nThanks, I didn't know about the preference. I'll change that.\n"},{"id":"411623","messageId":"CAHd-oW7gn+ryOu+HhNUs6Do6dsamp86hEK3Lz5S7ajXYF1-YSw@mail.gmail.com","threadId":"54525","inReplyTo":"CAP8UFD2+6DYpqM5d1-OCMr4hnGksng4R3e23hcQErT4-ff77=Q@mail.gmail.com","subject":"Re: [PATCH v4 10/19] unpack-trees: add basic support for parallel checkout","fromName":"Matheus Tavares Bernardino","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-07T19:06:16Z","receivedAt":"2020-12-07T19:07:37Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Hi, Christian\n\nOn Sun, Dec 6, 2020 at 8:36 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Wed, Nov 4, 2020 at 9:34 PM Matheus Tavares\n> <matheus.bernardino@usp.br> wrote:\n> >\n> > This new interface allows us to enqueue some of the entries being\n> > checked out to later call write_entry() for them in parallel. For now,\n> > the parallel checkout machinery is enabled by default and there is no\n> > user configuration, but run_parallel_checkout() just writes the queued\n> > entries in sequence (without spawning additional workers). The next\n> > patch will actually implement the parallelism and, later, we will make\n> > it configurable.\n\nI just noticed that this part of the commit message is a little\noutdated. I'll fix it for v5. Currently, the parallelism and\nconfiguration are added in the same patch (the next one). This way,\nthe patch that adds parallelism can already include runtime numbers\nfor different configuration values (which shows when the change is\nbeneficial).\n\n> I would think that it might be more logical to first add a\n> configuration that does nothing, then add writing the queued entries\n> in sequence without parallelism, and then add actual parallelism.\n\nI'm not sure I get the idea. Would the first patch add just the\ndocumentation for `checkout.workers` and\n`checkout.thresholdForParallelism` in\n`Documentation/config/checkout.txt`, without the support for it in the\ncode? In that case, wouldn't the patch become somewhat incomplete on\nits own?\n\n> > When there are path collisions among the entries being written (which\n> > can happen e.g. with case-sensitive files in case-insensitive file\n> > systems), the parallel checkout code detects the problem and marks the\n> > item with PC_ITEM_COLLIDED.\n>\n> Is this needed in this step that only writes the queued entries in\n> sequence without parallelism, or could this be added later, before the\n> step that adds actual parallelism?\n\nThis is already used in this patch. Even though the parallel checkout\nmachinery only learns to spawn additional workers in the next patch,\nit can already encounter path collisions when writing the entries\nsequentially. PC_ITEM_COLLIDED is then used to mark the colliding\nentries, so that they can be properly handled later.\n\n> > Later, these items are sequentially fed to\n> > checkout_entry() again. This is similar to the way the sequential code\n> > deals with collisions, overwriting the previously checked out entries\n> > with the subsequent ones. The only difference is that, when we start\n> > writing the entries in parallel, we won't be able to determine which of\n> > the colliding entries will survive on disk (for the sequential\n> > algorithm, it is always the last one).\n>\n> So I guess that PC_ITEM_COLLIDED will then be used to decide which\n> entries will not be checked out in parallel?\n\nYes, in the next patch, the parallel workers will detect collisions\nwhen `open(path, O_CREAT | O_EXCL)` fails with EEXIST or EISDIR. The\nworkers then mark such items with PC_ITEM_COLLIDED and let the main\nprocess sequentially write them later.\n\n> > I also experimented with the idea of not overwriting colliding entries,\n> > and it seemed to work well in my simple tests.\n>\n> There are a number of co-author of this patch, so it's not very clear\n> who \"I\" is. Maybe:\n>\n> \"The idea of not overwriting colliding entries seemed to work well in\n> simple tests, however ...\"\n\nMakes sense, thanks.\n\n> > However, because just one\n> > entry of each colliding group would be actually written, the others\n> > would have null lstat() fields on the index. This might not be a problem\n> > by itself, but it could cause performance penalties for subsequent\n> > commands that need to refresh the index: when the st_size value cached\n> > is 0, read-cache.c:ie_modified() will go to the filesystem to see if the\n> > contents match. As mentioned in the function:\n> >\n> >     * Immediately after read-tree or update-index --cacheinfo,\n> >     * the length field is zero, as we have never even read the\n> >     * lstat(2) information once, and we cannot trust DATA_CHANGED\n> >     * returned by ie_match_stat() which in turn was returned by\n> >     * ce_match_stat_basic() to signal that the filesize of the\n> >     * blob changed.  We have to actually go to the filesystem to\n> >     * see if the contents match, and if so, should answer \"unchanged\".\n> >\n> > So, if we have N entries in a colliding group and we decide to write and\n> > lstat() only one of them, every subsequent git-status will have to read,\n> > convert, and hash the written file N - 1 times, to check that the N - 1\n> > unwritten entries are dirty. By checking out all colliding entries (like\n> > the sequential code does), we only pay the overhead once.\n>\n> Ok.\n>\n> >  5 files changed, 410 insertions(+), 3 deletions(-)\n>\n> It looks like a lot of new code in one patch/commit, which is why it\n> might be interesting to split it.\n\nYeah, this and the following patch ended up quite big... But I wasn't\nsure how to further split them while still keeping each part buildable\nand self-contained :(\n\n> > @@ -7,6 +7,7 @@\n> >  #include \"progress.h\"\n> >  #include \"fsmonitor.h\"\n> >  #include \"entry.h\"\n> > +#include \"parallel-checkout.h\"\n> >\n> >  static void create_directories(const char *path, int path_len,\n> >                                const struct checkout *state)\n> > @@ -426,8 +427,17 @@ static void mark_colliding_entries(const struct checkout *state,\n> >         for (i = 0; i < state->istate->cache_nr; i++) {\n> >                 struct cache_entry *dup = state->istate->cache[i];\n> >\n> > -               if (dup == ce)\n> > -                       break;\n> > +               if (dup == ce) {\n> > +                       /*\n> > +                        * Parallel checkout creates the files in no particular\n> > +                        * order. So the other side of the collision may appear\n> > +                        * after the given cache_entry in the array.\n> > +                        */\n>\n> Is it really the case right now that the code creates files in no\n> particular order or will that be the case later when actual\n> parallelism is implemented?\n\nIn this patch, the code already creates files in no particular order.\nSince not all entries are eligible for parallel checkout, and because\nineligible entries are written first, the files are not created in the\nsame order that they appear in istate->cache[]. (Even though\neverything is still written sequentially in this patch).\n\n> > +                       if (parallel_checkout_status() == PC_RUNNING)\n> > +                               continue;\n> > +                       else\n> > +                               break;\n> > +               }\n>\n> > +struct parallel_checkout_item {\n> > +       /* pointer to a istate->cache[] entry. Not owned by us. */\n> > +       struct cache_entry *ce;\n> > +       struct conv_attrs ca;\n> > +       struct stat st;\n> > +       enum pc_item_status status;\n> > +};\n>\n> \"item\" seems not very clear to me. If there is only one\n> parallel_checkout_item for each cache_entry then it might be better to\n> use \"parallel_checkout_entry\" instead of \"parallel_checkout_item\".\n\nHmm, I'm a little uncertain about this one. I usually use \"item\" and\n\"entry\" interchangeably when talking about elements on a list, as in\nthis case. Could perhaps the 'struct parallel_checkout_item'\ndefinition be unclear because it's far from the 'struct\nparallel_checkout', where the list is actually defined?\n\n> > +enum pc_status {\n> > +       PC_UNINITIALIZED = 0,\n> > +       PC_ACCEPTING_ENTRIES,\n> > +       PC_RUNNING,\n> > +};\n> > +\n> > +enum pc_status parallel_checkout_status(void);\n> > +void init_parallel_checkout(void);\n>\n> Maybe a comment to tell what the above function does could be helpful.\n> If I had to guess, I would write something like:\n>\n> /*\n>  * Put parallel checkout into the PC_ACCEPTING_ENTRIES state.\n>  * Should be used only when in the PC_UNINITIALIZED state.\n>  */\n\nOK, will do, thanks!\n\n> > +/*\n> > + * Return -1 if parallel checkout is currently not enabled\n>\n> Is it \"enabled\" or \"initialized\" or \"configured\" here? Does it refer\n> to `enum pc_status` or a config option or something else? Looking at\n> the code, it is testing if the status PC_ACCEPTING_ENTRIES, so\n> perhaps: s/not enabled/not accepting entries/\n\nYes, that's better, thanks!\n"},{"id":"412358","messageId":"cover.1608128666.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1604521275.git.matheus.bernardino@usp.br","subject":"[PATCH v5 0/9] Parallel Checkout (part I)","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-16T14:50:29Z","receivedAt":"2020-12-16T14:51:45Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"The previous rounds got many great suggestions about patches 1 to 10,\nbut not as many comments on the latter patches. Christian commented that\npatches 10 and 11 are too long/complex, making the overall series harder\nto review. So he suggested that I eject patches 10 to 19, and send them\nlater in a separated part. This will hopefully make the series easier to\nreview and move forward. (I also hope to include a desing doc in part 2\nto make those two bigger patches more digestible.)\n\nSo this part is now composed only of the 9 preparatory patches, which\nmainly focus on: (1) adding the 'struct conv_attrs' parameter to some\nconvert.c and entry.c functions (to avoid re-loading the attributes\nduring parallel checkout); and (2) making some functions public (for\nparallel-checkout.c's later use).\n\nChanges since v4:\n\nGeneral:\n- Removed \"[matheus.bernardino: ...]\" lines from patches 1 to 4.\n- Ejected patches 10 to 19, which will now be sent in part 2.\n\nPatch 2:\n- Fixed typo on commit message (s/on a index/on an index/).\n- Added \"_ca\" to convert_to_working_tree_internal()'s name.\n\nPatch 4:\n- Reworded patch title for better clarity, as suggested by Christian.\n\nPatch 5:\n- Mentioned about moving a checkout_entry() comment from entry.c to\n  entry.h in the patch's message.\n\nPatch 7:\n- Make patch's title more explicit and reworded message for clarity.\n\nPatch 8:\n- Fixed the last call to write_entry() in checkout_entry(): it should\n  pass 'ca', not NULL.\n\nPatch 9:\n- Defined checkout_entry() -- which is now a wrapper to\n  checkout_entry_ca() -- as a static inline function instead of a macro.\n- Shortened patch's title.\n\nNote: to see the big picture where these patches should fit in, please\ncheck the previous round with the complete series:\nhttps://lore.kernel.org/git/cover.1604521275.git.matheus.bernardino@usp.br/\n\n\nJeff Hostetler (4):\n  convert: make convert_attrs() and convert structs public\n  convert: add [async_]convert_to_working_tree_ca() variants\n  convert: add get_stream_filter_ca() variant\n  convert: add classification for conv_attrs struct\n\nMatheus Tavares (5):\n  entry: extract a header file for entry.c functions\n  entry: make fstat_output() and read_blob_entry() public\n  entry: extract update_ce_after_write() from write_entry()\n  entry: move conv_attrs lookup up to checkout_entry()\n  entry: add checkout_entry_ca() taking preloaded conv_attrs\n\n apply.c                  |   1 +\n builtin/checkout-index.c |   1 +\n builtin/checkout.c       |   1 +\n builtin/difftool.c       |   1 +\n cache.h                  |  24 -------\n convert.c                | 143 ++++++++++++++++++++-------------------\n convert.h                |  96 +++++++++++++++++++++++---\n entry.c                  |  85 +++++++++++++----------\n entry.h                  |  59 ++++++++++++++++\n unpack-trees.c           |   1 +\n 10 files changed, 275 insertions(+), 137 deletions(-)\n create mode 100644 entry.h\n\nRange-diff against v4:\n 1:  2726f6dc05 !  1:  fa04185237 convert: make convert_attrs() and convert structs public\n    @@ Commit message\n         convert_crlf_action, which is more appropriate for the global namespace.\n     \n         Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n    -    [matheus.bernardino: squash and reword msg]\n         Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n     \n      ## convert.c ##\n 2:  fc03417592 !  2:  2d3c1dc0a1 convert: add [async_]convert_to_working_tree_ca() variants\n    @@ Commit message\n     \n         Separate the attribute gathering from the actual conversion by adding\n         _ca() variants of the conversion functions. These variants receive a\n    -    precomputed 'struct conv_attrs', not relying, thus, on a index state.\n    +    precomputed 'struct conv_attrs', not relying, thus, on an index state.\n         They will be used in a future patch adding parallel checkout support,\n         for two reasons:\n     \n    @@ Commit message\n           it for the main process, anyway.\n     \n         Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n    -    [matheus.bernardino: squash, remove one function definition and reword]\n         Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n     \n      ## convert.c ##\n    @@ convert.c: void convert_to_git_filter_fd(const struct index_state *istate,\n      }\n      \n     -static int convert_to_working_tree_internal(const struct index_state *istate,\n    -+static int convert_to_working_tree_internal(const struct conv_attrs *ca,\n    - \t\t\t\t\t    const char *path, const char *src,\n    - \t\t\t\t\t    size_t len, struct strbuf *dst,\n    - \t\t\t\t\t    int normalizing,\n    -@@ convert.c: static int convert_to_working_tree_internal(const struct index_state *istate,\n    - \t\t\t\t\t    struct delayed_checkout *dco)\n    +-\t\t\t\t\t    const char *path, const char *src,\n    +-\t\t\t\t\t    size_t len, struct strbuf *dst,\n    +-\t\t\t\t\t    int normalizing,\n    +-\t\t\t\t\t    const struct checkout_metadata *meta,\n    +-\t\t\t\t\t    struct delayed_checkout *dco)\n    ++static int convert_to_working_tree_ca_internal(const struct conv_attrs *ca,\n    ++\t\t\t\t\t       const char *path, const char *src,\n    ++\t\t\t\t\t       size_t len, struct strbuf *dst,\n    ++\t\t\t\t\t       int normalizing,\n    ++\t\t\t\t\t       const struct checkout_metadata *meta,\n    ++\t\t\t\t\t       struct delayed_checkout *dco)\n      {\n      \tint ret = 0, ret_filter = 0;\n     -\tstruct conv_attrs ca;\n    @@ convert.c: static int convert_to_working_tree_internal(const struct index_state\n     +\t\t\t\t     void *dco)\n      {\n     -\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco);\n    -+\treturn convert_to_working_tree_internal(ca, path, src, len, dst, 0, meta, dco);\n    ++\treturn convert_to_working_tree_ca_internal(ca, path, src, len, dst, 0,\n    ++\t\t\t\t\t\t   meta, dco);\n      }\n      \n     -int convert_to_working_tree(const struct index_state *istate,\n    @@ convert.c: static int convert_to_working_tree_internal(const struct index_state\n     +\t\t\t       const struct checkout_metadata *meta)\n      {\n     -\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL);\n    -+\treturn convert_to_working_tree_internal(ca, path, src, len, dst, 0, meta, NULL);\n    ++\treturn convert_to_working_tree_ca_internal(ca, path, src, len, dst, 0,\n    ++\t\t\t\t\t\t   meta, NULL);\n      }\n      \n      int renormalize_buffer(const struct index_state *istate, const char *path,\n    @@ convert.c: static int convert_to_working_tree_internal(const struct index_state\n     +\tint ret;\n     +\n     +\tconvert_attrs(istate, &ca, path);\n    -+\tret = convert_to_working_tree_internal(&ca, path, src, len, dst, 1, NULL, NULL);\n    ++\tret = convert_to_working_tree_ca_internal(&ca, path, src, len, dst, 1,\n    ++\t\t\t\t\t\t  NULL, NULL);\n      \tif (ret) {\n      \t\tsrc = dst->buf;\n      \t\tlen = dst->len;\n 3:  8ce20f1031 !  3:  8af17f6a9b convert: add get_stream_filter_ca() variant\n    @@ Commit message\n         attributes struct as a parameter.\n     \n         Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n    -    [matheus.bernardino: move header comment to ca() variant and reword msg]\n         Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n     \n      ## convert.c ##\n 4:  aa1eb461f4 !  4:  f829e2e08f convert: add conv_attrs classification\n    @@ Metadata\n     Author: Jeff Hostetler <jeffhost@microsoft.com>\n     \n      ## Commit message ##\n    -    convert: add conv_attrs classification\n    +    convert: add classification for conv_attrs struct\n     \n         Create `enum conv_attrs_classification` to express the different ways\n         that attributes are handled for a blob during checkout.\n    @@ Commit message\n         classifying logic is the same).\n     \n         Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n    -    [matheus.bernardino: use classification in get_stream_filter_ca()]\n         Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n     \n      ## convert.c ##\n 5:  cb3dea224b !  5:  934ead9519 entry: extract a header file for entry.c functions\n    @@ Commit message\n         reside in cache.h. Although not many, they contribute to the size of\n         cache.h and, when changed, cause the unnecessary recompilation of\n         modules that don't really use these functions. So let's move them to a\n    -    new entry.h header.\n    +    new entry.h header. While at it let's also move a comment related to\n    +    checkout_entry() from entry.c to entry.h as it's more useful to describe\n    +    the function there.\n     \n         Original-patch-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n         Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n 6:  46ed6274d7 =  6:  da6d1d7624 entry: make fstat_output() and read_blob_entry() public\n 7:  a0479d02ff !  7:  def654606d entry: extract cache_entry update from write_entry()\n    @@ Metadata\n     Author: Matheus Tavares <matheus.bernardino@usp.br>\n     \n      ## Commit message ##\n    -    entry: extract cache_entry update from write_entry()\n    +    entry: extract update_ce_after_write() from write_entry()\n     \n    -    This code will be used by the parallel checkout functions, outside\n    -    entry.c, so extract it to a public function.\n    +    The code that updates the in-memory index information after an entry is\n    +    written currently resides in write_entry(). Extract it to a public\n    +    function so that it can be called by the parallel checkout functions,\n    +    outside entry.c, in a later patch.\n     \n         Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n     \n 8:  5c993cc27f !  8:  fbde901518 entry: move conv_attrs lookup up to checkout_entry()\n    @@ entry.c: int checkout_entry(struct cache_entry *ce, const struct checkout *state\n     +\t\tca = &ca_buf;\n     +\t}\n     +\n    -+\treturn write_entry(ce, path.buf, NULL, state, 0);\n    ++\treturn write_entry(ce, path.buf, ca, state, 0);\n      }\n      \n      void unlink_entry(const struct cache_entry *ce)\n 9:  aa635bda21 !  9:  0556d32e8c entry: add checkout_entry_ca() which takes preloaded conv_attrs\n    @@ Metadata\n     Author: Matheus Tavares <matheus.bernardino@usp.br>\n     \n      ## Commit message ##\n    -    entry: add checkout_entry_ca() which takes preloaded conv_attrs\n    +    entry: add checkout_entry_ca() taking preloaded conv_attrs\n     \n         The parallel checkout machinery will call checkout_entry() for entries\n         that could not be written in parallel due to path collisions. At this\n    @@ entry.c: int checkout_entry(struct cache_entry *ce, const struct checkout *state\n      \t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n      \t\tca = &ca_buf;\n      \t}\n    - \n    --\treturn write_entry(ce, path.buf, NULL, state, 0);\n    -+\treturn write_entry(ce, path.buf, ca, state, 0);\n    - }\n    - \n    - void unlink_entry(const struct cache_entry *ce)\n     \n      ## entry.h ##\n     @@ entry.h: struct checkout {\n    @@ entry.h: struct checkout {\n       */\n     -int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n     -\t\t   char *topath, int *nr_checkouts);\n    -+#define checkout_entry(ce, state, topath, nr_checkouts) \\\n    -+\t\tcheckout_entry_ca(ce, NULL, state, topath, nr_checkouts)\n     +int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,\n     +\t\t      const struct checkout *state, char *topath,\n     +\t\t      int *nr_checkouts);\n    ++static inline int checkout_entry(struct cache_entry *ce,\n    ++\t\t\t\t const struct checkout *state, char *topath,\n    ++\t\t\t\t int *nr_checkouts)\n    ++{\n    ++\treturn checkout_entry_ca(ce, NULL, state, topath, nr_checkouts);\n    ++}\n      \n      void enable_delayed_checkout(struct checkout *state);\n      int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n10:  bc8447cd9c <  -:  ---------- unpack-trees: add basic support for parallel checkout\n11:  815137685a <  -:  ---------- parallel-checkout: make it truly parallel\n12:  2b42621582 <  -:  ---------- parallel-checkout: support progress displaying\n13:  960116579a <  -:  ---------- make_transient_cache_entry(): optionally alloc from mem_pool\n14:  fb9f2f580c <  -:  ---------- builtin/checkout.c: complete parallel checkout support\n15:  a844451e58 <  -:  ---------- checkout-index: add parallel checkout support\n16:  3733857ffa <  -:  ---------- parallel-checkout: add tests for basic operations\n17:  c8a2974f81 <  -:  ---------- parallel-checkout: add tests related to clone collisions\n18:  86fccd57d5 <  -:  ---------- parallel-checkout: add tests related to .gitattributes\n19:  7f3e23cc38 <  -:  ---------- ci: run test round with parallel-checkout enabled\n-- \n2.29.2\n\n"},{"id":"412359","messageId":"fa041852375a8733624b31b20cda1626ff93482e.1608128666.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1608128666.git.matheus.bernardino@usp.br","subject":"[PATCH v5 1/9] convert: make convert_attrs() and convert structs public","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-16T14:50:30Z","receivedAt":"2020-12-16T14:51:46Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nMove convert_attrs() declaration from convert.c to convert.h, together\nwith the conv_attrs struct and the crlf_action enum. This function and\nthe data structures will be used outside convert.c in the upcoming\nparallel checkout implementation. Note that crlf_action is renamed to\nconvert_crlf_action, which is more appropriate for the global namespace.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 35 ++++++++---------------------------\n convert.h | 24 ++++++++++++++++++++++++\n 2 files changed, 32 insertions(+), 27 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex ee360c2f07..f13b001273 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -24,17 +24,6 @@\n #define CONVERT_STAT_BITS_TXT_CRLF  0x2\n #define CONVERT_STAT_BITS_BIN       0x4\n \n-enum crlf_action {\n-\tCRLF_UNDEFINED,\n-\tCRLF_BINARY,\n-\tCRLF_TEXT,\n-\tCRLF_TEXT_INPUT,\n-\tCRLF_TEXT_CRLF,\n-\tCRLF_AUTO,\n-\tCRLF_AUTO_INPUT,\n-\tCRLF_AUTO_CRLF\n-};\n-\n struct text_stat {\n \t/* NUL, CR, LF and CRLF counts */\n \tunsigned nul, lonecr, lonelf, crlf;\n@@ -172,7 +161,7 @@ static int text_eol_is_crlf(void)\n \treturn 0;\n }\n \n-static enum eol output_eol(enum crlf_action crlf_action)\n+static enum eol output_eol(enum convert_crlf_action crlf_action)\n {\n \tswitch (crlf_action) {\n \tcase CRLF_BINARY:\n@@ -246,7 +235,7 @@ static int has_crlf_in_index(const struct index_state *istate, const char *path)\n }\n \n static int will_convert_lf_to_crlf(struct text_stat *stats,\n-\t\t\t\t   enum crlf_action crlf_action)\n+\t\t\t\t   enum convert_crlf_action crlf_action)\n {\n \tif (output_eol(crlf_action) != EOL_CRLF)\n \t\treturn 0;\n@@ -499,7 +488,7 @@ static int encode_to_worktree(const char *path, const char *src, size_t src_len,\n static int crlf_to_git(const struct index_state *istate,\n \t\t       const char *path, const char *src, size_t len,\n \t\t       struct strbuf *buf,\n-\t\t       enum crlf_action crlf_action, int conv_flags)\n+\t\t       enum convert_crlf_action crlf_action, int conv_flags)\n {\n \tstruct text_stat stats;\n \tchar *dst;\n@@ -585,8 +574,8 @@ static int crlf_to_git(const struct index_state *istate,\n \treturn 1;\n }\n \n-static int crlf_to_worktree(const char *src, size_t len,\n-\t\t\t    struct strbuf *buf, enum crlf_action crlf_action)\n+static int crlf_to_worktree(const char *src, size_t len, struct strbuf *buf,\n+\t\t\t    enum convert_crlf_action crlf_action)\n {\n \tchar *to_free = NULL;\n \tstruct text_stat stats;\n@@ -1247,7 +1236,7 @@ static const char *git_path_check_encoding(struct attr_check_item *check)\n \treturn value;\n }\n \n-static enum crlf_action git_path_check_crlf(struct attr_check_item *check)\n+static enum convert_crlf_action git_path_check_crlf(struct attr_check_item *check)\n {\n \tconst char *value = check->value;\n \n@@ -1297,18 +1286,10 @@ static int git_path_check_ident(struct attr_check_item *check)\n \treturn !!ATTR_TRUE(value);\n }\n \n-struct conv_attrs {\n-\tstruct convert_driver *drv;\n-\tenum crlf_action attr_action; /* What attr says */\n-\tenum crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n-\tint ident;\n-\tconst char *working_tree_encoding; /* Supported encoding or default encoding if NULL */\n-};\n-\n static struct attr_check *check;\n \n-static void convert_attrs(const struct index_state *istate,\n-\t\t\t  struct conv_attrs *ca, const char *path)\n+void convert_attrs(const struct index_state *istate,\n+\t\t   struct conv_attrs *ca, const char *path)\n {\n \tstruct attr_check_item *ccheck = NULL;\n \ndiff --git a/convert.h b/convert.h\nindex e29d1026a6..5678e99922 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -63,6 +63,30 @@ struct checkout_metadata {\n \tstruct object_id blob;\n };\n \n+enum convert_crlf_action {\n+\tCRLF_UNDEFINED,\n+\tCRLF_BINARY,\n+\tCRLF_TEXT,\n+\tCRLF_TEXT_INPUT,\n+\tCRLF_TEXT_CRLF,\n+\tCRLF_AUTO,\n+\tCRLF_AUTO_INPUT,\n+\tCRLF_AUTO_CRLF\n+};\n+\n+struct convert_driver;\n+\n+struct conv_attrs {\n+\tstruct convert_driver *drv;\n+\tenum convert_crlf_action attr_action; /* What attr says */\n+\tenum convert_crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n+\tint ident;\n+\tconst char *working_tree_encoding; /* Supported encoding or default encoding if NULL */\n+};\n+\n+void convert_attrs(const struct index_state *istate,\n+\t\t   struct conv_attrs *ca, const char *path);\n+\n extern enum eol core_eol;\n extern char *check_roundtrip_encoding;\n const char *get_cached_convert_stats_ascii(const struct index_state *istate,\n-- \n2.29.2\n\n"},{"id":"412360","messageId":"2d3c1dc0a12df04be83883019d725bd6dbeee1fb.1608128666.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1608128666.git.matheus.bernardino@usp.br","subject":"[PATCH v5 2/9] convert: add [async_]convert_to_working_tree_ca() variants","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-16T14:50:31Z","receivedAt":"2020-12-16T14:51:46Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSeparate the attribute gathering from the actual conversion by adding\n_ca() variants of the conversion functions. These variants receive a\nprecomputed 'struct conv_attrs', not relying, thus, on an index state.\nThey will be used in a future patch adding parallel checkout support,\nfor two reasons:\n\n- We will already load the conversion attributes in checkout_entry(),\n  before conversion, to decide whether a path is eligible for parallel\n  checkout. Therefore, it would be wasteful to load them again later,\n  for the actual conversion.\n\n- The parallel workers will be responsible for reading, converting and\n  writing blobs to the working tree. They won't have access to the main\n  process' index state, so they cannot load the attributes. Instead,\n  they will receive the preloaded ones and call the _ca() variant of\n  the conversion functions. Furthermore, the attributes machinery is\n  optimized to handle paths in sequential order, so it's better to leave\n  it for the main process, anyway.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 60 +++++++++++++++++++++++++++++--------------------------\n convert.h | 37 +++++++++++++++++++++++++---------\n 2 files changed, 60 insertions(+), 37 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex f13b001273..0307374241 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1447,19 +1447,16 @@ void convert_to_git_filter_fd(const struct index_state *istate,\n \tident_to_git(dst->buf, dst->len, dst, ca.ident);\n }\n \n-static int convert_to_working_tree_internal(const struct index_state *istate,\n-\t\t\t\t\t    const char *path, const char *src,\n-\t\t\t\t\t    size_t len, struct strbuf *dst,\n-\t\t\t\t\t    int normalizing,\n-\t\t\t\t\t    const struct checkout_metadata *meta,\n-\t\t\t\t\t    struct delayed_checkout *dco)\n+static int convert_to_working_tree_ca_internal(const struct conv_attrs *ca,\n+\t\t\t\t\t       const char *path, const char *src,\n+\t\t\t\t\t       size_t len, struct strbuf *dst,\n+\t\t\t\t\t       int normalizing,\n+\t\t\t\t\t       const struct checkout_metadata *meta,\n+\t\t\t\t\t       struct delayed_checkout *dco)\n {\n \tint ret = 0, ret_filter = 0;\n-\tstruct conv_attrs ca;\n-\n-\tconvert_attrs(istate, &ca, path);\n \n-\tret |= ident_to_worktree(src, len, dst, ca.ident);\n+\tret |= ident_to_worktree(src, len, dst, ca->ident);\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\n@@ -1469,49 +1466,56 @@ static int convert_to_working_tree_internal(const struct index_state *istate,\n \t * is a smudge or process filter (even if the process filter doesn't\n \t * support smudge).  The filters might expect CRLFs.\n \t */\n-\tif ((ca.drv && (ca.drv->smudge || ca.drv->process)) || !normalizing) {\n-\t\tret |= crlf_to_worktree(src, len, dst, ca.crlf_action);\n+\tif ((ca->drv && (ca->drv->smudge || ca->drv->process)) || !normalizing) {\n+\t\tret |= crlf_to_worktree(src, len, dst, ca->crlf_action);\n \t\tif (ret) {\n \t\t\tsrc = dst->buf;\n \t\t\tlen = dst->len;\n \t\t}\n \t}\n \n-\tret |= encode_to_worktree(path, src, len, dst, ca.working_tree_encoding);\n+\tret |= encode_to_worktree(path, src, len, dst, ca->working_tree_encoding);\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\n \t}\n \n \tret_filter = apply_filter(\n-\t\tpath, src, len, -1, dst, ca.drv, CAP_SMUDGE, meta, dco);\n-\tif (!ret_filter && ca.drv && ca.drv->required)\n-\t\tdie(_(\"%s: smudge filter %s failed\"), path, ca.drv->name);\n+\t\tpath, src, len, -1, dst, ca->drv, CAP_SMUDGE, meta, dco);\n+\tif (!ret_filter && ca->drv && ca->drv->required)\n+\t\tdie(_(\"%s: smudge filter %s failed\"), path, ca->drv->name);\n \n \treturn ret | ret_filter;\n }\n \n-int async_convert_to_working_tree(const struct index_state *istate,\n-\t\t\t\t  const char *path, const char *src,\n-\t\t\t\t  size_t len, struct strbuf *dst,\n-\t\t\t\t  const struct checkout_metadata *meta,\n-\t\t\t\t  void *dco)\n+int async_convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t\t     const char *path, const char *src,\n+\t\t\t\t     size_t len, struct strbuf *dst,\n+\t\t\t\t     const struct checkout_metadata *meta,\n+\t\t\t\t     void *dco)\n {\n-\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco);\n+\treturn convert_to_working_tree_ca_internal(ca, path, src, len, dst, 0,\n+\t\t\t\t\t\t   meta, dco);\n }\n \n-int convert_to_working_tree(const struct index_state *istate,\n-\t\t\t    const char *path, const char *src,\n-\t\t\t    size_t len, struct strbuf *dst,\n-\t\t\t    const struct checkout_metadata *meta)\n+int convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t       const char *path, const char *src,\n+\t\t\t       size_t len, struct strbuf *dst,\n+\t\t\t       const struct checkout_metadata *meta)\n {\n-\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL);\n+\treturn convert_to_working_tree_ca_internal(ca, path, src, len, dst, 0,\n+\t\t\t\t\t\t   meta, NULL);\n }\n \n int renormalize_buffer(const struct index_state *istate, const char *path,\n \t\t       const char *src, size_t len, struct strbuf *dst)\n {\n-\tint ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL, NULL);\n+\tstruct conv_attrs ca;\n+\tint ret;\n+\n+\tconvert_attrs(istate, &ca, path);\n+\tret = convert_to_working_tree_ca_internal(&ca, path, src, len, dst, 1,\n+\t\t\t\t\t\t  NULL, NULL);\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\ndiff --git a/convert.h b/convert.h\nindex 5678e99922..a4838b5e5c 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -99,15 +99,34 @@ const char *get_convert_attr_ascii(const struct index_state *istate,\n int convert_to_git(const struct index_state *istate,\n \t\t   const char *path, const char *src, size_t len,\n \t\t   struct strbuf *dst, int conv_flags);\n-int convert_to_working_tree(const struct index_state *istate,\n-\t\t\t    const char *path, const char *src,\n-\t\t\t    size_t len, struct strbuf *dst,\n-\t\t\t    const struct checkout_metadata *meta);\n-int async_convert_to_working_tree(const struct index_state *istate,\n-\t\t\t\t  const char *path, const char *src,\n-\t\t\t\t  size_t len, struct strbuf *dst,\n-\t\t\t\t  const struct checkout_metadata *meta,\n-\t\t\t\t  void *dco);\n+int convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t       const char *path, const char *src,\n+\t\t\t       size_t len, struct strbuf *dst,\n+\t\t\t       const struct checkout_metadata *meta);\n+int async_convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t\t     const char *path, const char *src,\n+\t\t\t\t     size_t len, struct strbuf *dst,\n+\t\t\t\t     const struct checkout_metadata *meta,\n+\t\t\t\t     void *dco);\n+static inline int convert_to_working_tree(const struct index_state *istate,\n+\t\t\t\t\t  const char *path, const char *src,\n+\t\t\t\t\t  size_t len, struct strbuf *dst,\n+\t\t\t\t\t  const struct checkout_metadata *meta)\n+{\n+\tstruct conv_attrs ca;\n+\tconvert_attrs(istate, &ca, path);\n+\treturn convert_to_working_tree_ca(&ca, path, src, len, dst, meta);\n+}\n+static inline int async_convert_to_working_tree(const struct index_state *istate,\n+\t\t\t\t\t\tconst char *path, const char *src,\n+\t\t\t\t\t\tsize_t len, struct strbuf *dst,\n+\t\t\t\t\t\tconst struct checkout_metadata *meta,\n+\t\t\t\t\t\tvoid *dco)\n+{\n+\tstruct conv_attrs ca;\n+\tconvert_attrs(istate, &ca, path);\n+\treturn async_convert_to_working_tree_ca(&ca, path, src, len, dst, meta, dco);\n+}\n int async_query_available_blobs(const char *cmd,\n \t\t\t\tstruct string_list *available_paths);\n int renormalize_buffer(const struct index_state *istate,\n-- \n2.29.2\n\n"},{"id":"412361","messageId":"8af17f6a9b613b73050f59a480302f04bf733b73.1608128666.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1608128666.git.matheus.bernardino@usp.br","subject":"[PATCH v5 3/9] convert: add get_stream_filter_ca() variant","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-16T14:50:32Z","receivedAt":"2020-12-16T14:52:03Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nLike the previous patch, we will also need to call get_stream_filter()\nwith a precomputed `struct conv_attrs`, when we add support for parallel\ncheckout workers. So add the _ca() variant which takes the conversion\nattributes struct as a parameter.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 28 +++++++++++++++++-----------\n convert.h |  2 ++\n 2 files changed, 19 insertions(+), 11 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex 0307374241..9af6aafc5a 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1942,34 +1942,31 @@ static struct stream_filter *ident_filter(const struct object_id *oid)\n }\n \n /*\n- * Return an appropriately constructed filter for the path, or NULL if\n+ * Return an appropriately constructed filter for the given ca, or NULL if\n  * the contents cannot be filtered without reading the whole thing\n  * in-core.\n  *\n  * Note that you would be crazy to set CRLF, smudge/clean or ident to a\n  * large binary blob you would want us not to slurp into the memory!\n  */\n-struct stream_filter *get_stream_filter(const struct index_state *istate,\n-\t\t\t\t\tconst char *path,\n-\t\t\t\t\tconst struct object_id *oid)\n+struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n+\t\t\t\t\t   const struct object_id *oid)\n {\n-\tstruct conv_attrs ca;\n \tstruct stream_filter *filter = NULL;\n \n-\tconvert_attrs(istate, &ca, path);\n-\tif (ca.drv && (ca.drv->process || ca.drv->smudge || ca.drv->clean))\n+\tif (ca->drv && (ca->drv->process || ca->drv->smudge || ca->drv->clean))\n \t\treturn NULL;\n \n-\tif (ca.working_tree_encoding)\n+\tif (ca->working_tree_encoding)\n \t\treturn NULL;\n \n-\tif (ca.crlf_action == CRLF_AUTO || ca.crlf_action == CRLF_AUTO_CRLF)\n+\tif (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF)\n \t\treturn NULL;\n \n-\tif (ca.ident)\n+\tif (ca->ident)\n \t\tfilter = ident_filter(oid);\n \n-\tif (output_eol(ca.crlf_action) == EOL_CRLF)\n+\tif (output_eol(ca->crlf_action) == EOL_CRLF)\n \t\tfilter = cascade_filter(filter, lf_to_crlf_filter());\n \telse\n \t\tfilter = cascade_filter(filter, &null_filter_singleton);\n@@ -1977,6 +1974,15 @@ struct stream_filter *get_stream_filter(const struct index_state *istate,\n \treturn filter;\n }\n \n+struct stream_filter *get_stream_filter(const struct index_state *istate,\n+\t\t\t\t\tconst char *path,\n+\t\t\t\t\tconst struct object_id *oid)\n+{\n+\tstruct conv_attrs ca;\n+\tconvert_attrs(istate, &ca, path);\n+\treturn get_stream_filter_ca(&ca, oid);\n+}\n+\n void free_stream_filter(struct stream_filter *filter)\n {\n \tfilter->vtbl->free(filter);\ndiff --git a/convert.h b/convert.h\nindex a4838b5e5c..484b50965d 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -179,6 +179,8 @@ struct stream_filter; /* opaque */\n struct stream_filter *get_stream_filter(const struct index_state *istate,\n \t\t\t\t\tconst char *path,\n \t\t\t\t\tconst struct object_id *);\n+struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n+\t\t\t\t\t   const struct object_id *oid);\n void free_stream_filter(struct stream_filter *);\n int is_null_stream_filter(struct stream_filter *);\n \n-- \n2.29.2\n\n"},{"id":"412362","messageId":"934ead9519a97e404a76e27331db32ac65670268.1608128666.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1608128666.git.matheus.bernardino@usp.br","subject":"[PATCH v5 5/9] entry: extract a header file for entry.c functions","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-16T14:50:34Z","receivedAt":"2020-12-16T14:52:26Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"The declarations of entry.c's public functions and structures currently\nreside in cache.h. Although not many, they contribute to the size of\ncache.h and, when changed, cause the unnecessary recompilation of\nmodules that don't really use these functions. So let's move them to a\nnew entry.h header. While at it let's also move a comment related to\ncheckout_entry() from entry.c to entry.h as it's more useful to describe\nthe function there.\n\nOriginal-patch-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n apply.c                  |  1 +\n builtin/checkout-index.c |  1 +\n builtin/checkout.c       |  1 +\n builtin/difftool.c       |  1 +\n cache.h                  | 24 -----------------------\n entry.c                  |  9 +--------\n entry.h                  | 42 ++++++++++++++++++++++++++++++++++++++++\n unpack-trees.c           |  1 +\n 8 files changed, 48 insertions(+), 32 deletions(-)\n create mode 100644 entry.h\n\ndiff --git a/apply.c b/apply.c\nindex 4a4e9a0158..fda7b4f770 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -21,6 +21,7 @@\n #include \"quote.h\"\n #include \"rerere.h\"\n #include \"apply.h\"\n+#include \"entry.h\"\n \n struct gitdiff_data {\n \tstruct strbuf *root;\ndiff --git a/builtin/checkout-index.c b/builtin/checkout-index.c\nindex 4bbfc92dce..9276ed0258 100644\n--- a/builtin/checkout-index.c\n+++ b/builtin/checkout-index.c\n@@ -11,6 +11,7 @@\n #include \"quote.h\"\n #include \"cache-tree.h\"\n #include \"parse-options.h\"\n+#include \"entry.h\"\n \n #define CHECKOUT_ALL 4\n static int nul_term_line;\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 9b82119129..f92f29bad3 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -26,6 +26,7 @@\n #include \"unpack-trees.h\"\n #include \"wt-status.h\"\n #include \"xdiff-interface.h\"\n+#include \"entry.h\"\n \n static const char * const checkout_usage[] = {\n \tN_(\"git checkout [<options>] <branch>\"),\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 6e18e623fd..ef25729d49 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -23,6 +23,7 @@\n #include \"lockfile.h\"\n #include \"object-store.h\"\n #include \"dir.h\"\n+#include \"entry.h\"\n \n static int trust_exit_code;\n \ndiff --git a/cache.h b/cache.h\nindex 8d279bc110..a3a70cd8a4 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1711,30 +1711,6 @@ const char *show_ident_date(const struct ident_split *id,\n  */\n int ident_cmp(const struct ident_split *, const struct ident_split *);\n \n-struct checkout {\n-\tstruct index_state *istate;\n-\tconst char *base_dir;\n-\tint base_dir_len;\n-\tstruct delayed_checkout *delayed_checkout;\n-\tstruct checkout_metadata meta;\n-\tunsigned force:1,\n-\t\t quiet:1,\n-\t\t not_new:1,\n-\t\t clone:1,\n-\t\t refresh_cache:1;\n-};\n-#define CHECKOUT_INIT { NULL, \"\" }\n-\n-#define TEMPORARY_FILENAME_LENGTH 25\n-int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath, int *nr_checkouts);\n-void enable_delayed_checkout(struct checkout *state);\n-int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n-/*\n- * Unlink the last component and schedule the leading directories for\n- * removal, such that empty directories get removed.\n- */\n-void unlink_entry(const struct cache_entry *ce);\n-\n struct cache_def {\n \tstruct strbuf path;\n \tint flags;\ndiff --git a/entry.c b/entry.c\nindex a0532f1f00..b0b8099699 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -6,6 +6,7 @@\n #include \"submodule.h\"\n #include \"progress.h\"\n #include \"fsmonitor.h\"\n+#include \"entry.h\"\n \n static void create_directories(const char *path, int path_len,\n \t\t\t       const struct checkout *state)\n@@ -429,14 +430,6 @@ static void mark_colliding_entries(const struct checkout *state,\n \t}\n }\n \n-/*\n- * Write the contents from ce out to the working tree.\n- *\n- * When topath[] is not NULL, instead of writing to the working tree\n- * file named by ce, a temporary file is created by this function and\n- * its name is returned in topath[], which must be able to hold at\n- * least TEMPORARY_FILENAME_LENGTH bytes long.\n- */\n int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t\t   char *topath, int *nr_checkouts)\n {\ndiff --git a/entry.h b/entry.h\nnew file mode 100644\nindex 0000000000..acbbb90220\n--- /dev/null\n+++ b/entry.h\n@@ -0,0 +1,42 @@\n+#ifndef ENTRY_H\n+#define ENTRY_H\n+\n+#include \"cache.h\"\n+#include \"convert.h\"\n+\n+struct checkout {\n+\tstruct index_state *istate;\n+\tconst char *base_dir;\n+\tint base_dir_len;\n+\tstruct delayed_checkout *delayed_checkout;\n+\tstruct checkout_metadata meta;\n+\tunsigned force:1,\n+\t\t quiet:1,\n+\t\t not_new:1,\n+\t\t clone:1,\n+\t\t refresh_cache:1;\n+};\n+#define CHECKOUT_INIT { NULL, \"\" }\n+\n+#define TEMPORARY_FILENAME_LENGTH 25\n+/*\n+ * Write the contents from ce out to the working tree.\n+ *\n+ * When topath[] is not NULL, instead of writing to the working tree\n+ * file named by ce, a temporary file is created by this function and\n+ * its name is returned in topath[], which must be able to hold at\n+ * least TEMPORARY_FILENAME_LENGTH bytes long.\n+ */\n+int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n+\t\t   char *topath, int *nr_checkouts);\n+\n+void enable_delayed_checkout(struct checkout *state);\n+int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n+\n+/*\n+ * Unlink the last component and schedule the leading directories for\n+ * removal, such that empty directories get removed.\n+ */\n+void unlink_entry(const struct cache_entry *ce);\n+\n+#endif /* ENTRY_H */\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 323280dd48..a511fadd89 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -16,6 +16,7 @@\n #include \"fsmonitor.h\"\n #include \"object-store.h\"\n #include \"promisor-remote.h\"\n+#include \"entry.h\"\n \n /*\n  * Error messages expected by scripts out of plumbing commands such as\n-- \n2.29.2\n\n"},{"id":"412363","messageId":"da6d1d762472ad0fdbe3d6145384078741d6516b.1608128666.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1608128666.git.matheus.bernardino@usp.br","subject":"[PATCH v5 6/9] entry: make fstat_output() and read_blob_entry() public","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-16T14:50:35Z","receivedAt":"2020-12-16T14:52:26Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"These two functions will be used by the parallel checkout code, so let's\nmake them public. Note: fstat_output() is renamed to\nfstat_checkout_output(), now that it has become public, seeking to avoid\nfuture name collisions.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 8 ++++----\n entry.h | 3 +++\n 2 files changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex b0b8099699..b36071a610 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -84,7 +84,7 @@ static int create_file(const char *path, unsigned int mode)\n \treturn open(path, O_WRONLY | O_CREAT | O_EXCL, mode);\n }\n \n-static void *read_blob_entry(const struct cache_entry *ce, unsigned long *size)\n+void *read_blob_entry(const struct cache_entry *ce, unsigned long *size)\n {\n \tenum object_type type;\n \tvoid *blob_data = read_object_file(&ce->oid, &type, size);\n@@ -109,7 +109,7 @@ static int open_output_fd(char *path, const struct cache_entry *ce, int to_tempf\n \t}\n }\n \n-static int fstat_output(int fd, const struct checkout *state, struct stat *st)\n+int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st)\n {\n \t/* use fstat() only when path == ce->name */\n \tif (fstat_is_reliable() &&\n@@ -132,7 +132,7 @@ static int streaming_write_entry(const struct cache_entry *ce, char *path,\n \t\treturn -1;\n \n \tresult |= stream_blob_to_fd(fd, &ce->oid, filter, 1);\n-\t*fstat_done = fstat_output(fd, state, statbuf);\n+\t*fstat_done = fstat_checkout_output(fd, state, statbuf);\n \tresult |= close(fd);\n \n \tif (result)\n@@ -346,7 +346,7 @@ static int write_entry(struct cache_entry *ce,\n \n \t\twrote = write_in_full(fd, new_blob, size);\n \t\tif (!to_tempfile)\n-\t\t\tfstat_done = fstat_output(fd, state, &st);\n+\t\t\tfstat_done = fstat_checkout_output(fd, state, &st);\n \t\tclose(fd);\n \t\tfree(new_blob);\n \t\tif (wrote < 0)\ndiff --git a/entry.h b/entry.h\nindex acbbb90220..60df93ca78 100644\n--- a/entry.h\n+++ b/entry.h\n@@ -39,4 +39,7 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n  */\n void unlink_entry(const struct cache_entry *ce);\n \n+void *read_blob_entry(const struct cache_entry *ce, unsigned long *size);\n+int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st);\n+\n #endif /* ENTRY_H */\n-- \n2.29.2\n\n"},{"id":"412364","messageId":"f829e2e08f4becd7c4341e26aab16b69ab71c38b.1608128666.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1608128666.git.matheus.bernardino@usp.br","subject":"[PATCH v5 4/9] convert: add classification for conv_attrs struct","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-16T14:50:33Z","receivedAt":"2020-12-16T14:52:26Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate `enum conv_attrs_classification` to express the different ways\nthat attributes are handled for a blob during checkout.\n\nThis will be used in a later commit when deciding whether to add a file\nto the parallel or delayed queue during checkout. For now, we can also\nuse it in get_stream_filter_ca() to simplify the function (as the\nclassifying logic is the same).\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 26 +++++++++++++++++++-------\n convert.h | 33 +++++++++++++++++++++++++++++++++\n 2 files changed, 52 insertions(+), 7 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex 9af6aafc5a..b9d25d9a47 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1954,13 +1954,7 @@ struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n {\n \tstruct stream_filter *filter = NULL;\n \n-\tif (ca->drv && (ca->drv->process || ca->drv->smudge || ca->drv->clean))\n-\t\treturn NULL;\n-\n-\tif (ca->working_tree_encoding)\n-\t\treturn NULL;\n-\n-\tif (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF)\n+\tif (classify_conv_attrs(ca) != CA_CLASS_STREAMABLE)\n \t\treturn NULL;\n \n \tif (ca->ident)\n@@ -2016,3 +2010,21 @@ void clone_checkout_metadata(struct checkout_metadata *dst,\n \tif (blob)\n \t\toidcpy(&dst->blob, blob);\n }\n+\n+enum conv_attrs_classification classify_conv_attrs(const struct conv_attrs *ca)\n+{\n+\tif (ca->drv) {\n+\t\tif (ca->drv->process)\n+\t\t\treturn CA_CLASS_INCORE_PROCESS;\n+\t\tif (ca->drv->smudge || ca->drv->clean)\n+\t\t\treturn CA_CLASS_INCORE_FILTER;\n+\t}\n+\n+\tif (ca->working_tree_encoding)\n+\t\treturn CA_CLASS_INCORE;\n+\n+\tif (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF)\n+\t\treturn CA_CLASS_INCORE;\n+\n+\treturn CA_CLASS_STREAMABLE;\n+}\ndiff --git a/convert.h b/convert.h\nindex 484b50965d..43e567a59b 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -200,4 +200,37 @@ int stream_filter(struct stream_filter *,\n \t\t  const char *input, size_t *isize_p,\n \t\t  char *output, size_t *osize_p);\n \n+enum conv_attrs_classification {\n+\t/*\n+\t * The blob must be loaded into a buffer before it can be\n+\t * smudged. All smudging is done in-proc.\n+\t */\n+\tCA_CLASS_INCORE,\n+\n+\t/*\n+\t * The blob must be loaded into a buffer, but uses a\n+\t * single-file driver filter, such as rot13.\n+\t */\n+\tCA_CLASS_INCORE_FILTER,\n+\n+\t/*\n+\t * The blob must be loaded into a buffer, but uses a\n+\t * long-running driver process, such as LFS. This might or\n+\t * might not use delayed operations. (The important thing is\n+\t * that there is a single subordinate long-running process\n+\t * handling all associated blobs and in case of delayed\n+\t * operations, may hold per-blob state.)\n+\t */\n+\tCA_CLASS_INCORE_PROCESS,\n+\n+\t/*\n+\t * The blob can be streamed and smudged without needing to\n+\t * completely read it into a buffer.\n+\t */\n+\tCA_CLASS_STREAMABLE,\n+};\n+\n+enum conv_attrs_classification classify_conv_attrs(\n+\tconst struct conv_attrs *ca);\n+\n #endif /* CONVERT_H */\n-- \n2.29.2\n\n"},{"id":"412365","messageId":"def654606d1ec8aef0d3807db15ebe259a30ac68.1608128666.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1608128666.git.matheus.bernardino@usp.br","subject":"[PATCH v5 7/9] entry: extract update_ce_after_write() from write_entry()","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-16T14:50:36Z","receivedAt":"2020-12-16T14:52:26Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"The code that updates the in-memory index information after an entry is\nwritten currently resides in write_entry(). Extract it to a public\nfunction so that it can be called by the parallel checkout functions,\noutside entry.c, in a later patch.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 25 ++++++++++++++++---------\n entry.h |  2 ++\n 2 files changed, 18 insertions(+), 9 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex b36071a610..1d2df188e5 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -251,6 +251,18 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts)\n \treturn errs;\n }\n \n+void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,\n+\t\t\t   struct stat *st)\n+{\n+\tif (state->refresh_cache) {\n+\t\tassert(state->istate);\n+\t\tfill_stat_cache_info(state->istate, ce, st);\n+\t\tce->ce_flags |= CE_UPDATE_IN_BASE;\n+\t\tmark_fsmonitor_invalid(state->istate, ce);\n+\t\tstate->istate->cache_changed |= CE_ENTRY_CHANGED;\n+\t}\n+}\n+\n static int write_entry(struct cache_entry *ce,\n \t\t       char *path, const struct checkout *state, int to_tempfile)\n {\n@@ -371,15 +383,10 @@ static int write_entry(struct cache_entry *ce,\n \n finish:\n \tif (state->refresh_cache) {\n-\t\tassert(state->istate);\n-\t\tif (!fstat_done)\n-\t\t\tif (lstat(ce->name, &st) < 0)\n-\t\t\t\treturn error_errno(\"unable to stat just-written file %s\",\n-\t\t\t\t\t\t   ce->name);\n-\t\tfill_stat_cache_info(state->istate, ce, &st);\n-\t\tce->ce_flags |= CE_UPDATE_IN_BASE;\n-\t\tmark_fsmonitor_invalid(state->istate, ce);\n-\t\tstate->istate->cache_changed |= CE_ENTRY_CHANGED;\n+\t\tif (!fstat_done && lstat(ce->name, &st) < 0)\n+\t\t\treturn error_errno(\"unable to stat just-written file %s\",\n+\t\t\t\t\t   ce->name);\n+\t\tupdate_ce_after_write(state, ce , &st);\n \t}\n delayed:\n \treturn 0;\ndiff --git a/entry.h b/entry.h\nindex 60df93ca78..ea7290bcd5 100644\n--- a/entry.h\n+++ b/entry.h\n@@ -41,5 +41,7 @@ void unlink_entry(const struct cache_entry *ce);\n \n void *read_blob_entry(const struct cache_entry *ce, unsigned long *size);\n int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st);\n+void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,\n+\t\t\t   struct stat *st);\n \n #endif /* ENTRY_H */\n-- \n2.29.2\n\n"},{"id":"412366","messageId":"0556d32e8c9b3119fd343f719db4be41617d5365.1608128666.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1608128666.git.matheus.bernardino@usp.br","subject":"[PATCH v5 9/9] entry: add checkout_entry_ca() taking preloaded conv_attrs","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-16T14:50:38Z","receivedAt":"2020-12-16T14:52:26Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"The parallel checkout machinery will call checkout_entry() for entries\nthat could not be written in parallel due to path collisions. At this\npoint, we will already be holding the conversion attributes for each\nentry, and it would be wasteful to let checkout_entry() load these\nagain. Instead, let's add the checkout_entry_ca() variant, which\noptionally takes a preloaded conv_attrs struct.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 11 ++++++-----\n entry.h | 16 ++++++++++++++--\n 2 files changed, 20 insertions(+), 7 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex 62ddd348bc..9d79a5671f 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -440,12 +440,13 @@ static void mark_colliding_entries(const struct checkout *state,\n \t}\n }\n \n-int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n-\t\t   char *topath, int *nr_checkouts)\n+int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,\n+\t\t      const struct checkout *state, char *topath,\n+\t\t      int *nr_checkouts)\n {\n \tstatic struct strbuf path = STRBUF_INIT;\n \tstruct stat st;\n-\tstruct conv_attrs ca_buf, *ca = NULL;\n+\tstruct conv_attrs ca_buf;\n \n \tif (ce->ce_flags & CE_WT_REMOVE) {\n \t\tif (topath)\n@@ -459,7 +460,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t}\n \n \tif (topath) {\n-\t\tif (S_ISREG(ce->ce_mode)) {\n+\t\tif (S_ISREG(ce->ce_mode) && !ca) {\n \t\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n \t\t\tca = &ca_buf;\n \t\t}\n@@ -530,7 +531,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \tif (nr_checkouts)\n \t\t(*nr_checkouts)++;\n \n-\tif (S_ISREG(ce->ce_mode)) {\n+\tif (S_ISREG(ce->ce_mode) && !ca) {\n \t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n \t\tca = &ca_buf;\n \t}\ndiff --git a/entry.h b/entry.h\nindex ea7290bcd5..b8c0e170dc 100644\n--- a/entry.h\n+++ b/entry.h\n@@ -26,9 +26,21 @@ struct checkout {\n  * file named by ce, a temporary file is created by this function and\n  * its name is returned in topath[], which must be able to hold at\n  * least TEMPORARY_FILENAME_LENGTH bytes long.\n+ *\n+ * With checkout_entry_ca(), callers can optionally pass a preloaded\n+ * conv_attrs struct (to avoid reloading it), when ce refers to a\n+ * regular file. If ca is NULL, the attributes will be loaded\n+ * internally when (and if) needed.\n  */\n-int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n-\t\t   char *topath, int *nr_checkouts);\n+int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,\n+\t\t      const struct checkout *state, char *topath,\n+\t\t      int *nr_checkouts);\n+static inline int checkout_entry(struct cache_entry *ce,\n+\t\t\t\t const struct checkout *state, char *topath,\n+\t\t\t\t int *nr_checkouts)\n+{\n+\treturn checkout_entry_ca(ce, NULL, state, topath, nr_checkouts);\n+}\n \n void enable_delayed_checkout(struct checkout *state);\n int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n-- \n2.29.2\n\n"},{"id":"412367","messageId":"fbde9015185c1a0ed3e74ebd9eb00bd4f99ba89e.1608128666.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1608128666.git.matheus.bernardino@usp.br","subject":"[PATCH v5 8/9] entry: move conv_attrs lookup up to checkout_entry()","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-16T14:50:37Z","receivedAt":"2020-12-16T14:52:26Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"In a following patch, checkout_entry() will use conv_attrs to decide\nwhether an entry should be enqueued for parallel checkout or not. But\nthe attributes lookup only happens lower in this call stack. To avoid\nthe unnecessary work of loading the attributes twice, let's move it up\nto checkout_entry(), and pass the loaded struct down to write_entry().\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 38 +++++++++++++++++++++++++++-----------\n 1 file changed, 27 insertions(+), 11 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex 1d2df188e5..62ddd348bc 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -263,8 +263,9 @@ void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,\n \t}\n }\n \n-static int write_entry(struct cache_entry *ce,\n-\t\t       char *path, const struct checkout *state, int to_tempfile)\n+/* Note: ca is used (and required) iff the entry refers to a regular file. */\n+static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca,\n+\t\t       const struct checkout *state, int to_tempfile)\n {\n \tunsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;\n \tstruct delayed_checkout *dco = state->delayed_checkout;\n@@ -281,8 +282,7 @@ static int write_entry(struct cache_entry *ce,\n \tclone_checkout_metadata(&meta, &state->meta, &ce->oid);\n \n \tif (ce_mode_s_ifmt == S_IFREG) {\n-\t\tstruct stream_filter *filter = get_stream_filter(state->istate, ce->name,\n-\t\t\t\t\t\t\t\t &ce->oid);\n+\t\tstruct stream_filter *filter = get_stream_filter_ca(ca, &ce->oid);\n \t\tif (filter &&\n \t\t    !streaming_write_entry(ce, path, filter,\n \t\t\t\t\t   state, to_tempfile,\n@@ -329,14 +329,17 @@ static int write_entry(struct cache_entry *ce,\n \t\t * Convert from git internal format to working tree format\n \t\t */\n \t\tif (dco && dco->state != CE_NO_DELAY) {\n-\t\t\tret = async_convert_to_working_tree(state->istate, ce->name, new_blob,\n-\t\t\t\t\t\t\t    size, &buf, &meta, dco);\n+\t\t\tret = async_convert_to_working_tree_ca(ca, ce->name,\n+\t\t\t\t\t\t\t       new_blob, size,\n+\t\t\t\t\t\t\t       &buf, &meta, dco);\n \t\t\tif (ret && string_list_has_string(&dco->paths, ce->name)) {\n \t\t\t\tfree(new_blob);\n \t\t\t\tgoto delayed;\n \t\t\t}\n-\t\t} else\n-\t\t\tret = convert_to_working_tree(state->istate, ce->name, new_blob, size, &buf, &meta);\n+\t\t} else {\n+\t\t\tret = convert_to_working_tree_ca(ca, ce->name, new_blob,\n+\t\t\t\t\t\t\t size, &buf, &meta);\n+\t\t}\n \n \t\tif (ret) {\n \t\t\tfree(new_blob);\n@@ -442,6 +445,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n {\n \tstatic struct strbuf path = STRBUF_INIT;\n \tstruct stat st;\n+\tstruct conv_attrs ca_buf, *ca = NULL;\n \n \tif (ce->ce_flags & CE_WT_REMOVE) {\n \t\tif (topath)\n@@ -454,8 +458,13 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t\treturn 0;\n \t}\n \n-\tif (topath)\n-\t\treturn write_entry(ce, topath, state, 1);\n+\tif (topath) {\n+\t\tif (S_ISREG(ce->ce_mode)) {\n+\t\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n+\t\t\tca = &ca_buf;\n+\t\t}\n+\t\treturn write_entry(ce, topath, ca, state, 1);\n+\t}\n \n \tstrbuf_reset(&path);\n \tstrbuf_add(&path, state->base_dir, state->base_dir_len);\n@@ -517,9 +526,16 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t\treturn 0;\n \n \tcreate_directories(path.buf, path.len, state);\n+\n \tif (nr_checkouts)\n \t\t(*nr_checkouts)++;\n-\treturn write_entry(ce, path.buf, state, 0);\n+\n+\tif (S_ISREG(ce->ce_mode)) {\n+\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n+\t\tca = &ca_buf;\n+\t}\n+\n+\treturn write_entry(ce, path.buf, ca, state, 0);\n }\n \n void unlink_entry(const struct cache_entry *ce)\n-- \n2.29.2\n\n"},{"id":"412368","messageId":"CAP8UFD1xrJ=bfB0srkMjSFATs6ym5YPokgd5=RE_eA3x6oUQOg@mail.gmail.com","threadId":"54525","inReplyTo":"cover.1608128666.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v5 0/9] Parallel Checkout (part I)","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2020-12-16T15:27:31Z","receivedAt":"2020-12-16T15:28:41Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Dec 16, 2020 at 3:50 PM Matheus Tavares\n<matheus.bernardino@usp.br> wrote:\n>\n> The previous rounds got many great suggestions about patches 1 to 10,\n> but not as many comments on the latter patches. Christian commented that\n> patches 10 and 11 are too long/complex, making the overall series harder\n> to review. So he suggested that I eject patches 10 to 19, and send them\n> later in a separated part. This will hopefully make the series easier to\n> review and move forward. (I also hope to include a desing doc in part 2\n> to make those two bigger patches more digestible.)\n\nThanks, and yeah, sorry I suggested that privately, but should have\ndone it on the mailing list.\n\nI actually think that patches 10 and 11 (which each one contains 400+\nnew lines) in the previous series should be mostly alone in a part 2,\nwith perhaps a part 3 that would have most of the rest, so\nimprovements and tests.\n\nIt might also be possible to split at least a bit a few things in\npatches 10 and 11. For example I think it's ok to add new\nconfiguration in a separate patch even if it's not used yet. It can\njust reserve the name. That could be in part  2 then.\n\n> Changes since v4:\n\nFrom a quick look at the range-diff, it looks good to me!\n\nThanks,\nChristian.\n"},{"id":"412446","messageId":"20201216223123.GH3783238@google.com","threadId":"54525","inReplyTo":"815137685ac3e41444201316f537db9797dcacd2.1604521276.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v4 11/19] parallel-checkout: make it truly parallel","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-16T22:31:23Z","receivedAt":"2020-12-16T22:32:26Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Wed, Nov 04, 2020 at 05:33:10PM -0300, Matheus Tavares wrote:\n> \n> Use multiple worker processes to distribute the queued entries and call\n> write_checkout_item() in parallel for them. The items are distributed\n> uniformly in contiguous chunks. This minimizes the chances of two\n> workers writing to the same directory simultaneously, which could\n> affect performance due to lock contention in the kernel. Work stealing\n> (or any other format of re-distribution) is not implemented yet.\n> \n> The parallel version was benchmarked during three operations in the\n> linux repo, with cold cache: cloning v5.8, checking out v5.8 from\n> v2.6.15 (checkout I) and checking out v5.8 from v5.7 (checkout II). The\n> four tables below show the mean run times and standard deviations for\n> 5 runs in: a local file system with SSD, a local file system with HDD, a\n> Linux NFS server, and Amazon EFS. The numbers of workers were chosen\n> based on what produces the best result for each case.\n> \n> Local SSD:\n> \n>             Clone                  Checkout I             Checkout II\n> Sequential  8.171 s ± 0.206 s      8.735 s ± 0.230 s      4.166 s ± 0.246 s\n> 10 workers  3.277 s ± 0.138 s      3.774 s ± 0.188 s      2.561 s ± 0.120 s\n> Speedup     2.49 ± 0.12            2.31 ± 0.13            1.63 ± 0.12\n> \n> Local HDD:\n> \n>             Clone                  Checkout I             Checkout II\n> Sequential  35.157 s ± 0.205 s     48.835 s ± 0.407 s     47.302 s ± 1.435 s\n> 8 workers   35.538 s ± 0.325 s     49.353 s ± 0.826 s     48.919 s ± 0.416 s\n> Speedup     0.99 ± 0.01            0.99 ± 0.02            0.97 ± 0.03\n> \n> Linux NFS server (v4.1, on EBS, single availability zone):\n> \n>             Clone                  Checkout I             Checkout II\n> Sequential  216.070 s ± 3.611 s    211.169 s ± 3.147 s    57.446 s ± 1.301 s\n> 32 workers  67.997 s ± 0.740 s     66.563 s ± 0.457 s     23.708 s ± 0.622 s\n> Speedup     3.18 ± 0.06            3.17 ± 0.05            2.42 ± 0.08\n> \n> EFS (v4.1, replicated over multiple availability zones):\n> \n>             Clone                  Checkout I             Checkout II\n> Sequential  1249.329 s ± 13.857 s  1438.979 s ± 78.792 s  543.919 s ± 18.745 s\n> 64 workers  225.864 s ± 12.433 s   316.345 s ± 1.887 s    183.648 s ± 10.095 s\n> Speedup     5.53 ± 0.31            4.55 ± 0.25            2.96 ± 0.19\n> \n> The above benchmarks show that parallel checkout is most effective on\n> repositories located on an SSD or over a distributed file system. For\n> local file systems on spinning disks, and/or older machines, the\n> parallelism does not always bring a good performance. In fact, it can\n> even increase the run time. For this reason, the sequential code is\n> still the default. Two settings are added to optionally enable and\n> configure the new parallel version as desired.\n> \n> Local SSD tests were executed in an i7-7700HQ (4 cores with\n> hyper-threading) running Manjaro Linux. Local HDD tests were executed in\n> an i7-2600 (also 4 cores with hyper-threading), HDD Seagate Barracuda\n> 7200 rpm SATA 3.0, running Debian 9.13. NFS and EFS tests were\n> executed in an Amazon EC2 c5n.large instance, with 2 vCPUs. The Linux\n> NFS server was running on a m6g.large instance with 1 TB, EBS GP2\n> volume. Before each timing, the linux repository was removed (or checked\n> out back), and `sync && sysctl vm.drop_caches=3` was executed.\n> \n> Co-authored-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> Co-authored-by: Jeff Hostetler <jeffhost@microsoft.com>\n> Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n\nOnly having done a quick skim, is there a reason that you are doing the\nworkqueue handling from scratch rather than using\nrun-command.h:run_processes_parallel()? The implementation you use and\nthe implementation in run-command.c seem really similar to me.\n\n - Emily\n"},{"id":"412468","messageId":"xmqqeejp1bj8.fsf@gitster.c.googlers.com","threadId":"54525","inReplyTo":"cover.1608128666.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v5 0/9] Parallel Checkout (part I)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-17T01:11:55Z","receivedAt":"2020-12-17T01:12:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matheus Tavares <matheus.bernardino@usp.br> writes:\n\n> The previous rounds got many great suggestions about patches 1 to 10,\n> but not as many comments on the latter patches. Christian commented that\n> patches 10 and 11 are too long/complex, making the overall series harder\n> to review. So he suggested that I eject patches 10 to 19, and send them\n> later in a separated part. This will hopefully make the series easier to\n> review and move forward. (I also hope to include a desing doc in part 2\n> to make those two bigger patches more digestible.)\n>\n> So this part is now composed only of the 9 preparatory patches, which\n> mainly focus on: (1) adding the 'struct conv_attrs' parameter to some\n> convert.c and entry.c functions (to avoid re-loading the attributes\n> during parallel checkout); and (2) making some functions public (for\n> parallel-checkout.c's later use).\n\nAll of these patches look sensible to me.\n\nWill replace.  Thanks.\n"},{"id":"412491","messageId":"CAHd-oW4zM91A0Oz77Px_gtAyhM8opMy3=ChJ4uAm1n0jEXcLmg@mail.gmail.com","threadId":"54525","inReplyTo":"20201216223123.GH3783238@google.com","subject":"Re: [PATCH v4 11/19] parallel-checkout: make it truly parallel","fromName":"Matheus Tavares Bernardino","fromEmail":"matheus.bernardino@usp.br","sentAt":"2020-12-17T15:00:31Z","receivedAt":"2020-12-17T15:01:41Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Hi, Emily\n\nOn Wed, Dec 16, 2020 at 7:31 PM Emily Shaffer <emilyshaffer@google.com> wrote:\n>\n> On Wed, Nov 04, 2020 at 05:33:10PM -0300, Matheus Tavares wrote:\n> >\n> > Use multiple worker processes to distribute the queued entries and call\n> > write_checkout_item() in parallel for them. The items are distributed\n> > uniformly in contiguous chunks. This minimizes the chances of two\n> > workers writing to the same directory simultaneously, which could\n> > affect performance due to lock contention in the kernel. Work stealing\n> > (or any other format of re-distribution) is not implemented yet.\n> >\n>\n> Only having done a quick skim, is there a reason that you are doing the\n> workqueue handling from scratch rather than using\n> run-command.h:run_processes_parallel()? The implementation you use and\n> the implementation in run-command.c seem really similar to me.\n\nTBH, I wasn't aware of run_process_parallel(). Thanks for bringing it\nto my attention! :)\n\nHmm, I still have to look further into it, but I think parallel\ncheckout wouldn't be compatible with the run_process_parallel()\nframework, in its current form.\n\nThe first obstacle would be that, IIUC, the framework only allows\ncallers to pass information to the child processes through argv,\nright? I saw that you already have a patch lifting this limitation\n[1], but the feed_pipe callback requires a strbuf and parallel\ncheckout communicates through pkt-line. This is important because,\notherwise, the workers wouldn't know where each parallel_checkout_item\nfinishes. (The size of each item is variable and, since we send binary\ndata, it might contain anything, even bytes normally used as delimiter\nsuch as '\\0' or '\\n'.)\n\nAnother difficulty is that the framework combines the child processes'\nstdout and stderr, dumping it to the foreground process' stderr.\nParallel checkout expects workers to print errors to stderr, but it\nneeds the stdout of each worker to get the results back. This is used\nboth to get stat() data from the workers and to advance the checkout\nprogress bar. I see that you've also sent a patch adding more\nflexibility in this area [2], but I'm not sure if parallel checkout\ncould use it without pkt-lines and separating stdout from stderr\n(although, of course, the latter could be implemented in the framework\nas an optional feature).\n\nAlso, we might want to later improve the main-process<=>worker\nprotocol by adding work stealing. This might help in the workload\nbalancing while still minimizing the chances of having multiple\nworkers writing to the same part of the working tree (as would happen\nwith round-robin, for example). I already have a rough patch for this\n[3], but it needs to be timed and tested further. With work stealing,\nthe protocol becomes a little more complex, so it might not be\nsuitable for the callback-style interface of run_process_parallel().\n\nI'm not really sure... I guess parallel checkout could use\nrun_process_parallel(), but it seems to me that it would require a\ngood amount of work on the framework and parallel checkout itself. I\ndon't know if it would be worth it.\n\n[1]: https://lore.kernel.org/git/20201205014607.1464119-15-emilyshaffer@google.com/\n[2]: https://lore.kernel.org/git/20201205014607.1464119-17-emilyshaffer@google.com/\n[3]: https://github.com/matheustavares/git/commit/a483df570a3cdd1165354388ea3c9fcc0ec66aaf\n"},{"id":"420027","messageId":"cover.1616508954.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1608128666.git.matheus.bernardino@usp.br","subject":"[PATCH v6 0/9] Parallel Checkout (part 1)","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2021-03-23T14:19:27Z","receivedAt":"2021-03-23T14:21:02Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Preparatory API changes for parallel checkout. This version was rebased\non top of 'master'. The only change required in this change of base was\nthe `entry.h` inclusion on `builtin/stash.c`, at the commit that creates\nthis header file.\n\nJeff Hostetler (4):\n  convert: make convert_attrs() and convert structs public\n  convert: add [async_]convert_to_working_tree_ca() variants\n  convert: add get_stream_filter_ca() variant\n  convert: add classification for conv_attrs struct\n\nMatheus Tavares (5):\n  entry: extract a header file for entry.c functions\n  entry: make fstat_output() and read_blob_entry() public\n  entry: extract update_ce_after_write() from write_entry()\n  entry: move conv_attrs lookup up to checkout_entry()\n  entry: add checkout_entry_ca() taking preloaded conv_attrs\n\n apply.c                  |   1 +\n builtin/checkout-index.c |   1 +\n builtin/checkout.c       |   1 +\n builtin/difftool.c       |   1 +\n builtin/stash.c          |   1 +\n cache.h                  |  24 -------\n convert.c                | 143 ++++++++++++++++++++-------------------\n convert.h                |  96 +++++++++++++++++++++++---\n entry.c                  |  85 +++++++++++++----------\n entry.h                  |  59 ++++++++++++++++\n unpack-trees.c           |   1 +\n 11 files changed, 276 insertions(+), 137 deletions(-)\n create mode 100644 entry.h\n\nRange-diff against v5:\n 1:  9909ccee14 =  1:  5e8a1b6a1c convert: make convert_attrs() and convert structs public\n 2:  ec4e645aea =  2:  91d1a3063b convert: add [async_]convert_to_working_tree_ca() variants\n 3:  1834a54dfd =  3:  ab1bf85b75 convert: add get_stream_filter_ca() variant\n 4:  b26022af45 =  4:  01ac6176cc convert: add classification for conv_attrs struct\n 5:  837fccde5b !  5:  4d85af1579 entry: extract a header file for entry.c functions\n    @@ builtin/difftool.c\n      static int trust_exit_code;\n      \n     \n    + ## builtin/stash.c ##\n    +@@\n    + #include \"log-tree.h\"\n    + #include \"diffcore.h\"\n    + #include \"exec-cmd.h\"\n    ++#include \"entry.h\"\n    + \n    + #define INCLUDE_ALL_FILES 2\n    + \n    +\n      ## cache.h ##\n     @@ cache.h: const char *show_ident_date(const struct ident_split *id,\n       */\n 6:  231e81fb82 =  6:  af8b1691cc entry: make fstat_output() and read_blob_entry() public\n 7:  2fd8d35242 =  7:  b28e518c0d entry: extract update_ce_after_write() from write_entry()\n 8:  7531ad195b =  8:  87b9d4590a entry: move conv_attrs lookup up to checkout_entry()\n 9:  bf6b7259cb =  9:  aa36bfee87 entry: add checkout_entry_ca() taking preloaded conv_attrs\n-- \n2.30.1\n\n"},{"id":"420028","messageId":"5e8a1b6a1cb612dd5012a2d8866f16535e1edbf5.1616508954.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1616508954.git.matheus.bernardino@usp.br","subject":"[PATCH v6 1/9] convert: make convert_attrs() and convert structs public","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2021-03-23T14:19:28Z","receivedAt":"2021-03-23T14:21:33Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nMove convert_attrs() declaration from convert.c to convert.h, together\nwith the conv_attrs struct and the crlf_action enum. This function and\nthe data structures will be used outside convert.c in the upcoming\nparallel checkout implementation. Note that crlf_action is renamed to\nconvert_crlf_action, which is more appropriate for the global namespace.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 35 ++++++++---------------------------\n convert.h | 24 ++++++++++++++++++++++++\n 2 files changed, 32 insertions(+), 27 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex 2d3a5a713c..d0a1a4ad9b 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -24,17 +24,6 @@\n #define CONVERT_STAT_BITS_TXT_CRLF  0x2\n #define CONVERT_STAT_BITS_BIN       0x4\n \n-enum crlf_action {\n-\tCRLF_UNDEFINED,\n-\tCRLF_BINARY,\n-\tCRLF_TEXT,\n-\tCRLF_TEXT_INPUT,\n-\tCRLF_TEXT_CRLF,\n-\tCRLF_AUTO,\n-\tCRLF_AUTO_INPUT,\n-\tCRLF_AUTO_CRLF\n-};\n-\n struct text_stat {\n \t/* NUL, CR, LF and CRLF counts */\n \tunsigned nul, lonecr, lonelf, crlf;\n@@ -172,7 +161,7 @@ static int text_eol_is_crlf(void)\n \treturn 0;\n }\n \n-static enum eol output_eol(enum crlf_action crlf_action)\n+static enum eol output_eol(enum convert_crlf_action crlf_action)\n {\n \tswitch (crlf_action) {\n \tcase CRLF_BINARY:\n@@ -246,7 +235,7 @@ static int has_crlf_in_index(const struct index_state *istate, const char *path)\n }\n \n static int will_convert_lf_to_crlf(struct text_stat *stats,\n-\t\t\t\t   enum crlf_action crlf_action)\n+\t\t\t\t   enum convert_crlf_action crlf_action)\n {\n \tif (output_eol(crlf_action) != EOL_CRLF)\n \t\treturn 0;\n@@ -499,7 +488,7 @@ static int encode_to_worktree(const char *path, const char *src, size_t src_len,\n static int crlf_to_git(const struct index_state *istate,\n \t\t       const char *path, const char *src, size_t len,\n \t\t       struct strbuf *buf,\n-\t\t       enum crlf_action crlf_action, int conv_flags)\n+\t\t       enum convert_crlf_action crlf_action, int conv_flags)\n {\n \tstruct text_stat stats;\n \tchar *dst;\n@@ -585,8 +574,8 @@ static int crlf_to_git(const struct index_state *istate,\n \treturn 1;\n }\n \n-static int crlf_to_worktree(const char *src, size_t len,\n-\t\t\t    struct strbuf *buf, enum crlf_action crlf_action)\n+static int crlf_to_worktree(const char *src, size_t len, struct strbuf *buf,\n+\t\t\t    enum convert_crlf_action crlf_action)\n {\n \tchar *to_free = NULL;\n \tstruct text_stat stats;\n@@ -1247,7 +1236,7 @@ static const char *git_path_check_encoding(struct attr_check_item *check)\n \treturn value;\n }\n \n-static enum crlf_action git_path_check_crlf(struct attr_check_item *check)\n+static enum convert_crlf_action git_path_check_crlf(struct attr_check_item *check)\n {\n \tconst char *value = check->value;\n \n@@ -1297,18 +1286,10 @@ static int git_path_check_ident(struct attr_check_item *check)\n \treturn !!ATTR_TRUE(value);\n }\n \n-struct conv_attrs {\n-\tstruct convert_driver *drv;\n-\tenum crlf_action attr_action; /* What attr says */\n-\tenum crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n-\tint ident;\n-\tconst char *working_tree_encoding; /* Supported encoding or default encoding if NULL */\n-};\n-\n static struct attr_check *check;\n \n-static void convert_attrs(const struct index_state *istate,\n-\t\t\t  struct conv_attrs *ca, const char *path)\n+void convert_attrs(const struct index_state *istate,\n+\t\t   struct conv_attrs *ca, const char *path)\n {\n \tstruct attr_check_item *ccheck = NULL;\n \ndiff --git a/convert.h b/convert.h\nindex e29d1026a6..5678e99922 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -63,6 +63,30 @@ struct checkout_metadata {\n \tstruct object_id blob;\n };\n \n+enum convert_crlf_action {\n+\tCRLF_UNDEFINED,\n+\tCRLF_BINARY,\n+\tCRLF_TEXT,\n+\tCRLF_TEXT_INPUT,\n+\tCRLF_TEXT_CRLF,\n+\tCRLF_AUTO,\n+\tCRLF_AUTO_INPUT,\n+\tCRLF_AUTO_CRLF\n+};\n+\n+struct convert_driver;\n+\n+struct conv_attrs {\n+\tstruct convert_driver *drv;\n+\tenum convert_crlf_action attr_action; /* What attr says */\n+\tenum convert_crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n+\tint ident;\n+\tconst char *working_tree_encoding; /* Supported encoding or default encoding if NULL */\n+};\n+\n+void convert_attrs(const struct index_state *istate,\n+\t\t   struct conv_attrs *ca, const char *path);\n+\n extern enum eol core_eol;\n extern char *check_roundtrip_encoding;\n const char *get_cached_convert_stats_ascii(const struct index_state *istate,\n-- \n2.30.1\n\n"},{"id":"420029","messageId":"91d1a3063b05de58eeecfdf1f7bb7931974156b3.1616508954.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1616508954.git.matheus.bernardino@usp.br","subject":"[PATCH v6 2/9] convert: add [async_]convert_to_working_tree_ca() variants","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2021-03-23T14:19:29Z","receivedAt":"2021-03-23T14:21:34Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSeparate the attribute gathering from the actual conversion by adding\n_ca() variants of the conversion functions. These variants receive a\nprecomputed 'struct conv_attrs', not relying, thus, on an index state.\nThey will be used in a future patch adding parallel checkout support,\nfor two reasons:\n\n- We will already load the conversion attributes in checkout_entry(),\n  before conversion, to decide whether a path is eligible for parallel\n  checkout. Therefore, it would be wasteful to load them again later,\n  for the actual conversion.\n\n- The parallel workers will be responsible for reading, converting and\n  writing blobs to the working tree. They won't have access to the main\n  process' index state, so they cannot load the attributes. Instead,\n  they will receive the preloaded ones and call the _ca() variant of\n  the conversion functions. Furthermore, the attributes machinery is\n  optimized to handle paths in sequential order, so it's better to leave\n  it for the main process, anyway.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 60 +++++++++++++++++++++++++++++--------------------------\n convert.h | 37 +++++++++++++++++++++++++---------\n 2 files changed, 60 insertions(+), 37 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex d0a1a4ad9b..f2be014af8 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1446,19 +1446,16 @@ void convert_to_git_filter_fd(const struct index_state *istate,\n \tident_to_git(dst->buf, dst->len, dst, ca.ident);\n }\n \n-static int convert_to_working_tree_internal(const struct index_state *istate,\n-\t\t\t\t\t    const char *path, const char *src,\n-\t\t\t\t\t    size_t len, struct strbuf *dst,\n-\t\t\t\t\t    int normalizing,\n-\t\t\t\t\t    const struct checkout_metadata *meta,\n-\t\t\t\t\t    struct delayed_checkout *dco)\n+static int convert_to_working_tree_ca_internal(const struct conv_attrs *ca,\n+\t\t\t\t\t       const char *path, const char *src,\n+\t\t\t\t\t       size_t len, struct strbuf *dst,\n+\t\t\t\t\t       int normalizing,\n+\t\t\t\t\t       const struct checkout_metadata *meta,\n+\t\t\t\t\t       struct delayed_checkout *dco)\n {\n \tint ret = 0, ret_filter = 0;\n-\tstruct conv_attrs ca;\n-\n-\tconvert_attrs(istate, &ca, path);\n \n-\tret |= ident_to_worktree(src, len, dst, ca.ident);\n+\tret |= ident_to_worktree(src, len, dst, ca->ident);\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\n@@ -1468,49 +1465,56 @@ static int convert_to_working_tree_internal(const struct index_state *istate,\n \t * is a smudge or process filter (even if the process filter doesn't\n \t * support smudge).  The filters might expect CRLFs.\n \t */\n-\tif ((ca.drv && (ca.drv->smudge || ca.drv->process)) || !normalizing) {\n-\t\tret |= crlf_to_worktree(src, len, dst, ca.crlf_action);\n+\tif ((ca->drv && (ca->drv->smudge || ca->drv->process)) || !normalizing) {\n+\t\tret |= crlf_to_worktree(src, len, dst, ca->crlf_action);\n \t\tif (ret) {\n \t\t\tsrc = dst->buf;\n \t\t\tlen = dst->len;\n \t\t}\n \t}\n \n-\tret |= encode_to_worktree(path, src, len, dst, ca.working_tree_encoding);\n+\tret |= encode_to_worktree(path, src, len, dst, ca->working_tree_encoding);\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\n \t}\n \n \tret_filter = apply_filter(\n-\t\tpath, src, len, -1, dst, ca.drv, CAP_SMUDGE, meta, dco);\n-\tif (!ret_filter && ca.drv && ca.drv->required)\n-\t\tdie(_(\"%s: smudge filter %s failed\"), path, ca.drv->name);\n+\t\tpath, src, len, -1, dst, ca->drv, CAP_SMUDGE, meta, dco);\n+\tif (!ret_filter && ca->drv && ca->drv->required)\n+\t\tdie(_(\"%s: smudge filter %s failed\"), path, ca->drv->name);\n \n \treturn ret | ret_filter;\n }\n \n-int async_convert_to_working_tree(const struct index_state *istate,\n-\t\t\t\t  const char *path, const char *src,\n-\t\t\t\t  size_t len, struct strbuf *dst,\n-\t\t\t\t  const struct checkout_metadata *meta,\n-\t\t\t\t  void *dco)\n+int async_convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t\t     const char *path, const char *src,\n+\t\t\t\t     size_t len, struct strbuf *dst,\n+\t\t\t\t     const struct checkout_metadata *meta,\n+\t\t\t\t     void *dco)\n {\n-\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco);\n+\treturn convert_to_working_tree_ca_internal(ca, path, src, len, dst, 0,\n+\t\t\t\t\t\t   meta, dco);\n }\n \n-int convert_to_working_tree(const struct index_state *istate,\n-\t\t\t    const char *path, const char *src,\n-\t\t\t    size_t len, struct strbuf *dst,\n-\t\t\t    const struct checkout_metadata *meta)\n+int convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t       const char *path, const char *src,\n+\t\t\t       size_t len, struct strbuf *dst,\n+\t\t\t       const struct checkout_metadata *meta)\n {\n-\treturn convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL);\n+\treturn convert_to_working_tree_ca_internal(ca, path, src, len, dst, 0,\n+\t\t\t\t\t\t   meta, NULL);\n }\n \n int renormalize_buffer(const struct index_state *istate, const char *path,\n \t\t       const char *src, size_t len, struct strbuf *dst)\n {\n-\tint ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL, NULL);\n+\tstruct conv_attrs ca;\n+\tint ret;\n+\n+\tconvert_attrs(istate, &ca, path);\n+\tret = convert_to_working_tree_ca_internal(&ca, path, src, len, dst, 1,\n+\t\t\t\t\t\t  NULL, NULL);\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\ndiff --git a/convert.h b/convert.h\nindex 5678e99922..a4838b5e5c 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -99,15 +99,34 @@ const char *get_convert_attr_ascii(const struct index_state *istate,\n int convert_to_git(const struct index_state *istate,\n \t\t   const char *path, const char *src, size_t len,\n \t\t   struct strbuf *dst, int conv_flags);\n-int convert_to_working_tree(const struct index_state *istate,\n-\t\t\t    const char *path, const char *src,\n-\t\t\t    size_t len, struct strbuf *dst,\n-\t\t\t    const struct checkout_metadata *meta);\n-int async_convert_to_working_tree(const struct index_state *istate,\n-\t\t\t\t  const char *path, const char *src,\n-\t\t\t\t  size_t len, struct strbuf *dst,\n-\t\t\t\t  const struct checkout_metadata *meta,\n-\t\t\t\t  void *dco);\n+int convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t       const char *path, const char *src,\n+\t\t\t       size_t len, struct strbuf *dst,\n+\t\t\t       const struct checkout_metadata *meta);\n+int async_convert_to_working_tree_ca(const struct conv_attrs *ca,\n+\t\t\t\t     const char *path, const char *src,\n+\t\t\t\t     size_t len, struct strbuf *dst,\n+\t\t\t\t     const struct checkout_metadata *meta,\n+\t\t\t\t     void *dco);\n+static inline int convert_to_working_tree(const struct index_state *istate,\n+\t\t\t\t\t  const char *path, const char *src,\n+\t\t\t\t\t  size_t len, struct strbuf *dst,\n+\t\t\t\t\t  const struct checkout_metadata *meta)\n+{\n+\tstruct conv_attrs ca;\n+\tconvert_attrs(istate, &ca, path);\n+\treturn convert_to_working_tree_ca(&ca, path, src, len, dst, meta);\n+}\n+static inline int async_convert_to_working_tree(const struct index_state *istate,\n+\t\t\t\t\t\tconst char *path, const char *src,\n+\t\t\t\t\t\tsize_t len, struct strbuf *dst,\n+\t\t\t\t\t\tconst struct checkout_metadata *meta,\n+\t\t\t\t\t\tvoid *dco)\n+{\n+\tstruct conv_attrs ca;\n+\tconvert_attrs(istate, &ca, path);\n+\treturn async_convert_to_working_tree_ca(&ca, path, src, len, dst, meta, dco);\n+}\n int async_query_available_blobs(const char *cmd,\n \t\t\t\tstruct string_list *available_paths);\n int renormalize_buffer(const struct index_state *istate,\n-- \n2.30.1\n\n"},{"id":"420030","messageId":"01ac6176ccd8fb20372cf72b6aee33d5ed98c54f.1616508954.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1616508954.git.matheus.bernardino@usp.br","subject":"[PATCH v6 4/9] convert: add classification for conv_attrs struct","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2021-03-23T14:19:31Z","receivedAt":"2021-03-23T14:21:34Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate `enum conv_attrs_classification` to express the different ways\nthat attributes are handled for a blob during checkout.\n\nThis will be used in a later commit when deciding whether to add a file\nto the parallel or delayed queue during checkout. For now, we can also\nuse it in get_stream_filter_ca() to simplify the function (as the\nclassifying logic is the same).\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 26 +++++++++++++++++++-------\n convert.h | 33 +++++++++++++++++++++++++++++++++\n 2 files changed, 52 insertions(+), 7 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex 3f39c62cac..3298e4acff 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1953,13 +1953,7 @@ struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n {\n \tstruct stream_filter *filter = NULL;\n \n-\tif (ca->drv && (ca->drv->process || ca->drv->smudge || ca->drv->clean))\n-\t\treturn NULL;\n-\n-\tif (ca->working_tree_encoding)\n-\t\treturn NULL;\n-\n-\tif (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF)\n+\tif (classify_conv_attrs(ca) != CA_CLASS_STREAMABLE)\n \t\treturn NULL;\n \n \tif (ca->ident)\n@@ -2015,3 +2009,21 @@ void clone_checkout_metadata(struct checkout_metadata *dst,\n \tif (blob)\n \t\toidcpy(&dst->blob, blob);\n }\n+\n+enum conv_attrs_classification classify_conv_attrs(const struct conv_attrs *ca)\n+{\n+\tif (ca->drv) {\n+\t\tif (ca->drv->process)\n+\t\t\treturn CA_CLASS_INCORE_PROCESS;\n+\t\tif (ca->drv->smudge || ca->drv->clean)\n+\t\t\treturn CA_CLASS_INCORE_FILTER;\n+\t}\n+\n+\tif (ca->working_tree_encoding)\n+\t\treturn CA_CLASS_INCORE;\n+\n+\tif (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF)\n+\t\treturn CA_CLASS_INCORE;\n+\n+\treturn CA_CLASS_STREAMABLE;\n+}\ndiff --git a/convert.h b/convert.h\nindex 484b50965d..43e567a59b 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -200,4 +200,37 @@ int stream_filter(struct stream_filter *,\n \t\t  const char *input, size_t *isize_p,\n \t\t  char *output, size_t *osize_p);\n \n+enum conv_attrs_classification {\n+\t/*\n+\t * The blob must be loaded into a buffer before it can be\n+\t * smudged. All smudging is done in-proc.\n+\t */\n+\tCA_CLASS_INCORE,\n+\n+\t/*\n+\t * The blob must be loaded into a buffer, but uses a\n+\t * single-file driver filter, such as rot13.\n+\t */\n+\tCA_CLASS_INCORE_FILTER,\n+\n+\t/*\n+\t * The blob must be loaded into a buffer, but uses a\n+\t * long-running driver process, such as LFS. This might or\n+\t * might not use delayed operations. (The important thing is\n+\t * that there is a single subordinate long-running process\n+\t * handling all associated blobs and in case of delayed\n+\t * operations, may hold per-blob state.)\n+\t */\n+\tCA_CLASS_INCORE_PROCESS,\n+\n+\t/*\n+\t * The blob can be streamed and smudged without needing to\n+\t * completely read it into a buffer.\n+\t */\n+\tCA_CLASS_STREAMABLE,\n+};\n+\n+enum conv_attrs_classification classify_conv_attrs(\n+\tconst struct conv_attrs *ca);\n+\n #endif /* CONVERT_H */\n-- \n2.30.1\n\n"},{"id":"420031","messageId":"ab1bf85b75118a205cb47db29143f997a08a931e.1616508954.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1616508954.git.matheus.bernardino@usp.br","subject":"[PATCH v6 3/9] convert: add get_stream_filter_ca() variant","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2021-03-23T14:19:30Z","receivedAt":"2021-03-23T14:21:34Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nLike the previous patch, we will also need to call get_stream_filter()\nwith a precomputed `struct conv_attrs`, when we add support for parallel\ncheckout workers. So add the _ca() variant which takes the conversion\nattributes struct as a parameter.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n convert.c | 28 +++++++++++++++++-----------\n convert.h |  2 ++\n 2 files changed, 19 insertions(+), 11 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex f2be014af8..3f39c62cac 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1941,34 +1941,31 @@ static struct stream_filter *ident_filter(const struct object_id *oid)\n }\n \n /*\n- * Return an appropriately constructed filter for the path, or NULL if\n+ * Return an appropriately constructed filter for the given ca, or NULL if\n  * the contents cannot be filtered without reading the whole thing\n  * in-core.\n  *\n  * Note that you would be crazy to set CRLF, smudge/clean or ident to a\n  * large binary blob you would want us not to slurp into the memory!\n  */\n-struct stream_filter *get_stream_filter(const struct index_state *istate,\n-\t\t\t\t\tconst char *path,\n-\t\t\t\t\tconst struct object_id *oid)\n+struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n+\t\t\t\t\t   const struct object_id *oid)\n {\n-\tstruct conv_attrs ca;\n \tstruct stream_filter *filter = NULL;\n \n-\tconvert_attrs(istate, &ca, path);\n-\tif (ca.drv && (ca.drv->process || ca.drv->smudge || ca.drv->clean))\n+\tif (ca->drv && (ca->drv->process || ca->drv->smudge || ca->drv->clean))\n \t\treturn NULL;\n \n-\tif (ca.working_tree_encoding)\n+\tif (ca->working_tree_encoding)\n \t\treturn NULL;\n \n-\tif (ca.crlf_action == CRLF_AUTO || ca.crlf_action == CRLF_AUTO_CRLF)\n+\tif (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF)\n \t\treturn NULL;\n \n-\tif (ca.ident)\n+\tif (ca->ident)\n \t\tfilter = ident_filter(oid);\n \n-\tif (output_eol(ca.crlf_action) == EOL_CRLF)\n+\tif (output_eol(ca->crlf_action) == EOL_CRLF)\n \t\tfilter = cascade_filter(filter, lf_to_crlf_filter());\n \telse\n \t\tfilter = cascade_filter(filter, &null_filter_singleton);\n@@ -1976,6 +1973,15 @@ struct stream_filter *get_stream_filter(const struct index_state *istate,\n \treturn filter;\n }\n \n+struct stream_filter *get_stream_filter(const struct index_state *istate,\n+\t\t\t\t\tconst char *path,\n+\t\t\t\t\tconst struct object_id *oid)\n+{\n+\tstruct conv_attrs ca;\n+\tconvert_attrs(istate, &ca, path);\n+\treturn get_stream_filter_ca(&ca, oid);\n+}\n+\n void free_stream_filter(struct stream_filter *filter)\n {\n \tfilter->vtbl->free(filter);\ndiff --git a/convert.h b/convert.h\nindex a4838b5e5c..484b50965d 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -179,6 +179,8 @@ struct stream_filter; /* opaque */\n struct stream_filter *get_stream_filter(const struct index_state *istate,\n \t\t\t\t\tconst char *path,\n \t\t\t\t\tconst struct object_id *);\n+struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n+\t\t\t\t\t   const struct object_id *oid);\n void free_stream_filter(struct stream_filter *);\n int is_null_stream_filter(struct stream_filter *);\n \n-- \n2.30.1\n\n"},{"id":"420032","messageId":"af8b1691cc7f634a9a9c0a65ab15dc22dc3b1007.1616508954.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1616508954.git.matheus.bernardino@usp.br","subject":"[PATCH v6 6/9] entry: make fstat_output() and read_blob_entry() public","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2021-03-23T14:19:33Z","receivedAt":"2021-03-23T14:21:34Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"These two functions will be used by the parallel checkout code, so let's\nmake them public. Note: fstat_output() is renamed to\nfstat_checkout_output(), now that it has become public, seeking to avoid\nfuture name collisions.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 8 ++++----\n entry.h | 3 +++\n 2 files changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex c3e511bfb3..1e2d9f7baa 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -84,7 +84,7 @@ static int create_file(const char *path, unsigned int mode)\n \treturn open(path, O_WRONLY | O_CREAT | O_EXCL, mode);\n }\n \n-static void *read_blob_entry(const struct cache_entry *ce, unsigned long *size)\n+void *read_blob_entry(const struct cache_entry *ce, unsigned long *size)\n {\n \tenum object_type type;\n \tvoid *blob_data = read_object_file(&ce->oid, &type, size);\n@@ -109,7 +109,7 @@ static int open_output_fd(char *path, const struct cache_entry *ce, int to_tempf\n \t}\n }\n \n-static int fstat_output(int fd, const struct checkout *state, struct stat *st)\n+int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st)\n {\n \t/* use fstat() only when path == ce->name */\n \tif (fstat_is_reliable() &&\n@@ -132,7 +132,7 @@ static int streaming_write_entry(const struct cache_entry *ce, char *path,\n \t\treturn -1;\n \n \tresult |= stream_blob_to_fd(fd, &ce->oid, filter, 1);\n-\t*fstat_done = fstat_output(fd, state, statbuf);\n+\t*fstat_done = fstat_checkout_output(fd, state, statbuf);\n \tresult |= close(fd);\n \n \tif (result)\n@@ -346,7 +346,7 @@ static int write_entry(struct cache_entry *ce,\n \n \t\twrote = write_in_full(fd, new_blob, size);\n \t\tif (!to_tempfile)\n-\t\t\tfstat_done = fstat_output(fd, state, &st);\n+\t\t\tfstat_done = fstat_checkout_output(fd, state, &st);\n \t\tclose(fd);\n \t\tfree(new_blob);\n \t\tif (wrote < 0)\ndiff --git a/entry.h b/entry.h\nindex acbbb90220..60df93ca78 100644\n--- a/entry.h\n+++ b/entry.h\n@@ -39,4 +39,7 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n  */\n void unlink_entry(const struct cache_entry *ce);\n \n+void *read_blob_entry(const struct cache_entry *ce, unsigned long *size);\n+int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st);\n+\n #endif /* ENTRY_H */\n-- \n2.30.1\n\n"},{"id":"420033","messageId":"87b9d4590aa182d4c4467ea9ee3a87bb53940a66.1616508954.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1616508954.git.matheus.bernardino@usp.br","subject":"[PATCH v6 8/9] entry: move conv_attrs lookup up to checkout_entry()","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2021-03-23T14:19:35Z","receivedAt":"2021-03-23T14:21:34Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"In a following patch, checkout_entry() will use conv_attrs to decide\nwhether an entry should be enqueued for parallel checkout or not. But\nthe attributes lookup only happens lower in this call stack. To avoid\nthe unnecessary work of loading the attributes twice, let's move it up\nto checkout_entry(), and pass the loaded struct down to write_entry().\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 38 +++++++++++++++++++++++++++-----------\n 1 file changed, 27 insertions(+), 11 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex 4cf0db352f..6339d54843 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -263,8 +263,9 @@ void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,\n \t}\n }\n \n-static int write_entry(struct cache_entry *ce,\n-\t\t       char *path, const struct checkout *state, int to_tempfile)\n+/* Note: ca is used (and required) iff the entry refers to a regular file. */\n+static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca,\n+\t\t       const struct checkout *state, int to_tempfile)\n {\n \tunsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;\n \tstruct delayed_checkout *dco = state->delayed_checkout;\n@@ -281,8 +282,7 @@ static int write_entry(struct cache_entry *ce,\n \tclone_checkout_metadata(&meta, &state->meta, &ce->oid);\n \n \tif (ce_mode_s_ifmt == S_IFREG) {\n-\t\tstruct stream_filter *filter = get_stream_filter(state->istate, ce->name,\n-\t\t\t\t\t\t\t\t &ce->oid);\n+\t\tstruct stream_filter *filter = get_stream_filter_ca(ca, &ce->oid);\n \t\tif (filter &&\n \t\t    !streaming_write_entry(ce, path, filter,\n \t\t\t\t\t   state, to_tempfile,\n@@ -329,14 +329,17 @@ static int write_entry(struct cache_entry *ce,\n \t\t * Convert from git internal format to working tree format\n \t\t */\n \t\tif (dco && dco->state != CE_NO_DELAY) {\n-\t\t\tret = async_convert_to_working_tree(state->istate, ce->name, new_blob,\n-\t\t\t\t\t\t\t    size, &buf, &meta, dco);\n+\t\t\tret = async_convert_to_working_tree_ca(ca, ce->name,\n+\t\t\t\t\t\t\t       new_blob, size,\n+\t\t\t\t\t\t\t       &buf, &meta, dco);\n \t\t\tif (ret && string_list_has_string(&dco->paths, ce->name)) {\n \t\t\t\tfree(new_blob);\n \t\t\t\tgoto delayed;\n \t\t\t}\n-\t\t} else\n-\t\t\tret = convert_to_working_tree(state->istate, ce->name, new_blob, size, &buf, &meta);\n+\t\t} else {\n+\t\t\tret = convert_to_working_tree_ca(ca, ce->name, new_blob,\n+\t\t\t\t\t\t\t size, &buf, &meta);\n+\t\t}\n \n \t\tif (ret) {\n \t\t\tfree(new_blob);\n@@ -442,6 +445,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n {\n \tstatic struct strbuf path = STRBUF_INIT;\n \tstruct stat st;\n+\tstruct conv_attrs ca_buf, *ca = NULL;\n \n \tif (ce->ce_flags & CE_WT_REMOVE) {\n \t\tif (topath)\n@@ -454,8 +458,13 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t\treturn 0;\n \t}\n \n-\tif (topath)\n-\t\treturn write_entry(ce, topath, state, 1);\n+\tif (topath) {\n+\t\tif (S_ISREG(ce->ce_mode)) {\n+\t\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n+\t\t\tca = &ca_buf;\n+\t\t}\n+\t\treturn write_entry(ce, topath, ca, state, 1);\n+\t}\n \n \tstrbuf_reset(&path);\n \tstrbuf_add(&path, state->base_dir, state->base_dir_len);\n@@ -517,9 +526,16 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t\treturn 0;\n \n \tcreate_directories(path.buf, path.len, state);\n+\n \tif (nr_checkouts)\n \t\t(*nr_checkouts)++;\n-\treturn write_entry(ce, path.buf, state, 0);\n+\n+\tif (S_ISREG(ce->ce_mode)) {\n+\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n+\t\tca = &ca_buf;\n+\t}\n+\n+\treturn write_entry(ce, path.buf, ca, state, 0);\n }\n \n void unlink_entry(const struct cache_entry *ce)\n-- \n2.30.1\n\n"},{"id":"420034","messageId":"4d85af1579738a3f2f5f3815007c3f8bfd1a31f8.1616508954.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1616508954.git.matheus.bernardino@usp.br","subject":"[PATCH v6 5/9] entry: extract a header file for entry.c functions","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2021-03-23T14:19:32Z","receivedAt":"2021-03-23T14:21:34Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"The declarations of entry.c's public functions and structures currently\nreside in cache.h. Although not many, they contribute to the size of\ncache.h and, when changed, cause the unnecessary recompilation of\nmodules that don't really use these functions. So let's move them to a\nnew entry.h header. While at it let's also move a comment related to\ncheckout_entry() from entry.c to entry.h as it's more useful to describe\nthe function there.\n\nOriginal-patch-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n apply.c                  |  1 +\n builtin/checkout-index.c |  1 +\n builtin/checkout.c       |  1 +\n builtin/difftool.c       |  1 +\n builtin/stash.c          |  1 +\n cache.h                  | 24 -----------------------\n entry.c                  |  9 +--------\n entry.h                  | 42 ++++++++++++++++++++++++++++++++++++++++\n unpack-trees.c           |  1 +\n 9 files changed, 49 insertions(+), 32 deletions(-)\n create mode 100644 entry.h\n\ndiff --git a/apply.c b/apply.c\nindex 6695a931e9..466f880d73 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -21,6 +21,7 @@\n #include \"quote.h\"\n #include \"rerere.h\"\n #include \"apply.h\"\n+#include \"entry.h\"\n \n struct gitdiff_data {\n \tstruct strbuf *root;\ndiff --git a/builtin/checkout-index.c b/builtin/checkout-index.c\nindex 023e49e271..c0bf4ac1b2 100644\n--- a/builtin/checkout-index.c\n+++ b/builtin/checkout-index.c\n@@ -11,6 +11,7 @@\n #include \"quote.h\"\n #include \"cache-tree.h\"\n #include \"parse-options.h\"\n+#include \"entry.h\"\n \n #define CHECKOUT_ALL 4\n static int nul_term_line;\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 2d6550bc3c..06c90d76e8 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -26,6 +26,7 @@\n #include \"unpack-trees.h\"\n #include \"wt-status.h\"\n #include \"xdiff-interface.h\"\n+#include \"entry.h\"\n \n static const char * const checkout_usage[] = {\n \tN_(\"git checkout [<options>] <branch>\"),\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 6e18e623fd..ef25729d49 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -23,6 +23,7 @@\n #include \"lockfile.h\"\n #include \"object-store.h\"\n #include \"dir.h\"\n+#include \"entry.h\"\n \n static int trust_exit_code;\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 3477e940e3..0cdcc75618 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -15,6 +15,7 @@\n #include \"log-tree.h\"\n #include \"diffcore.h\"\n #include \"exec-cmd.h\"\n+#include \"entry.h\"\n \n #define INCLUDE_ALL_FILES 2\n \ndiff --git a/cache.h b/cache.h\nindex 6fda8091f1..5d45d145fa 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1621,30 +1621,6 @@ const char *show_ident_date(const struct ident_split *id,\n  */\n int ident_cmp(const struct ident_split *, const struct ident_split *);\n \n-struct checkout {\n-\tstruct index_state *istate;\n-\tconst char *base_dir;\n-\tint base_dir_len;\n-\tstruct delayed_checkout *delayed_checkout;\n-\tstruct checkout_metadata meta;\n-\tunsigned force:1,\n-\t\t quiet:1,\n-\t\t not_new:1,\n-\t\t clone:1,\n-\t\t refresh_cache:1;\n-};\n-#define CHECKOUT_INIT { NULL, \"\" }\n-\n-#define TEMPORARY_FILENAME_LENGTH 25\n-int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath, int *nr_checkouts);\n-void enable_delayed_checkout(struct checkout *state);\n-int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n-/*\n- * Unlink the last component and schedule the leading directories for\n- * removal, such that empty directories get removed.\n- */\n-void unlink_entry(const struct cache_entry *ce);\n-\n struct cache_def {\n \tstruct strbuf path;\n \tint flags;\ndiff --git a/entry.c b/entry.c\nindex 7b9f43716f..c3e511bfb3 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -6,6 +6,7 @@\n #include \"submodule.h\"\n #include \"progress.h\"\n #include \"fsmonitor.h\"\n+#include \"entry.h\"\n \n static void create_directories(const char *path, int path_len,\n \t\t\t       const struct checkout *state)\n@@ -429,14 +430,6 @@ static void mark_colliding_entries(const struct checkout *state,\n \t}\n }\n \n-/*\n- * Write the contents from ce out to the working tree.\n- *\n- * When topath[] is not NULL, instead of writing to the working tree\n- * file named by ce, a temporary file is created by this function and\n- * its name is returned in topath[], which must be able to hold at\n- * least TEMPORARY_FILENAME_LENGTH bytes long.\n- */\n int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t\t   char *topath, int *nr_checkouts)\n {\ndiff --git a/entry.h b/entry.h\nnew file mode 100644\nindex 0000000000..acbbb90220\n--- /dev/null\n+++ b/entry.h\n@@ -0,0 +1,42 @@\n+#ifndef ENTRY_H\n+#define ENTRY_H\n+\n+#include \"cache.h\"\n+#include \"convert.h\"\n+\n+struct checkout {\n+\tstruct index_state *istate;\n+\tconst char *base_dir;\n+\tint base_dir_len;\n+\tstruct delayed_checkout *delayed_checkout;\n+\tstruct checkout_metadata meta;\n+\tunsigned force:1,\n+\t\t quiet:1,\n+\t\t not_new:1,\n+\t\t clone:1,\n+\t\t refresh_cache:1;\n+};\n+#define CHECKOUT_INIT { NULL, \"\" }\n+\n+#define TEMPORARY_FILENAME_LENGTH 25\n+/*\n+ * Write the contents from ce out to the working tree.\n+ *\n+ * When topath[] is not NULL, instead of writing to the working tree\n+ * file named by ce, a temporary file is created by this function and\n+ * its name is returned in topath[], which must be able to hold at\n+ * least TEMPORARY_FILENAME_LENGTH bytes long.\n+ */\n+int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n+\t\t   char *topath, int *nr_checkouts);\n+\n+void enable_delayed_checkout(struct checkout *state);\n+int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n+\n+/*\n+ * Unlink the last component and schedule the leading directories for\n+ * removal, such that empty directories get removed.\n+ */\n+void unlink_entry(const struct cache_entry *ce);\n+\n+#endif /* ENTRY_H */\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 9af8e796b3..f6cc6a8117 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -16,6 +16,7 @@\n #include \"fsmonitor.h\"\n #include \"object-store.h\"\n #include \"promisor-remote.h\"\n+#include \"entry.h\"\n \n /*\n  * Error messages expected by scripts out of plumbing commands such as\n-- \n2.30.1\n\n"},{"id":"420035","messageId":"aa36bfee87d4a00707090336a9d159d23d7aed11.1616508954.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1616508954.git.matheus.bernardino@usp.br","subject":"[PATCH v6 9/9] entry: add checkout_entry_ca() taking preloaded conv_attrs","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2021-03-23T14:19:36Z","receivedAt":"2021-03-23T14:21:34Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"The parallel checkout machinery will call checkout_entry() for entries\nthat could not be written in parallel due to path collisions. At this\npoint, we will already be holding the conversion attributes for each\nentry, and it would be wasteful to let checkout_entry() load these\nagain. Instead, let's add the checkout_entry_ca() variant, which\noptionally takes a preloaded conv_attrs struct.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 11 ++++++-----\n entry.h | 16 ++++++++++++++--\n 2 files changed, 20 insertions(+), 7 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex 6339d54843..2ce16414a7 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -440,12 +440,13 @@ static void mark_colliding_entries(const struct checkout *state,\n \t}\n }\n \n-int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n-\t\t   char *topath, int *nr_checkouts)\n+int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,\n+\t\t      const struct checkout *state, char *topath,\n+\t\t      int *nr_checkouts)\n {\n \tstatic struct strbuf path = STRBUF_INIT;\n \tstruct stat st;\n-\tstruct conv_attrs ca_buf, *ca = NULL;\n+\tstruct conv_attrs ca_buf;\n \n \tif (ce->ce_flags & CE_WT_REMOVE) {\n \t\tif (topath)\n@@ -459,7 +460,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \t}\n \n \tif (topath) {\n-\t\tif (S_ISREG(ce->ce_mode)) {\n+\t\tif (S_ISREG(ce->ce_mode) && !ca) {\n \t\t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n \t\t\tca = &ca_buf;\n \t\t}\n@@ -530,7 +531,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n \tif (nr_checkouts)\n \t\t(*nr_checkouts)++;\n \n-\tif (S_ISREG(ce->ce_mode)) {\n+\tif (S_ISREG(ce->ce_mode) && !ca) {\n \t\tconvert_attrs(state->istate, &ca_buf, ce->name);\n \t\tca = &ca_buf;\n \t}\ndiff --git a/entry.h b/entry.h\nindex ea7290bcd5..b8c0e170dc 100644\n--- a/entry.h\n+++ b/entry.h\n@@ -26,9 +26,21 @@ struct checkout {\n  * file named by ce, a temporary file is created by this function and\n  * its name is returned in topath[], which must be able to hold at\n  * least TEMPORARY_FILENAME_LENGTH bytes long.\n+ *\n+ * With checkout_entry_ca(), callers can optionally pass a preloaded\n+ * conv_attrs struct (to avoid reloading it), when ce refers to a\n+ * regular file. If ca is NULL, the attributes will be loaded\n+ * internally when (and if) needed.\n  */\n-int checkout_entry(struct cache_entry *ce, const struct checkout *state,\n-\t\t   char *topath, int *nr_checkouts);\n+int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,\n+\t\t      const struct checkout *state, char *topath,\n+\t\t      int *nr_checkouts);\n+static inline int checkout_entry(struct cache_entry *ce,\n+\t\t\t\t const struct checkout *state, char *topath,\n+\t\t\t\t int *nr_checkouts)\n+{\n+\treturn checkout_entry_ca(ce, NULL, state, topath, nr_checkouts);\n+}\n \n void enable_delayed_checkout(struct checkout *state);\n int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);\n-- \n2.30.1\n\n"},{"id":"420036","messageId":"b28e518c0d3459d8042f54a21f8b80df84a840c9.1616508954.git.matheus.bernardino@usp.br","threadId":"54525","inReplyTo":"cover.1616508954.git.matheus.bernardino@usp.br","subject":"[PATCH v6 7/9] entry: extract update_ce_after_write() from write_entry()","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2021-03-23T14:19:34Z","receivedAt":"2021-03-23T14:21:34Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"The code that updates the in-memory index information after an entry is\nwritten currently resides in write_entry(). Extract it to a public\nfunction so that it can be called by the parallel checkout functions,\noutside entry.c, in a later patch.\n\nSigned-off-by: Matheus Tavares <matheus.bernardino@usp.br>\n---\n entry.c | 25 ++++++++++++++++---------\n entry.h |  2 ++\n 2 files changed, 18 insertions(+), 9 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex 1e2d9f7baa..4cf0db352f 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -251,6 +251,18 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts)\n \treturn errs;\n }\n \n+void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,\n+\t\t\t   struct stat *st)\n+{\n+\tif (state->refresh_cache) {\n+\t\tassert(state->istate);\n+\t\tfill_stat_cache_info(state->istate, ce, st);\n+\t\tce->ce_flags |= CE_UPDATE_IN_BASE;\n+\t\tmark_fsmonitor_invalid(state->istate, ce);\n+\t\tstate->istate->cache_changed |= CE_ENTRY_CHANGED;\n+\t}\n+}\n+\n static int write_entry(struct cache_entry *ce,\n \t\t       char *path, const struct checkout *state, int to_tempfile)\n {\n@@ -371,15 +383,10 @@ static int write_entry(struct cache_entry *ce,\n \n finish:\n \tif (state->refresh_cache) {\n-\t\tassert(state->istate);\n-\t\tif (!fstat_done)\n-\t\t\tif (lstat(ce->name, &st) < 0)\n-\t\t\t\treturn error_errno(\"unable to stat just-written file %s\",\n-\t\t\t\t\t\t   ce->name);\n-\t\tfill_stat_cache_info(state->istate, ce, &st);\n-\t\tce->ce_flags |= CE_UPDATE_IN_BASE;\n-\t\tmark_fsmonitor_invalid(state->istate, ce);\n-\t\tstate->istate->cache_changed |= CE_ENTRY_CHANGED;\n+\t\tif (!fstat_done && lstat(ce->name, &st) < 0)\n+\t\t\treturn error_errno(\"unable to stat just-written file %s\",\n+\t\t\t\t\t   ce->name);\n+\t\tupdate_ce_after_write(state, ce , &st);\n \t}\n delayed:\n \treturn 0;\ndiff --git a/entry.h b/entry.h\nindex 60df93ca78..ea7290bcd5 100644\n--- a/entry.h\n+++ b/entry.h\n@@ -41,5 +41,7 @@ void unlink_entry(const struct cache_entry *ce);\n \n void *read_blob_entry(const struct cache_entry *ce, unsigned long *size);\n int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st);\n+void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,\n+\t\t\t   struct stat *st);\n \n #endif /* ENTRY_H */\n-- \n2.30.1\n\n"},{"id":"420051","messageId":"xmqqzgyt3ha8.fsf@gitster.g","threadId":"54525","inReplyTo":"cover.1616508954.git.matheus.bernardino@usp.br","subject":"Re: [PATCH v6 0/9] Parallel Checkout (part 1)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-23T17:34:55Z","receivedAt":"2021-03-23T17:35:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matheus Tavares <matheus.bernardino@usp.br> writes:\n\n> Preparatory API changes for parallel checkout. This version was rebased\n> on top of 'master'. The only change required in this change of base was\n> the `entry.h` inclusion on `builtin/stash.c`, at the commit that creates\n> this header file.\n\nThanks.  I think you had part-2 that depended on this one, so if I\nhave a(n old) copy I'll rebase them on this round myself.\n\n"}]}