Implement new recursive change-count matching algorithm for instance reconciliation

- Introduced a comprehensive rewrite of the matching algorithm, replacing the previous multi-pass approach with a recursive change-count scoring function and greedy assignment strategy.
- Updated the `hydrate.lua` and `matching.lua` files to utilize the new algorithm, enhancing the efficiency of instance matching during hydration.
- Documented the new algorithm's structure, including fast-paths for Ref pins and (Name, ClassName) grouping, which significantly improve matching accuracy and performance.
- Ensured that the new implementation maintains compatibility with existing systems while providing a more robust solution for handling ambiguous instances.

These changes enhance the clarity and efficiency of the reconciliation process, paving the way for improved project management and user experience.
This commit is contained in:
Joe 2026-02-17 03:54:06 -06:00
parent 4cb45cd607
commit 1a1d9ce886
14 changed files with 1047 additions and 1108 deletions

View file

@ -1118,10 +1118,7 @@ impl JobThreadContext {
continue;
}
let sibling_fs = tree.filesystem_name_for(sibling_ref);
let sibling_stem = sibling_fs
.split('.')
.next()
.unwrap_or(&sibling_fs);
let sibling_stem = sibling_fs.split('.').next().unwrap_or(&sibling_fs);
let sibling_base = match parse_dedup_suffix(sibling_stem) {
Some((b, _)) => b,
None => sibling_stem,
@ -1136,9 +1133,7 @@ impl JobThreadContext {
}
// Determine extension from the removed instance's FS name
let extension = removed_fs_name
.find('.')
.map(|i| &removed_fs_name[i + 1..]);
let extension = removed_fs_name.find('.').map(|i| &removed_fs_name[i + 1..]);
// Get the parent directory path
let parent_dir = {
@ -1180,26 +1175,17 @@ impl JobThreadContext {
self.unsuppress_path(&to);
} else {
// Update ref paths if the renamed instance has refs
let old_ref_segment = from
.file_name()
.and_then(|f| f.to_str())
.unwrap_or("");
let new_ref_segment = to
.file_name()
.and_then(|f| f.to_str())
.unwrap_or("");
let old_ref_segment =
from.file_name().and_then(|f| f.to_str()).unwrap_or("");
let new_ref_segment =
to.file_name().and_then(|f| f.to_str()).unwrap_or("");
if old_ref_segment != new_ref_segment {
// Build the old and new ref path prefixes
let parent_path =
crate::ref_target_path_from_tree(&tree, parent_ref);
let old_prefix =
format!("{}/{}", parent_path, old_ref_segment);
let new_prefix =
format!("{}/{}", parent_path, new_ref_segment);
self.update_ref_paths_after_rename(
&old_prefix,
&new_prefix,
);
let old_prefix = format!("{}/{}", parent_path, old_ref_segment);
let new_prefix = format!("{}/{}", parent_path, new_ref_segment);
self.update_ref_paths_after_rename(&old_prefix, &new_prefix);
}
}
}