cli: use `git` for anonymous fetch

Shell out to `git`, avoiding using `git2`. This is to avoid an
issue where somewhere within the fetch there is an attempt to lookup a
`rad/sigrefs` object, which says that the object is missing. It is
suspected that this is due to the object being localised in the same
packfile as other objects we are fetching. Since the `rad/sigrefs`
object is never needed nor 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.

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
Fintan Halpenny 2023-12-20 15:05:30 +00:00 committed by cloudhead
parent 79c1fb309e
commit 6baefdb13f
No known key found for this signature in database
5 changed files with 61 additions and 27 deletions

View File

@ -20,6 +20,7 @@ $ git fetch --all
Fetching rad
Fetching alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
$ cat .git/FETCH_HEAD
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 branch 'master' of rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
7461703ce0fda972df450d071d1d3702057a6352 not-for-merge branch 'alice/1' of rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 not-for-merge branch 'master' of rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
$ git merge FETCH_HEAD

View File

@ -6,7 +6,6 @@ use radicle::cob::patch::RevisionId;
use radicle::git::RefString;
use radicle::patch::PatchId;
use radicle::storage::git::Repository;
use radicle::storage::ReadRepository;
use radicle::{git, rad};
use crate::terminal as term;
@ -107,17 +106,14 @@ fn find_patch_commit<'a>(
working: &'a git::raw::Repository,
) -> anyhow::Result<git::raw::Commit<'a>> {
let head = *revision.head();
let workdir = working
.workdir()
.ok_or(anyhow::anyhow!("repository is a bare git repository "))?;
match working.find_commit(head) {
Ok(commit) => Ok(commit),
Err(e) if git::ext::is_not_found_err(&e) => {
let url = git::url::File::new(stored.path());
working.remote_anonymous(url.to_string().as_str())?.fetch(
&[head.to_string()],
None,
None,
)?;
git::process::fetch_local(workdir, stored, [head.into()])?;
working.find_commit(head).map_err(|e| e.into())
}
Err(e) => Err(e.into()),

View File

@ -5,7 +5,6 @@ use std::str::FromStr;
use thiserror::Error;
use radicle::git;
use radicle::storage::git::transport::local::Url;
use radicle::storage::ReadRepository;
use crate::read_line;
@ -30,7 +29,6 @@ pub enum Error {
pub fn run<R: ReadRepository>(
mut refs: Vec<(git::Oid, git::RefString)>,
working: &Path,
url: Url,
stored: R,
stdin: &io::Stdin,
) -> Result<(), Error> {
@ -53,23 +51,18 @@ pub fn run<R: ReadRepository>(
}
// Verify them and prepare the final refspecs.
let mut refspecs = Vec::new();
for (oid, refstr) in refs {
if let Some(nid) = url.namespace {
refspecs.push(nid.to_namespace().join(refstr).to_string());
} else {
// Just fetch the object directly in this case, it's simpler and faster.
refspecs.push(oid.to_string());
};
}
let oids = refs.into_iter().map(|(oid, _)| oid);
let mut opts = git::raw::FetchOptions::new();
// Setting this to false ensures that the FETCH_HEAD is set correctly. Go figure.
opts.update_fetchhead(false);
git::raw::Repository::open(working)?
.remote_anonymous(&git::url::File::new(stored.path()).to_string())?
.fetch(&refspecs, Some(&mut opts), None)?;
// N.b. we shell out to `git`, avoiding using `git2`. This is to
// avoid an issue where somewhere within the fetch there is an
// attempt to lookup a `rad/sigrefs` object, which says that the
// object is missing. We suspect that this is due to the object
// being localised in the same packfile as other objects we are
// fetching. Since the `rad/sigrefs` object is never needed nor
// 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)?;
// Nb. An empty line means we're done.
println!();

View File

@ -45,6 +45,9 @@ pub enum Error {
/// The `GIT_DIR` env var is not set.
#[error("the `GIT_DIR` environment variable is not set")]
NoGitDir,
/// No parent of `GIT_DIR` was found.
#[error("expected parent of .git but found {path:?}")]
NoWorkingCopy { path: PathBuf },
/// Git error.
#[error("git: {0}")]
Git(#[from] git::raw::Error),
@ -153,7 +156,14 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
let oid = git::Oid::from_str(oid)?;
let refstr = git::RefString::try_from(*refstr)?;
return fetch::run(vec![(oid, refstr)], &working, url, stored, &stdin)
// N.b. `working` is the `.git` folder and `fetch::run`
// requires the working directory.
let working = working.canonicalize()?;
let working = working.parent().ok_or_else(|| Error::NoWorkingCopy {
path: working.clone(),
})?;
return fetch::run(vec![(oid, refstr)], working, stored, &stdin)
.map_err(Error::from);
}
["push", refspec] => {

View File

@ -669,6 +669,40 @@ where
))
}
/// Functions that call to the `git` CLI instead of `git2`.
pub mod process {
use std::io;
use std::path::Path;
use crate::storage::ReadRepository;
use super::{run, url, Oid};
/// Perform a local fetch, i.e. `file://<storage path>`.
///
/// `oids` are the set of [`Oid`]s that are being fetched from the
/// `storage`.
pub fn fetch_local<R>(
working: &Path,
storage: &R,
oids: impl IntoIterator<Item = Oid>,
) -> 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
"--no-write-fetch-head".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, [])?;
Ok(())
}
}
/// Git URLs.
pub mod url {
use std::path::PathBuf;