use crate::simple_apt_repo::SimpleTrustedAptRepo;
use crate::DebianizePreferences;
use breezyshim::branch::{Branch, GenericBranch, PyBranch};
use breezyshim::error::Error as BrzError;
use breezyshim::workingtree::GenericWorkingTree;
const DEFAULT_VCS_FORMAT: &str = "git";
pub fn extract_branch_from_url(url: &url::Url) -> Option<String> {
if let Some(fragment) = url.fragment() {
if let Some(branch) = fragment.strip_prefix("branch=") {
return Some(branch.to_string());
}
}
for (key, value) in url.query_pairs() {
if key == "branch" {
return Some(value.to_string());
}
}
let path = url.path();
if let Some(pos) = path.find("/tree/") {
let branch_part = &path[pos + 6..];
if let Some(end) = branch_part.find('/') {
return Some(branch_part[..end].to_string());
} else if !branch_part.is_empty() {
return Some(branch_part.to_string());
}
}
None
}
use buildlog_consultant::Problem;
use ognibuild::buildlog::problem_to_dependency;
use ognibuild::debian::build::BuildOnceResult;
use ognibuild::debian::context::{Error, Phase};
use ognibuild::debian::fix_build::{DebianBuildFixer, IterateBuildError};
use ognibuild::fix_build::InterimError;
use ognibuild::upstream::find_upstream;
use std::path::Path;
pub struct DebianizeFixer<'a> {
vcs_directory: std::path::PathBuf,
apt_repo: SimpleTrustedAptRepo,
do_build: Box<
dyn for<'b, 'c, 'd> Fn(
&'b GenericWorkingTree,
&'c std::path::Path,
&'d std::path::Path,
Vec<&str>,
) -> Result<BuildOnceResult, IterateBuildError>,
>,
preferences: &'a DebianizePreferences,
}
impl<'a> DebianizeFixer<'a> {
pub fn new(
vcs_directory: std::path::PathBuf,
apt_repo: SimpleTrustedAptRepo,
do_build: Box<
dyn for<'b, 'c, 'd> Fn(
&'b GenericWorkingTree,
&'c std::path::Path,
&'d std::path::Path,
Vec<&str>,
) -> Result<BuildOnceResult, IterateBuildError>,
>,
preferences: &'a DebianizePreferences,
) -> Self {
Self {
vcs_directory,
apt_repo,
do_build,
preferences,
}
}
pub fn apt_repo(&self) -> &SimpleTrustedAptRepo {
&self.apt_repo
}
}
impl<'a> std::fmt::Debug for DebianizeFixer<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "DebianizeFixer")
}
}
impl<'a> std::fmt::Display for DebianizeFixer<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "DebianizeFixer")
}
}
impl<'a> DebianBuildFixer for DebianizeFixer<'a> {
fn can_fix(&self, problem: &dyn Problem) -> bool {
let dep = if let Some(dep) = problem_to_dependency(problem) {
dep
} else {
return false;
};
find_upstream(dep.as_ref()).is_some()
}
fn fix(&self, problem: &dyn Problem, _phase: &Phase) -> Result<bool, InterimError<Error>> {
let dep = match problem_to_dependency(problem) {
Some(dep) => dep,
None => {
log::error!("Unable to convert problem {:?} to dependency", problem);
return Ok(false);
}
};
log::debug!("Translated problem {:?} to requirement {:?}", problem, dep);
let upstream_info = if let Some(upstream_info) = find_upstream(dep.as_ref()) {
upstream_info
} else {
log::error!(
"Unable to find upstream information for requirement {:?}",
dep,
);
return Ok(false);
};
let (upstream_branch, upstream_subpath) = if let Some(url) = upstream_info.repository() {
log::info!("Packaging {:?} to address {:?}", url, problem);
let mut url: url::Url = url.parse().unwrap();
let branch_name = extract_branch_from_url(&url);
if branch_name.is_some() {
url.set_fragment(None);
let filtered_query: String = url
.query_pairs()
.filter(|(k, _)| k != "branch")
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join("&");
url.set_query(if filtered_query.is_empty() {
None
} else {
Some(&filtered_query)
});
}
let upstream_branch = match breezyshim::branch::open_as_generic(&url) {
Ok(branch) => {
if let Some(branch_name) = &branch_name {
log::info!("Opening specific branch: {}", branch_name);
match branch.controldir().get_branches() {
Ok(mut branches) => {
if let Some(specific_branch) = branches.remove(branch_name) {
log::info!("Successfully switched to branch: {}", branch_name);
Some(specific_branch as Box<dyn Branch>)
} else {
log::warn!(
"Branch '{}' not found, using default branch",
branch_name
);
Some(Box::new(branch) as Box<dyn Branch>)
}
}
Err(e) => {
log::warn!(
"Unable to get branches for {}: {}, using default",
url,
e
);
Some(Box::new(branch) as Box<dyn Branch>)
}
}
} else {
Some(Box::new(branch) as Box<dyn Branch>)
}
}
Err(e @ BrzError::NotBranchError { .. }) => {
log::warn!("Unable to open branch {}: {}", url, e);
None
}
Err(e) => panic!("Unexpected error opening branch: {:?}", e),
};
(upstream_branch, None)
} else {
(None, None)
};
let vcs_path = if let Some(name) = upstream_info.name() {
self.vcs_directory.join(name.replace("/", "-"))
} else {
panic!("no upstream name provided");
};
if vcs_path.exists() {
std::fs::remove_dir_all(&vcs_path).map_err(|e| InterimError::Other(e.into()))?;
}
let format = if let Some(upstream_branch) = upstream_branch.as_ref() {
upstream_branch.controldir().cloning_metadir()
} else {
use breezyshim::controldir::ControlDirFormatRegistry;
let registry = ControlDirFormatRegistry::new();
registry.make_controldir(DEFAULT_VCS_FORMAT).unwrap()
};
let result = breezyshim::controldir::create_branch_convenience_as_generic(
&url::Url::from_directory_path(vcs_path).unwrap(),
Some(true),
&format,
)
.unwrap();
let new_wt = result.controldir().open_workingtree().unwrap();
let new_subpath = Path::new("");
match crate::debianize(
&new_wt,
new_subpath,
upstream_branch.as_ref().and_then(|b| {
b.as_any()
.downcast_ref::<GenericBranch>()
.map(|gb| gb as &dyn PyBranch)
}),
upstream_subpath,
self.preferences,
upstream_info.version(),
&upstream_info,
) {
Ok(_) => {}
Err(e) => {
log::error!("Failed to debianize: {:?}", e);
return Ok(false);
}
}
match (self.do_build)(
&new_wt,
new_subpath,
self.apt_repo.directory(),
self.apt_repo
.sources_lines()
.iter()
.map(|s| s.as_str())
.collect(),
) {
Ok(_) => {}
Err(e) => {
log::error!("Failed to build: {:?}", e);
return Ok(false);
}
}
self.apt_repo
.refresh()
.map_err(|e| InterimError::Other(e.into()))?;
Ok(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_fixer_display_and_debug() {
let td = tempfile::tempdir().unwrap();
let apt_repo = crate::simple_apt_repo::SimpleTrustedAptRepo::new(td.path().to_path_buf());
let prefs = DebianizePreferences::default();
let fixer = DebianizeFixer {
vcs_directory: PathBuf::from("/tmp/vcs"),
apt_repo,
do_build: Box::new(|_, _, _, _| unreachable!()),
preferences: &prefs,
};
assert_eq!(format!("{}", fixer), "DebianizeFixer");
assert_eq!(format!("{:?}", fixer), "DebianizeFixer");
}
#[test]
fn test_fixer_new() {
let td = tempfile::tempdir().unwrap();
let apt_repo = crate::simple_apt_repo::SimpleTrustedAptRepo::new(td.path().to_path_buf());
let prefs = DebianizePreferences::default();
let fixer = DebianizeFixer::new(
PathBuf::from("/tmp/vcs"),
apt_repo,
Box::new(|_, _, _, _| {
Ok(BuildOnceResult {
source_package: "test".to_string(),
version: "1.0-1".parse().unwrap(),
changes_names: vec![],
})
}),
&prefs,
);
assert_eq!(fixer.vcs_directory, PathBuf::from("/tmp/vcs"));
assert!(fixer.apt_repo().url().is_none()); }
#[test]
fn test_apt_repo_accessor() {
let td = tempfile::tempdir().unwrap();
let apt_repo = crate::simple_apt_repo::SimpleTrustedAptRepo::new(td.path().to_path_buf());
let prefs = DebianizePreferences::default();
let fixer = DebianizeFixer {
vcs_directory: PathBuf::from("/tmp/vcs"),
apt_repo,
do_build: Box::new(|_, _, _, _| unreachable!()),
preferences: &prefs,
};
let repo = fixer.apt_repo();
assert_eq!(repo.directory(), td.path());
}
#[test]
fn test_can_fix_no_dependency() {
#[derive(Debug)]
struct MockProblem;
impl std::fmt::Display for MockProblem {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Mock Problem")
}
}
impl Problem for MockProblem {
fn kind(&self) -> std::borrow::Cow<'_, str> {
"mock".into()
}
fn json(&self) -> serde_json::Value {
serde_json::json!({
"type": "mock",
"message": "This is a mock problem"
})
}
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
self
}
}
let td = tempfile::tempdir().unwrap();
let apt_repo = crate::simple_apt_repo::SimpleTrustedAptRepo::new(td.path().to_path_buf());
let prefs = DebianizePreferences::default();
let fixer = DebianizeFixer {
vcs_directory: PathBuf::from("/tmp/vcs"),
apt_repo,
do_build: Box::new(|_, _, _, _| unreachable!()),
preferences: &prefs,
};
let problem = MockProblem;
assert!(!fixer.can_fix(&problem));
}
#[test]
fn test_extract_branch_from_url() {
let url = url::Url::parse("https://github.com/user/repo#branch=develop").unwrap();
assert_eq!(extract_branch_from_url(&url), Some("develop".to_string()));
let url = url::Url::parse("https://github.com/user/repo?branch=feature-123").unwrap();
assert_eq!(
extract_branch_from_url(&url),
Some("feature-123".to_string())
);
let url = url::Url::parse("https://github.com/user/repo/tree/main").unwrap();
assert_eq!(extract_branch_from_url(&url), Some("main".to_string()));
let url = url::Url::parse("https://gitlab.com/user/repo/tree/release-1.0/src").unwrap();
assert_eq!(
extract_branch_from_url(&url),
Some("release-1.0".to_string())
);
let url = url::Url::parse("https://github.com/user/repo").unwrap();
assert_eq!(extract_branch_from_url(&url), None);
}
}