Skip to content

Commit 75e6c6b

Browse files
authored
Merge pull request #2126 from ngie-eign/fix-execute-with_stdout-no-issues
git.cmd.Git.execute(..): fix `with_stdout=False`
2 parents 28c34c5 + 6fc4742 commit 75e6c6b

File tree

4 files changed

+58
-10
lines changed

4 files changed

+58
-10
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@ Contributors are:
5656
-Ethan Lin <et.repositories _at_ gmail.com>
5757
-Jonas Scharpf <jonas.scharpf _at_ checkmk.com>
5858
-Gordon Marx
59+
-Enji Cooper
5960

6061
Portions derived from other open source works and are clearly marked.

git/cmd.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,25 +1364,29 @@ def communicate() -> Tuple[AnyStr, AnyStr]:
13641364
if output_stream is None:
13651365
stdout_value, stderr_value = communicate()
13661366
# Strip trailing "\n".
1367-
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
1367+
if stdout_value is not None and stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
13681368
stdout_value = stdout_value[:-1]
1369-
if stderr_value.endswith(newline): # type: ignore[arg-type]
1369+
if stderr_value is not None and stderr_value.endswith(newline): # type: ignore[arg-type]
13701370
stderr_value = stderr_value[:-1]
13711371

13721372
status = proc.returncode
13731373
else:
13741374
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
1375-
stream_copy(proc.stdout, output_stream, max_chunk_size)
1376-
stdout_value = proc.stdout.read()
1377-
stderr_value = proc.stderr.read()
1375+
if proc.stdout is not None:
1376+
stream_copy(proc.stdout, output_stream, max_chunk_size)
1377+
stdout_value = proc.stdout.read()
1378+
if proc.stderr is not None:
1379+
stderr_value = proc.stderr.read()
13781380
# Strip trailing "\n".
1379-
if stderr_value.endswith(newline): # type: ignore[arg-type]
1381+
if stderr_value is not None and stderr_value.endswith(newline): # type: ignore[arg-type]
13801382
stderr_value = stderr_value[:-1]
13811383
status = proc.wait()
13821384
# END stdout handling
13831385
finally:
1384-
proc.stdout.close()
1385-
proc.stderr.close()
1386+
if proc.stdout is not None:
1387+
proc.stdout.close()
1388+
if proc.stderr is not None:
1389+
proc.stderr.close()
13861390

13871391
if self.GIT_PYTHON_TRACE == "full":
13881392
cmdstr = " ".join(redacted_command)

test/test_git.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import contextlib
77
import gc
88
import inspect
9+
import io
910
import logging
1011
import os
1112
import os.path as osp
@@ -201,6 +202,25 @@ def test_it_logs_istream_summary_for_stdin(self, case):
201202
def test_it_executes_git_and_returns_result(self):
202203
self.assertRegex(self.git.execute(["git", "version"]), r"^git version [\d\.]{2}.*$")
203204

205+
def test_it_output_stream_with_stdout_is_false(self):
206+
temp_stream = io.BytesIO()
207+
self.git.execute(
208+
["git", "version"],
209+
output_stream=temp_stream,
210+
with_stdout=False,
211+
)
212+
self.assertEqual(temp_stream.tell(), 0)
213+
214+
def test_it_executes_git_without_stdout_redirect(self):
215+
returncode, stdout, stderr = self.git.execute(
216+
["git", "version"],
217+
with_extended_output=True,
218+
with_stdout=False,
219+
)
220+
self.assertEqual(returncode, 0)
221+
self.assertIsNone(stdout)
222+
self.assertIsNotNone(stderr)
223+
204224
@ddt.data(
205225
# chdir_to_repo, shell, command, use_shell_impostor
206226
(False, False, ["git", "version"], False),

test/test_util.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def test_deletes_dir_with_readonly_files(self, tmp_path):
113113
sys.platform == "cygwin",
114114
reason="Cygwin can't set the permissions that make the test meaningful.",
115115
)
116-
def test_avoids_changing_permissions_outside_tree(self, tmp_path):
116+
def test_avoids_changing_permissions_outside_tree(self, tmp_path, request):
117117
# Automatically works on Windows, but on Unix requires either special handling
118118
# or refraining from attempting to fix PermissionError by making chmod calls.
119119

@@ -125,9 +125,32 @@ def test_avoids_changing_permissions_outside_tree(self, tmp_path):
125125

126126
dir2 = tmp_path / "dir2"
127127
dir2.mkdir()
128-
(dir2 / "symlink").symlink_to(dir1 / "file")
128+
symlink = dir2 / "symlink"
129+
symlink.symlink_to(dir1 / "file")
129130
dir2.chmod(stat.S_IRUSR | stat.S_IXUSR)
130131

132+
def preen_dir2():
133+
"""Don't leave unwritable directories behind.
134+
135+
pytest has difficulties cleaning up after the fact on some platforms,
136+
e.g., macOS, and whines incessantly until the issue is resolved--regardless
137+
of the pytest session.
138+
"""
139+
rwx = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
140+
if not dir2.exists():
141+
return
142+
if symlink.exists():
143+
try:
144+
# Try lchmod first, if the platform supports it.
145+
symlink.lchmod(rwx)
146+
except NotImplementedError:
147+
# The platform (probably win32) doesn't support lchmod; fall back to chmod.
148+
symlink.chmod(rwx)
149+
dir2.chmod(rwx)
150+
rmtree(dir2)
151+
152+
request.addfinalizer(preen_dir2)
153+
131154
try:
132155
rmtree(dir2)
133156
except PermissionError:

0 commit comments

Comments
 (0)