Skip to content

Commit 105449b

Browse files
committed
Respect path dependencies within Git dependencies
1 parent 4ec9ad2 commit 105449b

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

crates/uv-distribution/src/metadata/lowering.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ impl LoweredRequirement {
220220
}
221221
let source = path_source(
222222
PathBuf::from(path),
223+
git_member,
223224
origin,
224225
project_dir,
225226
workspace.install_path(),
@@ -465,6 +466,7 @@ impl LoweredRequirement {
465466
}
466467
let source = path_source(
467468
PathBuf::from(path),
469+
None,
468470
RequirementOrigin::Project,
469471
dir,
470472
dir,
@@ -553,6 +555,8 @@ pub enum LoweringError {
553555
WorkspaceFalse,
554556
#[error("Editable must refer to a local directory, not a file: `{0}`")]
555557
EditableFile(String),
558+
#[error("Git repository references local file source, but only directories are supported as transitive Git dependencies: `{0}`")]
559+
GitFile(String),
556560
#[error(transparent)]
557561
ParsedUrl(#[from] ParsedUrlError),
558562
#[error("Path must be UTF-8: `{0}`")]
@@ -678,6 +682,7 @@ fn registry_source(
678682
/// Convert a path string to a file or directory source.
679683
fn path_source(
680684
path: impl AsRef<Path>,
685+
git_member: Option<&GitWorkspaceMember>,
681686
origin: RequirementOrigin,
682687
project_dir: &Path,
683688
workspace_root: &Path,
@@ -702,6 +707,22 @@ fn path_source(
702707
install_path.extension().is_none()
703708
};
704709
if is_dir {
710+
if let Some(git_member) = git_member {
711+
let subdirectory = uv_fs::relative_to(install_path, git_member.fetch_root)
712+
.expect("Workspace member must be relative");
713+
return Ok(RequirementSource::Git {
714+
repository: git_member.git_source.git.repository().clone(),
715+
reference: git_member.git_source.git.reference().clone(),
716+
precise: git_member.git_source.git.precise(),
717+
subdirectory: if subdirectory == PathBuf::new() {
718+
None
719+
} else {
720+
Some(subdirectory)
721+
},
722+
url,
723+
});
724+
}
725+
705726
if editable {
706727
Ok(RequirementSource::Directory {
707728
install_path,
@@ -731,6 +752,10 @@ fn path_source(
731752
})
732753
}
733754
} else {
755+
// TODO(charlie): If a Git repo contains a source that points to a file, what should we do?
756+
if git_member.is_some() {
757+
return Err(LoweringError::GitFile(url.to_string()));
758+
}
734759
if editable {
735760
return Err(LoweringError::EditableFile(url.to_string()));
736761
}

crates/uv/tests/it/sync.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5200,3 +5200,92 @@ fn mismatched_name_cached_wheel() -> Result<()> {
52005200

52015201
Ok(())
52025202
}
5203+
5204+
/// Sync a Git repository that depends on a package within the same repository via a `path` source.
5205+
///
5206+
/// See: https://github.com/astral-sh/uv/issues/9516
5207+
#[test]
5208+
fn sync_git_path_dependency() -> Result<()> {
5209+
let context = TestContext::new("3.13");
5210+
5211+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
5212+
pyproject_toml.write_str(
5213+
r#"
5214+
[project]
5215+
name = "foo"
5216+
version = "0.1.0"
5217+
requires-python = ">=3.13"
5218+
dependencies = ["package2"]
5219+
5220+
[tool.uv.sources]
5221+
package2 = { git = "https://git@github.com/astral-sh/uv-path-dependency-test.git", subdirectory = "package2" }
5222+
"#,
5223+
)?;
5224+
5225+
uv_snapshot!(context.filters(), context.lock(), @r###"
5226+
success: true
5227+
exit_code: 0
5228+
----- stdout -----
5229+
5230+
----- stderr -----
5231+
Resolved 3 packages in [TIME]
5232+
"###);
5233+
5234+
let lock = context.read("uv.lock");
5235+
5236+
insta::with_settings!(
5237+
{
5238+
filters => context.filters(),
5239+
},
5240+
{
5241+
assert_snapshot!(
5242+
lock, @r###"
5243+
version = 1
5244+
requires-python = ">=3.13"
5245+
5246+
[options]
5247+
exclude-newer = "2024-03-25T00:00:00Z"
5248+
5249+
[[package]]
5250+
name = "foo"
5251+
version = "0.1.0"
5252+
source = { virtual = "." }
5253+
dependencies = [
5254+
{ name = "package2" },
5255+
]
5256+
5257+
[package.metadata]
5258+
requires-dist = [{ name = "package2", git = "https://github.com/astral-sh/uv-path-dependency-test.git?subdirectory=package2" }]
5259+
5260+
[[package]]
5261+
name = "package1"
5262+
version = "0.1.0"
5263+
source = { git = "https://github.com/astral-sh/uv-path-dependency-test.git?subdirectory=package1#28781b32cf1f260cdb2c8040628079eb265202bd" }
5264+
5265+
[[package]]
5266+
name = "package2"
5267+
version = "0.1.0"
5268+
source = { git = "https://github.com/astral-sh/uv-path-dependency-test.git?subdirectory=package2#28781b32cf1f260cdb2c8040628079eb265202bd" }
5269+
dependencies = [
5270+
{ name = "package1" },
5271+
]
5272+
"###
5273+
);
5274+
}
5275+
);
5276+
5277+
uv_snapshot!(context.filters(), context.sync(), @r###"
5278+
success: true
5279+
exit_code: 0
5280+
----- stdout -----
5281+
5282+
----- stderr -----
5283+
Resolved 3 packages in [TIME]
5284+
Prepared 2 packages in [TIME]
5285+
Installed 2 packages in [TIME]
5286+
+ package1==0.1.0 (from git+https://github.com/astral-sh/uv-path-dependency-test.git@28781b32cf1f260cdb2c8040628079eb265202bd#subdirectory=package1)
5287+
+ package2==0.1.0 (from git+https://github.com/astral-sh/uv-path-dependency-test.git@28781b32cf1f260cdb2c8040628079eb265202bd#subdirectory=package2)
5288+
"###);
5289+
5290+
Ok(())
5291+
}

0 commit comments

Comments
 (0)