Skip to content

Commit

Permalink
fix(core): handle created directories when watching on linux (#22980)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonathan Cammisuli <[email protected]>
  • Loading branch information
FrozenPandaz and Cammisuli authored Apr 24, 2024
1 parent df87a5e commit 12dd872
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 83 deletions.
199 changes: 125 additions & 74 deletions packages/nx/src/native/watch/types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
use napi::bindgen_prelude::*;

use std::path::PathBuf;
use crate::native::walker::nx_walker_sync;
use ignore::gitignore::GitignoreBuilder;
use ignore::Match;
use std::path::{Path, PathBuf};
use tracing::trace;
use watchexec_events::filekind::CreateKind;
use watchexec_events::filekind::FileEventKind;
use watchexec_events::filekind::ModifyKind::Name;
use watchexec_events::filekind::RenameMode;
use watchexec_events::{Event, Tag};

use crate::native::watch::utils::transform_event;
Expand All @@ -28,7 +35,7 @@ impl From<&WatchEventInternal> for WatchEvent {
fn from(value: &WatchEventInternal) -> Self {
let path = value
.path
.strip_prefix(value.origin.as_ref().expect("origin is available"))
.strip_prefix(&value.origin)
.unwrap_or(&value.path)
.display()
.to_string();
Expand All @@ -47,86 +54,130 @@ impl From<&WatchEventInternal> for WatchEvent {
pub(super) struct WatchEventInternal {
pub path: PathBuf,
pub r#type: EventType,
pub origin: Option<String>,
pub origin: String,
}

pub fn transform_event_to_watch_events(
value: &Event,
origin: &str,
) -> anyhow::Result<Vec<WatchEventInternal>> {
let transformed = transform_event(value);
let value = transformed.as_ref().unwrap_or(value);

let Some(path) = value.paths().next() else {
let error_msg = "unable to get path from the event";
trace!(?value, error_msg);
anyhow::bail!(error_msg)
};

let Some(event_kind) = value.tags.iter().find_map(|t| match t {
Tag::FileEventKind(event_kind) => Some(event_kind),
_ => None,
}) else {
let error_msg = "unable to get the file event kind";
trace!(?value, error_msg);
anyhow::bail!(error_msg)
};

let path_ref = path.0;
if path.1.is_none() && !path_ref.exists() {
Ok(vec![WatchEventInternal {
path: path_ref.into(),
r#type: EventType::delete,
origin: origin.to_owned(),
}])
} else {
#[cfg(target_os = "macos")]
{
use std::fs;
use std::os::macos::fs::MetadataExt;

let origin = origin.to_owned();
let t = fs::metadata(path_ref);
let event_type = match t {
Err(_) => EventType::delete,
Ok(t) => {
let modified_time = t.st_mtime();
let birth_time = t.st_birthtime();

// if a file is created and updated near the same time, we always get a create event
// so we need to check the timestamps to see if it was created or updated
// if the modified time is the same as birth_time then it was created
if modified_time == birth_time {
EventType::create
} else {
EventType::update
}
}
};

Ok(vec![WatchEventInternal {
path: path_ref.into(),
r#type: event_type,
origin,
}])
}

impl TryFrom<&Event> for WatchEventInternal {
type Error = anyhow::Error;

fn try_from(value: &Event) -> std::result::Result<Self, Self::Error> {
let transformed = transform_event(value);
let value = transformed.as_ref().unwrap_or(value);

let Some(path) = value.paths().next() else {
let error_msg = "unable to get path from the event";
trace!(?value, error_msg);
anyhow::bail!(error_msg)
};

let Some( event_kind ) = value
.tags
.iter()
.find_map(|t| match t {
Tag::FileEventKind(event_kind) => Some(event_kind),
_ => None,
}) else {
let error_msg = "unable to get the file event kind";
trace!(?value, error_msg);
anyhow::bail!(error_msg)
};

let path_ref = path.0;
let event_type = if path.1.is_none() && !path_ref.exists() {
EventType::delete
} else {
#[cfg(target_os = "macos")]
{
use std::fs;
use std::os::macos::fs::MetadataExt;

let t = fs::metadata(path_ref);
match t {
Err(_) => EventType::delete,
Ok(t) => {
let modified_time = t.st_mtime();
let birth_time = t.st_birthtime();

// if a file is created and updated near the same time, we always get a create event
// so we need to check the timestamps to see if it was created or updated
// if the modified time is the same as birth_time then it was created
if modified_time == birth_time {
EventType::create
} else {
EventType::update
}
#[cfg(target_os = "windows")]
{
Ok(create_watch_event_internal(origin, event_kind, path_ref))
}

#[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
{
if matches!(event_kind, FileEventKind::Create(CreateKind::Folder)) {
let mut result = vec![];

let mut gitignore_builder = GitignoreBuilder::new(origin);
let origin_path: &Path = origin.as_ref();
gitignore_builder.add(origin_path.join(".nxignore"));
let ignore = gitignore_builder.build()?;

for path in nx_walker_sync(path_ref, None) {
let path = path_ref.join(path);
let is_dir = path.is_dir();
if is_dir
|| matches!(
ignore.matched_path_or_any_parents(&path, is_dir),
Match::Ignore(_)
)
{
continue;
}
}
}

#[cfg(not(target_os = "macos"))]
{
use watchexec_events::filekind::FileEventKind;
use watchexec_events::filekind::ModifyKind::Name;
use watchexec_events::filekind::RenameMode;

match event_kind {
FileEventKind::Create(_) => EventType::create,
FileEventKind::Modify(Name(RenameMode::To)) => EventType::create,
FileEventKind::Modify(Name(RenameMode::From)) => EventType::delete,
FileEventKind::Modify(_) => EventType::update,
_ => EventType::update,
result.push(WatchEventInternal {
path,
r#type: EventType::create,
origin: origin.to_owned(),
});
}

Ok(result)
} else {
Ok(create_watch_event_internal(origin, event_kind, path_ref))
}
};
}
}

trace!(?path, ?event_kind, ?event_type, "event kind -> event type");

Ok(WatchEventInternal {
path: path.0.into(),
r#type: event_type,
origin: None,
})
}
}

fn create_watch_event_internal(
origin: &str,
event_kind: &FileEventKind,
path_ref: &Path,
) -> Vec<WatchEventInternal> {
let event_kind = match event_kind {
FileEventKind::Create(CreateKind::File) => EventType::create,
FileEventKind::Modify(Name(RenameMode::To)) => EventType::create,
FileEventKind::Modify(Name(RenameMode::From)) => EventType::delete,
FileEventKind::Modify(_) => EventType::update,
_ => EventType::update,
};

vec![WatchEventInternal {
path: path_ref.into(),
r#type: event_kind,
origin: origin.to_owned(),
}]
}
10 changes: 10 additions & 0 deletions packages/nx/src/native/watch/watch_filterer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ impl Filterer for WatchFilterer {
FileEventKind::Create(CreateKind::File) => continue,
FileEventKind::Remove(RemoveKind::File) => continue,

#[cfg(target_os = "linux")]
FileEventKind::Create(CreateKind::Folder) => continue,

#[cfg(windows)]
FileEventKind::Modify(ModifyKind::Any) => continue,
#[cfg(windows)]
Expand All @@ -92,6 +95,13 @@ impl Filterer for WatchFilterer {
path,
file_type: Some(FileType::File) | None,
} if !path.display().to_string().ends_with('~') => continue,

#[cfg(target_os = "linux")]
Tag::Path {
path: _,
file_type: Some(FileType::Dir),
} => continue,

Tag::Source(Source::Filesystem) => continue,
_ => return Ok(false),
}
Expand Down
14 changes: 5 additions & 9 deletions packages/nx/src/native/watch/watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::collections::HashMap;
use std::path::MAIN_SEPARATOR;
use std::sync::Arc;

use crate::native::watch::types::{EventType, WatchEvent, WatchEventInternal};
use crate::native::watch::types::{
transform_event_to_watch_events, EventType, WatchEvent, WatchEventInternal,
};
use crate::native::watch::watch_filterer;
use napi::bindgen_prelude::*;
use napi::threadsafe_function::{
Expand Down Expand Up @@ -118,14 +120,8 @@ impl Watcher {
let events = action
.events
.par_iter()
.filter_map(|ev| {
ev.try_into()
.map(|mut watch_event: WatchEventInternal| {
watch_event.origin = Some(origin_path.clone());
watch_event
})
.ok()
})
.filter_map(|ev| transform_event_to_watch_events(ev, &origin_path).ok())
.flatten()
.collect::<Vec<WatchEventInternal>>();

let mut group_events: HashMap<String, WatchEventInternal> = HashMap::new();
Expand Down

0 comments on commit 12dd872

Please sign in to comment.