Skip to content

Commit 09e1b3d

Browse files
authored
Merge pull request #1650 from EliahKagan/envcase
Fix Windows environment variable upcasing bug
2 parents 8017421 + eebdb25 commit 09e1b3d

File tree

4 files changed

+53
-21
lines changed

4 files changed

+53
-21
lines changed

‎git/cmd.py‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
importsubprocess
1515
importthreading
1616
fromtextwrapimportdedent
17-
importunittest.mock
1817

1918
fromgit.compatimport (
2019
defenc,
@@ -24,7 +23,7 @@
2423
is_win,
2524
)
2625
fromgit.excimportCommandError
27-
fromgit.utilimportis_cygwin_git, cygpath, expand_path, remove_password_if_present
26+
fromgit.utilimportis_cygwin_git, cygpath, expand_path, remove_password_if_present, patch_env
2827

2928
from .excimportGitCommandError, GitCommandNotFound, UnsafeOptionError, UnsafeProtocolError
3029
from .utilimport (
@@ -965,10 +964,10 @@ def execute(
965964
'"kill_after_timeout" feature is not supported on Windows.',
966965
)
967966
# Only search PATH, not CWD. This must be in the *caller* environment. The "1" can be any value.
968-
patch_caller_env=unittest.mock.patch.dict(os.environ,{"NoDefaultCurrentDirectoryInExePath":"1"})
967+
maybe_patch_caller_env=patch_env("NoDefaultCurrentDirectoryInExePath","1")
969968
else:
970969
cmd_not_found_exception=FileNotFoundError# NOQA # exists, flake8 unknown @UndefinedVariable
971-
patch_caller_env=contextlib.nullcontext()
970+
maybe_patch_caller_env=contextlib.nullcontext()
972971
# end handle
973972

974973
stdout_sink=PIPEifwith_stdoutelsegetattr(subprocess, "DEVNULL", None) oropen(os.devnull, "wb")
@@ -984,7 +983,7 @@ def execute(
984983
istream_ok,
985984
)
986985
try:
987-
withpatch_caller_env:
986+
withmaybe_patch_caller_env:
988987
proc=Popen(
989988
command,
990989
env=env,

‎git/util.py‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ def wrapper(self: "Remote", *args: Any, **kwargs: Any) -> T:
150150

151151
@contextlib.contextmanager
152152
defcwd(new_dir: PathLike) ->Generator[PathLike, None, None]:
153+
"""Context manager to temporarily change directory. Not reentrant."""
153154
old_dir=os.getcwd()
154155
os.chdir(new_dir)
155156
try:
@@ -158,6 +159,20 @@ def cwd(new_dir: PathLike) -> Generator[PathLike, None, None]:
158159
os.chdir(old_dir)
159160

160161

162+
@contextlib.contextmanager
163+
defpatch_env(name: str, value: str) ->Generator[None, None, None]:
164+
"""Context manager to temporarily patch an environment variable."""
165+
old_value=os.getenv(name)
166+
os.environ[name] =value
167+
try:
168+
yield
169+
finally:
170+
ifold_valueisNone:
171+
delos.environ[name]
172+
else:
173+
os.environ[name] =old_value
174+
175+
161176
defrmtree(path: PathLike) ->None:
162177
"""Remove the given recursively.
163178
@@ -935,7 +950,7 @@ def _obtain_lock_or_raise(self) -> None:
935950
)
936951

937952
try:
938-
withopen(lock_file, mode='w'):
953+
withopen(lock_file, mode="w"):
939954
pass
940955
exceptOSErrorase:
941956
raiseIOError(str(e)) frome

‎test/fixtures/env_case.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
importsubprocess
2+
importsys
3+
4+
importgit
5+
6+
7+
_, working_dir, env_var_name=sys.argv
8+
9+
# Importing git should be enough, but this really makes sure Git.execute is called.
10+
repo=git.Repo(working_dir) # Hold the reference.
11+
git.Git(repo.working_dir).execute(["git", "version"])
12+
13+
print(subprocess.check_output(["set", env_var_name], shell=True, text=True))

‎test/test_git.py‎

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,23 @@
44
#
55
# This module is part of GitPython and is released under
66
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
7-
importcontextlib
87
importos
98
importshutil
109
importsubprocess
1110
importsys
1211
fromtempfileimportTemporaryDirectory, TemporaryFile
13-
fromunittestimportmock
12+
fromunittestimportmock, skipUnless
1413

1514
fromgitimportGit, refresh, GitCommandError, GitCommandNotFound, Repo, cmd
1615
fromtest.libimportTestBase, fixture_path
1716
fromtest.libimportwith_rw_directory
18-
fromgit.utilimportfinalize_process
17+
fromgit.utilimportcwd, finalize_process
1918

2019
importos.pathasosp
2120

2221
fromgit.compatimportis_win
2322

2423

25-
@contextlib.contextmanager
26-
def_chdir(new_dir):
27-
"""Context manager to temporarily change directory. Not reentrant."""
28-
old_dir=os.getcwd()
29-
os.chdir(new_dir)
30-
try:
31-
yield
32-
finally:
33-
os.chdir(old_dir)
34-
35-
3624
classTestGit(TestBase):
3725
@classmethod
3826
defsetUpClass(cls):
@@ -102,9 +90,26 @@ def test_it_executes_git_not_from_cwd(self):
10290
print("#!/bin/sh", file=file)
10391
os.chmod(impostor_path, 0o755)
10492

105-
with_chdir(tmpdir):
93+
withcwd(tmpdir):
10694
self.assertRegex(self.git.execute(["git", "version"]), r"^git version\b")
10795

96+
@skipUnless(is_win, "The regression only affected Windows, and this test logic is OS-specific.")
97+
deftest_it_avoids_upcasing_unrelated_environment_variable_names(self):
98+
old_name="28f425ca_d5d8_4257_b013_8d63166c8158"
99+
ifold_name==old_name.upper():
100+
raiseRuntimeError("test bug or strange locale: old_name invariant under upcasing")
101+
os.putenv(old_name, "1") # It has to be done this lower-level way to set it lower-case.
102+
103+
cmdline= [
104+
sys.executable,
105+
fixture_path("env_case.py"),
106+
self.rorepo.working_dir,
107+
old_name,
108+
]
109+
pair_text=subprocess.check_output(cmdline, shell=False, text=True)
110+
new_name=pair_text.split("=")[0]
111+
self.assertEqual(new_name, old_name)
112+
108113
deftest_it_accepts_stdin(self):
109114
filename=fixture_path("cat_file_blob")
110115
withopen(filename, "r") asfh:

0 commit comments

Comments
(0)