Skip to content

Commit 5c0c447

Browse files
committed
git: Exit clearly when in a "dubious" directory
I hit a _very_ hard to diagnose issue today after switching part of a CI environment. It turns out that due to non-privileged container magic the CI step was not running as the user that cloned the repo. This manifested as the sdist generated during this step just not having a few extra data files; but the build still passed. So several steps later, suddenly files have just vanished from the sdist, which of course is not something that is checked for and so testing blows up in very strange ways. After I understood what was going on, there is a _tiny_ little message hidden amongst the logs that gives a hint of what's going on. ... writing requirements to src/proj.egg-info/requires.txt writing top-level names to src/proj.egg-info/top_level.txt listing git files failed - pretending there aren't any reading manifest file 'src/proj.egg-info/SOURCES.txt' writing manifest file 'src/proj.egg-info/SOURCES.txt' ... What is actually happening is that if you run git you get "git status fatal: detected dubious ownership in repository at '/..proj'". This is the well-known CVE-2022-24765 issue where trusting a `.git` config dir from another user causes problems. In 6a3bb96 all the calls in setuptools_scm/git.py were updated to use `--git-dir` directly -- git will not complain if you have told it to explicitly trust the config dir like this. However, there are calls in _file_finders/git.py to find the top-level that are not using this. It silently (modulo an easily missed log) skips adding files when this occurs. I can not see that this would ever be the behaviour you would want. If it had of exploded telling me the git call failed, it would have short-cut all of the problem finding. This adds an explicit match on the static part of this git failure message and raises a SystemExit if it hits. A test case that mocks such a situation is added. I have increased the "listing git files failed" to a warning; similar to the "shallow clone" warning. It seems like this is actually quite important. I'm sure someone relies on it for some reason, but I can't quite see why you'd want to ignore that files are not being added to your sdist. This necessitates ignoring this warning in several places, which I think is an improvement, as at least we have a clearer overview of where addition is being ignored if we want to examine this behaviour more closely. Closes: #784 Signed-off-by: Ian Wienand <iwienand@redhat.com>
1 parent e56b78f commit 5c0c447

File tree

5 files changed

+74
-5
lines changed

5 files changed

+74
-5
lines changed

src/setuptools_scm/_file_finders/git.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import subprocess
66
import tarfile
7+
import warnings
78

89
from typing import IO
910

@@ -22,8 +23,18 @@ def _git_toplevel(path: str) -> str | None:
2223
cwd = os.path.abspath(path or ".")
2324
res = _run(["git", "rev-parse", "HEAD"], cwd=cwd)
2425
if res.returncode:
26+
# This catches you being in a git directory, but the
27+
# permissions being incorrect. With modern contanizered
28+
# CI environments you can easily end up in a cloned repo
29+
# with incorrect permissions and we don't want to silently
30+
# ignore files.
31+
if "--add safe.directory" in res.stderr:
32+
log.error(res.stderr)
33+
raise SystemExit(
34+
"git introspection failed: {}".format(res.stderr.split("\n")[0])
35+
)
2536
# BAIL if there is no commit
26-
log.error("listing git files failed - pretending there aren't any")
37+
warnings.warn("listing git files failed - pretending there aren't any")
2738
return None
2839
res = _run(
2940
["git", "rev-parse", "--show-prefix"],

testing/test_file_finder.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,21 @@ def _sep(paths: Iterable[str]) -> set[str]:
5050
return {path.replace("/", os.path.sep) for path in paths}
5151

5252

53+
@pytest.mark.filterwarnings("ignore:listing git files failed")
5354
def test_basic(inwd: WorkDir) -> None:
5455
assert set(find_files()) == _sep({"file1", "adir/filea", "bdir/fileb"})
5556
assert set(find_files(".")) == _sep({"./file1", "./adir/filea", "./bdir/fileb"})
5657
assert set(find_files("adir")) == _sep({"adir/filea"})
5758

5859

60+
@pytest.mark.filterwarnings("ignore:listing git files failed")
5961
def test_whitespace(inwd: WorkDir) -> None:
6062
(inwd.cwd / "adir" / "space file").touch()
6163
inwd.add_and_commit()
6264
assert set(find_files("adir")) == _sep({"adir/space file", "adir/filea"})
6365

6466

67+
@pytest.mark.filterwarnings("ignore:listing git files failed")
6568
def test_case(inwd: WorkDir) -> None:
6669
(inwd.cwd / "CamelFile").touch()
6770
(inwd.cwd / "file2").touch()
@@ -71,6 +74,7 @@ def test_case(inwd: WorkDir) -> None:
7174
)
7275

7376

77+
@pytest.mark.filterwarnings("ignore:listing git files failed")
7478
@pytest.mark.skipif(
7579
os.path.normcase("B") != os.path.normcase("b"), reason="case sensitive filesystem"
7680
)
@@ -84,19 +88,22 @@ def test_case_cwd_evil(inwd: WorkDir, monkeypatch: pytest.MonkeyPatch) -> None:
8488
)
8589

8690

91+
@pytest.mark.filterwarnings("ignore:listing git files failed")
8792
@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
8893
def test_symlink_dir(inwd: WorkDir) -> None:
8994
(inwd.cwd / "adir" / "bdirlink").symlink_to("../bdir")
9095
inwd.add_and_commit()
9196
assert set(find_files("adir")) == _sep({"adir/filea", "adir/bdirlink/fileb"})
9297

9398

99+
@pytest.mark.filterwarnings("ignore:listing git files failed")
94100
@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
95101
def test_symlink_dir_source_not_in_scm(inwd: WorkDir) -> None:
96102
(inwd.cwd / "adir" / "bdirlink").symlink_to("../bdir")
97103
assert set(find_files("adir")) == _sep({"adir/filea"})
98104

99105

106+
@pytest.mark.filterwarnings("ignore:listing git files failed")
100107
@pytest.mark.skipif(
101108
sys.platform == "win32", reason="symlinks to files not supported on windows"
102109
)
@@ -108,6 +115,7 @@ def test_symlink_file(inwd: WorkDir) -> None:
108115
) # -> ../file1
109116

110117

118+
@pytest.mark.filterwarnings("ignore:listing git files failed")
111119
@pytest.mark.skipif(
112120
sys.platform == "win32", reason="symlinks to files not supported on windows"
113121
)
@@ -116,13 +124,15 @@ def test_symlink_file_source_not_in_scm(inwd: WorkDir) -> None:
116124
assert set(find_files("adir")) == _sep({"adir/filea"})
117125

118126

127+
@pytest.mark.filterwarnings("ignore:listing git files failed")
119128
@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
120129
def test_symlink_loop(inwd: WorkDir) -> None:
121130
(inwd.cwd / "adir" / "loop").symlink_to("../adir")
122131
inwd.add_and_commit()
123132
assert set(find_files("adir")) == _sep({"adir/filea", "adir/loop"}) # -> ../adir
124133

125134

135+
@pytest.mark.filterwarnings("ignore:listing git files failed")
126136
@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
127137
def test_symlink_loop_outside_path(inwd: WorkDir) -> None:
128138
(inwd.cwd / "bdir" / "loop").symlink_to("../bdir")
@@ -131,13 +141,15 @@ def test_symlink_loop_outside_path(inwd: WorkDir) -> None:
131141
assert set(find_files("adir")) == _sep({"adir/filea", "adir/bdirlink/fileb"})
132142

133143

144+
@pytest.mark.filterwarnings("ignore:listing git files failed")
134145
@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
135146
def test_symlink_dir_out_of_git(inwd: WorkDir) -> None:
136147
(inwd.cwd / "adir" / "outsidedirlink").symlink_to(os.path.join(__file__, ".."))
137148
inwd.add_and_commit()
138149
assert set(find_files("adir")) == _sep({"adir/filea"})
139150

140151

152+
@pytest.mark.filterwarnings("ignore:listing git files failed")
141153
@pytest.mark.skipif(
142154
sys.platform == "win32", reason="symlinks to files not supported on windows"
143155
)
@@ -147,6 +159,7 @@ def test_symlink_file_out_of_git(inwd: WorkDir) -> None:
147159
assert set(find_files("adir")) == _sep({"adir/filea"})
148160

149161

162+
@pytest.mark.filterwarnings("ignore:listing git files failed")
150163
@pytest.mark.parametrize("path_add", ["{cwd}", "{cwd}" + os.pathsep + "broken"])
151164
def test_ignore_root(
152165
inwd: WorkDir, monkeypatch: pytest.MonkeyPatch, path_add: str
@@ -155,6 +168,7 @@ def test_ignore_root(
155168
assert find_files() == []
156169

157170

171+
@pytest.mark.filterwarnings("ignore:listing git files failed")
158172
def test_empty_root(inwd: WorkDir) -> None:
159173
subdir = inwd.cwd / "cdir" / "subdir"
160174
subdir.mkdir(parents=True)
@@ -163,6 +177,7 @@ def test_empty_root(inwd: WorkDir) -> None:
163177
assert set(find_files("cdir")) == _sep({"cdir/subdir/filec"})
164178

165179

180+
@pytest.mark.filterwarnings("ignore:listing git files failed")
166181
def test_empty_subdir(inwd: WorkDir) -> None:
167182
subdir = inwd.cwd / "adir" / "emptysubdir" / "subdir"
168183
subdir.mkdir(parents=True)
@@ -173,6 +188,7 @@ def test_empty_subdir(inwd: WorkDir) -> None:
173188
)
174189

175190

191+
@pytest.mark.filterwarnings("ignore:listing git files failed")
176192
@pytest.mark.skipif(sys.platform == "win32", reason="symlinks not supported on windows")
177193
def test_double_include_through_symlink(inwd: WorkDir) -> None:
178194
(inwd.cwd / "data").mkdir()
@@ -192,6 +208,7 @@ def test_double_include_through_symlink(inwd: WorkDir) -> None:
192208
)
193209

194210

211+
@pytest.mark.filterwarnings("ignore:listing git files failed")
195212
@pytest.mark.skipif(sys.platform == "win32", reason="symlinks not supported on windows")
196213
def test_symlink_not_in_scm_while_target_is(inwd: WorkDir) -> None:
197214
(inwd.cwd / "data").mkdir()
@@ -211,12 +228,14 @@ def test_symlink_not_in_scm_while_target_is(inwd: WorkDir) -> None:
211228
)
212229

213230

231+
@pytest.mark.filterwarnings("ignore:listing git files failed")
214232
@pytest.mark.issue(587)
215233
@pytest.mark.skip_commit
216234
def test_not_commited(inwd: WorkDir) -> None:
217235
assert find_files() == []
218236

219237

238+
@pytest.mark.filterwarnings("ignore:listing git files failed")
220239
def test_unexpanded_git_archival(wd: WorkDir, monkeypatch: pytest.MonkeyPatch) -> None:
221240
# When substitutions in `.git_archival.txt` are not expanded, files should
222241
# not be automatically listed.
@@ -226,6 +245,7 @@ def test_unexpanded_git_archival(wd: WorkDir, monkeypatch: pytest.MonkeyPatch) -
226245
assert find_files() == []
227246

228247

248+
@pytest.mark.filterwarnings("ignore:listing git files failed")
229249
@pytest.mark.parametrize("archive_file", [".git_archival.txt", ".hg_archival.txt"])
230250
def test_archive(
231251
wd: WorkDir, monkeypatch: pytest.MonkeyPatch, archive_file: str

testing/test_git.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import shutil
66
import subprocess
77
import sys
8+
import textwrap
89

910
from datetime import date
1011
from datetime import datetime
@@ -19,6 +20,7 @@
1920
import pytest
2021

2122
import setuptools_scm._file_finders
23+
import setuptools_scm._file_finders.git
2224

2325
from setuptools_scm import Configuration
2426
from setuptools_scm import NonNormalizedVersion
@@ -117,11 +119,10 @@ def test_git_gone(wd: WorkDir, monkeypatch: pytest.MonkeyPatch) -> None:
117119
@pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/298")
118120
@pytest.mark.issue(403)
119121
def test_file_finder_no_history(wd: WorkDir, caplog: pytest.LogCaptureFixture) -> None:
120-
file_list = git_find_files(str(wd.cwd))
122+
with pytest.warns(UserWarning, match=r"pretending there aren't any"):
123+
file_list = git_find_files(str(wd.cwd))
121124
assert file_list == []
122125

123-
assert "listing git files failed - pretending there aren't any" in caplog.text
124-
125126

126127
@pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/281")
127128
def test_parse_call_order(wd: WorkDir) -> None:
@@ -160,7 +161,8 @@ def break_folder_permissions(path: Path) -> Generator[None, None, None]:
160161
sudo_devnull(["chgrp", "-R", str(original_stat.st_gid), path], check=True)
161162

162163

163-
@pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/707")
164+
@pytest.mark.filterwarnings("ignore:listing git files failed")
165+
@pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/784")
164166
def test_not_owner(wd: WorkDir) -> None:
165167
with break_folder_permissions(wd.cwd):
166168
assert git.parse(str(wd.cwd), Configuration())
@@ -861,3 +863,37 @@ def test_git_no_commits_uses_fallback_version(wd: WorkDir) -> None:
861863
assert str(version_no_fallback.tag) == "0.0"
862864
assert version_no_fallback.distance == 0
863865
assert version_no_fallback.dirty is True
866+
867+
868+
@pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/707")
869+
def test_dubious_dir(
870+
wd: WorkDir, caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch
871+
) -> None:
872+
"""Test that we exit clearly if we are in a unsafe directory"""
873+
wd.commit_testfile()
874+
git_wd = git.GitWorkdir(wd.cwd)
875+
876+
# This fakes "git rev-parse HEAD" as if it were in a directory not
877+
# owned by you.
878+
stderr = textwrap.dedent(f"""
879+
fatal: detected dubious ownership in repository at '{git_wd}'
880+
To add an exception for this directory, call:
881+
882+
git config --global --add safe.directory /this/is/a/fake/path
883+
""")
884+
orig_run = run
885+
886+
def _run(*args, **kwargs) -> CompletedProcess: # type: ignore[no-untyped-def]
887+
if args[0] == ["git", "rev-parse", "HEAD"]:
888+
return CompletedProcess(
889+
args=[], stdout="%cI", stderr=stderr, returncode=128
890+
)
891+
return orig_run(*args, **kwargs)
892+
893+
with pytest.raises(SystemExit):
894+
with patch.object(setuptools_scm._file_finders.git, "_run", _run):
895+
git_find_files(str(wd.cwd))
896+
897+
assert "fatal: detected dubious ownership in repository" in " ".join(
898+
caplog.messages
899+
)

testing/test_mercurial.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ def test_hg_command_from_env_is_invalid(
9797
assert wd.get_version(fallback_version="1.0") == "1.0"
9898

9999

100+
@pytest.mark.filterwarnings("ignore:listing git files failed")
100101
def test_find_files_stop_at_root_hg(
101102
wd: WorkDir, monkeypatch: pytest.MonkeyPatch
102103
) -> None:

testing/test_regressions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ def test_case_mismatch_nested_dir_windows_git(tmp_path: Path) -> None:
148148
raise
149149

150150

151+
@pytest.mark.filterwarnings("ignore:listing git files failed")
151152
def test_case_mismatch_force_assertion_failure(tmp_path: Path) -> None:
152153
"""Force the assertion failure by directly calling _git_toplevel with mismatched paths"""
153154
from setuptools_scm._file_finders.git import _git_toplevel

0 commit comments

Comments
 (0)