fix: ensure state synchronization even when the package update fails midway (#2613)

This commit is contained in:
三咲雅 · Misaki Masa 2025-04-13 14:46:16 +08:00 committed by GitHub
parent af92b92da8
commit d2cc73c927
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 98 additions and 93 deletions

View file

@ -8,13 +8,13 @@ impl Dependency {
self.header("Upgrading package `{name}`")?; self.header("Upgrading package `{name}`")?;
let path = self.local(); let path = self.local();
if !must_exists(&path).await { if must_exists(&path).await {
Git::clone(&self.remote(), &path).await?;
} else {
Git::pull(&path).await?; Git::pull(&path).await?;
} else {
Git::clone(&self.remote(), &path).await?;
}; };
self.rev = Git::hash(&path).await?; self.rev = Git::revision(&path).await?;
self.deploy().await self.deploy().await
} }
} }

View file

@ -1,4 +1,4 @@
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result};
use tokio::fs; use tokio::fs;
use yazi_fs::{maybe_exists, ok_or_not_found, remove_dir_clean, remove_sealed}; use yazi_fs::{maybe_exists, ok_or_not_found, remove_dir_clean, remove_sealed};
use yazi_macro::outln; use yazi_macro::outln;
@ -14,36 +14,9 @@ impl Dependency {
return Ok(outln!("Not found, skipping")?); return Ok(outln!("Not found, skipping")?);
} }
if self.hash != self.hash().await? { self.hash_check().await?;
bail!(
"You have modified the contents of the `{}` {}. For safety, the operation has been aborted.
Please manually delete it from: {}",
self.name,
if self.is_flavor { "flavor" } else { "plugin" },
dir.display()
);
}
let files = if self.is_flavor {
&["flavor.toml", "tmtheme.xml", "README.md", "preview.png", "LICENSE", "LICENSE-tmtheme"][..]
} else {
&["main.lua", "README.md", "LICENSE"][..]
};
for p in files.iter().map(|&f| dir.join(f)) {
ok_or_not_found(remove_sealed(&p).await)
.with_context(|| format!("failed to delete `{}`", p.display()))?;
}
self.delete_assets().await?; self.delete_assets().await?;
if ok_or_not_found(fs::remove_dir(&dir).await).is_ok() { self.delete_sources().await?;
outln!("Done!")?;
} else {
outln!(
"Done!
For safety, user data has been preserved, please manually delete them within: {}",
dir.display()
)?;
}
Ok(()) Ok(())
} }
@ -65,4 +38,29 @@ For safety, user data has been preserved, please manually delete them within: {}
remove_dir_clean(&assets).await; remove_dir_clean(&assets).await;
Ok(()) Ok(())
} }
pub(super) async fn delete_sources(&self) -> Result<()> {
let dir = self.target();
let files = if self.is_flavor {
&["flavor.toml", "tmtheme.xml", "README.md", "preview.png", "LICENSE", "LICENSE-tmtheme"][..]
} else {
&["main.lua", "README.md", "LICENSE"][..]
};
for p in files.iter().map(|&f| dir.join(f)) {
ok_or_not_found(remove_sealed(&p).await)
.with_context(|| format!("failed to delete `{}`", p.display()))?;
}
if ok_or_not_found(fs::remove_dir(&dir).await).is_ok() {
outln!("Done!")?;
} else {
outln!(
"Done!
For safety, user data has been preserved, please manually delete them within: {}",
dir.display()
)?;
}
Ok(())
}
} }

View file

@ -1,6 +1,6 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result};
use tokio::fs; use tokio::fs;
use yazi_fs::{copy_and_seal, maybe_exists, remove_dir_clean}; use yazi_fs::{copy_and_seal, maybe_exists, remove_dir_clean};
use yazi_macro::outln; use yazi_macro::outln;
@ -15,44 +15,27 @@ impl Dependency {
self.is_flavor = maybe_exists(&from.join("flavor.toml")).await; self.is_flavor = maybe_exists(&from.join("flavor.toml")).await;
let to = self.target(); let to = self.target();
if maybe_exists(&to).await && self.hash != self.hash().await? { let exists = maybe_exists(&to).await;
bail!( if exists {
"You have modified the contents of the `{}` {}. For safety, the operation has been aborted. self.hash_check().await?;
Please manually delete it from `{}` and re-run the command.",
self.name,
if self.is_flavor { "flavor" } else { "plugin" },
to.display()
);
} }
fs::create_dir_all(&to).await?; fs::create_dir_all(&to).await?;
if let Err(e) = Self::deploy_sources(&from, &to, self.is_flavor).await {
remove_dir_clean(&to).await;
return Err(e);
}
self.delete_assets().await?; self.delete_assets().await?;
Self::deploy_assets(from.join("assets"), to.join("assets")).await?;
self.hash = self.hash().await?; let res1 = Self::deploy_assets(from.join("assets"), to.join("assets")).await;
outln!("Done!")?; let res2 = Self::deploy_sources(&from, &to, self.is_flavor).await;
if !exists && (res2.is_err() || res1.is_err()) {
Ok(()) self.delete_assets().await?;
} self.delete_sources().await?;
async fn deploy_sources(from: &Path, to: &Path, is_flavor: bool) -> Result<()> {
let files = if is_flavor {
&["flavor.toml", "tmtheme.xml", "README.md", "preview.png", "LICENSE", "LICENSE-tmtheme"][..]
} else {
&["main.lua", "README.md", "LICENSE"][..]
};
for file in files {
let (from, to) = (from.join(file), to.join(file));
copy_and_seal(&from, &to)
.await
.with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()))?;
} }
remove_dir_clean(&to).await;
self.hash = self.hash().await?;
res2?;
res1?;
outln!("Done!")?;
Ok(()) Ok(())
} }
@ -72,4 +55,20 @@ Please manually delete it from `{}` and re-run the command.",
} }
Ok(()) Ok(())
} }
async fn deploy_sources(from: &Path, to: &Path, is_flavor: bool) -> Result<()> {
let files = if is_flavor {
&["flavor.toml", "tmtheme.xml", "README.md", "preview.png", "LICENSE", "LICENSE-tmtheme"][..]
} else {
&["main.lua", "README.md", "LICENSE"][..]
};
for file in files {
let (from, to) = (from.join(file), to.join(file));
copy_and_seal(&from, &to)
.await
.with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()))?;
}
Ok(())
}
} }

View file

@ -25,7 +25,7 @@ impl Git {
Ok(()) Ok(())
} }
pub(super) async fn hash(path: &Path) -> Result<String> { pub(super) async fn revision(path: &Path) -> Result<String> {
let output = Command::new("git") let output = Command::new("git")
.args(["rev-parse", "--short", "HEAD"]) .args(["rev-parse", "--short", "HEAD"])
.current_dir(path) .current_dir(path)

View file

@ -52,4 +52,17 @@ impl Dependency {
Ok(format!("{:x}", h.finish_128())) Ok(format!("{:x}", h.finish_128()))
} }
pub(super) async fn hash_check(&self) -> Result<()> {
if self.hash != self.hash().await? {
bail!(
"You have modified the contents of the `{}` {}. For safety, the operation has been aborted.
Please manually delete it from `{}` and re-run the command.",
self.name,
if self.is_flavor { "flavor" } else { "plugin" },
self.target().display()
);
}
Ok(())
}
} }

View file

@ -15,7 +15,7 @@ impl Dependency {
}; };
if self.rev.is_empty() { if self.rev.is_empty() {
self.rev = Git::hash(&path).await?; self.rev = Git::revision(&path).await?;
} else { } else {
Git::checkout(&path, self.rev.trim_start_matches('=')).await?; Git::checkout(&path, self.rev.trim_start_matches('=')).await?;
} }

View file

@ -25,41 +25,36 @@ impl Package {
pub(crate) async fn add_many(&mut self, uses: &[String]) -> Result<()> { pub(crate) async fn add_many(&mut self, uses: &[String]) -> Result<()> {
for u in uses { for u in uses {
if let Err(e) = self.add(u).await { let r = self.add(u).await;
self.save().await?; self.save().await?;
return Err(e); r?;
}
} }
self.save().await Ok(())
} }
pub(crate) async fn delete_many(&mut self, uses: &[String]) -> Result<()> { pub(crate) async fn delete_many(&mut self, uses: &[String]) -> Result<()> {
for u in uses { for u in uses {
if let Err(e) = self.delete(u).await { let r = self.delete(u).await;
self.save().await?; self.save().await?;
return Err(e); r?;
}
} }
self.save().await Ok(())
} }
pub(crate) async fn install(&mut self, upgrade: bool) -> Result<()> { pub(crate) async fn install(&mut self, upgrade: bool) -> Result<()> {
for d in &mut self.plugins { for i in 0..self.plugins.len() {
if upgrade { let r =
d.upgrade().await?; if upgrade { self.plugins[i].upgrade().await } else { self.plugins[i].install().await };
} else { self.save().await?;
d.install().await?; r?;
}
} }
for d in &mut self.flavors { for i in 0..self.flavors.len() {
if upgrade { let r =
d.upgrade().await?; if upgrade { self.flavors[i].upgrade().await } else { self.flavors[i].install().await };
} else { self.save().await?;
d.install().await?; r?;
}
} }
Ok(())
self.save().await
} }
pub(crate) fn print(&self) -> Result<()> { pub(crate) fn print(&self) -> Result<()> {