Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: different filenames should be treated as the same file on case-insensitive file systems #1151

Merged
merged 3 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cspell.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"0.2","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt"],"language":"en","flagWords":[]}
{"language":"en","version":"0.2","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath"]}
12 changes: 6 additions & 6 deletions yazi-boot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"

[dependencies]
regex = "1.10.4"
regex = "1.10.5"
yazi-adapter = { path = "../yazi-adapter", version = "0.2.5" }
yazi-config = { path = "../yazi-config", version = "0.2.5" }
yazi-shared = { path = "../yazi-shared", version = "0.2.5" }

# External dependencies
clap = { version = "4.5.4", features = [ "derive" ] }
clap = { version = "4.5.7", features = [ "derive" ] }
serde = { version = "1.0.203", features = [ "derive" ] }

[build-dependencies]
clap = { version = "4.5.4", features = [ "derive" ] }
clap_complete = "4.5.2"
clap_complete_nushell = "4.5.1"
clap_complete_fig = "4.5.0"
clap = { version = "4.5.7", features = [ "derive" ] }
clap_complete = "4.5.5"
clap_complete_nushell = "4.5.2"
clap_complete_fig = "4.5.1"
vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] }
10 changes: 5 additions & 5 deletions yazi-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }

# External dependencies
anyhow = "1.0.86"
clap = { version = "4.5.4", features = [ "derive" ] }
clap = { version = "4.5.7", features = [ "derive" ] }
crossterm = "0.27.0"
md-5 = "0.10.6"
serde_json = "1.0.117"
Expand All @@ -23,10 +23,10 @@ toml_edit = "0.22.14"

[build-dependencies]
anyhow = "1.0.86"
clap = { version = "4.5.4", features = [ "derive" ] }
clap_complete = "4.5.2"
clap_complete_fig = "4.5.0"
clap_complete_nushell = "4.5.1"
clap = { version = "4.5.7", features = [ "derive" ] }
clap_complete = "4.5.5"
clap_complete_fig = "4.5.1"
clap_complete_nushell = "4.5.2"
serde_json = "1.0.117"
vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] }

Expand Down
2 changes: 1 addition & 1 deletion yazi-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ futures = "0.3.30"
notify = { version = "6.1.1", default-features = false, features = [ "macos_fsevent" ] }
parking_lot = "0.12.3"
ratatui = "0.26.3"
regex = "1.10.4"
regex = "1.10.5"
scopeguard = "1.2.0"
serde = "1.0.203"
shell-words = "1.1.0"
Expand Down
52 changes: 33 additions & 19 deletions yazi-core/src/manager/commands/create.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::path::PathBuf;
use std::collections::HashMap;

use anyhow::Result;
use tokio::fs;
use yazi_config::popup::InputCfg;
use yazi_proxy::{InputProxy, ManagerProxy};
use yazi_shared::{event::Cmd, fs::{maybe_exists, File, FilesOp, Url}};
use yazi_proxy::{InputProxy, TabProxy, WATCHER};
use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, symlink_realpath, File, FilesOp, Url}};

use crate::manager::Manager;

Expand All @@ -24,29 +25,42 @@ impl Manager {
let Some(Ok(name)) = result.recv().await else {
return Ok(());
};
if name.is_empty() {
return Ok(());
}

let path = cwd.join(&name);
if !opt.force && maybe_exists(&path).await {
let new = cwd.join(&name);
if !opt.force && maybe_exists(&new).await {
match InputProxy::show(InputCfg::overwrite()).recv().await {
Some(Ok(c)) if c == "y" || c == "Y" => (),
_ => return Ok(()),
}
}

if name.ends_with('/') || name.ends_with('\\') {
fs::create_dir_all(&path).await?;
} else {
fs::create_dir_all(&path.parent().unwrap()).await.ok();
fs::File::create(&path).await?;
}

let child =
Url::from(path.components().take(cwd.components().count() + 1).collect::<PathBuf>());
if let Ok(f) = File::from(child.clone()).await {
FilesOp::Creating(cwd, vec![f]).emit();
ManagerProxy::hover(Some(child));
}
Ok::<(), anyhow::Error>(())
Self::create_do(new, name.ends_with('/') || name.ends_with('\\')).await
});
}

async fn create_do(new: Url, dir: bool) -> Result<()> {
let Some(parent) = new.parent_url() else { return Ok(()) };
let _permit = WATCHER.acquire().await.unwrap();

if dir {
fs::create_dir_all(&new).await?;
} else if let Ok(real) = symlink_realpath(&new).await {
ok_or_not_found(fs::remove_file(&new).await)?;
FilesOp::Deleting(parent.clone(), vec![Url::from(real)]).emit();
fs::File::create(&new).await?;
} else {
fs::create_dir_all(&parent).await.ok();
ok_or_not_found(fs::remove_file(&new).await)?;
fs::File::create(&new).await?;
}

if let Ok(f) = File::from(new.clone()).await {
FilesOp::Upserting(parent, HashMap::from_iter([(f.url(), f)])).emit();
TabProxy::reveal(&new)
}
Ok(())
}
}
22 changes: 13 additions & 9 deletions yazi-core/src/manager/commands/rename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use anyhow::Result;
use tokio::fs;
use yazi_config::popup::InputCfg;
use yazi_dds::Pubsub;
use yazi_proxy::{InputProxy, ManagerProxy, WATCHER};
use yazi_shared::{event::Cmd, fs::{maybe_exists, File, FilesOp, Url}};
use yazi_proxy::{InputProxy, TabProxy, WATCHER};
use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, symlink_realpath, File, FilesOp, Url}};

use crate::manager::Manager;

Expand Down Expand Up @@ -77,19 +77,23 @@ impl Manager {
}

async fn rename_do(tab: usize, old: Url, new: Url) -> Result<()> {
let Some(p_old) = old.parent_url() else { return Ok(()) };
let Some(p_new) = new.parent_url() else { return Ok(()) };
let _permit = WATCHER.acquire().await.unwrap();

let overwritten = symlink_realpath(&new).await;
fs::rename(&old, &new).await?;
if old.parent() != new.parent() {
return Ok(());
}

let file = File::from(new.clone()).await?;
if let Ok(p) = overwritten {
ok_or_not_found(fs::rename(&p, &new).await)?;
FilesOp::Deleting(p_new.clone(), vec![Url::from(p)]).emit();
}
Pubsub::pub_from_rename(tab, &old, &new);

FilesOp::Deleting(file.parent().unwrap(), vec![new.clone()]).emit();
FilesOp::Upserting(file.parent().unwrap(), HashMap::from_iter([(old, file)])).emit();
Ok(ManagerProxy::hover(Some(new)))
let file = File::from(new.clone()).await?;
FilesOp::Deleting(p_old, vec![old]).emit();
FilesOp::Upserting(p_new, HashMap::from_iter([(new.clone(), file)])).emit();
Ok(TabProxy::reveal(&new))
}

fn empty_url_part(url: &Url, by: &str) -> String {
Expand Down
38 changes: 25 additions & 13 deletions yazi-core/src/manager/watcher.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{collections::{HashMap, HashSet}, time::{Duration, SystemTime}};
use std::{borrow::Cow, collections::{HashMap, HashSet}, time::{Duration, SystemTime}};

use anyhow::Result;
use notify::{RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
Expand All @@ -8,7 +8,7 @@ use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
use tracing::error;
use yazi_plugin::isolate;
use yazi_proxy::WATCHER;
use yazi_shared::{fs::{File, FilesOp, Url}, RoCell};
use yazi_shared::{fs::{symlink_realpath_with, File, FilesOp, Url}, RoCell};

use super::Linked;
use crate::folder::{Files, Folder};
Expand All @@ -35,8 +35,8 @@ impl Watcher {
Default::default(),
);

tokio::spawn(Self::on_in(in_rx, watcher.unwrap()));
tokio::spawn(Self::on_out(out_rx));
tokio::spawn(Self::fan_in(in_rx, watcher.unwrap()));
tokio::spawn(Self::fan_out(out_rx));
Self { tx: in_tx }
}

Expand Down Expand Up @@ -65,7 +65,7 @@ impl Watcher {
});
}

async fn on_in(mut rx: watch::Receiver<HashSet<Url>>, mut watcher: RecommendedWatcher) {
async fn fan_in(mut rx: watch::Receiver<HashSet<Url>>, mut watcher: RecommendedWatcher) {
loop {
let (mut to_unwatch, mut to_watch): (HashSet<_>, HashSet<_>) = {
let (new, old) = (&*rx.borrow_and_update(), &*WATCHED.read());
Expand All @@ -91,27 +91,39 @@ impl Watcher {
}
}

async fn on_out(rx: UnboundedReceiver<Url>) {
async fn fan_out(rx: UnboundedReceiver<Url>) {
// TODO: revert this once a new notification is implemented
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(50));
pin!(rx);

while let Some(urls) = rx.next().await {
while let Some(chunk) = rx.next().await {
let urls: HashSet<_> = chunk.into_iter().collect();
let mut cached: HashMap<_, _> = HashMap::new();

let _permit = WATCHER.acquire().await.unwrap();
let mut reload = Vec::with_capacity(urls.len());

for u in urls.into_iter().collect::<HashSet<_>>() {
let Some(parent) = u.parent_url() else { continue };

let Ok(file) = File::from(u.clone()).await else {
FilesOp::Deleting(parent, vec![u]).emit();
for url in urls {
let Some(parent) = url.parent_url() else { continue };
let Ok(file) = File::from(url.clone()).await else {
FilesOp::Deleting(parent, vec![url]).emit();
continue;
};

let real = if file.is_link() {
symlink_realpath_with(&url, &mut cached).await
} else {
fs::canonicalize(&url).await.map(Cow::Owned)
};
if !real.is_ok_and(|p| p == *url) {
FilesOp::Deleting(parent, vec![url]).emit();
continue;
}

if !file.is_dir() {
reload.push(file.clone());
}
FilesOp::Upserting(parent, HashMap::from_iter([(u, file)])).emit();
FilesOp::Upserting(parent, HashMap::from_iter([(url, file)])).emit();
}

if reload.is_empty() {
Expand Down
12 changes: 8 additions & 4 deletions yazi-dds/src/body/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,15 @@ impl Body<'static> {
if matches!(
kind,
"hi"
| "hey" | "bye"
| "cd" | "hover"
| "hey"
| "bye"
| "cd"
| "hover"
| "rename"
| "bulk" | "yank"
| "move" | "trash"
| "bulk"
| "yank"
| "move"
| "trash"
| "delete"
) {
bail!("Cannot construct system event");
Expand Down
Loading