Enhance CloneCommand and Syncback functionality

- Added a check in `CloneCommand` to prevent cloning into an existing directory, providing a clearer error message for users.
- Updated the `SyncbackCommand` to support a configurable working directory for resolving relative paths, improving flexibility in project structure.
- Refactored the commit message in the `CloneCommand` to accurately reflect the syncback operation.
- Improved path resolution logic in `SyncbackCommand` to handle both absolute and relative paths more effectively.
This commit is contained in:
Joe 2026-02-06 19:29:37 -06:00
parent 5442c02f67
commit fa5393121e
4 changed files with 40 additions and 12 deletions

View file

@ -1,4 +1,4 @@
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use anyhow::{bail, Context}; use anyhow::{bail, Context};
@ -51,6 +51,13 @@ impl CloneCommand {
name name
); );
} }
if Path::new(&folder).exists() {
bail!(
"Directory '{}' already exists. \
Remove it or use --path to specify a different location.",
folder
);
}
println!("Using folder: {folder}"); println!("Using folder: {folder}");
PathBuf::from(folder) PathBuf::from(folder)
} }
@ -68,21 +75,20 @@ impl CloneCommand {
init.run()?; init.run()?;
let project_path = path.join("default.project.json5");
let syncback = SyncbackCommand { let syncback = SyncbackCommand {
project: project_path, project: PathBuf::from("default.project.json5"),
input: PathBuf::from("Project.rbxl"), input: PathBuf::from("Project.rbxl"),
download: Some(self.placeid), download: Some(self.placeid),
list: false, list: false,
dry_run: false, dry_run: false,
interactive: false, interactive: false,
incremental: false, incremental: false,
working_dir: path.clone(),
}; };
syncback.run(global)?; syncback.run(global)?;
// Commit the full project (init + syncback) in one shot // Commit syncback result
if !skip_git { if !skip_git {
let _ = Command::new("git") let _ = Command::new("git")
.args(["add", "."]) .args(["add", "."])
@ -92,7 +98,7 @@ impl CloneCommand {
.status(); .status();
let _ = Command::new("git") let _ = Command::new("git")
.args(["commit", "-m", "init"]) .args(["commit", "-m", "syncback"])
.current_dir(&path) .current_dir(&path)
.stdout(Stdio::null()) .stdout(Stdio::null())
.stderr(Stdio::null()) .stderr(Stdio::null())

View file

@ -74,14 +74,28 @@ pub struct SyncbackCommand {
/// project layout that exactly matches the input file, removing any orphaned files. /// project layout that exactly matches the input file, removing any orphaned files.
#[clap(long, short = 'n')] #[clap(long, short = 'n')]
pub incremental: bool, pub incremental: bool,
/// Base directory for resolving relative paths (project, input).
/// Defaults to the current working directory.
#[clap(long, hide = true, default_value = ".")]
pub working_dir: PathBuf,
} }
impl SyncbackCommand { impl SyncbackCommand {
pub fn run(&self, global: GlobalOptions) -> anyhow::Result<()> { pub fn run(&self, global: GlobalOptions) -> anyhow::Result<()> {
let path_old = resolve_path(&self.project); let base = resolve_path(&self.working_dir);
let path_old = if self.project.is_absolute() {
self.project.clone()
} else {
base.join(&self.project)
};
// Determine if we need to download the input file // Determine if we need to download the input file
let resolved_input = resolve_path(&self.input); let resolved_input = if self.input.is_absolute() {
self.input.clone()
} else {
base.join(&self.input)
};
let _temp_file: Option<NamedTempFile>; let _temp_file: Option<NamedTempFile>;
// Track if we should delete the input file after successful syncback // Track if we should delete the input file after successful syncback
// (when using default Project.rbxl and file exists locally) // (when using default Project.rbxl and file exists locally)
@ -111,11 +125,11 @@ impl SyncbackCommand {
_temp_file = None; _temp_file = None;
// If using default input path, mark for deletion after success // If using default input path, mark for deletion after success
delete_input_after_syncback = if self.input.as_os_str() == "Project.rbxl" { delete_input_after_syncback = if self.input.as_os_str() == "Project.rbxl" {
Some(resolved_input.clone().into_owned()) Some(resolved_input.clone())
} else { } else {
None None
}; };
resolved_input.into_owned() resolved_input
} }
None => { None => {
// No --download flag, input file doesn't exist: auto-download // No --download flag, input file doesn't exist: auto-download

View file

@ -113,8 +113,13 @@ pub fn syncback_loop_with_stats(
// TODO: Add a better way to tell if the root of a project is a directory // TODO: Add a better way to tell if the root of a project is a directory
let skip_pruning = if let Some(path) = &project.tree.path { let skip_pruning = if let Some(path) = &project.tree.path {
let resolved = if path.path().is_absolute() {
path.path().to_path_buf()
} else {
project.folder_location().join(path.path())
};
let middleware = let middleware =
Middleware::middleware_for_path(vfs, &project.sync_rules, path.path()).unwrap(); Middleware::middleware_for_path(vfs, &project.sync_rules, &resolved).unwrap();
if let Some(middleware) = middleware { if let Some(middleware) = middleware {
middleware.is_dir() middleware.is_dir()
} else { } else {

View file

@ -38,7 +38,10 @@ impl Internable<Ref> for ReadResponse<'_> {
redactions.intern(root_id); redactions.intern(root_id);
let root_instance = self.instances.get(&root_id).unwrap(); let root_instance = self.instances.get(&root_id).unwrap();
intern_variant_refs(redactions, root_instance.properties.values().map(|v| v.as_ref())); intern_variant_refs(
redactions,
root_instance.properties.values().map(|v| v.as_ref()),
);
for &child_id in root_instance.children.iter() { for &child_id in root_instance.children.iter() {
self.intern(redactions, child_id); self.intern(redactions, child_id);