rust/hg-core/src/copy_tracing.rs
changeset 45986 cc759d3db1e8
parent 45979 46a16b2c082d
child 45987 8b99c473aae2
--- a/rust/hg-core/src/copy_tracing.rs	Sun Nov 29 00:05:50 2020 +0100
+++ b/rust/hg-core/src/copy_tracing.rs	Tue Apr 21 15:00:32 2020 +0200
@@ -1,6 +1,7 @@
 use crate::utils::hg_path::HgPathBuf;
 use crate::Revision;
 
+use im_rc::ordmap::DiffItem;
 use im_rc::ordmap::OrdMap;
 
 use std::collections::HashMap;
@@ -8,7 +9,7 @@
 
 pub type PathCopies = HashMap<HgPathBuf, HgPathBuf>;
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq)]
 struct TimeStampedPathCopy {
     /// revision at which the copy information was added
     rev: Revision,
@@ -199,70 +200,116 @@
     changes: &ChangedFiles,
     is_ancestor: &impl Fn(Revision, Revision) -> bool,
 ) -> TimeStampedPathCopies {
-    let mut result = minor.clone();
-    for (dest, src_major) in major {
-        let overwrite;
-        if let Some(src_minor) = minor.get(&dest) {
-            {
+    if minor.is_empty() {
+        return major;
+    } else if major.is_empty() {
+        return minor;
+    }
+    let mut override_minor = Vec::new();
+    let mut override_major = Vec::new();
+
+    let mut to_major = |k: &HgPathBuf, v: &TimeStampedPathCopy| {
+        override_major.push((k.clone(), v.clone()))
+    };
+    let mut to_minor = |k: &HgPathBuf, v: &TimeStampedPathCopy| {
+        override_minor.push((k.clone(), v.clone()))
+    };
+
+    // The diff function leverage detection of the identical subpart if minor
+    // and major has some common ancestors. This make it very fast is most
+    // case.
+    //
+    // In case where the two map are vastly different in size, the current
+    // approach is still slowish because the iteration will iterate over
+    // all the "exclusive" content of the larger on. This situation can be
+    // frequent when the subgraph of revision we are processing has a lot
+    // of roots. Each roots adding they own fully new map to the mix (and
+    // likely a small map, if the path from the root to the "main path" is
+    // small.
+    //
+    // We could do better by detecting such situation and processing them
+    // differently.
+    for d in minor.diff(&major) {
+        match d {
+            DiffItem::Add(k, v) => to_minor(k, v),
+            DiffItem::Remove(k, v) => to_major(k, v),
+            DiffItem::Update { old, new } => {
+                let (dest, src_major) = new;
+                let (_, src_minor) = old;
+                let mut pick_minor = || (to_major(dest, src_minor));
+                let mut pick_major = || (to_minor(dest, src_major));
                 if src_major.path == src_minor.path {
-                    // we have the same value, no need to battle;
+                    // we have the same value, but from other source;
                     if src_major.rev == src_minor.rev {
                         // If the two entry are identical, no need to do
-                        // anything
-                        overwrite = false;
+                        // anything (but diff should not have yield them)
+                        unreachable!();
                     } else if is_ancestor(src_major.rev, src_minor.rev) {
-                        overwrite = false;
+                        pick_minor();
                     } else {
-                        overwrite = true;
+                        pick_major();
                     }
                 } else if src_major.rev == src_minor.rev {
                     // We cannot get copy information for both p1 and p2 in the
                     // same rev. So this is the same value.
-                    overwrite = false;
+                    unreachable!();
                 } else if src_major.path.is_none()
-                    && changes.salvaged.contains(&dest)
+                    && changes.salvaged.contains(dest)
                 {
                     // If the file is "deleted" in the major side but was
                     // salvaged by the merge, we keep the minor side alive
-                    overwrite = false;
+                    pick_minor();
                 } else if src_minor.path.is_none()
-                    && changes.salvaged.contains(&dest)
+                    && changes.salvaged.contains(dest)
                 {
                     // If the file is "deleted" in the minor side but was
                     // salvaged by the merge, unconditionnaly preserve the
                     // major side.
-                    overwrite = true;
-                } else if changes.merged.contains(&dest) {
+                    pick_major();
+                } else if changes.merged.contains(dest) {
                     // If the file was actively merged, copy information from
                     // each side might conflict.  The major side will win such
                     // conflict.
-                    overwrite = true;
+                    pick_major();
                 } else if is_ancestor(src_major.rev, src_minor.rev) {
                     // If the minor side is strictly newer than the major side,
                     // it should be kept.
-                    overwrite = false;
+                    pick_minor();
                 } else if src_major.path.is_some() {
                     // without any special case, the "major" value win other
                     // the "minor" one.
-                    overwrite = true;
+                    pick_major();
                 } else if is_ancestor(src_minor.rev, src_major.rev) {
                     // the "major" rev is a direct ancestors of "minor", any
                     // different value should overwrite
-                    overwrite = true;
+                    pick_major();
                 } else {
                     // major version is None (so the file was deleted on that
                     // branch) and that branch is independant (neither minor
                     // nor major is an ancestors of the other one.) We preserve
                     // the new information about the new file.
-                    overwrite = false;
+                    pick_minor();
                 }
             }
+        };
+    }
+
+    let updates;
+    let mut result;
+    if override_major.is_empty() {
+        result = major
+    } else if override_minor.is_empty() {
+        result = minor
+    } else {
+        if override_minor.len() < override_major.len() {
+            updates = override_minor;
+            result = minor;
         } else {
-            // minor had no value
-            overwrite = true;
+            updates = override_major;
+            result = major;
         }
-        if overwrite {
-            result.insert(dest, src_major);
+        for (k, v) in updates {
+            result.insert(k, v);
         }
     }
     result