Skip to content

Commit 851a7c4

Browse files
authored
Merge pull request #1758 from GitoxideLabs/git-shell
Fix `gix_path::env::login_shell()` by removing it
2 parents 31d83a4 + 5400320 commit 851a7c4

File tree

5 files changed

+117
-34
lines changed

5 files changed

+117
-34
lines changed

gitoxide-core/src/lib.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#![deny(rust_2018_idioms)]
3131
#![forbid(unsafe_code)]
3232

33+
use anyhow::bail;
3334
use std::str::FromStr;
3435

3536
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
@@ -82,6 +83,51 @@ pub mod repository;
8283
mod discover;
8384
pub use discover::discover;
8485

86+
pub fn env(mut out: impl std::io::Write, format: OutputFormat) -> anyhow::Result<()> {
87+
if format != OutputFormat::Human {
88+
bail!("JSON output isn't supported");
89+
};
90+
91+
let width = 15;
92+
writeln!(
93+
out,
94+
"{field:>width$}: {}",
95+
std::path::Path::new(gix::path::env::shell()).display(),
96+
field = "shell",
97+
)?;
98+
writeln!(
99+
out,
100+
"{field:>width$}: {:?}",
101+
gix::path::env::installation_config_prefix(),
102+
field = "config prefix",
103+
)?;
104+
writeln!(
105+
out,
106+
"{field:>width$}: {:?}",
107+
gix::path::env::installation_config(),
108+
field = "config",
109+
)?;
110+
writeln!(
111+
out,
112+
"{field:>width$}: {}",
113+
gix::path::env::exe_invocation().display(),
114+
field = "git exe",
115+
)?;
116+
writeln!(
117+
out,
118+
"{field:>width$}: {:?}",
119+
gix::path::env::system_prefix(),
120+
field = "system prefix",
121+
)?;
122+
writeln!(
123+
out,
124+
"{field:>width$}: {:?}",
125+
gix::path::env::core_dir(),
126+
field = "core dir",
127+
)?;
128+
Ok(())
129+
}
130+
85131
#[cfg(all(feature = "async-client", feature = "blocking-client"))]
86132
compile_error!("Cannot set both 'blocking-client' and 'async-client' features as they are mutually exclusive");
87133

gix-path/src/env/mod.rs

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::ffi::OsString;
1+
use std::ffi::{OsStr, OsString};
22
use std::path::{Path, PathBuf};
33

44
use bstr::{BString, ByteSlice};
@@ -28,21 +28,25 @@ pub fn installation_config_prefix() -> Option<&'static Path> {
2828
installation_config().map(git::config_to_base_path)
2929
}
3030

31-
/// Return the shell that Git would prefer as login shell, the shell to execute Git commands from.
31+
/// Return the shell that Git would use, the shell to execute commands from.
3232
///
33-
/// On Windows, this is the `bash.exe` bundled with it, and on Unix it's the shell specified by `SHELL`,
34-
/// or `None` if it is truly unspecified.
35-
pub fn login_shell() -> Option<&'static Path> {
36-
static PATH: Lazy<Option<PathBuf>> = Lazy::new(|| {
33+
/// On Windows, this is the full path to `sh.exe` bundled with Git, and on
34+
/// Unix it's `/bin/sh` as posix compatible shell.
35+
/// If the bundled shell on Windows cannot be found, `sh` is returned as the name of a shell
36+
/// as it could possibly be found in `PATH`.
37+
/// Note that the returned path might not be a path on disk.
38+
pub fn shell() -> &'static OsStr {
39+
static PATH: Lazy<OsString> = Lazy::new(|| {
3740
if cfg!(windows) {
38-
installation_config_prefix()
39-
.and_then(|p| p.parent())
40-
.map(|p| p.join("usr").join("bin").join("bash.exe"))
41+
core_dir()
42+
.and_then(|p| p.ancestors().nth(3)) // Skip something like mingw64/libexec/git-core.
43+
.map(|p| p.join("usr").join("bin").join("sh.exe"))
44+
.map_or_else(|| OsString::from("sh"), Into::into)
4145
} else {
42-
std::env::var_os("SHELL").map(PathBuf::from)
46+
"/bin/sh".into()
4347
}
4448
});
45-
PATH.as_deref()
49+
PATH.as_ref()
4650
}
4751

4852
/// Return the name of the Git executable to invoke it.
@@ -102,6 +106,36 @@ pub fn xdg_config(file: &str, env_var: &mut dyn FnMut(&str) -> Option<OsString>)
102106
})
103107
}
104108

109+
static GIT_CORE_DIR: Lazy<Option<PathBuf>> = Lazy::new(|| {
110+
let mut cmd = std::process::Command::new(exe_invocation());
111+
112+
#[cfg(windows)]
113+
{
114+
use std::os::windows::process::CommandExt;
115+
const CREATE_NO_WINDOW: u32 = 0x08000000;
116+
cmd.creation_flags(CREATE_NO_WINDOW);
117+
}
118+
let output = cmd.arg("--exec-path").output().ok()?;
119+
120+
if !output.status.success() {
121+
return None;
122+
}
123+
124+
BString::new(output.stdout)
125+
.strip_suffix(b"\n")?
126+
.to_path()
127+
.ok()?
128+
.to_owned()
129+
.into()
130+
});
131+
132+
/// Return the directory obtained by calling `git --exec-path`.
133+
///
134+
/// Returns `None` if Git could not be found or if it returned an error.
135+
pub fn core_dir() -> Option<&'static Path> {
136+
GIT_CORE_DIR.as_deref()
137+
}
138+
105139
/// Returns the platform dependent system prefix or `None` if it cannot be found (right now only on windows).
106140
///
107141
/// ### Performance
@@ -125,22 +159,7 @@ pub fn system_prefix() -> Option<&'static Path> {
125159
}
126160
}
127161

128-
let mut cmd = std::process::Command::new(exe_invocation());
129-
#[cfg(windows)]
130-
{
131-
use std::os::windows::process::CommandExt;
132-
const CREATE_NO_WINDOW: u32 = 0x08000000;
133-
cmd.creation_flags(CREATE_NO_WINDOW);
134-
}
135-
cmd.arg("--exec-path").stderr(std::process::Stdio::null());
136-
gix_trace::debug!(cmd = ?cmd, "invoking git to get system prefix/exec path");
137-
let path = cmd.output().ok()?.stdout;
138-
let path = BString::new(path)
139-
.trim_with(|b| b.is_ascii_whitespace())
140-
.to_path()
141-
.ok()?
142-
.to_owned();
143-
162+
let path = GIT_CORE_DIR.as_deref()?;
144163
let one_past_prefix = path.components().enumerate().find_map(|(idx, c)| {
145164
matches!(c,std::path::Component::Normal(name) if name.to_str() == Some("libexec")).then_some(idx)
146165
})?;

gix-path/tests/path/env.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@ fn exe_invocation() {
88
}
99

1010
#[test]
11-
fn login_shell() {
12-
// On CI, the $SHELL variable isn't necessarily set. Maybe other ways to get the login shell should be used then.
13-
if !gix_testtools::is_ci::cached() {
14-
assert!(gix_path::env::login_shell()
15-
.expect("There should always be the notion of a shell used by git")
16-
.exists());
17-
}
11+
fn shell() {
12+
assert!(
13+
std::path::Path::new(gix_path::env::shell()).exists(),
14+
"On CI and on Unix we'd expect a full path to the shell that exists on disk"
15+
);
1816
}
1917

2018
#[test]
@@ -26,6 +24,16 @@ fn installation_config() {
2624
);
2725
}
2826

27+
#[test]
28+
fn core_dir() {
29+
assert!(
30+
gix_path::env::core_dir()
31+
.expect("Git is always in PATH when we run tests")
32+
.is_dir(),
33+
"The core directory is a valid directory"
34+
);
35+
}
36+
2937
#[test]
3038
fn system_prefix() {
3139
assert_ne!(

src/plumbing/main.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,15 @@ pub fn main() -> Result<()> {
146146
}
147147

148148
match cmd {
149+
Subcommands::Env => prepare_and_run(
150+
"env",
151+
trace,
152+
verbose,
153+
progress,
154+
progress_keep_open,
155+
None,
156+
move |_progress, out, _err| core::env(out, format),
157+
),
149158
Subcommands::Merge(merge::Platform { cmd }) => match cmd {
150159
merge::SubCommands::File {
151160
resolve_with,

src/plumbing/options/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ pub enum Subcommands {
148148
Corpus(corpus::Platform),
149149
MergeBase(merge_base::Command),
150150
Merge(merge::Platform),
151+
Env,
151152
Diff(diff::Platform),
152153
Log(log::Platform),
153154
Worktree(worktree::Platform),

0 commit comments

Comments
 (0)