Fix git features not working when a Windows host collaborates with a unix guest (#43515)
We were using `std::path::Path::strip_prefix` to determine which repository an absolute path belongs to, which doesn't work when the paths are Windows-style but the code is running on unix. Replace it with a platform-agnostic implementation of `strip_prefix`. Release Notes: - Fixed git features not working when a Windows host collaborates with a unix guest
This commit is contained in:
@@ -1423,7 +1423,7 @@ mod tests {
|
||||
rel_path("b/eight.txt"),
|
||||
];
|
||||
|
||||
let slash = PathStyle::local().separator();
|
||||
let slash = PathStyle::local().primary_separator();
|
||||
|
||||
let mut opened_editors = Vec::new();
|
||||
for path in paths {
|
||||
|
||||
@@ -3989,7 +3989,7 @@ impl AcpThreadView {
|
||||
let file = buffer.read(cx).file()?;
|
||||
let path = file.path();
|
||||
let path_style = file.path_style(cx);
|
||||
let separator = file.path_style(cx).separator();
|
||||
let separator = file.path_style(cx).primary_separator();
|
||||
|
||||
let file_path = path.parent().and_then(|parent| {
|
||||
if parent.is_empty() {
|
||||
|
||||
@@ -1060,7 +1060,7 @@ impl FileFinderDelegate {
|
||||
(
|
||||
filename.to_string(),
|
||||
Vec::new(),
|
||||
prefix.display(path_style).to_string() + path_style.separator(),
|
||||
prefix.display(path_style).to_string() + path_style.primary_separator(),
|
||||
Vec::new(),
|
||||
)
|
||||
} else {
|
||||
@@ -1071,7 +1071,7 @@ impl FileFinderDelegate {
|
||||
.map_or(String::new(), |f| f.to_string_lossy().into_owned()),
|
||||
Vec::new(),
|
||||
entry_path.absolute.parent().map_or(String::new(), |path| {
|
||||
path.to_string_lossy().into_owned() + path_style.separator()
|
||||
path.to_string_lossy().into_owned() + path_style.primary_separator()
|
||||
}),
|
||||
Vec::new(),
|
||||
)
|
||||
|
||||
@@ -1598,7 +1598,7 @@ async fn test_history_match_positions(cx: &mut gpui::TestAppContext) {
|
||||
assert_eq!(file_label.highlight_indices(), &[0, 1, 2]);
|
||||
assert_eq!(
|
||||
path_label.text(),
|
||||
format!("test{}", PathStyle::local().separator())
|
||||
format!("test{}", PathStyle::local().primary_separator())
|
||||
);
|
||||
assert_eq!(path_label.highlight_indices(), &[] as &[usize]);
|
||||
});
|
||||
|
||||
@@ -559,7 +559,7 @@ impl PickerDelegate for OpenPathDelegate {
|
||||
parent_path,
|
||||
candidate.path.string,
|
||||
if candidate.is_dir {
|
||||
path_style.separator()
|
||||
path_style.primary_separator()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
@@ -569,7 +569,7 @@ impl PickerDelegate for OpenPathDelegate {
|
||||
parent_path,
|
||||
candidate.path.string,
|
||||
if candidate.is_dir {
|
||||
path_style.separator()
|
||||
path_style.primary_separator()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
@@ -826,7 +826,13 @@ impl PickerDelegate for OpenPathDelegate {
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
Arc::from(format!("[directory{}]filename.ext", self.path_style.separator()).as_str())
|
||||
Arc::from(
|
||||
format!(
|
||||
"[directory{}]filename.ext",
|
||||
self.path_style.primary_separator()
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
}
|
||||
|
||||
fn separators_after_indices(&self) -> Vec<usize> {
|
||||
|
||||
@@ -107,7 +107,7 @@ pub fn match_fixed_path_set(
|
||||
.display(path_style)
|
||||
.chars()
|
||||
.collect::<Vec<_>>();
|
||||
path_prefix_chars.extend(path_style.separator().chars());
|
||||
path_prefix_chars.extend(path_style.primary_separator().chars());
|
||||
let lowercase_pfx = path_prefix_chars
|
||||
.iter()
|
||||
.map(|c| c.to_ascii_lowercase())
|
||||
|
||||
@@ -4351,8 +4351,11 @@ impl GitPanel {
|
||||
.when(strikethrough, Label::strikethrough),
|
||||
),
|
||||
(true, false) => this.child(
|
||||
self.entry_label(format!("{dir}{}", path_style.separator()), path_color)
|
||||
.when(strikethrough, Label::strikethrough),
|
||||
self.entry_label(
|
||||
format!("{dir}{}", path_style.primary_separator()),
|
||||
path_color,
|
||||
)
|
||||
.when(strikethrough, Label::strikethrough),
|
||||
),
|
||||
_ => this,
|
||||
}
|
||||
|
||||
@@ -3222,10 +3222,8 @@ impl RepositorySnapshot {
|
||||
abs_path: &Path,
|
||||
path_style: PathStyle,
|
||||
) -> Option<RepoPath> {
|
||||
abs_path
|
||||
.strip_prefix(&work_directory_abs_path)
|
||||
.ok()
|
||||
.and_then(|path| RepoPath::from_std_path(path, path_style).ok())
|
||||
let rel_path = path_style.strip_prefix(abs_path, work_directory_abs_path)?;
|
||||
Some(RepoPath::from_rel_path(&rel_path))
|
||||
}
|
||||
|
||||
pub fn had_conflict_on_last_merge_head_change(&self, repo_path: &RepoPath) -> bool {
|
||||
|
||||
@@ -927,7 +927,7 @@ impl DirectoryLister {
|
||||
.map(|worktree| worktree.read(cx).abs_path().to_string_lossy().into_owned())
|
||||
.or_else(|| std::env::home_dir().map(|dir| dir.to_string_lossy().into_owned()))
|
||||
.map(|mut s| {
|
||||
s.push_str(path_style.separator());
|
||||
s.push_str(path_style.primary_separator());
|
||||
s
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
|
||||
@@ -4837,7 +4837,7 @@ impl ProjectPanel {
|
||||
.collect::<Vec<_>>();
|
||||
let active_index = folded_ancestors.active_index();
|
||||
let components_len = components.len();
|
||||
let delimiter = SharedString::new(path_style.separator());
|
||||
let delimiter = SharedString::new(path_style.primary_separator());
|
||||
for (index, component) in components.iter().enumerate() {
|
||||
if index != 0 {
|
||||
let delimiter_target_index = index - 1;
|
||||
|
||||
@@ -2192,7 +2192,7 @@ impl SettingsWindow {
|
||||
format!(
|
||||
"{}{}{}",
|
||||
directory_name,
|
||||
path_style.separator(),
|
||||
path_style.primary_separator(),
|
||||
path.display(path_style)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -876,7 +876,7 @@ impl ToolchainSelectorDelegate {
|
||||
.strip_prefix(&worktree_root)
|
||||
.ok()
|
||||
.and_then(|suffix| suffix.to_str())
|
||||
.map(|suffix| format!(".{}{suffix}", path_style.separator()).into())
|
||||
.map(|suffix| format!(".{}{suffix}", path_style.primary_separator()).into())
|
||||
.unwrap_or(path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::Ordering;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
@@ -331,13 +332,20 @@ impl PathStyle {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn separator(&self) -> &'static str {
|
||||
pub fn primary_separator(&self) -> &'static str {
|
||||
match self {
|
||||
PathStyle::Posix => "/",
|
||||
PathStyle::Windows => "\\",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separators(&self) -> &'static [&'static str] {
|
||||
match self {
|
||||
PathStyle::Posix => &["/"],
|
||||
PathStyle::Windows => &["\\", "/"],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_windows(&self) -> bool {
|
||||
*self == PathStyle::Windows
|
||||
}
|
||||
@@ -353,25 +361,54 @@ impl PathStyle {
|
||||
} else {
|
||||
Some(format!(
|
||||
"{left}{}{right}",
|
||||
if left.ends_with(self.separator()) {
|
||||
if left.ends_with(self.primary_separator()) {
|
||||
""
|
||||
} else {
|
||||
self.separator()
|
||||
self.primary_separator()
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split(self, path_like: &str) -> (Option<&str>, &str) {
|
||||
let Some(pos) = path_like.rfind(self.separator()) else {
|
||||
let Some(pos) = path_like.rfind(self.primary_separator()) else {
|
||||
return (None, path_like);
|
||||
};
|
||||
let filename_start = pos + self.separator().len();
|
||||
let filename_start = pos + self.primary_separator().len();
|
||||
(
|
||||
Some(&path_like[..filename_start]),
|
||||
&path_like[filename_start..],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn strip_prefix<'a>(
|
||||
&self,
|
||||
child: &'a Path,
|
||||
parent: &'a Path,
|
||||
) -> Option<std::borrow::Cow<'a, RelPath>> {
|
||||
let parent = parent.to_str()?;
|
||||
if parent.is_empty() {
|
||||
return RelPath::new(child, *self).ok();
|
||||
}
|
||||
let parent = self
|
||||
.separators()
|
||||
.iter()
|
||||
.find_map(|sep| parent.strip_suffix(sep))
|
||||
.unwrap_or(parent);
|
||||
let child = child.to_str()?;
|
||||
let stripped = child.strip_prefix(parent)?;
|
||||
if let Some(relative) = self
|
||||
.separators()
|
||||
.iter()
|
||||
.find_map(|sep| stripped.strip_prefix(sep))
|
||||
{
|
||||
RelPath::new(relative.as_ref(), *self).ok()
|
||||
} else if stripped.is_empty() {
|
||||
Some(Cow::Borrowed(RelPath::empty()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -788,7 +825,7 @@ impl PathMatcher {
|
||||
|
||||
fn check_with_end_separator(&self, path: &Path) -> bool {
|
||||
let path_str = path.to_string_lossy();
|
||||
let separator = self.path_style.separator();
|
||||
let separator = self.path_style.primary_separator();
|
||||
if path_str.ends_with(separator) {
|
||||
false
|
||||
} else {
|
||||
@@ -1311,6 +1348,8 @@ impl WslPath {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::rel_path::rel_path;
|
||||
|
||||
use super::*;
|
||||
use util_macros::perf;
|
||||
|
||||
@@ -2480,6 +2519,89 @@ mod tests {
|
||||
assert_eq!(strip_path_suffix(base, suffix), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strip_prefix() {
|
||||
let expected = [
|
||||
(
|
||||
PathStyle::Posix,
|
||||
"/a/b/c",
|
||||
"/a/b",
|
||||
Some(rel_path("c").into_arc()),
|
||||
),
|
||||
(
|
||||
PathStyle::Posix,
|
||||
"/a/b/c",
|
||||
"/a/b/",
|
||||
Some(rel_path("c").into_arc()),
|
||||
),
|
||||
(
|
||||
PathStyle::Posix,
|
||||
"/a/b/c",
|
||||
"/",
|
||||
Some(rel_path("a/b/c").into_arc()),
|
||||
),
|
||||
(PathStyle::Posix, "/a/b/c", "", None),
|
||||
(PathStyle::Posix, "/a/b//c", "/a/b/", None),
|
||||
(PathStyle::Posix, "/a/bc", "/a/b", None),
|
||||
(
|
||||
PathStyle::Posix,
|
||||
"/a/b/c",
|
||||
"/a/b/c",
|
||||
Some(rel_path("").into_arc()),
|
||||
),
|
||||
(
|
||||
PathStyle::Windows,
|
||||
"C:\\a\\b\\c",
|
||||
"C:\\a\\b",
|
||||
Some(rel_path("c").into_arc()),
|
||||
),
|
||||
(
|
||||
PathStyle::Windows,
|
||||
"C:\\a\\b\\c",
|
||||
"C:\\a\\b\\",
|
||||
Some(rel_path("c").into_arc()),
|
||||
),
|
||||
(
|
||||
PathStyle::Windows,
|
||||
"C:\\a\\b\\c",
|
||||
"C:\\",
|
||||
Some(rel_path("a/b/c").into_arc()),
|
||||
),
|
||||
(PathStyle::Windows, "C:\\a\\b\\c", "", None),
|
||||
(PathStyle::Windows, "C:\\a\\b\\\\c", "C:\\a\\b\\", None),
|
||||
(PathStyle::Windows, "C:\\a\\bc", "C:\\a\\b", None),
|
||||
(
|
||||
PathStyle::Windows,
|
||||
"C:\\a\\b/c",
|
||||
"C:\\a\\b",
|
||||
Some(rel_path("c").into_arc()),
|
||||
),
|
||||
(
|
||||
PathStyle::Windows,
|
||||
"C:\\a\\b/c",
|
||||
"C:\\a\\b\\",
|
||||
Some(rel_path("c").into_arc()),
|
||||
),
|
||||
(
|
||||
PathStyle::Windows,
|
||||
"C:\\a\\b/c",
|
||||
"C:\\a\\b/",
|
||||
Some(rel_path("c").into_arc()),
|
||||
),
|
||||
];
|
||||
let actual = expected.clone().map(|(style, child, parent, _)| {
|
||||
(
|
||||
style,
|
||||
child,
|
||||
parent,
|
||||
style
|
||||
.strip_prefix(child.as_ref(), parent.as_ref())
|
||||
.map(|rel_path| rel_path.into_arc()),
|
||||
)
|
||||
});
|
||||
pretty_assertions::assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn test_wsl_path() {
|
||||
|
||||
@@ -965,7 +965,7 @@ impl VimCommand {
|
||||
}
|
||||
};
|
||||
|
||||
let rel_path = if args.ends_with(PathStyle::local().separator()) {
|
||||
let rel_path = if args.ends_with(PathStyle::local().primary_separator()) {
|
||||
rel_path
|
||||
} else {
|
||||
rel_path
|
||||
@@ -998,7 +998,7 @@ impl VimCommand {
|
||||
.display(PathStyle::local())
|
||||
.to_string();
|
||||
if dir.is_dir {
|
||||
path_string.push_str(PathStyle::local().separator());
|
||||
path_string.push_str(PathStyle::local().primary_separator());
|
||||
}
|
||||
path_string
|
||||
})
|
||||
|
||||
@@ -999,7 +999,7 @@ impl Worktree {
|
||||
};
|
||||
|
||||
if worktree_relative_path.components().next().is_some() {
|
||||
full_path_string.push_str(self.path_style.separator());
|
||||
full_path_string.push_str(self.path_style.primary_separator());
|
||||
full_path_string.push_str(&worktree_relative_path.display(self.path_style));
|
||||
}
|
||||
|
||||
@@ -2108,8 +2108,8 @@ impl Snapshot {
|
||||
if path.file_name().is_some() {
|
||||
let mut abs_path = self.abs_path.to_string();
|
||||
for component in path.components() {
|
||||
if !abs_path.ends_with(self.path_style.separator()) {
|
||||
abs_path.push_str(self.path_style.separator());
|
||||
if !abs_path.ends_with(self.path_style.primary_separator()) {
|
||||
abs_path.push_str(self.path_style.primary_separator());
|
||||
}
|
||||
abs_path.push_str(component);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user