10 use std::collections::HashMap; |
10 use std::collections::HashMap; |
11 use std::convert::TryInto; |
11 use std::convert::TryInto; |
12 |
12 |
13 pub type PathCopies = HashMap<HgPathBuf, HgPathBuf>; |
13 pub type PathCopies = HashMap<HgPathBuf, HgPathBuf>; |
14 |
14 |
15 type PathToken = HgPathBuf; |
15 type PathToken = usize; |
16 |
16 |
17 #[derive(Clone, Debug, PartialEq)] |
17 #[derive(Clone, Debug, PartialEq, Copy)] |
18 struct TimeStampedPathCopy { |
18 struct TimeStampedPathCopy { |
19 /// revision at which the copy information was added |
19 /// revision at which the copy information was added |
20 rev: Revision, |
20 rev: Revision, |
21 /// the copy source, (Set to None in case of deletion of the associated |
21 /// the copy source, (Set to None in case of deletion of the associated |
22 /// key) |
22 /// key) |
310 enum Parent { |
310 enum Parent { |
311 /// The `p1(x) → x` edge |
311 /// The `p1(x) → x` edge |
312 FirstParent, |
312 FirstParent, |
313 /// The `p2(x) → x` edge |
313 /// The `p2(x) → x` edge |
314 SecondParent, |
314 SecondParent, |
|
315 } |
|
316 |
|
317 /// A small "tokenizer" responsible of turning full HgPath into lighter |
|
318 /// PathToken |
|
319 /// |
|
320 /// Dealing with small object, like integer is much faster, so HgPath input are |
|
321 /// turned into integer "PathToken" and converted back in the end. |
|
322 #[derive(Clone, Debug, Default)] |
|
323 struct TwoWayPathMap { |
|
324 token: HashMap<HgPathBuf, PathToken>, |
|
325 path: Vec<HgPathBuf>, |
|
326 } |
|
327 |
|
328 impl TwoWayPathMap { |
|
329 fn tokenize(&mut self, path: &HgPath) -> PathToken { |
|
330 match self.token.get(path) { |
|
331 Some(a) => *a, |
|
332 None => { |
|
333 let a = self.token.len(); |
|
334 let buf = path.to_owned(); |
|
335 self.path.push(buf.clone()); |
|
336 self.token.insert(buf, a); |
|
337 a |
|
338 } |
|
339 } |
|
340 } |
|
341 |
|
342 fn untokenize(&self, token: PathToken) -> &HgPathBuf { |
|
343 assert!(token < self.path.len(), format!("Unknown token: {}", token)); |
|
344 &self.path[token] |
|
345 } |
315 } |
346 } |
316 |
347 |
317 /// Same as mercurial.copies._combine_changeset_copies, but in Rust. |
348 /// Same as mercurial.copies._combine_changeset_copies, but in Rust. |
318 /// |
349 /// |
319 /// Arguments are: |
350 /// Arguments are: |
335 is_ancestor: &A, |
366 is_ancestor: &A, |
336 ) -> PathCopies { |
367 ) -> PathCopies { |
337 let mut all_copies = HashMap::new(); |
368 let mut all_copies = HashMap::new(); |
338 let mut oracle = AncestorOracle::new(is_ancestor); |
369 let mut oracle = AncestorOracle::new(is_ancestor); |
339 |
370 |
|
371 let mut path_map = TwoWayPathMap::default(); |
|
372 |
340 for rev in revs { |
373 for rev in revs { |
341 let mut d: DataHolder<D> = DataHolder { data: None }; |
374 let mut d: DataHolder<D> = DataHolder { data: None }; |
342 let (p1, p2, changes) = rev_info(rev, &mut d); |
375 let (p1, p2, changes) = rev_info(rev, &mut d); |
343 |
376 |
344 // We will chain the copies information accumulated for the parent with |
377 // We will chain the copies information accumulated for the parent with |
410 .remove(&target_rev) |
446 .remove(&target_rev) |
411 .expect("target revision was not processed"); |
447 .expect("target revision was not processed"); |
412 let mut result = PathCopies::default(); |
448 let mut result = PathCopies::default(); |
413 for (dest, tt_source) in tt_result { |
449 for (dest, tt_source) in tt_result { |
414 if let Some(path) = tt_source.path { |
450 if let Some(path) = tt_source.path { |
415 result.insert(dest, path); |
451 let path_dest = path_map.untokenize(dest).to_owned(); |
|
452 let path_path = path_map.untokenize(path).to_owned(); |
|
453 result.insert(path_dest, path_path); |
416 } |
454 } |
417 } |
455 } |
418 result |
456 result |
419 } |
457 } |
420 |
458 |
445 } |
483 } |
446 |
484 |
447 /// Combine ChangedFiles with some existing PathCopies information and return |
485 /// Combine ChangedFiles with some existing PathCopies information and return |
448 /// the result |
486 /// the result |
449 fn add_from_changes( |
487 fn add_from_changes( |
|
488 path_map: &mut TwoWayPathMap, |
450 base_copies: &TimeStampedPathCopies, |
489 base_copies: &TimeStampedPathCopies, |
451 changes: &ChangedFiles, |
490 changes: &ChangedFiles, |
452 parent: Parent, |
491 parent: Parent, |
453 current_rev: Revision, |
492 current_rev: Revision, |
454 ) -> TimeStampedPathCopies { |
493 ) -> TimeStampedPathCopies { |
455 let mut copies = base_copies.clone(); |
494 let mut copies = base_copies.clone(); |
456 for action in changes.iter_actions(parent) { |
495 for action in changes.iter_actions(parent) { |
457 match action { |
496 match action { |
458 Action::Copied(dest, source) => { |
497 Action::Copied(path_dest, path_source) => { |
|
498 let dest = path_map.tokenize(path_dest); |
|
499 let source = path_map.tokenize(path_source); |
459 let entry; |
500 let entry; |
460 if let Some(v) = base_copies.get(source) { |
501 if let Some(v) = base_copies.get(&source) { |
461 entry = match &v.path { |
502 entry = match &v.path { |
462 Some(path) => Some((*(path)).to_owned()), |
503 Some(path) => Some((*(path)).to_owned()), |
463 None => Some(source.to_owned()), |
504 None => Some(source.to_owned()), |
464 } |
505 } |
465 } else { |
506 } else { |
473 rev: current_rev, |
514 rev: current_rev, |
474 path: entry, |
515 path: entry, |
475 }; |
516 }; |
476 copies.insert(dest.to_owned(), ttpc); |
517 copies.insert(dest.to_owned(), ttpc); |
477 } |
518 } |
478 Action::Removed(f) => { |
519 Action::Removed(deleted_path) => { |
479 // We must drop copy information for removed file. |
520 // We must drop copy information for removed file. |
480 // |
521 // |
481 // We need to explicitly record them as dropped to |
522 // We need to explicitly record them as dropped to |
482 // propagate this information when merging two |
523 // propagate this information when merging two |
483 // TimeStampedPathCopies object. |
524 // TimeStampedPathCopies object. |
484 if copies.contains_key(f.as_ref()) { |
525 let deleted = path_map.tokenize(deleted_path); |
|
526 if copies.contains_key(&deleted) { |
485 let ttpc = TimeStampedPathCopy { |
527 let ttpc = TimeStampedPathCopy { |
486 rev: current_rev, |
528 rev: current_rev, |
487 path: None, |
529 path: None, |
488 }; |
530 }; |
489 copies.insert(f.to_owned(), ttpc); |
531 copies.insert(deleted, ttpc); |
490 } |
532 } |
491 } |
533 } |
492 } |
534 } |
493 } |
535 } |
494 copies |
536 copies |
497 /// merge two copies-mapping together, minor and major |
539 /// merge two copies-mapping together, minor and major |
498 /// |
540 /// |
499 /// In case of conflict, value from "major" will be picked, unless in some |
541 /// In case of conflict, value from "major" will be picked, unless in some |
500 /// cases. See inline documentation for details. |
542 /// cases. See inline documentation for details. |
501 fn merge_copies_dict<A: Fn(Revision, Revision) -> bool>( |
543 fn merge_copies_dict<A: Fn(Revision, Revision) -> bool>( |
|
544 path_map: &TwoWayPathMap, |
502 mut minor: TimeStampedPathCopies, |
545 mut minor: TimeStampedPathCopies, |
503 mut major: TimeStampedPathCopies, |
546 mut major: TimeStampedPathCopies, |
504 changes: &ChangedFiles, |
547 changes: &ChangedFiles, |
505 oracle: &mut AncestorOracle<A>, |
548 oracle: &mut AncestorOracle<A>, |
506 ) -> TimeStampedPathCopies { |
549 ) -> TimeStampedPathCopies { |
509 // code is more settled. |
552 // code is more settled. |
510 let mut cmp_value = |
553 let mut cmp_value = |
511 |dest: &PathToken, |
554 |dest: &PathToken, |
512 src_minor: &TimeStampedPathCopy, |
555 src_minor: &TimeStampedPathCopy, |
513 src_major: &TimeStampedPathCopy| { |
556 src_major: &TimeStampedPathCopy| { |
514 compare_value(changes, oracle, dest, src_minor, src_major) |
557 compare_value( |
|
558 path_map, changes, oracle, dest, src_minor, src_major, |
|
559 ) |
515 }; |
560 }; |
516 if minor.is_empty() { |
561 if minor.is_empty() { |
517 major |
562 major |
518 } else if major.is_empty() { |
563 } else if major.is_empty() { |
519 minor |
564 minor |
639 } |
684 } |
640 |
685 |
641 /// decide which side prevails in case of conflicting values |
686 /// decide which side prevails in case of conflicting values |
642 #[allow(clippy::if_same_then_else)] |
687 #[allow(clippy::if_same_then_else)] |
643 fn compare_value<A: Fn(Revision, Revision) -> bool>( |
688 fn compare_value<A: Fn(Revision, Revision) -> bool>( |
|
689 path_map: &TwoWayPathMap, |
644 changes: &ChangedFiles, |
690 changes: &ChangedFiles, |
645 oracle: &mut AncestorOracle<A>, |
691 oracle: &mut AncestorOracle<A>, |
646 dest: &PathToken, |
692 dest: &PathToken, |
647 src_minor: &TimeStampedPathCopy, |
693 src_minor: &TimeStampedPathCopy, |
648 src_major: &TimeStampedPathCopy, |
694 src_major: &TimeStampedPathCopy, |
662 // same rev. So this is the same value. |
708 // same rev. So this is the same value. |
663 unreachable!( |
709 unreachable!( |
664 "conflict information from p1 and p2 in the same revision" |
710 "conflict information from p1 and p2 in the same revision" |
665 ); |
711 ); |
666 } else { |
712 } else { |
667 let action = changes.get_merge_case(&dest); |
713 let dest_path = path_map.untokenize(*dest); |
|
714 let action = changes.get_merge_case(dest_path); |
668 if src_major.path.is_none() && action == MergeCase::Salvaged { |
715 if src_major.path.is_none() && action == MergeCase::Salvaged { |
669 // If the file is "deleted" in the major side but was |
716 // If the file is "deleted" in the major side but was |
670 // salvaged by the merge, we keep the minor side alive |
717 // salvaged by the merge, we keep the minor side alive |
671 MergePick::Minor |
718 MergePick::Minor |
672 } else if src_minor.path.is_none() && action == MergeCase::Salvaged { |
719 } else if src_minor.path.is_none() && action == MergeCase::Salvaged { |