feat(source): support cherry-picking commits for git sources

- add source.cherry_pick (and cherry-pick alias) to package spec parsing/serialization
- apply listed cherry-picks in order after git checkout
- reject cherry_pick on non-git sources with explicit error
- include cherry_pick in interactive spec TOML output
- add parsing/behavior tests and update Source test fixtures
This commit is contained in:
2026-02-28 02:13:46 -06:00
parent 7e7bd84a43
commit 73a994ac53
15 changed files with 229 additions and 1 deletions
+18
View File
@@ -425,6 +425,7 @@ pub fn create_interactive() -> Result<PackageSpec> {
extract_dir,
patches: Vec::new(),
post_extract: Vec::new(),
cherry_pick: Vec::new(),
};
if show_advanced {
@@ -791,6 +792,17 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
),
);
}
if !s.cherry_pick.is_empty() {
st.insert(
"cherry_pick".into(),
Value::Array(
s.cherry_pick
.iter()
.map(|rev| Value::String(rev.clone()))
.collect(),
),
);
}
arr.push(Value::Table(st));
}
root.insert("source".into(), Value::Array(arr));
@@ -1316,6 +1328,7 @@ mod tests {
extract_dir: "foo-1.0".into(),
patches: Vec::new(),
post_extract: Vec::new(),
cherry_pick: Vec::new(),
}],
build: Build {
build_type: BuildType::Autotools,
@@ -1365,6 +1378,7 @@ mod tests {
extract_dir: "foo-1.0".into(),
patches: Vec::new(),
post_extract: Vec::new(),
cherry_pick: Vec::new(),
}],
build: Build {
build_type: BuildType::Autotools,
@@ -1401,6 +1415,7 @@ mod tests {
extract_dir: "foo-1.0".into(),
patches: Vec::new(),
post_extract: Vec::new(),
cherry_pick: Vec::new(),
}],
build: Build {
build_type: BuildType::Autotools,
@@ -1464,6 +1479,7 @@ mod tests {
extract_dir: "foo-1.0".into(),
patches: vec!["fix.patch".into()],
post_extract: vec!["autoreconf -fi".into()],
cherry_pick: Vec::new(),
}],
build: Build {
build_type: BuildType::Autotools,
@@ -1525,6 +1541,7 @@ mod tests {
extract_dir: "$name-$version".into(),
patches: Vec::new(),
post_extract: Vec::new(),
cherry_pick: Vec::new(),
}],
build: Build {
build_type: BuildType::Autotools,
@@ -1560,6 +1577,7 @@ mod tests {
extract_dir: "foo-1.0".into(),
patches: Vec::new(),
post_extract: Vec::new(),
cherry_pick: Vec::new(),
}],
build: Build {
build_type: BuildType::Autotools,
+48
View File
@@ -1153,6 +1153,7 @@ type = "custom"
);
assert!(spec.sources()[0].patches.is_empty());
assert!(spec.sources()[0].post_extract.is_empty());
assert!(spec.sources()[0].cherry_pick.is_empty());
assert_eq!(spec.spec_dir, tmp.path());
}
@@ -1193,6 +1194,40 @@ type = "custom"
assert_eq!(spec.sources()[1].extract_dir, "bar");
}
#[test]
fn parse_git_source_with_cherry_pick() {
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("pkg.toml");
std::fs::write(
&path,
r#"
[package]
name = "foo"
version = "1.0"
description = "d"
homepage = "h"
license = "MIT"
[source]
url = "https://example.com/foo.git#main"
sha256 = "skip"
extract_dir = "foo"
cherry_pick = ["deadbeef", "cafebabe"]
[build]
type = "custom"
"#,
)
.unwrap();
let spec = PackageSpec::from_file(&path).unwrap();
assert_eq!(
spec.sources()[0].cherry_pick,
vec!["deadbeef".to_string(), "cafebabe".to_string()]
);
}
#[test]
fn parse_package_dependencies_overrides() {
let tmp = tempfile::tempdir().unwrap();
@@ -2378,6 +2413,7 @@ type = "custom"
extract_dir: "e".into(),
patches: Vec::new(),
post_extract: Vec::new(),
cherry_pick: Vec::new(),
}],
build: Build {
build_type: BuildType::Custom,
@@ -2503,6 +2539,18 @@ pub struct Source {
/// post_extract = ["autoreconf -fi"]
#[serde(default)]
pub post_extract: Vec<String>,
/// Optional list of git commit hashes/revs to cherry-pick after checkout.
///
/// This is only valid for git sources (`*.git` URL or `url#rev` git form).
/// Example:
/// cherry_pick = ["a1b2c3d4", "deadbeef"]
#[serde(
default,
alias = "cherry-pick",
deserialize_with = "deserialize_string_or_array"
)]
pub cherry_pick: Vec<String>,
}
/// Manual source copied before standard source fetching.