Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
radicle: use `git push` to avoid `libgit2` push
Merged fintohaps opened 1 year ago

In issue #2836 of libgit2, the speed of libgit2’s file:// protocol for git operations is found to be very slow. This is further corrobarated by rad init taking 13 hours to initialise the hardenedbsd ports repository.

There are two areas where the heartwood project uses libgit2 to push using the file protocol. The first is when via the trasnport::local smart transport registration and the second is the final push to storage in the git-remote-rad binary.

When both these push operations are changed to use the git binary instead, the ports repository can be initialised in less than 10 minutes (nearly 100x speed up).

This change is clearly required if heartwood wishes to support larger repositories.

2 files changed +64 -13 433483e0 8fd04483
modified radicle-remote-helper/src/push.rs
@@ -1,7 +1,7 @@
#![allow(clippy::too_many_arguments)]
use std::collections::HashMap;
use std::io::IsTerminal;
-
use std::path::Path;
+
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::{assert_eq, io};

@@ -30,6 +30,10 @@ use crate::{hint, read_line, warn, Options};

#[derive(Debug, Error)]
pub enum Error {
+
    #[error(
+
        "the Git repository found at {path:?} is a bare repository, expected a working directory"
+
    )]
+
    BareRepository { path: PathBuf },
    /// Public key doesn't match the remote namespace we're pushing to.
    #[error("cannot push to remote namespace owned by {0}")]
    KeyMismatch(Did),
@@ -777,12 +781,32 @@ fn push_ref(
    working: &git::raw::Repository,
    stored: &git::raw::Repository,
) -> Result<(), Error> {
-
    let mut remote = working.remote_anonymous(&git::url::File::new(stored.path()).to_string())?;
-
    let refspec = git::Refspec { src, dst, force };
-

+
    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.
-
    remote.push(&[refspec.to_string().as_str()], None)?;
+
    let refspec = git::Refspec { src, dst, force };
+
    let repo = working.workdir().ok_or(Error::BareRepository {
+
        path: working.path().to_path_buf(),
+
    })?;
+

+
    radicle::git::run::<_, _, &str, &str>(
+
        repo,
+
        [
+
            "push",
+
            url.to_string().as_str(),
+
            refspec.to_string().as_str(),
+
        ],
+
        [],
+
    )
+
    .map_err(|err| {
+
        Error::Io(std::io::Error::new(
+
            std::io::ErrorKind::Other,
+
            format!(
+
                "failed to run `git push {url} {refspec}` in {:?}: {err}",
+
                working.path()
+
            ),
+
        ))
+
    })?;

    Ok(())
}
modified radicle/src/rad.rs
@@ -1,6 +1,6 @@
#![allow(clippy::let_unit_value)]
use std::io;
-
use std::path::Path;
+
use std::path::{Path, PathBuf};
use std::str::FromStr;

use once_cell::sync::Lazy;
@@ -29,6 +29,10 @@ pub static PATCHES_REFNAME: Lazy<git::RefString> = Lazy::new(|| git::refname!("r

#[derive(Error, Debug)]
pub enum InitError {
+
    #[error(
+
        "the Git repository found at {path:?} is a bare repository, expected a working directory"
+
    )]
+
    BareRepository { path: PathBuf },
    #[error("doc: {0}")]
    Doc(#[from] DocError),
    #[error("repository: {0}")]
@@ -103,13 +107,36 @@ where

    git::configure_repository(repo)?;
    git::configure_remote(repo, &REMOTE_NAME, url, &url.clone().with_namespace(*pk))?;
-
    git::push(
-
        repo,
-
        &REMOTE_NAME,
-
        [(
-
            &git::fmt::lit::refs_heads(default_branch).into(),
-
            &git::fmt::lit::refs_heads(default_branch).into(),
-
        )],
+
    let branch = git::Qualified::from(git::fmt::lit::refs_heads(default_branch));
+
    // Pushes to default branch to the namespace of the `signer`
+
    let pushspec = git::Refspec {
+
        src: branch.clone(),
+
        dst: branch.with_namespace(git::Component::from(pk)),
+
        force: false,
+
    };
+
    git::run::<_, _, &str, &str>(
+
        repo.workdir().ok_or(InitError::BareRepository {
+
            path: repo.path().to_path_buf(),
+
        })?,
+
        [
+
            "push",
+
            &format!("{}", stored.path().canonicalize()?.display()),
+
            &pushspec.to_string(),
+
        ],
+
        [],
+
    )?;
+
    // N.b. we need to create the remote branch for the default branch
+
    let rad_remote =
+
        git::Qualified::from(git::lit::refs_remotes(&*REMOTE_COMPONENT)).join(default_branch);
+
    let oid = repo.refname_to_id(branch.as_str())?;
+
    repo.reference(
+
        rad_remote.as_str(),
+
        oid,
+
        false,
+
        &format!(
+
            "radicle: remote branch {}/{}",
+
            *REMOTE_COMPONENT, default_branch
+
        ),
    )?;
    stored.set_remote_identity_root_to(pk, identity)?;
    stored.set_identity_head_to(identity)?;