Refactor git_changed_files_impl for improved performance and clarity

- Replaced the previous implementation of tracking changes with a more efficient approach using `git status` and `git diff`, reducing the number of command calls and enhancing performance.
- Consolidated the handling of tracked and untracked files, improving code readability and maintainability.
- Updated logging to provide more detailed timing information for git operations, aiding in performance monitoring.

These changes aim to streamline git interactions and enhance the overall efficiency of the git module.
This commit is contained in:
Joe 2026-03-01 18:36:45 -06:00
parent 4846fc1f59
commit 65f10f111c
2 changed files with 59 additions and 57 deletions

View file

@ -129,3 +129,21 @@ DEBUG Prefetch I/O: read 248397 files in 702.5ms, canonicalize 322970 paths in 3
DEBUG Prefetch maps: 322970 is_file + 74573 children dirs built
DEBUG Prefetch total: 248397 files + 322970 canonical paths in 7.9s
get rid of this bulk path canonaclize where possible, it seems utterly pointless in most cases
we should still have the canonaclize go through a helper function as it is so we dont have to modify like 9000 locations, but just get rid of the system it blows.
[optimize git metadata fetching]
INFO librojo::change_processor: VFS event SUPPRESSED (API syncback echo): D:\UserGenerated\steal-a-brainrot\src\ServerScriptService\UserGenerated\Analytics\init.luau
DEBUG librojo::web::api: [TIMING] handle_api_rojo: total 0ms
DEBUG librojo::web::api: Git metadata: using 1 project prefixes: ["src"]
DEBUG librojo::git: [TIMING] compute_git_metadata: git_changed_files_impl 2106ms (2 tracked, 0 untracked)
DEBUG librojo::git: [TIMING] compute_git_metadata: git_changed_files_impl 2106ms (2 tracked, 0 untracked)
DEBUG librojo::git: [TIMING] compute_git_metadata: tree resolution 0ms (2 changed -> 2 resolved)
DEBUG librojo::git: [TIMING] compute_git_metadata: batch_check_hashes 71ms (6 refs)
DEBUG librojo::git: [TIMING] compute_git_metadata: total 2179ms (2 changed_ids, 2 script_hashes, 0 new_file_ids)
INFO roblox: [Studio] [Rojo] One-shot mode: skipping all auto-accept paths, requiring explicit confirmation
DEBUG librojo::web::api: WebSocket subscription established for session 99102267-df96-4f2d-a2c3-6ea78fe97bed
DEBUG librojo::serve_session: Tree validation skipped (VALIDATE_TREE_ON_CONNECT = false)
DEBUG librojo::web::api: WebSocket subscription closed by client
INFO roblox: [Studio] [Rojo] User selections: 0 push, 0 pull, 0 ignored
INFO roblox: [Studio] [Rojo] One-shot sync: disconnecting after initial sync complete

View file

@ -80,46 +80,58 @@ fn git_changed_files_impl(
let mut tracked = HashSet::new();
let mut untracked = HashSet::new();
let base = initial_head.unwrap_or("HEAD");
let t = Instant::now();
let mut diff_cmd = Command::new("git");
diff_cmd
.args(["diff", base, "--name-only"])
let mut status_cmd = Command::new("git");
status_cmd
.args(["status", "--porcelain", "--no-renames", "-uall"])
.current_dir(repo_root);
if !project_prefixes.is_empty() {
diff_cmd.arg("--");
status_cmd.arg("--");
for prefix in project_prefixes {
diff_cmd.arg(prefix);
status_cmd.arg(prefix);
}
}
let diff_output = diff_cmd.output().ok()?;
if diff_output.status.success() {
for line in String::from_utf8_lossy(&diff_output.stdout).lines() {
let trimmed = line.trim();
if !trimmed.is_empty() {
tracked.insert(PathBuf::from(trimmed));
let (status_output, diff_output) = if let Some(ih) = initial_head {
let mut diff_cmd = Command::new("git");
diff_cmd
.args(["diff", ih, "--name-only"])
.current_dir(repo_root);
if !project_prefixes.is_empty() {
diff_cmd.arg("--");
for prefix in project_prefixes {
diff_cmd.arg(prefix);
}
}
} else {
log::debug!("git diff {} failed (possibly no commits yet)", base);
}
let mut cached_cmd = Command::new("git");
cached_cmd
.args(["diff", "--cached", "--name-only"])
.current_dir(repo_root);
if !project_prefixes.is_empty() {
cached_cmd.arg("--");
for prefix in project_prefixes {
cached_cmd.arg(prefix);
std::thread::scope(|s| {
let diff_handle = s.spawn(move || diff_cmd.output().ok());
let status = status_cmd.output().ok();
let diff = diff_handle.join().ok().flatten();
(status, diff)
})
} else {
(status_cmd.output().ok(), None)
};
let output = status_output?;
if output.status.success() {
for line in String::from_utf8_lossy(&output.stdout).lines() {
if line.len() < 4 {
continue;
}
let xy = &line[..2];
let path = &line[3..];
if xy == "??" {
untracked.insert(PathBuf::from(path));
} else {
tracked.insert(PathBuf::from(path));
}
}
}
if let Ok(cached_output) = cached_cmd.output() {
if cached_output.status.success() {
for line in String::from_utf8_lossy(&cached_output.stdout).lines() {
if let Some(output) = diff_output {
if output.status.success() {
for line in String::from_utf8_lossy(&output.stdout).lines() {
let trimmed = line.trim();
if !trimmed.is_empty() {
tracked.insert(PathBuf::from(trimmed));
@ -128,34 +140,6 @@ fn git_changed_files_impl(
}
}
let mut ls_cmd = Command::new("git");
ls_cmd
.args(["ls-files", "--others", "--exclude-standard"])
.current_dir(repo_root);
if !project_prefixes.is_empty() {
ls_cmd.arg("--");
for prefix in project_prefixes {
ls_cmd.arg(prefix);
}
}
if let Ok(untracked_output) = ls_cmd.output() {
if untracked_output.status.success() {
for line in String::from_utf8_lossy(&untracked_output.stdout).lines() {
let trimmed = line.trim();
if !trimmed.is_empty() {
untracked.insert(PathBuf::from(trimmed));
}
}
}
}
log::debug!(
"[TIMING] compute_git_metadata: git_changed_files_impl {}ms ({} tracked, {} untracked)",
t.elapsed().as_millis(),
tracked.len(),
untracked.len()
);
Some(ChangedFiles { tracked, untracked })
}