mirror of
https://github.com/astral-sh/uv.git
synced 2025-12-23 09:19:48 +00:00
fix: uv tree orphaned roots and premature deduplication
This commit is contained in:
parent
137edcf239
commit
3d05d0bd71
2 changed files with 88 additions and 44 deletions
|
|
@ -379,40 +379,8 @@ impl<'env> TreeDisplay<'env> {
|
|||
|
||||
roots
|
||||
} else {
|
||||
let mut edges = vec![];
|
||||
|
||||
// Remove any cycles.
|
||||
let feedback_set: Vec<EdgeIndex> = petgraph::algo::greedy_feedback_arc_set(&graph)
|
||||
.map(|e| e.id())
|
||||
.collect();
|
||||
for edge_id in feedback_set {
|
||||
if let Some((source, target)) = graph.edge_endpoints(edge_id) {
|
||||
if let Some(weight) = graph.remove_edge(edge_id) {
|
||||
edges.push((source, target, weight));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the root nodes: nodes with no incoming edges, or only an edge from the proxy.
|
||||
let mut roots = graph
|
||||
.node_indices()
|
||||
.filter(|index| {
|
||||
graph
|
||||
.edges_directed(*index, Direction::Incoming)
|
||||
.next()
|
||||
.is_none()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort the roots.
|
||||
roots.sort_by_key(|index| &graph[*index]);
|
||||
|
||||
// Re-add the removed edges.
|
||||
for (source, target, weight) in edges {
|
||||
graph.add_edge(source, target, weight);
|
||||
}
|
||||
|
||||
roots
|
||||
// Use the root node directly.
|
||||
vec![root]
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -527,16 +495,19 @@ impl<'env> TreeDisplay<'env> {
|
|||
let mut lines = vec![line];
|
||||
|
||||
// Keep track of the dependency path to avoid cycles.
|
||||
visited.insert(
|
||||
package_id,
|
||||
dependencies
|
||||
.iter()
|
||||
.filter_map(|node| match self.graph[node.node()] {
|
||||
Node::Package(package_id) => Some(package_id),
|
||||
Node::Root => None,
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
// Only mark as visited if we're going to expand children (not at depth limit).
|
||||
if path.len() < self.depth {
|
||||
visited.insert(
|
||||
package_id,
|
||||
dependencies
|
||||
.iter()
|
||||
.filter_map(|node| match self.graph[node.node()] {
|
||||
Node::Package(package_id) => Some(package_id),
|
||||
Node::Root => None,
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
path.push(package_id);
|
||||
|
||||
for (index, dep) in dependencies.iter().enumerate() {
|
||||
|
|
|
|||
|
|
@ -1004,6 +1004,79 @@ fn cycle() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cycle_no_orphaned_roots() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["apache-airflow"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// With --depth 1, only project should appear as a root
|
||||
uv_snapshot!(context.filters(), context.tree().arg("--universal").arg("--depth").arg("1"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
project v0.1.0
|
||||
└── apache-airflow v2.8.3
|
||||
|
||||
----- stderr -----
|
||||
Resolved 135 packages in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cycle_no_infinite_loop() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["testtools==2.3.0", "fixtures==3.0.0"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// This should complete without hanging, and cycles should be marked with (*)
|
||||
uv_snapshot!(context.filters(), context.tree().arg("--universal").arg("--depth").arg("2"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
project v0.1.0
|
||||
├── fixtures v3.0.0
|
||||
│ ├── pbr v6.0.0
|
||||
│ ├── six v1.16.0
|
||||
│ └── testtools v2.3.0
|
||||
└── testtools v2.3.0
|
||||
├── extras v1.0.0
|
||||
├── fixtures v3.0.0 (*)
|
||||
├── pbr v6.0.0
|
||||
├── python-mimeparse v1.6.0
|
||||
├── six v1.16.0
|
||||
├── traceback2 v1.4.0
|
||||
└── unittest2 v1.1.0
|
||||
(*) Package tree already displayed
|
||||
|
||||
----- stderr -----
|
||||
Resolved 11 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_dev() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue