Better absolute path handling (#19727)
Closes #19866 This PR supersedes #19228, as #19228 encountered too many merge conflicts. After some exploration, I found that for paths with the `\\?\` prefix, we can safely remove it and consistently use the clean paths in all cases. Previously, in #19228, I thought we would still need the `\\?\` prefix for IO operations to handle long paths better. However, this turns out to be unnecessary because Rust automatically manages this for us when calling IO-related APIs. For details, refer to Rust's internal function [`get_long_path`](017ae1b21f/library/std/src/sys/path/windows.rs (L225-L233)
). Therefore, we can always store and use paths without the `\\?\` prefix. This PR introduces a `SanitizedPath` structure, which represents a path stripped of the `\\?\` prefix. To prevent untrimmed paths from being mistakenly passed into `Worktree`, the type of `Worktree`’s `abs_path` member variable has been changed to `SanitizedPath`. Additionally, this PR reverts the changes of #15856 and #18726. After testing, it appears that the issues those PRs addressed can be resolved by this PR. ### Existing Issue To keep the scope of modifications manageable, `Worktree::abs_path` has retained its current signature as `fn abs_path(&self) -> Arc<Path>`, rather than returning a `SanitizedPath`. Updating the method to return `SanitizedPath`—which may better resolve path inconsistencies—would likely introduce extensive changes similar to those in #19228. Currently, the limitation is as follows: ```rust let abs_path: &Arc<Path> = snapshot.abs_path(); let some_non_trimmed_path = Path::new("\\\\?\\C:\\Users\\user\\Desktop\\project"); // The caller performs some actions here: some_non_trimmed_path.strip_prefix(abs_path); // This fails some_non_trimmed_path.starts_with(abs_path); // This fails too ``` The final two lines will fail because `snapshot.abs_path()` returns a clean path without the `\\?\` prefix. I have identified two relevant instances that may face this issue: - [lsp_store.rs#L3578](0173479d18/crates/project/src/lsp_store.rs (L3578)
) - [worktree.rs#L4338](0173479d18/crates/worktree/src/worktree.rs (L4338)
) Switching `Worktree::abs_path` to return `SanitizedPath` would resolve these issues but would also lead to many code changes. Any suggestions or feedback on this approach are very welcome. cc @SomeoneToIgnore Release Notes: - N/A
This commit is contained in:
parent
d0bafce86b
commit
cff9ae0bbc
12 changed files with 189 additions and 113 deletions
|
@ -5577,7 +5577,7 @@ impl LspStore {
|
|||
|
||||
let worktree = worktree_handle.read(cx);
|
||||
let worktree_id = worktree.id();
|
||||
let worktree_path = worktree.abs_path();
|
||||
let root_path = worktree.abs_path();
|
||||
let key = (worktree_id, adapter.name.clone());
|
||||
|
||||
if self.language_server_ids.contains_key(&key) {
|
||||
|
@ -5599,7 +5599,6 @@ impl LspStore {
|
|||
as Arc<dyn LspAdapterDelegate>;
|
||||
|
||||
let server_id = self.languages.next_language_server_id();
|
||||
let root_path = worktree_path.clone();
|
||||
log::info!(
|
||||
"attempting to start language server {:?}, path: {root_path:?}, id: {server_id}",
|
||||
adapter.name.0
|
||||
|
|
|
@ -23,7 +23,7 @@ use smol::{
|
|||
stream::StreamExt,
|
||||
};
|
||||
use text::ReplicaId;
|
||||
use util::ResultExt;
|
||||
use util::{paths::SanitizedPath, ResultExt};
|
||||
use worktree::{Entry, ProjectEntryId, Worktree, WorktreeId, WorktreeSettings};
|
||||
|
||||
use crate::{search::SearchQuery, ProjectPath};
|
||||
|
@ -52,7 +52,7 @@ pub struct WorktreeStore {
|
|||
worktrees_reordered: bool,
|
||||
#[allow(clippy::type_complexity)]
|
||||
loading_worktrees:
|
||||
HashMap<Arc<Path>, Shared<Task<Result<Model<Worktree>, Arc<anyhow::Error>>>>>,
|
||||
HashMap<SanitizedPath, Shared<Task<Result<Model<Worktree>, Arc<anyhow::Error>>>>>,
|
||||
state: WorktreeStoreState,
|
||||
}
|
||||
|
||||
|
@ -147,11 +147,12 @@ impl WorktreeStore {
|
|||
|
||||
pub fn find_worktree(
|
||||
&self,
|
||||
abs_path: &Path,
|
||||
abs_path: impl Into<SanitizedPath>,
|
||||
cx: &AppContext,
|
||||
) -> Option<(Model<Worktree>, PathBuf)> {
|
||||
let abs_path: SanitizedPath = abs_path.into();
|
||||
for tree in self.worktrees() {
|
||||
if let Ok(relative_path) = abs_path.strip_prefix(tree.read(cx).abs_path()) {
|
||||
if let Ok(relative_path) = abs_path.as_path().strip_prefix(tree.read(cx).abs_path()) {
|
||||
return Some((tree.clone(), relative_path.into()));
|
||||
}
|
||||
}
|
||||
|
@ -192,12 +193,12 @@ impl WorktreeStore {
|
|||
|
||||
pub fn create_worktree(
|
||||
&mut self,
|
||||
abs_path: impl AsRef<Path>,
|
||||
abs_path: impl Into<SanitizedPath>,
|
||||
visible: bool,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<Worktree>>> {
|
||||
let path: Arc<Path> = abs_path.as_ref().into();
|
||||
if !self.loading_worktrees.contains_key(&path) {
|
||||
let abs_path: SanitizedPath = abs_path.into();
|
||||
if !self.loading_worktrees.contains_key(&abs_path) {
|
||||
let task = match &self.state {
|
||||
WorktreeStoreState::Remote {
|
||||
upstream_client, ..
|
||||
|
@ -205,20 +206,26 @@ impl WorktreeStore {
|
|||
if upstream_client.is_via_collab() {
|
||||
Task::ready(Err(Arc::new(anyhow!("cannot create worktrees via collab"))))
|
||||
} else {
|
||||
self.create_ssh_worktree(upstream_client.clone(), abs_path, visible, cx)
|
||||
self.create_ssh_worktree(
|
||||
upstream_client.clone(),
|
||||
abs_path.clone(),
|
||||
visible,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}
|
||||
WorktreeStoreState::Local { fs } => {
|
||||
self.create_local_worktree(fs.clone(), abs_path, visible, cx)
|
||||
self.create_local_worktree(fs.clone(), abs_path.clone(), visible, cx)
|
||||
}
|
||||
};
|
||||
|
||||
self.loading_worktrees.insert(path.clone(), task.shared());
|
||||
self.loading_worktrees
|
||||
.insert(abs_path.clone(), task.shared());
|
||||
}
|
||||
let task = self.loading_worktrees.get(&path).unwrap().clone();
|
||||
let task = self.loading_worktrees.get(&abs_path).unwrap().clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let result = task.await;
|
||||
this.update(&mut cx, |this, _| this.loading_worktrees.remove(&path))
|
||||
this.update(&mut cx, |this, _| this.loading_worktrees.remove(&abs_path))
|
||||
.ok();
|
||||
match result {
|
||||
Ok(worktree) => Ok(worktree),
|
||||
|
@ -230,12 +237,11 @@ impl WorktreeStore {
|
|||
fn create_ssh_worktree(
|
||||
&mut self,
|
||||
client: AnyProtoClient,
|
||||
abs_path: impl AsRef<Path>,
|
||||
abs_path: impl Into<SanitizedPath>,
|
||||
visible: bool,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<Worktree>, Arc<anyhow::Error>>> {
|
||||
let path_key: Arc<Path> = abs_path.as_ref().into();
|
||||
let mut abs_path = path_key.clone().to_string_lossy().to_string();
|
||||
let mut abs_path = Into::<SanitizedPath>::into(abs_path).to_string();
|
||||
// If we start with `/~` that means the ssh path was something like `ssh://user@host/~/home-dir-folder/`
|
||||
// in which case want to strip the leading the `/`.
|
||||
// On the host-side, the `~` will get expanded.
|
||||
|
@ -293,12 +299,12 @@ impl WorktreeStore {
|
|||
fn create_local_worktree(
|
||||
&mut self,
|
||||
fs: Arc<dyn Fs>,
|
||||
abs_path: impl AsRef<Path>,
|
||||
abs_path: impl Into<SanitizedPath>,
|
||||
visible: bool,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<Worktree>, Arc<anyhow::Error>>> {
|
||||
let next_entry_id = self.next_entry_id.clone();
|
||||
let path: Arc<Path> = abs_path.as_ref().into();
|
||||
let path: SanitizedPath = abs_path.into();
|
||||
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let worktree = Worktree::local(path.clone(), visible, fs, next_entry_id, &mut cx).await;
|
||||
|
@ -308,7 +314,7 @@ impl WorktreeStore {
|
|||
|
||||
if visible {
|
||||
cx.update(|cx| {
|
||||
cx.add_recent_document(&path);
|
||||
cx.add_recent_document(path.as_path());
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue