@@ -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.
145180pub ( super ) fn config_to_base_path ( config_path : & Path ) -> & Path {
146181 config_path
0 commit comments