Skip to content

Commit dd65e7b

Browse files
authored
Merge pull request #1567 from EliahKagan/config-origin
Find git installation-level configuration more robustly
2 parents 1cfe577 + 5ac5f74 commit dd65e7b

File tree

7 files changed

+317
-71
lines changed

7 files changed

+317
-71
lines changed

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gix-path/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ once_cell = "1.17.1"
2424
home = "0.5.5"
2525

2626
[dev-dependencies]
27-
tempfile = "3.3.0"
27+
gix-testtools = { path = "../tests/tools" }
28+
serial_test = { version = "3.1.0", default-features = false }
2829

2930
[target.'cfg(windows)'.dev-dependencies]
3031
known-folders = "1.1.0"

gix-path/src/env/git/mod.rs

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -80,21 +80,14 @@ pub(super) static EXE_NAME: &str = "git";
8080
/// Invoke the git executable to obtain the origin configuration, which is cached and returned.
8181
///
8282
/// The git executable is the one found in PATH or an alternative location.
83-
pub(super) static EXE_INFO: Lazy<Option<BString>> = Lazy::new(|| {
84-
let git_cmd = |executable: PathBuf| {
85-
let mut cmd = Command::new(executable);
86-
#[cfg(windows)]
87-
{
88-
use std::os::windows::process::CommandExt;
89-
const CREATE_NO_WINDOW: u32 = 0x08000000;
90-
cmd.creation_flags(CREATE_NO_WINDOW);
91-
}
92-
cmd.args(["config", "-l", "--show-origin"])
93-
.current_dir(env::temp_dir())
94-
.stdin(Stdio::null())
95-
.stderr(Stdio::null());
96-
cmd
97-
};
83+
pub(super) static EXE_INFO: Lazy<Option<BString>> = Lazy::new(exe_info);
84+
85+
#[cfg(windows)]
86+
static NULL_DEVICE: &str = "NUL";
87+
#[cfg(not(windows))]
88+
static NULL_DEVICE: &str = "/dev/null";
89+
90+
fn exe_info() -> Option<BString> {
9891
let mut cmd = git_cmd(EXE_NAME.into());
9992
gix_trace::debug!(cmd = ?cmd, "invoking git for installation config path");
10093
let cmd_output = match cmd.output() {
@@ -112,7 +105,55 @@ pub(super) static EXE_INFO: Lazy<Option<BString>> = Lazy::new(|| {
112105
};
113106

114107
first_file_from_config_with_origin(cmd_output.as_slice().into()).map(ToOwned::to_owned)
115-
});
108+
}
109+
110+
fn git_cmd(executable: PathBuf) -> Command {
111+
let mut cmd = Command::new(executable);
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+
// We will try to run `git` from a location fairly high in the filesystem, in the hope it may
119+
// be faster if we are deeply nested, on a slow disk, or in a directory that has been deleted.
120+
let cwd = if cfg!(windows) {
121+
// We try the Windows directory (usually `C:\Windows`) first. It is given by `SystemRoot`,
122+
// except in rare cases where our own parent has not passed down that environment variable.
123+
env::var_os("SystemRoot")
124+
.or_else(|| env::var_os("windir"))
125+
.map(PathBuf::from)
126+
.filter(|p| p.is_absolute())
127+
.unwrap_or_else(env::temp_dir)
128+
} else {
129+
"/".into()
130+
};
131+
// Git 2.8.0 and higher support --show-origin. The -l, -z, and --name-only options were
132+
// supported even before that. In contrast, --show-scope was introduced later, in Git 2.26.0.
133+
// Low versions of Git are still sometimes used, and this is sometimes reasonable because
134+
// downstream distributions often backport security patches without adding most new features.
135+
// So for now, we forgo the convenience of --show-scope for greater backward compatibility.
136+
//
137+
// Separately from that, we can't use --system here, because scopes treated higher than the
138+
// system scope are possible. This commonly happens on macOS with Apple Git, where the config
139+
// file under `/Library` is shown as an "unknown" scope but takes precedence over the system
140+
// scope. Although `GIT_CONFIG_NOSYSTEM` will suppress this as well, passing --system omits it.
141+
cmd.args(["config", "-lz", "--show-origin", "--name-only"])
142+
.current_dir(cwd)
143+
.env_remove("GIT_COMMON_DIR") // We are setting `GIT_DIR`.
144+
.env_remove("GIT_DISCOVERY_ACROSS_FILESYSTEM")
145+
.env("GIT_DIR", NULL_DEVICE) // Avoid getting local-scope config.
146+
.env("GIT_WORK_TREE", NULL_DEVICE) // Avoid confusion when debugging.
147+
.stdin(Stdio::null())
148+
.stderr(Stdio::null());
149+
cmd
150+
}
151+
152+
fn first_file_from_config_with_origin(source: &BStr) -> Option<&BStr> {
153+
let file = source.strip_prefix(b"file:")?;
154+
let end_pos = file.find_byte(b'\0')?;
155+
file[..end_pos].as_bstr().into()
156+
}
116157

117158
/// Try to find the file that contains git configuration coming with the git installation.
118159
///
@@ -135,12 +176,6 @@ pub(super) fn install_config_path() -> Option<&'static BStr> {
135176
PATH.as_ref().map(AsRef::as_ref)
136177
}
137178

138-
fn first_file_from_config_with_origin(source: &BStr) -> Option<&BStr> {
139-
let file = source.strip_prefix(b"file:")?;
140-
let end_pos = file.find_byte(b'\t')?;
141-
file[..end_pos].trim_with(|c| c == '"').as_bstr().into()
142-
}
143-
144179
/// Given `config_path` as obtained from `install_config_path()`, return the path of the git installation base.
145180
pub(super) fn config_to_base_path(config_path: &Path) -> &Path {
146181
config_path

0 commit comments

Comments
 (0)