Refactor PatchTree tests and enhance git index change detection

- Updated the `changedProperties` structure in `PatchTree.spec.lua` to use a nested table format for `Source`, improving data consistency.
- Introduced a new `repStorageId` in `PatchTree.spec.lua` for better instance mapping.
- Refactored the `collect_tree_index_changes` function in `git.rs` to utilize a HashMap for tree entries, streamlining the detection of index changes.
- Enhanced the `flatten_tree` function to improve the collection of paths in the git repository.

These changes aim to improve the clarity and efficiency of both the PatchTree tests and the git index change detection logic.
This commit is contained in:
Joe 2026-03-01 14:50:04 -06:00
parent 380041dc2a
commit 84253019c2
3 changed files with 32 additions and 20 deletions

View file

@ -755,7 +755,7 @@ return function()
table.insert(patch.updated, { table.insert(patch.updated, {
id = id1, id = id1,
changedName = nil, changedName = nil,
changedProperties = { Source = "-- changed" }, changedProperties = { Source = { String = "-- changed" } },
}) })
local gitMetadata = { local gitMetadata = {
@ -862,7 +862,7 @@ return function()
table.insert(patch.updated, { table.insert(patch.updated, {
id = childId, id = childId,
changedProperties = { Source = "return {}" }, changedProperties = { Source = { String = "return {}" } },
}) })
local tree = PatchTree.build(patch, instanceMap, { "Property", "Current", "Incoming" }) local tree = PatchTree.build(patch, instanceMap, { "Property", "Current", "Incoming" })
@ -898,6 +898,9 @@ return function()
-- Use a service-level instance (direct child of DataModel) -- Use a service-level instance (direct child of DataModel)
local repStorage = game:GetService("ReplicatedStorage") local repStorage = game:GetService("ReplicatedStorage")
local repStorageId = HttpService:GenerateGUID(false)
instanceMap:insert(repStorageId, repStorage)
local testObj = Instance.new("Folder") local testObj = Instance.new("Folder")
testObj.Name = "TopLevel" testObj.Name = "TopLevel"
testObj.Parent = repStorage testObj.Parent = repStorage

View file

@ -190,17 +190,14 @@ fn collect_tree_index_changes(
Err(_) => return, Err(_) => return,
}; };
let mut tree_entries: HashMap<String, gix::ObjectId> = HashMap::new();
flatten_tree(repo, &tree, "", &mut tree_entries);
for entry in index.entries() { for entry in index.entries() {
let path_bstr = entry.path(index); let path_bstr = entry.path(index);
let path_str = path_bstr.to_str_lossy(); let path_str = path_bstr.to_str_lossy();
let tree_entry_id = tree match tree_entries.remove(path_str.as_ref()) {
.lookup_entry_by_path(path_str.as_ref())
.ok()
.flatten()
.map(|e| e.object_id());
match tree_entry_id {
Some(tid) if tid == entry.id => {} Some(tid) if tid == entry.id => {}
_ => { _ => {
tracked.insert(PathBuf::from(path_str.as_ref())); tracked.insert(PathBuf::from(path_str.as_ref()));
@ -208,15 +205,16 @@ fn collect_tree_index_changes(
} }
} }
collect_tree_only_paths(repo, &tree, "", index, tracked); for path in tree_entries.into_keys() {
tracked.insert(PathBuf::from(path));
}
} }
fn collect_tree_only_paths( fn flatten_tree(
repo: &gix::Repository, repo: &gix::Repository,
tree: &gix::Tree<'_>, tree: &gix::Tree<'_>,
prefix: &str, prefix: &str,
index: &gix::index::File, out: &mut HashMap<String, gix::ObjectId>,
tracked: &mut HashSet<PathBuf>,
) { ) {
for entry in tree.iter() { for entry in tree.iter() {
let entry = match entry { let entry = match entry {
@ -233,14 +231,11 @@ fn collect_tree_only_paths(
if entry.mode().is_tree() { if entry.mode().is_tree() {
if let Ok(sub_obj) = entry.id().object() { if let Ok(sub_obj) = entry.id().object() {
if let Ok(sub_tree) = sub_obj.peel_to_tree() { if let Ok(sub_tree) = sub_obj.peel_to_tree() {
collect_tree_only_paths(repo, &sub_tree, &full_path, index, tracked); flatten_tree(repo, &sub_tree, &full_path, out);
} }
} }
} else if entry.mode().is_blob() || entry.mode().is_executable() { } else if entry.mode().is_blob() || entry.mode().is_executable() {
let path_as_bstr: &gix::bstr::BStr = full_path.as_bytes().as_bstr(); out.insert(full_path, entry.id().detach());
if index.entry_by_path(path_as_bstr).is_none() {
tracked.insert(PathBuf::from(&full_path));
}
} }
} }
} }

View file

@ -7,6 +7,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
str::FromStr, str::FromStr,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
time::Instant,
}; };
use bytes::Bytes; use bytes::Bytes;
@ -392,6 +393,8 @@ impl ApiService {
/// Get a summary of information about the server /// Get a summary of information about the server
async fn handle_api_rojo(&self) -> Response<Full<Bytes>> { async fn handle_api_rojo(&self) -> Response<Full<Bytes>> {
let handler_start = Instant::now();
let root_instance_id = self.serve_session.tree().get_root_id(); let root_instance_id = self.serve_session.tree().get_root_id();
let ignore_hidden_services = self.serve_session.ignore_hidden_services(); let ignore_hidden_services = self.serve_session.ignore_hidden_services();
@ -408,15 +411,26 @@ impl ApiService {
.serve_session .serve_session
.initial_head_commit() .initial_head_commit()
.map(|s| s.to_owned()); .map(|s| s.to_owned());
tokio::task::spawn_blocking(move || { let t = Instant::now();
let result = tokio::task::spawn_blocking(move || {
crate::git::compute_git_metadata(&tree_handle, &repo_root, initial_head.as_deref()) crate::git::compute_git_metadata(&tree_handle, &repo_root, initial_head.as_deref())
}) })
.await .await
.ok() .ok();
log::trace!(
"[TIMING] handle_api_rojo: compute_git_metadata {}ms",
t.elapsed().as_millis()
);
result
} else { } else {
None None
}; };
log::trace!(
"[TIMING] handle_api_rojo: total {}ms",
handler_start.elapsed().as_millis()
);
msgpack_ok(&ServerInfoResponse { msgpack_ok(&ServerInfoResponse {
server_version: SERVER_VERSION.to_owned(), server_version: SERVER_VERSION.to_owned(),
protocol_version: PROTOCOL_VERSION, protocol_version: PROTOCOL_VERSION,