aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLucas Trzesniewski <lucas.trzesniewski@gmail.com>2026-05-04 22:12:12 +0200
committerGitHub <noreply@github.com>2026-05-04 13:12:12 -0700
commit9609759d775c8851d84eadf983c51db2798ebc81 (patch)
tree5edfa3bbc42f93cdbde7ecbe503f70a32ee18bbd
parentchore(release): prepare for release 18.16.0 (#3457) (diff)
downloadatuin-9609759d775c8851d84eadf983c51db2798ebc81.zip
fix: atuin update on windows (#3453)
This fixes the `atuin update` command on Windows. Windows doesn't let you overwrite a running exe, but it lets you rename it. This PR special-cases the official `update` plugin by renaming the running `atuin.exe` to `atuin.old` before the update, and rolling it back if the update fails. Note that the `atuin.old` file is left behind on success, which shouldn't be a problem in practice: it will be overwritten on the next call to `atuin update` (also deleted if there's no update available), and is located in `~/.atuin/bin`, which is an isolated location specific to Atuin. Fixes #3451 ## Checks - [x] I am happy for maintainers to push small adjustments to this PR, to speed up the review cycle - [x] I have checked that there are no existing pull requests for the same thing
-rw-r--r--crates/atuin-client/src/plugin.rs55
-rw-r--r--crates/atuin/src/command/client/daemon.rs1
-rw-r--r--crates/atuin/src/command/external.rs12
3 files changed, 66 insertions, 2 deletions
diff --git a/crates/atuin-client/src/plugin.rs b/crates/atuin-client/src/plugin.rs
index 21a2bcef..6f351bf1 100644
--- a/crates/atuin-client/src/plugin.rs
+++ b/crates/atuin-client/src/plugin.rs
@@ -42,7 +42,7 @@ impl OfficialPluginRegistry {
"Update atuin to the latest version",
"The 'atuin update' command is provided by the atuin-update plugin.\n\
It is only installed if you used the install script\n \
- If you used a package manager (brew, apt, etc), please continue to use it for updates"
+ If you used a package manager (brew, apt, etc), please continue to use it for updates",
),
);
}
@@ -68,6 +68,59 @@ impl Default for OfficialPluginRegistry {
}
}
+pub struct PluginContext {
+ #[cfg(windows)]
+ _update_on_windows: Option<UpdateOnWindowsContext>,
+}
+
+impl PluginContext {
+ pub fn new(_subcommand: &str) -> Self {
+ PluginContext {
+ #[cfg(windows)]
+ _update_on_windows: (_subcommand == "update").then(UpdateOnWindowsContext::new),
+ }
+ }
+}
+
+impl Drop for PluginContext {
+ fn drop(&mut self) {}
+}
+
+#[cfg(windows)]
+struct UpdateOnWindowsContext {
+ initial_exe: Option<std::path::PathBuf>,
+}
+
+#[cfg(windows)]
+impl UpdateOnWindowsContext {
+ const OLD_FILE_NAME: &'static str = "atuin.old";
+
+ pub fn new() -> Self {
+ // Windows doesn't let you overwrite a running exe, but it lets you rename it,
+ // so make some room for atuin-update to install the new version.
+ let initial_exe = std::env::current_exe().ok().and_then(|exe| {
+ std::fs::rename(&exe, exe.with_file_name(Self::OLD_FILE_NAME)).ok()?;
+ Some(exe)
+ });
+
+ Self { initial_exe }
+ }
+}
+
+#[cfg(windows)]
+impl Drop for UpdateOnWindowsContext {
+ fn drop(&mut self) {
+ if let Some(exe) = &self.initial_exe
+ && !exe.exists()
+ {
+ // The update failed, roll back the current exe to its initial name.
+ std::fs::rename(exe.with_file_name(Self::OLD_FILE_NAME), exe).unwrap_or_else(|e| {
+ eprintln!("Failed to roll back the update, you may need to reinstall Atuin: {e}");
+ });
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/crates/atuin/src/command/client/daemon.rs b/crates/atuin/src/command/client/daemon.rs
index cd769373..483829e9 100644
--- a/crates/atuin/src/command/client/daemon.rs
+++ b/crates/atuin/src/command/client/daemon.rs
@@ -331,6 +331,7 @@ async fn wait_until_ready(settings: &Settings, timeout: Duration) -> Result<Hist
}
}
+#[allow(clippy::unnecessary_wraps)]
fn ensure_autostart_supported(settings: &Settings) -> Result<()> {
#[cfg(unix)]
if settings.daemon.systemd_socket {
diff --git a/crates/atuin/src/command/external.rs b/crates/atuin/src/command/external.rs
index 657aea56..5d875e9d 100644
--- a/crates/atuin/src/command/external.rs
+++ b/crates/atuin/src/command/external.rs
@@ -3,7 +3,7 @@ use std::process::Command;
use std::{io, process};
#[cfg(feature = "client")]
-use atuin_client::plugin::OfficialPluginRegistry;
+use atuin_client::plugin::{OfficialPluginRegistry, PluginContext};
use clap::CommandFactory;
use clap::builder::{StyledStr, Styles};
use eyre::Result;
@@ -16,6 +16,9 @@ pub fn run(args: &[String]) -> Result<()> {
let mut cmd = Command::new(&bin);
cmd.args(&args[1..]);
+ #[cfg(feature = "client")]
+ let context = PluginContext::new(subcommand);
+
let spawn_result = match cmd.spawn() {
Ok(child) => Ok(child),
Err(e) => match e.kind() {
@@ -33,11 +36,18 @@ pub fn run(args: &[String]) -> Result<()> {
if status.success() {
Ok(())
} else {
+ #[cfg(feature = "client")]
+ drop(context);
+
process::exit(status.code().unwrap_or(1));
}
}
Err(e) => {
eprintln!("{}", e.ansi());
+
+ #[cfg(feature = "client")]
+ drop(context);
+
process::exit(1);
}
}