mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
Refactor syncback process to improve clean mode handling and directory management
- Removed the ensure_project_paths_exist function, streamlining the syncback process by directly managing directory creation and cleanup within the syncback command. - Enhanced error handling for sourcemap generation, logging warnings when clean mode removes directories that only contain meta files. - Updated the logic for middleware selection in clean mode to better handle directory and script transitions based on the filesystem structure. - Added utility functions to ensure base project directories exist before syncback, addressing the need for directory structure in clean mode. - Improved test coverage for clean mode scenarios, ensuring that directory existence checks are performed and documented.
This commit is contained in:
parent
1f476d6cc9
commit
1534a54808
6 changed files with 229 additions and 104 deletions
|
|
@ -28,52 +28,6 @@ use super::{
|
|||
GlobalOptions,
|
||||
};
|
||||
|
||||
/// In clean mode, ensures all $path directories exist before loading the project.
|
||||
/// This allows syncback to restore deleted directories.
|
||||
fn ensure_project_paths_exist(project_path: &Path, vfs: &Vfs) -> anyhow::Result<()> {
|
||||
// Load the project file (handles both file and directory paths)
|
||||
let project = Project::load_initial_project(vfs, project_path)
|
||||
.with_context(|| format!("Failed to load project file: {}", project_path.display()))?;
|
||||
|
||||
let base_path = project.folder_location();
|
||||
|
||||
// Recursively find all $path entries in the project tree
|
||||
fn collect_paths(node: &crate::project::ProjectNode, base: &Path, paths: &mut Vec<PathBuf>) {
|
||||
if let Some(ref path_node) = node.path {
|
||||
let full_path = if path_node.path().is_absolute() {
|
||||
path_node.path().to_path_buf()
|
||||
} else {
|
||||
base.join(path_node.path())
|
||||
};
|
||||
paths.push(full_path);
|
||||
}
|
||||
for child in node.children.values() {
|
||||
collect_paths(child, base, paths);
|
||||
}
|
||||
}
|
||||
|
||||
let mut paths_to_check = Vec::new();
|
||||
collect_paths(&project.tree, base_path, &mut paths_to_check);
|
||||
|
||||
// Create missing directories
|
||||
for path in paths_to_check {
|
||||
if !path.exists() {
|
||||
log::debug!(
|
||||
"Clean mode: creating missing $path directory: {}",
|
||||
path.display()
|
||||
);
|
||||
std::fs::create_dir_all(&path).with_context(|| {
|
||||
format!(
|
||||
"Failed to create directory for $path: {}",
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const UNKNOWN_INPUT_KIND_ERR: &str = "Could not detect what kind of file was inputted. \
|
||||
Expected input file to end in .rbxl, .rbxlx, .rbxm, or .rbxmx.";
|
||||
|
||||
|
|
@ -185,12 +139,6 @@ impl SyncbackCommand {
|
|||
// watcher errors shouldn't terminate the process
|
||||
let vfs = Vfs::new_oneshot();
|
||||
|
||||
// In clean mode, ensure all $path directories exist before loading
|
||||
// the project. This allows syncback to restore deleted directories.
|
||||
if !self.incremental {
|
||||
ensure_project_paths_exist(&path_old, &vfs)?;
|
||||
}
|
||||
|
||||
let project_start_timer = Instant::now();
|
||||
let session_old = ServeSession::new(vfs, path_old.clone())?;
|
||||
log::debug!(
|
||||
|
|
@ -271,19 +219,35 @@ impl SyncbackCommand {
|
|||
// Generate sourcemap after successful syncback.
|
||||
// We need a fresh session that reads the post-syncback filesystem state,
|
||||
// since session_old contains the pre-syncback tree.
|
||||
//
|
||||
// Note: This may fail if clean mode removed directories that the project
|
||||
// references. This is expected for projects that have meta-only directories
|
||||
// (e.g., `$path` pointing to a folder with only init.meta.json5). In such
|
||||
// cases, we log a warning but don't fail the syncback.
|
||||
let sourcemap_path = base_path.join("sourcemap.json");
|
||||
let sourcemap_vfs = Vfs::new_oneshot();
|
||||
let sourcemap_session = ServeSession::new(sourcemap_vfs, path_old.clone())?;
|
||||
write_sourcemap(
|
||||
&sourcemap_session,
|
||||
Some(&sourcemap_path),
|
||||
filter_nothing,
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
log::info!("Generated sourcemap at {}", sourcemap_path.display());
|
||||
// Avoid expensive drop
|
||||
forget(sourcemap_session);
|
||||
match ServeSession::new(sourcemap_vfs, path_old.clone()) {
|
||||
Ok(sourcemap_session) => {
|
||||
write_sourcemap(
|
||||
&sourcemap_session,
|
||||
Some(&sourcemap_path),
|
||||
filter_nothing,
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
log::info!("Generated sourcemap at {}", sourcemap_path.display());
|
||||
// Avoid expensive drop
|
||||
forget(sourcemap_session);
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!(
|
||||
"Could not generate sourcemap after syncback: {}. \
|
||||
This can happen when clean mode removes directories that \
|
||||
only contain meta files.",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh git index if in a git repository
|
||||
refresh_git_index(base_path);
|
||||
|
|
|
|||
|
|
@ -427,7 +427,7 @@ pub fn syncback_project<'sync>(
|
|||
let inferred_middleware = match new_inst.class.as_str() {
|
||||
"ModuleScript" => Middleware::ModuleScriptDir,
|
||||
"Script" => Middleware::ServerScriptDir,
|
||||
"LocalScript" => Middleware::ClientScriptDir,
|
||||
"LocalScript" => Middleware::LocalScriptDir,
|
||||
"Folder" => Middleware::Dir,
|
||||
// For other classes, default to Dir
|
||||
_ => Middleware::Dir,
|
||||
|
|
@ -450,24 +450,45 @@ pub fn syncback_project<'sync>(
|
|||
}
|
||||
};
|
||||
|
||||
// In clean mode, when the filesystem has a directory (Dir middleware)
|
||||
// but the new instance is a script, we need to use the appropriate
|
||||
// script-dir middleware to create the init file and restore the script.
|
||||
if !snapshot.data.is_incremental() && middleware == Middleware::Dir {
|
||||
let script_middleware = match new_inst.class.as_str() {
|
||||
"ModuleScript" => Some(Middleware::ModuleScriptDir),
|
||||
"Script" => Some(Middleware::ServerScriptDir),
|
||||
"LocalScript" => Some(Middleware::ClientScriptDir),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(new_middleware) = script_middleware {
|
||||
// In clean mode, we may need to override middleware when the filesystem
|
||||
// structure doesn't match the new instance type.
|
||||
if !snapshot.data.is_incremental() {
|
||||
// Case 1: Filesystem has a directory (Dir middleware) but new instance
|
||||
// is a script - use script-dir middleware to create the init file.
|
||||
if middleware == Middleware::Dir {
|
||||
let script_middleware = match new_inst.class.as_str() {
|
||||
"ModuleScript" => Some(Middleware::ModuleScriptDir),
|
||||
"Script" => Some(Middleware::ServerScriptDir),
|
||||
"LocalScript" => Some(Middleware::LocalScriptDir),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(new_middleware) = script_middleware {
|
||||
log::debug!(
|
||||
"Clean mode: overriding Dir->{:?} middleware for {} (class {})",
|
||||
new_middleware,
|
||||
full_path.display(),
|
||||
new_inst.class
|
||||
);
|
||||
middleware = new_middleware;
|
||||
}
|
||||
}
|
||||
|
||||
// Case 2: Filesystem has a script-dir (init.luau exists) but new instance
|
||||
// is a Folder - use Dir middleware. The init file will be removed as orphan.
|
||||
let is_script_dir_middleware = matches!(
|
||||
middleware,
|
||||
Middleware::ModuleScriptDir
|
||||
| Middleware::ServerScriptDir
|
||||
| Middleware::ClientScriptDir
|
||||
| Middleware::LocalScriptDir
|
||||
);
|
||||
if is_script_dir_middleware && new_inst.class == "Folder" {
|
||||
log::debug!(
|
||||
"Clean mode: overriding Dir->{:?} middleware for {} (class {})",
|
||||
new_middleware,
|
||||
full_path.display(),
|
||||
new_inst.class
|
||||
"Clean mode: overriding {:?}->Dir middleware for {} (class Folder)",
|
||||
middleware,
|
||||
full_path.display()
|
||||
);
|
||||
middleware = new_middleware;
|
||||
middleware = Middleware::Dir;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -589,19 +589,16 @@ pub fn syncback_loop_with_stats(
|
|||
for old_path in &existing_paths {
|
||||
let old_path_norm = normalize_existing_path(old_path);
|
||||
|
||||
// Never remove the project file itself
|
||||
// Never remove the ROOT project file itself
|
||||
if old_path_norm == project_file {
|
||||
log::trace!("Skipping project file: {}", old_path.display());
|
||||
log::trace!("Skipping root project file: {}", old_path.display());
|
||||
continue;
|
||||
}
|
||||
// Never remove any nested project files - they define project structure
|
||||
// and may be referenced via $path from other project files
|
||||
if let Some(file_name) = old_path_norm.file_name().and_then(|n| n.to_str()) {
|
||||
if file_name.ends_with(".project.json5") || file_name.ends_with(".project.json") {
|
||||
log::trace!("Skipping nested project file: {}", old_path.display());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// In clean mode, orphan nested project files ARE removed.
|
||||
// Clean mode represents "delete everything and rebuild from rbxm" -
|
||||
// orphan project files that aren't referenced by the main project
|
||||
// should not survive. Only the root project file is protected.
|
||||
//
|
||||
// Skip if this exact path is being written to
|
||||
if added_paths.contains(&old_path_norm) {
|
||||
log::trace!("Skipping {} (exact path in added_paths)", old_path.display());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue