mirror of
https://github.com/Llewellynvdm/zoxide.git
synced 2024-11-10 23:30:57 +00:00
Copy owner from previous database file (#364)
This commit is contained in:
parent
90e781a192
commit
17365710af
19
CHANGELOG.md
19
CHANGELOG.md
@ -15,11 +15,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bash/Zsh: rename `_z` completion function to avoid conflicts with other shell plugins.
|
||||
- Bash/Zsh: rename `_z` completion function to avoid conflicts with other shell
|
||||
plugins.
|
||||
- Elvish: upgrade to new lambda syntax.
|
||||
- Fzf: added `--keep-right` option by default, upgraded minimum version to v0.21.0.
|
||||
- Fzf: added `--keep-right` option by default, upgraded minimum version to
|
||||
v0.21.0.
|
||||
- Bash: only enable completions on 4.4+.
|
||||
- Fzf: bypass `ls` alias in preview window.
|
||||
- Retain ownership of database file.
|
||||
|
||||
## [0.8.0] - 2021-12-25
|
||||
|
||||
@ -30,7 +33,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Changed
|
||||
|
||||
- Fzf: better default options.
|
||||
- Fish: interactive completions are only triggered when the last argument is empty.
|
||||
- Fish: interactive completions are only triggered when the last argument is
|
||||
empty.
|
||||
- PowerShell: installation instructions.
|
||||
|
||||
### Fixed
|
||||
@ -131,7 +135,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- Auto-generated shell completions.
|
||||
- `zoxide query --all` for listing deleted directories.
|
||||
- Lazy deletion for removed directories that have not been accessed in > 90 days.
|
||||
- Lazy deletion for removed directories that have not been accessed in > 90
|
||||
days.
|
||||
- Nushell: support for 0.32.0+.
|
||||
|
||||
### Fixed
|
||||
@ -158,7 +163,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Fixed
|
||||
|
||||
- `cd -` on fish shells.
|
||||
- `__zoxide_hook` no longer changes value of `$?` within `$PROMPT_COMMAND` on bash.
|
||||
- `__zoxide_hook` no longer changes value of `$?` within `$PROMPT_COMMAND` on
|
||||
bash.
|
||||
|
||||
### Removed
|
||||
|
||||
@ -195,7 +201,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
|
||||
- `$_ZO_EXCLUDE_DIRS` now supports globs.
|
||||
- `zoxide init` now defines `__zoxide_z*` functions that can be aliased as needed.
|
||||
- `zoxide init` now defines `__zoxide_z*` functions that can be aliased as
|
||||
needed.
|
||||
- Support for the [xonsh](https://xon.sh/) shell.
|
||||
- `zoxide import` can now import from Autojump.
|
||||
|
||||
|
82
Cargo.lock
generated
82
Cargo.lock
generated
@ -13,9 +13,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.55"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
|
||||
checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27"
|
||||
|
||||
[[package]]
|
||||
name = "askama"
|
||||
@ -117,6 +117,12 @@ dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@ -125,9 +131,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.1.5"
|
||||
version = "3.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312"
|
||||
checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
@ -365,6 +371,15 @@ version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.16"
|
||||
@ -387,6 +402,19 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.0"
|
||||
@ -500,24 +528,6 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.11"
|
||||
@ -539,9 +549,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
version = "1.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -695,26 +705,6 @@ version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.4"
|
||||
@ -825,12 +815,12 @@ dependencies = [
|
||||
"clap_complete_fig",
|
||||
"dirs",
|
||||
"dunce",
|
||||
"fastrand",
|
||||
"glob",
|
||||
"nix",
|
||||
"ordered-float",
|
||||
"rand",
|
||||
"rstest",
|
||||
"rstest_reuse",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
]
|
||||
|
13
Cargo.toml
13
Cargo.toml
@ -23,17 +23,13 @@ bincode = "1.3.1"
|
||||
clap = { version = "3.1.0", features = ["derive"] }
|
||||
dirs = "4.0.0"
|
||||
dunce = "1.0.1"
|
||||
fastrand = "1.7.0"
|
||||
glob = "0.3.0"
|
||||
ordered-float = "2.0.0"
|
||||
serde = { version = "1.0.116", features = ["derive"] }
|
||||
tempfile = "3.1.0"
|
||||
thiserror = "1.0.30"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
rand = { version = "0.8.4", features = [
|
||||
"getrandom",
|
||||
"small_rng",
|
||||
], default-features = false }
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = "0.23.1"
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "3.1.0", features = ["derive"] }
|
||||
@ -44,10 +40,11 @@ clap_complete_fig = "3.1.0"
|
||||
assert_cmd = "2.0.0"
|
||||
rstest = "0.12.0"
|
||||
rstest_reuse = "0.3.0"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
nix = []
|
||||
nix-dev = []
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
@ -3,10 +3,10 @@ use std::io::{self, Write};
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::cmd::{Query, Run};
|
||||
use crate::config;
|
||||
use crate::db::{Database, DatabaseFile};
|
||||
use crate::error::BrokenPipeHandler;
|
||||
use crate::fzf::Fzf;
|
||||
use crate::{config, util};
|
||||
use crate::util::{self, Fzf};
|
||||
|
||||
impl Run for Query {
|
||||
fn run(&self) -> Result<()> {
|
||||
|
@ -3,9 +3,9 @@ use std::io::{self, Write};
|
||||
use anyhow::{bail, Context, Result};
|
||||
|
||||
use crate::cmd::{Remove, Run};
|
||||
use crate::config;
|
||||
use crate::db::DatabaseFile;
|
||||
use crate::fzf::Fzf;
|
||||
use crate::{config, util};
|
||||
use crate::util::{self, Fzf};
|
||||
|
||||
impl Run for Remove {
|
||||
fn run(&self) -> Result<()> {
|
||||
|
@ -2,13 +2,14 @@ mod dir;
|
||||
mod stream;
|
||||
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
pub use dir::{Dir, DirList, Epoch, Rank};
|
||||
pub use stream::Stream;
|
||||
use tempfile::{NamedTempFile, PersistError};
|
||||
|
||||
use crate::util;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Database<'file> {
|
||||
@ -24,18 +25,8 @@ impl<'file> Database<'file> {
|
||||
}
|
||||
|
||||
let buffer = self.dirs.to_bytes()?;
|
||||
let mut file = NamedTempFile::new_in(self.data_dir)
|
||||
.with_context(|| format!("could not create temporary database in: {}", self.data_dir.display()))?;
|
||||
|
||||
// Preallocate enough space on the file, preventing copying later on. This optimization may
|
||||
// fail on some filesystems, but it is safe to ignore it and proceed.
|
||||
let _ = file.as_file().set_len(buffer.len() as _);
|
||||
file.write_all(&buffer)
|
||||
.with_context(|| format!("could not write to temporary database: {}", file.path().display()))?;
|
||||
|
||||
let path = db_path(&self.data_dir);
|
||||
persist(file, &path).with_context(|| format!("could not replace database: {}", path.display()))?;
|
||||
|
||||
util::write(&path, &buffer).context("could not write to database")?;
|
||||
self.modified = false;
|
||||
Ok(())
|
||||
}
|
||||
@ -120,44 +111,6 @@ impl<'file> Database<'file> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn persist<P: AsRef<Path>>(file: NamedTempFile, path: P) -> Result<(), PersistError> {
|
||||
file.persist(path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn persist<P: AsRef<Path>>(mut file: NamedTempFile, path: P) -> Result<(), PersistError> {
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
use rand::rngs::SmallRng;
|
||||
use rand::SeedableRng;
|
||||
|
||||
// File renames on Windows are not atomic and sometimes fail with `PermissionDenied`. This is
|
||||
// extremely unlikely unless it's running in a loop on multiple threads. Nevertheless, we guard
|
||||
// against it by retrying the rename a fixed number of times.
|
||||
const MAX_TRIES: usize = 10;
|
||||
let mut rng = None;
|
||||
|
||||
for _ in 0..MAX_TRIES {
|
||||
match file.persist(&path) {
|
||||
Ok(_) => break,
|
||||
Err(e) if e.error.kind() == io::ErrorKind::PermissionDenied => {
|
||||
let mut rng = rng.get_or_insert_with(SmallRng::from_entropy);
|
||||
let between = Uniform::from(50..150);
|
||||
let duration = Duration::from_millis(between.sample(&mut rng));
|
||||
thread::sleep(duration);
|
||||
file = e.file;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct DatabaseFile {
|
||||
buffer: Vec<u8>,
|
||||
data_dir: PathBuf,
|
||||
|
15
src/error.rs
15
src/error.rs
@ -1,19 +1,20 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::io;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("could not find fzf, is it installed?")]
|
||||
pub struct FzfNotFound;
|
||||
|
||||
/// Custom error type for early exit.
|
||||
#[derive(Debug, Error)]
|
||||
#[error("")]
|
||||
#[derive(Debug)]
|
||||
pub struct SilentExit {
|
||||
pub code: i32,
|
||||
}
|
||||
|
||||
impl Display for SilentExit {
|
||||
fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BrokenPipeHandler {
|
||||
fn pipe_exit(self, device: &str) -> Result<()>;
|
||||
}
|
||||
|
77
src/fzf.rs
77
src/fzf.rs
@ -1,77 +0,0 @@
|
||||
use std::io::{self, Read};
|
||||
use std::mem;
|
||||
use std::process::{Child, ChildStdin, Stdio};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
|
||||
use crate::error::{FzfNotFound, SilentExit};
|
||||
use crate::{config, util};
|
||||
|
||||
pub struct Fzf {
|
||||
child: Child,
|
||||
}
|
||||
|
||||
impl Fzf {
|
||||
pub fn new(multiple: bool) -> Result<Self> {
|
||||
let bin = if cfg!(windows) { "fzf.exe" } else { "fzf" };
|
||||
let mut command = util::get_command(bin).map_err(|_| FzfNotFound)?;
|
||||
if multiple {
|
||||
command.arg("-m");
|
||||
}
|
||||
command.arg("-n2..").stdin(Stdio::piped()).stdout(Stdio::piped());
|
||||
if let Some(fzf_opts) = config::fzf_opts() {
|
||||
command.env("FZF_DEFAULT_OPTS", fzf_opts);
|
||||
} else {
|
||||
command.args(&[
|
||||
// Search result
|
||||
"--no-sort",
|
||||
// Interface
|
||||
"--keep-right",
|
||||
// Layout
|
||||
"--height=40%",
|
||||
"--info=inline",
|
||||
"--layout=reverse",
|
||||
// Scripting
|
||||
"--exit-0",
|
||||
"--select-1",
|
||||
// Key/Event bindings
|
||||
"--bind=ctrl-z:ignore",
|
||||
]);
|
||||
if cfg!(unix) {
|
||||
command.env("SHELL", "sh");
|
||||
command.arg(r"--preview=\command -p ls -p {2..}");
|
||||
}
|
||||
}
|
||||
|
||||
let child = match command.spawn() {
|
||||
Ok(child) => child,
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => bail!(FzfNotFound),
|
||||
Err(e) => Err(e).context("could not launch fzf")?,
|
||||
};
|
||||
|
||||
Ok(Fzf { child })
|
||||
}
|
||||
|
||||
pub fn stdin(&mut self) -> &mut ChildStdin {
|
||||
self.child.stdin.as_mut().unwrap()
|
||||
}
|
||||
|
||||
pub fn select(mut self) -> Result<String> {
|
||||
// Drop stdin to prevent deadlock.
|
||||
mem::drop(self.child.stdin.take());
|
||||
|
||||
let mut stdout = self.child.stdout.take().unwrap();
|
||||
let mut output = String::new();
|
||||
stdout.read_to_string(&mut output).context("failed to read from fzf")?;
|
||||
|
||||
let status = self.child.wait().context("wait failed on fzf")?;
|
||||
match status.code() {
|
||||
Some(0) => Ok(output),
|
||||
Some(1) => bail!("no match found"),
|
||||
Some(2) => bail!("fzf returned an error"),
|
||||
Some(code @ 130) => bail!(SilentExit { code }),
|
||||
Some(128..=254) | None => bail!("fzf was terminated"),
|
||||
_ => bail!("fzf returned an unknown error"),
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ mod cmd;
|
||||
mod config;
|
||||
mod db;
|
||||
mod error;
|
||||
mod fzf;
|
||||
mod shell;
|
||||
mod util;
|
||||
|
||||
|
@ -32,7 +32,7 @@ make_template!(Powershell, "powershell.txt");
|
||||
make_template!(Xonsh, "xonsh.txt");
|
||||
make_template!(Zsh, "zsh.txt");
|
||||
|
||||
#[cfg(feature = "nix")]
|
||||
#[cfg(feature = "nix-dev")]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use askama::Template;
|
||||
|
163
src/util.rs
163
src/util.rs
@ -1,11 +1,172 @@
|
||||
use std::env;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::mem;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::process::{Child, ChildStdin, Stdio};
|
||||
use std::time::SystemTime;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
|
||||
use crate::config;
|
||||
use crate::db::Epoch;
|
||||
use crate::error::SilentExit;
|
||||
|
||||
pub struct Fzf {
|
||||
child: Child,
|
||||
}
|
||||
|
||||
impl Fzf {
|
||||
const ERR_NOT_FOUND: &'static str = "could not find fzf, is it installed?";
|
||||
|
||||
pub fn new(multiple: bool) -> Result<Self> {
|
||||
let bin = if cfg!(windows) { "fzf.exe" } else { "fzf" };
|
||||
let mut command = get_command(bin).map_err(|_| anyhow!(Self::ERR_NOT_FOUND))?;
|
||||
if multiple {
|
||||
command.arg("-m");
|
||||
}
|
||||
command.arg("-n2..").stdin(Stdio::piped()).stdout(Stdio::piped());
|
||||
if let Some(fzf_opts) = config::fzf_opts() {
|
||||
command.env("FZF_DEFAULT_OPTS", fzf_opts);
|
||||
} else {
|
||||
command.args(&[
|
||||
// Search result
|
||||
"--no-sort",
|
||||
// Interface
|
||||
"--keep-right",
|
||||
// Layout
|
||||
"--height=40%",
|
||||
"--info=inline",
|
||||
"--layout=reverse",
|
||||
// Scripting
|
||||
"--exit-0",
|
||||
"--select-1",
|
||||
// Key/Event bindings
|
||||
"--bind=ctrl-z:ignore",
|
||||
]);
|
||||
if cfg!(unix) {
|
||||
command.env("SHELL", "sh");
|
||||
command.arg(r"--preview=\command -p ls -p {2..}");
|
||||
}
|
||||
}
|
||||
|
||||
let child = match command.spawn() {
|
||||
Ok(child) => child,
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => bail!(Self::ERR_NOT_FOUND),
|
||||
Err(e) => Err(e).context("could not launch fzf")?,
|
||||
};
|
||||
|
||||
Ok(Fzf { child })
|
||||
}
|
||||
|
||||
pub fn stdin(&mut self) -> &mut ChildStdin {
|
||||
self.child.stdin.as_mut().unwrap()
|
||||
}
|
||||
|
||||
pub fn select(mut self) -> Result<String> {
|
||||
// Drop stdin to prevent deadlock.
|
||||
mem::drop(self.child.stdin.take());
|
||||
|
||||
let mut stdout = self.child.stdout.take().unwrap();
|
||||
let mut output = String::new();
|
||||
stdout.read_to_string(&mut output).context("failed to read from fzf")?;
|
||||
|
||||
let status = self.child.wait().context("wait failed on fzf")?;
|
||||
match status.code() {
|
||||
Some(0) => Ok(output),
|
||||
Some(1) => bail!("no match found"),
|
||||
Some(2) => bail!("fzf returned an error"),
|
||||
Some(code @ 130) => bail!(SilentExit { code }),
|
||||
Some(128..=254) | None => bail!("fzf was terminated"),
|
||||
_ => bail!("fzf returned an unknown error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Similar to [`fs::write`], but atomic (best effort on Windows).
|
||||
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
let contents = contents.as_ref();
|
||||
let dir = path.parent().unwrap();
|
||||
|
||||
// Create a tmpfile.
|
||||
let (mut tmp_file, tmp_path) = tmpfile(dir)?;
|
||||
let result = (|| {
|
||||
// Write to the tmpfile.
|
||||
let _ = tmp_file.set_len(contents.len() as u64);
|
||||
tmp_file.write_all(contents).with_context(|| format!("could not write to file: {}", tmp_path.display()))?;
|
||||
|
||||
// Set the owner of the tmpfile (UNIX only).
|
||||
#[cfg(unix)]
|
||||
if let Ok(metadata) = path.metadata() {
|
||||
use nix::unistd::{self, Gid, Uid};
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
let uid = Uid::from_raw(metadata.uid());
|
||||
let gid = Gid::from_raw(metadata.gid());
|
||||
let _ = unistd::fchown(tmp_file.as_raw_fd(), Some(uid), Some(gid));
|
||||
}
|
||||
|
||||
// Close and rename the tmpfile.
|
||||
mem::drop(tmp_file);
|
||||
rename(&tmp_path, path)
|
||||
})();
|
||||
// In case of an error, delete the tmpfile.
|
||||
if result.is_err() {
|
||||
let _ = fs::remove_file(&tmp_path);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Atomically create a tmpfile in the given directory.
|
||||
fn tmpfile<P: AsRef<Path>>(dir: P) -> Result<(File, PathBuf)> {
|
||||
const MAX_ATTEMPTS: usize = 5;
|
||||
const TMP_NAME_LEN: usize = 16;
|
||||
let dir = dir.as_ref();
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
attempts += 1;
|
||||
|
||||
// Generate a random name for the tmpfile.
|
||||
let mut name = String::with_capacity(TMP_NAME_LEN);
|
||||
name.push_str("tmp_");
|
||||
while name.len() < TMP_NAME_LEN {
|
||||
name.push(fastrand::alphanumeric());
|
||||
}
|
||||
let path = dir.join(name);
|
||||
|
||||
// Atomically create the tmpfile.
|
||||
match OpenOptions::new().write(true).create_new(true).open(&path) {
|
||||
Ok(file) => break Ok((file, path)),
|
||||
Err(e) if e.kind() == io::ErrorKind::AlreadyExists && attempts < MAX_ATTEMPTS => (),
|
||||
Err(e) => break Err(e).with_context(|| format!("could not create file: {}", path.display())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Similar to [`fs::rename`], but retries on Windows.
|
||||
fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
|
||||
const MAX_ATTEMPTS: usize = 5;
|
||||
let from = from.as_ref();
|
||||
let to = to.as_ref();
|
||||
|
||||
if cfg!(windows) {
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
attempts += 1;
|
||||
match fs::rename(from, to) {
|
||||
Err(e) if e.kind() == io::ErrorKind::PermissionDenied && attempts < MAX_ATTEMPTS => (),
|
||||
result => break result,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fs::rename(from, to)
|
||||
}
|
||||
.with_context(|| format!("could not rename file: {} -> {}", from.display(), to.display()))
|
||||
}
|
||||
|
||||
pub fn canonicalize<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
||||
dunce::canonicalize(path).with_context(|| format!("could not resolve path: {}", path.as_ref().display()))
|
||||
|
@ -84,8 +84,8 @@ function __zoxide_z_complete
|
||||
# If the last argument is empty, use interactive selection.
|
||||
set -l query $tokens[2..-1]
|
||||
set -l result (zoxide query -i -- $query)
|
||||
and commandline -p "$tokens[1] "(string escape $result)
|
||||
commandline -f repaint
|
||||
commandline --current-process "$tokens[1] "(string escape $result)
|
||||
commandline --function repaint
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
//! Test clap generated completions.
|
||||
#![cfg(feature = "nix")]
|
||||
#![cfg(feature = "nix-dev")]
|
||||
|
||||
use assert_cmd::Command;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user