1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
//! Tree merging.
use crate::branch::PyBranch;
use crate::graph::Graph;
use crate::hooks::HookDict;
use crate::transform::TreeTransform;
use crate::tree::PyTree;
use crate::RevisionId;
use pyo3::import_exception;
use pyo3::prelude::*;
use pyo3::types::PyDict;
import_exception!(breezy.errors, UnrelatedBranches);
/// Errors that can occur during merge operations.
pub enum Error {
/// Error indicating that the branches being merged are unrelated.
///
/// This occurs when the branches have no common ancestor.
UnrelatedBranches,
}
impl From<PyErr> for Error {
fn from(e: PyErr) -> Self {
Python::attach(|py| {
if e.is_instance_of::<UnrelatedBranches>(py) {
Error::UnrelatedBranches
} else {
panic!("unexpected error: {:?}", e)
}
})
}
}
/// Represents a merge operation between two branches.
///
/// This struct provides methods to configure and perform merges between branches,
/// including finding the base revision, setting merge parameters, and executing the merge.
pub struct Merger(Py<PyAny>);
/// Types of merge algorithms that can be used.
pub enum MergeType {
/// Three-way merge algorithm.
///
/// This is the standard merge algorithm that uses a common base revision
/// and the two branches to be merged.
Merge3,
}
impl From<Py<PyAny>> for Merger {
fn from(obj: Py<PyAny>) -> Self {
Merger(obj)
}
}
impl Merger {
/// Create a new merger for merging into a tree.
///
/// # Arguments
///
/// * `branch` - The branch to merge from
/// * `this_tree` - The tree to merge into
/// * `revision_graph` - The graph of revisions to use for finding common ancestors
///
/// # Returns
///
/// A new Merger object
pub fn new<T: PyTree>(branch: &dyn PyBranch, this_tree: &T, revision_graph: &Graph) -> Self {
Python::attach(|py| {
let m = py.import("breezy.merge").unwrap();
let cls = m.getattr("Merger").unwrap();
let kwargs = PyDict::new(py);
kwargs
.set_item("this_tree", this_tree.to_object(py))
.unwrap();
kwargs
.set_item("revision_graph", revision_graph.as_pyobject())
.unwrap();
let merger = cls.call((branch.to_object(py),), Some(&kwargs)).unwrap();
Merger(merger.into())
})
}
/// Find the base revision for the merge.
///
/// # Returns
///
/// The base revision ID if found, or None if the branches are unrelated
pub fn find_base(&self) -> Result<Option<RevisionId>, crate::error::Error> {
Python::attach(|py| match self.0.call_method0(py, "find_base") {
Ok(_py_obj) => Ok(self
.0
.getattr(py, "base_rev_id")
.unwrap()
.extract(py)
.unwrap()),
Err(err) => {
if err.is_instance_of::<UnrelatedBranches>(py) {
Ok(None)
} else {
Err(err)
}
}
})
.map_err(Into::into)
}
/// Set the other revision to merge.
///
/// # Arguments
///
/// * `other_revision` - The revision ID to merge
/// * `other_branch` - The branch containing the revision
///
/// # Returns
///
/// Ok(()) on success, or an error if the operation fails
pub fn set_other_revision(
&mut self,
other_revision: &RevisionId,
other_branch: &dyn PyBranch,
) -> Result<(), crate::error::Error> {
Python::attach(|py| {
self.0.call_method1(
py,
"set_other_revision",
(other_revision.clone(), other_branch.to_object(py)),
)?;
Ok(())
})
}
/// Set the base revision for the merge.
///
/// # Arguments
///
/// * `base_revision` - The base revision ID to use
/// * `base_branch` - The branch containing the base revision
///
/// # Returns
///
/// Ok(()) on success, or an error if the operation fails
pub fn set_base_revision(
&mut self,
base_revision: &RevisionId,
base_branch: &dyn PyBranch,
) -> Result<(), crate::error::Error> {
Python::attach(|py| {
self.0.call_method1(
py,
"set_base_revision",
(base_revision.clone(), base_branch.to_object(py)),
)?;
Ok(())
})
}
/// Set the merge algorithm to use.
///
/// # Arguments
///
/// * `merge_type` - The merge algorithm to use
pub fn set_merge_type(&mut self, merge_type: MergeType) {
Python::attach(|py| {
let m = py.import("breezy.merge").unwrap();
let merge_type = match merge_type {
MergeType::Merge3 => m.getattr("Merge3Merger").unwrap(),
};
self.0.setattr(py, "merge_type", merge_type).unwrap();
})
}
/// Create a submerger to execute the merge.
///
/// # Returns
///
/// A Submerger object that can perform the actual merge
pub fn make_merger(&self) -> Result<Submerger, crate::error::Error> {
Python::attach(|py| {
let merger = self.0.call_method0(py, "make_merger")?;
Ok(Submerger(merger))
})
}
/// Create a merger from specific revision IDs.
///
/// # Arguments
///
/// * `other_tree` - The tree to merge from
/// * `other_branch` - The branch containing the revision to merge
/// * `other` - The revision ID to merge
/// * `tree_branch` - The branch containing the tree to merge into
///
/// # Returns
///
/// A new Merger object, or an error if the operation fails
pub fn from_revision_ids<T: PyTree>(
other_tree: &T,
other_branch: &dyn PyBranch,
other: &RevisionId,
tree_branch: &dyn PyBranch,
) -> Result<Self, Error> {
Python::attach(|py| {
let m = py.import("breezy.merge").unwrap();
let cls = m.getattr("Merger").unwrap();
let kwargs = PyDict::new(py);
kwargs
.set_item("other_branch", other_branch.to_object(py))
.unwrap();
kwargs.set_item("other", other.clone()).unwrap();
kwargs
.set_item("tree_branch", tree_branch.to_object(py))
.unwrap();
let merger = cls.call_method(
"from_revision_ids",
(other_tree.to_object(py),),
Some(&kwargs),
)?;
Ok(Merger(merger.into()))
})
}
}
/// Performs the actual merge operation.
///
/// This struct is created by the Merger.make_merger() method and provides
/// methods to execute the merge and create transformations.
pub struct Submerger(Py<PyAny>);
impl Submerger {
/// Create a preview transformation of the merge.
///
/// This allows inspecting the changes that would be made by the merge
/// without actually applying them to the working tree.
///
/// # Returns
///
/// A TreeTransform object representing the merge changes
pub fn make_preview_transform(&self) -> Result<TreeTransform, crate::error::Error> {
Python::attach(|py| {
let transform = self.0.call_method0(py, "make_preview_transform")?;
Ok(TreeTransform::from(transform))
})
}
}
lazy_static::lazy_static! {
/// Hooks that are called during merge operations.
pub static ref MERGE_HOOKS: HookDict = HookDict::new("breezy.merge", "Merger", "hooks");
}