Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2a21f09

Browse files
Harmon758rickprice
authored andcommitted
v2.1.15
Try to merge changes from the patch, modified call to maybe_patch_caller_env More code changes More changes Fix syntax error Add blank line to remove it Remove blank line again Fix syntax error Fix syntax error Fix Python3 f string Add in venv, and sort list Change name of package to be more correct
1 parent bcf9f1c commit 2a21f09

File tree

8 files changed

+157
-28
lines changed

8 files changed

+157
-28
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.1.14
1+
2.1.15

doc/source/changes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
Changelog
33
=========
44

5+
2.1.15 - Bugfixes
6+
=================
7+
8+
Fix requirements.txt formatting and version lock gitdb2 to <3 for Python 2 compatibility.
9+
510
2.1.14 - Bugfixes
611
=================
712

git/cmd.py

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,59 @@ def pump_stream(cmdline, name, stream, is_decode, handler):
124124
return finalizer(process)
125125

126126

127+
def _safer_popen_windows(command, shell, env=None, **kwargs):
128+
"""Call :class:`subprocess.Popen` on Windows but don't include a CWD in the search.
129+
This avoids an untrusted search path condition where a file like ``git.exe`` in a
130+
malicious repository would be run when GitPython operates on the repository. The
131+
process using GitPython may have an untrusted repository's working tree as its
132+
current working directory. Some operations may temporarily change to that directory
133+
before running a subprocess. In addition, while by default GitPython does not run
134+
external commands with a shell, it can be made to do so, in which case the CWD of
135+
the subprocess, which GitPython usually sets to a repository working tree, can
136+
itself be searched automatically by the shell. This wrapper covers all those cases.
137+
:note: This currently works by setting the ``NoDefaultCurrentDirectoryInExePath``
138+
environment variable during subprocess creation. It also takes care of passing
139+
Windows-specific process creation flags, but that is unrelated to path search.
140+
:note: The current implementation contains a race condition on :attr:`os.environ`.
141+
GitPython isn't thread-safe, but a program using it on one thread should ideally
142+
be able to mutate :attr:`os.environ` on another, without unpredictable results.
143+
See comments in https://github.com/gitpython-developers/GitPython/pull/1650.
144+
"""
145+
# CREATE_NEW_PROCESS_GROUP is needed for some ways of killing it afterwards. See:
146+
# https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
147+
# https://docs.python.org/3/library/subprocess.html#subprocess.CREATE_NEW_PROCESS_GROUP
148+
creationflags = subprocess.CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP
149+
150+
# When using a shell, the shell is the direct subprocess, so the variable must be
151+
# set in its environment, to affect its search behavior. (The "1" can be any value.)
152+
if shell:
153+
safer_env = {} if env is None else dict(env)
154+
safer_env["NoDefaultCurrentDirectoryInExePath"] = "1"
155+
else:
156+
safer_env = env
157+
158+
# When not using a shell, the current process does the search in a CreateProcessW
159+
# API call, so the variable must be set in our environment. With a shell, this is
160+
# unnecessary, in versions where https://github.com/python/cpython/issues/101283 is
161+
# patched. If not, in the rare case the ComSpec environment variable is unset, the
162+
# shell is searched for unsafely. Setting NoDefaultCurrentDirectoryInExePath in all
163+
# cases, as here, is simpler and protects against that. (The "1" can be any value.)
164+
with patch_env("NoDefaultCurrentDirectoryInExePath", "1"):
165+
return Popen(
166+
command,
167+
shell=shell,
168+
env=safer_env,
169+
creationflags=creationflags,
170+
**kwargs
171+
)
172+
173+
174+
if os.name == "nt":
175+
safer_popen = _safer_popen_windows
176+
else:
177+
safer_popen = Popen
178+
179+
127180
def dashify(string):
128181
return string.replace('_', '-')
129182

@@ -144,11 +197,6 @@ def dict_to_slots_and__excluded_are_none(self, d, excluded=()):
144197
# value of Windows process creation flag taken from MSDN
145198
CREATE_NO_WINDOW = 0x08000000
146199

147-
## CREATE_NEW_PROCESS_GROUP is needed to allow killing it afterwards,
148-
# see https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
149-
PROC_CREATIONFLAGS = (CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP
150-
if is_win else 0)
151-
152200

153201
class Git(LazyMixin):
154202

@@ -721,19 +769,18 @@ def execute(self, command,
721769
log.debug("Popen(%s, cwd=%s, universal_newlines=%s, shell=%s, istream=%s)",
722770
command, cwd, universal_newlines, shell, istream_ok)
723771
try:
724-
proc = Popen(command,
725-
env=env,
726-
cwd=cwd,
727-
bufsize=-1,
728-
stdin=istream,
729-
stderr=PIPE,
730-
stdout=stdout_sink,
731-
shell=shell is not None and shell or self.USE_SHELL,
732-
close_fds=is_posix, # unsupported on windows
733-
universal_newlines=universal_newlines,
734-
creationflags=PROC_CREATIONFLAGS,
735-
**subprocess_kwargs
736-
)
772+
proc = safer_popen(
773+
command,
774+
env=env,
775+
cwd=cwd,
776+
bufsize=-1,
777+
stdin=istream,
778+
stderr=PIPE,
779+
stdout=stdout_sink,
780+
shell=shell is not None and shell or self.USE_SHELL,
781+
universal_newlines=universal_newlines,
782+
**subprocess_kwargs
783+
)
737784
except cmd_not_found_exception as err:
738785
raise GitCommandNotFound(command, err)
739786

git/index/fun.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
)
1414
import subprocess
1515

16-
from git.cmd import PROC_CREATIONFLAGS, handle_process_output
16+
from git.cmd import handle_process_output, safer_popen
1717
from git.compat import (
1818
PY3,
1919
defenc,
@@ -76,13 +76,12 @@ def run_commit_hook(name, index, *args):
7676
env['GIT_INDEX_FILE'] = safe_decode(index.path) if PY3 else safe_encode(index.path)
7777
env['GIT_EDITOR'] = ':'
7878
try:
79-
cmd = subprocess.Popen([hp] + list(args),
79+
cmd = safer_open([hp] + list(args),
8080
env=env,
8181
stdout=subprocess.PIPE,
8282
stderr=subprocess.PIPE,
8383
cwd=index.repo.working_dir,
84-
close_fds=is_posix,
85-
creationflags=PROC_CREATIONFLAGS,)
84+
close_fds=is_posix)
8685
except Exception as ex:
8786
raise HookExecutionError(hp, ex)
8887
else:

git/test/lib/helper.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import textwrap
1717
import time
1818
import unittest
19+
import venv
1920

2021
from git.compat import string_types, is_win
2122
from git.util import rmtree, cwd
@@ -36,7 +37,8 @@
3637
__all__ = (
3738
'fixture_path', 'fixture', 'StringProcessAdapter',
3839
'with_rw_directory', 'with_rw_repo', 'with_rw_and_rw_remote_repo',
39-
'TestBase', 'TestCase',
40+
'TestBase', 'VirtualEnvironment',
41+
'TestCase',
4042
'SkipTest', 'skipIf',
4143
'GIT_REPO', 'GIT_DAEMON_PORT'
4244
)
@@ -83,13 +85,13 @@ def with_rw_directory(func):
8385
test succeeds, but leave it otherwise to aid additional debugging"""
8486

8587
@wraps(func)
86-
def wrapper(self):
88+
def wrapper(self, *args, **kwargs):
8789
path = tempfile.mktemp(prefix=func.__name__)
8890
os.mkdir(path)
8991
keep = False
9092
try:
9193
try:
92-
return func(self, path)
94+
return func(self, path, *args, **kwargs)
9395
except Exception:
9496
log.info("Test %s.%s failed, output is at %r\n",
9597
type(self).__name__, func.__name__, path)
@@ -379,3 +381,46 @@ def _make_file(self, rela_path, data, repo=None):
379381
with open(abs_path, "w") as fp:
380382
fp.write(data)
381383
return abs_path
384+
385+
386+
class VirtualEnvironment:
387+
"""A newly created Python virtual environment for use in a test."""
388+
389+
__slots__ = ("_env_dir",)
390+
391+
def __init__(self, env_dir, with_pip):
392+
if os.name == "nt":
393+
self._env_dir = osp.realpath(env_dir)
394+
venv.create(self.env_dir, symlinks=False, with_pip=with_pip)
395+
else:
396+
self._env_dir = env_dir
397+
venv.create(self.env_dir, symlinks=True, with_pip=with_pip)
398+
399+
@property
400+
def env_dir(self):
401+
"""The top-level directory of the environment."""
402+
return self._env_dir
403+
404+
@property
405+
def python(self):
406+
"""Path to the Python executable in the environment."""
407+
return self._executable("python")
408+
409+
@property
410+
def pip(self):
411+
"""Path to the pip executable in the environment, or RuntimeError if absent."""
412+
return self._executable("pip")
413+
414+
@property
415+
def sources(self):
416+
"""Path to a src directory in the environment, which may not exist yet."""
417+
return os.path.join(self.env_dir, "src")
418+
419+
def _executable(self, basename):
420+
if os.name == "nt":
421+
path = osp.join(self.env_dir, "Scripts", basename + ".exe")
422+
else:
423+
path = osp.join(self.env_dir, "bin", basename)
424+
if osp.isfile(path) or osp.islink(path):
425+
return path
426+
raise RuntimeError("no regular file or symlink " + str(path))

git/test/test_git.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,30 @@
3838
except ImportError:
3939
import mock
4040

41+
try:
42+
from pathlib import Path
43+
except ImportError:
44+
from pathlib2 import Path
45+
4146
from git.compat import is_win
4247

4348

49+
@contextlib.contextmanager
50+
def _patch_out_env(name):
51+
try:
52+
old_value = os.environ[name]
53+
except KeyError:
54+
old_value = None
55+
else:
56+
del os.environ[name]
57+
try:
58+
yield
59+
finally:
60+
if old_value is not None:
61+
os.environ[name] = old_value
62+
63+
64+
4465
class TestGit(TestBase):
4566

4667
@classmethod

git/util.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,17 @@ def _get_exe_extensions():
192192

193193

194194
def py_where(program, path=None):
195+
"""Perform a path search to assist :func:`is_cygwin_git`.
196+
This is not robust for general use. It is an implementation detail of
197+
:func:`is_cygwin_git`. When a search following all shell rules is needed,
198+
:func:`shutil.which` can be used instead.
199+
:note: Neither this function nor :func:`shutil.which` will predict the effect of an
200+
executable search on a native Windows system due to a :class:`subprocess.Popen`
201+
call without ``shell=True``, because shell and non-shell executable search on
202+
Windows differ considerably.
203+
"""
204+
205+
195206
# From: http://stackoverflow.com/a/377028/548792
196207
winprog_exts = _get_exe_extensions()
197208

test-requirements.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
ddt>=1.1.1
21
coverage
2+
ddt>=1.1.1
33
flake8
4+
mock; python_version=='2.7'
45
nose
56
tox
6-
mock; python_version=='2.7'
7+
virtualenv

0 commit comments

Comments
 (0)