1use breezyshim::branch::{Branch, PyBranch};
3use breezyshim::error::Error as BrzError;
4use breezyshim::merge::{Error as MergeError, MergeType, Merger, MERGE_HOOKS};
5use breezyshim::repository::Repository;
6use breezyshim::tree::WorkingTree;
7use breezyshim::workingtree::GenericWorkingTree;
8use breezyshim::RevisionId;
9use std::collections::HashMap;
10
11pub struct TempSprout {
13 pub workingtree: GenericWorkingTree,
15
16 pub tempdir: Option<tempfile::TempDir>,
18}
19
20impl TempSprout {
21 pub fn new(
23 branch: &dyn PyBranch,
24 additional_colocated_branches: Option<HashMap<String, String>>,
25 ) -> Result<Self, BrzError> {
26 let (wt, td) = create_temp_sprout(branch, additional_colocated_branches, None, None)?;
27 Ok(Self {
28 workingtree: wt,
29 tempdir: td,
30 })
31 }
32
33 pub fn new_in(
35 branch: &dyn PyBranch,
36 additional_colocated_branches: Option<HashMap<String, String>>,
37 dir: &std::path::Path,
38 ) -> Result<Self, BrzError> {
39 let (wt, tempdir) =
40 create_temp_sprout(branch, additional_colocated_branches, Some(dir), None)?;
41 Ok(Self {
42 workingtree: wt,
43 tempdir,
44 })
45 }
46
47 pub fn new_in_path(
49 branch: &dyn PyBranch,
50 additional_colocated_branches: Option<HashMap<String, String>>,
51 path: &std::path::Path,
52 ) -> Result<Self, BrzError> {
53 let (wt, tempdir) =
54 create_temp_sprout(branch, additional_colocated_branches, None, Some(path))?;
55 Ok(Self {
56 workingtree: wt,
57 tempdir,
58 })
59 }
60
61 pub fn tree(&self) -> &dyn WorkingTree {
63 &self.workingtree
64 }
65}
66
67impl std::ops::Deref for TempSprout {
68 type Target = dyn WorkingTree;
69
70 fn deref(&self) -> &Self::Target {
71 &self.workingtree
72 }
73}
74
75pub fn create_temp_sprout(
79 branch: &dyn PyBranch,
80 additional_colocated_branches: Option<HashMap<String, String>>,
81 dir: Option<&std::path::Path>,
82 path: Option<&std::path::Path>,
83) -> Result<(GenericWorkingTree, Option<tempfile::TempDir>), BrzError> {
84 let (td, path) = if let Some(path) = path {
85 assert!(path.is_absolute());
87 (None, path.to_path_buf())
88 } else {
89 let td = if let Some(dir) = dir {
90 tempfile::tempdir_in(dir).unwrap()
91 } else {
92 tempfile::tempdir().unwrap()
93 };
94 let path = td.path().to_path_buf();
95 (Some(td), path)
96 };
97
98 let use_stacking =
99 branch.format().supports_stacking() && branch.repository().format().supports_chks();
100 let to_url: url::Url = url::Url::from_directory_path(path).unwrap();
101
102 let to_dir =
104 branch
105 .controldir()
106 .sprout(to_url, Some(branch), Some(true), Some(use_stacking), None)?;
107
108 for (from_branch_name, to_branch_name) in additional_colocated_branches.unwrap_or_default() {
110 let controldir = branch.controldir();
111 match controldir.open_branch(Some(from_branch_name.as_str())) {
112 Ok(add_branch) => {
113 let local_add_branch = to_dir.create_branch(Some(to_branch_name.as_str()))?;
114 add_branch.push(
115 local_add_branch.as_ref(),
116 false,
117 None,
118 Some(Box::new(|_ps| true)),
119 )?;
120 }
121 Err(err) => {
122 log::warn!("Unable to clone branch {}: {}", from_branch_name, err);
123 return Err(err);
124 }
125 }
126 }
127
128 let wt = to_dir.open_workingtree()?;
129 Ok((wt, td))
130}
131
132pub fn merge_conflicts<B1: PyBranch, B2: PyBranch>(
134 main_branch: &B1,
135 other_branch: &B2,
136 other_revision: Option<&RevisionId>,
137) -> Result<bool, BrzError> {
138 let other_revision = other_revision.map_or_else(|| other_branch.last_revision(), |r| r.clone());
139 let other_repository = other_branch.repository();
140 let graph = other_repository.get_graph();
141
142 if graph.is_ancestor(&main_branch.last_revision(), &other_revision)? {
143 return Ok(false);
144 }
145
146 other_repository.fetch(
147 &main_branch.repository(),
148 Some(&main_branch.last_revision()),
149 )?;
150
151 let old_file_contents_mergers = MERGE_HOOKS.get("merge_file_content").unwrap();
154 MERGE_HOOKS.clear("merge_file_contents").unwrap();
155
156 let other_tree = other_repository.revision_tree(&other_revision).unwrap();
157 let result = match Merger::from_revision_ids(
158 &other_tree,
159 other_branch,
160 &main_branch.last_revision(),
161 other_branch,
162 ) {
163 Ok(mut merger) => {
164 merger.set_merge_type(MergeType::Merge3);
165 let tree_merger = merger.make_merger().unwrap();
166 let tt = tree_merger.make_preview_transform().unwrap();
167 !tt.cooked_conflicts().unwrap().is_empty()
168 }
169 Err(MergeError::UnrelatedBranches) => {
170 true
173 }
174 };
175 for hook in old_file_contents_mergers {
176 MERGE_HOOKS.add("merge_file_content", hook).unwrap();
177 }
178 Ok(result)
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use breezyshim::testing::TestEnv;
185 use serial_test::serial;
186
187 #[test]
188 #[serial]
189 fn test_sprout() {
190 let _test_env = TestEnv::new();
191 let base = tempfile::tempdir().unwrap();
192 let wt = breezyshim::controldir::create_standalone_workingtree(
193 base.path(),
194 &breezyshim::controldir::ControlDirFormat::default(),
195 )
196 .unwrap();
197 let revid = wt
198 .build_commit()
199 .message("Initial commit")
200 .allow_pointless(true)
201 .commit()
202 .unwrap();
203
204 let sprout = TempSprout::new(&wt.branch(), None).unwrap();
205
206 assert_eq!(sprout.last_revision().unwrap(), revid);
207 let tree = sprout.tree();
208 assert_eq!(tree.last_revision().unwrap(), revid);
209 std::mem::drop(sprout);
210 }
211
212 #[test]
213 #[serial]
214 fn test_sprout_in() {
215 let _test_env = TestEnv::new();
216 let base = tempfile::tempdir().unwrap();
217 let wt = breezyshim::controldir::create_standalone_workingtree(
218 base.path(),
219 &breezyshim::controldir::ControlDirFormat::default(),
220 )
221 .unwrap();
222 let revid = wt
223 .build_commit()
224 .message("Initial commit")
225 .allow_pointless(true)
226 .commit()
227 .unwrap();
228
229 let sprout = TempSprout::new_in(&wt.branch(), None, base.path()).unwrap();
230
231 assert_eq!(sprout.last_revision().unwrap(), revid);
232 let tree = sprout.tree();
233 assert_eq!(tree.last_revision().unwrap(), revid);
234 std::mem::drop(sprout);
235 }
236
237 #[test]
238 #[serial]
239 fn test_sprout_in_path() {
240 let _test_env = TestEnv::new();
241 let base = tempfile::tempdir().unwrap();
242 let target = tempfile::tempdir().unwrap();
243 let wt = breezyshim::controldir::create_standalone_workingtree(
244 base.path(),
245 &breezyshim::controldir::ControlDirFormat::default(),
246 )
247 .unwrap();
248 let revid = wt
249 .build_commit()
250 .message("Initial commit")
251 .allow_pointless(true)
252 .commit()
253 .unwrap();
254
255 let sprout = TempSprout::new_in_path(&wt.branch(), None, target.path()).unwrap();
256
257 assert_eq!(sprout.last_revision().unwrap(), revid);
258 let tree = sprout.tree();
259 assert_eq!(tree.last_revision().unwrap(), revid);
260 std::mem::drop(sprout);
261 }
262}