Skip to content

Commit d6fef5e

Browse files
committed
fix: Use nul instead of NUL on Windows
`NULL_DEVICE` is `/dev/null` except on Windows, where there are several possible choices. Previously we were using `NUL` because the more modern full path `\\.\NUL` is not supported by `git`. However, `git` also rejects `NUL`, when capitalized, on some Windows systems. This can be observed on Windows 11 ARM64 builds. In contrast, the lower-case `nul`, which Windows itself treats the same as `NUL`, is always accepted by Git for Windows. Although it's not readily apparent why Git for Windows accepts `NUL` on some Windows operating systems and/or platforms and not others, the preferential treatment of `nul` (not extending to `NUL`) can be seen in a few places in the Git for Windows source code, including in `mingw_access` (`strcmp` is case-sensitive): https://github.com/git-for-windows/git/blob/a36f720572491aafc59d63ff926b04d9fa787ec3/compat/mingw.c#L1093 Therefore, this changes `"NUL"` to `"nul"` for the Windows null device path. This allows some functionality where `git` is invoked that had been broken on ARM64 Windows systems to start working. See 7280a2d (GitoxideLabs#1567) and 3cf9fc1 (GitoxideLabs#1570) for further context. Git for Windows also special-cases the literal path `/dev/null` in access checks and when opening it for read or write (thought not if an attempt is made to execute or change directory to it). We nonetheless change `NUL` to `nul` on Windows, rather than changing it to `/dev/null` and using that value on all platforms. The reason is that treating `/dev/null` as the null device on Windows is specific to Git for Windows. `/dev/null` on Windows would not be the null device, nor otherwise special, if we open it ourselves, nor if opened indirectly by other programs that don't special-case it. Then it would be equivalent to `\dev\null` (a relative path, resolved as `X:\dev\null` where `X:` is replaced by the current drive, which is not what we want).
1 parent 48dfacf commit d6fef5e

File tree

2 files changed

+8
-4
lines changed

2 files changed

+8
-4
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,12 @@ pub(super) const EXE_NAME: &str = "git";
8484
/// The git executable is the one found in PATH or an alternative location.
8585
pub(super) static GIT_HIGHEST_SCOPE_CONFIG_PATH: Lazy<Option<BString>> = Lazy::new(exe_info);
8686

87+
// There are a number of ways to refer to the null device on Windows, but they are not all equally
88+
// well supported. Git for Windows rejects `\\.\NUL` and `\\.\nul`. On Windows 11 ARM64 (and maybe
89+
// some others), it rejects even the legacy name `NUL`, when capitalized. But it always accepts the
90+
// lower-case `nul`, handling it in various path checks, some of which are done case-insensitively.
8791
#[cfg(windows)]
88-
const NULL_DEVICE: &str = "NUL";
92+
const NULL_DEVICE: &str = "nul";
8993
#[cfg(not(windows))]
9094
const NULL_DEVICE: &str = "/dev/null";
9195

tests/tools/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ fn scripted_fixture_read_only_with_args_inner(
619619
}
620620

621621
#[cfg(windows)]
622-
const NULL_DEVICE: &str = "NUL";
622+
const NULL_DEVICE: &str = "nul"; // See `gix_path::env::git::NULL_DEVICE` on why this form is used.
623623
#[cfg(not(windows))]
624624
const NULL_DEVICE: &str = "/dev/null";
625625

@@ -1027,7 +1027,7 @@ mod tests {
10271027
const CONFIG_DATA: &[u8] = b"[foo]\n\tbar = baz\n";
10281028

10291029
let paths: &[PathBuf] = if cfg!(windows) {
1030-
let unc_literal_nul = dir.canonicalize().expect("directory exists").join("NUL");
1030+
let unc_literal_nul = dir.canonicalize().expect("directory exists").join("nul");
10311031
&[dir.join(SCOPE_ENV_VALUE), dir.join("-"), unc_literal_nul]
10321032
} else {
10331033
&[dir.join(SCOPE_ENV_VALUE), dir.join("-"), dir.join(":")]
@@ -1036,7 +1036,7 @@ mod tests {
10361036
for path in paths {
10371037
std::fs::write(path, CONFIG_DATA).expect("can write contents");
10381038
}
1039-
// Verify the files. This is mostly to show we really made a `\\?\...\NUL` on Windows.
1039+
// Verify the files. This is mostly to show we really made a `\\?\...\nul` on Windows.
10401040
for path in paths {
10411041
let buf = std::fs::read(path).expect("the file really exists");
10421042
assert_eq!(buf, CONFIG_DATA, "{path:?} should be a config file");

0 commit comments

Comments
 (0)