fix(conventions): report 3rdparty submodules without a .gitmodules entry (#4304)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Paritosh Lahre 2026-10-12 13:01:24 +05:30 committed by GitHub
parent f961c52ad8
commit 436e310ddb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 39 additions and 3 deletions

View File

@ -143,7 +143,18 @@ class Check:
if match and not match.group(1).startswith("3rdparty/"):
self.report("system-dependency", ".gitmodules", number, match.group(1))
def submodules(self):
result = subprocess.run(["git", "config", "--no-includes", "-z", "--blob", f"{self.head}:.gitmodules", "--list"],
capture_output=True, encoding="utf-8", errors="replace")
values = {key: value for key, _, value in (entry.partition("\n") for entry in result.stdout.split("\0") if entry)}
return {path for key, path in values.items() if key.endswith(".path") and values.get(f"{key[:-5]}.url")}
def repository(self):
gitlinks = {path for path, mode in self.modes.items() if mode == "160000" and path.startswith("3rdparty/")}
checked = gitlinks if ".gitmodules" in self.status else gitlinks & self.status.keys()
if checked:
for path in sorted(checked - self.submodules()):
self.report("system-dependency", path, 0, "submodule without a url in .gitmodules")
for path, (added, deleted) in self.numstat.items():
if path.startswith("3rdparty/"):
continue

View File

@ -295,6 +295,14 @@ class RepositoryChecksTests(unittest.TestCase):
self.git("add", ".")
self.git("commit", "-qm", "test: add convention fixture")
def add_thirdparty_submodule(self, gitmodules=None):
head = self.git("rev-parse", "HEAD")
if gitmodules is not None:
(self.root / ".gitmodules").write_text(gitmodules, encoding="utf-8")
self.git("add", ".gitmodules")
self.git("update-index", "--add", "--cacheinfo", "160000", head, "3rdparty/vendor")
self.git("commit", "-qm", "build: add submodule")
def test_quoted_utf8_paths_are_checked(self):
name = "docs/dev/測試guide.md" if os.name == "nt" else "docs/dev/測試\tguide.md"
path = self.root / name
@ -337,13 +345,30 @@ class RepositoryChecksTests(unittest.TestCase):
self.assertIn(("system-dependency", "vendor", 0, "submodule outside 3rdparty/"), check.findings)
def test_thirdparty_submodule_remains_allowed(self):
head = self.git("rev-parse", "HEAD")
self.git("update-index", "--add", "--cacheinfo", "160000", head, "3rdparty/vendor")
self.git("commit", "-qm", "build: add allowed submodule")
self.add_thirdparty_submodule('[submodule "vendor"]\n\tpath = 3rdparty/vendor\n'
"\turl = https://example.com/vendor.git\n")
check = Check("HEAD~1", "HEAD")
check.repository()
self.assertEqual(check.findings, [])
def test_thirdparty_submodule_without_gitmodules_entry_is_reported(self):
self.add_thirdparty_submodule()
check = Check("HEAD~1", "HEAD")
check.repository()
self.assertEqual(check.findings,
[("system-dependency", "3rdparty/vendor", 0, "submodule without a url in .gitmodules")])
def test_dropping_a_submodule_url_is_reported(self):
self.add_thirdparty_submodule('[submodule "vendor"]\n\tpath = 3rdparty/vendor\n'
"\turl = https://example.com/vendor.git\n")
(self.root / ".gitmodules").write_text('[submodule "vendor"]\n\tpath = 3rdparty/vendor\n', encoding="utf-8")
self.git("add", ".gitmodules")
self.git("commit", "-qm", "build: drop submodule url")
check = Check("HEAD~1", "HEAD")
check.repository()
self.assertEqual(check.findings,
[("system-dependency", "3rdparty/vendor", 0, "submodule without a url in .gitmodules")])
def test_relative_link_url_decoding(self):
self.assertEqual(links("docs/dev/index.md", "[guide](./space%20name.md#details)"),
[("./space%20name.md", "docs/dev/space name.md")])