mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-24 14:28:42 +00:00
Refactor: Move mod git
into binstalk-downloader
(#1285)
To speedup codegen time for `binstalk`, also fixed the docs.rs build for `binstalk-downloader`. Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
parent
6a1d317fde
commit
c57356e870
7 changed files with 14 additions and 7 deletions
175
crates/binstalk-downloader/src/git.rs
Normal file
175
crates/binstalk-downloader/src/git.rs
Normal file
|
@ -0,0 +1,175 @@
|
|||
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 thiserror::Error as ThisError;
|
||||
use tracing::debug;
|
||||
|
||||
mod progress_tracing;
|
||||
use progress_tracing::TracingProgress;
|
||||
|
||||
pub use gix::url::parse::Error as GitUrlParseError;
|
||||
|
||||
#[derive(Debug, ThisError)]
|
||||
#[non_exhaustive]
|
||||
pub enum GitError {
|
||||
#[error("Failed to prepare for fetch: {0}")]
|
||||
PrepareFetchError(#[source] Box<clone::Error>),
|
||||
|
||||
#[error("Failed to fetch: {0}")]
|
||||
FetchError(#[source] Box<clone::fetch::Error>),
|
||||
|
||||
#[error("Failed to checkout: {0}")]
|
||||
CheckOutError(#[source] Box<clone::checkout::main_worktree::Error>),
|
||||
|
||||
#[error("HEAD ref was corrupt in crates-io index repository clone")]
|
||||
HeadCommit(#[source] Box<gix::reference::head_commit::Error>),
|
||||
|
||||
#[error("tree of head commit wasn't present in crates-io index repository clone")]
|
||||
GetTreeOfCommit(#[source] Box<gix::object::commit::Error>),
|
||||
|
||||
#[error("An object was missing in the crates-io index repository clone")]
|
||||
ObjectLookup(#[source] Box<gix::object::find::existing::Error>),
|
||||
}
|
||||
|
||||
impl From<clone::Error> for GitError {
|
||||
fn from(e: clone::Error) -> Self {
|
||||
Self::PrepareFetchError(Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<clone::fetch::Error> for GitError {
|
||||
fn from(e: clone::fetch::Error) -> Self {
|
||||
Self::FetchError(Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<clone::checkout::main_worktree::Error> for GitError {
|
||||
fn from(e: clone::checkout::main_worktree::Error) -> Self {
|
||||
Self::CheckOutError(Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<gix::reference::head_commit::Error> for GitError {
|
||||
fn from(e: gix::reference::head_commit::Error) -> Self {
|
||||
Self::HeadCommit(Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<gix::object::commit::Error> for GitError {
|
||||
fn from(e: gix::object::commit::Error) -> Self {
|
||||
Self::GetTreeOfCommit(Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<gix::object::find::existing::Error> for GitError {
|
||||
fn from(e: gix::object::find::existing::Error) -> Self {
|
||||
Self::ObjectLookup(Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GitUrl(Url);
|
||||
|
||||
impl fmt::Display for GitUrl {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let url_bstr = self.0.to_bstring();
|
||||
let url_str = String::from_utf8_lossy(&url_bstr);
|
||||
|
||||
f.write_str(&url_str)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for GitUrl {
|
||||
type Err = GitUrlParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Url::try_from(s).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Repository(gix::ThreadSafeRepository);
|
||||
|
||||
impl Repository {
|
||||
fn prepare_fetch(
|
||||
url: GitUrl,
|
||||
path: &Path,
|
||||
kind: create::Kind,
|
||||
) -> Result<clone::PrepareFetch, GitError> {
|
||||
Ok(clone::PrepareFetch::new(
|
||||
url.0,
|
||||
path,
|
||||
kind,
|
||||
create::Options {
|
||||
destination_must_be_empty: true,
|
||||
..Default::default()
|
||||
},
|
||||
open::Options::isolated(),
|
||||
)?
|
||||
.with_shallow(remote::fetch::Shallow::DepthAtRemote(
|
||||
NonZeroU32::new(1).unwrap(),
|
||||
)))
|
||||
}
|
||||
|
||||
/// WARNING: This is a blocking operation, if you want to use it in
|
||||
/// async context then you must wrap the call in [`tokio::task::spawn_blocking`].
|
||||
///
|
||||
/// WARNING: This function must be called after tokio runtime is initialized.
|
||||
pub fn shallow_clone_bare(url: GitUrl, path: &Path) -> Result<Self, GitError> {
|
||||
debug!("Shallow cloning {url} to {}", path.display());
|
||||
|
||||
Ok(Self(
|
||||
Self::prepare_fetch(url, path, create::Kind::Bare)?
|
||||
.fetch_only(
|
||||
&mut TracingProgress::new(CompactString::new("Cloning")),
|
||||
&AtomicBool::new(false),
|
||||
)?
|
||||
.0
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
|
||||
/// WARNING: This is a blocking operation, if you want to use it in
|
||||
/// async context then you must wrap the call in [`tokio::task::spawn_blocking`].
|
||||
///
|
||||
/// WARNING: This function must be called after tokio runtime is initialized.
|
||||
pub fn shallow_clone(url: GitUrl, path: &Path) -> Result<Self, GitError> {
|
||||
debug!("Shallow cloning {url} to {} with worktree", path.display());
|
||||
|
||||
let mut progress = TracingProgress::new(CompactString::new("Cloning"));
|
||||
|
||||
Ok(Self(
|
||||
Self::prepare_fetch(url, path, create::Kind::WithWorktree)?
|
||||
.fetch_then_checkout(&mut progress, &AtomicBool::new(false))?
|
||||
.0
|
||||
.main_worktree(&mut progress, &AtomicBool::new(false))?
|
||||
.0
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_head_commit_entry_data_by_path(
|
||||
&self,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<Option<Vec<u8>>, GitError> {
|
||||
fn inner(this: &Repository, path: &Path) -> Result<Option<Vec<u8>>, GitError> {
|
||||
Ok(
|
||||
if let Some(entry) = this
|
||||
.0
|
||||
.to_thread_local()
|
||||
.head_commit()?
|
||||
.tree()?
|
||||
.peel_to_entry_by_path(path)?
|
||||
{
|
||||
Some(mem::take(&mut entry.object()?.data))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
inner(self, path.as_ref())
|
||||
}
|
||||
}
|
144
crates/binstalk-downloader/src/git/progress_tracing.rs
Normal file
144
crates/binstalk-downloader/src/git/progress_tracing.rs
Normal file
|
@ -0,0 +1,144 @@
|
|||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use compact_str::{format_compact, CompactString};
|
||||
use gix::progress::{
|
||||
prodash::messages::MessageLevel, Id, Progress, Step, StepShared, Unit, UNKNOWN,
|
||||
};
|
||||
use tokio::time;
|
||||
use tracing::{error, info};
|
||||
|
||||
pub(super) struct TracingProgress {
|
||||
name: CompactString,
|
||||
id: Id,
|
||||
max: Option<usize>,
|
||||
unit: Option<Unit>,
|
||||
step: usize,
|
||||
trigger: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
const EMIT_LOG_EVERY_S: f32 = 0.5;
|
||||
const SEP: &str = "::";
|
||||
|
||||
impl TracingProgress {
|
||||
/// Create a new instanCompactce from `name`.
|
||||
pub fn new(name: CompactString) -> Self {
|
||||
let trigger = Arc::new(AtomicBool::new(true));
|
||||
tokio::spawn({
|
||||
let mut interval = time::interval(Duration::from_secs_f32(EMIT_LOG_EVERY_S));
|
||||
interval.set_missed_tick_behavior(time::MissedTickBehavior::Skip);
|
||||
|
||||
let trigger = Arc::clone(&trigger);
|
||||
async move {
|
||||
while Arc::strong_count(&trigger) > 1 {
|
||||
trigger.store(true, Ordering::Relaxed);
|
||||
|
||||
interval.tick().await;
|
||||
}
|
||||
}
|
||||
});
|
||||
Self {
|
||||
name,
|
||||
id: UNKNOWN,
|
||||
max: None,
|
||||
step: 0,
|
||||
unit: None,
|
||||
trigger,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Progress for TracingProgress {
|
||||
type SubProgress = TracingProgress;
|
||||
|
||||
fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {
|
||||
self.add_child_with_id(name, UNKNOWN)
|
||||
}
|
||||
|
||||
fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {
|
||||
Self {
|
||||
name: format_compact!("{}{}{}", self.name, SEP, Into::<String>::into(name)),
|
||||
id,
|
||||
step: 0,
|
||||
max: None,
|
||||
unit: None,
|
||||
trigger: Arc::clone(&self.trigger),
|
||||
}
|
||||
}
|
||||
|
||||
fn init(&mut self, max: Option<usize>, unit: Option<Unit>) {
|
||||
self.max = max;
|
||||
self.unit = unit;
|
||||
}
|
||||
|
||||
fn set(&mut self, step: usize) {
|
||||
self.step = step;
|
||||
if self.trigger.swap(false, Ordering::Relaxed) {
|
||||
match (self.max, &self.unit) {
|
||||
(max, Some(unit)) => {
|
||||
info!("{} → {}", self.name, unit.display(step, max, None))
|
||||
}
|
||||
(Some(max), None) => info!("{} → {} / {}", self.name, step, max),
|
||||
(None, None) => info!("{} → {}", self.name, step),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unit(&self) -> Option<Unit> {
|
||||
self.unit.clone()
|
||||
}
|
||||
|
||||
fn max(&self) -> Option<usize> {
|
||||
self.max
|
||||
}
|
||||
|
||||
fn set_max(&mut self, max: Option<Step>) -> Option<Step> {
|
||||
let prev = self.max;
|
||||
self.max = max;
|
||||
prev
|
||||
}
|
||||
|
||||
fn step(&self) -> usize {
|
||||
self.step
|
||||
}
|
||||
|
||||
fn inc_by(&mut self, step: usize) {
|
||||
self.set(self.step + step)
|
||||
}
|
||||
|
||||
fn set_name(&mut self, name: impl Into<String>) {
|
||||
let name = name.into();
|
||||
self.name = self
|
||||
.name
|
||||
.split("::")
|
||||
.next()
|
||||
.map(|parent| format_compact!("{}{}{}", parent.to_owned(), SEP, name))
|
||||
.unwrap_or_else(|| name.into());
|
||||
}
|
||||
|
||||
fn name(&self) -> Option<String> {
|
||||
self.name.split(SEP).nth(1).map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
fn id(&self) -> Id {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn message(&self, level: MessageLevel, message: impl Into<String>) {
|
||||
let message: String = message.into();
|
||||
match level {
|
||||
MessageLevel::Info => info!("ℹ{} → {}", self.name, message),
|
||||
MessageLevel::Failure => error!("𐄂{} → {}", self.name, message),
|
||||
MessageLevel::Success => info!("✓{} → {}", self.name, message),
|
||||
}
|
||||
}
|
||||
|
||||
fn counter(&self) -> Option<StepShared> {
|
||||
None
|
||||
}
|
||||
}
|
|
@ -12,4 +12,7 @@ pub mod gh_api_client;
|
|||
|
||||
pub mod remote;
|
||||
|
||||
#[cfg(feature = "git")]
|
||||
pub mod git;
|
||||
|
||||
mod utils;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue