2007-04-22 02:09:02 +00:00
|
|
|
#!/bin/sh
|
|
|
|
|
|
|
|
test_description='blob conversion via gitattributes'
|
|
|
|
|
2020-11-18 23:44:21 +00:00
|
|
|
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
|
tests: mark tests relying on the current default for `init.defaultBranch`
In addition to the manual adjustment to let the `linux-gcc` CI job run
the test suite with `master` and then with `main`, this patch makes sure
that GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME is set in all test scripts
that currently rely on the initial branch name being `master by default.
To determine which test scripts to mark up, the first step was to
force-set the default branch name to `master` in
- all test scripts that contain the keyword `master`,
- t4211, which expects `t/t4211/history.export` with a hard-coded ref to
initialize the default branch,
- t5560 because it sources `t/t556x_common` which uses `master`,
- t8002 and t8012 because both source `t/annotate-tests.sh` which also
uses `master`)
This trick was performed by this command:
$ sed -i '/^ *\. \.\/\(test-lib\|lib-\(bash\|cvs\|git-svn\)\|gitweb-lib\)\.sh$/i\
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master\
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\
' $(git grep -l master t/t[0-9]*.sh) \
t/t4211*.sh t/t5560*.sh t/t8002*.sh t/t8012*.sh
After that, careful, manual inspection revealed that some of the test
scripts containing the needle `master` do not actually rely on a
specific default branch name: either they mention `master` only in a
comment, or they initialize that branch specificially, or they do not
actually refer to the current default branch. Therefore, the
aforementioned modification was undone in those test scripts thusly:
$ git checkout HEAD -- \
t/t0027-auto-crlf.sh t/t0060-path-utils.sh \
t/t1011-read-tree-sparse-checkout.sh \
t/t1305-config-include.sh t/t1309-early-config.sh \
t/t1402-check-ref-format.sh t/t1450-fsck.sh \
t/t2024-checkout-dwim.sh \
t/t2106-update-index-assume-unchanged.sh \
t/t3040-subprojects-basic.sh t/t3301-notes.sh \
t/t3308-notes-merge.sh t/t3423-rebase-reword.sh \
t/t3436-rebase-more-options.sh \
t/t4015-diff-whitespace.sh t/t4257-am-interactive.sh \
t/t5323-pack-redundant.sh t/t5401-update-hooks.sh \
t/t5511-refspec.sh t/t5526-fetch-submodules.sh \
t/t5529-push-errors.sh t/t5530-upload-pack-error.sh \
t/t5548-push-porcelain.sh \
t/t5552-skipping-fetch-negotiator.sh \
t/t5572-pull-submodule.sh t/t5608-clone-2gb.sh \
t/t5614-clone-submodules-shallow.sh \
t/t7508-status.sh t/t7606-merge-custom.sh \
t/t9302-fast-import-unpack-limit.sh
We excluded one set of test scripts in these commands, though: the range
of `git p4` tests. The reason? `git p4` stores the (foreign) remote
branch in the branch called `p4/master`, which is obviously not the
default branch. Manual analysis revealed that only five of these tests
actually require a specific default branch name to pass; They were
modified thusly:
$ sed -i '/^ *\. \.\/lib-git-p4\.sh$/i\
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master\
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\
' t/t980[0167]*.sh t/t9811*.sh
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-11-18 23:44:19 +00:00
|
|
|
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
|
|
|
|
|
2007-04-22 02:09:02 +00:00
|
|
|
. ./test-lib.sh
|
2021-08-26 19:10:06 +00:00
|
|
|
. "$TEST_DIRECTORY"/lib-terminal.sh
|
2007-04-22 02:09:02 +00:00
|
|
|
|
t0021: use Windows-friendly `pwd`
In Git for Windows, when passing paths from shell scripts to regular
Win32 executables, thanks to the MSYS2 runtime a somewhat magic path
conversion happens that lets the shell script think that there is a file
at `/git/Makefile` and the Win32 process it spawned thinks that the
shell script said `C:/git-sdk-64/git/Makefile` instead.
This conversion is documented in detail over here:
https://www.msys2.org/docs/filesystem-paths/#automatic-unix-windows-path-conversion
As all automatic conversions, there are gaps. For example, to avoid
mistaking command-line options like `/LOG=log.txt` (which are quite
common in the Windows world) from being mistaken for a Unix-style
absolute path, the MSYS2 runtime specifically exempts arguments
containing a `=` character from that conversion.
We are about to change `test_cmp` to use `git diff --no-index`, which
involves spawning precisely such a Win32 process.
In combination, this would cause a failure in `t0021-conversion.sh`
where we pass an absolute path containing an equal character to the
`test_cmp` function.
Seeing as the Unix tools like `cp` and `diff` that are used by Git's
test suite in the Git for Windows SDK (thanks to the MSYS2 project)
understand both Unix-style as well as Windows-style paths, we can stave
off this problem by simply switching to Windows-style paths and
side-stepping the need for any automatic path conversion.
Note: The `PATH` variable is obviously special, as it is colon-separated
in the MSYS2 Bash used by Git for Windows, and therefore _cannot_
contain absolute Windows-style paths, lest the colon after the drive
letter is mistaken for a path separator. Therefore, we need to be
careful to keep the Unix-style when modifying the `PATH` variable.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-12-06 15:07:46 +00:00
|
|
|
PATH=$PWD:$PATH
|
|
|
|
TEST_ROOT="$(pwd)"
|
2016-10-16 23:20:37 +00:00
|
|
|
|
2016-11-02 18:17:51 +00:00
|
|
|
write_script <<\EOF "$TEST_ROOT/rot13.sh"
|
2008-03-11 17:40:45 +00:00
|
|
|
tr \
|
|
|
|
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \
|
|
|
|
'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
|
2007-04-21 10:14:13 +00:00
|
|
|
EOF
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
generate_random_characters () {
|
|
|
|
LEN=$1
|
|
|
|
NAME=$2
|
2018-03-24 07:44:42 +00:00
|
|
|
test-tool genrandom some-seed $LEN |
|
2016-10-16 23:20:37 +00:00
|
|
|
perl -pe "s/./chr((ord($&) % 26) + ord('a'))/sge" >"$TEST_ROOT/$NAME"
|
|
|
|
}
|
|
|
|
|
|
|
|
filter_git () {
|
2017-06-01 08:22:00 +00:00
|
|
|
rm -f *.log &&
|
2016-11-11 21:07:37 +00:00
|
|
|
git "$@"
|
2016-10-16 23:20:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Compare two files and ensure that `clean` and `smudge` respectively are
|
|
|
|
# called at least once if specified in the `expect` file. The actual
|
|
|
|
# invocation count is not relevant because their number can vary.
|
2021-02-05 18:22:29 +00:00
|
|
|
# c.f. https://lore.kernel.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/
|
2016-10-16 23:20:37 +00:00
|
|
|
test_cmp_count () {
|
|
|
|
expect=$1
|
|
|
|
actual=$2
|
|
|
|
for FILE in "$expect" "$actual"
|
|
|
|
do
|
2016-11-03 20:12:13 +00:00
|
|
|
sort "$FILE" | uniq -c |
|
2017-06-01 08:21:59 +00:00
|
|
|
sed -e "s/^ *[0-9][0-9]*[ ]*IN: /x IN: /" >"$FILE.tmp"
|
2016-10-16 23:20:37 +00:00
|
|
|
done &&
|
2017-06-01 08:21:59 +00:00
|
|
|
test_cmp "$expect.tmp" "$actual.tmp" &&
|
|
|
|
rm "$expect.tmp" "$actual.tmp"
|
2016-10-16 23:20:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Compare two files but exclude all `clean` invocations because Git can
|
|
|
|
# call `clean` zero or more times.
|
2021-02-05 18:22:29 +00:00
|
|
|
# c.f. https://lore.kernel.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/
|
2016-10-16 23:20:37 +00:00
|
|
|
test_cmp_exclude_clean () {
|
|
|
|
expect=$1
|
|
|
|
actual=$2
|
|
|
|
for FILE in "$expect" "$actual"
|
|
|
|
do
|
2017-06-01 08:21:59 +00:00
|
|
|
grep -v "IN: clean" "$FILE" >"$FILE.tmp"
|
2016-10-16 23:20:37 +00:00
|
|
|
done &&
|
2017-06-01 08:21:59 +00:00
|
|
|
test_cmp "$expect.tmp" "$actual.tmp" &&
|
|
|
|
rm "$expect.tmp" "$actual.tmp"
|
2016-10-16 23:20:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Check that the contents of two files are equal and that their rot13 version
|
|
|
|
# is equal to the committed content.
|
|
|
|
test_cmp_committed_rot13 () {
|
|
|
|
test_cmp "$1" "$2" &&
|
2016-11-02 18:18:25 +00:00
|
|
|
rot13.sh <"$1" >expected &&
|
2016-10-16 23:20:37 +00:00
|
|
|
git cat-file blob :"$2" >actual &&
|
|
|
|
test_cmp expected actual
|
|
|
|
}
|
2007-04-21 10:14:13 +00:00
|
|
|
|
2007-04-22 02:09:02 +00:00
|
|
|
test_expect_success setup '
|
2007-04-21 10:14:13 +00:00
|
|
|
git config filter.rot13.smudge ./rot13.sh &&
|
|
|
|
git config filter.rot13.clean ./rot13.sh &&
|
|
|
|
|
2007-04-22 02:09:02 +00:00
|
|
|
{
|
tests: fix broken &&-chains in `{...}` groups
The top-level &&-chain checker built into t/test-lib.sh causes tests to
magically exit with code 117 if the &&-chain is broken. However, it has
the shortcoming that the magic does not work within `{...}` groups,
`(...)` subshells, `$(...)` substitutions, or within bodies of compound
statements, such as `if`, `for`, `while`, `case`, etc. `chainlint.sed`
partly fills in the gap by catching broken &&-chains in `(...)`
subshells, but bugs can still lurk behind broken &&-chains in the other
cases.
Fix broken &&-chains in `{...}` groups in order to reduce the number of
possible lurking bugs.
Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
Reviewed-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-12-09 05:11:08 +00:00
|
|
|
echo "*.t filter=rot13" &&
|
2007-04-22 02:09:02 +00:00
|
|
|
echo "*.i ident"
|
|
|
|
} >.gitattributes &&
|
|
|
|
|
|
|
|
{
|
tests: fix broken &&-chains in `{...}` groups
The top-level &&-chain checker built into t/test-lib.sh causes tests to
magically exit with code 117 if the &&-chain is broken. However, it has
the shortcoming that the magic does not work within `{...}` groups,
`(...)` subshells, `$(...)` substitutions, or within bodies of compound
statements, such as `if`, `for`, `while`, `case`, etc. `chainlint.sed`
partly fills in the gap by catching broken &&-chains in `(...)`
subshells, but bugs can still lurk behind broken &&-chains in the other
cases.
Fix broken &&-chains in `{...}` groups in order to reduce the number of
possible lurking bugs.
Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
Reviewed-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-12-09 05:11:08 +00:00
|
|
|
echo a b c d e f g h i j k l m &&
|
|
|
|
echo n o p q r s t u v w x y z &&
|
2007-05-14 13:37:25 +00:00
|
|
|
echo '\''$Id$'\''
|
2007-04-22 02:09:02 +00:00
|
|
|
} >test &&
|
|
|
|
cat test >test.t &&
|
|
|
|
cat test >test.o &&
|
|
|
|
cat test >test.i &&
|
|
|
|
git add test test.t test.i &&
|
|
|
|
rm -f test test.t test.i &&
|
2016-10-16 23:20:37 +00:00
|
|
|
git checkout -- test test.t test.i &&
|
|
|
|
|
|
|
|
echo "content-test2" >test2.o &&
|
2016-12-03 19:45:16 +00:00
|
|
|
echo "content-test3 - filename with special characters" >"test3 '\''sq'\'',\$x=.o"
|
2007-04-22 02:09:02 +00:00
|
|
|
'
|
|
|
|
|
2007-05-14 13:37:25 +00:00
|
|
|
script='s/^\$Id: \([0-9a-f]*\) \$/\1/p'
|
2007-04-22 02:09:02 +00:00
|
|
|
|
|
|
|
test_expect_success check '
|
|
|
|
|
2016-10-16 23:20:26 +00:00
|
|
|
test_cmp test.o test &&
|
|
|
|
test_cmp test.o test.t &&
|
2007-04-22 02:09:02 +00:00
|
|
|
|
|
|
|
# ident should be stripped in the repository
|
|
|
|
git diff --raw --exit-code :test :test.i &&
|
|
|
|
id=$(git rev-parse --verify :test) &&
|
|
|
|
embedded=$(sed -ne "$script" test.i) &&
|
2007-10-19 19:48:04 +00:00
|
|
|
test "z$id" = "z$embedded" &&
|
|
|
|
|
2016-10-16 23:20:26 +00:00
|
|
|
git cat-file blob :test.t >test.r &&
|
2007-10-19 19:48:04 +00:00
|
|
|
|
2016-10-16 23:20:26 +00:00
|
|
|
./rot13.sh <test.o >test.t &&
|
|
|
|
test_cmp test.r test.t
|
2007-04-22 02:09:02 +00:00
|
|
|
'
|
|
|
|
|
2007-05-27 10:52:11 +00:00
|
|
|
# If an expanded ident ever gets into the repository, we want to make sure that
|
|
|
|
# it is collapsed before being expanded again on checkout
|
|
|
|
test_expect_success expanded_in_repo '
|
2021-12-09 05:11:04 +00:00
|
|
|
cat >expanded-keywords.0 <<-\EOF &&
|
|
|
|
File with expanded keywords
|
|
|
|
$Id$
|
|
|
|
$Id:$
|
|
|
|
$Id: 0000000000000000000000000000000000000000 $
|
|
|
|
$Id: NoSpaceAtEnd$
|
|
|
|
$Id:NoSpaceAtFront $
|
|
|
|
$Id:NoSpaceAtEitherEnd$
|
|
|
|
$Id: NoTerminatingSymbol
|
|
|
|
$Id: Foreign Commit With Spaces $
|
|
|
|
EOF
|
2007-05-27 10:52:11 +00:00
|
|
|
|
2011-05-25 01:02:48 +00:00
|
|
|
{
|
|
|
|
cat expanded-keywords.0 &&
|
|
|
|
printf "\$Id: NoTerminatingSymbolAtEOF"
|
|
|
|
} >expanded-keywords &&
|
|
|
|
cat expanded-keywords >expanded-keywords-crlf &&
|
|
|
|
git add expanded-keywords expanded-keywords-crlf &&
|
2011-05-21 21:25:06 +00:00
|
|
|
git commit -m "File with keywords expanded" &&
|
|
|
|
id=$(git rev-parse --verify :expanded-keywords) &&
|
|
|
|
|
2021-12-09 05:11:04 +00:00
|
|
|
cat >expected-output.0 <<-EOF &&
|
|
|
|
File with expanded keywords
|
|
|
|
\$Id: $id \$
|
|
|
|
\$Id: $id \$
|
|
|
|
\$Id: $id \$
|
|
|
|
\$Id: $id \$
|
|
|
|
\$Id: $id \$
|
|
|
|
\$Id: $id \$
|
|
|
|
\$Id: NoTerminatingSymbol
|
|
|
|
\$Id: Foreign Commit With Spaces \$
|
|
|
|
EOF
|
2011-05-25 01:02:48 +00:00
|
|
|
{
|
|
|
|
cat expected-output.0 &&
|
2011-05-21 21:25:06 +00:00
|
|
|
printf "\$Id: NoTerminatingSymbolAtEOF"
|
2011-05-25 01:02:48 +00:00
|
|
|
} >expected-output &&
|
|
|
|
{
|
|
|
|
append_cr <expected-output.0 &&
|
|
|
|
printf "\$Id: NoTerminatingSymbolAtEOF"
|
|
|
|
} >expected-output-crlf &&
|
|
|
|
{
|
tests: fix broken &&-chains in `{...}` groups
The top-level &&-chain checker built into t/test-lib.sh causes tests to
magically exit with code 117 if the &&-chain is broken. However, it has
the shortcoming that the magic does not work within `{...}` groups,
`(...)` subshells, `$(...)` substitutions, or within bodies of compound
statements, such as `if`, `for`, `while`, `case`, etc. `chainlint.sed`
partly fills in the gap by catching broken &&-chains in `(...)`
subshells, but bugs can still lurk behind broken &&-chains in the other
cases.
Fix broken &&-chains in `{...}` groups in order to reduce the number of
possible lurking bugs.
Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
Reviewed-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-12-09 05:11:08 +00:00
|
|
|
echo "expanded-keywords ident" &&
|
2011-05-25 01:02:48 +00:00
|
|
|
echo "expanded-keywords-crlf ident text eol=crlf"
|
|
|
|
} >>.gitattributes &&
|
2007-05-27 10:52:11 +00:00
|
|
|
|
2011-05-25 01:02:48 +00:00
|
|
|
rm -f expanded-keywords expanded-keywords-crlf &&
|
2007-05-27 10:52:11 +00:00
|
|
|
|
|
|
|
git checkout -- expanded-keywords &&
|
2018-10-05 21:54:04 +00:00
|
|
|
test_cmp expected-output expanded-keywords &&
|
2011-05-25 01:02:48 +00:00
|
|
|
|
|
|
|
git checkout -- expanded-keywords-crlf &&
|
2018-10-05 21:54:04 +00:00
|
|
|
test_cmp expected-output-crlf expanded-keywords-crlf
|
2007-05-27 10:52:11 +00:00
|
|
|
'
|
|
|
|
|
2010-12-22 14:40:13 +00:00
|
|
|
# The use of %f in a filter definition is expanded to the path to
|
|
|
|
# the filename being smudged or cleaned. It must be shell escaped.
|
|
|
|
# First, set up some interesting file names and pet them in
|
|
|
|
# .gitattributes.
|
|
|
|
test_expect_success 'filter shell-escaped filenames' '
|
|
|
|
cat >argc.sh <<-EOF &&
|
|
|
|
#!$SHELL_PATH
|
2010-12-22 23:18:47 +00:00
|
|
|
cat >/dev/null
|
2010-12-22 14:40:13 +00:00
|
|
|
echo argc: \$# "\$@"
|
|
|
|
EOF
|
|
|
|
normal=name-no-magic &&
|
|
|
|
special="name with '\''sq'\'' and \$x" &&
|
|
|
|
echo some test text >"$normal" &&
|
|
|
|
echo some test text >"$special" &&
|
|
|
|
git add "$normal" "$special" &&
|
|
|
|
git commit -q -m "add files" &&
|
|
|
|
echo "name* filter=argc" >.gitattributes &&
|
|
|
|
|
|
|
|
# delete the files and check them out again, using a smudge filter
|
|
|
|
# that will count the args and echo the command-line back to us
|
2016-10-16 23:20:26 +00:00
|
|
|
test_config filter.argc.smudge "sh ./argc.sh %f" &&
|
2010-12-22 14:40:13 +00:00
|
|
|
rm "$normal" "$special" &&
|
|
|
|
git checkout -- "$normal" "$special" &&
|
|
|
|
|
|
|
|
# make sure argc.sh counted the right number of args
|
|
|
|
echo "argc: 1 $normal" >expect &&
|
|
|
|
test_cmp expect "$normal" &&
|
|
|
|
echo "argc: 1 $special" >expect &&
|
|
|
|
test_cmp expect "$special" &&
|
|
|
|
|
|
|
|
# do the same thing, but with more args in the filter expression
|
2016-10-16 23:20:26 +00:00
|
|
|
test_config filter.argc.smudge "sh ./argc.sh %f --my-extra-arg" &&
|
2010-12-22 14:40:13 +00:00
|
|
|
rm "$normal" "$special" &&
|
|
|
|
git checkout -- "$normal" "$special" &&
|
|
|
|
|
|
|
|
# make sure argc.sh counted the right number of args
|
|
|
|
echo "argc: 2 $normal --my-extra-arg" >expect &&
|
|
|
|
test_cmp expect "$normal" &&
|
|
|
|
echo "argc: 2 $special --my-extra-arg" >expect &&
|
|
|
|
test_cmp expect "$special" &&
|
|
|
|
:
|
|
|
|
'
|
|
|
|
|
2014-08-26 15:23:25 +00:00
|
|
|
test_expect_success 'required filter should filter data' '
|
2016-10-16 23:20:26 +00:00
|
|
|
test_config filter.required.smudge ./rot13.sh &&
|
|
|
|
test_config filter.required.clean ./rot13.sh &&
|
|
|
|
test_config filter.required.required true &&
|
Add a setting to require a filter to be successful
By default, a missing filter driver or a failure from the filter driver is
not an error, but merely makes the filter operation a no-op pass through.
This is useful to massage the content into a shape that is more convenient
for the platform, filesystem, and the user to use, and the content filter
mechanism is not used to turn something unusable into usable.
However, we could also use of the content filtering mechanism and store
the content that cannot be directly used in the repository (e.g. a UUID
that refers to the true content stored outside git, or an encrypted
content) and turn it into a usable form upon checkout (e.g. download the
external content, or decrypt the encrypted content). For such a use case,
the content cannot be used when filter driver fails, and we need a way to
tell Git to abort the whole operation for such a failing or missing filter
driver.
Add a new "filter.<driver>.required" configuration variable to mark the
second use case. When it is set, git will abort the operation when the
filter driver does not exist or exits with a non-zero status code.
Signed-off-by: Jehan Bing <jehan@orb.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-02-17 01:19:03 +00:00
|
|
|
|
|
|
|
echo "*.r filter=required" >.gitattributes &&
|
|
|
|
|
2014-08-26 15:23:25 +00:00
|
|
|
cat test.o >test.r &&
|
Add a setting to require a filter to be successful
By default, a missing filter driver or a failure from the filter driver is
not an error, but merely makes the filter operation a no-op pass through.
This is useful to massage the content into a shape that is more convenient
for the platform, filesystem, and the user to use, and the content filter
mechanism is not used to turn something unusable into usable.
However, we could also use of the content filtering mechanism and store
the content that cannot be directly used in the repository (e.g. a UUID
that refers to the true content stored outside git, or an encrypted
content) and turn it into a usable form upon checkout (e.g. download the
external content, or decrypt the encrypted content). For such a use case,
the content cannot be used when filter driver fails, and we need a way to
tell Git to abort the whole operation for such a failing or missing filter
driver.
Add a new "filter.<driver>.required" configuration variable to mark the
second use case. When it is set, git will abort the operation when the
filter driver does not exist or exits with a non-zero status code.
Signed-off-by: Jehan Bing <jehan@orb.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-02-17 01:19:03 +00:00
|
|
|
git add test.r &&
|
2014-08-26 15:23:25 +00:00
|
|
|
|
Add a setting to require a filter to be successful
By default, a missing filter driver or a failure from the filter driver is
not an error, but merely makes the filter operation a no-op pass through.
This is useful to massage the content into a shape that is more convenient
for the platform, filesystem, and the user to use, and the content filter
mechanism is not used to turn something unusable into usable.
However, we could also use of the content filtering mechanism and store
the content that cannot be directly used in the repository (e.g. a UUID
that refers to the true content stored outside git, or an encrypted
content) and turn it into a usable form upon checkout (e.g. download the
external content, or decrypt the encrypted content). For such a use case,
the content cannot be used when filter driver fails, and we need a way to
tell Git to abort the whole operation for such a failing or missing filter
driver.
Add a new "filter.<driver>.required" configuration variable to mark the
second use case. When it is set, git will abort the operation when the
filter driver does not exist or exits with a non-zero status code.
Signed-off-by: Jehan Bing <jehan@orb.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-02-17 01:19:03 +00:00
|
|
|
rm -f test.r &&
|
2014-08-26 15:23:25 +00:00
|
|
|
git checkout -- test.r &&
|
2016-10-16 23:20:26 +00:00
|
|
|
test_cmp test.o test.r &&
|
2014-08-26 15:23:25 +00:00
|
|
|
|
|
|
|
./rot13.sh <test.o >expected &&
|
|
|
|
git cat-file blob :test.r >actual &&
|
2016-10-16 23:20:26 +00:00
|
|
|
test_cmp expected actual
|
Add a setting to require a filter to be successful
By default, a missing filter driver or a failure from the filter driver is
not an error, but merely makes the filter operation a no-op pass through.
This is useful to massage the content into a shape that is more convenient
for the platform, filesystem, and the user to use, and the content filter
mechanism is not used to turn something unusable into usable.
However, we could also use of the content filtering mechanism and store
the content that cannot be directly used in the repository (e.g. a UUID
that refers to the true content stored outside git, or an encrypted
content) and turn it into a usable form upon checkout (e.g. download the
external content, or decrypt the encrypted content). For such a use case,
the content cannot be used when filter driver fails, and we need a way to
tell Git to abort the whole operation for such a failing or missing filter
driver.
Add a new "filter.<driver>.required" configuration variable to mark the
second use case. When it is set, git will abort the operation when the
filter driver does not exist or exits with a non-zero status code.
Signed-off-by: Jehan Bing <jehan@orb.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-02-17 01:19:03 +00:00
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success 'required filter smudge failure' '
|
2016-10-16 23:20:26 +00:00
|
|
|
test_config filter.failsmudge.smudge false &&
|
|
|
|
test_config filter.failsmudge.clean cat &&
|
|
|
|
test_config filter.failsmudge.required true &&
|
Add a setting to require a filter to be successful
By default, a missing filter driver or a failure from the filter driver is
not an error, but merely makes the filter operation a no-op pass through.
This is useful to massage the content into a shape that is more convenient
for the platform, filesystem, and the user to use, and the content filter
mechanism is not used to turn something unusable into usable.
However, we could also use of the content filtering mechanism and store
the content that cannot be directly used in the repository (e.g. a UUID
that refers to the true content stored outside git, or an encrypted
content) and turn it into a usable form upon checkout (e.g. download the
external content, or decrypt the encrypted content). For such a use case,
the content cannot be used when filter driver fails, and we need a way to
tell Git to abort the whole operation for such a failing or missing filter
driver.
Add a new "filter.<driver>.required" configuration variable to mark the
second use case. When it is set, git will abort the operation when the
filter driver does not exist or exits with a non-zero status code.
Signed-off-by: Jehan Bing <jehan@orb.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-02-17 01:19:03 +00:00
|
|
|
|
|
|
|
echo "*.fs filter=failsmudge" >.gitattributes &&
|
|
|
|
|
|
|
|
echo test >test.fs &&
|
|
|
|
git add test.fs &&
|
|
|
|
rm -f test.fs &&
|
|
|
|
test_must_fail git checkout -- test.fs
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success 'required filter clean failure' '
|
2016-10-16 23:20:26 +00:00
|
|
|
test_config filter.failclean.smudge cat &&
|
|
|
|
test_config filter.failclean.clean false &&
|
|
|
|
test_config filter.failclean.required true &&
|
Add a setting to require a filter to be successful
By default, a missing filter driver or a failure from the filter driver is
not an error, but merely makes the filter operation a no-op pass through.
This is useful to massage the content into a shape that is more convenient
for the platform, filesystem, and the user to use, and the content filter
mechanism is not used to turn something unusable into usable.
However, we could also use of the content filtering mechanism and store
the content that cannot be directly used in the repository (e.g. a UUID
that refers to the true content stored outside git, or an encrypted
content) and turn it into a usable form upon checkout (e.g. download the
external content, or decrypt the encrypted content). For such a use case,
the content cannot be used when filter driver fails, and we need a way to
tell Git to abort the whole operation for such a failing or missing filter
driver.
Add a new "filter.<driver>.required" configuration variable to mark the
second use case. When it is set, git will abort the operation when the
filter driver does not exist or exits with a non-zero status code.
Signed-off-by: Jehan Bing <jehan@orb.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-02-17 01:19:03 +00:00
|
|
|
|
|
|
|
echo "*.fc filter=failclean" >.gitattributes &&
|
|
|
|
|
|
|
|
echo test >test.fc &&
|
|
|
|
test_must_fail git add test.fc
|
|
|
|
'
|
|
|
|
|
2021-02-26 16:23:54 +00:00
|
|
|
test_expect_success 'required filter with absent clean field' '
|
|
|
|
test_config filter.absentclean.smudge cat &&
|
|
|
|
test_config filter.absentclean.required true &&
|
|
|
|
|
|
|
|
echo "*.ac filter=absentclean" >.gitattributes &&
|
|
|
|
|
|
|
|
echo test >test.ac &&
|
|
|
|
test_must_fail git add test.ac 2>stderr &&
|
|
|
|
test_i18ngrep "fatal: test.ac: clean filter .absentclean. failed" stderr
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success 'required filter with absent smudge field' '
|
|
|
|
test_config filter.absentsmudge.clean cat &&
|
|
|
|
test_config filter.absentsmudge.required true &&
|
|
|
|
|
|
|
|
echo "*.as filter=absentsmudge" >.gitattributes &&
|
|
|
|
|
|
|
|
echo test >test.as &&
|
|
|
|
git add test.as &&
|
|
|
|
rm -f test.as &&
|
|
|
|
test_must_fail git checkout -- test.as 2>stderr &&
|
|
|
|
test_i18ngrep "fatal: test.as: smudge filter absentsmudge failed" stderr
|
|
|
|
'
|
|
|
|
|
2014-08-26 15:23:25 +00:00
|
|
|
test_expect_success 'filtering large input to small output should use little memory' '
|
2016-10-16 23:20:26 +00:00
|
|
|
test_config filter.devnull.clean "cat >/dev/null" &&
|
|
|
|
test_config filter.devnull.required true &&
|
2021-12-09 05:11:12 +00:00
|
|
|
for i in $(test_seq 1 30); do printf "%1048576d" 1 || return 1; done >30MB &&
|
2014-08-26 15:23:25 +00:00
|
|
|
echo "30MB filter=devnull" >.gitattributes &&
|
|
|
|
GIT_MMAP_LIMIT=1m GIT_ALLOC_LIMIT=1m git add 30MB
|
|
|
|
'
|
|
|
|
|
2015-05-19 18:08:23 +00:00
|
|
|
test_expect_success 'filter that does not read is fine' '
|
2018-03-24 07:44:42 +00:00
|
|
|
test-tool genrandom foo $((128 * 1024 + 1)) >big &&
|
2015-05-19 18:08:23 +00:00
|
|
|
echo "big filter=epipe" >.gitattributes &&
|
2016-10-16 23:20:26 +00:00
|
|
|
test_config filter.epipe.clean "echo xyzzy" &&
|
2015-05-19 18:08:23 +00:00
|
|
|
git add big &&
|
|
|
|
git cat-file blob :big >actual &&
|
|
|
|
echo xyzzy >expect &&
|
|
|
|
test_cmp expect actual
|
|
|
|
'
|
|
|
|
|
xread, xwrite: limit size of IO to 8MB
Checking out 2GB or more through an external filter (see test) fails
on Mac OS X 10.8.4 (12E55) for a 64-bit executable with:
error: read from external filter cat failed
error: cannot feed the input to external filter cat
error: cat died of signal 13
error: external filter cat failed 141
error: external filter cat failed
The reason is that read() immediately returns with EINVAL when asked
to read more than 2GB. According to POSIX [1], if the value of
nbyte passed to read() is greater than SSIZE_MAX, the result is
implementation-defined. The write function has the same restriction
[2]. Since OS X still supports running 32-bit executables, the
32-bit limit (SSIZE_MAX = INT_MAX = 2GB - 1) seems to be also
imposed on 64-bit executables under certain conditions. For write,
the problem has been addressed earlier [6c642a].
Address the problem for read() and write() differently, by limiting
size of IO chunks unconditionally on all platforms in xread() and
xwrite(). Large chunks only cause problems, like causing latencies
when killing the process, even if OS X was not buggy. Doing IO in
reasonably sized smaller chunks should have no negative impact on
performance.
The compat wrapper clipped_write() introduced earlier [6c642a] is
not needed anymore. It will be reverted in a separate commit. The
new test catches read and write problems.
Note that 'git add' exits with 0 even if it prints filtering errors
to stderr. The test, therefore, checks stderr. 'git add' should
probably be changed (sometime in another commit) to exit with
nonzero if filtering fails. The test could then be changed to use
test_must_fail.
Thanks to the following people for suggestions and testing:
Johannes Sixt <j6t@kdbg.org>
John Keeping <john@keeping.me.uk>
Jonathan Nieder <jrnieder@gmail.com>
Kyle J. McKay <mackyle@gmail.com>
Linus Torvalds <torvalds@linux-foundation.org>
Torsten Bögershausen <tboegi@web.de>
[1] http://pubs.opengroup.org/onlinepubs/009695399/functions/read.html
[2] http://pubs.opengroup.org/onlinepubs/009695399/functions/write.html
[6c642a] commit 6c642a878688adf46b226903858b53e2d31ac5c3
compate/clipped-write.c: large write(2) fails on Mac OS X/XNU
Signed-off-by: Steffen Prohaska <prohaska@zib.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-20 06:43:54 +00:00
|
|
|
test_expect_success EXPENSIVE 'filter large file' '
|
2016-10-16 23:20:26 +00:00
|
|
|
test_config filter.largefile.smudge cat &&
|
|
|
|
test_config filter.largefile.clean cat &&
|
2021-12-09 05:11:12 +00:00
|
|
|
for i in $(test_seq 1 2048); do printf "%1048576d" 1 || return 1; done >2GB &&
|
xread, xwrite: limit size of IO to 8MB
Checking out 2GB or more through an external filter (see test) fails
on Mac OS X 10.8.4 (12E55) for a 64-bit executable with:
error: read from external filter cat failed
error: cannot feed the input to external filter cat
error: cat died of signal 13
error: external filter cat failed 141
error: external filter cat failed
The reason is that read() immediately returns with EINVAL when asked
to read more than 2GB. According to POSIX [1], if the value of
nbyte passed to read() is greater than SSIZE_MAX, the result is
implementation-defined. The write function has the same restriction
[2]. Since OS X still supports running 32-bit executables, the
32-bit limit (SSIZE_MAX = INT_MAX = 2GB - 1) seems to be also
imposed on 64-bit executables under certain conditions. For write,
the problem has been addressed earlier [6c642a].
Address the problem for read() and write() differently, by limiting
size of IO chunks unconditionally on all platforms in xread() and
xwrite(). Large chunks only cause problems, like causing latencies
when killing the process, even if OS X was not buggy. Doing IO in
reasonably sized smaller chunks should have no negative impact on
performance.
The compat wrapper clipped_write() introduced earlier [6c642a] is
not needed anymore. It will be reverted in a separate commit. The
new test catches read and write problems.
Note that 'git add' exits with 0 even if it prints filtering errors
to stderr. The test, therefore, checks stderr. 'git add' should
probably be changed (sometime in another commit) to exit with
nonzero if filtering fails. The test could then be changed to use
test_must_fail.
Thanks to the following people for suggestions and testing:
Johannes Sixt <j6t@kdbg.org>
John Keeping <john@keeping.me.uk>
Jonathan Nieder <jrnieder@gmail.com>
Kyle J. McKay <mackyle@gmail.com>
Linus Torvalds <torvalds@linux-foundation.org>
Torsten Bögershausen <tboegi@web.de>
[1] http://pubs.opengroup.org/onlinepubs/009695399/functions/read.html
[2] http://pubs.opengroup.org/onlinepubs/009695399/functions/write.html
[6c642a] commit 6c642a878688adf46b226903858b53e2d31ac5c3
compate/clipped-write.c: large write(2) fails on Mac OS X/XNU
Signed-off-by: Steffen Prohaska <prohaska@zib.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-20 06:43:54 +00:00
|
|
|
echo "2GB filter=largefile" >.gitattributes &&
|
|
|
|
git add 2GB 2>err &&
|
2016-10-16 23:20:26 +00:00
|
|
|
test_must_be_empty err &&
|
xread, xwrite: limit size of IO to 8MB
Checking out 2GB or more through an external filter (see test) fails
on Mac OS X 10.8.4 (12E55) for a 64-bit executable with:
error: read from external filter cat failed
error: cannot feed the input to external filter cat
error: cat died of signal 13
error: external filter cat failed 141
error: external filter cat failed
The reason is that read() immediately returns with EINVAL when asked
to read more than 2GB. According to POSIX [1], if the value of
nbyte passed to read() is greater than SSIZE_MAX, the result is
implementation-defined. The write function has the same restriction
[2]. Since OS X still supports running 32-bit executables, the
32-bit limit (SSIZE_MAX = INT_MAX = 2GB - 1) seems to be also
imposed on 64-bit executables under certain conditions. For write,
the problem has been addressed earlier [6c642a].
Address the problem for read() and write() differently, by limiting
size of IO chunks unconditionally on all platforms in xread() and
xwrite(). Large chunks only cause problems, like causing latencies
when killing the process, even if OS X was not buggy. Doing IO in
reasonably sized smaller chunks should have no negative impact on
performance.
The compat wrapper clipped_write() introduced earlier [6c642a] is
not needed anymore. It will be reverted in a separate commit. The
new test catches read and write problems.
Note that 'git add' exits with 0 even if it prints filtering errors
to stderr. The test, therefore, checks stderr. 'git add' should
probably be changed (sometime in another commit) to exit with
nonzero if filtering fails. The test could then be changed to use
test_must_fail.
Thanks to the following people for suggestions and testing:
Johannes Sixt <j6t@kdbg.org>
John Keeping <john@keeping.me.uk>
Jonathan Nieder <jrnieder@gmail.com>
Kyle J. McKay <mackyle@gmail.com>
Linus Torvalds <torvalds@linux-foundation.org>
Torsten Bögershausen <tboegi@web.de>
[1] http://pubs.opengroup.org/onlinepubs/009695399/functions/read.html
[2] http://pubs.opengroup.org/onlinepubs/009695399/functions/write.html
[6c642a] commit 6c642a878688adf46b226903858b53e2d31ac5c3
compate/clipped-write.c: large write(2) fails on Mac OS X/XNU
Signed-off-by: Steffen Prohaska <prohaska@zib.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-20 06:43:54 +00:00
|
|
|
rm -f 2GB &&
|
|
|
|
git checkout -- 2GB 2>err &&
|
2016-10-16 23:20:26 +00:00
|
|
|
test_must_be_empty err
|
xread, xwrite: limit size of IO to 8MB
Checking out 2GB or more through an external filter (see test) fails
on Mac OS X 10.8.4 (12E55) for a 64-bit executable with:
error: read from external filter cat failed
error: cannot feed the input to external filter cat
error: cat died of signal 13
error: external filter cat failed 141
error: external filter cat failed
The reason is that read() immediately returns with EINVAL when asked
to read more than 2GB. According to POSIX [1], if the value of
nbyte passed to read() is greater than SSIZE_MAX, the result is
implementation-defined. The write function has the same restriction
[2]. Since OS X still supports running 32-bit executables, the
32-bit limit (SSIZE_MAX = INT_MAX = 2GB - 1) seems to be also
imposed on 64-bit executables under certain conditions. For write,
the problem has been addressed earlier [6c642a].
Address the problem for read() and write() differently, by limiting
size of IO chunks unconditionally on all platforms in xread() and
xwrite(). Large chunks only cause problems, like causing latencies
when killing the process, even if OS X was not buggy. Doing IO in
reasonably sized smaller chunks should have no negative impact on
performance.
The compat wrapper clipped_write() introduced earlier [6c642a] is
not needed anymore. It will be reverted in a separate commit. The
new test catches read and write problems.
Note that 'git add' exits with 0 even if it prints filtering errors
to stderr. The test, therefore, checks stderr. 'git add' should
probably be changed (sometime in another commit) to exit with
nonzero if filtering fails. The test could then be changed to use
test_must_fail.
Thanks to the following people for suggestions and testing:
Johannes Sixt <j6t@kdbg.org>
John Keeping <john@keeping.me.uk>
Jonathan Nieder <jrnieder@gmail.com>
Kyle J. McKay <mackyle@gmail.com>
Linus Torvalds <torvalds@linux-foundation.org>
Torsten Bögershausen <tboegi@web.de>
[1] http://pubs.opengroup.org/onlinepubs/009695399/functions/read.html
[2] http://pubs.opengroup.org/onlinepubs/009695399/functions/write.html
[6c642a] commit 6c642a878688adf46b226903858b53e2d31ac5c3
compate/clipped-write.c: large write(2) fails on Mac OS X/XNU
Signed-off-by: Steffen Prohaska <prohaska@zib.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-20 06:43:54 +00:00
|
|
|
'
|
|
|
|
|
sha1_file: pass empty buffer to index empty file
`git add` of an empty file with a filter pops complaints from
`copy_fd` about a bad file descriptor.
This traces back to these lines in sha1_file.c:index_core:
if (!size) {
ret = index_mem(sha1, NULL, size, type, path, flags);
The problem here is that content to be added to the index can be
supplied from an fd, or from a memory buffer, or from a pathname. This
call is supplying a NULL buffer pointer and a zero size.
Downstream logic takes the complete absence of a buffer to mean the
data is to be found elsewhere -- for instance, these, from convert.c:
if (params->src) {
write_err = (write_in_full(child_process.in, params->src, params->size) < 0);
} else {
write_err = copy_fd(params->fd, child_process.in);
}
~If there's a buffer, write from that, otherwise the data must be coming
from an open fd.~
Perfectly reasonable logic in a routine that's going to write from
either a buffer or an fd.
So change `index_core` to supply an empty buffer when indexing an empty
file.
There's a patch out there that instead changes the logic quoted above to
take a `-1` fd to mean "use the buffer", but it seems to me that the
distinction between a missing buffer and an empty one carries intrinsic
semantics, where the logic change is adapting the code to handle
incorrect arguments.
Signed-off-by: Jim Hill <gjthill@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-05-18 00:41:45 +00:00
|
|
|
test_expect_success "filter: clean empty file" '
|
2016-10-16 23:20:26 +00:00
|
|
|
test_config filter.in-repo-header.clean "echo cleaned && cat" &&
|
|
|
|
test_config filter.in-repo-header.smudge "sed 1d" &&
|
sha1_file: pass empty buffer to index empty file
`git add` of an empty file with a filter pops complaints from
`copy_fd` about a bad file descriptor.
This traces back to these lines in sha1_file.c:index_core:
if (!size) {
ret = index_mem(sha1, NULL, size, type, path, flags);
The problem here is that content to be added to the index can be
supplied from an fd, or from a memory buffer, or from a pathname. This
call is supplying a NULL buffer pointer and a zero size.
Downstream logic takes the complete absence of a buffer to mean the
data is to be found elsewhere -- for instance, these, from convert.c:
if (params->src) {
write_err = (write_in_full(child_process.in, params->src, params->size) < 0);
} else {
write_err = copy_fd(params->fd, child_process.in);
}
~If there's a buffer, write from that, otherwise the data must be coming
from an open fd.~
Perfectly reasonable logic in a routine that's going to write from
either a buffer or an fd.
So change `index_core` to supply an empty buffer when indexing an empty
file.
There's a patch out there that instead changes the logic quoted above to
take a `-1` fd to mean "use the buffer", but it seems to me that the
distinction between a missing buffer and an empty one carries intrinsic
semantics, where the logic change is adapting the code to handle
incorrect arguments.
Signed-off-by: Jim Hill <gjthill@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-05-18 00:41:45 +00:00
|
|
|
|
|
|
|
echo "empty-in-worktree filter=in-repo-header" >>.gitattributes &&
|
|
|
|
>empty-in-worktree &&
|
|
|
|
|
|
|
|
echo cleaned >expected &&
|
|
|
|
git add empty-in-worktree &&
|
|
|
|
git show :empty-in-worktree >actual &&
|
|
|
|
test_cmp expected actual
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success "filter: smudge empty file" '
|
2016-10-16 23:20:26 +00:00
|
|
|
test_config filter.empty-in-repo.clean "cat >/dev/null" &&
|
|
|
|
test_config filter.empty-in-repo.smudge "echo smudged && cat" &&
|
sha1_file: pass empty buffer to index empty file
`git add` of an empty file with a filter pops complaints from
`copy_fd` about a bad file descriptor.
This traces back to these lines in sha1_file.c:index_core:
if (!size) {
ret = index_mem(sha1, NULL, size, type, path, flags);
The problem here is that content to be added to the index can be
supplied from an fd, or from a memory buffer, or from a pathname. This
call is supplying a NULL buffer pointer and a zero size.
Downstream logic takes the complete absence of a buffer to mean the
data is to be found elsewhere -- for instance, these, from convert.c:
if (params->src) {
write_err = (write_in_full(child_process.in, params->src, params->size) < 0);
} else {
write_err = copy_fd(params->fd, child_process.in);
}
~If there's a buffer, write from that, otherwise the data must be coming
from an open fd.~
Perfectly reasonable logic in a routine that's going to write from
either a buffer or an fd.
So change `index_core` to supply an empty buffer when indexing an empty
file.
There's a patch out there that instead changes the logic quoted above to
take a `-1` fd to mean "use the buffer", but it seems to me that the
distinction between a missing buffer and an empty one carries intrinsic
semantics, where the logic change is adapting the code to handle
incorrect arguments.
Signed-off-by: Jim Hill <gjthill@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-05-18 00:41:45 +00:00
|
|
|
|
|
|
|
echo "empty-in-repo filter=empty-in-repo" >>.gitattributes &&
|
|
|
|
echo dead data walking >empty-in-repo &&
|
|
|
|
git add empty-in-repo &&
|
|
|
|
|
|
|
|
echo smudged >expected &&
|
|
|
|
git checkout-index --prefix=filtered- empty-in-repo &&
|
|
|
|
test_cmp expected filtered-empty-in-repo
|
|
|
|
'
|
|
|
|
|
2016-01-29 08:21:37 +00:00
|
|
|
test_expect_success 'disable filter with empty override' '
|
|
|
|
test_config_global filter.disable.smudge false &&
|
|
|
|
test_config_global filter.disable.clean false &&
|
|
|
|
test_config filter.disable.smudge false &&
|
|
|
|
test_config filter.disable.clean false &&
|
|
|
|
|
|
|
|
echo "*.disable filter=disable" >.gitattributes &&
|
|
|
|
|
|
|
|
echo test >test.disable &&
|
|
|
|
git -c filter.disable.clean= add test.disable 2>err &&
|
|
|
|
test_must_be_empty err &&
|
|
|
|
rm -f test.disable &&
|
|
|
|
git -c filter.disable.smudge= checkout -- test.disable 2>err &&
|
|
|
|
test_must_be_empty err
|
|
|
|
'
|
|
|
|
|
diff: do not reuse worktree files that need "clean" conversion
When accessing a blob for a diff, we may try to reuse file
contents in the working tree, under the theory that it is
faster to mmap those file contents than it would be to
extract the content from the object database.
When we have to filter those contents, though, that
assumption does not hold. Even for our internal conversions
like CRLF, we have to allocate and fill a new buffer anyway.
But much worse, for external clean filters we have to exec
an arbitrary script, and we have no idea how expensive it
may be to run.
So let's skip this optimization when conversion into git's
"clean" form is required. This applies whenever the
"want_file" flag is false. When it's true, the caller
actually wants the smudged worktree contents, which the
reused file by definition already has (in fact, this is a
key optimization going the other direction, since reusing
the worktree file there lets us skip smudge filters).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2016-07-22 15:27:53 +00:00
|
|
|
test_expect_success 'diff does not reuse worktree files that need cleaning' '
|
|
|
|
test_config filter.counter.clean "echo . >>count; sed s/^/clean:/" &&
|
|
|
|
echo "file filter=counter" >.gitattributes &&
|
|
|
|
test_commit one file &&
|
|
|
|
test_commit two file &&
|
|
|
|
|
|
|
|
>count &&
|
|
|
|
git diff-tree -p HEAD &&
|
|
|
|
test_line_count = 0 count
|
|
|
|
'
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success 'required process filter should filter data' '
|
|
|
|
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" &&
|
2016-10-16 23:20:37 +00:00
|
|
|
test_config_global filter.protocol.required true &&
|
|
|
|
rm -rf repo &&
|
|
|
|
mkdir repo &&
|
|
|
|
(
|
|
|
|
cd repo &&
|
|
|
|
git init &&
|
|
|
|
|
|
|
|
echo "*.r filter=protocol" >.gitattributes &&
|
|
|
|
git add . &&
|
2016-12-04 13:37:31 +00:00
|
|
|
git commit -m "test commit 1" &&
|
2016-10-16 23:20:37 +00:00
|
|
|
git branch empty-branch &&
|
|
|
|
|
|
|
|
cp "$TEST_ROOT/test.o" test.r &&
|
|
|
|
cp "$TEST_ROOT/test2.o" test2.r &&
|
|
|
|
mkdir testsubdir &&
|
2016-12-03 19:45:16 +00:00
|
|
|
cp "$TEST_ROOT/test3 '\''sq'\'',\$x=.o" "testsubdir/test3 '\''sq'\'',\$x=.r" &&
|
2016-10-16 23:20:37 +00:00
|
|
|
>test4-empty.r &&
|
|
|
|
|
2020-11-07 01:12:57 +00:00
|
|
|
S=$(test_file_size test.r) &&
|
|
|
|
S2=$(test_file_size test2.r) &&
|
|
|
|
S3=$(test_file_size "testsubdir/test3 '\''sq'\'',\$x=.r") &&
|
2020-03-16 18:05:04 +00:00
|
|
|
M=$(git hash-object test.r) &&
|
|
|
|
M2=$(git hash-object test2.r) &&
|
|
|
|
M3=$(git hash-object "testsubdir/test3 '\''sq'\'',\$x=.r") &&
|
|
|
|
EMPTY=$(git hash-object /dev/null) &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
filter_git add . &&
|
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
|
|
|
IN: clean test.r $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: clean test2.r $S2 [OK] -- OUT: $S2 . [OK]
|
|
|
|
IN: clean test4-empty.r 0 [OK] -- OUT: 0 [OK]
|
2016-12-03 19:45:16 +00:00
|
|
|
IN: clean testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK]
|
2016-10-16 23:20:37 +00:00
|
|
|
STOP
|
|
|
|
EOF
|
2017-06-01 08:22:00 +00:00
|
|
|
test_cmp_count expected.log debug.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
2016-12-18 12:37:48 +00:00
|
|
|
git commit -m "test commit 2" &&
|
2020-11-18 23:44:21 +00:00
|
|
|
MAIN=$(git rev-parse --verify main) &&
|
|
|
|
META="ref=refs/heads/main treeish=$MAIN" &&
|
2016-12-03 19:45:16 +00:00
|
|
|
rm -f test2.r "testsubdir/test3 '\''sq'\'',\$x=.r" &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
filter_git checkout --quiet --no-progress . &&
|
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
2020-03-16 18:05:04 +00:00
|
|
|
IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
|
|
|
|
IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
|
2016-10-16 23:20:37 +00:00
|
|
|
STOP
|
|
|
|
EOF
|
2017-06-01 08:22:00 +00:00
|
|
|
test_cmp_exclude_clean expected.log debug.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
2019-08-22 19:22:40 +00:00
|
|
|
# Make sure that the file appears dirty, so checkout below has to
|
|
|
|
# run the configured filter.
|
|
|
|
touch test.r &&
|
2016-10-16 23:20:37 +00:00
|
|
|
filter_git checkout --quiet --no-progress empty-branch &&
|
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
|
|
|
IN: clean test.r $S [OK] -- OUT: $S . [OK]
|
|
|
|
STOP
|
|
|
|
EOF
|
2017-06-01 08:22:00 +00:00
|
|
|
test_cmp_exclude_clean expected.log debug.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
2020-11-18 23:44:21 +00:00
|
|
|
filter_git checkout --quiet --no-progress main &&
|
2016-10-16 23:20:37 +00:00
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
2020-03-16 18:05:04 +00:00
|
|
|
IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
|
|
|
|
IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0 [OK]
|
|
|
|
IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
|
2016-10-16 23:20:37 +00:00
|
|
|
STOP
|
|
|
|
EOF
|
2017-06-01 08:22:00 +00:00
|
|
|
test_cmp_exclude_clean expected.log debug.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r &&
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r &&
|
2016-12-03 19:45:16 +00:00
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test3 '\''sq'\'',\$x=.o" "testsubdir/test3 '\''sq'\'',\$x=.r"
|
2016-10-16 23:20:37 +00:00
|
|
|
)
|
|
|
|
'
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success 'required process filter should filter data for various subcommands' '
|
|
|
|
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" &&
|
2020-03-16 18:05:06 +00:00
|
|
|
test_config_global filter.protocol.required true &&
|
|
|
|
(
|
|
|
|
cd repo &&
|
|
|
|
|
2020-11-07 01:12:57 +00:00
|
|
|
S=$(test_file_size test.r) &&
|
|
|
|
S2=$(test_file_size test2.r) &&
|
|
|
|
S3=$(test_file_size "testsubdir/test3 '\''sq'\'',\$x=.r") &&
|
2020-03-16 18:05:06 +00:00
|
|
|
M=$(git hash-object test.r) &&
|
|
|
|
M2=$(git hash-object test2.r) &&
|
|
|
|
M3=$(git hash-object "testsubdir/test3 '\''sq'\'',\$x=.r") &&
|
|
|
|
EMPTY=$(git hash-object /dev/null) &&
|
|
|
|
|
2020-11-18 23:44:21 +00:00
|
|
|
MAIN=$(git rev-parse --verify main) &&
|
2020-03-16 18:05:06 +00:00
|
|
|
|
|
|
|
cp "$TEST_ROOT/test.o" test5.r &&
|
|
|
|
git add test5.r &&
|
|
|
|
git commit -m "test commit 3" &&
|
|
|
|
git checkout empty-branch &&
|
2020-11-18 23:44:21 +00:00
|
|
|
filter_git rebase --onto empty-branch main^^ main &&
|
|
|
|
MAIN2=$(git rev-parse --verify main) &&
|
|
|
|
META="ref=refs/heads/main treeish=$MAIN2" &&
|
2020-03-16 18:05:06 +00:00
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
|
|
|
IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
|
|
|
|
IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0 [OK]
|
|
|
|
IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
|
|
|
|
STOP
|
|
|
|
EOF
|
2020-03-16 18:05:07 +00:00
|
|
|
test_cmp_exclude_clean expected.log debug.log &&
|
|
|
|
|
|
|
|
git reset --hard empty-branch &&
|
2020-11-18 23:44:21 +00:00
|
|
|
filter_git reset --hard $MAIN &&
|
|
|
|
META="treeish=$MAIN" &&
|
2020-03-16 18:05:07 +00:00
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
|
|
|
IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
|
|
|
|
IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0 [OK]
|
|
|
|
IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
|
|
|
|
STOP
|
|
|
|
EOF
|
|
|
|
test_cmp_exclude_clean expected.log debug.log &&
|
|
|
|
|
2020-11-18 23:44:21 +00:00
|
|
|
git branch old-main $MAIN &&
|
2020-03-16 18:05:07 +00:00
|
|
|
git reset --hard empty-branch &&
|
2020-11-18 23:44:21 +00:00
|
|
|
filter_git reset --hard old-main &&
|
|
|
|
META="ref=refs/heads/old-main treeish=$MAIN" &&
|
2020-03-16 18:05:07 +00:00
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
|
|
|
IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
|
|
|
|
IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0 [OK]
|
|
|
|
IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
|
|
|
|
STOP
|
|
|
|
EOF
|
2020-03-16 18:05:08 +00:00
|
|
|
test_cmp_exclude_clean expected.log debug.log &&
|
|
|
|
|
|
|
|
git checkout -b merge empty-branch &&
|
2020-11-18 23:44:21 +00:00
|
|
|
git branch -f main $MAIN2 &&
|
|
|
|
filter_git merge main &&
|
|
|
|
META="treeish=$MAIN2" &&
|
2020-03-16 18:05:08 +00:00
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
|
|
|
IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
|
|
|
|
IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0 [OK]
|
|
|
|
IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
|
|
|
|
STOP
|
|
|
|
EOF
|
|
|
|
test_cmp_exclude_clean expected.log debug.log &&
|
|
|
|
|
2020-11-18 23:44:21 +00:00
|
|
|
filter_git archive main >/dev/null &&
|
|
|
|
META="ref=refs/heads/main treeish=$MAIN2" &&
|
2020-03-16 18:05:08 +00:00
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
|
|
|
IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
|
|
|
|
IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0 [OK]
|
|
|
|
IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
|
|
|
|
STOP
|
|
|
|
EOF
|
|
|
|
test_cmp_exclude_clean expected.log debug.log &&
|
|
|
|
|
2020-11-18 23:44:21 +00:00
|
|
|
TREE="$(git rev-parse $MAIN2^{tree})" &&
|
2020-03-16 18:05:08 +00:00
|
|
|
filter_git archive $TREE >/dev/null &&
|
|
|
|
META="treeish=$TREE" &&
|
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
|
|
|
IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
|
|
|
|
IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0 [OK]
|
|
|
|
IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
|
|
|
|
STOP
|
|
|
|
EOF
|
2020-03-16 18:05:06 +00:00
|
|
|
test_cmp_exclude_clean expected.log debug.log
|
|
|
|
)
|
|
|
|
'
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success 'required process filter takes precedence' '
|
2016-10-16 23:20:37 +00:00
|
|
|
test_config_global filter.protocol.clean false &&
|
2022-08-15 01:06:38 +00:00
|
|
|
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean" &&
|
2016-10-16 23:20:37 +00:00
|
|
|
test_config_global filter.protocol.required true &&
|
|
|
|
rm -rf repo &&
|
|
|
|
mkdir repo &&
|
|
|
|
(
|
|
|
|
cd repo &&
|
|
|
|
git init &&
|
|
|
|
|
|
|
|
echo "*.r filter=protocol" >.gitattributes &&
|
|
|
|
cp "$TEST_ROOT/test.o" test.r &&
|
2020-11-07 01:12:57 +00:00
|
|
|
S=$(test_file_size test.r) &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
# Check that the process filter is invoked here
|
|
|
|
filter_git add . &&
|
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
|
|
|
IN: clean test.r $S [OK] -- OUT: $S . [OK]
|
|
|
|
STOP
|
|
|
|
EOF
|
2017-06-01 08:22:00 +00:00
|
|
|
test_cmp_count expected.log debug.log
|
2016-10-16 23:20:37 +00:00
|
|
|
)
|
|
|
|
'
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success 'required process filter should be used only for "clean" operation only' '
|
|
|
|
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean" &&
|
2016-10-16 23:20:37 +00:00
|
|
|
rm -rf repo &&
|
|
|
|
mkdir repo &&
|
|
|
|
(
|
|
|
|
cd repo &&
|
|
|
|
git init &&
|
|
|
|
|
|
|
|
echo "*.r filter=protocol" >.gitattributes &&
|
|
|
|
cp "$TEST_ROOT/test.o" test.r &&
|
2020-11-07 01:12:57 +00:00
|
|
|
S=$(test_file_size test.r) &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
filter_git add . &&
|
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
|
|
|
IN: clean test.r $S [OK] -- OUT: $S . [OK]
|
|
|
|
STOP
|
|
|
|
EOF
|
2017-06-01 08:22:00 +00:00
|
|
|
test_cmp_count expected.log debug.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
rm test.r &&
|
|
|
|
|
|
|
|
filter_git checkout --quiet --no-progress . &&
|
|
|
|
# If the filter would be used for "smudge", too, we would see
|
|
|
|
# "IN: smudge test.r 57 [OK] -- OUT: 57 . [OK]" here
|
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
|
|
|
STOP
|
|
|
|
EOF
|
2017-06-01 08:22:00 +00:00
|
|
|
test_cmp_exclude_clean expected.log debug.log
|
2016-10-16 23:20:37 +00:00
|
|
|
)
|
|
|
|
'
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success 'required process filter should process multiple packets' '
|
|
|
|
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" &&
|
2016-10-16 23:20:37 +00:00
|
|
|
test_config_global filter.protocol.required true &&
|
|
|
|
|
|
|
|
rm -rf repo &&
|
|
|
|
mkdir repo &&
|
|
|
|
(
|
|
|
|
cd repo &&
|
|
|
|
git init &&
|
|
|
|
|
|
|
|
# Generate data requiring 1, 2, 3 packets
|
|
|
|
S=65516 && # PKTLINE_DATA_MAXLEN -> Maximal size of a packet
|
|
|
|
generate_random_characters $(($S )) 1pkt_1__.file &&
|
|
|
|
generate_random_characters $(($S +1)) 2pkt_1+1.file &&
|
|
|
|
generate_random_characters $(($S*2-1)) 2pkt_2-1.file &&
|
|
|
|
generate_random_characters $(($S*2 )) 2pkt_2__.file &&
|
|
|
|
generate_random_characters $(($S*2+1)) 3pkt_2+1.file &&
|
|
|
|
|
|
|
|
for FILE in "$TEST_ROOT"/*.file
|
|
|
|
do
|
|
|
|
cp "$FILE" . &&
|
2021-12-09 05:11:12 +00:00
|
|
|
rot13.sh <"$FILE" >"$FILE.rot13" || return 1
|
2016-10-16 23:20:37 +00:00
|
|
|
done &&
|
|
|
|
|
|
|
|
echo "*.file filter=protocol" >.gitattributes &&
|
|
|
|
filter_git add *.file .gitattributes &&
|
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
|
|
|
IN: clean 1pkt_1__.file $(($S )) [OK] -- OUT: $(($S )) . [OK]
|
|
|
|
IN: clean 2pkt_1+1.file $(($S +1)) [OK] -- OUT: $(($S +1)) .. [OK]
|
|
|
|
IN: clean 2pkt_2-1.file $(($S*2-1)) [OK] -- OUT: $(($S*2-1)) .. [OK]
|
|
|
|
IN: clean 2pkt_2__.file $(($S*2 )) [OK] -- OUT: $(($S*2 )) .. [OK]
|
|
|
|
IN: clean 3pkt_2+1.file $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK]
|
|
|
|
STOP
|
|
|
|
EOF
|
2017-06-01 08:22:00 +00:00
|
|
|
test_cmp_count expected.log debug.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
2020-03-16 18:05:04 +00:00
|
|
|
M1="blob=$(git hash-object 1pkt_1__.file)" &&
|
|
|
|
M2="blob=$(git hash-object 2pkt_1+1.file)" &&
|
|
|
|
M3="blob=$(git hash-object 2pkt_2-1.file)" &&
|
|
|
|
M4="blob=$(git hash-object 2pkt_2__.file)" &&
|
|
|
|
M5="blob=$(git hash-object 3pkt_2+1.file)" &&
|
|
|
|
rm -f *.file debug.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
filter_git checkout --quiet --no-progress -- *.file &&
|
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
2020-03-16 18:05:04 +00:00
|
|
|
IN: smudge 1pkt_1__.file $M1 $(($S )) [OK] -- OUT: $(($S )) . [OK]
|
|
|
|
IN: smudge 2pkt_1+1.file $M2 $(($S +1)) [OK] -- OUT: $(($S +1)) .. [OK]
|
|
|
|
IN: smudge 2pkt_2-1.file $M3 $(($S*2-1)) [OK] -- OUT: $(($S*2-1)) .. [OK]
|
|
|
|
IN: smudge 2pkt_2__.file $M4 $(($S*2 )) [OK] -- OUT: $(($S*2 )) .. [OK]
|
|
|
|
IN: smudge 3pkt_2+1.file $M5 $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK]
|
2016-10-16 23:20:37 +00:00
|
|
|
STOP
|
|
|
|
EOF
|
2017-06-01 08:22:00 +00:00
|
|
|
test_cmp_exclude_clean expected.log debug.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
for FILE in *.file
|
|
|
|
do
|
2021-12-09 05:11:12 +00:00
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/$FILE" $FILE || return 1
|
2016-10-16 23:20:37 +00:00
|
|
|
done
|
|
|
|
)
|
|
|
|
'
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success 'required process filter with clean error should fail' '
|
|
|
|
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" &&
|
2016-10-16 23:20:37 +00:00
|
|
|
test_config_global filter.protocol.required true &&
|
|
|
|
rm -rf repo &&
|
|
|
|
mkdir repo &&
|
|
|
|
(
|
|
|
|
cd repo &&
|
|
|
|
git init &&
|
|
|
|
|
|
|
|
echo "*.r filter=protocol" >.gitattributes &&
|
|
|
|
|
|
|
|
cp "$TEST_ROOT/test.o" test.r &&
|
|
|
|
echo "this is going to fail" >clean-write-fail.r &&
|
|
|
|
echo "content-test3-subdir" >test3.r &&
|
|
|
|
|
|
|
|
test_must_fail git add .
|
|
|
|
)
|
|
|
|
'
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success 'process filter should restart after unexpected write failure' '
|
|
|
|
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" &&
|
2016-10-16 23:20:37 +00:00
|
|
|
rm -rf repo &&
|
|
|
|
mkdir repo &&
|
|
|
|
(
|
|
|
|
cd repo &&
|
|
|
|
git init &&
|
|
|
|
|
|
|
|
echo "*.r filter=protocol" >.gitattributes &&
|
|
|
|
|
|
|
|
cp "$TEST_ROOT/test.o" test.r &&
|
|
|
|
cp "$TEST_ROOT/test2.o" test2.r &&
|
|
|
|
echo "this is going to fail" >smudge-write-fail.o &&
|
|
|
|
cp smudge-write-fail.o smudge-write-fail.r &&
|
|
|
|
|
2020-11-07 01:12:57 +00:00
|
|
|
S=$(test_file_size test.r) &&
|
|
|
|
S2=$(test_file_size test2.r) &&
|
|
|
|
SF=$(test_file_size smudge-write-fail.r) &&
|
2020-03-16 18:05:04 +00:00
|
|
|
M=$(git hash-object test.r) &&
|
|
|
|
M2=$(git hash-object test2.r) &&
|
|
|
|
MF=$(git hash-object smudge-write-fail.r) &&
|
|
|
|
rm -f debug.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
git add . &&
|
|
|
|
rm -f *.r &&
|
|
|
|
|
2017-06-01 08:22:00 +00:00
|
|
|
rm -f debug.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
git checkout --quiet --no-progress . 2>git-stderr.log &&
|
|
|
|
|
2022-08-15 01:06:36 +00:00
|
|
|
grep "smudge write error" git-stderr.log &&
|
2018-07-21 07:49:29 +00:00
|
|
|
test_i18ngrep "error: external filter" git-stderr.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
2020-03-16 18:05:04 +00:00
|
|
|
IN: smudge smudge-write-fail.r blob=$MF $SF [OK] -- [WRITE FAIL]
|
2016-10-16 23:20:37 +00:00
|
|
|
START
|
|
|
|
init handshake complete
|
2020-03-16 18:05:04 +00:00
|
|
|
IN: smudge test.r blob=$M $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
|
2016-10-16 23:20:37 +00:00
|
|
|
STOP
|
|
|
|
EOF
|
2017-06-01 08:22:00 +00:00
|
|
|
test_cmp_exclude_clean expected.log debug.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r &&
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r &&
|
|
|
|
|
|
|
|
# Smudge failed
|
|
|
|
! test_cmp smudge-write-fail.o smudge-write-fail.r &&
|
2016-11-02 18:18:25 +00:00
|
|
|
rot13.sh <smudge-write-fail.o >expected &&
|
2016-10-16 23:20:37 +00:00
|
|
|
git cat-file blob :smudge-write-fail.r >actual &&
|
|
|
|
test_cmp expected actual
|
|
|
|
)
|
|
|
|
'
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success 'process filter should not be restarted if it signals an error' '
|
|
|
|
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" &&
|
2016-10-16 23:20:37 +00:00
|
|
|
rm -rf repo &&
|
|
|
|
mkdir repo &&
|
|
|
|
(
|
|
|
|
cd repo &&
|
|
|
|
git init &&
|
|
|
|
|
|
|
|
echo "*.r filter=protocol" >.gitattributes &&
|
|
|
|
|
|
|
|
cp "$TEST_ROOT/test.o" test.r &&
|
|
|
|
cp "$TEST_ROOT/test2.o" test2.r &&
|
|
|
|
echo "this will cause an error" >error.o &&
|
|
|
|
cp error.o error.r &&
|
|
|
|
|
2020-11-07 01:12:57 +00:00
|
|
|
S=$(test_file_size test.r) &&
|
|
|
|
S2=$(test_file_size test2.r) &&
|
|
|
|
SE=$(test_file_size error.r) &&
|
2020-03-16 18:05:04 +00:00
|
|
|
M=$(git hash-object test.r) &&
|
|
|
|
M2=$(git hash-object test2.r) &&
|
|
|
|
ME=$(git hash-object error.r) &&
|
|
|
|
rm -f debug.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
git add . &&
|
|
|
|
rm -f *.r &&
|
|
|
|
|
|
|
|
filter_git checkout --quiet --no-progress . &&
|
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
2020-03-16 18:05:04 +00:00
|
|
|
IN: smudge error.r blob=$ME $SE [OK] -- [ERROR]
|
|
|
|
IN: smudge test.r blob=$M $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
|
2016-10-16 23:20:37 +00:00
|
|
|
STOP
|
|
|
|
EOF
|
2017-06-01 08:22:00 +00:00
|
|
|
test_cmp_exclude_clean expected.log debug.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r &&
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r &&
|
|
|
|
test_cmp error.o error.r
|
|
|
|
)
|
|
|
|
'
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success 'process filter abort stops processing of all further files' '
|
|
|
|
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" &&
|
2016-10-16 23:20:37 +00:00
|
|
|
rm -rf repo &&
|
|
|
|
mkdir repo &&
|
|
|
|
(
|
|
|
|
cd repo &&
|
|
|
|
git init &&
|
|
|
|
|
|
|
|
echo "*.r filter=protocol" >.gitattributes &&
|
|
|
|
|
|
|
|
cp "$TEST_ROOT/test.o" test.r &&
|
|
|
|
cp "$TEST_ROOT/test2.o" test2.r &&
|
|
|
|
echo "error this blob and all future blobs" >abort.o &&
|
|
|
|
cp abort.o abort.r &&
|
|
|
|
|
2020-03-16 18:05:04 +00:00
|
|
|
M="blob=$(git hash-object abort.r)" &&
|
|
|
|
rm -f debug.log &&
|
2020-11-07 01:12:57 +00:00
|
|
|
SA=$(test_file_size abort.r) &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
git add . &&
|
|
|
|
rm -f *.r &&
|
|
|
|
|
2020-03-16 18:05:04 +00:00
|
|
|
|
2016-10-16 23:20:37 +00:00
|
|
|
# Note: This test assumes that Git filters files in alphabetical
|
|
|
|
# order ("abort.r" before "test.r").
|
|
|
|
filter_git checkout --quiet --no-progress . &&
|
|
|
|
cat >expected.log <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
2020-03-16 18:05:04 +00:00
|
|
|
IN: smudge abort.r $M $SA [OK] -- [ABORT]
|
2016-10-16 23:20:37 +00:00
|
|
|
STOP
|
|
|
|
EOF
|
2017-06-01 08:22:00 +00:00
|
|
|
test_cmp_exclude_clean expected.log debug.log &&
|
2016-10-16 23:20:37 +00:00
|
|
|
|
|
|
|
test_cmp "$TEST_ROOT/test.o" test.r &&
|
|
|
|
test_cmp "$TEST_ROOT/test2.o" test2.r &&
|
|
|
|
test_cmp abort.o abort.r
|
|
|
|
)
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success PERL 'invalid process filter must fail (and not hang!)' '
|
|
|
|
test_config_global filter.protocol.process cat &&
|
|
|
|
test_config_global filter.protocol.required true &&
|
|
|
|
rm -rf repo &&
|
|
|
|
mkdir repo &&
|
|
|
|
(
|
|
|
|
cd repo &&
|
|
|
|
git init &&
|
|
|
|
|
|
|
|
echo "*.r filter=protocol" >.gitattributes &&
|
|
|
|
|
|
|
|
cp "$TEST_ROOT/test.o" test.r &&
|
|
|
|
test_must_fail git add . 2>git-stderr.log &&
|
2017-07-26 18:17:29 +00:00
|
|
|
grep "expected git-filter-server" git-stderr.log
|
2016-10-16 23:20:37 +00:00
|
|
|
)
|
|
|
|
'
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success 'delayed checkout in process filter' '
|
|
|
|
test_config_global filter.a.process "test-tool rot13-filter --log=a.log clean smudge delay" &&
|
2017-06-30 20:41:28 +00:00
|
|
|
test_config_global filter.a.required true &&
|
2022-08-15 01:06:38 +00:00
|
|
|
test_config_global filter.b.process "test-tool rot13-filter --log=b.log clean smudge delay" &&
|
2017-06-30 20:41:28 +00:00
|
|
|
test_config_global filter.b.required true &&
|
|
|
|
|
|
|
|
rm -rf repo &&
|
|
|
|
mkdir repo &&
|
|
|
|
(
|
|
|
|
cd repo &&
|
|
|
|
git init &&
|
|
|
|
echo "*.a filter=a" >.gitattributes &&
|
|
|
|
echo "*.b filter=b" >>.gitattributes &&
|
|
|
|
cp "$TEST_ROOT/test.o" test.a &&
|
|
|
|
cp "$TEST_ROOT/test.o" test-delay10.a &&
|
|
|
|
cp "$TEST_ROOT/test.o" test-delay11.a &&
|
|
|
|
cp "$TEST_ROOT/test.o" test-delay20.a &&
|
|
|
|
cp "$TEST_ROOT/test.o" test-delay10.b &&
|
|
|
|
git add . &&
|
|
|
|
git commit -m "test commit"
|
|
|
|
) &&
|
|
|
|
|
2020-11-07 01:12:57 +00:00
|
|
|
S=$(test_file_size "$TEST_ROOT/test.o") &&
|
2020-11-18 23:44:21 +00:00
|
|
|
PM="ref=refs/heads/main treeish=$(git -C repo rev-parse --verify main) " &&
|
|
|
|
M="${PM}blob=$(git -C repo rev-parse --verify main:test.a)" &&
|
2017-06-30 20:41:28 +00:00
|
|
|
cat >a.exp <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
2020-03-16 18:05:04 +00:00
|
|
|
IN: smudge test.a $M $S [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge test-delay10.a $M $S [OK] -- [DELAYED]
|
|
|
|
IN: smudge test-delay11.a $M $S [OK] -- [DELAYED]
|
|
|
|
IN: smudge test-delay20.a $M $S [OK] -- [DELAYED]
|
2017-06-30 20:41:28 +00:00
|
|
|
IN: list_available_blobs test-delay10.a test-delay11.a [OK]
|
2020-03-16 18:05:04 +00:00
|
|
|
IN: smudge test-delay10.a $M 0 [OK] -- OUT: $S . [OK]
|
|
|
|
IN: smudge test-delay11.a $M 0 [OK] -- OUT: $S . [OK]
|
2017-06-30 20:41:28 +00:00
|
|
|
IN: list_available_blobs test-delay20.a [OK]
|
2020-03-16 18:05:04 +00:00
|
|
|
IN: smudge test-delay20.a $M 0 [OK] -- OUT: $S . [OK]
|
2017-06-30 20:41:28 +00:00
|
|
|
IN: list_available_blobs [OK]
|
|
|
|
STOP
|
|
|
|
EOF
|
|
|
|
cat >b.exp <<-EOF &&
|
|
|
|
START
|
|
|
|
init handshake complete
|
2020-03-16 18:05:04 +00:00
|
|
|
IN: smudge test-delay10.b $M $S [OK] -- [DELAYED]
|
2017-06-30 20:41:28 +00:00
|
|
|
IN: list_available_blobs test-delay10.b [OK]
|
2020-03-16 18:05:04 +00:00
|
|
|
IN: smudge test-delay10.b $M 0 [OK] -- OUT: $S . [OK]
|
2017-06-30 20:41:28 +00:00
|
|
|
IN: list_available_blobs [OK]
|
|
|
|
STOP
|
|
|
|
EOF
|
|
|
|
|
|
|
|
rm -rf repo-cloned &&
|
|
|
|
filter_git clone repo repo-cloned &&
|
|
|
|
test_cmp_count a.exp repo-cloned/a.log &&
|
|
|
|
test_cmp_count b.exp repo-cloned/b.log &&
|
|
|
|
|
|
|
|
(
|
|
|
|
cd repo-cloned &&
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.a &&
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.a &&
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay11.a &&
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay20.a &&
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.b &&
|
|
|
|
|
|
|
|
rm *.a *.b &&
|
|
|
|
filter_git checkout . &&
|
2020-03-16 18:05:05 +00:00
|
|
|
# We are not checking out a ref here, so filter out ref metadata.
|
|
|
|
sed -e "s!$PM!!" ../a.exp >a.exp.filtered &&
|
|
|
|
sed -e "s!$PM!!" ../b.exp >b.exp.filtered &&
|
|
|
|
test_cmp_count a.exp.filtered a.log &&
|
|
|
|
test_cmp_count b.exp.filtered b.log &&
|
2017-06-30 20:41:28 +00:00
|
|
|
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.a &&
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.a &&
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay11.a &&
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay20.a &&
|
|
|
|
test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.b
|
|
|
|
)
|
|
|
|
'
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success 'missing file in delayed checkout' '
|
|
|
|
test_config_global filter.bug.process "test-tool rot13-filter --log=bug.log clean smudge delay" &&
|
2017-06-30 20:41:28 +00:00
|
|
|
test_config_global filter.bug.required true &&
|
|
|
|
|
|
|
|
rm -rf repo &&
|
|
|
|
mkdir repo &&
|
|
|
|
(
|
|
|
|
cd repo &&
|
|
|
|
git init &&
|
|
|
|
echo "*.a filter=bug" >.gitattributes &&
|
2018-07-02 00:23:55 +00:00
|
|
|
cp "$TEST_ROOT/test.o" missing-delay.a &&
|
2017-06-30 20:41:28 +00:00
|
|
|
git add . &&
|
|
|
|
git commit -m "test commit"
|
|
|
|
) &&
|
|
|
|
|
|
|
|
rm -rf repo-cloned &&
|
|
|
|
test_must_fail git clone repo repo-cloned 2>git-stderr.log &&
|
|
|
|
grep "error: .missing-delay\.a. was not filtered properly" git-stderr.log
|
|
|
|
'
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success 'invalid file in delayed checkout' '
|
|
|
|
test_config_global filter.bug.process "test-tool rot13-filter --log=bug.log clean smudge delay" &&
|
2017-06-30 20:41:28 +00:00
|
|
|
test_config_global filter.bug.required true &&
|
|
|
|
|
|
|
|
rm -rf repo &&
|
|
|
|
mkdir repo &&
|
|
|
|
(
|
|
|
|
cd repo &&
|
|
|
|
git init &&
|
|
|
|
echo "*.a filter=bug" >.gitattributes &&
|
|
|
|
cp "$TEST_ROOT/test.o" invalid-delay.a &&
|
2018-07-02 00:23:55 +00:00
|
|
|
cp "$TEST_ROOT/test.o" unfiltered &&
|
2017-06-30 20:41:28 +00:00
|
|
|
git add . &&
|
|
|
|
git commit -m "test commit"
|
|
|
|
) &&
|
|
|
|
|
|
|
|
rm -rf repo-cloned &&
|
|
|
|
test_must_fail git clone repo repo-cloned 2>git-stderr.log &&
|
|
|
|
grep "error: external filter .* signaled that .unfiltered. is now available although it has not been delayed earlier" git-stderr.log
|
|
|
|
'
|
|
|
|
|
checkout: fix bug that makes checkout follow symlinks in leading path
Before checking out a file, we have to confirm that all of its leading
components are real existing directories. And to reduce the number of
lstat() calls in this process, we cache the last leading path known to
contain only directories. However, when a path collision occurs (e.g.
when checking out case-sensitive files in case-insensitive file
systems), a cached path might have its file type changed on disk,
leaving the cache on an invalid state. Normally, this doesn't bring
any bad consequences as we usually check out files in index order, and
therefore, by the time the cached path becomes outdated, we no longer
need it anyway (because all files in that directory would have already
been written).
But, there are some users of the checkout machinery that do not always
follow the index order. In particular: checkout-index writes the paths
in the same order that they appear on the CLI (or stdin); and the
delayed checkout feature -- used when a long-running filter process
replies with "status=delayed" -- postpones the checkout of some entries,
thus modifying the checkout order.
When we have to check out an out-of-order entry and the lstat() cache is
invalid (due to a previous path collision), checkout_entry() may end up
using the invalid data and thrusting that the leading components are
real directories when, in reality, they are not. In the best case
scenario, where the directory was replaced by a regular file, the user
will get an error: "fatal: unable to create file 'foo/bar': Not a
directory". But if the directory was replaced by a symlink, checkout
could actually end up following the symlink and writing the file at a
wrong place, even outside the repository. Since delayed checkout is
affected by this bug, it could be used by an attacker to write
arbitrary files during the clone of a maliciously crafted repository.
Some candidate solutions considered were to disable the lstat() cache
during unordered checkouts or sort the entries before passing them to
the checkout machinery. But both ideas include some performance penalty
and they don't future-proof the code against new unordered use cases.
Instead, we now manually reset the lstat cache whenever we successfully
remove a directory. Note: We are not even checking whether the directory
was the same as the lstat cache points to because we might face a
scenario where the paths refer to the same location but differ due to
case folding, precomposed UTF-8 issues, or the presence of `..`
components in the path. Two regression tests, with case-collisions and
utf8-collisions, are also added for both checkout-index and delayed
checkout.
Note: to make the previously mentioned clone attack unfeasible, it would
be sufficient to reset the lstat cache only after the remove_subtree()
call inside checkout_entry(). This is the place where we would remove a
directory whose path collides with the path of another entry that we are
currently trying to check out (possibly a symlink). However, in the
interest of a thorough fix that does not leave Git open to
similar-but-not-identical attack vectors, we decided to intercept
all `rmdir()` calls in one fell swoop.
This addresses CVE-2021-21300.
Co-authored-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>
2020-12-10 13:27:55 +00:00
|
|
|
for mode in 'case' 'utf-8'
|
|
|
|
do
|
|
|
|
case "$mode" in
|
|
|
|
case) dir='A' symlink='a' mode_prereq='CASE_INSENSITIVE_FS' ;;
|
|
|
|
utf-8)
|
|
|
|
dir=$(printf "\141\314\210") symlink=$(printf "\303\244")
|
|
|
|
mode_prereq='UTF8_NFD_TO_NFC' ;;
|
|
|
|
esac
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success SYMLINKS,$mode_prereq \
|
checkout: fix bug that makes checkout follow symlinks in leading path
Before checking out a file, we have to confirm that all of its leading
components are real existing directories. And to reduce the number of
lstat() calls in this process, we cache the last leading path known to
contain only directories. However, when a path collision occurs (e.g.
when checking out case-sensitive files in case-insensitive file
systems), a cached path might have its file type changed on disk,
leaving the cache on an invalid state. Normally, this doesn't bring
any bad consequences as we usually check out files in index order, and
therefore, by the time the cached path becomes outdated, we no longer
need it anyway (because all files in that directory would have already
been written).
But, there are some users of the checkout machinery that do not always
follow the index order. In particular: checkout-index writes the paths
in the same order that they appear on the CLI (or stdin); and the
delayed checkout feature -- used when a long-running filter process
replies with "status=delayed" -- postpones the checkout of some entries,
thus modifying the checkout order.
When we have to check out an out-of-order entry and the lstat() cache is
invalid (due to a previous path collision), checkout_entry() may end up
using the invalid data and thrusting that the leading components are
real directories when, in reality, they are not. In the best case
scenario, where the directory was replaced by a regular file, the user
will get an error: "fatal: unable to create file 'foo/bar': Not a
directory". But if the directory was replaced by a symlink, checkout
could actually end up following the symlink and writing the file at a
wrong place, even outside the repository. Since delayed checkout is
affected by this bug, it could be used by an attacker to write
arbitrary files during the clone of a maliciously crafted repository.
Some candidate solutions considered were to disable the lstat() cache
during unordered checkouts or sort the entries before passing them to
the checkout machinery. But both ideas include some performance penalty
and they don't future-proof the code against new unordered use cases.
Instead, we now manually reset the lstat cache whenever we successfully
remove a directory. Note: We are not even checking whether the directory
was the same as the lstat cache points to because we might face a
scenario where the paths refer to the same location but differ due to
case folding, precomposed UTF-8 issues, or the presence of `..`
components in the path. Two regression tests, with case-collisions and
utf8-collisions, are also added for both checkout-index and delayed
checkout.
Note: to make the previously mentioned clone attack unfeasible, it would
be sufficient to reset the lstat cache only after the remove_subtree()
call inside checkout_entry(). This is the place where we would remove a
directory whose path collides with the path of another entry that we are
currently trying to check out (possibly a symlink). However, in the
interest of a thorough fix that does not leave Git open to
similar-but-not-identical attack vectors, we decided to intercept
all `rmdir()` calls in one fell swoop.
This addresses CVE-2021-21300.
Co-authored-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>
2020-12-10 13:27:55 +00:00
|
|
|
"delayed checkout with $mode-collision don't write to the wrong place" '
|
|
|
|
test_config_global filter.delay.process \
|
2022-08-15 01:06:38 +00:00
|
|
|
"test-tool rot13-filter --always-delay --log=delayed.log clean smudge delay" &&
|
checkout: fix bug that makes checkout follow symlinks in leading path
Before checking out a file, we have to confirm that all of its leading
components are real existing directories. And to reduce the number of
lstat() calls in this process, we cache the last leading path known to
contain only directories. However, when a path collision occurs (e.g.
when checking out case-sensitive files in case-insensitive file
systems), a cached path might have its file type changed on disk,
leaving the cache on an invalid state. Normally, this doesn't bring
any bad consequences as we usually check out files in index order, and
therefore, by the time the cached path becomes outdated, we no longer
need it anyway (because all files in that directory would have already
been written).
But, there are some users of the checkout machinery that do not always
follow the index order. In particular: checkout-index writes the paths
in the same order that they appear on the CLI (or stdin); and the
delayed checkout feature -- used when a long-running filter process
replies with "status=delayed" -- postpones the checkout of some entries,
thus modifying the checkout order.
When we have to check out an out-of-order entry and the lstat() cache is
invalid (due to a previous path collision), checkout_entry() may end up
using the invalid data and thrusting that the leading components are
real directories when, in reality, they are not. In the best case
scenario, where the directory was replaced by a regular file, the user
will get an error: "fatal: unable to create file 'foo/bar': Not a
directory". But if the directory was replaced by a symlink, checkout
could actually end up following the symlink and writing the file at a
wrong place, even outside the repository. Since delayed checkout is
affected by this bug, it could be used by an attacker to write
arbitrary files during the clone of a maliciously crafted repository.
Some candidate solutions considered were to disable the lstat() cache
during unordered checkouts or sort the entries before passing them to
the checkout machinery. But both ideas include some performance penalty
and they don't future-proof the code against new unordered use cases.
Instead, we now manually reset the lstat cache whenever we successfully
remove a directory. Note: We are not even checking whether the directory
was the same as the lstat cache points to because we might face a
scenario where the paths refer to the same location but differ due to
case folding, precomposed UTF-8 issues, or the presence of `..`
components in the path. Two regression tests, with case-collisions and
utf8-collisions, are also added for both checkout-index and delayed
checkout.
Note: to make the previously mentioned clone attack unfeasible, it would
be sufficient to reset the lstat cache only after the remove_subtree()
call inside checkout_entry(). This is the place where we would remove a
directory whose path collides with the path of another entry that we are
currently trying to check out (possibly a symlink). However, in the
interest of a thorough fix that does not leave Git open to
similar-but-not-identical attack vectors, we decided to intercept
all `rmdir()` calls in one fell swoop.
This addresses CVE-2021-21300.
Co-authored-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>
2020-12-10 13:27:55 +00:00
|
|
|
test_config_global filter.delay.required true &&
|
|
|
|
|
|
|
|
git init $mode-collision &&
|
|
|
|
(
|
|
|
|
cd $mode-collision &&
|
|
|
|
mkdir target-dir &&
|
|
|
|
|
|
|
|
empty_oid=$(printf "" | git hash-object -w --stdin) &&
|
|
|
|
symlink_oid=$(printf "%s" "$PWD/target-dir" | git hash-object -w --stdin) &&
|
|
|
|
attr_oid=$(echo "$dir/z filter=delay" | git hash-object -w --stdin) &&
|
|
|
|
|
|
|
|
cat >objs <<-EOF &&
|
|
|
|
100644 blob $empty_oid $dir/x
|
|
|
|
100644 blob $empty_oid $dir/y
|
|
|
|
100644 blob $empty_oid $dir/z
|
|
|
|
120000 blob $symlink_oid $symlink
|
|
|
|
100644 blob $attr_oid .gitattributes
|
|
|
|
EOF
|
|
|
|
|
|
|
|
git update-index --index-info <objs &&
|
|
|
|
git commit -m "test commit"
|
|
|
|
) &&
|
|
|
|
|
|
|
|
git clone $mode-collision $mode-collision-cloned &&
|
|
|
|
# Make sure z was really delayed
|
|
|
|
grep "IN: smudge $dir/z .* \\[DELAYED\\]" $mode-collision-cloned/delayed.log &&
|
|
|
|
|
|
|
|
# Should not create $dir/z at $symlink/z
|
|
|
|
test_path_is_missing $mode-collision/target-dir/z
|
|
|
|
'
|
|
|
|
done
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success SYMLINKS,CASE_INSENSITIVE_FS \
|
2021-02-02 21:09:52 +00:00
|
|
|
"delayed checkout with submodule collision don't write to the wrong place" '
|
|
|
|
git init collision-with-submodule &&
|
|
|
|
(
|
|
|
|
cd collision-with-submodule &&
|
2022-08-15 01:06:38 +00:00
|
|
|
git config filter.delay.process "test-tool rot13-filter --always-delay --log=delayed.log clean smudge delay" &&
|
2021-02-02 21:09:52 +00:00
|
|
|
git config filter.delay.required true &&
|
|
|
|
|
|
|
|
# We need Git to treat the submodule "a" and the
|
|
|
|
# leading dir "A" as different paths in the index.
|
|
|
|
git config --local core.ignoreCase false &&
|
|
|
|
|
|
|
|
empty_oid=$(printf "" | git hash-object -w --stdin) &&
|
|
|
|
attr_oid=$(echo "A/B/y filter=delay" | git hash-object -w --stdin) &&
|
|
|
|
cat >objs <<-EOF &&
|
|
|
|
100644 blob $empty_oid A/B/x
|
|
|
|
100644 blob $empty_oid A/B/y
|
|
|
|
100644 blob $attr_oid .gitattributes
|
|
|
|
EOF
|
|
|
|
git update-index --index-info <objs &&
|
|
|
|
|
|
|
|
git init a &&
|
|
|
|
mkdir target-dir &&
|
|
|
|
symlink_oid=$(printf "%s" "$PWD/target-dir" | git -C a hash-object -w --stdin) &&
|
|
|
|
echo "120000 blob $symlink_oid b" >objs &&
|
|
|
|
git -C a update-index --index-info <objs &&
|
|
|
|
git -C a commit -m sub &&
|
|
|
|
git submodule add ./a &&
|
|
|
|
git commit -m super &&
|
|
|
|
|
|
|
|
git checkout --recurse-submodules . &&
|
|
|
|
grep "IN: smudge A/B/y .* \\[DELAYED\\]" delayed.log &&
|
|
|
|
test_path_is_missing target-dir/y
|
|
|
|
)
|
|
|
|
'
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success 'setup for progress tests' '
|
2021-08-26 19:10:06 +00:00
|
|
|
git init progress &&
|
|
|
|
(
|
|
|
|
cd progress &&
|
2022-08-15 01:06:38 +00:00
|
|
|
git config filter.delay.process "test-tool rot13-filter --log=delay-progress.log clean smudge delay" &&
|
2021-08-26 19:10:06 +00:00
|
|
|
git config filter.delay.required true &&
|
|
|
|
|
|
|
|
echo "*.a filter=delay" >.gitattributes &&
|
|
|
|
touch test-delay10.a &&
|
|
|
|
git add . &&
|
|
|
|
git commit -m files
|
|
|
|
)
|
|
|
|
'
|
|
|
|
|
|
|
|
test_delayed_checkout_progress () {
|
|
|
|
if test "$1" = "!"
|
|
|
|
then
|
|
|
|
local expect_progress=N &&
|
|
|
|
shift
|
|
|
|
else
|
|
|
|
local expect_progress=
|
|
|
|
fi &&
|
|
|
|
|
|
|
|
if test $# -lt 1
|
|
|
|
then
|
|
|
|
BUG "no command given to test_delayed_checkout_progress"
|
|
|
|
fi &&
|
|
|
|
|
|
|
|
(
|
|
|
|
cd progress &&
|
|
|
|
GIT_PROGRESS_DELAY=0 &&
|
|
|
|
export GIT_PROGRESS_DELAY &&
|
|
|
|
rm -f *.a delay-progress.log &&
|
|
|
|
|
|
|
|
"$@" 2>err &&
|
|
|
|
grep "IN: smudge test-delay10.a .* \\[DELAYED\\]" delay-progress.log &&
|
|
|
|
if test "$expect_progress" = N
|
|
|
|
then
|
|
|
|
! grep "Filtering content" err
|
|
|
|
else
|
|
|
|
grep "Filtering content" err
|
|
|
|
fi
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
for mode in pathspec branch
|
|
|
|
do
|
|
|
|
case "$mode" in
|
|
|
|
pathspec) opt='.' ;;
|
|
|
|
branch) opt='-f HEAD' ;;
|
|
|
|
esac
|
|
|
|
|
|
|
|
test_expect_success PERL,TTY "delayed checkout shows progress by default on tty ($mode checkout)" '
|
|
|
|
test_delayed_checkout_progress test_terminal git checkout $opt
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success PERL "delayed checkout ommits progress on non-tty ($mode checkout)" '
|
|
|
|
test_delayed_checkout_progress ! git checkout $opt
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success PERL,TTY "delayed checkout ommits progress with --quiet ($mode checkout)" '
|
|
|
|
test_delayed_checkout_progress ! test_terminal git checkout --quiet $opt
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success PERL,TTY "delayed checkout honors --[no]-progress ($mode checkout)" '
|
|
|
|
test_delayed_checkout_progress ! test_terminal git checkout --no-progress $opt &&
|
|
|
|
test_delayed_checkout_progress test_terminal git checkout --quiet --progress $opt
|
|
|
|
'
|
|
|
|
done
|
|
|
|
|
2022-08-15 01:06:38 +00:00
|
|
|
test_expect_success 'delayed checkout correctly reports the number of updated entries' '
|
2022-07-14 11:49:10 +00:00
|
|
|
rm -rf repo &&
|
|
|
|
git init repo &&
|
|
|
|
(
|
|
|
|
cd repo &&
|
2022-08-15 01:06:38 +00:00
|
|
|
git config filter.delay.process "test-tool rot13-filter --log=delayed.log clean smudge delay" &&
|
2022-07-14 11:49:10 +00:00
|
|
|
git config filter.delay.required true &&
|
|
|
|
|
|
|
|
echo "*.a filter=delay" >.gitattributes &&
|
|
|
|
echo a >test-delay10.a &&
|
|
|
|
echo a >test-delay11.a &&
|
|
|
|
git add . &&
|
|
|
|
git commit -m files &&
|
|
|
|
|
|
|
|
rm *.a &&
|
|
|
|
git checkout . 2>err &&
|
|
|
|
grep "IN: smudge test-delay10.a .* \\[DELAYED\\]" delayed.log &&
|
|
|
|
grep "IN: smudge test-delay11.a .* \\[DELAYED\\]" delayed.log &&
|
|
|
|
grep "Updated 2 paths from the index" err
|
|
|
|
)
|
|
|
|
'
|
|
|
|
|
2007-04-22 02:09:02 +00:00
|
|
|
test_done
|