Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Omit system/global config in fixture scripts even amidst strange filenames #1570

Merged
merged 11 commits into from
Aug 31, 2024

Conversation

EliahKagan
Copy link
Member

Since 33be0e0 (#966), configuration from the system and global scopes is cleared in the environment used to run test fixture scripts by setting GIT_CONFIG_SYSTEM and GIT_CONFIG_GLOBAL.

https://github.com/Byron/gitoxide/blob/dd65e7bd0adf7afeec377f47ba67970ae5126fa3/tests/tools/src/lib.rs#L591

https://github.com/Byron/gitoxide/blob/dd65e7bd0adf7afeec377f47ba67970ae5126fa3/tests/tools/src/lib.rs#L602-L603

This is usually effective, because there is usually not a file named : or -. However, these names are not treated specially, and they can exist, in which case configuration variables will be read from them. Because they would have to exist in the CWD of the fixture script, the impact is very limited. However, because tests (and thus their fixtures) often deliberately introduce edge cases, including unusual filenames, it is plausible for this problem to occur unintentionally.

Another factor that could lead to it is if such files are created accidentally, such as by passing - where it is meant to indicate stdin but not interpreted that way, or (though less likely) by using a : somewhere it is intended to be a separator when it is instead treated as a path. In particular, though I am not sure because they may just have been selected as valid but unlikely paths, it seems like those might have been the intentions of those names as they are currently used in gix-testtools. (The reason they do not have those meanings is that the : is in an environment variable that is interpreted as a single path rather than a list of paths, and the - is in an environment variable rather than on the command line.)

git(1) recommends using the null device path for GIT_CONFIG_SYSTEM and/or GIT_CONFIG_GLOBAL when one wishes to prevent real configuration associated with those scopes from being read:

GIT_CONFIG_GLOBAL
GIT_CONFIG_SYSTEM

Take the configuration from the given files instead from global or system-level configuration files. If GIT_CONFIG_SYSTEM is set, the system config file defined at build time (usually /etc/gitconfig) will not be read. Likewise, if GIT_CONFIG_GLOBAL is set, neither $HOME/.gitconfig nor $XDG_CONFIG_HOME/git/config will be read. Can be set to /dev/null to skip reading configuration files of the respective level.

In addition to being more reliable for the reasons described above, this has two more (also minor) advantages:

  • It clearly expresses intent, since it achieves the effect more as the documentation recommends doing it.
  • Reading the configuration with git config and specifying the scope explicitly with --global or --system no longer gives exit code 128 due to an attempt to open a nonexistent file.

This PR makes that change. For Windows, where /dev/null is not generally a usable path for the null device, it uses NUL.

This also adds tests that the system and global scopes are cleared and that attempts to access configuration variables avoid unusual results, for situations:

  • Where they were already successfully cleared.
  • Where weirdly named files (files named : or - depending on platform) would inject configuration for them until the changes here (those tests fail before the bugfix and pass after it, as expected).
  • When an actual file called NUL exists on Windows in the directory being operated in. This is possible with \\?\ paths, and the main point of testing this is to show that accessing the null device as NUL still works and is unaffected by such a file. Thus this is not just renaming the hypothetical file that would mess things up, but eliminating it.

Regarding that last point, the main goal of explicitly testing with a file called NUL is demonstrative. But there may be a practical benefit, in that it is possible to write code that wrongly opens such a file, if one starts out with NUL and joins it to the result of calling canonicalize(). The canonicalize() functions in Rust return UNC paths on Windows, which may be unexpected. (This, albeit deliberately, is how I made the file in the first place.)

It think testing with a file actually named NUL is not really necessary, and I am unsure if it's worthwhile to keep this or not, because the tests can be shorter without it. But another option, rather than removing that, would be to move these unit tests of private members in tests/tools/src/lib.rs from there to a newly created tests/tools/src/tests.rs module. Or both changes could be made. For now I've left the elaborated tests and also not (yet?) moved them to a different file. (Neither these tests nor anything affected by this PR should be confused with the integration tests introduced in #1560, which are inside tests/tools/tests/.)

Although the topic of this PR is somewhat conceptually related to that of some other recent PRs such as #1567, the small bug this is addressing and the changes it makes are independent of those. This PR introduces and uses a private NULL_DEVICE constant for the null device path, which is defined the same way as in the gix-path crate as of 9917d47 (#1568). But it doesn't necessarily make sense for gix-path to expose that, gix-testtools also does not already directly declare gix-path as a dependency, and I have not thought of a reasonable other gitoxide crate that ought to have, and commit to having, it. So at least for now I think the duplication is acceptable.

@EliahKagan
Copy link
Member Author

EliahKagan commented Aug 31, 2024

Although this really is independent of #1569 and all its predecessors, I think the history would be easier to understand if this is rebased anyway, since it looks like it would interact with those other recent changes even though it actually wouldn't. Given that running the tests again is not much of an inconvenience ever since the speedup in #1556, I'm going to go ahead and rebase this onto main.

Edit: Done.

Rather than just not having the configuration variable we add.

- Keep testing the current failure mode where the unusually named
  `never_path` files can actually exist.

- Also test for paths for those scopes' configuration files, coming
  from the environment.

- Test that the output of showing the full scope's contents is
  empty, of all values. Note that this includes the system scope
  but not all scopes higher than the global scope: the "unknown"
  scope associated with an Apple Git configuration is deliberately
  not asserted to be cleared here. (`gix-testtools` possibly
  intentionally does not clear that scope for fixture scripts.)
This covers a possible concern pertaining to a forthcoming minor
gix-testtools bugfix.

The null device, accessible as `/dev/null` on a Unix-like system,
or as `NUL` (and in some other ways) on Windows, is the best
candidate to replace the current value of `:` (on Unix) or `-`
(on Windows) for `GIT_CONFIG_SYSTEM` and `GIT_CONFIG_GLOBAL` when
test fixture scripts are running.

This is because the filenames `:` (on Unix) and `-` (on Windows) do
not have any special meaning. They are valid but unusual filenames;
files of these names most often do not exist, but can, and when
they do, their contents are used the populate the system or global
scope for test fixtures, which is not the intended behavior. `git`
does not treat them as anything but filenames: a `:` is not treated
as a separator because those variables are interpreted as single
paths rather than lists of paths, and a `-` is not treated to mean
standard input (or any other special data source) because it
appears in an environment variable rather than as a command-line
argument.

But `/dev/null` and `NUL` can exist, too.

On Unix, where `/dev/null` should be used, the whole point is that
`/dev/null` exists. That absolute path is not treated specially.
Instead, the device node for the null device is linked there.

On Windows, where the null device is named `NUL`, an entry for it
with that name does also sort of exist in the filesystem,
accessible via the fully qualified device path `\\.\NUL` as well as
by some other fully qualified forms such as `\??\NUL`. However, not
all interfaces support all path forms, so accessing it in those
ways is not necessarily more robust than accessing it with the
relative path `NUL`.

Yet it is possible for an actual file, not the null device, to
exist with the name `NUL` in an arbitrary directory. Such a file
can only be referred to using the UNC syntax with a `\\?\` prefixed
path (not to be confused with the `\\.\` device namespace prefix,
nor the NT `\??\` prefix).

When such a file exists, `NUL` itself still does not refer to it;
`NUL` still refers to the null device. The additon to the test here
(and refactorings to support it) create an acutal file called `NUL`
to demonstrate that changing `-` to `NUL` really does avoid getting
a configuration file, regardless of what files exist.
Ordinarily such a check would not be justified, but the behavior
of `canonicalize()` in giving UNC paths is non-obvious.
This uses the null device, `/dev/null` on Unix-like systems and
`NUL` on Windows, as the value of `GIT_CONFIG_SYSTEM` and
`GIT_CONFIG_GLOBAL` when `gix-testtols` runs test fixture shell
scripts.

`/dev/null` is explicitly recommended for this purpose, when
setting those environment variables for the purpose of preventing
configuration files from being read, in the Git documentation:

- https://git-scm.com/docs/git#Documentation/git.txt-codeGITCONFIGGLOBALcode

On Windows, `NUL` is an analogue of `/dev/null`. Even in the
unusual scenario that a `\\?\` prefixed UNC path is used to create
an actual file named `NUL` in the directory the fixture script
operates in, the relative path `NUL` still resolves to the null
device and not to that file.

The previous behavior was to use a value of `:` on Unix-like
systems or `-` on Windows. But these were really just unusual but
valid paths, such that files of those names could exist in any
location. `git` furthermore treats them as paths: a `:` is not
special in these environment variables because they hold a single
path rather than a list of paths, and a `-` is not special (for
example, it does not specify stdin) because it appears in an
environment variable rather than a command-line argument.

While `:` and `-` are unusual filenames, this code is used in
testing, including of edge cases where unusual files may be used.
So this change may make the test tools slightly more robust.
Besides the style change done in the previous commit, `clippy` also
identified `File::create_new` is newer than we may wish to rely on:

  error: current MSRV (Minimum Supported Rust Version) is `1.76.0` but this item is stable since `1.77.0`
     --> tests/tools/src/lib.rs:906:13
      |
  906 |             File::create_new(path)
      |             ^^^^^^^^^^^^^^^^

This changes that code to use `OpenOptions` instead. This is
simlar to the suggested equivalent code in the `File::create_new`
documentation:

  File::options().read(true).write(true).create_new(true).open(...)

But the approach used here differs in that `read(true)` is removed,
since this call is only creating and writing, not reading.
It still does clean up (that is done by RAII; explicitly calling
`close()` is just to be able to identify and handle errors). The
assertion that it does is not part of what the test is testing, nor
is it critical to the correctness of the test itself. I had put it
there to catch bugs that might arise during development of the test
itself. (In some libraries/frameworks, a recursive deletion on
Windows that involves files that need to be accessed with `\\?\`
paths does not automatically succeed.)
@EliahKagan EliahKagan changed the title Omit system and global config in fixture scripts even if strangely named files exist Omit system/global config in fixture scripts even amidst strange filenames Aug 31, 2024
Copy link
Member

@Byron Byron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's such a great catch - I definitely forgot about this atrocity and I am glad the overrides are now finally done properly.

Thank you!

@Byron Byron enabled auto-merge August 31, 2024 15:28
@Byron Byron merged commit 6f128dd into GitoxideLabs:main Aug 31, 2024
15 checks passed
@EliahKagan EliahKagan deleted the tools-nulldev branch August 31, 2024 20:39
@EliahKagan
Copy link
Member Author

EliahKagan commented Aug 31, 2024

I am glad the overrides are now finally done properly.

A remaining question is whether high-scoped configuration that is associated with the git installation, but not in the system scope, should be omitted from the environment in which test fixture scripts run. This is the difference between setting GIT_CONFIG_SYSTEM to /dev/null (or NUL) and setting GIT_CONFIG_NOSYSTEM to 1 (or true, etc.).

This distinction between the scopes affected by GIT_CONFIG_SYSTEM and GIT_CONFIG_NOSYSTEM is the reason gix-path cannot pass --system to git config -l. This relates to the "unknown" scope mentioned #1523 (comment) and in this code comment introduced in #1567:

https://github.com/Byron/gitoxide/blob/6f128dd6adf9148e859a0cd027ff1c0ba0b619c0/gix-path/src/env/git/mod.rs#L137-L140

So far, gix-testtools does not set GIT_CONFIG_NOSYSTEM and therefore does get the even higher "unknown" scope associated with an Apple Git installation. I suspect that leaving this in may be intentional and I am not sure that it really should be excluded.

Setting GIT_CONFIG_NOSYSTEM to 1 takes precedence over GIT_CONFIG_SYSTEM. So if that were done then a fixture script that doesn't clear GIT_CONFIG_NOSYSTEM but does set GIT_CONFIG_SYSTEM would not achieve the desired effect. I'm not sure how much of a drawback that is, however, since using GIT_CONFIG_SYSTEM to gain configuration variables in a fixture script seems much less likely than using GIT_CONFIG_GLOBAL, which is not suppressed by GIT_CONFIG_NOSYSTEM.

Setting variables in the local (or worktree) scope, as well as in the command scope with git -c or with GIT_CONFIG_{COUNT,KEY,VALUE}, are of course likewise not suppressed by anything to do with installation-level configuration, and these are even more likely ways than GIT_CONFIG_SYSTEM or GIT_CONFIG_GLOBAL for a test fixture script to set configuration for itself.

@Byron
Copy link
Member

Byron commented Sep 1, 2024

Thanks so much for interrupting the victory dance, I now think it's still unwarranted.

So far, gix-testtools does not set GIT_CONFIG_NOSYSTEM and therefore does get the even higher "unknown" scope associated with an Apple Git installation. I suspect that leaving this in may be intentional and I am not sure that it really should be excluded.

Actually, this sounds like a bug as tests should be isolated to just their own scope, without seeing anything related to the system.

Maybe this is something to try then - instead of setting GIT_CONFIG_SYSTEM to a null-value, one could suppress system scopes and the unknown scope.

I just tried this on MacOS and this works, leaving only the local scope.

> GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1 git config -l --name-only --show-origin --show-scope
local   file:.git/config        core.repositoryformatversion
local   file:.git/config        core.filemode
local   file:.git/config        core.bare
[..]

Whereas the following will still have the unknown scope like you said:

> GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null git config -l --name-only --show-origin --show-scope
unknown file:/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig    credential.helper
unknown file:/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig    init.defaultbranch
local   file:.git/config        core.repositoryformatversion
local   file:.git/config        core.filemode

Is this something you'd want to contribute?

@EliahKagan
Copy link
Member Author

Yes. I have opened #1571 for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants