1#!/usr/bin/env python3
2"""
3Diff workspace dependencies between two git revisions.
4
5Compares both Cargo.toml [workspace.dependencies] and Cargo.lock [[package]]
6entries to find which dependencies have changed.
7
8Usage: diff-cargo-deps <old-git-ref>
9
10Outputs one dependency name per line for any deps that were added, removed,
11or modified (version, features, checksum, etc).
12"""
13
14import subprocess
15import sys
16import tomllib
17
18
19def get_file_at_ref(ref: str, path: str) -> str:
20 """Get file contents at a specific git ref."""
21 result = subprocess.run(
22 ["git", "show", f"{ref}:{path}"],
23 capture_output=True,
24 text=True,
25 )
26 return result.stdout if result.returncode == 0 else ""
27
28
29def get_workspace_deps(toml_content: str) -> dict:
30 """Extract [workspace.dependencies] from Cargo.toml content."""
31 if not toml_content:
32 return {}
33 try:
34 data = tomllib.loads(toml_content)
35 return data.get("workspace", {}).get("dependencies", {})
36 except Exception:
37 return {}
38
39
40def get_lock_packages(lock_content: str) -> dict[str, dict]:
41 """Extract [[package]] entries from Cargo.lock as {name: full_entry}."""
42 if not lock_content:
43 return {}
44 try:
45 data = tomllib.loads(lock_content)
46 return {pkg["name"]: pkg for pkg in data.get("package", [])}
47 except Exception:
48 return {}
49
50
51def diff_dicts(old: dict, new: dict) -> set[str]:
52 """Find keys that were added, removed, or whose values changed."""
53 changed = set()
54
55 for name, value in new.items():
56 if name not in old or old[name] != value:
57 changed.add(name)
58
59 for name in old:
60 if name not in new:
61 changed.add(name)
62
63 return changed
64
65
66def main():
67 if len(sys.argv) < 2:
68 print("Usage: diff-cargo-deps <old-git-ref>", file=sys.stderr)
69 sys.exit(1)
70
71 old_ref = sys.argv[1]
72 changed = set()
73
74 # Diff Cargo.toml workspace dependencies
75 old_toml = get_file_at_ref(old_ref, "Cargo.toml")
76 new_toml = open("Cargo.toml").read()
77 old_deps = get_workspace_deps(old_toml)
78 new_deps = get_workspace_deps(new_toml)
79 changed |= diff_dicts(old_deps, new_deps)
80
81 # Diff Cargo.lock packages
82 old_lock = get_file_at_ref(old_ref, "Cargo.lock")
83 new_lock = open("Cargo.lock").read()
84 old_pkgs = get_lock_packages(old_lock)
85 new_pkgs = get_lock_packages(new_lock)
86 changed |= diff_dicts(old_pkgs, new_pkgs)
87
88 for name in sorted(changed):
89 print(name)
90
91
92if __name__ == "__main__":
93 main()