← 返回首页
Fix Windows environment variable upcasing bug by EliahKagan · Pull Request #1650 · gitpython-developers/GitPython · GitHub
Skip to content

Navigation Menu

Toggle navigation
Sign in
Appearance settings
Search or jump to...

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Include my email address so I can be contacted

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Resetting focus
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (4) All 1 file type selected Viewed files
Conversations
Failed to load comments. Retry
Loading
Jump to
Jump to file
Failed to load files. Retry
Loading
Diff view
Unified
Split
Hide whitespace
Apply and reload
Show whitespace
Diff view
Unified
Split
Hide whitespace
Apply and reload
9 changes: 4 additions & 5 deletions git/cmd.py
Show comments View file Edit file Delete file Open in desktop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import subprocess
import threading
from textwrap import dedent
import unittest.mock

from git.compat import (
defenc,
Expand All @@ -24,7 +23,7 @@
is_win,
)
from git.exc import CommandError
from git.util import is_cygwin_git, cygpath, expand_path, remove_password_if_present
from git.util import is_cygwin_git, cygpath, expand_path, remove_password_if_present, patch_env

from .exc import GitCommandError, GitCommandNotFound, UnsafeOptionError, UnsafeProtocolError
from .util import (
Expand Down Expand Up @@ -965,10 +964,10 @@ def execute(
'"kill_after_timeout" feature is not supported on Windows.',
)
# Only search PATH, not CWD. This must be in the *caller* environment. The "1" can be any value.
patch_caller_env = unittest.mock.patch.dict(os.environ, {"NoDefaultCurrentDirectoryInExePath": "1"})
maybe_patch_caller_env = patch_env("NoDefaultCurrentDirectoryInExePath", "1")
else:
cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
patch_caller_env = contextlib.nullcontext()
maybe_patch_caller_env = contextlib.nullcontext()
# end handle

stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
Expand All @@ -984,7 +983,7 @@ def execute(
istream_ok,
)
try:
with patch_caller_env:
with maybe_patch_caller_env:
proc = Popen(
command,
env=env,
Expand Down
17 changes: 16 additions & 1 deletion git/util.py
Show comments View file Edit file Delete file Open in desktop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ def wrapper(self: "Remote", *args: Any, **kwargs: Any) -> T:

@contextlib.contextmanager
def cwd(new_dir: PathLike) -> Generator[PathLike, None, None]:
"""Context manager to temporarily change directory. Not reentrant."""
Comment thread
Byron marked this conversation as resolved.
Show resolved Hide resolved
old_dir = os.getcwd()
os.chdir(new_dir)
try:
Expand All @@ -158,6 +159,20 @@ def cwd(new_dir: PathLike) -> Generator[PathLike, None, None]:
os.chdir(old_dir)


@contextlib.contextmanager
def patch_env(name: str, value: str) -> Generator[None, None, None]:
"""Context manager to temporarily patch an environment variable."""
old_value = os.getenv(name)
os.environ[name] = value
try:
yield
finally:
if old_value is None:
del os.environ[name]
else:
os.environ[name] = old_value


def rmtree(path: PathLike) -> None:
"""Remove the given recursively.

Expand Down Expand Up @@ -935,7 +950,7 @@ def _obtain_lock_or_raise(self) -> None:
)

try:
with open(lock_file, mode='w'):
with open(lock_file, mode="w"):
Comment thread
Byron marked this conversation as resolved.
Show resolved Hide resolved
pass
except OSError as e:
raise IOError(str(e)) from e
Expand Down
13 changes: 13 additions & 0 deletions test/fixtures/env_case.py
Show comments View file Edit file Delete file Open in desktop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import subprocess
import sys

import git


_, working_dir, env_var_name = sys.argv

# Importing git should be enough, but this really makes sure Git.execute is called.
repo = git.Repo(working_dir) # Hold the reference.
git.Git(repo.working_dir).execute(["git", "version"])

print(subprocess.check_output(["set", env_var_name], shell=True, text=True))
35 changes: 20 additions & 15 deletions test/test_git.py
Show comments View file Edit file Delete file Open in desktop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,23 @@
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
import contextlib
import os
import shutil
import subprocess
import sys
from tempfile import TemporaryDirectory, TemporaryFile
from unittest import mock
from unittest import mock, skipUnless

from git import Git, refresh, GitCommandError, GitCommandNotFound, Repo, cmd
from test.lib import TestBase, fixture_path
from test.lib import with_rw_directory
from git.util import finalize_process
from git.util import cwd, finalize_process

import os.path as osp

from git.compat import is_win


@contextlib.contextmanager
def _chdir(new_dir):
"""Context manager to temporarily change directory. Not reentrant."""
old_dir = os.getcwd()
os.chdir(new_dir)
try:
yield
finally:
os.chdir(old_dir)


class TestGit(TestBase):
@classmethod
def setUpClass(cls):
Expand Down Expand Up @@ -102,9 +90,26 @@ def test_it_executes_git_not_from_cwd(self):
print("#!/bin/sh", file=file)
os.chmod(impostor_path, 0o755)

with _chdir(tmpdir):
with cwd(tmpdir):
self.assertRegex(self.git.execute(["git", "version"]), r"^git version\b")

@skipUnless(is_win, "The regression only affected Windows, and this test logic is OS-specific.")
def test_it_avoids_upcasing_unrelated_environment_variable_names(self):
old_name = "28f425ca_d5d8_4257_b013_8d63166c8158"
Comment thread
Byron marked this conversation as resolved.
Show resolved Hide resolved
if old_name == old_name.upper():
raise RuntimeError("test bug or strange locale: old_name invariant under upcasing")
Comment thread
Byron marked this conversation as resolved.
Show resolved Hide resolved
os.putenv(old_name, "1") # It has to be done this lower-level way to set it lower-case.

cmdline = [
sys.executable,
fixture_path("env_case.py"),
self.rorepo.working_dir,
old_name,
]
pair_text = subprocess.check_output(cmdline, shell=False, text=True)
new_name = pair_text.split("=")[0]
self.assertEqual(new_name, old_name)

def test_it_accepts_stdin(self):
filename = fixture_path("cat_file_blob")
with open(filename, "r") as fh:
Expand Down
Toggle all file notes Toggle all file annotations

Footer

© 2026 GitHub, Inc.