Skip to content

Commit

Permalink
Allow git invocations to read git config
Browse files Browse the repository at this point in the history
Fixes #125.

The old code path:

1. Was unnecessary given the change in direction in 0.4.0 that got rid
   of `git nomad init`.

2. Conflated the parameters needed to run `git` in test environments
   with `git` invocations in user environments.
  • Loading branch information
rraval committed Sep 22, 2023
1 parent 5bbb7f6 commit c6d45b9
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 24 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ However, the output of the application is designed for humans, not machines, and

## [Unreleased]

### Changed

- `git` invocations will now read system and global configuration, so things like credential helpers will will be respected in the underlying `git push` that `git nomad sync` makes. Fixes [#125][i125].

## [0.6.0] - 2022-10-10

### Added
Expand Down Expand Up @@ -120,3 +124,4 @@ An initial release with a reasonable complete implementation.
[i3]: https://github.com/rraval/git-nomad/issues/3
[i4]: https://github.com/rraval/git-nomad/issues/4
[i5]: https://github.com/rraval/git-nomad/issues/5
[i125]: https://github.com/rraval/git-nomad/issues/125
118 changes: 94 additions & 24 deletions src/git_binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,29 @@ use crate::{
verbosity::{is_output_allowed, output_stdout, run_notable, run_trivial, Verbosity},
};

/// Attempt to run a git binary without impurities from the environment slipping in.
///
/// Doing this correctly seems to have a long and complicated history:
/// <https://stackoverflow.com/a/67512433>
pub fn git_command<S: AsRef<OsStr>>(name: S) -> Command {
let mut command = Command::new(name);

let author_name = "git-nomad";
let author_email = "git-nomad@invalid";
let author_date = "1970-01-01T00:00:00";
/// Run the git binary inheriting the same environment that this git-nomad
/// binary is running under.
#[cfg(not(test))]
pub fn git_command(name: impl AsRef<OsStr>) -> Command {
Command::new(name)
}

/// Constructs a standalone git invocation that works in test environments without any ambient
/// configuration.
#[cfg(test)]
pub fn git_command(name: impl AsRef<OsStr>) -> Command {
let mut command = Command::new(name);
command
.env("GIT_CONFIG_NOSYSTEM", "1")
.env("GIT_CONFIG_NOGLOBAL", "1")
.env("HOME", "")
.env("XDG_CONFIG_HOME", "")
.env("GIT_AUTHOR_NAME", author_name)
.env("GIT_AUTHOR_EMAIL", author_email)
.env("GIT_AUTHOR_DATE", author_date)
.env("GIT_COMMITTER_NAME", author_name)
.env("GIT_COMMITTER_EMAIL", author_email)
.env("GIT_COMMITTER_DATE", author_date);
// These allow tests to exercise global config reading behaviour
.env_remove("GIT_CONFIG_SYSTEM")
.env_remove("GIT_CONFIG_GLOBAL")
// This allows `git commit` to work
.args([
"-c",
"user.name=git-nomad",
"-c",
"user.email=git-nomad@invalid",
]);
command
}

Expand All @@ -45,7 +46,7 @@ mod namespace {

/// The main name that we declare to be ours and nobody elses. This lays claim to the section
/// in `git config` and the `refs/{PREFIX}` hierarchy in all git repos!
const PREFIX: &str = "nomad";
pub const PREFIX: &str = "nomad";

/// Where information is stored for `git config`.
pub fn config_key(key: &str) -> String {
Expand Down Expand Up @@ -270,10 +271,18 @@ impl GitBinary<'_> {

/// Wraps `git config` to read a single namespaced value.
pub fn get_config(&self, key: &str) -> Result<Option<String>> {
self.get_config_with_env(key, [] as [(&str, &str); 0])
}

fn get_config_with_env(
&self,
key: &str,
vars: impl IntoIterator<Item = (impl AsRef<OsStr>, impl AsRef<OsStr>)>,
) -> Result<Option<String>> {
run_trivial(
self.verbosity,
format!("Get config {}", key),
self.command().args([
self.command().envs(vars).args([
"config",
// Use a default to prevent git from returning a non-zero exit code when the value does
// not exist.
Expand Down Expand Up @@ -686,7 +695,7 @@ mod test_line_arity {

#[cfg(test)]
mod test_impl {
use std::{borrow::Cow, fs::create_dir};
use std::{borrow::Cow, fs};

use tempfile::{tempdir, TempDir};

Expand Down Expand Up @@ -737,7 +746,7 @@ mod test_impl {
fn toplevel_in_subdir() -> Result<()> {
let (name, tmpdir) = git_init()?;
let subdir = tmpdir.path().join("subdir");
create_dir(&subdir)?;
fs::create_dir(&subdir)?;

let git = GitBinary::new(None, name, subdir.as_path())?;
assert_eq!(
Expand Down Expand Up @@ -774,6 +783,67 @@ mod test_impl {
Ok(())
}

/// Generates git config files for testing.
mod gitconfig {
use std::{fs, path::Path};

use anyhow::Result;
use tempfile::{tempdir, TempDir};

use crate::git_binary::namespace;

pub const KEY: &str = "testkey";
pub const VALUE: &str = "testvalue";

pub fn write(
dirs: impl IntoIterator<Item = impl AsRef<Path>>,
filename: impl AsRef<Path>,
) -> Result<TempDir> {
let root = tempdir()?;

let mut path = root.path().to_path_buf();
path.extend(dirs);

fs::create_dir_all(&path)?;

path.push(filename);
fs::write(
&path,
format!("[{}]\n {} = {}", namespace::PREFIX, KEY, VALUE),
)?;

Ok(root)
}
}

/// Git invocations should read from `$HOME/.gitconfig`
#[test]
fn read_home_config() -> Result<()> {
let (name, tmpdir) = git_init()?;
let git = GitBinary::new(None, name, tmpdir.path())?;

let home = gitconfig::write([] as [&str; 0], ".gitconfig")?;
let got = git.get_config_with_env(gitconfig::KEY, [("HOME", home.path())])?;

assert_eq!(got, Some(gitconfig::VALUE.into()));

Ok(())
}

/// Git invocations should read from `$XDG_CONFIG_HOME/git/config`
#[test]
fn read_xdg_config() -> Result<()> {
let (name, tmpdir) = git_init()?;
let git = GitBinary::new(None, name, tmpdir.path())?;

let xdg = gitconfig::write(["git"], "config")?;
let got = git.get_config_with_env(gitconfig::KEY, [("XDG_CONFIG_HOME", xdg.path())])?;

assert_eq!(got, Some(gitconfig::VALUE.into()));

Ok(())
}

/// Reading the current branch should work as expected, even when the repository is completely
/// empty (and hence that branch doesn't have a corresponding commit ID).
#[test]
Expand Down

0 comments on commit c6d45b9

Please sign in to comment.