fix(tools): follow Depends on chains in pr_overlap
pr_overlap only skipped a pair when one pull request named the other directly. In a stack where #3 depends on #2 and #2 on #1, #1 and #3 were reported as overlapping, and two pull requests built on the same #1 were reported for the files and exports both inherit from it. Dependencies are now followed through open pull requests, and what a shared dependency adds is left out of the same-file and same-export columns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0189deyapHNNqKQJboZBJQei
This commit is contained in:
parent
2ccbd376f8
commit
fd0d55ce21
|
|
@ -115,14 +115,26 @@ def conflicts(a, b):
|
|||
return {path: [] if path in a["added"] else clashes(a, b, path) for path in files}
|
||||
|
||||
|
||||
def dependencies(prs, number):
|
||||
found, pending = set(), [number]
|
||||
while pending:
|
||||
for n in prs[pending.pop()]["depends"]:
|
||||
if n in prs and n not in found:
|
||||
found.add(n)
|
||||
pending.append(n)
|
||||
return found
|
||||
|
||||
|
||||
def overlaps(prs, a, b):
|
||||
pa, pb = prs[a], prs[b]
|
||||
if b in pa["depends"] or a in pb["depends"]:
|
||||
da, db = dependencies(prs, a), dependencies(prs, b)
|
||||
if b in da or a in db:
|
||||
return None
|
||||
found = {"added": pa["added"] & pb["added"]}
|
||||
shared = [prs[n] for n in da & db]
|
||||
found = {"added": (pa["added"] & pb["added"]).difference(*(pr["added"] for pr in shared))}
|
||||
if pa["author"] != pb["author"]:
|
||||
found["conflicts"] = conflicts(pa, pb)
|
||||
found["exports"] = pa["exports"] & pb["exports"]
|
||||
found["exports"] = (pa["exports"] & pb["exports"]).difference(*(pr["exports"] for pr in shared))
|
||||
return found if any(found.values()) else None
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,23 @@ def pull_requests(body):
|
|||
return pr_overlap.open_prs("main")
|
||||
|
||||
|
||||
STACK = {
|
||||
1: (set(), {"one.cpp"}, {"sceFooInit"}),
|
||||
2: ({1, 999}, {"one.cpp", "two.cpp"}, {"sceFooInit"}),
|
||||
3: ({2}, {"one.cpp", "two.cpp", "three.cpp"}, {"sceFooInit"}),
|
||||
4: ({1}, {"one.cpp", "four.cpp"}, {"sceFooInit", "sceFooTerm"}),
|
||||
5: ({1}, {"one.cpp", "four.cpp"}, {"sceFooInit", "sceFooTerm"}),
|
||||
6: (set(), {"one.cpp"}, {"sceFooInit"}),
|
||||
7: ({8}, {"one.cpp", "seven.cpp"}, {"sceFooInit"}),
|
||||
8: ({7, 1}, {"one.cpp", "seven.cpp"}, {"sceFooInit"}),
|
||||
}
|
||||
|
||||
|
||||
def stacked_pull_requests():
|
||||
return {number: {"author": f"author{number}", "depends": depends, "added": added, "exports": exports, "tree": None}
|
||||
for number, (depends, added, exports) in STACK.items()}
|
||||
|
||||
|
||||
class DependencyTests(unittest.TestCase):
|
||||
def test_documented_form_without_colon(self):
|
||||
for body in ("Depends on #2.", "Depends on #2, #3.", "Depends on\t#2 and #3.",
|
||||
|
|
@ -95,5 +112,30 @@ class DependencyTests(unittest.TestCase):
|
|||
self.assertEqual(pr_overlap.overlaps(prs, 1, 2), {"added": {"shared.cpp"}})
|
||||
|
||||
|
||||
class StackedDependencyTests(unittest.TestCase):
|
||||
def test_transitive_dependency_suppresses_overlap(self):
|
||||
prs = stacked_pull_requests()
|
||||
self.assertIsNone(pr_overlap.overlaps(prs, 1, 3))
|
||||
self.assertIsNone(pr_overlap.overlaps(prs, 3, 1))
|
||||
|
||||
def test_changes_inherited_from_a_shared_dependency_are_not_overlap(self):
|
||||
prs = stacked_pull_requests()
|
||||
self.assertIsNone(pr_overlap.overlaps(prs, 3, 4))
|
||||
self.assertIsNone(pr_overlap.overlaps(prs, 2, 4))
|
||||
|
||||
def test_overlap_beyond_a_shared_dependency_is_kept(self):
|
||||
self.assertEqual(pr_overlap.overlaps(stacked_pull_requests(), 4, 5),
|
||||
{"added": {"four.cpp"}, "conflicts": {}, "exports": {"sceFooTerm"}})
|
||||
|
||||
def test_independent_pull_request_keeps_inherited_overlap(self):
|
||||
self.assertEqual(pr_overlap.overlaps(stacked_pull_requests(), 3, 6),
|
||||
{"added": {"one.cpp"}, "conflicts": {}, "exports": {"sceFooInit"}})
|
||||
|
||||
def test_dependency_cycle_terminates(self):
|
||||
prs = stacked_pull_requests()
|
||||
self.assertIsNone(pr_overlap.overlaps(prs, 7, 8))
|
||||
self.assertIsNone(pr_overlap.overlaps(prs, 8, 2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Reference in New Issue