forked from rojo-rbx/rojo
Compare commits
5 Commits
891b74b135
...
4ca26efccb
| Author | SHA1 | Date | |
|---|---|---|---|
|
4ca26efccb
|
|||
|
ce0db54e0a
|
|||
|
b8106354b0
|
|||
|
c552fdc52e
|
|||
|
0dc37ac848
|
@@ -41,14 +41,41 @@ function reifyInstanceInner(unappliedPatch, deferredRefs, instanceMap, virtualIn
|
|||||||
invariant("Cannot reify an instance not present in virtualInstances\nID: {}", id)
|
invariant("Cannot reify an instance not present in virtualInstances\nID: {}", id)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Instance.new can fail if we're passing in something that can't be
|
-- Before creating a new instance, check if the parent already has an
|
||||||
-- created, like a service, something enabled with a feature flag, or
|
-- untracked child with the same Name and ClassName. This enables "late
|
||||||
-- something that requires higher security than we have.
|
-- adoption" of instances that exist in Studio but weren't in the initial
|
||||||
local createSuccess, instance = pcall(Instance.new, virtualInstance.ClassName)
|
-- Rojo tree (e.g., when using --git-since filtering). Without this,
|
||||||
|
-- newly acknowledged files would create duplicate instances.
|
||||||
|
local adoptedExisting = false
|
||||||
|
local instance = nil
|
||||||
|
|
||||||
if not createSuccess then
|
for _, child in ipairs(parentInstance:GetChildren()) do
|
||||||
addAllToPatch(unappliedPatch, virtualInstances, id)
|
local accessSuccess, name, className = pcall(function()
|
||||||
return
|
return child.Name, child.ClassName
|
||||||
|
end)
|
||||||
|
|
||||||
|
if accessSuccess
|
||||||
|
and name == virtualInstance.Name
|
||||||
|
and className == virtualInstance.ClassName
|
||||||
|
and instanceMap.fromInstances[child] == nil
|
||||||
|
then
|
||||||
|
instance = child
|
||||||
|
adoptedExisting = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not adoptedExisting then
|
||||||
|
-- Instance.new can fail if we're passing in something that can't be
|
||||||
|
-- created, like a service, something enabled with a feature flag, or
|
||||||
|
-- something that requires higher security than we have.
|
||||||
|
local createSuccess
|
||||||
|
createSuccess, instance = pcall(Instance.new, virtualInstance.ClassName)
|
||||||
|
|
||||||
|
if not createSuccess then
|
||||||
|
addAllToPatch(unappliedPatch, virtualInstances, id)
|
||||||
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: Can this fail? Previous versions of Rojo guarded against this, but
|
-- TODO: Can this fail? Previous versions of Rojo guarded against this, but
|
||||||
@@ -96,7 +123,9 @@ function reifyInstanceInner(unappliedPatch, deferredRefs, instanceMap, virtualIn
|
|||||||
reifyInstanceInner(unappliedPatch, deferredRefs, instanceMap, virtualInstances, childId, instance)
|
reifyInstanceInner(unappliedPatch, deferredRefs, instanceMap, virtualInstances, childId, instance)
|
||||||
end
|
end
|
||||||
|
|
||||||
instance.Parent = parentInstance
|
if not adoptedExisting then
|
||||||
|
instance.Parent = parentInstance
|
||||||
|
end
|
||||||
instanceMap:insert(id, instance)
|
instanceMap:insert(id, instance)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ pub struct SyncbackCommand {
|
|||||||
/// If provided, the prompt for writing to the file system is skipped.
|
/// If provided, the prompt for writing to the file system is skipped.
|
||||||
#[clap(long, short = 'y')]
|
#[clap(long, short = 'y')]
|
||||||
pub non_interactive: bool,
|
pub non_interactive: bool,
|
||||||
|
|
||||||
|
/// If provided, forces syncback to use JSON model files instead of binary
|
||||||
|
/// .rbxm files for instances that would otherwise serialize as binary.
|
||||||
|
#[clap(long)]
|
||||||
|
pub dangerously_force_json: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyncbackCommand {
|
impl SyncbackCommand {
|
||||||
@@ -104,6 +109,7 @@ impl SyncbackCommand {
|
|||||||
&mut dom_old,
|
&mut dom_old,
|
||||||
dom_new,
|
dom_new,
|
||||||
session_old.root_project(),
|
session_old.root_project(),
|
||||||
|
self.dangerously_force_json,
|
||||||
)?;
|
)?;
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Syncback finished in {:.02}s!",
|
"Syncback finished in {:.02}s!",
|
||||||
|
|||||||
@@ -83,6 +83,19 @@ pub fn snapshot_project(
|
|||||||
// file being updated.
|
// file being updated.
|
||||||
snapshot.metadata.relevant_paths.push(path.to_path_buf());
|
snapshot.metadata.relevant_paths.push(path.to_path_buf());
|
||||||
|
|
||||||
|
// When git filter is active, also register the project folder as a
|
||||||
|
// relevant path. This serves as a catch-all so that file changes
|
||||||
|
// not under any specific $path node can still walk up the directory
|
||||||
|
// tree and trigger a re-snapshot of the entire project.
|
||||||
|
if context.has_git_filter() {
|
||||||
|
if let Some(folder) = path.parent() {
|
||||||
|
let normalized = vfs
|
||||||
|
.canonicalize(folder)
|
||||||
|
.unwrap_or_else(|_| folder.to_path_buf());
|
||||||
|
snapshot.metadata.relevant_paths.push(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Some(snapshot))
|
Ok(Some(snapshot))
|
||||||
}
|
}
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
@@ -137,6 +150,26 @@ pub fn snapshot_project_node(
|
|||||||
// Take the snapshot's metadata as-is, which will be mutated later
|
// Take the snapshot's metadata as-is, which will be mutated later
|
||||||
// on.
|
// on.
|
||||||
metadata = snapshot.metadata;
|
metadata = snapshot.metadata;
|
||||||
|
} else if context.has_git_filter() {
|
||||||
|
// When the git filter is active and the $path was filtered out
|
||||||
|
// (no acknowledged files yet), we still need to register the path
|
||||||
|
// in relevant_paths. This allows the change processor to map file
|
||||||
|
// changes in this directory back to this project node instance,
|
||||||
|
// triggering a re-snapshot that will pick up newly modified files.
|
||||||
|
let normalized = vfs
|
||||||
|
.canonicalize(full_path.as_ref())
|
||||||
|
.unwrap_or_else(|_| full_path.to_path_buf());
|
||||||
|
metadata.relevant_paths.push(normalized);
|
||||||
|
|
||||||
|
// The VFS only sets up file watches via read() and read_dir(),
|
||||||
|
// not via metadata(). Since the git filter caused snapshot_from_vfs
|
||||||
|
// to return early (before read_dir was called), the VFS is not
|
||||||
|
// watching this path. We must read the directory here to ensure
|
||||||
|
// the VFS sets up a recursive watch, otherwise file change events
|
||||||
|
// will never fire and live sync won't detect modifications.
|
||||||
|
if full_path.is_dir() {
|
||||||
|
let _ = vfs.read_dir(&full_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ pub fn syncback_loop(
|
|||||||
old_tree: &mut RojoTree,
|
old_tree: &mut RojoTree,
|
||||||
mut new_tree: WeakDom,
|
mut new_tree: WeakDom,
|
||||||
project: &Project,
|
project: &Project,
|
||||||
|
force_json: bool,
|
||||||
) -> anyhow::Result<FsSnapshot> {
|
) -> anyhow::Result<FsSnapshot> {
|
||||||
let ignore_patterns = project
|
let ignore_patterns = project
|
||||||
.syncback_rules
|
.syncback_rules
|
||||||
@@ -153,6 +154,7 @@ pub fn syncback_loop(
|
|||||||
old_tree,
|
old_tree,
|
||||||
new_tree: &new_tree,
|
new_tree: &new_tree,
|
||||||
project,
|
project,
|
||||||
|
force_json,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut snapshots = vec![SyncbackSnapshot {
|
let mut snapshots = vec![SyncbackSnapshot {
|
||||||
@@ -197,7 +199,7 @@ pub fn syncback_loop(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let middleware = get_best_middleware(&snapshot);
|
let middleware = get_best_middleware(&snapshot, force_json);
|
||||||
|
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"Middleware for {inst_path} is {:?} (path is {})",
|
"Middleware for {inst_path} is {:?} (path is {})",
|
||||||
@@ -213,10 +215,14 @@ pub fn syncback_loop(
|
|||||||
let syncback = match middleware.syncback(&snapshot) {
|
let syncback = match middleware.syncback(&snapshot) {
|
||||||
Ok(syncback) => syncback,
|
Ok(syncback) => syncback,
|
||||||
Err(err) if middleware == Middleware::Dir => {
|
Err(err) if middleware == Middleware::Dir => {
|
||||||
let new_middleware = match env::var(DEBUG_MODEL_FORMAT_VAR) {
|
let new_middleware = if force_json {
|
||||||
Ok(value) if value == "1" => Middleware::Rbxmx,
|
Middleware::JsonModel
|
||||||
Ok(value) if value == "2" => Middleware::JsonModel,
|
} else {
|
||||||
_ => Middleware::Rbxm,
|
match env::var(DEBUG_MODEL_FORMAT_VAR) {
|
||||||
|
Ok(value) if value == "1" => Middleware::Rbxmx,
|
||||||
|
Ok(value) if value == "2" => Middleware::JsonModel,
|
||||||
|
_ => Middleware::Rbxm,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let file_name = snapshot
|
let file_name = snapshot
|
||||||
.path
|
.path
|
||||||
@@ -295,7 +301,7 @@ pub struct SyncbackReturn<'sync> {
|
|||||||
pub removed_children: Vec<InstanceWithMeta<'sync>>,
|
pub removed_children: Vec<InstanceWithMeta<'sync>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_best_middleware(snapshot: &SyncbackSnapshot) -> Middleware {
|
pub fn get_best_middleware(snapshot: &SyncbackSnapshot, force_json: bool) -> Middleware {
|
||||||
// At some point, we're better off using an O(1) method for checking
|
// At some point, we're better off using an O(1) method for checking
|
||||||
// equality for classes like this.
|
// equality for classes like this.
|
||||||
static JSON_MODEL_CLASSES: OnceLock<HashSet<&str>> = OnceLock::new();
|
static JSON_MODEL_CLASSES: OnceLock<HashSet<&str>> = OnceLock::new();
|
||||||
@@ -367,10 +373,18 @@ pub fn get_best_middleware(snapshot: &SyncbackSnapshot) -> Middleware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if middleware == Middleware::Rbxm {
|
if middleware == Middleware::Rbxm {
|
||||||
middleware = match env::var(DEBUG_MODEL_FORMAT_VAR) {
|
middleware = if force_json {
|
||||||
Ok(value) if value == "1" => Middleware::Rbxmx,
|
if !inst.children().is_empty() {
|
||||||
Ok(value) if value == "2" => Middleware::JsonModel,
|
Middleware::Dir
|
||||||
_ => Middleware::Rbxm,
|
} else {
|
||||||
|
Middleware::JsonModel
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match env::var(DEBUG_MODEL_FORMAT_VAR) {
|
||||||
|
Ok(value) if value == "1" => Middleware::Rbxmx,
|
||||||
|
Ok(value) if value == "2" => Middleware::JsonModel,
|
||||||
|
_ => Middleware::Rbxm,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ pub struct SyncbackData<'sync> {
|
|||||||
pub(super) old_tree: &'sync RojoTree,
|
pub(super) old_tree: &'sync RojoTree,
|
||||||
pub(super) new_tree: &'sync WeakDom,
|
pub(super) new_tree: &'sync WeakDom,
|
||||||
pub(super) project: &'sync Project,
|
pub(super) project: &'sync Project,
|
||||||
|
pub(super) force_json: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SyncbackSnapshot<'sync> {
|
pub struct SyncbackSnapshot<'sync> {
|
||||||
@@ -43,7 +44,7 @@ impl<'sync> SyncbackSnapshot<'sync> {
|
|||||||
path: PathBuf::new(),
|
path: PathBuf::new(),
|
||||||
middleware: None,
|
middleware: None,
|
||||||
};
|
};
|
||||||
let middleware = get_best_middleware(&snapshot);
|
let middleware = get_best_middleware(&snapshot, self.data.force_json);
|
||||||
let name = name_for_inst(middleware, snapshot.new_inst(), snapshot.old_inst())?;
|
let name = name_for_inst(middleware, snapshot.new_inst(), snapshot.old_inst())?;
|
||||||
snapshot.path = self.path.join(name.as_ref());
|
snapshot.path = self.path.join(name.as_ref());
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ impl<'sync> SyncbackSnapshot<'sync> {
|
|||||||
path: PathBuf::new(),
|
path: PathBuf::new(),
|
||||||
middleware: None,
|
middleware: None,
|
||||||
};
|
};
|
||||||
let middleware = get_best_middleware(&snapshot);
|
let middleware = get_best_middleware(&snapshot, self.data.force_json);
|
||||||
let name = name_for_inst(middleware, snapshot.new_inst(), snapshot.old_inst())?;
|
let name = name_for_inst(middleware, snapshot.new_inst(), snapshot.old_inst())?;
|
||||||
snapshot.path = base_path.join(name.as_ref());
|
snapshot.path = base_path.join(name.as_ref());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user