From 8e61175e81052a627e8ed6db1fb12e71a9b75e3b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 26 Apr 2026 14:38:29 +0000 Subject: [PATCH 1/8] test-lib: allow bare repository access when breaking changes are enabled A future patch will change the `safe.bareRepository` default from `all` to `explicit` under `WITH_BREAKING_CHANGES`. At that point, every test that operates on a bare repository through implicit discovery would fail, regardless of whether the test is actually about discovery or about how a specific command behaves once inside a bare repository. The maintainer suggested [1] setting `safe.bareRepository=all` in the test environment's global config whenever `WITH_BREAKING_CHANGES` is in effect, rather than adjusting each affected test to access bare repositories explicitly (via `--git-dir`, `GIT_DIR`, or similar). This means the test suite continues to exercise only the historical default behavior even after the user-facing default changes, relying on a small number of dedicated tests in t0035 to validate the new, stricter default. Since `$HOME` points at the trash directory (which doubles as the test repository's working tree), writing to `$HOME/.gitconfig` also creates a file inside the working tree. Exclude it via `.git/info/exclude` to limit the fallout, though this does not help tests that use `git ls-files --others` without `--exclude-standard` or `git status --ignored`; those are addressed by subsequent commits. The `.git/info/exclude` write is guarded by `test -d .git/info` rather than using `mkdir -p`, because some tests (e.g. t0008) expect to create `.git/info/` themselves and would fail with Patrick Steinhardt's `set -e` preparation (ps/test-set-e-clean) if the directory already existed. For tests using `TEST_NO_CREATE_REPO` (where no `.git/` exists at all), the guard also handles that case. [1] https://lore.kernel.org/git/xmqqse98cc51.fsf@gitster.g/ Original-patch-by: Junio C Hamano Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/test-lib.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/t/test-lib.sh b/t/test-lib.sh index 70fd3e9baf..72ed87b781 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1597,6 +1597,19 @@ cd -P "$TRASH_DIRECTORY" || BAIL_OUT "cannot cd -P to \"$TRASH_DIRECTORY\"" TRASH_DIRECTORY=$(pwd) HOME="$TRASH_DIRECTORY" +if test -n "$WITH_BREAKING_CHANGES" +then + git config --global safe.bareRepository all && + # Only write to .git/info/exclude when the directory exists + # (i.e. when git init created the repo). If we mkdir -p it + # ourselves, tests that expect to create .git/info/ themselves + # (e.g. t0008) would fail. + if test -d .git/info + then + echo "/.gitconfig" >>.git/info/exclude + fi +fi + start_test_output "$0" # Convenience From a27fec3e7eb13a0941e75cd16a31e424c276b334 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 26 Apr 2026 14:38:30 +0000 Subject: [PATCH 2/8] t7900: do not let `$HOME/.gitconfig` interfere with XDG tests The XDG config tests for `git maintenance register/unregister` create a fresh `$XDG_CONFIG_HOME/git/config` and expect git to use that location. However, if `$HOME/.gitconfig` exists (which may happen when test-lib.sh writes global config, e.g. to set `safe.bareRepository`), git prefers `$HOME/.gitconfig` over the XDG location, and the `maintenance.repo` entry ends up in the wrong file. This is an inherent consequence of setting global config in test-lib.sh rather than adjusting individual tests: writing any entry to `$HOME/.gitconfig` has side effects beyond the intended setting, because the mere existence of that file changes which global config location git prefers for all subsequent writes. Individual per-test adjustments would not have this interaction. Fix this by overriding `HOME` to a non-existent directory inside the subshells that test XDG behavior. Since these subshells already override `XDG_CONFIG_HOME`, they do not need `$HOME/.gitconfig` at all, and the subshell scoping ensures the original `HOME` is restored automatically. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/t7900-maintenance.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 4700beacc1..4358df0424 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -101,8 +101,12 @@ test_expect_success "maintenance.autoDetach overrides gc.autoDetach" ' test_expect_success 'register uses XDG_CONFIG_HOME config if it exists' ' test_when_finished rm -r .config/git/config && ( + # Override HOME so that .gitconfig (which test-lib.sh may + # have created, e.g. to set safe.bareRepository) does not + # take precedence over the XDG location. + HOME=$PWD/must-not-exist && XDG_CONFIG_HOME=.config && - export XDG_CONFIG_HOME && + export HOME XDG_CONFIG_HOME && mkdir -p $XDG_CONFIG_HOME/git && >$XDG_CONFIG_HOME/git/config && git maintenance register && @@ -124,8 +128,12 @@ test_expect_success 'register does not need XDG_CONFIG_HOME config to exist' ' test_expect_success 'unregister uses XDG_CONFIG_HOME config if it exists' ' test_when_finished rm -r .config/git/config && ( + # Override HOME so that .gitconfig (which test-lib.sh may + # have created, e.g. to set safe.bareRepository) does not + # take precedence over the XDG location. + HOME=$PWD/must-not-exist && XDG_CONFIG_HOME=.config && - export XDG_CONFIG_HOME && + export HOME XDG_CONFIG_HOME && mkdir -p $XDG_CONFIG_HOME/git && >$XDG_CONFIG_HOME/git/config && git maintenance register && From 5fe676f44823dce11011b9b3b2214824a876eba4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 26 Apr 2026 14:38:31 +0000 Subject: [PATCH 3/8] t1300: remove global config settings injected by test-lib.sh Since test-lib.sh now writes `safe.bareRepository=all` to the global config when `WITH_BREAKING_CHANGES` is in effect, that entry shows up in `git config --list` output. Tests in t1300 that expect exact config contents then fail because of this unexpected extra line. Unlike the working-tree contamination fixed in the preceding commits, this is not about the file's existence but about its content leaking into test expectations. Since t1300 does not use bare repositories, simply remove the injected setting in a preparatory step. Signed-off-by: Johannes Schindelin Assisted-by: Claude Opus 4.6 Signed-off-by: Junio C Hamano --- t/t1300-config.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/t/t1300-config.sh b/t/t1300-config.sh index 128971ee12..11fc976f3a 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -11,6 +11,13 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh +# test-lib.sh may have added global config (e.g. safe.bareRepository) +# that would appear in "git config --list" output and break tests +# that expect exact config contents. +test_expect_success 'remove global config from test-lib.sh' ' + test_might_fail git config --global --unset-all safe.bareRepository +' + for mode in legacy subcommands do From 59227c0e27b527062440200c074f63ee40fb7bf4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 26 Apr 2026 14:38:32 +0000 Subject: [PATCH 4/8] t1305: use `--git-dir=.` for bare repo in include cycle test Earlier tests in t1305 overwrite `$HOME/.gitconfig` with their own content as part of testing config includes. This clobbers the `safe.bareRepository=all` entry that test-lib.sh writes when `WITH_BREAKING_CHANGES` is in effect, causing `git -C cycle config` to fail with "not in a git directory" when it tries to access the bare repository created by `git init --bare cycle`. Use `--git-dir=.` to access the bare repo explicitly, avoiding the dependency on global config for repository discovery. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/t1305-config-include.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh index 6e51f892f3..f3892578e4 100755 --- a/t/t1305-config-include.sh +++ b/t/t1305-config-include.sh @@ -350,9 +350,9 @@ test_expect_success 'conditional include, onbranch, implicit /** for /' ' test_expect_success 'include cycles are detected' ' git init --bare cycle && - git -C cycle config include.path cycle && + git -C cycle --git-dir=. config include.path cycle && git config -f cycle/cycle include.path config && - test_must_fail git -C cycle config --get-all test.value 2>stderr && + test_must_fail git -C cycle --git-dir=. config --get-all test.value 2>stderr && grep "exceeded maximum include depth" stderr ' From d97f27bf01a1ba2cfa66cc8e55f03e701084240e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 26 Apr 2026 14:38:33 +0000 Subject: [PATCH 5/8] t5601: restore `.gitconfig` after includeIf test One test in t5601 overwrites `$HOME/.gitconfig` with an `includeIf` configuration snippet and removes the file in its cleanup. This destroys the `safe.bareRepository=all` entry that test-lib.sh writes when `WITH_BREAKING_CHANGES` is in effect, causing later tests that use `git -C config` to fail with "not in a git directory". Back up `.gitconfig` before overwriting and restore it in the cleanup, so the global config survives into subsequent tests. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/t5601-clone.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index d743d986c4..3dd229c186 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -813,7 +813,9 @@ test_expect_success 'clone with includeIf' ' test_when_finished "rm -rf repo \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\"" && git clone --bare --no-local src "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && - test_when_finished "rm \"$HOME\"/.gitconfig" && + test_when_finished "cp \"$HOME\"/.gitconfig.bak \ + \"$HOME\"/.gitconfig 2>/dev/null || rm -f \"$HOME\"/.gitconfig" && + cp "$HOME"/.gitconfig "$HOME"/.gitconfig.bak 2>/dev/null && cat >"$HOME"/.gitconfig <<-EOF && [includeIf "onbranch:something"] path = /does/not/exist.inc From 83228f16611eae979421915836c5416eb4d02cf3 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 26 Apr 2026 14:38:34 +0000 Subject: [PATCH 6/8] ls-files tests: filter `.gitconfig` from `--others` output The global `safe.bareRepository=all` setting in test-lib.sh is written to `$HOME/.gitconfig`, which unfortunately lives inside the test repository's working tree. The `.git/info/exclude` entry added alongside it handles most commands, but `git ls-files --others` without `--exclude-standard` does not consult `info/exclude` at all, so the file appears in the output. Ideally, each test that accesses a bare repository would simply specify `--git-dir` or `GIT_DIR` explicitly, which would require no global config and produce no side effects in the working tree. As that approach was not taken, filter `.gitconfig` from the output before comparing against expected results. In t7104, the test already uses `--exclude-standard`, so it suffices to switch from the bare `git ls-files -o` to `git ls-files -o --exclude-standard` which respects the `info/exclude` entry; the other tests deliberately omit `--exclude-standard` because their purpose is to verify unfiltered `--others` output. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/t3000-ls-files-others.sh | 4 ++++ t/t3001-ls-files-others-exclude.sh | 3 +++ t/t3002-ls-files-dashpath.sh | 2 ++ t/t3009-ls-files-others-nonsubmodule.sh | 1 + t/t3011-common-prefixes-and-directory-traversal.sh | 3 ++- t/t7104-reset-hard.sh | 2 +- t/test-lib-functions.sh | 8 ++++++++ 7 files changed, 21 insertions(+), 2 deletions(-) diff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh index b41e7f0daa..b4f0fbfc55 100755 --- a/t/t3000-ls-files-others.sh +++ b/t/t3000-ls-files-others.sh @@ -53,16 +53,19 @@ test_expect_success 'setup: expected output' ' test_expect_success 'ls-files --others' ' git ls-files --others >output && + test_filter_gitconfig output && test_cmp expected1 output ' test_expect_success 'ls-files --others --directory' ' git ls-files --others --directory >output && + test_filter_gitconfig output && test_cmp expected2 output ' test_expect_success '--no-empty-directory hides empty directory' ' git ls-files --others --directory --no-empty-directory >output && + test_filter_gitconfig output && test_cmp expected3 output ' @@ -70,6 +73,7 @@ test_expect_success 'ls-files --others handles non-submodule .git' ' mkdir not-a-submodule && echo foo >not-a-submodule/.git && git ls-files -o >output && + test_filter_gitconfig output && test_cmp expected1 output ' diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index 4b67646285..202fb8d9ea 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -72,6 +72,7 @@ test_expect_success 'git ls-files --others with various exclude options.' ' --exclude-per-directory=.gitignore \ --exclude-from=.git/ignore \ >output && + test_filter_gitconfig output && test_cmp expect output ' @@ -84,6 +85,7 @@ test_expect_success 'git ls-files --others with \r\n line endings.' ' --exclude-per-directory=.gitignore \ --exclude-from=.git/ignore \ >output && + test_filter_gitconfig output && test_cmp expect output ' @@ -99,6 +101,7 @@ test_expect_success 'git ls-files --others with various exclude options.' ' --exclude-per-directory=.gitignore \ --exclude-from=.git/ignore \ >output && + test_filter_gitconfig output && test_cmp expect output ' diff --git a/t/t3002-ls-files-dashpath.sh b/t/t3002-ls-files-dashpath.sh index 31462cb441..6acaadbd67 100755 --- a/t/t3002-ls-files-dashpath.sh +++ b/t/t3002-ls-files-dashpath.sh @@ -24,6 +24,7 @@ test_expect_success 'setup' ' test_expect_success 'git ls-files without path restriction.' ' test_when_finished "rm -f expect" && git ls-files --others >output && + test_filter_gitconfig output && cat >expect <<-\EOF && -- -foo @@ -63,6 +64,7 @@ test_expect_success 'git ls-files with path restriction with -- --.' ' test_expect_success 'git ls-files with no path restriction.' ' test_when_finished "rm -f expect" && git ls-files --others -- >output && + test_filter_gitconfig output && cat >expect <<-\EOF && -- -foo diff --git a/t/t3009-ls-files-others-nonsubmodule.sh b/t/t3009-ls-files-others-nonsubmodule.sh index 963f3462b7..dc990c277b 100755 --- a/t/t3009-ls-files-others-nonsubmodule.sh +++ b/t/t3009-ls-files-others-nonsubmodule.sh @@ -36,6 +36,7 @@ test_expect_success 'setup: directories' ' test_expect_success 'ls-files --others handles untracked git repositories' ' git ls-files -o >output && + test_filter_gitconfig output && cat >expect <<-EOF && nonrepo-untracked-file/untracked output diff --git a/t/t3011-common-prefixes-and-directory-traversal.sh b/t/t3011-common-prefixes-and-directory-traversal.sh index 3da5b2b6e7..455e97954d 100755 --- a/t/t3011-common-prefixes-and-directory-traversal.sh +++ b/t/t3011-common-prefixes-and-directory-traversal.sh @@ -26,7 +26,7 @@ test_expect_success 'setup' ' ' test_expect_success 'git ls-files -o shows the right entries' ' - cat <<-EOF >expect && + cat >expect <<-EOF && .gitignore actual an_ignored_dir/ignored @@ -39,6 +39,7 @@ test_expect_success 'git ls-files -o shows the right entries' ' untracked_repo/ EOF git ls-files -o >actual && + test_filter_gitconfig actual && test_cmp expect actual ' diff --git a/t/t7104-reset-hard.sh b/t/t7104-reset-hard.sh index 7948ec392b..c23d6e3f52 100755 --- a/t/t7104-reset-hard.sh +++ b/t/t7104-reset-hard.sh @@ -21,7 +21,7 @@ test_expect_success setup ' rm -f hello && mkdir -p hello && >hello/world && - test "$(git ls-files -o)" = hello/world + test "$(git ls-files -o --exclude-standard)" = hello/world ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index f3af10fb7e..0505da78e8 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -2069,3 +2069,11 @@ test_trailing_hash () { test_redact_non_printables () { tr -d "\n\r" | tr "[\001-\040][\177-\377]" "." } + +# Remove .gitconfig entries from a file in place. test-lib.sh may +# create $HOME/.gitconfig (e.g. to set safe.bareRepository) which +# can appear in ls-files or status output. +test_filter_gitconfig () { + sed "/\\.gitconfig/d" "$1" >"$1.filtered" && + mv "$1.filtered" "$1" +} From a09d2b27ff9331d03a629b5ffcf81cb16bbe6544 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 26 Apr 2026 14:38:35 +0000 Subject: [PATCH 7/8] status tests: filter `.gitconfig` from status output Since test-lib.sh creates `$HOME/.gitconfig` when `WITH_BREAKING_CHANGES` is in effect, the file appears in `git status` output as either untracked (`?? .gitconfig`) or ignored (`!! .gitconfig` / `! .gitconfig`, depending on porcelain version), because the `.git/info/exclude` entry causes git to treat it as an ignored file rather than hiding it entirely. In t7061 and t7521, which are pervasively affected, introduce a `filter_gitconfig` helper that strips all status-prefix variants of `.gitconfig` from the output before comparison. In the remaining scripts (t7060, t7064, t7508), apply targeted adjustments. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/t7060-wtstatus.sh | 3 +-- t/t7061-wtstatus-ignore.sh | 27 +++++++++++++++++++++++++++ t/t7064-wtstatus-pv2.sh | 1 + t/t7508-status.sh | 4 ++++ t/t7521-ignored-mode.sh | 1 + 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh index 0f4344c55e..942ddbbf0e 100755 --- a/t/t7060-wtstatus.sh +++ b/t/t7060-wtstatus.sh @@ -9,6 +9,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME test_expect_success setup ' git config --global advice.statusuoption false && + echo "/.gitconfig" >>.git/info/exclude && test_commit A && test_commit B oneside added && git checkout A^0 && @@ -221,7 +222,6 @@ test_expect_success 'status --branch with detached HEAD' ' git status --branch --porcelain >actual && cat >expected <<-EOF && ## HEAD (no branch) - ?? .gitconfig ?? actual ?? expect ?? expected @@ -237,7 +237,6 @@ test_expect_success 'status --porcelain=v1 --branch with detached HEAD' ' git status --branch --porcelain=v1 >actual && cat >expected <<-EOF && ## HEAD (no branch) - ?? .gitconfig ?? actual ?? expect ?? expected diff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh index 2f9bea9793..14ddaba2f3 100755 --- a/t/t7061-wtstatus-ignore.sh +++ b/t/t7061-wtstatus-ignore.sh @@ -18,6 +18,7 @@ test_expect_success 'status untracked directory with --ignored' ' : >untracked/ignored && : >untracked/uncommitted && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -27,6 +28,7 @@ test_expect_success 'same with gitignore starting with BOM' ' : >untracked/ignored && : >untracked/uncommitted && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -40,18 +42,22 @@ test_expect_success 'status untracked files --ignored with pathspec (no match)' test_expect_success 'status untracked files --ignored with pathspec (literal match)' ' git status --porcelain --ignored -- untracked/ignored >actual && echo "!! untracked/ignored" >expected && + test_filter_gitconfig actual && test_cmp expected actual && git status --porcelain --ignored -- untracked/uncommitted >actual && echo "?? untracked/uncommitted" >expected && + test_filter_gitconfig actual && test_cmp expected actual ' test_expect_success 'status untracked files --ignored with pathspec (glob match)' ' git status --porcelain --ignored -- untracked/i\* >actual && echo "!! untracked/ignored" >expected && + test_filter_gitconfig actual && test_cmp expected actual && git status --porcelain --ignored -- untracked/u\* >actual && echo "?? untracked/uncommitted" >expected && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -65,6 +71,7 @@ EOF test_expect_success 'status untracked directory with --ignored -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' cat >expected <<\EOF @@ -76,9 +83,11 @@ test_expect_success 'status of untracked directory with --ignored works with or git status --porcelain --ignored >tmp && grep untracked/ tmp >actual && rm tmp && + test_filter_gitconfig actual && test_cmp expected actual && git status --porcelain --ignored untracked/ >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -89,6 +98,7 @@ EOF test_expect_success 'status prefixed untracked sub-directory with --ignored -u' ' git status --porcelain --ignored -u untracked/ >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -104,6 +114,7 @@ test_expect_success 'status ignored directory with --ignore' ' mkdir ignored && : >ignored/uncommitted && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -116,6 +127,7 @@ EOF test_expect_success 'status ignored directory with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -130,6 +142,7 @@ test_expect_success 'status empty untracked directory with --ignore' ' mkdir untracked-ignored && mkdir untracked-ignored/test && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -141,6 +154,7 @@ EOF test_expect_success 'status empty untracked directory with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -155,6 +169,7 @@ test_expect_success 'status untracked directory with ignored files with --ignore : >untracked-ignored/ignored && : >untracked-ignored/test/ignored && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -168,6 +183,7 @@ EOF test_expect_success 'status untracked directory with ignored files with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -185,6 +201,7 @@ test_expect_success 'status ignored tracked directory with --ignore' ' git commit -m. && echo "tracked" >.gitignore && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -196,6 +213,7 @@ EOF test_expect_success 'status ignored tracked directory with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -208,6 +226,7 @@ EOF test_expect_success 'status ignored tracked directory and ignored file with --ignore' ' echo "committed" >>.gitignore && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -219,6 +238,7 @@ EOF test_expect_success 'status ignored tracked directory and ignored file with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -233,6 +253,7 @@ test_expect_success 'status ignored tracked directory and uncommitted file with echo "tracked" >.gitignore && : >tracked/uncommitted && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -245,6 +266,7 @@ EOF test_expect_success 'status ignored tracked directory and uncommitted file with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -260,6 +282,7 @@ test_expect_success 'status ignored tracked directory with uncommitted file in u mkdir tracked/ignored && : >tracked/ignored/uncommitted && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -272,6 +295,7 @@ EOF test_expect_success 'status ignored tracked directory with uncommitted file in untracked subdir with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -287,6 +311,7 @@ test_expect_success 'status ignored tracked directory with uncommitted file in t git add -f tracked/ignored/committed && git commit -m. && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -299,6 +324,7 @@ EOF test_expect_success 'status ignored tracked directory with uncommitted file in tracked subdir with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -310,6 +336,7 @@ test_expect_success 'status ignores submodule in excluded directory' ' git init tracked/submodule && test_commit -C tracked/submodule initial && git status --porcelain --ignored -u tracked/submodule >actual && + test_filter_gitconfig actual && test_cmp expected actual ' diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh index 8bbc5ce6d9..be6c931a96 100755 --- a/t/t7064-wtstatus-pv2.sh +++ b/t/t7064-wtstatus-pv2.sh @@ -231,6 +231,7 @@ test_expect_success 'ignored files are printed with --ignored' ' EOF git status --porcelain=v2 --ignored --untracked-files=all >actual && + test_filter_gitconfig actual && test_cmp expect actual ' diff --git a/t/t7508-status.sh b/t/t7508-status.sh index a5e21bf8bf..5f76ec62d8 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -263,6 +263,7 @@ test_expect_success 'status with gitignore' ' !! untracked EOF git status -s --ignored >output && + test_filter_gitconfig output && test_cmp expect output && cat >expect <<\EOF && @@ -296,6 +297,7 @@ Ignored files: EOF git status --ignored >output && + test_filter_gitconfig output && test_cmp expect output ' @@ -328,6 +330,7 @@ test_expect_success 'status with gitignore (nothing untracked)' ' !! untracked EOF git status -s --ignored >output && + test_filter_gitconfig output && test_cmp expect output && cat >expect <<\EOF && @@ -358,6 +361,7 @@ Ignored files: EOF git status --ignored >output && + test_filter_gitconfig output && test_cmp expect output ' diff --git a/t/t7521-ignored-mode.sh b/t/t7521-ignored-mode.sh index a88b02b06e..7ea0b0d2f2 100755 --- a/t/t7521-ignored-mode.sh +++ b/t/t7521-ignored-mode.sh @@ -30,6 +30,7 @@ test_expect_success 'Verify behavior of status on directories with ignored files dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign && git status --porcelain=v2 --ignored=matching --untracked-files=all >output && + test_filter_gitconfig output && test_cmp expect output ' From 985b38ca6cdbd04432a7072c790f8ddf9dcc717c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 26 Apr 2026 14:38:36 +0000 Subject: [PATCH 8/8] safe.bareRepository: default to "explicit" with WITH_BREAKING_CHANGES When an attacker can convince a user to clone a crafted repository that contains an embedded bare repository with malicious hooks, any Git command the user runs after entering that subdirectory will discover the bare repository and execute the hooks. The user does not even need to run a Git command explicitly: many shell prompts run `git status` in the background to display branch and dirty state information, and `git status` in turn may invoke the fsmonitor hook if so configured, making the user vulnerable the moment they `cd` into the directory. The `safe.bareRepository` configuration variable (introduced in 8959555cee7e (setup_git_directory(): add an owner check for the top-level directory, 2022-03-02)) already provides protection against this attack vector by allowing users to set it to "explicit", but the default remained "all" for backwards compatibility. Since Git 3.0 is the natural point to change defaults to safer values, flip the default from "all" to "explicit" when built with `WITH_BREAKING_CHANGES`. This means Git will refuse to work with bare repositories that are discovered implicitly by walking up the directory tree. Bare repositories specified via `--git-dir` or `GIT_DIR` continue to work, and directories that look like `.git`, worktrees, or submodule directories are unaffected (the existing `is_implicit_bare_repo()` whitelist handles those cases). Users who rely on implicit bare repository discovery can restore the previous behavior by setting `safe.bareRepository=all` in their global or system configuration. The test for the "safe.bareRepository in the repository" scenario needed a more involved fix: it writes a `safe.bareRepository=all` entry into the bare repository's own config to verify that repo-local config does not override the protected (global) setting. Previously, `test_config -C` was used to write that entry, but its cleanup runs `git -C config --unset`, which itself fails when the default is "explicit" and the global config has already been cleaned up. Switching to direct git config --file access avoids going through repository discovery entirely. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/BreakingChanges.adoc | 24 ++++++++++++++++++++++++ Documentation/config/safe.adoc | 10 ++++++++-- setup.c | 4 ++++ t/t0035-safe-bare-repository.sh | 10 ++++++++-- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc index af59c43f42..73bb939359 100644 --- a/Documentation/BreakingChanges.adoc +++ b/Documentation/BreakingChanges.adoc @@ -216,6 +216,30 @@ would be significant, we may decide to defer this change to a subsequent minor release. This evaluation will also take into account our own experience with how painful it is to keep Rust an optional component. +* The default value of `safe.bareRepository` will change from `all` to + `explicit`. It is all too easy for an attacker to trick a user into cloning a + repository that contains an embedded bare repository with malicious hooks + configured. If the user enters that subdirectory and runs any Git command, Git + discovers the bare repository and the hooks fire. The user does not even need + to run a Git command explicitly: many shell prompts run `git status` in the + background to display branch and dirty state information, and `git status` in + turn may invoke the fsmonitor hook if so configured, making the user + vulnerable the moment they `cd` into the directory. The `safe.bareRepository` + configuration variable was introduced in 8959555cee (setup_git_directory(): + add an owner check for the top-level directory, 2022-03-02) with a default of + `all` to preserve backwards compatibility. ++ +Changing the default to `explicit` means that Git will refuse to work with bare +repositories that are discovered implicitly by walking up the directory tree. +Bare repositories specified explicitly via the `--git-dir` command-line option +or the `GIT_DIR` environment variable continue to work regardless of this +setting. Repositories that look like a `.git` directory, a worktree, or a +submodule directory are also unaffected. ++ +Users who rely on implicit discovery of bare repositories can restore the +previous behavior by setting `safe.bareRepository=all` in their global or +system configuration. + === Removals * Support for grafting commits has long been superseded by git-replace(1). diff --git a/Documentation/config/safe.adoc b/Documentation/config/safe.adoc index 2d45c98b12..5b1690aebe 100644 --- a/Documentation/config/safe.adoc +++ b/Documentation/config/safe.adoc @@ -2,10 +2,12 @@ safe.bareRepository:: Specifies which bare repositories Git will work with. The currently supported values are: + -* `all`: Git works with all bare repositories. This is the default. +* `all`: Git works with all bare repositories. This is the default in + Git 2.x. * `explicit`: Git only works with bare repositories specified via the top-level `--git-dir` command-line option, or the `GIT_DIR` - environment variable (see linkgit:git[1]). + environment variable (see linkgit:git[1]). This will be the default + in Git 3.0. + If you do not use bare repositories in your workflow, then it may be beneficial to set `safe.bareRepository` to `explicit` in your global @@ -13,6 +15,10 @@ config. This will protect you from attacks that involve cloning a repository that contains a bare repository and running a Git command within that directory. + +If you use bare repositories regularly and want to preserve the current +behavior after upgrading to Git 3.0, set `safe.bareRepository` to `all` +in your global or system config. ++ This config setting is only respected in protected configuration (see <>). This prevents untrusted repositories from tampering with this value. diff --git a/setup.c b/setup.c index 7ec4427368..17c0662076 100644 --- a/setup.c +++ b/setup.c @@ -1485,7 +1485,11 @@ static int allowed_bare_repo_cb(const char *key, const char *value, static enum allowed_bare_repo get_allowed_bare_repo(void) { +#ifdef WITH_BREAKING_CHANGES + enum allowed_bare_repo result = ALLOWED_BARE_REPO_EXPLICIT; +#else enum allowed_bare_repo result = ALLOWED_BARE_REPO_ALL; +#endif git_protected_config(allowed_bare_repo_cb, &result); return result; } diff --git a/t/t0035-safe-bare-repository.sh b/t/t0035-safe-bare-repository.sh index ae7ef092ab..1d3d19f5b4 100755 --- a/t/t0035-safe-bare-repository.sh +++ b/t/t0035-safe-bare-repository.sh @@ -44,11 +44,16 @@ test_expect_success 'setup an embedded bare repo, secondary worktree and submodu test_path_is_dir outer-repo/.git/modules/subn ' -test_expect_success 'safe.bareRepository unset' ' +test_expect_success !WITH_BREAKING_CHANGES 'safe.bareRepository unset' ' test_unconfig --global safe.bareRepository && expect_accepted_implicit -C outer-repo/bare-repo ' +test_expect_success WITH_BREAKING_CHANGES 'safe.bareRepository unset (defaults to explicit)' ' + test_unconfig --global safe.bareRepository && + expect_rejected -C outer-repo/bare-repo +' + test_expect_success 'safe.bareRepository=all' ' test_config_global safe.bareRepository all && expect_accepted_implicit -C outer-repo/bare-repo @@ -63,7 +68,8 @@ test_expect_success 'safe.bareRepository in the repository' ' # safe.bareRepository must not be "explicit", otherwise # git config fails with "fatal: not in a git directory" (like # safe.directory) - test_config -C outer-repo/bare-repo safe.bareRepository all && + test_when_finished "git config --file outer-repo/bare-repo/config --unset safe.bareRepository" && + git config --file outer-repo/bare-repo/config safe.bareRepository all && test_config_global safe.bareRepository explicit && expect_rejected -C outer-repo/bare-repo '