git: Add support for opening git worktrees (#20164)
This adds support for [git worktrees](https://matklad.github.io/2024/07/25/git-worktrees.html). It fixes the errors that show up (git blame not working) and actually adds support for detecting git changes in a `.git` folder that's outside of our path (and not even in the ancestor chain of our root path). (While working on this we discovered that our `.gitignore` handling is not 100% correct. For example: we do stop processing `.gitignore` files once we found a `.git` repository and don't go further up the ancestors, which is correct, but then we also don't take into account the `excludesFile` that a user might have configured, see: https://git-scm.com/docs/gitignore) Closes https://github.com/zed-industries/zed/issues/19842 Closes https://github.com/zed-industries/zed/issues/4670 Release Notes: - Added support for git worktrees. Zed can now open git worktrees and the git status in them is correctly handled. --------- Co-authored-by: Antonio <antonio@zed.dev> Co-authored-by: Bennet <bennet@zed.dev>
This commit is contained in:
parent
3f777f0c68
commit
bd03dea296
8 changed files with 337 additions and 205 deletions
121
crates/fs/src/linux_watcher.rs
Normal file
121
crates/fs/src/linux_watcher.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
use notify::EventKind;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::{PathEvent, PathEventKind, Watcher};
|
||||
|
||||
pub struct LinuxWatcher {
|
||||
tx: smol::channel::Sender<()>,
|
||||
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
|
||||
}
|
||||
|
||||
impl LinuxWatcher {
|
||||
pub fn new(
|
||||
tx: smol::channel::Sender<()>,
|
||||
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
tx,
|
||||
pending_path_events,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Watcher for LinuxWatcher {
|
||||
fn add(&self, path: &std::path::Path) -> gpui::Result<()> {
|
||||
let root_path = path.to_path_buf();
|
||||
|
||||
let tx = self.tx.clone();
|
||||
let pending_paths = self.pending_path_events.clone();
|
||||
|
||||
use notify::Watcher;
|
||||
|
||||
global({
|
||||
|g| {
|
||||
g.add(move |event: ¬ify::Event| {
|
||||
let kind = match event.kind {
|
||||
EventKind::Create(_) => Some(PathEventKind::Created),
|
||||
EventKind::Modify(_) => Some(PathEventKind::Changed),
|
||||
EventKind::Remove(_) => Some(PathEventKind::Removed),
|
||||
_ => None,
|
||||
};
|
||||
let mut path_events = event
|
||||
.paths
|
||||
.iter()
|
||||
.filter_map(|event_path| {
|
||||
event_path.starts_with(&root_path).then(|| PathEvent {
|
||||
path: event_path.clone(),
|
||||
kind,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !path_events.is_empty() {
|
||||
path_events.sort();
|
||||
let mut pending_paths = pending_paths.lock();
|
||||
if pending_paths.is_empty() {
|
||||
tx.try_send(()).ok();
|
||||
}
|
||||
util::extend_sorted(
|
||||
&mut *pending_paths,
|
||||
path_events,
|
||||
usize::MAX,
|
||||
|a, b| a.path.cmp(&b.path),
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
})?;
|
||||
|
||||
global(|g| {
|
||||
g.inotify
|
||||
.lock()
|
||||
.watch(path, notify::RecursiveMode::NonRecursive)
|
||||
})??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&self, path: &std::path::Path) -> gpui::Result<()> {
|
||||
use notify::Watcher;
|
||||
Ok(global(|w| w.inotify.lock().unwatch(path))??)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GlobalWatcher {
|
||||
// two mutexes because calling inotify.add triggers an inotify.event, which needs watchers.
|
||||
pub(super) inotify: Mutex<notify::INotifyWatcher>,
|
||||
pub(super) watchers: Mutex<Vec<Box<dyn Fn(¬ify::Event) + Send + Sync>>>,
|
||||
}
|
||||
|
||||
impl GlobalWatcher {
|
||||
pub(super) fn add(&self, cb: impl Fn(¬ify::Event) + Send + Sync + 'static) {
|
||||
self.watchers.lock().push(Box::new(cb))
|
||||
}
|
||||
}
|
||||
|
||||
static INOTIFY_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> = OnceLock::new();
|
||||
|
||||
fn handle_event(event: Result<notify::Event, notify::Error>) {
|
||||
let Some(event) = event.log_err() else { return };
|
||||
global::<()>(move |watcher| {
|
||||
for f in watcher.watchers.lock().iter() {
|
||||
f(&event)
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
pub fn global<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
|
||||
let result = INOTIFY_INSTANCE.get_or_init(|| {
|
||||
notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher {
|
||||
inotify: Mutex::new(file_watcher),
|
||||
watchers: Default::default(),
|
||||
})
|
||||
});
|
||||
match result {
|
||||
Ok(g) => Ok(f(g)),
|
||||
Err(e) => Err(anyhow::anyhow!("{}", e)),
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue