From d3378b37a2f494b99bf9a7aad3eb502737349b59 Mon Sep 17 00:00:00 2001 From: chexit Date: Tue, 3 Nov 2026 00:18:41 +0300 Subject: [PATCH] fix: synchronize the engine submodule URL before updating Reproduce stale upstream configuration with real local Git repositories, including the already-matching pin fast path. Co-authored-by: Codex --- tools/airdef.py | 13 +++++--- tools/test_airdef_build.py | 63 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 tools/test_airdef_build.py diff --git a/tools/airdef.py b/tools/airdef.py index 57fcd12..d1962bc 100644 --- a/tools/airdef.py +++ b/tools/airdef.py @@ -73,13 +73,12 @@ def engine_identity() -> str: def fork(): """Initialize the exact gitlink; never replace a modified developer checkout.""" expected = engine_commit() + matched = False if (FORK / ".git").exists(): current = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=FORK, text=True).strip() - if current == expected: - print(f"horizon/: закреплённый коммит {expected}") - return + matched = current == expected dirty = subprocess.check_output(["git", "status", "--porcelain"], cwd=FORK, text=True).strip() - if dirty: + if not matched and dirty: sys.exit("horizon/: есть изменения при другом закреплённом SHA; сохраните их перед обновлением сабмодуля") if (FORK / ".git").is_dir(): # Migrate the old standalone clone without deleting it or any local branches. @@ -88,6 +87,12 @@ def fork(): run(["git", "remote", "set-url", "origin", url], cwd=FORK) elif FORK.exists() and any(FORK.iterdir()): sys.exit("horizon/: непустой каталог без Git; сохраните его перед инициализацией сабмодуля") + # Existing clones cache the previous upstream URL in local Git configuration. + # Synchronize even when HEAD already matches: the next pin may exist only in our fork. + run(["git", "submodule", "sync", "--recursive", "--", "horizon"], cwd=ROOT) + if matched: + print(f"horizon/: закреплённый коммит {expected}") + return run(["git", "submodule", "update", "--init", "--recursive", "--", "horizon"], cwd=ROOT) current = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=FORK, text=True).strip() if current != expected: diff --git a/tools/test_airdef_build.py b/tools/test_airdef_build.py new file mode 100644 index 0000000..b281dbd --- /dev/null +++ b/tools/test_airdef_build.py @@ -0,0 +1,63 @@ +"""Real Git migration regression; no network, user settings or player data.""" +import importlib.util +import os +from pathlib import Path +import subprocess +import tempfile +import unittest +from unittest.mock import patch + +SPEC = importlib.util.spec_from_file_location("airdef_build", Path(__file__).with_name("airdef.py")) +airdef = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(airdef) + + +def git(at, *args): + return subprocess.check_output(["git", "-C", str(at), *args], text=True, stderr=subprocess.STDOUT).strip() + + +def init(at): + at.mkdir() + git(at, "init", "-q") + git(at, "config", "user.name", "Test") + git(at, "config", "user.email", "test@example.invalid") + + +def commit(at, text): + (at / "engine.txt").write_text(text) + git(at, "add", "engine.txt") + git(at, "commit", "-qm", text) + return git(at, "rev-parse", "HEAD") + + +class ForkMigrationTests(unittest.TestCase): + def test_stale_local_url_is_synced_before_fetching_new_pin(self): + with tempfile.TemporaryDirectory(prefix="airdef-fork-test-") as tmp, patch.dict(os.environ, {"GIT_ALLOW_PROTOCOL": "file"}): + root = Path(tmp) + source, parent = root / "source", root / "airdef" + init(source) + old = commit(source, "old") + canonical = root / "canonical.git" + git(root, "clone", "--bare", str(source), str(canonical)) + init(parent) + git(parent, "submodule", "add", str(canonical), "horizon") + child = parent / "horizon" + new = commit(source, "new-fork-only-pin") + git(source, "push", str(canonical), "HEAD") + git(parent, "update-index", "--cacheinfo", f"160000,{new},horizon") + stale = str(root / "missing-upstream.git") + git(parent, "config", "submodule.horizon.url", stale) + git(child, "remote", "set-url", "origin", stale) + self.assertEqual(git(child, "rev-parse", "HEAD"), old) + with patch.object(airdef, "ROOT", parent), patch.object(airdef, "FORK", child): + airdef.fork() + self.assertEqual(git(child, "rev-parse", "HEAD"), new) + self.assertEqual(git(child, "remote", "get-url", "origin"), str(canonical)) + # The fast path must also repair stale configuration for the next update. + git(child, "remote", "set-url", "origin", stale) + airdef.fork() + self.assertEqual(git(child, "remote", "get-url", "origin"), str(canonical)) + + +if __name__ == "__main__": + unittest.main()