| @@ -3,15 +3,24 @@ use std::path::Path; · 2 unmodified lines |
| 3 | 3 | #[cfg(windows)] |
| 4 | 4 | mod locations { |
| 5 | 5 | use std::{ |
| 6 | | − ffi::{OsStr, OsString}, |
| 6 | + ffi::{c_void, OsStr, OsString}, |
| 7 | 7 | io::ErrorKind, |
| 8 | + os::windows::ffi::OsStringExt, |
| 8 | 9 | path::{Path, PathBuf}, |
| 9 | 10 | }; |
| 10 | 11 | |
| 11 | | − use known_folders::{get_known_folder_path, KnownFolder}; |
| 12 | 12 | use windows::{ |
| 13 | | − core::{Result as WindowsResult, BOOL}, |
| 14 | | − Win32::System::Threading::{GetCurrentProcess, IsWow64Process}, |
| 13 | + core::{Result as WindowsResult, BOOL, GUID, PWSTR}, |
| 14 | + Win32::{ |
| 15 | + System::{ |
| 16 | + Com::CoTaskMemFree, |
| 17 | + Threading::{GetCurrentProcess, IsWow64Process}, |
| 18 | + }, |
| 19 | + UI::Shell::{ |
| 20 | + FOLDERID_LocalAppData, FOLDERID_ProgramFiles, FOLDERID_ProgramFilesX86, FOLDERID_UserProgramFiles, |
| 21 | + SHGetKnownFolderPath, KF_FLAG_DEFAULT, KF_FLAG_DONT_VERIFY, KNOWN_FOLDER_FLAG, |
| 22 | + }, |
| 23 | + }, |
| 15 | 24 | }; |
| 16 | 25 | use winreg::{ |
| 17 | 26 | enums::{HKEY_LOCAL_MACHINE, KEY_QUERY_VALUE}, |
| @@ -55,7 +64,7 @@ mod locations { · 37 unmodified lines |
| 55 | 64 | } |
| 56 | 65 | |
| 57 | 66 | #[test] |
| 58 | | − fn locations_under_program_files_ordinary_values_current_var_only() { |
| 67 | + fn locations_under_program_files_global_only_ordinary_values_current_var_only() { |
| 59 | 68 | assert_eq!( |
| 60 | 69 | locations_from!( |
| 61 | 70 | "ProgramFiles" => r"C:\Program Files", |
| @@ -72,7 +81,7 @@ mod locations { · 10 unmodified lines |
| 72 | 81 | } |
| 73 | 82 | |
| 74 | 83 | #[test] |
| 75 | | − fn locations_under_program_files_ordinary_values_all_vars() { |
| 84 | + fn locations_under_program_files_global_only_ordinary_values_all_vars() { |
| 76 | 85 | assert_eq!( |
| 77 | 86 | locations_from!( |
| 78 | 87 | "ProgramFiles" => { |
| @@ -94,7 +103,7 @@ mod locations { · 15 unmodified lines |
| 94 | 103 | } |
| 95 | 104 | |
| 96 | 105 | #[test] |
| 97 | | − fn locations_under_program_files_strange_values_all_vars_distinct() { |
| 106 | + fn locations_under_program_files_global_only_strange_values_all_vars_distinct() { |
| 98 | 107 | assert_eq!( |
| 99 | 108 | locations_from!( |
| 100 | 109 | "ProgramFiles" => r"X:\cur\rent", |
| @@ -121,7 +130,7 @@ mod locations { · 20 unmodified lines |
| 121 | 130 | } |
| 122 | 131 | |
| 123 | 132 | #[test] |
| 124 | | − fn locations_under_program_files_strange_values_64bit_var_only() { |
| 133 | + fn locations_under_program_files_global_only_strange_values_64bit_var_only() { |
| 125 | 134 | assert_eq!( |
| 126 | 135 | locations_from!( |
| 127 | 136 | "ProgramW6432" => r"Z:\wi\de", |
| @@ -131,7 +140,7 @@ mod locations { · 3 unmodified lines |
| 131 | 140 | } |
| 132 | 141 | |
| 133 | 142 | #[test] |
| 134 | | − fn locations_under_program_files_strange_values_all_vars_path_cruft() { |
| 143 | + fn locations_under_program_files_global_only_strange_values_all_vars_path_cruft() { |
| 135 | 144 | assert_eq!( |
| 136 | 145 | locations_from!( |
| 137 | 146 | "ProgramFiles" => r"Z:/wi//de/", |
| @@ -156,7 +165,7 @@ mod locations { · 18 unmodified lines |
| 156 | 165 | } |
| 157 | 166 | |
| 158 | 167 | #[test] |
| 159 | | − fn locations_under_program_files_strange_values_some_relative() { |
| 168 | + fn locations_under_program_files_global_only_strange_values_some_relative() { |
| 160 | 169 | assert_eq!( |
| 161 | 170 | locations_from!( |
| 162 | 171 | "ProgramFiles" => r"foo\bar", |
| @@ -167,6 +176,235 @@ mod locations { · 4 unmodified lines |
| 167 | 176 | ); |
| 168 | 177 | } |
| 169 | 178 | |
| 179 | + #[test] |
| 180 | + fn locations_under_program_files_local_only_ordinary_value() { |
| 181 | + assert_eq!( |
| 182 | + locations_from!( |
| 183 | + "LocalAppData" => r"C:\Users\alice\AppData\Local", |
| 184 | + ), |
| 185 | + pathbuf_vec![ |
| 186 | + r"C:\Users\alice\AppData\Local\Programs\Git\clangarm64\bin", |
| 187 | + r"C:\Users\alice\AppData\Local\Programs\Git\mingw64\bin", |
| 188 | + r"C:\Users\alice\AppData\Local\Programs\Git\mingw32\bin", |
| 189 | + ], |
| 190 | + ); |
| 191 | + } |
| 192 | + |
| 193 | + #[test] |
| 194 | + fn locations_under_program_files_local_only_strange_value_path_cruft() { |
| 195 | + assert_eq!( |
| 196 | + locations_from!( |
| 197 | + "LocalAppData" => r"\\.\Q:\Documents and Settings/bob\/weird\.\sub//dir", |
| 198 | + ), |
| 199 | + pathbuf_vec![ |
| 200 | + r"\\.\Q:\Documents and Settings\bob\weird\sub\dir\Programs\Git\clangarm64\bin", |
| 201 | + r"\\.\Q:\Documents and Settings\bob\weird\sub\dir\Programs\Git\mingw64\bin", |
| 202 | + r"\\.\Q:\Documents and Settings\bob\weird\sub\dir\Programs\Git\mingw32\bin", |
| 203 | + ], |
| 204 | + ); |
| 205 | + } |
| 206 | + |
| 207 | + #[test] |
| 208 | + fn locations_under_program_files_local_only_strange_value_empty() { |
| 209 | + assert_eq!( |
| 210 | + locations_from!( |
| 211 | + "LocalAppData" => "", |
| 212 | + ), |
| 213 | + Vec::<PathBuf>::new(), |
| 214 | + ); |
| 215 | + } |
| 216 | + |
| 217 | + #[test] |
| 218 | + fn locations_under_program_files_local_only_strange_value_relative_nonempty() { |
| 219 | + assert_eq!( |
| 220 | + locations_from!( |
| 221 | + "LocalAppData" => r"AppData\Local", |
| 222 | + ), |
| 223 | + Vec::<PathBuf>::new(), |
| 224 | + ); |
| 225 | + } |
| 226 | + |
| 227 | + #[test] |
| 228 | + fn locations_under_program_files_local_and_global_ordinary_values_limited_vars() { |
| 229 | + assert_eq!( |
| 230 | + locations_from!( |
| 231 | + "LocalAppData" => r"C:\Users\alice\AppData\Local", |
| 232 | + "ProgramFiles" => r"C:\Program Files", |
| 233 | + ), |
| 234 | + if cfg!(target_pointer_width = "64") { |
| 235 | + pathbuf_vec![ |
| 236 | + r"C:\Users\alice\AppData\Local\Programs\Git\clangarm64\bin", |
| 237 | + r"C:\Users\alice\AppData\Local\Programs\Git\mingw64\bin", |
| 238 | + r"C:\Users\alice\AppData\Local\Programs\Git\mingw32\bin", |
| 239 | + r"C:\Program Files\Git\clangarm64\bin", |
| 240 | + r"C:\Program Files\Git\mingw64\bin", |
| 241 | + ] |
| 242 | + } else { |
| 243 | + pathbuf_vec![ |
| 244 | + r"C:\Users\alice\AppData\Local\Programs\Git\clangarm64\bin", |
| 245 | + r"C:\Users\alice\AppData\Local\Programs\Git\mingw64\bin", |
| 246 | + r"C:\Users\alice\AppData\Local\Programs\Git\mingw32\bin", |
| 247 | + r"C:\Program Files\Git\mingw32\bin", |
| 248 | + ] |
| 249 | + }, |
| 250 | + ); |
| 251 | + } |
| 252 | + |
| 253 | + #[test] |
| 254 | + fn locations_under_program_files_local_and_global_ordinary_values_all_vars() { |
| 255 | + assert_eq!( |
| 256 | + locations_from!( |
| 257 | + "LocalAppData" => r"C:\Users\bob\AppData\Local", |
| 258 | + "ProgramFiles" => { |
| 259 | + if cfg!(target_pointer_width = "64") { |
| 260 | + r"C:\Program Files" |
| 261 | + } else { |
| 262 | + r"C:\Program Files (x86)" |
| 263 | + } |
| 264 | + }, |
| 265 | + "ProgramFiles(x86)" => r"C:\Program Files (x86)", |
| 266 | + "ProgramW6432" => r"C:\Program Files", |
| 267 | + ), |
| 268 | + pathbuf_vec![ |
| 269 | + r"C:\Users\bob\AppData\Local\Programs\Git\clangarm64\bin", |
| 270 | + r"C:\Users\bob\AppData\Local\Programs\Git\mingw64\bin", |
| 271 | + r"C:\Users\bob\AppData\Local\Programs\Git\mingw32\bin", |
| 272 | + r"C:\Program Files\Git\clangarm64\bin", |
| 273 | + r"C:\Program Files\Git\mingw64\bin", |
| 274 | + r"C:\Program Files (x86)\Git\mingw32\bin", |
| 275 | + ], |
| 276 | + ); |
| 277 | + } |
| 278 | + |
| 279 | + #[test] |
| 280 | + fn locations_under_program_files_local_and_global_strange_values_all_vars_distinct() { |
| 281 | + assert_eq!( |
| 282 | + locations_from!( |
| 283 | + "LocalAppData" => r"W:\us\er", |
| 284 | + "ProgramFiles" => r"X:\cur\rent", |
| 285 | + "ProgramFiles(x86)" => r"Y:\nar\row", |
| 286 | + "ProgramW6432" => r"Z:\wi\de", |
| 287 | + ), |
| 288 | + if cfg!(target_pointer_width = "64") { |
| 289 | + pathbuf_vec![ |
| 290 | + r"W:\us\er\Programs\Git\clangarm64\bin", |
| 291 | + r"W:\us\er\Programs\Git\mingw64\bin", |
| 292 | + r"W:\us\er\Programs\Git\mingw32\bin", |
| 293 | + r"Z:\wi\de\Git\clangarm64\bin", |
| 294 | + r"Z:\wi\de\Git\mingw64\bin", |
| 295 | + r"Y:\nar\row\Git\mingw32\bin", |
| 296 | + r"X:\cur\rent\Git\clangarm64\bin", |
| 297 | + r"X:\cur\rent\Git\mingw64\bin", |
| 298 | + ] |
| 299 | + } else { |
| 300 | + pathbuf_vec![ |
| 301 | + r"W:\us\er\Programs\Git\clangarm64\bin", |
| 302 | + r"W:\us\er\Programs\Git\mingw64\bin", |
| 303 | + r"W:\us\er\Programs\Git\mingw32\bin", |
| 304 | + r"Z:\wi\de\Git\clangarm64\bin", |
| 305 | + r"Z:\wi\de\Git\mingw64\bin", |
| 306 | + r"Y:\nar\row\Git\mingw32\bin", |
| 307 | + r"X:\cur\rent\Git\mingw32\bin", |
| 308 | + ] |
| 309 | + }, |
| 310 | + ); |
| 311 | + } |
| 312 | + |
| 313 | + #[test] |
| 314 | + fn locations_under_program_files_local_and_global_strange_values_limited_64bit_var() { |
| 315 | + assert_eq!( |
| 316 | + locations_from!( |
| 317 | + "LocalAppData" => r"W:\us\er", |
| 318 | + "ProgramW6432" => r"Z:\wi\de", |
| 319 | + ), |
| 320 | + pathbuf_vec![ |
| 321 | + r"W:\us\er\Programs\Git\clangarm64\bin", |
| 322 | + r"W:\us\er\Programs\Git\mingw64\bin", |
| 323 | + r"W:\us\er\Programs\Git\mingw32\bin", |
| 324 | + r"Z:\wi\de\Git\clangarm64\bin", |
| 325 | + r"Z:\wi\de\Git\mingw64\bin", |
| 326 | + ], |
| 327 | + ); |
| 328 | + } |
| 329 | + |
| 330 | + #[test] |
| 331 | + fn locations_under_program_files_local_and_global_strange_values_crufty_cross_clash() { |
| 332 | + assert_eq!( |
| 333 | + locations_from!( |
| 334 | + "LocalAppData" => r"Y:\nar\row", |
| 335 | + "ProgramFiles" => r"Z:/wi//de/", |
| 336 | + "ProgramFiles(x86)" => r"Y:/\nar/row\Programs", |
| 337 | + "ProgramW6432" => r"Z:\wi\.\de", |
| 338 | + ), |
| 339 | + if cfg!(target_pointer_width = "64") { |
| 340 | + pathbuf_vec![ |
| 341 | + r"Y:\nar\row\Programs\Git\clangarm64\bin", |
| 342 | + r"Y:\nar\row\Programs\Git\mingw64\bin", |
| 343 | + r"Y:\nar\row\Programs\Git\mingw32\bin", |
| 344 | + r"Z:\wi\de\Git\clangarm64\bin", |
| 345 | + r"Z:\wi\de\Git\mingw64\bin", |
| 346 | + ] |
| 347 | + } else { |
| 348 | + pathbuf_vec![ |
| 349 | + r"Y:\nar\row\Programs\Git\clangarm64\bin", |
| 350 | + r"Y:\nar\row\Programs\Git\mingw64\bin", |
| 351 | + r"Y:\nar\row\Programs\Git\mingw32\bin", |
| 352 | + r"Z:\wi\de\Git\clangarm64\bin", |
| 353 | + r"Z:\wi\de\Git\mingw64\bin", |
| 354 | + r"Z:\wi\de\Git\mingw32\bin", |
| 355 | + ] |
| 356 | + }, |
| 357 | + ); |
| 358 | + } |
| 359 | + |
| 360 | + #[test] |
| 361 | + fn locations_under_program_files_local_and_global_strange_values_some_relative() { |
| 362 | + assert_eq!( |
| 363 | + locations_from!( |
| 364 | + "LocalAppData" => "dir", |
| 365 | + "ProgramFiles" => r"foo\bar", |
| 366 | + "ProgramFiles(x86)" => r"\\host\share\subdir", |
| 367 | + "ProgramW6432" => r"", |
| 368 | + ), |
| 369 | + pathbuf_vec![r"\\host\share\subdir\Git\mingw32\bin"], |
| 370 | + ); |
| 371 | + } |
| 372 | + |
| 373 | + /// Owner of a null-terminated `PWSTR` that must be freed with `CoTaskMemFree`. |
| 374 | + struct CoStr { |
| 375 | + pwstr: PWSTR, |
| 376 | + } |
| 377 | + |
| 378 | + impl CoStr { |
| 379 | + /// SAFETY: The caller must ensure `pwstr` is a non-null pointer to the beginning of a |
| 380 | + /// null-terminated (zero-codepoint-terminated) wide string releasable by `CoTaskMemFree`. |
| 381 | + unsafe fn new(pwstr: PWSTR) -> Self { |
| 382 | + Self { pwstr } |
| 383 | + } |
| 384 | + |
| 385 | + fn to_os_string(&self) -> OsString { |
| 386 | + // SAFETY: We know `pwstr` is derefrenceable and the string is null-terminated. |
| 387 | + let wide = unsafe { self.pwstr.as_wide() }; |
| 388 | + OsString::from_wide(wide) |
| 389 | + } |
| 390 | + } |
| 391 | + |
| 392 | + impl Drop for CoStr { |
| 393 | + fn drop(&mut self) { |
| 394 | + // SAFETY: `pwstr` is allowed to be passed to `CoTaskMemFree`. (We happen to know it's |
| 395 | + // non-null, but `CoTaskMemFree` permits null as well, so the cast is doubly safe.) |
| 396 | + unsafe { CoTaskMemFree(Some(self.pwstr.as_ptr().cast::<c_void>())) }; |
| 397 | + } |
| 398 | + } |
| 399 | + |
| 400 | + fn get_known_folder_path_with_flag(id: GUID, flag: KNOWN_FOLDER_FLAG) -> WindowsResult<PathBuf> { |
| 401 | + // SAFETY: `SHGetKnownFolderPath` in the `windows` crate wraps the API function and returns |
| 402 | + // a non-null pointer to a null-terminated wide string, or an error, not a null pointer. |
| 403 | + // As in the wrapped API function, the pointer it returns can be passed to `CoTaskMemFree`. |
| 404 | + let costr = unsafe { CoStr::new(SHGetKnownFolderPath(&id, flag, None)?) }; |
| 405 | + Ok(PathBuf::from(costr.to_os_string())) |
| 406 | + } |
| 407 | + |
| 170 | 408 | #[derive(Clone, Copy, Debug)] |
| 171 | 409 | enum PlatformBitness { |
| 172 | 410 | Is32on32, |
| @@ -205,9 +443,10 @@ mod locations { · 32 unmodified lines |
| 205 | 443 | Some(folded_text.ends_with(&folded_pattern)) |
| 206 | 444 | } |
| 207 | 445 | |
| 208 | | − /// The most common global program files paths on this system, by process and system architecture. |
| 446 | + /// The most common program files paths, as they are available in this environment. |
| 209 | 447 | /// |
| 210 | | − /// This omits the 32-bit ARM program files directory, as Git for Windows is never installed there. |
| 448 | + /// This omits the global 32-bit ARM program files directory, because Git for Windows is never |
| 449 | + /// installed there. |
| 211 | 450 | #[derive(Clone, Debug)] |
| 212 | 451 | struct ProgramFilesPaths { |
| 213 | 452 | /// The program files directory used for whatever architecture this program was built for. |
| @@ -224,10 +463,13 @@ mod locations { · 10 unmodified lines |
| 224 | 463 | /// AMD64 programs use the same program files directory while 32-bit x86 and 32-bit ARM |
| 225 | 464 | /// programs use two others. Only a 32-bit system has no 64-bit program files directory. |
| 226 | 465 | maybe_64bit: Option<PathBuf>, |
| 466 | + |
| 467 | + /// The per-user `Programs` subdirectory of the user's local application data directory. |
| 468 | + user: PathBuf, |
| 227 | 469 | } |
| 228 | 470 | |
| 229 | 471 | impl ProgramFilesPaths { |
| 230 | | − /// Get the three most common kinds of global program files paths without environment variables. |
| 472 | + /// Get the four most common kinds of program files paths without environment variables. |
| 231 | 473 | /// |
| 232 | 474 | /// The idea here is to obtain this information, which the `alternative_locations()` unit |
| 233 | 475 | /// test uses to learn the expected alternative locations, without duplicating *any* of the |
| @@ -235,9 +477,10 @@ mod locations { · 1 unmodified line |
| 235 | 477 | /// here is also more reliable than using environment variables, but it is a bit more |
| 236 | 478 | /// complex, and it requires either additional dependencies or the use of unsafe code. |
| 237 | 479 | /// |
| 238 | | − /// This gets `pf_current` and `pf_x86` by the [known folders][known-folders] system. But |
| 239 | | − /// it gets `maybe_pf_64bit` from the registry, as the corresponding known folder is not |
| 240 | | − /// available to 32-bit processes. See the [`KNOWNFOLDDERID`][knownfolderid] documentation. |
| 480 | + /// This gets `pf_user`, `pf_current`, and `pf_x86` by the [known folders][known-folders] |
| 481 | + /// system. But it gets `maybe_pf_64bit` from the registry, as the corresponding known |
| 482 | + /// folder is not available to 32-bit processes. See the [`KNOWNFOLDDERID`][knownfolderid] |
| 483 | + /// documentation. |
| 241 | 484 | /// |
| 242 | 485 | /// If in the future the implementation of `ALTERNATIVE_LOCATIONS` uses these techniques, |
| 243 | 486 | /// then this function can be changed to use environment variables and renamed accordingly. |
| @@ -245,10 +488,10 @@ mod locations { · 1 unmodified line |
| 245 | 488 | /// [known-folders]: https://learn.microsoft.com/en-us/windows/win32/shell/known-folders |
| 246 | 489 | /// [knownfolderid]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#remarks |
| 247 | 490 | fn obtain_envlessly() -> Self { |
| 248 | | − let pf_current = get_known_folder_path(KnownFolder::ProgramFiles) |
| 491 | + let pf_current = get_known_folder_path_with_flag(FOLDERID_ProgramFiles, KF_FLAG_DEFAULT) |
| 249 | 492 | .expect("The process architecture specific program files folder is always available"); |
| 250 | 493 | |
| 251 | | − let pf_x86 = get_known_folder_path(KnownFolder::ProgramFilesX86) |
| 494 | + let pf_x86 = get_known_folder_path_with_flag(FOLDERID_ProgramFilesX86, KF_FLAG_DEFAULT) |
| 252 | 495 | .expect("The x86 program files folder will in practice always be available"); |
| 253 | 496 | |
| 254 | 497 | let maybe_pf_64bit = RegKey::predef(HKEY_LOCAL_MACHINE) |
| @@ -262,10 +505,14 @@ mod locations { · 7 unmodified lines |
| 262 | 505 | }) |
| 263 | 506 | .ok(); |
| 264 | 507 | |
| 508 | + let pf_user = get_known_folder_path_with_flag(FOLDERID_UserProgramFiles, KF_FLAG_DONT_VERIFY) |
| 509 | + .expect("The path where the user's local Programs folder is, or would be created, is known"); |
| 510 | + |
| 265 | 511 | Self { |
| 266 | 512 | current: pf_current, |
| 267 | 513 | x86: pf_x86, |
| 268 | 514 | maybe_64bit: maybe_pf_64bit, |
| 515 | + user: pf_user, |
| 269 | 516 | } |
| 270 | 517 | } |
| 271 | 518 | |
| @@ -274,6 +521,12 @@ mod locations { · 2 unmodified lines |
| 274 | 521 | /// This checks that `obtain_envlessly()` returned paths that are likely to be correct and |
| 275 | 522 | /// that satisfy the most important properties based on the current system and process. |
| 276 | 523 | fn validated(self) -> Self { |
| 524 | + self.validate_global_folders(); |
| 525 | + self.validate_user_folder(); |
| 526 | + self |
| 527 | + } |
| 528 | + |
| 529 | + fn validate_global_folders(&self) { |
| 277 | 530 | match PlatformBitness::current().expect("Process and system 'bitness' should be available") { |
| 278 | 531 | PlatformBitness::Is32on32 => { |
| 279 | 532 | assert_eq!( |
| @@ -325,59 +578,114 @@ mod locations { · 45 unmodified lines |
| 325 | 578 | ); |
| 326 | 579 | } |
| 327 | 580 | } |
| 581 | + } |
| 328 | 582 | |
| 329 | | − self |
| 583 | + fn validate_user_folder(&self) { |
| 584 | + let expected = get_known_folder_path_with_flag(FOLDERID_LocalAppData, KF_FLAG_DEFAULT) |
| 585 | + .expect("The user's local application data directory is available") |
| 586 | + .join("Programs"); |
| 587 | + assert_eq!( |
| 588 | + self.user, expected, |
| 589 | + "The user program files directory is Programs in the local application data directory.", |
| 590 | + ); |
| 330 | 591 | } |
| 331 | 592 | } |
| 332 | 593 | |
| 333 | | − /// Paths relative to process architecture specific program files directories. |
| 594 | + /// Architecture-specific Git for Windows paths relative to the user program files directory. |
| 334 | 595 | #[derive(Clone, Debug)] |
| 335 | | − struct RelativeGitBinPaths<'a> { |
| 596 | + struct RelativeUserGitBinPaths<'a> { |
| 597 | + x86: &'a Path, |
| 598 | + x64: &'a Path, |
| 599 | + arm64: &'a Path, |
| 600 | + } |
| 601 | + |
| 602 | + impl<'a> RelativeUserGitBinPaths<'a> { |
| 603 | + /// Assert that `locations` leads with the given user path prefix, and extract the suffixes. |
| 604 | + fn assert_from(pf_user: &'a Path, locations: &'static [PathBuf]) -> Self { |
| 605 | + match locations { |
| 606 | + [path1, path2, path3, ..] => { |
| 607 | + let suffix_user_arm64 = path1 |
| 608 | + .strip_prefix(pf_user) |
| 609 | + .expect("It gives a per-user 64-bit ARM64 path and lists it first"); |
| 610 | + let suffix_user_x64 = path2 |
| 611 | + .strip_prefix(pf_user) |
| 612 | + .expect("It gives a per-user 64-bit x86 path and lists it second"); |
| 613 | + let suffix_user_x86 = path3 |
| 614 | + .strip_prefix(pf_user) |
| 615 | + .expect("It gives a per-user 32-bit x86 path and lists it third"); |
| 616 | + Self { |
| 617 | + x86: suffix_user_x86, |
| 618 | + x64: suffix_user_x64, |
| 619 | + arm64: suffix_user_arm64, |
| 620 | + } |
| 621 | + } |
| 622 | + other => panic!( |
| 623 | + "{:?} has length {}, so some expected leading user program files paths are absent", |
| 624 | + other, |
| 625 | + other.len() |
| 626 | + ), |
| 627 | + } |
| 628 | + } |
| 629 | + |
| 630 | + /// Assert that suffixes are common Git install locations relative to a program files directory. |
| 631 | + fn assert_architectures(&self) { |
| 632 | + assert_eq!(self.x86, Path::new("Git/mingw32/bin")); |
| 633 | + assert_eq!(self.x64, Path::new("Git/mingw64/bin")); |
| 634 | + assert_eq!(self.arm64, Path::new("Git/clangarm64/bin")); |
| 635 | + } |
| 636 | + } |
| 637 | + |
| 638 | + /// Architecture-specific Git for Windows paths relative to global program files directories. |
| 639 | + #[derive(Clone, Debug)] |
| 640 | + struct RelativeGlobalGitBinPaths<'a> { |
| 336 | 641 | x86: &'a Path, |
| 337 | 642 | maybe_x64: Option<&'a Path>, |
| 338 | 643 | maybe_arm64: Option<&'a Path>, |
| 339 | 644 | } |
| 340 | 645 | |
| 341 | | − impl<'a> RelativeGitBinPaths<'a> { |
| 342 | | − /// Assert that `locations` has the given path prefixes, and extract the suffixes. |
| 646 | + impl<'a> RelativeGlobalGitBinPaths<'a> { |
| 647 | + /// Assert that `locations` trails with the given global path prefixes, and extract the suffixes. |
| 343 | 648 | fn assert_from(pf: &'a ProgramFilesPaths, locations: &'static [PathBuf]) -> Self { |
| 344 | 649 | match locations { |
| 345 | | − [primary, secondary, tertiary] => { |
| 650 | + [_, _, _, path4, path5, path6] => { |
| 346 | 651 | let prefix_64bit = pf |
| 347 | 652 | .maybe_64bit |
| 348 | 653 | .as_ref() |
| 349 | | − .expect("It gives three paths only if some can be 64-bit"); |
| 350 | | − let suffix_arm64 = primary |
| 654 | + .expect("It gives 6 paths only if some global paths can be 64-bit"); |
| 655 | + let suffix_global_arm64 = path4 |
| 351 | 656 | .strip_prefix(prefix_64bit) |
| 352 | | − .expect("It gives the 64-bit ARM64 path and lists it first"); |
| 353 | | − let suffix_x64 = secondary |
| 657 | + .expect("It gives a global 64-bit ARM64 path and lists it fourth"); |
| 658 | + let suffix_global_x64 = path5 |
| 354 | 659 | .strip_prefix(prefix_64bit) |
| 355 | | − .expect("It gives the 64-bit x86 path and lists it second"); |
| 356 | | − let suffix_x86 = tertiary |
| 357 | | − .strip_prefix(pf.x86.as_path()) |
| 358 | | − .expect("It gives the 32-bit path and lists it third"); |
| 660 | + .expect("It gives a global 64-bit x86 path and lists it fifth"); |
| 661 | + let suffix_global_x86 = path6 |
| 662 | + .strip_prefix(&pf.x86) |
| 663 | + .expect("It gives a global 32-bit path and lists it sixth"); |
| 359 | 664 | Self { |
| 360 | | − x86: suffix_x86, |
| 361 | | − maybe_x64: Some(suffix_x64), |
| 362 | | − maybe_arm64: Some(suffix_arm64), |
| 665 | + x86: suffix_global_x86, |
| 666 | + maybe_x64: Some(suffix_global_x64), |
| 667 | + maybe_arm64: Some(suffix_global_arm64), |
| 363 | 668 | } |
| 364 | 669 | } |
| 365 | | − [only] => { |
| 366 | | − assert_eq!(pf.maybe_64bit, None, "It gives one path only if none can be 64-bit."); |
| 367 | | − let suffix_x86 = only |
| 368 | | − .strip_prefix(pf.x86.as_path()) |
| 369 | | − .expect("The one path it gives is the 32-bit path"); |
| 670 | + [_, _, _, path4] => { |
| 671 | + assert_eq!( |
| 672 | + pf.maybe_64bit, None, |
| 673 | + "It gives 4 paths only if no global paths can be 64-bit.", |
| 674 | + ); |
| 675 | + let suffix_global_x86 = path4 |
| 676 | + .strip_prefix(&pf.x86) |
| 677 | + .expect("It gives a global 32-bit path and lists it fourth"); |
| 370 | 678 | Self { |
| 371 | | − x86: suffix_x86, |
| 679 | + x86: suffix_global_x86, |
| 372 | 680 | maybe_x64: None, |
| 373 | 681 | maybe_arm64: None, |
| 374 | 682 | } |
| 375 | 683 | } |
| 376 | | − other => panic!("{:?} has length {}, expected 1 or 3.", other, other.len()), |
| 684 | + other => panic!("{:?} has length {}, expected 4 or 6.", other, other.len()), |
| 377 | 685 | } |
| 378 | 686 | } |
| 379 | 687 | |
| 380 | | − /// Assert that the suffixes (relative subdirectories) are the common per-architecture Git install locations. |
| 688 | + /// Assert that suffixes are common Git install locations relative to a program files directory. |
| 381 | 689 | fn assert_architectures(&self) { |
| 382 | 690 | assert_eq!(self.x86, Path::new("Git/mingw32/bin")); |
| 383 | 691 | |
| @@ -390,6 +698,28 @@ mod locations { · 6 unmodified lines |
| 390 | 698 | } |
| 391 | 699 | } |
| 392 | 700 | |
| 701 | + /// Architecture-specific Git for Windows paths relative to particular program files directories. |
| 702 | + #[derive(Clone, Debug)] |
| 703 | + struct RelativeGitBinPaths<'a> { |
| 704 | + global: RelativeGlobalGitBinPaths<'a>, |
| 705 | + user: RelativeUserGitBinPaths<'a>, |
| 706 | + } |
| 707 | + |
| 708 | + impl<'a> RelativeGitBinPaths<'a> { |
| 709 | + /// Assert that `locations` has the given path prefixes, and extract the suffixes. |
| 710 | + fn assert_from(pf: &'a ProgramFilesPaths, locations: &'static [PathBuf]) -> Self { |
| 711 | + let user = RelativeUserGitBinPaths::assert_from(&pf.user, locations); |
| 712 | + let global = RelativeGlobalGitBinPaths::assert_from(pf, locations); |
| 713 | + Self { global, user } |
| 714 | + } |
| 715 | + |
| 716 | + /// Assert that global and user suffixes (relative subdirectories) are common Git install locations. |
| 717 | + fn assert_architectures(&self) { |
| 718 | + self.global.assert_architectures(); |
| 719 | + self.user.assert_architectures(); |
| 720 | + } |
| 721 | + } |
| 722 | + |
| 393 | 723 | #[test] |
| 394 | 724 | fn alternative_locations() { |
| 395 | 725 | // Obtain program files directory paths by other means and check that they seem correct. |