feat: scope uv.lock changes by dependency graph#12
Merged
Conversation
Add an opt-in --lock-scope flag (lock-scope action input). Instead of treating any uv.lock change as a blanket test_all trigger, diff the lock against its base revision, reverse-walk each changed dependency to the workspace members that use it, and fall back to test_all only for manifest or tooling changes. Default behavior is unchanged when the flag is off.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #12 +/- ##
==========================================
- Coverage 98.16% 97.12% -1.05%
==========================================
Files 6 7 +1
Lines 491 626 +135
==========================================
+ Hits 482 608 +126
- Misses 9 18 +9 ☔ View full report in Codecov by Harness. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
--lock-scopeCLI flag (andlock-scopeaction input). Without it, auv.lockchange is a blankettest_alltrigger exactly as before, so behavior is unchanged for existing users.uv.lockstops being a blanket trigger. difftrace diffs the lock against its base revision, reverse-walks each changed dependency to the workspace members that transitively use it, and unions those into the affected set. In a large monorepo most dependency bumps reach only a handful of packages, so the common bump stops fanning out to the whole matrix.[manifest]change (members list or dependency-groups), a top-levelrequires-python/resolution-markerschange, or a changed dependency that no member reaches. That last case is the tooling that lives only in the manifest dependency-groups (pytest, coverage, dvc, …) and runs in every package's build, so scoping it would under-build.Design
parse_full_lockis the full-package-graph mode — it skips the members-only filter so every locked package is a node, third-party packages included, and records a per-package fingerprint (version, source, artifact hashes) for the diff.read_lock_at_refreads the base lock viagit show <base>:<path>, returningNonewhen the lock didn't exist at that ref so the caller falls back.[[package]]level (added / removed / fingerprint-changed), applies the manifest and top-level fall-back, and reverse-walks the changed packages to members over the union of the base and current graphs. "Reaches no member" is what distinguishes manifest-only tooling from a real member dependency.--lock-scope, dropsuv.lockfrom the trigger set and runs the per-workspace lock diff, unioning the result into the file-based affected set. Multi-lock stays workspace-scoped; a single-lock fall-back sets globaltest_all, mirroring today's root-trigger semantics. It respects--no-dev/--no-optional, and--direct-onlyomits lock-derived members since they are transitive.Two approximations, both on the safe side
Test plan
tests/test_lockscope.py(17 tests): missing / unparseable base, member-list / dependency-group / requires-python / resolution-marker fall-backs, resolution-marker reorder is not a change, tooling fall-back, scoped single / shared / added / removed dependency changes, hash-only change, member-fingerprint change, and a--no-devedge.parse_full_lockunit tests: third-party nodes present, edges unfiltered by membership, reverse edges reach third-party, fingerprint tracks version and wheel hash,--no-devexcludes dev edges.read_lock_at_reftests: returns content, missing object →None, nested path, timeout raises.run()integration under--lock-scope: scoped bump, shared-dep fan-out, tooling fall-back, manifest fall-back, no-op lock change rebuilds nothing, file + lock union, missing-base fall-back,--direct-only, and a regression guard thatuv.lockstill triggerstest_allwithout the flag.ruff check,ruff format --check, andpyrightclean.test_all.