1#![allow(missing_docs)]
16
17use std::fmt;
18use std::fmt::Display;
19
20use itertools::EitherOrBoth;
21
22use crate::backend::CommitId;
23use crate::index::Index;
24use crate::merge::trivial_merge;
25use crate::merge::Merge;
26use crate::op_store::RefTarget;
27use crate::op_store::RemoteRef;
28use crate::revset;
29
30pub fn diff_named_ref_targets<'a, 'b, K: Ord>(
34 refs1: impl IntoIterator<Item = (K, &'a RefTarget)>,
35 refs2: impl IntoIterator<Item = (K, &'b RefTarget)>,
36) -> impl Iterator<Item = (K, (&'a RefTarget, &'b RefTarget))> {
37 iter_named_pairs(
38 refs1,
39 refs2,
40 || RefTarget::absent_ref(),
41 || RefTarget::absent_ref(),
42 )
43 .filter(|(_, (target1, target2))| target1 != target2)
44}
45
46pub fn diff_named_remote_refs<'a, 'b, K: Ord>(
50 refs1: impl IntoIterator<Item = (K, &'a RemoteRef)>,
51 refs2: impl IntoIterator<Item = (K, &'b RemoteRef)>,
52) -> impl Iterator<Item = (K, (&'a RemoteRef, &'b RemoteRef))> {
53 iter_named_pairs(
54 refs1,
55 refs2,
56 || RemoteRef::absent_ref(),
57 || RemoteRef::absent_ref(),
58 )
59 .filter(|(_, (ref1, ref2))| ref1 != ref2)
60}
61
62pub fn iter_named_local_remote_refs<'a, 'b, K: Ord>(
66 refs1: impl IntoIterator<Item = (K, &'a RefTarget)>,
67 refs2: impl IntoIterator<Item = (K, &'b RemoteRef)>,
68) -> impl Iterator<Item = (K, (&'a RefTarget, &'b RemoteRef))> {
69 iter_named_pairs(
70 refs1,
71 refs2,
72 || RefTarget::absent_ref(),
73 || RemoteRef::absent_ref(),
74 )
75}
76
77fn iter_named_pairs<K: Ord, V1, V2>(
78 refs1: impl IntoIterator<Item = (K, V1)>,
79 refs2: impl IntoIterator<Item = (K, V2)>,
80 absent_ref1: impl Fn() -> V1,
81 absent_ref2: impl Fn() -> V2,
82) -> impl Iterator<Item = (K, (V1, V2))> {
83 itertools::merge_join_by(refs1, refs2, |(name1, _), (name2, _)| name1.cmp(name2)).map(
84 move |entry| match entry {
85 EitherOrBoth::Both((name, target1), (_, target2)) => (name, (target1, target2)),
86 EitherOrBoth::Left((name, target1)) => (name, (target1, absent_ref2())),
87 EitherOrBoth::Right((name, target2)) => (name, (absent_ref1(), target2)),
88 },
89 )
90}
91
92pub fn merge_ref_targets(
93 index: &dyn Index,
94 left: &RefTarget,
95 base: &RefTarget,
96 right: &RefTarget,
97) -> RefTarget {
98 if let Some(&resolved) = trivial_merge(&[left, base, right]) {
99 return resolved.clone();
100 }
101
102 let mut merge = Merge::from_vec(vec![
103 left.as_merge().clone(),
104 base.as_merge().clone(),
105 right.as_merge().clone(),
106 ])
107 .flatten()
108 .simplify();
109 if let Some(resolved) = merge.resolve_trivial() {
112 RefTarget::resolved(resolved.clone())
113 } else {
114 merge_ref_targets_non_trivial(index, &mut merge);
115 RefTarget::from_merge(merge)
118 }
119}
120
121pub fn merge_remote_refs(
122 index: &dyn Index,
123 left: &RemoteRef,
124 base: &RemoteRef,
125 right: &RemoteRef,
126) -> RemoteRef {
127 let target = merge_ref_targets(index, &left.target, &base.target, &right.target);
134 let state = *trivial_merge(&[left.state, base.state, right.state]).unwrap_or(&base.state);
137 RemoteRef { target, state }
138}
139
140fn merge_ref_targets_non_trivial(index: &dyn Index, conflict: &mut Merge<Option<CommitId>>) {
141 while let Some((remove_index, add_index)) = find_pair_to_remove(index, conflict) {
142 conflict.swap_remove(remove_index, add_index);
143 }
144}
145
146fn find_pair_to_remove(
147 index: &dyn Index,
148 conflict: &Merge<Option<CommitId>>,
149) -> Option<(usize, usize)> {
150 for (add_index1, add1) in conflict.adds().enumerate() {
153 for (add_index2, add2) in conflict.adds().enumerate().skip(add_index1 + 1) {
154 let (add_index, add_id) = match (add1, add2) {
157 (Some(id1), Some(id2)) if id1 == id2 => (add_index1, id1),
158 (Some(id1), Some(id2)) if index.is_ancestor(id1, id2) => (add_index1, id1),
159 (Some(id1), Some(id2)) if index.is_ancestor(id2, id1) => (add_index2, id2),
160 _ => continue,
161 };
162 if let Some(remove_index) = conflict.removes().position(|remove| match remove {
163 Some(id) => index.is_ancestor(id, add_id),
164 None => true, }) {
166 return Some((remove_index, add_index));
167 }
168 }
169 }
170
171 None
172}
173
174#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
179pub struct RemoteRefSymbolBuf {
180 pub name: String,
182 pub remote: String,
184}
185
186impl RemoteRefSymbolBuf {
187 pub fn as_ref(&self) -> RemoteRefSymbol<'_> {
189 RemoteRefSymbol {
190 name: &self.name,
191 remote: &self.remote,
192 }
193 }
194}
195
196#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
201pub struct RemoteRefSymbol<'a> {
202 pub name: &'a str,
204 pub remote: &'a str,
206}
207
208impl RemoteRefSymbol<'_> {
209 pub fn to_owned(self) -> RemoteRefSymbolBuf {
211 RemoteRefSymbolBuf {
212 name: self.name.to_owned(),
213 remote: self.remote.to_owned(),
214 }
215 }
216}
217
218impl From<RemoteRefSymbol<'_>> for RemoteRefSymbolBuf {
219 fn from(value: RemoteRefSymbol<'_>) -> Self {
220 value.to_owned()
221 }
222}
223
224impl PartialEq<RemoteRefSymbol<'_>> for RemoteRefSymbolBuf {
225 fn eq(&self, other: &RemoteRefSymbol) -> bool {
226 self.as_ref() == *other
227 }
228}
229
230impl PartialEq<RemoteRefSymbol<'_>> for &RemoteRefSymbolBuf {
231 fn eq(&self, other: &RemoteRefSymbol) -> bool {
232 self.as_ref() == *other
233 }
234}
235
236impl PartialEq<RemoteRefSymbolBuf> for RemoteRefSymbol<'_> {
237 fn eq(&self, other: &RemoteRefSymbolBuf) -> bool {
238 *self == other.as_ref()
239 }
240}
241
242impl PartialEq<&RemoteRefSymbolBuf> for RemoteRefSymbol<'_> {
243 fn eq(&self, other: &&RemoteRefSymbolBuf) -> bool {
244 *self == other.as_ref()
245 }
246}
247
248impl Display for RemoteRefSymbolBuf {
249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250 Display::fmt(&self.as_ref(), f)
251 }
252}
253
254impl Display for RemoteRefSymbol<'_> {
255 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256 let RemoteRefSymbol { name, remote } = self;
257 f.pad(&revset::format_remote_symbol(name, remote))
258 }
259}
260
261#[derive(Clone, Copy, Debug, Eq, PartialEq)]
263pub struct LocalAndRemoteRef<'a> {
264 pub local_target: &'a RefTarget,
265 pub remote_ref: &'a RemoteRef,
266}
267
268#[derive(Debug, PartialEq, Eq, Clone, Hash)]
269pub struct BookmarkPushUpdate {
270 pub old_target: Option<CommitId>,
271 pub new_target: Option<CommitId>,
272}
273
274#[derive(Debug, PartialEq, Eq, Clone)]
275pub enum BookmarkPushAction {
276 Update(BookmarkPushUpdate),
277 AlreadyMatches,
278 LocalConflicted,
279 RemoteConflicted,
280 RemoteUntracked,
281}
282
283pub fn classify_bookmark_push_action(targets: LocalAndRemoteRef) -> BookmarkPushAction {
286 let local_target = targets.local_target;
287 let remote_target = targets.remote_ref.tracking_target();
288 if local_target == remote_target {
289 BookmarkPushAction::AlreadyMatches
290 } else if local_target.has_conflict() {
291 BookmarkPushAction::LocalConflicted
292 } else if remote_target.has_conflict() {
293 BookmarkPushAction::RemoteConflicted
294 } else if targets.remote_ref.is_present() && !targets.remote_ref.is_tracking() {
295 BookmarkPushAction::RemoteUntracked
296 } else {
297 BookmarkPushAction::Update(BookmarkPushUpdate {
298 old_target: remote_target.as_normal().cloned(),
299 new_target: local_target.as_normal().cloned(),
300 })
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use crate::op_store::RemoteRefState;
308
309 fn new_remote_ref(target: RefTarget) -> RemoteRef {
310 RemoteRef {
311 target,
312 state: RemoteRefState::New,
313 }
314 }
315
316 fn tracking_remote_ref(target: RefTarget) -> RemoteRef {
317 RemoteRef {
318 target,
319 state: RemoteRefState::Tracking,
320 }
321 }
322
323 #[test]
324 fn test_classify_bookmark_push_action_unchanged() {
325 let commit_id1 = CommitId::from_hex("11");
326 let targets = LocalAndRemoteRef {
327 local_target: &RefTarget::normal(commit_id1.clone()),
328 remote_ref: &tracking_remote_ref(RefTarget::normal(commit_id1)),
329 };
330 assert_eq!(
331 classify_bookmark_push_action(targets),
332 BookmarkPushAction::AlreadyMatches
333 );
334 }
335
336 #[test]
337 fn test_classify_bookmark_push_action_added() {
338 let commit_id1 = CommitId::from_hex("11");
339 let targets = LocalAndRemoteRef {
340 local_target: &RefTarget::normal(commit_id1.clone()),
341 remote_ref: RemoteRef::absent_ref(),
342 };
343 assert_eq!(
344 classify_bookmark_push_action(targets),
345 BookmarkPushAction::Update(BookmarkPushUpdate {
346 old_target: None,
347 new_target: Some(commit_id1),
348 })
349 );
350 }
351
352 #[test]
353 fn test_classify_bookmark_push_action_removed() {
354 let commit_id1 = CommitId::from_hex("11");
355 let targets = LocalAndRemoteRef {
356 local_target: RefTarget::absent_ref(),
357 remote_ref: &tracking_remote_ref(RefTarget::normal(commit_id1.clone())),
358 };
359 assert_eq!(
360 classify_bookmark_push_action(targets),
361 BookmarkPushAction::Update(BookmarkPushUpdate {
362 old_target: Some(commit_id1),
363 new_target: None,
364 })
365 );
366 }
367
368 #[test]
369 fn test_classify_bookmark_push_action_updated() {
370 let commit_id1 = CommitId::from_hex("11");
371 let commit_id2 = CommitId::from_hex("22");
372 let targets = LocalAndRemoteRef {
373 local_target: &RefTarget::normal(commit_id2.clone()),
374 remote_ref: &tracking_remote_ref(RefTarget::normal(commit_id1.clone())),
375 };
376 assert_eq!(
377 classify_bookmark_push_action(targets),
378 BookmarkPushAction::Update(BookmarkPushUpdate {
379 old_target: Some(commit_id1),
380 new_target: Some(commit_id2),
381 })
382 );
383 }
384
385 #[test]
386 fn test_classify_bookmark_push_action_removed_untracked() {
387 let commit_id1 = CommitId::from_hex("11");
390 let targets = LocalAndRemoteRef {
391 local_target: RefTarget::absent_ref(),
392 remote_ref: &new_remote_ref(RefTarget::normal(commit_id1.clone())),
393 };
394 assert_eq!(
395 classify_bookmark_push_action(targets),
396 BookmarkPushAction::AlreadyMatches
397 );
398 }
399
400 #[test]
401 fn test_classify_bookmark_push_action_updated_untracked() {
402 let commit_id1 = CommitId::from_hex("11");
403 let commit_id2 = CommitId::from_hex("22");
404 let targets = LocalAndRemoteRef {
405 local_target: &RefTarget::normal(commit_id2.clone()),
406 remote_ref: &new_remote_ref(RefTarget::normal(commit_id1.clone())),
407 };
408 assert_eq!(
409 classify_bookmark_push_action(targets),
410 BookmarkPushAction::RemoteUntracked
411 );
412 }
413
414 #[test]
415 fn test_classify_bookmark_push_action_local_conflicted() {
416 let commit_id1 = CommitId::from_hex("11");
417 let commit_id2 = CommitId::from_hex("22");
418 let targets = LocalAndRemoteRef {
419 local_target: &RefTarget::from_legacy_form([], [commit_id1.clone(), commit_id2]),
420 remote_ref: &tracking_remote_ref(RefTarget::normal(commit_id1)),
421 };
422 assert_eq!(
423 classify_bookmark_push_action(targets),
424 BookmarkPushAction::LocalConflicted
425 );
426 }
427
428 #[test]
429 fn test_classify_bookmark_push_action_remote_conflicted() {
430 let commit_id1 = CommitId::from_hex("11");
431 let commit_id2 = CommitId::from_hex("22");
432 let targets = LocalAndRemoteRef {
433 local_target: &RefTarget::normal(commit_id1.clone()),
434 remote_ref: &tracking_remote_ref(RefTarget::from_legacy_form(
435 [],
436 [commit_id1, commit_id2],
437 )),
438 };
439 assert_eq!(
440 classify_bookmark_push_action(targets),
441 BookmarkPushAction::RemoteConflicted
442 );
443 }
444}