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
import subprocess
1515
import threading
1616
from textwrap import dedent
17-
import unittest.mock
1817

1918
from git.compat import (
2019
defenc,
@@ -24,7 +23,7 @@
2423
is_win,
2524
)
2625
from git.exc import CommandError
27-
from git.util import is_cygwin_git, cygpath, expand_path, remove_password_if_present
26+
from git.util import is_cygwin_git, cygpath, expand_path, remove_password_if_present, patch_env
2827

2928
from .exc import GitCommandError, GitCommandNotFound, UnsafeOptionError, UnsafeProtocolError
3029
from .util import (
@@ -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 = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
@@ -984,7 +983,7 @@ def execute(
984983
istream_ok,
985984
)
986985
try:
987-
with patch_caller_env:
986+
with maybe_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
def cwd(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+
def patch_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+
if old_value is None:
171+
del os.environ[name]
172+
else:
173+
os.environ[name] = old_value
174+
175+
161176
def rmtree(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-
with open(lock_file, mode='w'):
953+
with open(lock_file, mode="w"):
939954
pass
940955
except OSError as e:
941956
raise IOError(str(e)) from e

test/fixtures/env_case.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import subprocess
2+
import sys
3+
4+
import git
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-
import contextlib
87
import os
98
import shutil
109
import subprocess
1110
import sys
1211
from tempfile import TemporaryDirectory, TemporaryFile
13-
from unittest import mock
12+
from unittest import mock, skipUnless
1413

1514
from git import Git, refresh, GitCommandError, GitCommandNotFound, Repo, cmd
1615
from test.lib import TestBase, fixture_path
1716
from test.lib import with_rw_directory
18-
from git.util import finalize_process
17+
from git.util import cwd, finalize_process
1918

2019
import os.path as osp
2120

2221
from git.compat import is_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
class TestGit(TestBase):
3725
@classmethod
3826
def setUpClass(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+
with cwd(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+
def test_it_avoids_upcasing_unrelated_environment_variable_names(self):
98+
old_name = "28f425ca_d5d8_4257_b013_8d63166c8158"
99+
if old_name == old_name.upper():
100+
raise RuntimeError("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
def test_it_accepts_stdin(self):
109114
filename = fixture_path("cat_file_blob")
110115
with open(filename, "r") as fh:

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy