remote-helper: Simplify handling of `GIT_DIR`

The contract for Git remote handlers says that we can expect
`GIT_DIR` to be set, and this also simplifies logic regarding
bare vs. non-bare repositories.
This commit is contained in:
Lorenz Leutgeb 2025-09-11 21:35:43 +02:00 committed by Fintan Halpenny
parent 354565c579
commit 606882f01f
2 changed files with 23 additions and 22 deletions

View File

@ -7,6 +7,14 @@
//! Usage of standard streams: //! Usage of standard streams:
//! - Standard Error ([`eprintln`]) is used for communicating with the user. //! - Standard Error ([`eprintln`]) is used for communicating with the user.
//! - Standard Output ([`println`]) is used for communicating with Git tooling. //! - Standard Output ([`println`]) is used for communicating with Git tooling.
//!
//! This process assumes that the environment variable `GIT_DIR` is set
//! appropriately (to the repository being pushed from or fetched to), as
//! mentioned in the documentation on Git remote helpers.
//!
//! For example, the following two mechanisms rely on `GIT_DIR` being set:
//! - [`git::raw::Repository::open_from_env`] to open the repository
//! - [`radicle::git::run`] (with [`None`] as first argument) to invoke `git`
mod fetch; mod fetch;
mod list; mod list;
@ -89,12 +97,6 @@ pub enum Error {
/// I/O error. /// I/O error.
#[error("i/o error: {0}")] #[error("i/o error: {0}")]
Io(#[from] io::Error), Io(#[from] io::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. /// Git error.
#[error("git: {0}")] #[error("git: {0}")]
Git(#[from] git::raw::Error), Git(#[from] git::raw::Error),
@ -185,13 +187,14 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
} }
}; };
// Assume the default remote if there was no remote.
let remote = remote.unwrap_or_else(|| (*radicle::rad::REMOTE_NAME).clone());
let stored = profile.storage.repository_mut(url.repo)?; let stored = profile.storage.repository_mut(url.repo)?;
if stored.is_empty()? { if stored.is_empty()? {
return Err(Error::RepositoryNotFound(stored.path().to_path_buf())); return Err(Error::RepositoryNotFound(stored.path().to_path_buf()));
} }
// `GIT_DIR` is set by Git tooling, if we're in a working copy.
let working = env::var("GIT_DIR").map(PathBuf::from);
// Whether we should output debug logs. // Whether we should output debug logs.
let debug = radicle::profile::env::debug(); let debug = radicle::profile::env::debug();
@ -243,25 +246,23 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
let oid = git::Oid::from_str(oid)?; let oid = git::Oid::from_str(oid)?;
let refstr = git::RefString::try_from(*refstr)?; let refstr = git::RefString::try_from(*refstr)?;
return fetch::run(vec![(oid, refstr)], stored, &stdin, opts.verbosity) return Ok(fetch::run(
.map_err(Error::from); vec![(oid, refstr)],
stored,
&stdin,
opts.verbosity,
)?);
} }
["push", refspec] => { ["push", refspec] => {
// We have to be in a working copy to push. return Ok(push::run(
let working = working.map_err(|_| Error::NoGitDir)?;
return push::run(
vec![refspec.to_string()], vec![refspec.to_string()],
&working, remote,
// N.b. assume the default remote if there was no remote
remote.unwrap_or((*radicle::rad::REMOTE_NAME).clone()),
url, url,
&stored, &stored,
&profile, &profile,
&stdin, &stdin,
opts, opts,
) )?);
.map_err(Error::from);
} }
["list"] => { ["list"] => {
list::for_fetch(&url, &profile, &stored)?; list::for_fetch(&url, &profile, &stored)?;

View File

@ -5,7 +5,6 @@ mod error;
use std::collections::HashMap; use std::collections::HashMap;
use std::io::IsTerminal; use std::io::IsTerminal;
use std::path::Path;
use std::process::ExitStatus; use std::process::ExitStatus;
use std::str::FromStr; use std::str::FromStr;
use std::{assert_eq, io}; use std::{assert_eq, io};
@ -247,7 +246,6 @@ impl PushAction {
/// Run a git push command. /// Run a git push command.
pub fn run( pub fn run(
mut specs: Vec<String>, mut specs: Vec<String>,
working: &Path,
remote: git::RefString, remote: git::RefString,
url: Url, url: Url,
stored: &storage::git::Repository, stored: &storage::git::Repository,
@ -292,7 +290,9 @@ pub fn run(
let canonical_ref = git::refs::branch(project.default_branch()); let canonical_ref = git::refs::branch(project.default_branch());
let mut set_canonical_refs: Vec<(git::Qualified, git::canonical::Object)> = let mut set_canonical_refs: Vec<(git::Qualified, git::canonical::Object)> =
Vec::with_capacity(specs.len()); Vec::with_capacity(specs.len());
let working = git::raw::Repository::open(working)?;
// Rely on the environment variable `GIT_DIR`.
let working = git::raw::Repository::open_from_env()?;
// For each refspec, push a ref or delete a ref. // For each refspec, push a ref or delete a ref.
for spec in specs { for spec in specs {