@@ -80,21 +80,14 @@ pub(super) static EXE_NAME: &str = "git";
80
80
/// Invoke the git executable to obtain the origin configuration, which is cached and returned.
81
81
///
82
82
/// 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 > {
98
91
let mut cmd = git_cmd ( EXE_NAME . into ( ) ) ;
99
92
gix_trace:: debug!( cmd = ?cmd, "invoking git for installation config path" ) ;
100
93
let cmd_output = match cmd. output ( ) {
@@ -112,7 +105,55 @@ pub(super) static EXE_INFO: Lazy<Option<BString>> = Lazy::new(|| {
112
105
} ;
113
106
114
107
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
+ }
116
157
117
158
/// Try to find the file that contains git configuration coming with the git installation.
118
159
///
@@ -135,12 +176,6 @@ pub(super) fn install_config_path() -> Option<&'static BStr> {
135
176
PATH . as_ref ( ) . map ( AsRef :: as_ref)
136
177
}
137
178
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
-
144
179
/// Given `config_path` as obtained from `install_config_path()`, return the path of the git installation base.
145
180
pub ( super ) fn config_to_base_path ( config_path : & Path ) -> & Path {
146
181
config_path
0 commit comments