Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e12ef59

Browse files
authoredOct 14, 2016
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

Lines changed: 21 additions & 22 deletions
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 20 additions & 24 deletions
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

Lines changed: 13 additions & 41 deletions
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

‎git/test/lib/helper.py

Lines changed: 63 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,29 @@
55
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
66
from __future__ import print_function
77

8-
import os
9-
from unittest import TestCase
10-
import time
11-
import tempfile
8+
from functools import wraps
129
import io
1310
import logging
11+
import os
12+
import tempfile
13+
import textwrap
14+
import time
15+
from unittest import TestCase
16+
import unittest
1417

15-
from functools import wraps
16-
18+
from git.compat import string_types, is_win, PY3
1719
from git.util import rmtree
18-
from git.compat import string_types, is_win
19-
import textwrap
2020

21-
osp = os.path.dirname
21+
import os.path as osp
22+
23+
24+
ospd = osp.dirname
2225

23-
GIT_REPO = os.environ.get("GIT_PYTHON_TEST_GIT_REPO_BASE", osp(osp(osp(osp(__file__)))))
24-
GIT_DAEMON_PORT = os.environ.get("GIT_PYTHON_TEST_GIT_DAEMON_PORT", "9418")
26+
GIT_REPO = os.environ.get("GIT_PYTHON_TEST_GIT_REPO_BASE", ospd(ospd(ospd(ospd(__file__)))))
27+
GIT_DAEMON_PORT = os.environ.get("GIT_PYTHON_TEST_GIT_DAEMON_PORT", "19418")
2528

2629
__all__ = (
27-
'fixture_path', 'fixture', 'absolute_project_path', 'StringProcessAdapter',
30+
'fixture_path', 'fixture', 'StringProcessAdapter',
2831
'with_rw_directory', 'with_rw_repo', 'with_rw_and_rw_remote_repo', 'TestBase', 'TestCase',
2932
'GIT_REPO', 'GIT_DAEMON_PORT'
3033
)
@@ -35,18 +38,13 @@
3538

3639

3740
def fixture_path(name):
38-
test_dir = osp(osp(__file__))
39-
return os.path.join(test_dir, "fixtures", name)
41+
return osp.join(ospd(ospd(__file__)), 'fixtures', name)
4042

4143

4244
def fixture(name):
4345
with open(fixture_path(name), 'rb') as fd:
4446
return fd.read()
4547

46-
47-
def absolute_project_path():
48-
return os.path.abspath(os.path.join(osp(__file__), "..", ".."))
49-
5048
#} END routines
5149

5250
#{ Adapters
@@ -71,18 +69,6 @@ def wait(self):
7169
#{ Decorators
7270

7371

74-
def _mktemp(*args):
75-
"""Wrapper around default tempfile.mktemp to fix an osx issue
76-
:note: the OSX special case was removed as it was unclear why that was needed in the first place. It seems
77-
to be just fine without it. However, if we leave this special case, and if TMPDIR is set to something custom,
78-
prefixing /private/ will lead to incorrect paths on OSX."""
79-
tdir = tempfile.mktemp(*args)
80-
# See :note: above to learn why this is comented out.
81-
# if is_darwin:
82-
# tdir = '/private' + tdir
83-
return tdir
84-
85-
8672
def with_rw_directory(func):
8773
"""Create a temporary directory which can be written to, remove it if the
8874
test succeeds, but leave it otherwise to aid additional debugging"""
@@ -132,7 +118,7 @@ def repo_creator(self):
132118
if bare:
133119
prefix = ''
134120
# END handle prefix
135-
repo_dir = _mktemp("%sbare_%s" % (prefix, func.__name__))
121+
repo_dir = tempfile.mktemp("%sbare_%s" % (prefix, func.__name__))
136122
rw_repo = self.rorepo.clone(repo_dir, shared=True, bare=bare, n=True)
137123

138124
rw_repo.head.commit = rw_repo.commit(working_tree_ref)
@@ -165,26 +151,31 @@ def repo_creator(self):
165151
return argument_passer
166152

167153

168-
def launch_git_daemon(temp_dir, ip, port):
154+
def launch_git_daemon(base_path, ip, port):
169155
from git import Git
170156
if is_win:
171157
## On MINGW-git, daemon exists in .\Git\mingw64\libexec\git-core\,
172158
# but if invoked as 'git daemon', it detaches from parent `git` cmd,
173159
# and then CANNOT DIE!
174160
# So, invoke it as a single command.
175-
## Cygwin-git has no daemon.
161+
## Cygwin-git has no daemon. But it can use MINGW's.
176162
#
177-
daemon_cmd = ['git-daemon', temp_dir,
163+
daemon_cmd = ['git-daemon',
178164
'--enable=receive-pack',
179165
'--listen=%s' % ip,
180-
'--port=%s' % port]
166+
'--port=%s' % port,
167+
'--base-path=%s' % base_path,
168+
base_path]
181169
gd = Git().execute(daemon_cmd, as_process=True)
182170
else:
183-
gd = Git().daemon(temp_dir,
171+
gd = Git().daemon(base_path,
184172
enable='receive-pack',
185173
listen=ip,
186174
port=port,
175+
base_path=base_path,
187176
as_process=True)
177+
# yes, I know ... fortunately, this is always going to work if sleep time is just large enough
178+
time.sleep(0.5)
188179
return gd
189180

190181

@@ -212,15 +203,16 @@ def case(self, rw_repo, rw_remote_repo)
212203
See working dir info in with_rw_repo
213204
:note: We attempt to launch our own invocation of git-daemon, which will be shutdown at the end of the test.
214205
"""
215-
from git import Remote, GitCommandError
206+
from git import Git, Remote # To avoid circular deps.
207+
216208
assert isinstance(working_tree_ref, string_types), "Decorator requires ref name for working tree checkout"
217209

218210
def argument_passer(func):
219211

220212
@wraps(func)
221213
def remote_repo_creator(self):
222-
remote_repo_dir = _mktemp("remote_repo_%s" % func.__name__)
223-
repo_dir = _mktemp("remote_clone_non_bare_repo")
214+
remote_repo_dir = tempfile.mktemp("remote_repo_%s" % func.__name__)
215+
repo_dir = tempfile.mktemp("remote_clone_non_bare_repo")
224216

225217
rw_remote_repo = self.rorepo.clone(remote_repo_dir, shared=True, bare=True)
226218
# recursive alternates info ?
@@ -240,54 +232,38 @@ def remote_repo_creator(self):
240232
pass
241233
crw.set(section, "receivepack", True)
242234

243-
# initialize the remote - first do it as local remote and pull, then
244-
# we change the url to point to the daemon. The daemon should be started
245-
# by the user, not by us
235+
# Initialize the remote - first do it as local remote and pull, then
236+
# we change the url to point to the daemon.
246237
d_remote = Remote.create(rw_repo, "daemon_origin", remote_repo_dir)
247238
d_remote.fetch()
248-
remote_repo_url = "git://localhost:%s%s" % (GIT_DAEMON_PORT, remote_repo_dir)
249239

240+
base_path, rel_repo_dir = osp.split(remote_repo_dir)
241+
242+
remote_repo_url = Git.polish_url("git://localhost:%s/%s" % (GIT_DAEMON_PORT, rel_repo_dir))
250243
with d_remote.config_writer as cw:
251244
cw.set('url', remote_repo_url)
252245

253-
temp_dir = osp(_mktemp())
254-
gd = launch_git_daemon(temp_dir, '127.0.0.1', GIT_DAEMON_PORT)
255246
try:
256-
# yes, I know ... fortunately, this is always going to work if sleep time is just large enough
257-
time.sleep(0.5)
258-
# end
259-
260-
# try to list remotes to diagnoes whether the server is up
261-
try:
262-
rw_repo.git.ls_remote(d_remote)
263-
except GitCommandError as e:
264-
# We assume in good faith that we didn't start the daemon - but make sure we kill it anyway
265-
# Of course we expect it to work here already, but maybe there are timing constraints
266-
# on some platforms ?
267-
try:
268-
gd.proc.terminate()
269-
except Exception as ex:
270-
log.debug("Ignoring %r while terminating proc after %r.", ex, e)
271-
log.warning('git(%s) ls-remote failed due to:%s',
272-
rw_repo.git_dir, e)
273-
if is_win:
274-
msg = textwrap.dedent("""
275-
MINGW yet has problems with paths, and `git-daemon.exe` must be in PATH
276-
(look into .\Git\mingw64\libexec\git-core\);
277-
CYGWIN has no daemon, but if one exists, it gets along fine (has also paths problems)
278-
Anyhow, alternatively try starting `git-daemon` manually:""")
279-
else:
280-
msg = "Please try starting `git-daemon` manually:"
281-
282-
msg += textwrap.dedent("""
283-
git daemon --enable=receive-pack '%s'
284-
You can also run the daemon on a different port by passing --port=<port>"
285-
and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to <port>
286-
""" % temp_dir)
287-
from unittest import SkipTest
288-
raise SkipTest(msg) if is_win else AssertionError(msg)
289-
# END make assertion
290-
# END catch ls remote error
247+
gd = launch_git_daemon(Git.polish_url(base_path), '127.0.0.1', GIT_DAEMON_PORT)
248+
except Exception as ex:
249+
if is_win:
250+
msg = textwrap.dedent("""
251+
The `git-daemon.exe` must be in PATH.
252+
For MINGW, look into .\Git\mingw64\libexec\git-core\), but problems with paths might appear.
253+
CYGWIN has no daemon, but if one exists, it gets along fine (has also paths problems)
254+
Anyhow, alternatively try starting `git-daemon` manually:""")
255+
else:
256+
msg = "Please try starting `git-daemon` manually:"
257+
msg += textwrap.dedent("""
258+
git daemon --enable=receive-pack --base-path=%s %s
259+
You can also run the daemon on a different port by passing --port=<port>"
260+
and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to <port>
261+
""" % (base_path, base_path))
262+
raise AssertionError(ex, msg)
263+
# END make assertion
264+
else:
265+
# Try listing remotes, to diagnose whether the daemon is up.
266+
rw_repo.git.ls_remote(d_remote)
291267

292268
# adjust working dir
293269
prev_cwd = os.getcwd()
@@ -305,6 +281,7 @@ def remote_repo_creator(self):
305281

306282
finally:
307283
try:
284+
log.debug("Killing git-daemon...")
308285
gd.proc.kill()
309286
except:
310287
## Either it has died (and we're here), or it won't die, again here...
@@ -352,9 +329,13 @@ class TestBase(TestCase):
352329
of the project history ( to assure tests don't fail for others ).
353330
"""
354331

332+
if not PY3:
333+
assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
334+
355335
def _small_repo_url(self):
356336
""":return" a path to a small, clonable repository"""
357-
return os.path.join(self.rorepo.working_tree_dir, 'git/ext/gitdb/gitdb/ext/smmap')
337+
from git.cmd import Git
338+
return Git.polish_url(osp.join(self.rorepo.working_tree_dir, 'git/ext/gitdb/gitdb/ext/smmap'))
358339

359340
@classmethod
360341
def setUpClass(cls):
@@ -378,7 +359,7 @@ def _make_file(self, rela_path, data, repo=None):
378359
with the given data. Returns absolute path to created file.
379360
"""
380361
repo = repo or self.rorepo
381-
abs_path = os.path.join(repo.working_tree_dir, rela_path)
362+
abs_path = osp.join(repo.working_tree_dir, rela_path)
382363
with open(abs_path, "w") as fp:
383364
fp.write(data)
384365
return abs_path

‎git/test/test_base.py

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def tearDown(self):
4141
def test_base_object(self):
4242
# test interface of base object classes
4343
types = (Blob, Tree, Commit, TagObject)
44-
assert len(types) == len(self.type_tuples)
44+
self.assertEqual(len(types), len(self.type_tuples))
4545

4646
s = set()
4747
num_objs = 0
@@ -55,12 +55,12 @@ def test_base_object(self):
5555
item = obj_type(self.rorepo, binsha, 0, path)
5656
# END handle index objects
5757
num_objs += 1
58-
assert item.hexsha == hexsha
59-
assert item.type == typename
58+
self.assertEqual(item.hexsha, hexsha)
59+
self.assertEqual(item.type, typename)
6060
assert item.size
61-
assert item == item
62-
assert not item != item
63-
assert str(item) == item.hexsha
61+
self.assertEqual(item, item)
62+
self.assertNotEqual(not item, item)
63+
self.assertEqual(str(item), item.hexsha)
6464
assert repr(item)
6565
s.add(item)
6666

@@ -78,16 +78,16 @@ def test_base_object(self):
7878

7979
tmpfilename = tempfile.mktemp(suffix='test-stream')
8080
with open(tmpfilename, 'wb+') as tmpfile:
81-
assert item == item.stream_data(tmpfile)
81+
self.assertEqual(item, item.stream_data(tmpfile))
8282
tmpfile.seek(0)
83-
assert tmpfile.read() == data
83+
self.assertEqual(tmpfile.read(), data)
8484
os.remove(tmpfilename)
8585
# END for each object type to create
8686

8787
# each has a unique sha
88-
assert len(s) == num_objs
89-
assert len(s | s) == num_objs
90-
assert num_index_objs == 2
88+
self.assertEqual(len(s), num_objs)
89+
self.assertEqual(len(s | s), num_objs)
90+
self.assertEqual(num_index_objs, 2)
9191

9292
def test_get_object_type_by_name(self):
9393
for tname in base.Object.TYPES:
@@ -98,7 +98,7 @@ def test_get_object_type_by_name(self):
9898

9999
def test_object_resolution(self):
100100
# objects must be resolved to shas so they compare equal
101-
assert self.rorepo.head.reference.object == self.rorepo.active_branch.object
101+
self.assertEqual(self.rorepo.head.reference.object, self.rorepo.active_branch.object)
102102

103103
@with_rw_repo('HEAD', bare=True)
104104
def test_with_bare_rw_repo(self, bare_rw_repo):
@@ -110,17 +110,7 @@ def test_with_rw_repo(self, rw_repo):
110110
assert not rw_repo.config_reader("repository").getboolean("core", "bare")
111111
assert os.path.isdir(os.path.join(rw_repo.working_tree_dir, 'lib'))
112112

113-
# @skipIf(HIDE_WINDOWS_KNOWN_ERRORS, """
114-
# FIXME: helper.wrapper fails with:
115-
# PermissionError: [WinError 5] Access is denied:
116-
# 'C:\\Users\\appveyor\\AppData\\Local\\Temp\\1\\test_work_tree_unsupportedryfa60di\\
117-
# master_repo\\.git\\objects\\pack\\pack-bc9e0787aef9f69e1591ef38ea0a6f566ec66fe3.idx'
118-
# AND
119-
# FIXME: git-daemon failing with:
120-
# git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
121-
# cmdline: git ls-remote daemon_origin
122-
# stderr: 'fatal: bad config line 15 in file .git/config'
123-
# """)
113+
#@skipIf(HIDE_WINDOWS_FREEZE_ERRORS, "FIXME: Freezes! sometimes...")
124114
@with_rw_and_rw_remote_repo('0.1.6')
125115
def test_with_rw_remote_and_rw_repo(self, rw_repo, rw_remote_repo):
126116
assert not rw_repo.config_reader("repository").getboolean("core", "bare")

‎git/test/test_docs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def tearDown(self):
1616
import gc
1717
gc.collect()
1818

19-
# @skipIf(HIDE_WINDOWS_KNOWN_ERRORS,
19+
# @skipIf(HIDE_WINDOWS_KNOWN_ERRORS, ## ACTUALLY skipped by `git.submodule.base#L869`.
2020
# "FIXME: helper.wrapper fails with: PermissionError: [WinError 5] Access is denied: "
2121
# "'C:\\Users\\appveyor\\AppData\\Local\\Temp\\1\\test_work_tree_unsupportedryfa60di\\master_repo\\.git\\objects\\pack\\pack-bc9e0787aef9f69e1591ef38ea0a6f566ec66fe3.idx") # noqa E501
2222
@with_rw_directory

‎git/test/test_git.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -207,18 +207,15 @@ def test_environment(self, rw_dir):
207207
rw_repo = Repo.init(os.path.join(rw_dir, 'repo'))
208208
remote = rw_repo.create_remote('ssh-origin', "ssh://git@server/foo")
209209

210-
# This only works if we are not evaluating git-push/pull output in a thread !
211-
import select
212-
if hasattr(select, 'poll'):
213-
with rw_repo.git.custom_environment(GIT_SSH=path):
214-
try:
215-
remote.fetch()
216-
except GitCommandError as err:
217-
if sys.version_info[0] < 3 and is_darwin:
218-
self.assertIn('ssh-orig, ' in str(err))
219-
self.assertEqual(err.status, 128)
220-
else:
221-
self.assertIn('FOO', str(err))
210+
with rw_repo.git.custom_environment(GIT_SSH=path):
211+
try:
212+
remote.fetch()
213+
except GitCommandError as err:
214+
if sys.version_info[0] < 3 and is_darwin:
215+
self.assertIn('ssh-orig, ' in str(err))
216+
self.assertEqual(err.status, 128)
217+
else:
218+
self.assertIn('FOO', str(err))
222219

223220
def test_handle_process_output(self):
224221
from git.cmd import handle_process_output

‎git/test/test_remote.py

Lines changed: 122 additions & 104 deletions
Large diffs are not rendered by default.

‎git/test/test_repo.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -472,12 +472,16 @@ def test_creation_deletion(self):
472472
head = self.rorepo.create_head("new_head", "HEAD~1")
473473
self.rorepo.delete_head(head)
474474

475-
tag = self.rorepo.create_tag("new_tag", "HEAD~2")
476-
self.rorepo.delete_tag(tag)
475+
try:
476+
tag = self.rorepo.create_tag("new_tag", "HEAD~2")
477+
finally:
478+
self.rorepo.delete_tag(tag)
477479
with self.rorepo.config_writer():
478480
pass
479-
remote = self.rorepo.create_remote("new_remote", "git@server:repo.git")
480-
self.rorepo.delete_remote(remote)
481+
try:
482+
remote = self.rorepo.create_remote("new_remote", "git@server:repo.git")
483+
finally:
484+
self.rorepo.delete_remote(remote)
481485

482486
def test_comparison_and_hash(self):
483487
# this is only a preliminary test, more testing done in test_index

‎git/test/test_submodule.py

Lines changed: 39 additions & 37 deletions
Large diffs are not rendered by default.

‎git/util.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from functools import wraps
1818

1919
from git.compat import is_win
20-
from gitdb.util import (# NOQA
20+
from gitdb.util import (# NOQA @IgnorePep8
2121
make_sha,
2222
LockedFD, # @UnusedImport
2323
file_contents_ro, # @UnusedImport
@@ -51,6 +51,7 @@
5151
#: so the errors marked with this var are considered "acknowledged" ones, awaiting remedy,
5252
#: till then, we wish to hide them.
5353
HIDE_WINDOWS_KNOWN_ERRORS = is_win and os.environ.get('HIDE_WINDOWS_KNOWN_ERRORS', True)
54+
HIDE_WINDOWS_FREEZE_ERRORS = is_win and os.environ.get('HIDE_WINDOWS_FREEZE_ERRORS', HIDE_WINDOWS_KNOWN_ERRORS)
5455

5556
#{ Utility Methods
5657

@@ -198,33 +199,34 @@ class RemoteProgress(object):
198199
DONE_TOKEN = 'done.'
199200
TOKEN_SEPARATOR = ', '
200201

201-
__slots__ = ("_cur_line", "_seen_ops", "_error_lines")
202+
__slots__ = ('_cur_line',
203+
'_seen_ops',
204+
'error_lines', # Lines that started with 'error:' or 'fatal:'.
205+
'other_lines') # Lines not denoting progress (i.e.g. push-infos).
202206
re_op_absolute = re.compile(r"(remote: )?([\w\s]+):\s+()(\d+)()(.*)")
203207
re_op_relative = re.compile(r"(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)")
204208

205209
def __init__(self):
206210
self._seen_ops = list()
207211
self._cur_line = None
208-
self._error_lines = []
209-
210-
def error_lines(self):
211-
"""Returns all lines that started with error: or fatal:"""
212-
return self._error_lines
212+
self.error_lines = []
213+
self.other_lines = []
213214

214215
def _parse_progress_line(self, line):
215216
"""Parse progress information from the given line as retrieved by git-push
216217
or git-fetch.
217218
218-
Lines that seem to contain an error (i.e. start with error: or fatal:) are stored
219-
separately and can be queried using `error_lines()`.
219+
- Lines that do not contain progress info are stored in :attr:`other_lines`.
220+
- Lines that seem to contain an error (i.e. start with error: or fatal:) are stored
221+
in :attr:`error_lines`.
220222
221223
:return: list(line, ...) list of lines that could not be processed"""
222224
# handle
223225
# Counting objects: 4, done.
224226
# Compressing objects: 50% (1/2) \rCompressing objects: 100% (2/2) \rCompressing objects: 100% (2/2), done.
225227
self._cur_line = line
226-
if len(self._error_lines) > 0 or self._cur_line.startswith(('error:', 'fatal:')):
227-
self._error_lines.append(self._cur_line)
228+
if len(self.error_lines) > 0 or self._cur_line.startswith(('error:', 'fatal:')):
229+
self.error_lines.append(self._cur_line)
228230
return []
229231

230232
sub_lines = line.split('\r')
@@ -283,6 +285,7 @@ def _parse_progress_line(self, line):
283285
self.line_dropped(sline)
284286
# Note: Don't add this line to the failed lines, as we have to silently
285287
# drop it
288+
self.other_lines.extend(failed_lines)
286289
return failed_lines
287290
# END handle op code
288291

@@ -308,6 +311,7 @@ def _parse_progress_line(self, line):
308311
max_count and float(max_count),
309312
message)
310313
# END for each sub line
314+
self.other_lines.extend(failed_lines)
311315
return failed_lines
312316

313317
def new_message_handler(self):

0 commit comments

Comments
 (0)
Please sign in to comment.