Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
remote-helper: Interpret verbosity option
Lorenz Leutgeb committed 7 months ago
commit f939c452ce757f8b3342dbf93e00d042e7c88f14
parent 89d978e1ebe7756ea3c06d1b65ae7f37b2f4ec78
5 files changed +111 -25
modified crates/radicle-cli/src/commands/patch/checkout.rs
@@ -132,7 +132,7 @@ fn find_patch_commit<'a>(
    match working.find_commit(head) {
        Ok(commit) => Ok(commit),
        Err(e) if git::ext::is_not_found_err(&e) => {
-
            git::process::fetch_local(workdir, stored, [head.into()])?;
+
            git::process::fetch_local(workdir, stored, [head.into()], git::Verbosity::default())?;
            working.find_commit(head).map_err(|e| e.into())
        }
        Err(e) => Err(e.into()),
modified crates/radicle-remote-helper/src/fetch.rs
@@ -7,7 +7,7 @@ use thiserror::Error;
use radicle::git;
use radicle::storage::ReadRepository;

-
use crate::read_line;
+
use crate::{read_line, Verbosity};

#[derive(Debug, Error)]
pub enum Error {
@@ -31,6 +31,7 @@ pub fn run<R: ReadRepository>(
    working: &Path,
    stored: R,
    stdin: &io::Stdin,
+
    verbosity: Verbosity,
) -> Result<(), Error> {
    // Read all the `fetch` lines.
    let mut line = String::new();
@@ -65,7 +66,7 @@ pub fn run<R: ReadRepository>(
    // used in the working copy, this will always result in the object
    // missing. This seems to only be an issue with `libgit2`/`git2`
    // and not `git` itself.
-
    git::process::fetch_local(working, &stored, oids)?;
+
    git::process::fetch_local(working, &stored, oids, verbosity.into())?;

    // Nb. An empty line means we're done.
    println!();
modified crates/radicle-remote-helper/src/lib.rs
@@ -70,6 +70,40 @@ pub enum Error {
    List(#[from] list::Error),
}

+
/// Models values for the `verbosity` option, see
+
/// <https://git-scm.com/docs/gitremote-helpers#Documentation/gitremote-helpers.txt-optionverbosityn>.
+
#[derive(Copy, Clone, Debug)]
+
struct Verbosity(u8);
+

+
impl From<Verbosity> for radicle::git::Verbosity {
+
    /// Converts the verbosity option passed to a Git remote helper to
+
    /// one that can be passed to other Git commands via command line.
+
    /// Note that these scales are one off: While the default verbosity
+
    /// for remote helpers is 1, the default verbosity via command line
+
    /// (omitting the flag) is 0.
+
    /// This implementation also cuts off verbosities greater than [`i8::MAX`].
+
    fn from(val: Verbosity) -> Self {
+
        radicle::git::Verbosity::from(i8::try_from(val.0).unwrap_or(i8::MAX) - 1)
+
    }
+
}
+

+
/// The documentation on Git remote helpers, see
+
/// <https://git-scm.com/docs/gitremote-helpers#Documentation/gitremote-helpers.txt-optionverbosityn>
+
/// says: "1 is the default level of verbosity".
+
impl Default for Verbosity {
+
    fn default() -> Self {
+
        Self(1)
+
    }
+
}
+

+
impl FromStr for Verbosity {
+
    type Err = std::num::ParseIntError;
+

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

#[derive(Debug, Default, Clone)]
pub struct Options {
    /// Don't sync after push.
@@ -84,6 +118,7 @@ pub struct Options {
    base: Option<Rev>,
    /// Patch message.
    message: cli::patch::Message,
+
    verbosity: Verbosity,
}

/// Run the radicle remote helper using the given profile.
@@ -139,9 +174,15 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
                println!("fetch");
                println!();
            }
-
            ["option", "verbosity"] => {
-
                println!("ok");
-
            }
+
            ["option", "verbosity", verbosity] => match verbosity.parse::<Verbosity>() {
+
                Ok(verbosity) => {
+
                    opts.verbosity = verbosity;
+
                    println!("ok");
+
                }
+
                Err(err) => {
+
                    println!("error {err}");
+
                }
+
            },
            ["option", "push-option", args @ ..] => {
                // Nb. Git documentation says that we can print `error <msg>` or `unsupported`
                // for options that are not supported, but this results in Git saying that
@@ -167,7 +208,7 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
                    path: working.clone(),
                })?;

-
                return fetch::run(vec![(oid, refstr)], working, stored, &stdin)
+
                return fetch::run(vec![(oid, refstr)], working, stored, &stdin, opts.verbosity)
                    .map_err(Error::from);
            }
            ["push", refspec] => {
modified crates/radicle-remote-helper/src/push.rs
@@ -31,7 +31,7 @@ use radicle::{git, rad};
use radicle_cli as cli;
use radicle_cli::terminal as term;

-
use crate::{hint, read_line, Options};
+
use crate::{hint, read_line, Options, Verbosity};

#[derive(Debug, Error)]
pub enum Error {
@@ -346,8 +346,17 @@ pub fn run(
                        let rules = crefs.rules();
                        let me = Did::from(nid);

-
                        let explorer =
-
                            push(src, &dst, *force, &nid, &working, stored, patches, &signer)?;
+
                        let explorer = push(
+
                            src,
+
                            &dst,
+
                            *force,
+
                            &nid,
+
                            &working,
+
                            stored,
+
                            patches,
+
                            &signer,
+
                            opts.verbosity,
+
                        )?;
                        // If we're trying to update the canonical head, make sure
                        // we don't diverge from the current head. This only applies
                        // to repos with more than one delegate.
@@ -487,7 +496,7 @@ where
    //
    // In case the reference is not properly deleted, the next attempt to open a patch should
    // not fail, since the reference will already exist with the correct OID.
-
    push_ref(src, &dst, false, working, stored.raw())?;
+
    push_ref(src, &dst, false, working, stored.raw(), opts.verbosity)?;

    let (_, target) = stored.canonical_head()?;
    let base = if let Some(base) = opts.base {
@@ -601,7 +610,7 @@ where
    let commit = *src;
    let dst = dst.with_namespace(nid.into());

-
    push_ref(src, &dst, force, working, stored.raw())?;
+
    push_ref(src, &dst, force, working, stored.raw(), opts.verbosity)?;

    let Ok(Some(patch)) = patches.get(&patch_id) else {
        return Err(Error::NotFound(patch_id));
@@ -671,6 +680,7 @@ fn push<G>(
        cob::cache::StoreWriter,
    >,
    signer: &Device<G>,
+
    verbosity: Verbosity,
) -> Result<Option<ExplorerResource>, Error>
where
    G: crypto::signature::Signer<crypto::Signature>,
@@ -680,7 +690,7 @@ where
    // It's ok for the destination reference to be unknown, eg. when pushing a new branch.
    let old = stored.backend.find_reference(dst.as_str()).ok();

-
    push_ref(src, &dst, force, working, stored.raw())?;
+
    push_ref(src, &dst, force, working, stored.raw(), verbosity)?;

    if let Some(old) = old {
        let proj = stored.project()?;
@@ -863,22 +873,22 @@ fn push_ref(
    force: bool,
    working: &git::raw::Repository,
    stored: &git::raw::Repository,
+
    verbosity: Verbosity,
) -> Result<(), Error> {
    let url = git::url::File::new(stored.path()).to_string();
    // Nb. The *force* indicator (`+`) is processed by Git tooling before we even reach this code.
    // This happens during the `list for-push` phase.
    let refspec = git::Refspec { src, dst, force };
    let repo = working.workdir().unwrap_or_else(|| working.path());
+
 
+
    let mut args = vec!["push".to_string()];

-
    radicle::git::run::<_, _, &str, &str>(
-
        repo,
-
        [
-
            "push",
-
            url.to_string().as_str(),
-
            refspec.to_string().as_str(),
-
        ],
-
        [],
-
    )
+
    let verbosity: git::Verbosity = verbosity.into();
+
    args.extend(verbosity.into_flag());
+

+
    args.extend([url.to_string(), refspec.to_string()]);
+

+
    radicle::git::run::<_, _, &str, &str>(repo, args, [])
    .map_err(|err| {
        Error::Io(std::io::Error::other(format!(
            "failed to run `git push {url} {refspec}` in {:?}: {err}",
modified crates/radicle/src/git.rs
@@ -54,6 +54,38 @@ impl std::fmt::Display for Version {
    }
}

+
/// Verbosity level for Git commands.
+
#[derive(Default, Clone, Copy)]
+
pub struct Verbosity(i8);
+

+
impl Verbosity {
+
    /// Transform into a command line flag, helpful for passing to invocations
+
    /// of `git`.
+
    ///
+
    /// See <https://github.com/git/git/blob/c44beea485f0f2feaf460e2ac87fdd5608d63cf0/builtin/pull.c#L264-L276>
+
    pub fn into_flag(&self) -> Option<String> {
+
        const FLAG_PREFIX: &str = "-";
+
        const FLAG_QUIET: &str = "q";
+
        const FLAG_VERBOSE: &str = "v";
+

+
        let repetitions = self.0.unsigned_abs() as usize;
+

+
        if repetitions == 0 {
+
            return None;
+
        }
+

+
        let flag = if self.0 > 0 { FLAG_VERBOSE } else { FLAG_QUIET };
+

+
        Some(FLAG_PREFIX.to_string() + &flag.repeat(repetitions))
+
    }
+
}
+

+
impl From<i8> for Verbosity {
+
    fn from(v: i8) -> Self {
+
        Self(v)
+
    }
+
}
+

#[derive(thiserror::Error, Debug)]
pub enum VersionError {
    #[error("malformed git version string")]
@@ -761,7 +793,7 @@ pub mod process {

    use crate::storage::ReadRepository;

-
    use super::{run, url, Oid};
+
    use super::{run, url, Oid, Verbosity};

    /// Perform a local fetch, i.e. `file://<storage path>`.
    ///
@@ -771,16 +803,18 @@ pub mod process {
        working: &Path,
        storage: &R,
        oids: impl IntoIterator<Item = Oid>,
+
        verbosity: Verbosity,
    ) -> Result<(), io::Error>
    where
        R: ReadRepository,
    {
        let mut fetch = vec![
            "fetch".to_string(),
-
            url::File::new(storage.path()).to_string(),
-
            // N.b. avoid writing fetch head since we're only fetching objects
+
            // Avoid writing fetch head since we're only fetching objects
            "--no-write-fetch-head".to_string(),
        ];
+
        fetch.extend(verbosity.into_flag());
+
        fetch.push(url::File::new(storage.path()).to_string());
        fetch.extend(oids.into_iter().map(|oid| oid.to_string()));
        // N.b. `.` is used since we're fetching within the working copy
        run::<_, _, &str, &str>(working, fetch, [])?;