3017 lines · 101.0 KB · Rust
RawDownload
pub mod fs_watcher; use parking_lot::Mutex;use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering};use std::time::Instant;use util::maybe; use anyhow::{Context as _, Result, anyhow};#[cfg(any(target_os = "linux", target_os = "freebsd"))]use ashpd::desktop::trash;use futures::stream::iter;use gpui::App;use gpui::BackgroundExecutor;use gpui::Global;use gpui::ReadGlobal as _;use gpui::SharedString;use std::borrow::Cow;#[cfg(unix)]use std::ffi::CString;use util::command::new_command; #[cfg(unix)]use std::os::fd::{AsFd, AsRawFd};#[cfg(unix)]use std::os::unix::ffi::OsStrExt; #[cfg(unix)]use std::os::unix::fs::{FileTypeExt, MetadataExt}; #[cfg(any(target_os = "macos", target_os = "freebsd"))]use std::mem::MaybeUninit; use async_tar::Archive;use futures::{AsyncRead, Stream, StreamExt, future::BoxFuture};use git::repository::{GitRepository, RealGitRepository};use is_executable::IsExecutable;use rope::Rope;use serde::{Deserialize, Serialize};use smol::io::AsyncWriteExt;#[cfg(feature = "test-support")]use std::path::Component;use std::{    io::{self, Write},    path::{Path, PathBuf},    pin::Pin,    sync::Arc,    time::{Duration, SystemTime, UNIX_EPOCH},};use tempfile::TempDir;use text::LineEnding; #[cfg(feature = "test-support")]mod fake_git_repo;#[cfg(feature = "test-support")]use collections::{BTreeMap, btree_map};#[cfg(feature = "test-support")]use fake_git_repo::FakeGitRepositoryState;#[cfg(feature = "test-support")]use git::{    repository::{InitialGraphCommitData, RepoPath, repo_path},    status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus},}; #[cfg(feature = "test-support")]use smol::io::AsyncReadExt;#[cfg(feature = "test-support")]use std::ffi::OsStr; pub trait Watcher: Send + Sync {    fn add(&self, path: &Path) -> Result<()>;    fn remove(&self, path: &Path) -> Result<()>;} #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]pub enum PathEventKind {    Removed,    Created,    Changed,} #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]pub struct PathEvent {    pub path: PathBuf,    pub kind: Option<PathEventKind>,} impl From<PathEvent> for PathBuf {    fn from(event: PathEvent) -> Self {        event.path    }} #[async_trait::async_trait]pub trait Fs: Send + Sync {    async fn create_dir(&self, path: &Path) -> Result<()>;    async fn create_symlink(&self, path: &Path, target: PathBuf) -> Result<()>;    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;    async fn create_file_with(        &self,        path: &Path,        content: Pin<&mut (dyn AsyncRead + Send)>,    ) -> Result<()>;    async fn extract_tar_file(        &self,        path: &Path,        content: Archive<Pin<&mut (dyn AsyncRead + Send)>>,    ) -> Result<()>;    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()>;    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>;    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>;    async fn trash_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {        self.remove_dir(path, options).await    }    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;    async fn trash_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {        self.remove_file(path, options).await    }    async fn open_handle(&self, path: &Path) -> Result<Arc<dyn FileHandle>>;    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read + Send + Sync>>;    async fn load(&self, path: &Path) -> Result<String> {        Ok(String::from_utf8(self.load_bytes(path).await?)?)    }    async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>>;    async fn atomic_write(&self, path: PathBuf, text: String) -> Result<()>;    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;    async fn write(&self, path: &Path, content: &[u8]) -> Result<()>;    async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;    async fn is_file(&self, path: &Path) -> bool;    async fn is_dir(&self, path: &Path) -> bool;    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>;    async fn read_link(&self, path: &Path) -> Result<PathBuf>;    async fn read_dir(        &self,        path: &Path,    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;     async fn watch(        &self,        path: &Path,        latency: Duration,    ) -> (        Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,        Arc<dyn Watcher>,    );     fn open_repo(        &self,        abs_dot_git: &Path,        system_git_binary_path: Option<&Path>,    ) -> Result<Arc<dyn GitRepository>>;    async fn git_init(&self, abs_work_directory: &Path, fallback_branch_name: String)    -> Result<()>;    async fn git_clone(&self, repo_url: &str, abs_work_directory: &Path) -> Result<()>;    fn is_fake(&self) -> bool;    async fn is_case_sensitive(&self) -> bool;    fn subscribe_to_jobs(&self) -> JobEventReceiver;     #[cfg(feature = "test-support")]    fn as_fake(&self) -> Arc<FakeFs> {        panic!("called as_fake on a real fs");    }} struct GlobalFs(Arc<dyn Fs>); impl Global for GlobalFs {} impl dyn Fs {    /// Returns the global [`Fs`].    pub fn global(cx: &App) -> Arc<Self> {        GlobalFs::global(cx).0.clone()    }     /// Sets the global [`Fs`].    pub fn set_global(fs: Arc<Self>, cx: &mut App) {        cx.set_global(GlobalFs(fs));    }} #[derive(Copy, Clone, Default)]pub struct CreateOptions {    pub overwrite: bool,    pub ignore_if_exists: bool,} #[derive(Copy, Clone, Default)]pub struct CopyOptions {    pub overwrite: bool,    pub ignore_if_exists: bool,} #[derive(Copy, Clone, Default)]pub struct RenameOptions {    pub overwrite: bool,    pub ignore_if_exists: bool,    /// Whether to create parent directories if they do not exist.    pub create_parents: bool,} #[derive(Copy, Clone, Default)]pub struct RemoveOptions {    pub recursive: bool,    pub ignore_if_not_exists: bool,} #[derive(Copy, Clone, Debug)]pub struct Metadata {    pub inode: u64,    pub mtime: MTime,    pub is_symlink: bool,    pub is_dir: bool,    pub len: u64,    pub is_fifo: bool,    pub is_executable: bool,} /// Filesystem modification time. The purpose of this newtype is to discourage use of operations/// that do not make sense for mtimes. In particular, it is not always valid to compare mtimes using/// `<` or `>`, as there are many things that can cause the mtime of a file to be earlier than it/// was. See ["mtime comparison considered harmful" - apenwarr](https://apenwarr.ca/log/20181113).////// Do not derive Ord, PartialOrd, or arithmetic operation traits.#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]#[serde(transparent)]pub struct MTime(SystemTime); pub type JobId = usize; #[derive(Clone, Debug)]pub struct JobInfo {    pub start: Instant,    pub message: SharedString,    pub id: JobId,} #[derive(Debug, Clone)]pub enum JobEvent {    Started { info: JobInfo },    Completed { id: JobId },} pub type JobEventSender = futures::channel::mpsc::UnboundedSender<JobEvent>;pub type JobEventReceiver = futures::channel::mpsc::UnboundedReceiver<JobEvent>; struct JobTracker {    id: JobId,    subscribers: Arc<Mutex<Vec<JobEventSender>>>,} impl JobTracker {    fn new(info: JobInfo, subscribers: Arc<Mutex<Vec<JobEventSender>>>) -> Self {        let id = info.id;        {            let mut subs = subscribers.lock();            subs.retain(|sender| {                sender                    .unbounded_send(JobEvent::Started { info: info.clone() })                    .is_ok()            });        }        Self { id, subscribers }    }} impl Drop for JobTracker {    fn drop(&mut self) {        let mut subs = self.subscribers.lock();        subs.retain(|sender| {            sender                .unbounded_send(JobEvent::Completed { id: self.id })                .is_ok()        });    }} impl MTime {    /// Conversion intended for persistence and testing.    pub fn from_seconds_and_nanos(secs: u64, nanos: u32) -> Self {        MTime(UNIX_EPOCH + Duration::new(secs, nanos))    }     /// Conversion intended for persistence.    pub fn to_seconds_and_nanos_for_persistence(self) -> Option<(u64, u32)> {        self.0            .duration_since(UNIX_EPOCH)            .ok()            .map(|duration| (duration.as_secs(), duration.subsec_nanos()))    }     /// Returns the value wrapped by this `MTime`, for presentation to the user. The name including    /// "_for_user" is to discourage misuse - this method should not be used when making decisions    /// about file dirtiness.    pub fn timestamp_for_user(self) -> SystemTime {        self.0    }     /// Temporary method to split out the behavior changes from introduction of this newtype.    pub fn bad_is_greater_than(self, other: MTime) -> bool {        self.0 > other.0    }} impl From<proto::Timestamp> for MTime {    fn from(timestamp: proto::Timestamp) -> Self {        MTime(timestamp.into())    }} impl From<MTime> for proto::Timestamp {    fn from(mtime: MTime) -> Self {        mtime.0.into()    }} pub struct RealFs {    bundled_git_binary_path: Option<PathBuf>,    executor: BackgroundExecutor,    next_job_id: Arc<AtomicUsize>,    job_event_subscribers: Arc<Mutex<Vec<JobEventSender>>>,    is_case_sensitive: AtomicU8,} pub trait FileHandle: Send + Sync + std::fmt::Debug {    fn current_path(&self, fs: &Arc<dyn Fs>) -> Result<PathBuf>;} impl FileHandle for std::fs::File {    #[cfg(target_os = "macos")]    fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {        use std::{            ffi::{CStr, OsStr},            os::unix::ffi::OsStrExt,        };         let fd = self.as_fd();        let mut path_buf = MaybeUninit::<[u8; libc::PATH_MAX as usize]>::uninit();         let result = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GETPATH, path_buf.as_mut_ptr()) };        anyhow::ensure!(result != -1, "fcntl returned -1");         // SAFETY: `fcntl` will initialize the path buffer.        let c_str = unsafe { CStr::from_ptr(path_buf.as_ptr().cast()) };        anyhow::ensure!(!c_str.is_empty(), "Could find a path for the file handle");        let path = PathBuf::from(OsStr::from_bytes(c_str.to_bytes()));        Ok(path)    }     #[cfg(target_os = "linux")]    fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {        let fd = self.as_fd();        let fd_path = format!("/proc/self/fd/{}", fd.as_raw_fd());        let new_path = std::fs::read_link(fd_path)?;        if new_path            .file_name()            .is_some_and(|f| f.to_string_lossy().ends_with(" (deleted)"))        {            anyhow::bail!("file was deleted")        };         Ok(new_path)    }     #[cfg(target_os = "freebsd")]    fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {        use std::{            ffi::{CStr, OsStr},            os::unix::ffi::OsStrExt,        };         let fd = self.as_fd();        let mut kif = MaybeUninit::<libc::kinfo_file>::uninit();        kif.kf_structsize = libc::KINFO_FILE_SIZE;         let result = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_KINFO, kif.as_mut_ptr()) };        anyhow::ensure!(result != -1, "fcntl returned -1");         // SAFETY: `fcntl` will initialize the kif.        let c_str = unsafe { CStr::from_ptr(kif.assume_init().kf_path.as_ptr()) };        anyhow::ensure!(!c_str.is_empty(), "Could find a path for the file handle");        let path = PathBuf::from(OsStr::from_bytes(c_str.to_bytes()));        Ok(path)    }     #[cfg(target_os = "windows")]    fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {        use std::ffi::OsString;        use std::os::windows::ffi::OsStringExt;        use std::os::windows::io::AsRawHandle;         use windows::Win32::Foundation::HANDLE;        use windows::Win32::Storage::FileSystem::{            FILE_NAME_NORMALIZED, GetFinalPathNameByHandleW,        };         let handle = HANDLE(self.as_raw_handle() as _);         // Query required buffer size (in wide chars)        let required_len =            unsafe { GetFinalPathNameByHandleW(handle, &mut [], FILE_NAME_NORMALIZED) };        anyhow::ensure!(            required_len != 0,            "GetFinalPathNameByHandleW returned 0 length"        );         // Allocate buffer and retrieve the path        let mut buf: Vec<u16> = vec![0u16; required_len as usize + 1];        let written = unsafe { GetFinalPathNameByHandleW(handle, &mut buf, FILE_NAME_NORMALIZED) };        anyhow::ensure!(            written != 0,            "GetFinalPathNameByHandleW failed to write path"        );         let os_str: OsString = OsString::from_wide(&buf[..written as usize]);        anyhow::ensure!(!os_str.is_empty(), "Could find a path for the file handle");        Ok(PathBuf::from(os_str))    }} pub struct RealWatcher {} impl RealFs {    pub fn new(git_binary_path: Option<PathBuf>, executor: BackgroundExecutor) -> Self {        Self {            bundled_git_binary_path: git_binary_path,            executor,            next_job_id: Arc::new(AtomicUsize::new(0)),            job_event_subscribers: Arc::new(Mutex::new(Vec::new())),            is_case_sensitive: Default::default(),        }    }     #[cfg(target_os = "windows")]    fn canonicalize(path: &Path) -> Result<PathBuf> {        use std::ffi::OsString;        use std::os::windows::ffi::OsStringExt;        use windows::Win32::Storage::FileSystem::GetVolumePathNameW;        use windows::core::HSTRING;         // std::fs::canonicalize resolves mapped network paths to UNC paths, which can        // confuse some software. To mitigate this, we canonicalize the input, then rebase        // the result onto the input's original volume root if both paths are on the same        // volume. This keeps the same drive letter or mount point the caller used.         let abs_path = if path.is_relative() {            std::env::current_dir()?.join(path)        } else {            path.to_path_buf()        };         let path_hstring = HSTRING::from(abs_path.as_os_str());        let mut vol_buf = vec![0u16; abs_path.as_os_str().len() + 2];        unsafe { GetVolumePathNameW(&path_hstring, &mut vol_buf)? };        let volume_root = {            let len = vol_buf                .iter()                .position(|&c| c == 0)                .unwrap_or(vol_buf.len());            PathBuf::from(OsString::from_wide(&vol_buf[..len]))        };         let resolved_path = dunce::canonicalize(&abs_path)?;        let resolved_root = dunce::canonicalize(&volume_root)?;         if let Ok(relative) = resolved_path.strip_prefix(&resolved_root) {            let mut result = volume_root;            result.push(relative);            Ok(result)        } else {            Ok(resolved_path)        }    }} #[cfg(any(target_os = "macos", target_os = "linux"))]fn rename_without_replace(source: &Path, target: &Path) -> io::Result<()> {    let source = path_to_c_string(source)?;    let target = path_to_c_string(target)?;     #[cfg(target_os = "macos")]    let result = unsafe { libc::renamex_np(source.as_ptr(), target.as_ptr(), libc::RENAME_EXCL) };     #[cfg(target_os = "linux")]    let result = unsafe {        libc::syscall(            libc::SYS_renameat2,            libc::AT_FDCWD,            source.as_ptr(),            libc::AT_FDCWD,            target.as_ptr(),            libc::RENAME_NOREPLACE,        )    };     if result == 0 {        Ok(())    } else {        Err(io::Error::last_os_error())    }} 

Lines 1–500 of 3017.