mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-24 22:30:03 +00:00
feat: git::Repository
cancellation support (#1288)
feat: `git::Repository` support cancellation. To make sure users can cancel git operation via signal, e.g. when the git operation fail or users no longer want to install. Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
parent
ef99dd795f
commit
fbed317df5
7 changed files with 113 additions and 16 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -290,6 +290,7 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"compact_str",
|
"compact_str",
|
||||||
|
"derive_destructure2",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
|
@ -756,6 +757,17 @@ version = "0.3.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929"
|
checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_destructure2"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35cb7e5875e1028a73e551747d6d0118f25c3d6dbba2dadf97cc0f4d0c53f2f5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "detect-targets"
|
name = "detect-targets"
|
||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
|
|
|
@ -17,6 +17,7 @@ binstalk-types = { version = "0.5.0", path = "../binstalk-types" }
|
||||||
bytes = "1.4.0"
|
bytes = "1.4.0"
|
||||||
bzip2 = "0.4.4"
|
bzip2 = "0.4.4"
|
||||||
compact_str = "0.7.0"
|
compact_str = "0.7.0"
|
||||||
|
derive_destructure2 = "0.1"
|
||||||
flate2 = { version = "1.0.26", default-features = false }
|
flate2 = { version = "1.0.26", default-features = false }
|
||||||
futures-util = "0.3.28"
|
futures-util = "0.3.28"
|
||||||
generic-array = "0.14.7"
|
generic-array = "0.14.7"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::{fmt, mem, num::NonZeroU32, path::Path, str::FromStr, sync::atomic::AtomicBool};
|
use std::{fmt, mem, num::NonZeroU32, path::Path, str::FromStr, sync::atomic::AtomicBool};
|
||||||
|
|
||||||
use compact_str::CompactString;
|
|
||||||
use gix::{clone, create, open, remote, Url};
|
use gix::{clone, create, open, remote, Url};
|
||||||
use thiserror::Error as ThisError;
|
use thiserror::Error as ThisError;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
@ -8,6 +7,9 @@ use tracing::debug;
|
||||||
mod progress_tracing;
|
mod progress_tracing;
|
||||||
use progress_tracing::TracingProgress;
|
use progress_tracing::TracingProgress;
|
||||||
|
|
||||||
|
mod cancellation_token;
|
||||||
|
pub use cancellation_token::{GitCancelOnDrop, GitCancellationToken};
|
||||||
|
|
||||||
pub use gix::url::parse::Error as GitUrlParseError;
|
pub use gix::url::parse::Error as GitUrlParseError;
|
||||||
|
|
||||||
#[derive(Debug, ThisError)]
|
#[derive(Debug, ThisError)]
|
||||||
|
@ -116,14 +118,21 @@ impl Repository {
|
||||||
/// async context then you must wrap the call in [`tokio::task::spawn_blocking`].
|
/// async context then you must wrap the call in [`tokio::task::spawn_blocking`].
|
||||||
///
|
///
|
||||||
/// WARNING: This function must be called after tokio runtime is initialized.
|
/// WARNING: This function must be called after tokio runtime is initialized.
|
||||||
pub fn shallow_clone_bare(url: GitUrl, path: &Path) -> Result<Self, GitError> {
|
pub fn shallow_clone_bare(
|
||||||
|
url: GitUrl,
|
||||||
|
path: &Path,
|
||||||
|
cancellation_token: Option<GitCancellationToken>,
|
||||||
|
) -> Result<Self, GitError> {
|
||||||
debug!("Shallow cloning {url} to {}", path.display());
|
debug!("Shallow cloning {url} to {}", path.display());
|
||||||
|
|
||||||
Ok(Self(
|
Ok(Self(
|
||||||
Self::prepare_fetch(url, path, create::Kind::Bare)?
|
Self::prepare_fetch(url, path, create::Kind::Bare)?
|
||||||
.fetch_only(
|
.fetch_only(
|
||||||
&mut TracingProgress::new(CompactString::new("Cloning")),
|
&mut TracingProgress::new("Cloning bare"),
|
||||||
&AtomicBool::new(false),
|
cancellation_token
|
||||||
|
.as_ref()
|
||||||
|
.map(GitCancellationToken::get_atomic)
|
||||||
|
.unwrap_or(&AtomicBool::new(false)),
|
||||||
)?
|
)?
|
||||||
.0
|
.0
|
||||||
.into(),
|
.into(),
|
||||||
|
@ -134,16 +143,26 @@ impl Repository {
|
||||||
/// async context then you must wrap the call in [`tokio::task::spawn_blocking`].
|
/// async context then you must wrap the call in [`tokio::task::spawn_blocking`].
|
||||||
///
|
///
|
||||||
/// WARNING: This function must be called after tokio runtime is initialized.
|
/// WARNING: This function must be called after tokio runtime is initialized.
|
||||||
pub fn shallow_clone(url: GitUrl, path: &Path) -> Result<Self, GitError> {
|
pub fn shallow_clone(
|
||||||
|
url: GitUrl,
|
||||||
|
path: &Path,
|
||||||
|
cancellation_token: Option<GitCancellationToken>,
|
||||||
|
) -> Result<Self, GitError> {
|
||||||
debug!("Shallow cloning {url} to {} with worktree", path.display());
|
debug!("Shallow cloning {url} to {} with worktree", path.display());
|
||||||
|
|
||||||
let mut progress = TracingProgress::new(CompactString::new("Cloning"));
|
let mut progress = TracingProgress::new("Cloning with worktree");
|
||||||
|
|
||||||
Ok(Self(
|
Ok(Self(
|
||||||
Self::prepare_fetch(url, path, create::Kind::WithWorktree)?
|
Self::prepare_fetch(url, path, create::Kind::WithWorktree)?
|
||||||
.fetch_then_checkout(&mut progress, &AtomicBool::new(false))?
|
.fetch_then_checkout(&mut progress, &AtomicBool::new(false))?
|
||||||
.0
|
.0
|
||||||
.main_worktree(&mut progress, &AtomicBool::new(false))?
|
.main_worktree(
|
||||||
|
&mut progress,
|
||||||
|
cancellation_token
|
||||||
|
.as_ref()
|
||||||
|
.map(GitCancellationToken::get_atomic)
|
||||||
|
.unwrap_or(&AtomicBool::new(false)),
|
||||||
|
)?
|
||||||
.0
|
.0
|
||||||
.into(),
|
.into(),
|
||||||
))
|
))
|
||||||
|
|
44
crates/binstalk-downloader/src/git/cancellation_token.rs
Normal file
44
crates/binstalk-downloader/src/git/cancellation_token.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicBool, Ordering::Relaxed},
|
||||||
|
Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use derive_destructure2::destructure;
|
||||||
|
|
||||||
|
/// Token that can be used to cancel git operation.
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct GitCancellationToken(Arc<AtomicBool>);
|
||||||
|
|
||||||
|
impl GitCancellationToken {
|
||||||
|
/// Create a guard that cancel the git operation on drop.
|
||||||
|
#[must_use = "You must assign the guard to a variable, \
|
||||||
|
otherwise it is equivalent to `GitCancellationToken::cancel()`"]
|
||||||
|
pub fn cancel_on_drop(self) -> GitCancelOnDrop {
|
||||||
|
GitCancelOnDrop(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cancel the git operation.
|
||||||
|
pub fn cancel(&self) {
|
||||||
|
self.0.store(true, Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_atomic(&self) -> &AtomicBool {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Guard used to cancel git operation on drop
|
||||||
|
#[derive(Debug, destructure)]
|
||||||
|
pub struct GitCancelOnDrop(GitCancellationToken);
|
||||||
|
|
||||||
|
impl Drop for GitCancelOnDrop {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl GitCancelOnDrop {
|
||||||
|
/// Disarm the guard, return the token.
|
||||||
|
pub fn disarm(self) -> GitCancellationToken {
|
||||||
|
self.destructure().0
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ const SEP: &str = "::";
|
||||||
|
|
||||||
impl TracingProgress {
|
impl TracingProgress {
|
||||||
/// Create a new instanCompactce from `name`.
|
/// Create a new instanCompactce from `name`.
|
||||||
pub fn new(name: CompactString) -> Self {
|
pub fn new(name: &str) -> Self {
|
||||||
let trigger = Arc::new(AtomicBool::new(true));
|
let trigger = Arc::new(AtomicBool::new(true));
|
||||||
tokio::spawn({
|
tokio::spawn({
|
||||||
let mut interval = time::interval(Duration::from_secs_f32(EMIT_LOG_EVERY_S));
|
let mut interval = time::interval(Duration::from_secs_f32(EMIT_LOG_EVERY_S));
|
||||||
|
@ -43,7 +43,7 @@ impl TracingProgress {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Self {
|
Self {
|
||||||
name,
|
name: CompactString::new(name),
|
||||||
id: UNKNOWN,
|
id: UNKNOWN,
|
||||||
max: None,
|
max: None,
|
||||||
step: 0,
|
step: 0,
|
||||||
|
|
|
@ -16,7 +16,7 @@ use crate::{
|
||||||
errors::BinstallError,
|
errors::BinstallError,
|
||||||
helpers::{
|
helpers::{
|
||||||
cargo_toml::Manifest,
|
cargo_toml::Manifest,
|
||||||
git::{GitUrl, Repository},
|
git::{GitCancellationToken, GitUrl, Repository},
|
||||||
remote::Client,
|
remote::Client,
|
||||||
},
|
},
|
||||||
manifests::cargo_toml_binstall::Meta,
|
manifests::cargo_toml_binstall::Meta,
|
||||||
|
@ -30,10 +30,14 @@ struct GitIndex {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GitIndex {
|
impl GitIndex {
|
||||||
fn new(url: GitUrl) -> Result<Self, BinstallError> {
|
fn new(url: GitUrl, cancellation_token: GitCancellationToken) -> Result<Self, BinstallError> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
|
||||||
let repo = Repository::shallow_clone_bare(url.clone(), tempdir.as_ref())?;
|
let repo = Repository::shallow_clone_bare(
|
||||||
|
url.clone(),
|
||||||
|
tempdir.as_ref(),
|
||||||
|
Some(cancellation_token),
|
||||||
|
)?;
|
||||||
|
|
||||||
let config: RegistryConfig = {
|
let config: RegistryConfig = {
|
||||||
let config = repo
|
let config = repo
|
||||||
|
@ -108,6 +112,10 @@ impl GitRegistry {
|
||||||
let version_req = version_req.clone();
|
let version_req = version_req.clone();
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
|
|
||||||
|
let cancellation_token = GitCancellationToken::default();
|
||||||
|
// Cancel git operation if the future is cancelled (dropped).
|
||||||
|
let cancel_on_drop = cancellation_token.clone().cancel_on_drop();
|
||||||
|
|
||||||
let (matched_version, dl_url) = spawn_blocking(move || {
|
let (matched_version, dl_url) = spawn_blocking(move || {
|
||||||
let GitIndex {
|
let GitIndex {
|
||||||
_tempdir: _,
|
_tempdir: _,
|
||||||
|
@ -116,7 +124,7 @@ impl GitRegistry {
|
||||||
} = this
|
} = this
|
||||||
.0
|
.0
|
||||||
.git_index
|
.git_index
|
||||||
.get_or_try_init(|| GitIndex::new(this.0.url.clone()))?;
|
.get_or_try_init(|| GitIndex::new(this.0.url.clone(), cancellation_token))?;
|
||||||
|
|
||||||
let matched_version =
|
let matched_version =
|
||||||
Self::find_crate_matched_ver(repo, &crate_name, &crate_prefix, &version_req)?;
|
Self::find_crate_matched_ver(repo, &crate_name, &crate_prefix, &version_req)?;
|
||||||
|
@ -132,6 +140,9 @@ impl GitRegistry {
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
|
// Git operation done, disarm it
|
||||||
|
cancel_on_drop.disarm();
|
||||||
|
|
||||||
parse_manifest(client, name, dl_url, matched_version).await
|
parse_manifest(client, name, dl_url, matched_version).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -373,16 +373,26 @@ impl PackageInfo {
|
||||||
}
|
}
|
||||||
#[cfg(feature = "git")]
|
#[cfg(feature = "git")]
|
||||||
Some(Git(git_url)) => {
|
Some(Git(git_url)) => {
|
||||||
|
use helpers::git::{GitCancellationToken, Repository as GitRepository};
|
||||||
|
|
||||||
let git_url = git_url.clone();
|
let git_url = git_url.clone();
|
||||||
let name = name.clone();
|
let name = name.clone();
|
||||||
|
let cancellation_token = GitCancellationToken::default();
|
||||||
|
// Cancel git operation if the future is cancelled (dropped).
|
||||||
|
let cancel_on_drop = cancellation_token.clone().cancel_on_drop();
|
||||||
|
|
||||||
spawn_blocking(move || {
|
let ret = spawn_blocking(move || {
|
||||||
let dir = TempDir::new()?;
|
let dir = TempDir::new()?;
|
||||||
helpers::git::Repository::shallow_clone(git_url, dir.as_ref())?;
|
GitRepository::shallow_clone(git_url, dir.as_ref(), Some(cancellation_token))?;
|
||||||
|
|
||||||
load_manifest_from_workspace(dir.as_ref(), &name).map_err(BinstallError::from)
|
load_manifest_from_workspace(dir.as_ref(), &name).map_err(BinstallError::from)
|
||||||
})
|
})
|
||||||
.await??
|
.await??;
|
||||||
|
|
||||||
|
// Git operation done, disarm it
|
||||||
|
cancel_on_drop.disarm();
|
||||||
|
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
Box::pin(
|
Box::pin(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue