feat: implement dependency overrides

This commit is contained in:
daimond113 2024-07-22 22:00:09 +02:00
parent 64ed3931cf
commit 14463b7205
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
13 changed files with 186 additions and 178 deletions

41
Cargo.lock generated
View file

@ -304,9 +304,6 @@ name = "bitflags"
version = "2.6.0" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
@ -1109,7 +1106,6 @@ dependencies = [
"gix-index", "gix-index",
"gix-lock", "gix-lock",
"gix-macros", "gix-macros",
"gix-mailmap",
"gix-negotiate", "gix-negotiate",
"gix-object", "gix-object",
"gix-odb", "gix-odb",
@ -1136,7 +1132,6 @@ dependencies = [
"once_cell", "once_cell",
"parking_lot", "parking_lot",
"regex", "regex",
"serde",
"smallvec", "smallvec",
"thiserror", "thiserror",
] ]
@ -1151,7 +1146,6 @@ dependencies = [
"gix-date", "gix-date",
"gix-utils", "gix-utils",
"itoa", "itoa",
"serde",
"thiserror", "thiserror",
"winnow 0.6.14", "winnow 0.6.14",
] ]
@ -1168,7 +1162,6 @@ dependencies = [
"gix-quote", "gix-quote",
"gix-trace", "gix-trace",
"kstring", "kstring",
"serde",
"smallvec", "smallvec",
"thiserror", "thiserror",
"unicode-bom", "unicode-bom",
@ -1215,7 +1208,6 @@ dependencies = [
"gix-features", "gix-features",
"gix-hash", "gix-hash",
"memmap2", "memmap2",
"serde",
"thiserror", "thiserror",
] ]
@ -1267,7 +1259,6 @@ dependencies = [
"gix-sec", "gix-sec",
"gix-trace", "gix-trace",
"gix-url", "gix-url",
"serde",
"thiserror", "thiserror",
] ]
@ -1279,7 +1270,6 @@ checksum = "9eed6931f21491ee0aeb922751bd7ec97b4b2fe8fbfedcb678e2a2dce5f3b8c0"
dependencies = [ dependencies = [
"bstr", "bstr",
"itoa", "itoa",
"serde",
"thiserror", "thiserror",
"time", "time",
] ]
@ -1374,7 +1364,6 @@ dependencies = [
"bstr", "bstr",
"gix-features", "gix-features",
"gix-path", "gix-path",
"serde",
] ]
[[package]] [[package]]
@ -1384,7 +1373,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93d7df7366121b5018f947a04d37f034717e113dcf9ccd85c34b58e57a74d5e" checksum = "f93d7df7366121b5018f947a04d37f034717e113dcf9ccd85c34b58e57a74d5e"
dependencies = [ dependencies = [
"faster-hex", "faster-hex",
"serde",
"thiserror", "thiserror",
] ]
@ -1409,7 +1397,6 @@ dependencies = [
"gix-glob", "gix-glob",
"gix-path", "gix-path",
"gix-trace", "gix-trace",
"serde",
"unicode-bom", "unicode-bom",
] ]
@ -1437,7 +1424,6 @@ dependencies = [
"libc", "libc",
"memmap2", "memmap2",
"rustix", "rustix",
"serde",
"smallvec", "smallvec",
"thiserror", "thiserror",
] ]
@ -1464,19 +1450,6 @@ dependencies = [
"syn 2.0.71", "syn 2.0.71",
] ]
[[package]]
name = "gix-mailmap"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cb2da346958252cbc8656529f5479830a3bc6046f3d86405c9e77f71dfdf7b2"
dependencies = [
"bstr",
"gix-actor",
"gix-date",
"serde",
"thiserror",
]
[[package]] [[package]]
name = "gix-negotiate" name = "gix-negotiate"
version = "0.13.1" version = "0.13.1"
@ -1507,7 +1480,6 @@ dependencies = [
"gix-utils", "gix-utils",
"gix-validate", "gix-validate",
"itoa", "itoa",
"serde",
"smallvec", "smallvec",
"thiserror", "thiserror",
"winnow 0.6.14", "winnow 0.6.14",
@ -1529,7 +1501,6 @@ dependencies = [
"gix-path", "gix-path",
"gix-quote", "gix-quote",
"parking_lot", "parking_lot",
"serde",
"tempfile", "tempfile",
"thiserror", "thiserror",
] ]
@ -1550,7 +1521,6 @@ dependencies = [
"gix-tempfile", "gix-tempfile",
"memmap2", "memmap2",
"parking_lot", "parking_lot",
"serde",
"smallvec", "smallvec",
"thiserror", "thiserror",
] ]
@ -1634,7 +1604,6 @@ dependencies = [
"gix-transport", "gix-transport",
"gix-utils", "gix-utils",
"maybe-async", "maybe-async",
"serde",
"thiserror", "thiserror",
"winnow 0.6.14", "winnow 0.6.14",
] ]
@ -1668,7 +1637,6 @@ dependencies = [
"gix-utils", "gix-utils",
"gix-validate", "gix-validate",
"memmap2", "memmap2",
"serde",
"thiserror", "thiserror",
"winnow 0.6.14", "winnow 0.6.14",
] ]
@ -1700,7 +1668,6 @@ dependencies = [
"gix-object", "gix-object",
"gix-revwalk", "gix-revwalk",
"gix-trace", "gix-trace",
"serde",
"thiserror", "thiserror",
] ]
@ -1728,7 +1695,6 @@ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"gix-path", "gix-path",
"libc", "libc",
"serde",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@ -1782,7 +1748,6 @@ dependencies = [
"gix-sec", "gix-sec",
"gix-url", "gix-url",
"reqwest", "reqwest",
"serde",
"thiserror", "thiserror",
] ]
@ -1813,7 +1778,6 @@ dependencies = [
"gix-features", "gix-features",
"gix-path", "gix-path",
"home", "home",
"serde",
"thiserror", "thiserror",
"url", "url",
] ]
@ -1855,7 +1819,6 @@ dependencies = [
"gix-object", "gix-object",
"gix-path", "gix-path",
"gix-validate", "gix-validate",
"serde",
] ]
[[package]] [[package]]
@ -2284,7 +2247,6 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747" checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747"
dependencies = [ dependencies = [
"serde",
"static_assertions", "static_assertions",
] ]
@ -3356,9 +3318,6 @@ name = "smallvec"
version = "1.13.2" version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "smol_str" name = "smol_str"

View file

@ -29,7 +29,7 @@ serde = { version = "1.0.204", features = ["derive"] }
toml = "0.8.15" toml = "0.8.15"
serde_json = "1.0.120" serde_json = "1.0.120"
serde_with = "3.9.0" serde_with = "3.9.0"
gix = { version = "0.63.0", default-features = false, features = ["blocking-http-transport-reqwest-rust-tls", "revparse-regex", "credentials", "serde"] } gix = { version = "0.63.0", default-features = false, features = ["blocking-http-transport-reqwest-rust-tls", "revparse-regex", "credentials"] }
semver = { version = "1.0.23", features = ["serde"] } semver = { version = "1.0.23", features = ["serde"] }
reqwest = { version = "0.12.5", default-features = false, features = ["rustls-tls", "blocking"] } reqwest = { version = "0.12.5", default-features = false, features = ["rustls-tls", "blocking"] }
tar = "0.4.41" tar = "0.4.41"

View file

@ -32,143 +32,141 @@ impl InitCommand {
match project.read_manifest() { match project.read_manifest() {
Ok(_) => { Ok(_) => {
println!("{}", "project already initialized".red()); println!("{}", "project already initialized".red());
Ok(()) return Ok(());
} }
Err(ManifestReadError::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => { Err(ManifestReadError::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => {}
let mut manifest = toml_edit::DocumentMut::new(); Err(e) => return Err(e.into()),
};
manifest["name"] = toml_edit::value( let mut manifest = toml_edit::DocumentMut::new();
inquire::Text::new("What is the name of the project?")
.with_validator(|name: &str| {
Ok(match PackageName::from_str(name) {
Ok(_) => Validation::Valid,
Err(e) => Validation::Invalid(e.to_string().into()),
})
})
.prompt()
.unwrap(),
);
manifest["version"] = toml_edit::value("0.1.0");
let description = inquire::Text::new( manifest["name"] = toml_edit::value(
"What is the description of the project? (leave empty for none)", inquire::Text::new("What is the name of the project?")
) .with_validator(|name: &str| {
.prompt() Ok(match PackageName::from_str(name) {
.unwrap();
if !description.is_empty() {
manifest["description"] = toml_edit::value(description);
}
let authors = inquire::Text::new(
"Who are the authors of this project? (leave empty for none, comma separated)",
)
.prompt()
.unwrap();
let authors = authors
.split(',')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.map(|s| s.into())
.collect::<Vec<toml_edit::Value>>();
if !authors.is_empty() {
let mut authors_arr = toml_edit::Array::new();
authors_arr.extend(authors);
manifest["authors"] = toml_edit::value(authors_arr);
}
let repo = inquire::Text::new(
"What is the repository URL of this project? (leave empty for none)",
)
.with_validator(|repo: &str| {
if repo.is_empty() {
return Ok(Validation::Valid);
}
Ok(match url::Url::parse(repo) {
Ok(_) => Validation::Valid, Ok(_) => Validation::Valid,
Err(e) => Validation::Invalid(e.to_string().into()), Err(e) => Validation::Invalid(e.to_string().into()),
}) })
}) })
.prompt() .prompt()
.unwrap(); .unwrap(),
if !repo.is_empty() { );
manifest["repository"] = toml_edit::value(repo); manifest["version"] = toml_edit::value("0.1.0");
}
let license = inquire::Text::new( let description =
"What is the license of this project? (leave empty for none)", inquire::Text::new("What is the description of the project? (leave empty for none)")
) .prompt()
.unwrap();
if !description.is_empty() {
manifest["description"] = toml_edit::value(description);
}
let authors = inquire::Text::new(
"Who are the authors of this project? (leave empty for none, comma separated)",
)
.prompt()
.unwrap();
let authors = authors
.split(',')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.map(|s| s.into())
.collect::<Vec<toml_edit::Value>>();
if !authors.is_empty() {
let mut authors_arr = toml_edit::Array::new();
authors_arr.extend(authors);
manifest["authors"] = toml_edit::value(authors_arr);
}
let repo = inquire::Text::new(
"What is the repository URL of this project? (leave empty for none)",
)
.with_validator(|repo: &str| {
if repo.is_empty() {
return Ok(Validation::Valid);
}
Ok(match url::Url::parse(repo) {
Ok(_) => Validation::Valid,
Err(e) => Validation::Invalid(e.to_string().into()),
})
})
.prompt()
.unwrap();
if !repo.is_empty() {
manifest["repository"] = toml_edit::value(repo);
}
let license =
inquire::Text::new("What is the license of this project? (leave empty for none)")
.with_initial_value("MIT") .with_initial_value("MIT")
.prompt() .prompt()
.unwrap(); .unwrap();
if !license.is_empty() { if !license.is_empty() {
manifest["license"] = toml_edit::value(license); manifest["license"] = toml_edit::value(license);
} }
let target_env = inquire::Select::new( let target_env = inquire::Select::new(
"What environment are you targeting for your package?", "What environment are you targeting for your package?",
vec![ vec![
#[cfg(feature = "roblox")] #[cfg(feature = "roblox")]
"roblox", "roblox",
#[cfg(feature = "lune")] #[cfg(feature = "lune")]
"lune", "lune",
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
"luau", "luau",
], ],
) )
.prompt() .prompt()
.unwrap();
let mut target = toml_edit::Table::new();
target["environment"] = toml_edit::value(target_env);
if target_env == "roblox"
|| inquire::Confirm::new(&format!(
"Would you like to setup a default {} script?",
ScriptName::RobloxSyncConfigGenerator
))
.prompt()
.unwrap()
{
let folder = project.path().join(concat!(".", env!("CARGO_PKG_NAME")));
std::fs::create_dir_all(&folder).context("failed to create scripts folder")?;
std::fs::write(
folder.join(format!("{}.luau", ScriptName::RobloxSyncConfigGenerator)),
script_contents(Path::new(&format!(
"lune/rojo/{}.luau",
ScriptName::RobloxSyncConfigGenerator
))),
)
.context("failed to write script file")?;
let scripts = manifest
.entry("scripts")
.or_insert(toml_edit::Item::Table(toml_edit::Table::new()))
.as_table_mut()
.unwrap(); .unwrap();
let mut target = toml_edit::Table::new(); scripts[&ScriptName::RobloxSyncConfigGenerator.to_string()] =
target["environment"] = toml_edit::value(target_env); toml_edit::value(format!(
concat!(".", env!("CARGO_PKG_NAME"), "/{}.luau"),
if target_env == "roblox" ScriptName::RobloxSyncConfigGenerator
|| inquire::Confirm::new(&format!( ));
"Would you like to setup a default {} script?",
ScriptName::RobloxSyncConfigGenerator
))
.prompt()
.unwrap()
{
let folder = project.path().join(concat!(".", env!("CARGO_PKG_NAME")));
std::fs::create_dir_all(&folder).context("failed to create scripts folder")?;
std::fs::write(
folder.join(format!("{}.luau", ScriptName::RobloxSyncConfigGenerator)),
script_contents(Path::new(&format!(
"lune/rojo/{}.luau",
ScriptName::RobloxSyncConfigGenerator
))),
)
.context("failed to write script file")?;
let scripts = manifest
.entry("scripts")
.or_insert(toml_edit::Item::Table(toml_edit::Table::new()))
.as_table_mut()
.unwrap();
scripts[&ScriptName::RobloxSyncConfigGenerator.to_string()] =
toml_edit::value(format!(
concat!(".", env!("CARGO_PKG_NAME"), "/{}.luau"),
ScriptName::RobloxSyncConfigGenerator
));
}
let mut indices = toml_edit::Table::new();
indices[DEFAULT_INDEX_NAME] =
toml_edit::value(read_config(project.data_dir())?.default_index.as_str());
project.write_manifest(manifest.to_string())?;
println!("{}", "initialized project".green());
Ok(())
}
Err(e) => Err(e.into()),
} }
let mut indices = toml_edit::Table::new();
indices[DEFAULT_INDEX_NAME] =
toml_edit::value(read_config(project.data_dir())?.default_index.as_str());
project.write_manifest(manifest.to_string())?;
println!("{}", "initialized project".green());
Ok(())
} }
} }

View file

@ -16,7 +16,7 @@ impl InstallCommand {
.context("failed to read manifest")?; .context("failed to read manifest")?;
let lockfile = if project let lockfile = if project
.is_up_to_date() .is_up_to_date(false)
.context("failed to check if project is up to date")? .context("failed to check if project is up to date")?
{ {
match project.deser_lockfile() { match project.deser_lockfile() {

View file

@ -1,4 +1,4 @@
use crate::git::authenticate_conn; use crate::util::authenticate_conn;
use anyhow::Context; use anyhow::Context;
use gix::remote::Direction; use gix::remote::Direction;
use keyring::Entry; use keyring::Entry;
@ -229,11 +229,11 @@ pub fn update_scripts_folder(project: &Project) -> anyhow::Result<()> {
} }
pub trait IsUpToDate { pub trait IsUpToDate {
fn is_up_to_date(&self) -> anyhow::Result<bool>; fn is_up_to_date(&self, strict: bool) -> anyhow::Result<bool>;
} }
impl IsUpToDate for Project { impl IsUpToDate for Project {
fn is_up_to_date(&self) -> anyhow::Result<bool> { fn is_up_to_date(&self, strict: bool) -> anyhow::Result<bool> {
let manifest = self.deser_manifest()?; let manifest = self.deser_manifest()?;
let lockfile = match self.deser_lockfile() { let lockfile = match self.deser_lockfile() {
Ok(lockfile) => lockfile, Ok(lockfile) => lockfile,
@ -245,6 +245,11 @@ impl IsUpToDate for Project {
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
}; };
if !strict {
// the resolver will use the old lockfile and update it itself. it can't handle overrides only
return Ok(manifest.overrides == lockfile.overrides);
}
if manifest.name != lockfile.name || manifest.version != lockfile.version { if manifest.name != lockfile.name || manifest.version != lockfile.version {
return Ok(false); return Ok(false);
} }

View file

@ -22,7 +22,7 @@ pub struct RunCommand {
impl RunCommand { impl RunCommand {
pub fn run(self, project: Project) -> anyhow::Result<()> { pub fn run(self, project: Project) -> anyhow::Result<()> {
if let Ok(pkg_name) = self.package_or_script.parse::<PackageName>() { if let Ok(pkg_name) = self.package_or_script.parse::<PackageName>() {
let graph = if project.is_up_to_date()? { let graph = if project.is_up_to_date(true)? {
project.deser_lockfile()?.graph project.deser_lockfile()?.graph
} else { } else {
anyhow::bail!("outdated lockfile, please run the install command first") anyhow::bail!("outdated lockfile, please run the install command first")

View file

@ -8,7 +8,6 @@ use once_cell::sync::Lazy;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub mod download; pub mod download;
mod git;
pub mod linking; pub mod linking;
pub mod lockfile; pub mod lockfile;
pub mod manifest; pub mod manifest;
@ -16,6 +15,7 @@ pub mod names;
pub mod resolver; pub mod resolver;
pub mod scripts; pub mod scripts;
pub mod source; pub mod source;
pub(crate) mod util;
pub const MANIFEST_FILE_NAME: &str = "pesde.toml"; pub const MANIFEST_FILE_NAME: &str = "pesde.toml";
pub const LOCKFILE_FILE_NAME: &str = "pesde.lock"; pub const LOCKFILE_FILE_NAME: &str = "pesde.lock";

View file

@ -17,6 +17,7 @@ pub struct DependencyGraphNode {
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub direct: Option<(String, DependencySpecifiers)>, pub direct: Option<(String, DependencySpecifiers)>,
pub pkg_ref: PackageRefs, pub pkg_ref: PackageRefs,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub dependencies: BTreeMap<PackageNames, (Version, String)>, pub dependencies: BTreeMap<PackageNames, (Version, String)>,
pub ty: DependencyType, pub ty: DependencyType,
} }

View file

@ -5,7 +5,7 @@ use pesde::{AuthConfig, Project};
use std::fs::create_dir_all; use std::fs::create_dir_all;
mod cli; mod cli;
pub mod git; pub mod util;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[clap(version, about = "pesde is a feature-rich package manager for Luau")] #[clap(version, about = "pesde is a feature-rich package manager for Luau")]

View file

@ -11,7 +11,7 @@ use semver::Version;
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet, VecDeque};
impl Project { impl Project {
// TODO: implement dependency overrides, account for targets using the is_compatible_with method // TODO: account for targets using the is_compatible_with method
pub fn dependency_graph( pub fn dependency_graph(
&self, &self,
previous_graph: Option<&DependencyGraph>, previous_graph: Option<&DependencyGraph>,
@ -101,17 +101,28 @@ impl Project {
let mut queue = all_specifiers let mut queue = all_specifiers
.into_iter() .into_iter()
.map(|((spec, ty), alias)| (alias, spec, ty, None::<(PackageNames, Version)>, 0usize)) .map(|((spec, ty), alias)| {
(
alias.to_string(),
spec,
ty,
None::<(PackageNames, Version)>,
vec![alias.to_string()],
false,
)
})
.collect::<VecDeque<_>>(); .collect::<VecDeque<_>>();
while let Some((alias, specifier, ty, dependant, depth)) = queue.pop_front() { while let Some((alias, specifier, ty, dependant, path, overridden)) = queue.pop_front() {
let depth = path.len() - 1;
log::debug!( log::debug!(
"{}resolving {specifier} ({alias}) from {dependant:?}", "{}resolving {specifier} ({alias}) from {dependant:?}",
"\t".repeat(depth) "\t".repeat(depth)
); );
let source = match &specifier { let source = match &specifier {
DependencySpecifiers::Pesde(specifier) => { DependencySpecifiers::Pesde(specifier) => {
let index_url = if depth == 0 { let index_url = if depth == 0 || overridden {
let index_name = specifier.index.as_deref().unwrap_or(DEFAULT_INDEX_NAME); let index_name = specifier.index.as_deref().unwrap_or(DEFAULT_INDEX_NAME);
let index_url = manifest.indices.get(index_name).ok_or( let index_url = manifest.indices.get(index_name).ok_or(
errors::DependencyGraphError::IndexNotFound(index_name.to_string()), errors::DependencyGraphError::IndexNotFound(index_name.to_string()),
@ -235,12 +246,28 @@ impl Project {
continue; continue;
} }
let overridden = manifest.overrides.iter().find_map(|(key, spec)| {
key.0.iter().find_map(|override_path| {
// if the path up until the last element is the same as the current path,
// and the last element in the path is the dependency alias,
// then the specifier is to be overridden
(path.len() == override_path.len() - 1
&& path == override_path[..override_path.len() - 1]
&& override_path.last() == Some(&dependency_alias))
.then_some(spec)
})
});
queue.push_back(( queue.push_back((
dependency_alias, dependency_alias,
dependency_spec, overridden.cloned().unwrap_or(dependency_spec),
dependency_ty, dependency_ty,
Some((name.clone(), target_version.clone())), Some((name.clone(), target_version.clone())),
depth + 1, path.iter()
.cloned()
.chain(std::iter::once(alias.to_string()))
.collect(),
overridden.is_some(),
)); ));
} }
} }

View file

@ -15,10 +15,10 @@ use pkg_ref::PesdePackageRef;
use specifier::PesdeDependencySpecifier; use specifier::PesdeDependencySpecifier;
use crate::{ use crate::{
git::authenticate_conn,
manifest::{DependencyType, Target, TargetKind}, manifest::{DependencyType, Target, TargetKind},
names::{PackageName, PackageNames}, names::{PackageName, PackageNames},
source::{hash, DependencySpecifiers, PackageSource, ResolveResult}, source::{hash, DependencySpecifiers, PackageSource, ResolveResult},
util::authenticate_conn,
Project, REQWEST_CLIENT, Project, REQWEST_CLIENT,
}; };

View file

@ -13,7 +13,12 @@ use crate::{
pub struct PesdePackageRef { pub struct PesdePackageRef {
pub name: PackageName, pub name: PackageName,
pub version: Version, pub version: Version,
#[serde(
serialize_with = "crate::util::serialize_gix_url",
deserialize_with = "crate::util::deserialize_gix_url"
)]
pub index_url: gix::Url, pub index_url: gix::Url,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub dependencies: BTreeMap<String, (DependencySpecifiers, DependencyType)>, pub dependencies: BTreeMap<String, (DependencySpecifiers, DependencyType)>,
pub target: Target, pub target: Target,
} }

View file

@ -1,4 +1,6 @@
use crate::AuthConfig; use crate::AuthConfig;
use gix::bstr::BStr;
use serde::{Deserialize, Deserializer, Serializer};
pub fn authenticate_conn( pub fn authenticate_conn(
conn: &mut gix::remote::Connection< conn: &mut gix::remote::Connection<
@ -21,3 +23,14 @@ pub fn authenticate_conn(
}); });
} }
} }
pub fn serialize_gix_url<S: Serializer>(url: &gix::Url, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&url.to_bstring().to_string())
}
pub fn deserialize_gix_url<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<gix::Url, D::Error> {
let s = String::deserialize(deserializer)?;
gix::Url::from_bytes(BStr::new(&s)).map_err(serde::de::Error::custom)
}