Skip to content

Commit

Permalink
feat: Support install directly from git repo
Browse files Browse the repository at this point in the history
Fixed #3

Signed-off-by: Jiahao XU <[email protected]>
  • Loading branch information
NobodyXu committed Jun 21, 2023
1 parent 138112c commit 70b41d4
Show file tree
Hide file tree
Showing 15 changed files with 1,510 additions and 101 deletions.
1,164 changes: 1,098 additions & 66 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion crates/bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ tracing-subscriber = { version = "0.3.17", features = ["fmt", "json", "ansi"], d
embed-resource = "2.1.1"

[features]
default = ["static", "rustls", "trust-dns", "fancy-no-backtrace", "zstd-thin"]
default = ["static", "rustls", "trust-dns", "fancy-no-backtrace", "zstd-thin", "git"]

git = ["binstalk/git"]

mimalloc = ["dep:mimalloc"]

Expand All @@ -74,3 +76,6 @@ log_max_level_debug = ["log/max_level_debug", "tracing/max_level_debug", "log_re

log_release_max_level_info = ["log/release_max_level_info", "tracing/release_max_level_info"]
log_release_max_level_debug = ["log/release_max_level_debug", "tracing/release_max_level_debug"]

[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
39 changes: 37 additions & 2 deletions crates/bin/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub struct Args {
/// install. The version syntax is as with the --version option.
///
/// When multiple names are provided, the --version option and override option
/// `manifest_path` is unavailable due to ambiguity.
/// `--manifest-path` and `--git` are unavailable due to ambiguity.
///
/// If duplicate names are provided, the last one (and their version requirement)
/// is kept.
Expand Down Expand Up @@ -88,9 +88,21 @@ pub struct Args {
/// This skips searching crates.io for a manifest and uses the specified path directly, useful
/// for debugging and when adding Binstall support. This may be either the path to the folder
/// containing a Cargo.toml file, or the Cargo.toml file itself.
///
/// This option cannot be used with `--git`.
#[clap(help_heading = "Overrides", long)]
pub manifest_path: Option<PathBuf>,

#[cfg(feature = "git")]
/// Override how to fetch Cargo.toml package manifest.
///
/// This skip searching crates.io and instead clone the repository specified and
/// runs as if `--manifest-path $cloned_repo` is passed to binstall.
///
/// This option cannot be used with `--manifest-path`.
#[clap(help_heading = "Overrides", long)]
pub git: Option<binstalk::helpers::git::GitUrl>,

/// Override Cargo.toml package manifest bin-dir.
#[clap(help_heading = "Overrides", long)]
pub bin_dir: Option<String>,
Expand Down Expand Up @@ -391,13 +403,36 @@ pub fn parse() -> Args {
// Ensure no conflict
let mut command = Args::command();

#[cfg(feature = "git")]
if opts.manifest_path.is_some() && opts.git.is_some() {
command
.error(
ErrorKind::ArgumentConflict,
format_args!(
r#"Multiple override options for Cargo.toml fetching.
You cannot use --manifest-path and --git. Do one or the other."#
),
)
.exit();
}

if opts.crate_names.len() > 1 {
let option = if opts.version_req.is_some() {
"version"
} else if opts.manifest_path.is_some() {
"manifest-path"
} else {
""
#[cfg(not(feature = "git"))]
{
""
}

#[cfg(feature = "git")]
if opts.git.is_some() {
"git"
} else {
""
}
};

if !option.is_empty() {
Expand Down
17 changes: 13 additions & 4 deletions crates/bin/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use binstalk::{
ops::{
self,
resolve::{CrateName, Resolution, ResolutionFetch, VersionReqExt},
Resolver,
CargoTomlFetchOverride, Options, Resolver,
},
};
use binstalk_manifests::cargo_toml_binstall::PkgOverride;
Expand Down Expand Up @@ -95,7 +95,7 @@ pub fn install_crates(
let gh_api_client = GhApiClient::new(client.clone(), args.github_token);

// Create binstall_opts
let binstall_opts = Arc::new(ops::Options {
let binstall_opts = Arc::new(Options {
no_symlinks: args.no_symlinks,
dry_run: args.dry_run,
force: args.force,
Expand All @@ -104,7 +104,16 @@ pub fn install_crates(
no_track: args.no_track,

version_req: args.version_req,
manifest_path: args.manifest_path,
#[cfg(feature = "git")]
cargo_toml_fetch_override: match (args.manifest_path, args.git) {
(Some(manifest_path), None) => Some(CargoTomlFetchOverride::Path(manifest_path)),
(None, Some(git_url)) => Some(CargoTomlFetchOverride::Git(git_url)),
(None, None) => None,
_ => unreachable!("manifest_path and git cannot be specified at the same time"),
},

#[cfg(not(feature = "git"))]
cargo_toml_fetch_override: args.manifest_path.map(CargoTomlFetchOverride::Path),
cli_overrides,

desired_targets,
Expand Down Expand Up @@ -326,7 +335,7 @@ fn do_install_fetches(
resolution_fetchs: Vec<Box<ResolutionFetch>>,
// Take manifests by value to drop the `FileLock`.
manifests: Option<Manifests>,
binstall_opts: &ops::Options,
binstall_opts: &Options,
dry_run: bool,
temp_dir: tempfile::TempDir,
no_cleanup: bool,
Expand Down
2 changes: 2 additions & 0 deletions crates/bin/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

pub mod args;
pub mod bin_util;
pub mod entry;
Expand Down
8 changes: 7 additions & 1 deletion crates/binstalk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ command-group = { version = "2.1.0", features = ["with-tokio"] }
compact_str = { version = "0.7.0", features = ["serde"] }
detect-targets = { version = "0.1.7", path = "../detect-targets" }
either = "1.8.1"
gix = { version = "0.46.0", features = ["blocking-http-transport-reqwest-rust-tls"], optional = true }
home = "0.5.5"
itertools = "0.10.5"
jobslot = { version = "0.2.11", features = ["tokio"] }
Expand All @@ -43,7 +44,9 @@ xz2 = "0.1.7"
windows = { version = "0.48.0", features = ["Win32_Storage_FileSystem", "Win32_Foundation"] }

[features]
default = ["static", "rustls"]
default = ["static", "rustls", "git"]

git = ["dep:gix"]

static = ["binstalk-downloader/static"]
pkg-config = ["binstalk-downloader/pkg-config"]
Expand All @@ -57,3 +60,6 @@ trust-dns = ["binstalk-downloader/trust-dns"]

zstd-thin = ["binstalk-downloader/zstd-thin"]
cross-lang-fat-lto = ["binstalk-downloader/cross-lang-fat-lto"]

[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
11 changes: 11 additions & 0 deletions crates/binstalk/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,15 @@ pub enum BinstallError {
#[diagnostic(severity(error), code(binstall::target_triple_parse_error))]
TargetTripleParseError(#[source] Box<TargetTripleParseError>),

/// Failed to shallow clone git repository
///
/// - Code: `binstall::git`
/// - Exit: 98
#[cfg(feature = "git")]
#[error("Failed to shallow clone git repository: {0}")]
#[diagnostic(severity(error), code(binstall::git))]
GitError(#[from] crate::helpers::git::GitError),

/// A wrapped error providing the context of which crate the error is about.
#[error(transparent)]
#[diagnostic(transparent)]
Expand Down Expand Up @@ -358,6 +367,8 @@ impl BinstallError {
InvalidPkgFmt(..) => 95,
GhApiErr(..) => 96,
TargetTripleParseError(..) => 97,
#[cfg(feature = "git")]
GitError(_) => 98,
CrateContext(context) => context.err.exit_number(),
};

Expand Down
2 changes: 2 additions & 0 deletions crates/binstalk/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pub mod futures_resolver;
#[cfg(feature = "git")]
pub mod git;
pub mod jobserver_client;
pub mod remote;
pub mod signal;
Expand Down
89 changes: 89 additions & 0 deletions crates/binstalk/src/helpers/git.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use std::{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;

#[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>),
}

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))
}
}

#[derive(Clone, Debug)]
pub struct GitUrl(Url);

impl FromStr for GitUrl {
type Err = gix::url::parse::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Url::try_from(s).map(Self)
}
}

#[derive(Debug)]
pub struct Repository(gix::Repository);

impl Repository {
/// 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> {
let url_bstr = url.0.to_bstring();
let url_str = String::from_utf8_lossy(&url_bstr);

debug!("Shallow cloning {url_str} to {}", path.display());

let mut progress = TracingProgress::new(CompactString::new("Cloning"));

Ok(Self(
clone::PrepareFetch::new(
url.0,
path,
create::Kind::WithWorktree,
create::Options {
destination_must_be_empty: true,
..Default::default()
},
open::Options::isolated(),
)?
.with_shallow(remote::fetch::Shallow::DepthAtRemote(
NonZeroU32::new(1).unwrap(),
))
.fetch_then_checkout(&mut progress, &AtomicBool::new(false))?
.0
.main_worktree(&mut progress, &AtomicBool::new(false))?
.0,
))
}
}
Loading

0 comments on commit 70b41d4

Please sign in to comment.