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}`")?;
let path = self.local();
if !must_exists(&path).await {
Git::clone(&self.remote(), &path).await?;
} else {
if must_exists(&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
}
}

View file

@ -1,4 +1,4 @@
use anyhow::{Context, Result, bail};
use anyhow::{Context, Result};
use tokio::fs;
use yazi_fs::{maybe_exists, ok_or_not_found, remove_dir_clean, remove_sealed};
use yazi_macro::outln;
@ -14,36 +14,9 @@ impl Dependency {
return Ok(outln!("Not found, skipping")?);
}
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: {}",
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.hash_check().await?;
self.delete_assets().await?;
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()
)?;
}
self.delete_sources().await?;
Ok(())
}
@ -65,4 +38,29 @@ For safety, user data has been preserved, please manually delete them within: {}
remove_dir_clean(&assets).await;
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 anyhow::{Context, Result, bail};
use anyhow::{Context, Result};
use tokio::fs;
use yazi_fs::{copy_and_seal, maybe_exists, remove_dir_clean};
use yazi_macro::outln;
@ -15,44 +15,27 @@ impl Dependency {
self.is_flavor = maybe_exists(&from.join("flavor.toml")).await;
let to = self.target();
if maybe_exists(&to).await && 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" },
to.display()
);
let exists = maybe_exists(&to).await;
if exists {
self.hash_check().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::deploy_assets(from.join("assets"), to.join("assets")).await?;
self.hash = self.hash().await?;
outln!("Done!")?;
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()))?;
let res1 = Self::deploy_assets(from.join("assets"), to.join("assets")).await;
let res2 = Self::deploy_sources(&from, &to, self.is_flavor).await;
if !exists && (res2.is_err() || res1.is_err()) {
self.delete_assets().await?;
self.delete_sources().await?;
}
remove_dir_clean(&to).await;
self.hash = self.hash().await?;
res2?;
res1?;
outln!("Done!")?;
Ok(())
}
@ -72,4 +55,20 @@ Please manually delete it from `{}` and re-run the command.",
}
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(())
}
pub(super) async fn hash(path: &Path) -> Result<String> {
pub(super) async fn revision(path: &Path) -> Result<String> {
let output = Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.current_dir(path)

View file

@ -52,4 +52,17 @@ impl Dependency {
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() {
self.rev = Git::hash(&path).await?;
self.rev = Git::revision(&path).await?;
} else {
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<()> {
for u in uses {
if let Err(e) = self.add(u).await {
self.save().await?;
return Err(e);
}
let r = self.add(u).await;
self.save().await?;
r?;
}
self.save().await
Ok(())
}
pub(crate) async fn delete_many(&mut self, uses: &[String]) -> Result<()> {
for u in uses {
if let Err(e) = self.delete(u).await {
self.save().await?;
return Err(e);
}
let r = self.delete(u).await;
self.save().await?;
r?;
}
self.save().await
Ok(())
}
pub(crate) async fn install(&mut self, upgrade: bool) -> Result<()> {
for d in &mut self.plugins {
if upgrade {
d.upgrade().await?;
} else {
d.install().await?;
}
for i in 0..self.plugins.len() {
let r =
if upgrade { self.plugins[i].upgrade().await } else { self.plugins[i].install().await };
self.save().await?;
r?;
}
for d in &mut self.flavors {
if upgrade {
d.upgrade().await?;
} else {
d.install().await?;
}
for i in 0..self.flavors.len() {
let r =
if upgrade { self.flavors[i].upgrade().await } else { self.flavors[i].install().await };
self.save().await?;
r?;
}
self.save().await
Ok(())
}
pub(crate) fn print(&self) -> Result<()> {