Skip to content

Commit a8c1a7e

Browse files
committed
fix(ci): ignore graft files during source ancestry verification
1 parent ff2426a commit a8c1a7e

3 files changed

Lines changed: 18 additions & 5 deletions

File tree

‎docs/upstream-sync/ci-policy.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ documents, and tags.
4242
`scripts/ci/check-sync-ancestry.py --checkpoint SHA --target SHA --merge SHA`
4343
requires full commit IDs, resolves each exact object, proves checkpoint is an
4444
ancestor of the pinned upstream target, and proves the checkpoint is an exact non-first parent of the integration
45-
merge. Early batches do not need to contain the final campaign target. Git replacement refs are disabled
45+
merge. Early batches do not need to contain the final campaign target. Git replacement refs and graft files are disabled
4646
for these graph checks, so a synthetic replacement cannot manufacture the
4747
ancestry claim. The helper is local/manual and does not run hosted Actions.
4848
Jenkins runs its self-test, which constructs true and replacement-ref-faked
@@ -58,7 +58,7 @@ python3 scripts/ci/check-github-actions-policy.py
5858
python3 scripts/ci/test_github_actions_policy.py
5959
Ran 4 tests; OK (real workflows plus positive/negative fixtures)
6060
python3 scripts/ci/test_sync_ancestry.py
61-
Ran 5 tests; OK (early checkpoint, exact parent, replacement-ref spoof, invalid inputs)
61+
Ran 6 tests; OK (early checkpoint, exact parent, replacement/graft spoof, invalid inputs)
6262
git diff --check
6363
passed
6464
```

‎scripts/ci/check-sync-ancestry.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
import sys
88

99

10+
GIT_ENV = os.environ | {"GIT_NO_REPLACE_OBJECTS": "1", "GIT_GRAFT_FILE": os.devnull}
11+
1012
def git(*args: str) -> str:
11-
env = os.environ | {"GIT_NO_REPLACE_OBJECTS": "1"}
12-
return subprocess.check_output(["git", *args], text=True, stderr=subprocess.PIPE, env=env).strip()
13+
return subprocess.check_output(["git", *args], text=True, stderr=subprocess.PIPE, env=GIT_ENV).strip()
1314

1415

1516
def exact_commit(value: str) -> str:
@@ -26,7 +27,7 @@ def verify(checkpoint: str, target: str, merge: str) -> None:
2627
target = exact_commit(target)
2728
merge = exact_commit(merge)
2829
if subprocess.run(["git", "merge-base", "--is-ancestor", checkpoint, target],
29-
env=os.environ | {"GIT_NO_REPLACE_OBJECTS": "1"}, check=False).returncode:
30+
env=GIT_ENV, check=False).returncode:
3031
raise ValueError("checkpoint is not an ancestor of pinned target")
3132
parents = git("rev-list", "--parents", "-n", "1", merge).split()[1:]
3233
if len(parents) < 2:

‎scripts/ci/test_sync_ancestry.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import subprocess
77
import tempfile
88
import unittest
9+
from unittest import mock
910

1011
SCRIPT = Path(__file__).with_name("check-sync-ancestry.py")
1112
SPEC = importlib.util.spec_from_file_location("sync_ancestry", SCRIPT)
@@ -71,6 +72,17 @@ def test_rejects_ancestor_claim_using_replacement_ref(self):
7172
with self.assertRaises(ValueError):
7273
ancestry.verify(self.base, unrelated, self.merge)
7374

75+
def test_rejects_grafted_merge_parent(self):
76+
self.git("commit", "--allow-empty", "-qm", "ordinary commit")
77+
ordinary = self.git("rev-parse", "HEAD")
78+
graft = self.repo / ".git" / "info" / "grafts"
79+
graft.write_text(f"{ordinary} {self.base} {self.target}\n")
80+
with self.assertRaises(ValueError):
81+
ancestry.verify(self.target, self.target, ordinary)
82+
with mock.patch.dict(os.environ, {"GIT_GRAFT_FILE": str(graft)}):
83+
with self.assertRaises(ValueError):
84+
ancestry.verify(self.target, self.target, ordinary)
85+
7486
def test_rejects_non_merge_result_and_abbreviated_sha(self):
7587
with self.assertRaises(ValueError):
7688
ancestry.verify(self.base, self.target, self.target)

0 commit comments

Comments
 (0)