Skip to content

Commit e12ef59

Browse files
authored
Merge pull request #530 fixing some #525 Windows errors
+ git-daemon: + Use git-daemon PORT above 10k; on Windows all below need Admin rights. + Used relative daemon-paths with `--base-pth`. + Simplify git-daemon start/stop/ex-hanlding. +FIXED git-daemon @with_rw_and_rw_remote_repo(): + "Polish" most remote & config urls, converting \-->/. + test_base.test_with_rw_remote_and_rw_repo() PASS. + Remote: + test_remote: apply polish-urls on `_do_test_fetch()` checking function. + test_remote.test_base() now freezes on Windows! (so still hidden win_err). pump fetch-infos instead of GIL-reading stderr. + Push-cmd also keep (and optionally raise) any error messages. + `cmd.handle_process_output()` accepts null-finalizer, to pump completely stderr before raising any errors. + test: Enable `TestGit.test_environment()` on Windows (to checks stderr consumption). + util: delete unused `absolute_project_path()`. + Control separately *freezing* TCs on Windows with `git.util.HIDE_WINDOWS_FREEZE_ERRORS` flag.
2 parents e316575 + c8e914e commit e12ef59

File tree

12 files changed

+328
-361
lines changed

12 files changed

+328
-361
lines changed

git/cmd.py

+21-22
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@
4242
)
4343

4444

45-
execute_kwargs = set(('istream', 'with_keep_cwd', 'with_extended_output',
45+
execute_kwargs = set(('istream', 'with_extended_output',
4646
'with_exceptions', 'as_process', 'stdout_as_string',
4747
'output_stream', 'with_stdout', 'kill_after_timeout',
4848
'universal_newlines', 'shell'))
4949

50-
log = logging.getLogger('git.cmd')
50+
log = logging.getLogger(__name__)
5151
log.addHandler(logging.NullHandler())
5252

5353
__all__ = ('Git',)
@@ -59,7 +59,8 @@
5959
# Documentation
6060
## @{
6161

62-
def handle_process_output(process, stdout_handler, stderr_handler, finalizer, decode_streams=True):
62+
def handle_process_output(process, stdout_handler, stderr_handler,
63+
finalizer=None, decode_streams=True):
6364
"""Registers for notifications to lean that process output is ready to read, and dispatches lines to
6465
the respective line handlers.
6566
This function returns once the finalizer returns
@@ -108,10 +109,13 @@ def pump_stream(cmdline, name, stream, is_decode, handler):
108109
t.start()
109110
threads.append(t)
110111

112+
## FIXME: Why Join?? Will block if `stdin` needs feeding...
113+
#
111114
for t in threads:
112115
t.join()
113116

114-
return finalizer(process)
117+
if finalizer:
118+
return finalizer(process)
115119

116120

117121
def dashify(string):
@@ -186,14 +190,18 @@ def __setstate__(self, d):
186190
# Override this value using `Git.USE_SHELL = True`
187191
USE_SHELL = False
188192

189-
class AutoInterrupt(object):
193+
@classmethod
194+
def polish_url(cls, url):
195+
return url.replace("\\\\", "\\").replace("\\", "/")
190196

197+
class AutoInterrupt(object):
191198
"""Kill/Interrupt the stored process instance once this instance goes out of scope. It is
192199
used to prevent processes piling up in case iterators stop reading.
193200
Besides all attributes are wired through to the contained process object.
194201
195202
The wait method was overridden to perform automatic status code checking
196203
and possibly raise."""
204+
197205
__slots__ = ("proc", "args")
198206

199207
def __init__(self, proc, args):
@@ -239,7 +247,7 @@ def __del__(self):
239247
def __getattr__(self, attr):
240248
return getattr(self.proc, attr)
241249

242-
def wait(self, stderr=b''):
250+
def wait(self, stderr=b''): # TODO: Bad choice to mimic `proc.wait()` but with different args.
243251
"""Wait for the process and return its status code.
244252
245253
:param stderr: Previously read value of stderr, in case stderr is already closed.
@@ -418,7 +426,6 @@ def version_info(self):
418426

419427
def execute(self, command,
420428
istream=None,
421-
with_keep_cwd=False,
422429
with_extended_output=False,
423430
with_exceptions=True,
424431
as_process=False,
@@ -441,11 +448,6 @@ def execute(self, command,
441448
:param istream:
442449
Standard input filehandle passed to subprocess.Popen.
443450
444-
:param with_keep_cwd:
445-
Whether to use the current working directory from os.getcwd().
446-
The cmd otherwise uses its own working_dir that it has been initialized
447-
with if possible.
448-
449451
:param with_extended_output:
450452
Whether to return a (status, stdout, stderr) tuple.
451453
@@ -518,10 +520,7 @@ def execute(self, command,
518520
log.info(' '.join(command))
519521

520522
# Allow the user to have the command executed in their working dir.
521-
if with_keep_cwd or self._working_dir is None:
522-
cwd = os.getcwd()
523-
else:
524-
cwd = self._working_dir
523+
cwd = self._working_dir or os.getcwd()
525524

526525
# Start the process
527526
env = os.environ.copy()
@@ -544,6 +543,9 @@ def execute(self, command,
544543
cmd_not_found_exception = OSError
545544
# end handle
546545

546+
stdout_sink = (PIPE
547+
if with_stdout
548+
else getattr(subprocess, 'DEVNULL', open(os.devnull, 'wb')))
547549
log.debug("Popen(%s, cwd=%s, universal_newlines=%s, shell=%s)",
548550
command, cwd, universal_newlines, shell)
549551
try:
@@ -553,9 +555,9 @@ def execute(self, command,
553555
bufsize=-1,
554556
stdin=istream,
555557
stderr=PIPE,
556-
stdout=PIPE if with_stdout else open(os.devnull, 'wb'),
558+
stdout=stdout_sink,
557559
shell=shell is not None and shell or self.USE_SHELL,
558-
close_fds=(is_posix), # unsupported on windows
560+
close_fds=is_posix, # unsupported on windows
559561
universal_newlines=universal_newlines,
560562
creationflags=PROC_CREATIONFLAGS,
561563
**subprocess_kwargs
@@ -647,10 +649,7 @@ def as_text(stdout_value):
647649
# END handle debug printing
648650

649651
if with_exceptions and status != 0:
650-
if with_extended_output:
651-
raise GitCommandError(command, status, stderr_value, stdout_value)
652-
else:
653-
raise GitCommandError(command, status, stderr_value)
652+
raise GitCommandError(command, status, stderr_value, stdout_value)
654653

655654
if isinstance(stdout_value, bytes) and stdout_as_string: # could also be output_stream
656655
stdout_value = safe_decode(stdout_value)

git/objects/submodule/base.py

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from unittest.case import SkipTest
4242
from git.util import HIDE_WINDOWS_KNOWN_ERRORS
4343
from git.objects.base import IndexObject, Object
44+
from git.cmd import Git
4445

4546
__all__ = ["Submodule", "UpdateProgress"]
4647

@@ -394,6 +395,9 @@ def add(cls, repo, name, path, url=None, branch=None, no_checkout=False):
394395
mrepo = cls._clone_repo(repo, url, path, name, **kwargs)
395396
# END verify url
396397

398+
## See #525 for ensuring git urls in config-files valid under Windows.
399+
url = Git.polish_url(url)
400+
397401
# It's important to add the URL to the parent config, to let `git submodule` know.
398402
# otherwise there is a '-' character in front of the submodule listing
399403
# a38efa84daef914e4de58d1905a500d8d14aaf45 mymodule (v0.9.0-1-ga38efa8)

git/remote.py

+20-24
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@
2727
)
2828
from git.util import (
2929
join_path,
30-
finalize_process
3130
)
32-
from git.cmd import handle_process_output
31+
from git.cmd import handle_process_output, Git
3332
from gitdb.util import join
3433
from git.compat import (defenc, force_text, is_win)
3534
import logging
@@ -570,7 +569,7 @@ def create(cls, repo, name, url, **kwargs):
570569
:raise GitCommandError: in case an origin with that name already exists"""
571570
scmd = 'add'
572571
kwargs['insert_kwargs_after'] = scmd
573-
repo.git.remote(scmd, name, url, **kwargs)
572+
repo.git.remote(scmd, name, Git.polish_url(url), **kwargs)
574573
return cls(repo, name)
575574

576575
# add is an alias
@@ -630,25 +629,19 @@ def _get_fetch_info_from_stderr(self, proc, progress):
630629
cmds = set(PushInfo._flag_map.keys()) & set(FetchInfo._flag_map.keys())
631630

632631
progress_handler = progress.new_message_handler()
632+
handle_process_output(proc, None, progress_handler, finalizer=None, decode_streams=False)
633633

634-
stderr_text = None
634+
stderr_text = progress.error_lines and '\n'.join(progress.error_lines) or ''
635+
proc.wait(stderr=stderr_text)
636+
if stderr_text:
637+
log.warning("Error lines received while fetching: %s", stderr_text)
635638

636-
for line in proc.stderr:
639+
for line in progress.other_lines:
637640
line = force_text(line)
638-
for pline in progress_handler(line):
639-
# END handle special messages
640-
for cmd in cmds:
641-
if len(line) > 1 and line[0] == ' ' and line[1] == cmd:
642-
fetch_info_lines.append(line)
643-
continue
644-
# end find command code
645-
# end for each comand code we know
646-
# end for each line progress didn't handle
647-
# end
648-
if progress.error_lines():
649-
stderr_text = '\n'.join(progress.error_lines())
650-
651-
finalize_process(proc, stderr=stderr_text)
641+
for cmd in cmds:
642+
if len(line) > 1 and line[0] == ' ' and line[1] == cmd:
643+
fetch_info_lines.append(line)
644+
continue
652645

653646
# read head information
654647
with open(join(self.repo.git_dir, 'FETCH_HEAD'), 'rb') as fp:
@@ -687,16 +680,19 @@ def stdout_handler(line):
687680
try:
688681
output.append(PushInfo._from_line(self, line))
689682
except ValueError:
690-
# if an error happens, additional info is given which we cannot parse
683+
# If an error happens, additional info is given which we parse below.
691684
pass
692-
# END exception handling
693-
# END for each line
694685

686+
handle_process_output(proc, stdout_handler, progress_handler, finalizer=None, decode_streams=False)
687+
stderr_text = progress.error_lines and '\n'.join(progress.error_lines) or ''
695688
try:
696-
handle_process_output(proc, stdout_handler, progress_handler, finalize_process, decode_streams=False)
689+
proc.wait(stderr=stderr_text)
697690
except Exception:
698-
if len(output) == 0:
691+
if not output:
699692
raise
693+
elif stderr_text:
694+
log.warning("Error lines received while fetching: %s", stderr_text)
695+
700696
return output
701697

702698
def _assert_refspec(self):

git/repo/base.py

+13-41
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,11 @@
6262
import os
6363
import sys
6464
import re
65+
import logging
6566
from collections import namedtuple
6667

68+
log = logging.getLogger(__name__)
69+
6770
DefaultDBType = GitCmdObjectDB
6871
if sys.version_info[:2] < (2, 5): # python 2.4 compatiblity
6972
DefaultDBType = GitCmdObjectDB
@@ -871,46 +874,15 @@ def _clone(cls, git, url, path, odb_default_type, progress, **kwargs):
871874
if progress is not None:
872875
progress = to_progress_instance(progress)
873876

874-
# special handling for windows for path at which the clone should be
875-
# created.
876-
# tilde '~' will be expanded to the HOME no matter where the ~ occours. Hence
877-
# we at least give a proper error instead of letting git fail
878-
prev_cwd = None
879-
prev_path = None
880877
odbt = kwargs.pop('odbt', odb_default_type)
881-
if is_win:
882-
if '~' in path:
883-
raise OSError("Git cannot handle the ~ character in path %r correctly" % path)
884-
885-
# on windows, git will think paths like c: are relative and prepend the
886-
# current working dir ( before it fails ). We temporarily adjust the working
887-
# dir to make this actually work
888-
match = re.match("(\w:[/\\\])(.*)", path)
889-
if match:
890-
prev_cwd = os.getcwd()
891-
prev_path = path
892-
drive, rest_of_path = match.groups()
893-
os.chdir(drive)
894-
path = rest_of_path
895-
kwargs['with_keep_cwd'] = True
896-
# END cwd preparation
897-
# END windows handling
898-
899-
try:
900-
proc = git.clone(url, path, with_extended_output=True, as_process=True,
901-
v=True, **add_progress(kwargs, git, progress))
902-
if progress:
903-
handle_process_output(proc, None, progress.new_message_handler(), finalize_process)
904-
else:
905-
(stdout, stderr) = proc.communicate() # FIXME: Will block of outputs are big!
906-
finalize_process(proc, stderr=stderr)
907-
# end handle progress
908-
finally:
909-
if prev_cwd is not None:
910-
os.chdir(prev_cwd)
911-
path = prev_path
912-
# END reset previous working dir
913-
# END bad windows handling
878+
proc = git.clone(url, path, with_extended_output=True, as_process=True,
879+
v=True, **add_progress(kwargs, git, progress))
880+
if progress:
881+
handle_process_output(proc, None, progress.new_message_handler(), finalize_process)
882+
else:
883+
(stdout, stderr) = proc.communicate() # FIXME: Will block of outputs are big!
884+
log.debug("Cmd(%s)'s unused stdout: %s", getattr(proc, 'args', ''), stdout)
885+
finalize_process(proc, stderr=stderr)
914886

915887
# our git command could have a different working dir than our actual
916888
# environment, hence we prepend its working dir if required
@@ -922,10 +894,10 @@ def _clone(cls, git, url, path, odb_default_type, progress, **kwargs):
922894
# that contains the remote from which we were clones, git stops liking it
923895
# as it will escape the backslashes. Hence we undo the escaping just to be
924896
# sure
925-
repo = cls(os.path.abspath(path), odbt=odbt)
897+
repo = cls(path, odbt=odbt)
926898
if repo.remotes:
927899
with repo.remotes[0].config_writer as writer:
928-
writer.set_value('url', repo.remotes[0].url.replace("\\\\", "\\").replace("\\", "/"))
900+
writer.set_value('url', Git.polish_url(repo.remotes[0].url))
929901
# END handle remote repo
930902
return repo
931903

0 commit comments

Comments
 (0)