use breezyshim::branch::{Branch, PyBranch};
use breezyshim::error::Error as BrzError;
use breezyshim::merge::{Error as MergeError, MergeType, Merger, MERGE_HOOKS};
use breezyshim::repository::Repository;
use breezyshim::tree::WorkingTree;
use breezyshim::workingtree::GenericWorkingTree;
use breezyshim::RevisionId;
use std::collections::HashMap;
pub struct TempSprout {
pub workingtree: GenericWorkingTree,
pub tempdir: Option<tempfile::TempDir>,
}
impl TempSprout {
pub fn new(
branch: &dyn PyBranch,
additional_colocated_branches: Option<HashMap<String, String>>,
) -> Result<Self, BrzError> {
let (wt, td) = create_temp_sprout(branch, additional_colocated_branches, None, None)?;
Ok(Self {
workingtree: wt,
tempdir: td,
})
}
pub fn new_in(
branch: &dyn PyBranch,
additional_colocated_branches: Option<HashMap<String, String>>,
dir: &std::path::Path,
) -> Result<Self, BrzError> {
let (wt, tempdir) =
create_temp_sprout(branch, additional_colocated_branches, Some(dir), None)?;
Ok(Self {
workingtree: wt,
tempdir,
})
}
pub fn new_in_path(
branch: &dyn PyBranch,
additional_colocated_branches: Option<HashMap<String, String>>,
path: &std::path::Path,
) -> Result<Self, BrzError> {
let (wt, tempdir) =
create_temp_sprout(branch, additional_colocated_branches, None, Some(path))?;
Ok(Self {
workingtree: wt,
tempdir,
})
}
pub fn tree(&self) -> &dyn WorkingTree {
&self.workingtree
}
}
impl std::ops::Deref for TempSprout {
type Target = dyn WorkingTree;
fn deref(&self) -> &Self::Target {
&self.workingtree
}
}
pub fn create_temp_sprout(
branch: &dyn PyBranch,
additional_colocated_branches: Option<HashMap<String, String>>,
dir: Option<&std::path::Path>,
path: Option<&std::path::Path>,
) -> Result<(GenericWorkingTree, Option<tempfile::TempDir>), BrzError> {
let (td, path) = if let Some(path) = path {
assert!(path.is_absolute());
(None, path.to_path_buf())
} else {
let td = if let Some(dir) = dir {
tempfile::tempdir_in(dir).unwrap()
} else {
tempfile::tempdir().unwrap()
};
let path = td.path().to_path_buf();
(Some(td), path)
};
let use_stacking =
branch.format().supports_stacking() && branch.repository().format().supports_chks();
let to_url: url::Url = url::Url::from_directory_path(path).unwrap();
let to_dir =
branch
.controldir()
.sprout(to_url, Some(branch), Some(true), Some(use_stacking), None)?;
for (from_branch_name, to_branch_name) in additional_colocated_branches.unwrap_or_default() {
let controldir = branch.controldir();
match controldir.open_branch(Some(from_branch_name.as_str())) {
Ok(add_branch) => {
let local_add_branch = to_dir.create_branch(Some(to_branch_name.as_str()))?;
add_branch.push(
local_add_branch.as_ref(),
false,
None,
Some(Box::new(|_ps| true)),
)?;
}
Err(err) => {
log::warn!("Unable to clone branch {}: {}", from_branch_name, err);
return Err(err);
}
}
}
let wt = to_dir.open_workingtree()?;
Ok((wt, td))
}
pub fn merge_conflicts<B1: PyBranch, B2: PyBranch>(
main_branch: &B1,
other_branch: &B2,
other_revision: Option<&RevisionId>,
) -> Result<bool, BrzError> {
let other_revision = other_revision.map_or_else(|| other_branch.last_revision(), |r| r.clone());
let other_repository = other_branch.repository();
let graph = other_repository.get_graph();
if graph.is_ancestor(&main_branch.last_revision(), &other_revision)? {
return Ok(false);
}
other_repository.fetch(
&main_branch.repository(),
Some(&main_branch.last_revision()),
)?;
let old_file_contents_mergers = MERGE_HOOKS.get("merge_file_content").unwrap();
MERGE_HOOKS.clear("merge_file_contents").unwrap();
let other_tree = other_repository.revision_tree(&other_revision).unwrap();
let result = match Merger::from_revision_ids(
&other_tree,
other_branch,
&main_branch.last_revision(),
other_branch,
) {
Ok(mut merger) => {
merger.set_merge_type(MergeType::Merge3);
let tree_merger = merger.make_merger().unwrap();
let tt = tree_merger.make_preview_transform().unwrap();
!tt.cooked_conflicts().unwrap().is_empty()
}
Err(MergeError::UnrelatedBranches) => {
true
}
};
for hook in old_file_contents_mergers {
MERGE_HOOKS.add("merge_file_content", hook).unwrap();
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sprout() {
let base = tempfile::tempdir().unwrap();
let wt = breezyshim::controldir::create_standalone_workingtree(
base.path(),
&breezyshim::controldir::ControlDirFormat::default(),
)
.unwrap();
let revid = wt
.build_commit()
.message("Initial commit")
.allow_pointless(true)
.commit()
.unwrap();
let sprout = TempSprout::new(&wt.branch(), None).unwrap();
assert_eq!(sprout.last_revision().unwrap(), revid);
let tree = sprout.tree();
assert_eq!(tree.last_revision().unwrap(), revid);
std::mem::drop(sprout);
}
#[test]
fn test_sprout_in() {
let base = tempfile::tempdir().unwrap();
let wt = breezyshim::controldir::create_standalone_workingtree(
base.path(),
&breezyshim::controldir::ControlDirFormat::default(),
)
.unwrap();
let revid = wt
.build_commit()
.message("Initial commit")
.allow_pointless(true)
.commit()
.unwrap();
let sprout = TempSprout::new_in(&wt.branch(), None, base.path()).unwrap();
assert_eq!(sprout.last_revision().unwrap(), revid);
let tree = sprout.tree();
assert_eq!(tree.last_revision().unwrap(), revid);
std::mem::drop(sprout);
}
#[test]
fn test_sprout_in_path() {
let base = tempfile::tempdir().unwrap();
let target = tempfile::tempdir().unwrap();
let wt = breezyshim::controldir::create_standalone_workingtree(
base.path(),
&breezyshim::controldir::ControlDirFormat::default(),
)
.unwrap();
let revid = wt
.build_commit()
.message("Initial commit")
.allow_pointless(true)
.commit()
.unwrap();
let sprout = TempSprout::new_in_path(&wt.branch(), None, target.path()).unwrap();
assert_eq!(sprout.last_revision().unwrap(), revid);
let tree = sprout.tree();
assert_eq!(tree.last_revision().unwrap(), revid);
std::mem::drop(sprout);
}
}