Skip to content

Commit

Permalink
Merge pull request #530 fixing some #525 Windows errors
Browse files Browse the repository at this point in the history
+ 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.
ankostis authored Oct 14, 2016

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents e316575 + c8e914e commit e12ef59
Showing 12 changed files with 328 additions and 361 deletions.
43 changes: 21 additions & 22 deletions git/cmd.py
Original file line number Diff line number Diff line change
@@ -42,12 +42,12 @@
)


execute_kwargs = set(('istream', 'with_keep_cwd', 'with_extended_output',
execute_kwargs = set(('istream', 'with_extended_output',
'with_exceptions', 'as_process', 'stdout_as_string',
'output_stream', 'with_stdout', 'kill_after_timeout',
'universal_newlines', 'shell'))

log = logging.getLogger('git.cmd')
log = logging.getLogger(__name__)
log.addHandler(logging.NullHandler())

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

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

## FIXME: Why Join?? Will block if `stdin` needs feeding...
#
for t in threads:
t.join()

return finalizer(process)
if finalizer:
return finalizer(process)


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

class AutoInterrupt(object):
@classmethod
def polish_url(cls, url):
return url.replace("\\\\", "\\").replace("\\", "/")

class AutoInterrupt(object):
"""Kill/Interrupt the stored process instance once this instance goes out of scope. It is
used to prevent processes piling up in case iterators stop reading.
Besides all attributes are wired through to the contained process object.
The wait method was overridden to perform automatic status code checking
and possibly raise."""

__slots__ = ("proc", "args")

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

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

def execute(self, command,
istream=None,
with_keep_cwd=False,
with_extended_output=False,
with_exceptions=True,
as_process=False,
@@ -441,11 +448,6 @@ def execute(self, command,
:param istream:
Standard input filehandle passed to subprocess.Popen.
:param with_keep_cwd:
Whether to use the current working directory from os.getcwd().
The cmd otherwise uses its own working_dir that it has been initialized
with if possible.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
@@ -518,10 +520,7 @@ def execute(self, command,
log.info(' '.join(command))

# Allow the user to have the command executed in their working dir.
if with_keep_cwd or self._working_dir is None:
cwd = os.getcwd()
else:
cwd = self._working_dir
cwd = self._working_dir or os.getcwd()

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

stdout_sink = (PIPE
if with_stdout
else getattr(subprocess, 'DEVNULL', open(os.devnull, 'wb')))
log.debug("Popen(%s, cwd=%s, universal_newlines=%s, shell=%s)",
command, cwd, universal_newlines, shell)
try:
@@ -553,9 +555,9 @@ def execute(self, command,
bufsize=-1,
stdin=istream,
stderr=PIPE,
stdout=PIPE if with_stdout else open(os.devnull, 'wb'),
stdout=stdout_sink,
shell=shell is not None and shell or self.USE_SHELL,
close_fds=(is_posix), # unsupported on windows
close_fds=is_posix, # unsupported on windows
universal_newlines=universal_newlines,
creationflags=PROC_CREATIONFLAGS,
**subprocess_kwargs
@@ -647,10 +649,7 @@ def as_text(stdout_value):
# END handle debug printing

if with_exceptions and status != 0:
if with_extended_output:
raise GitCommandError(command, status, stderr_value, stdout_value)
else:
raise GitCommandError(command, status, stderr_value)
raise GitCommandError(command, status, stderr_value, stdout_value)

if isinstance(stdout_value, bytes) and stdout_as_string: # could also be output_stream
stdout_value = safe_decode(stdout_value)
4 changes: 4 additions & 0 deletions git/objects/submodule/base.py
Original file line number Diff line number Diff line change
@@ -41,6 +41,7 @@
from unittest.case import SkipTest
from git.util import HIDE_WINDOWS_KNOWN_ERRORS
from git.objects.base import IndexObject, Object
from git.cmd import Git

__all__ = ["Submodule", "UpdateProgress"]

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

## See #525 for ensuring git urls in config-files valid under Windows.
url = Git.polish_url(url)

# It's important to add the URL to the parent config, to let `git submodule` know.
# otherwise there is a '-' character in front of the submodule listing
# a38efa84daef914e4de58d1905a500d8d14aaf45 mymodule (v0.9.0-1-ga38efa8)
44 changes: 20 additions & 24 deletions git/remote.py
Original file line number Diff line number Diff line change
@@ -27,9 +27,8 @@
)
from git.util import (
join_path,
finalize_process
)
from git.cmd import handle_process_output
from git.cmd import handle_process_output, Git
from gitdb.util import join
from git.compat import (defenc, force_text, is_win)
import logging
@@ -570,7 +569,7 @@ def create(cls, repo, name, url, **kwargs):
:raise GitCommandError: in case an origin with that name already exists"""
scmd = 'add'
kwargs['insert_kwargs_after'] = scmd
repo.git.remote(scmd, name, url, **kwargs)
repo.git.remote(scmd, name, Git.polish_url(url), **kwargs)
return cls(repo, name)

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

progress_handler = progress.new_message_handler()
handle_process_output(proc, None, progress_handler, finalizer=None, decode_streams=False)

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

for line in proc.stderr:
for line in progress.other_lines:
line = force_text(line)
for pline in progress_handler(line):
# END handle special messages
for cmd in cmds:
if len(line) > 1 and line[0] == ' ' and line[1] == cmd:
fetch_info_lines.append(line)
continue
# end find command code
# end for each comand code we know
# end for each line progress didn't handle
# end
if progress.error_lines():
stderr_text = '\n'.join(progress.error_lines())

finalize_process(proc, stderr=stderr_text)
for cmd in cmds:
if len(line) > 1 and line[0] == ' ' and line[1] == cmd:
fetch_info_lines.append(line)
continue

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

handle_process_output(proc, stdout_handler, progress_handler, finalizer=None, decode_streams=False)
stderr_text = progress.error_lines and '\n'.join(progress.error_lines) or ''
try:
handle_process_output(proc, stdout_handler, progress_handler, finalize_process, decode_streams=False)
proc.wait(stderr=stderr_text)
except Exception:
if len(output) == 0:
if not output:
raise
elif stderr_text:
log.warning("Error lines received while fetching: %s", stderr_text)

return output

def _assert_refspec(self):
54 changes: 13 additions & 41 deletions git/repo/base.py
Original file line number Diff line number Diff line change
@@ -62,8 +62,11 @@
import os
import sys
import re
import logging
from collections import namedtuple

log = logging.getLogger(__name__)

DefaultDBType = GitCmdObjectDB
if sys.version_info[:2] < (2, 5): # python 2.4 compatiblity
DefaultDBType = GitCmdObjectDB
@@ -871,46 +874,15 @@ def _clone(cls, git, url, path, odb_default_type, progress, **kwargs):
if progress is not None:
progress = to_progress_instance(progress)

# special handling for windows for path at which the clone should be
# created.
# tilde '~' will be expanded to the HOME no matter where the ~ occours. Hence
# we at least give a proper error instead of letting git fail
prev_cwd = None
prev_path = None
odbt = kwargs.pop('odbt', odb_default_type)
if is_win:
if '~' in path:
raise OSError("Git cannot handle the ~ character in path %r correctly" % path)

# on windows, git will think paths like c: are relative and prepend the
# current working dir ( before it fails ). We temporarily adjust the working
# dir to make this actually work
match = re.match("(\w:[/\\\])(.*)", path)
if match:
prev_cwd = os.getcwd()
prev_path = path
drive, rest_of_path = match.groups()
os.chdir(drive)
path = rest_of_path
kwargs['with_keep_cwd'] = True
# END cwd preparation
# END windows handling

try:
proc = git.clone(url, path, with_extended_output=True, as_process=True,
v=True, **add_progress(kwargs, git, progress))
if progress:
handle_process_output(proc, None, progress.new_message_handler(), finalize_process)
else:
(stdout, stderr) = proc.communicate() # FIXME: Will block of outputs are big!
finalize_process(proc, stderr=stderr)
# end handle progress
finally:
if prev_cwd is not None:
os.chdir(prev_cwd)
path = prev_path
# END reset previous working dir
# END bad windows handling
proc = git.clone(url, path, with_extended_output=True, as_process=True,
v=True, **add_progress(kwargs, git, progress))
if progress:
handle_process_output(proc, None, progress.new_message_handler(), finalize_process)
else:
(stdout, stderr) = proc.communicate() # FIXME: Will block of outputs are big!
log.debug("Cmd(%s)'s unused stdout: %s", getattr(proc, 'args', ''), stdout)
finalize_process(proc, stderr=stderr)

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

145 changes: 63 additions & 82 deletions git/test/lib/helper.py
Original file line number Diff line number Diff line change
@@ -5,26 +5,29 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
from __future__ import print_function

import os
from unittest import TestCase
import time
import tempfile
from functools import wraps
import io
import logging
import os
import tempfile
import textwrap
import time
from unittest import TestCase
import unittest

from functools import wraps

from git.compat import string_types, is_win, PY3
from git.util import rmtree
from git.compat import string_types, is_win
import textwrap

osp = os.path.dirname
import os.path as osp


ospd = osp.dirname

GIT_REPO = os.environ.get("GIT_PYTHON_TEST_GIT_REPO_BASE", osp(osp(osp(osp(__file__)))))
GIT_DAEMON_PORT = os.environ.get("GIT_PYTHON_TEST_GIT_DAEMON_PORT", "9418")
GIT_REPO = os.environ.get("GIT_PYTHON_TEST_GIT_REPO_BASE", ospd(ospd(ospd(ospd(__file__)))))
GIT_DAEMON_PORT = os.environ.get("GIT_PYTHON_TEST_GIT_DAEMON_PORT", "19418")

__all__ = (
'fixture_path', 'fixture', 'absolute_project_path', 'StringProcessAdapter',
'fixture_path', 'fixture', 'StringProcessAdapter',
'with_rw_directory', 'with_rw_repo', 'with_rw_and_rw_remote_repo', 'TestBase', 'TestCase',
'GIT_REPO', 'GIT_DAEMON_PORT'
)
@@ -35,18 +38,13 @@


def fixture_path(name):
test_dir = osp(osp(__file__))
return os.path.join(test_dir, "fixtures", name)
return osp.join(ospd(ospd(__file__)), 'fixtures', name)


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


def absolute_project_path():
return os.path.abspath(os.path.join(osp(__file__), "..", ".."))

#} END routines

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


def _mktemp(*args):
"""Wrapper around default tempfile.mktemp to fix an osx issue
:note: the OSX special case was removed as it was unclear why that was needed in the first place. It seems
to be just fine without it. However, if we leave this special case, and if TMPDIR is set to something custom,
prefixing /private/ will lead to incorrect paths on OSX."""
tdir = tempfile.mktemp(*args)
# See :note: above to learn why this is comented out.
# if is_darwin:
# tdir = '/private' + tdir
return tdir


def with_rw_directory(func):
"""Create a temporary directory which can be written to, remove it if the
test succeeds, but leave it otherwise to aid additional debugging"""
@@ -132,7 +118,7 @@ def repo_creator(self):
if bare:
prefix = ''
# END handle prefix
repo_dir = _mktemp("%sbare_%s" % (prefix, func.__name__))
repo_dir = tempfile.mktemp("%sbare_%s" % (prefix, func.__name__))
rw_repo = self.rorepo.clone(repo_dir, shared=True, bare=bare, n=True)

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


def launch_git_daemon(temp_dir, ip, port):
def launch_git_daemon(base_path, ip, port):
from git import Git
if is_win:
## On MINGW-git, daemon exists in .\Git\mingw64\libexec\git-core\,
# but if invoked as 'git daemon', it detaches from parent `git` cmd,
# and then CANNOT DIE!
# So, invoke it as a single command.
## Cygwin-git has no daemon.
## Cygwin-git has no daemon. But it can use MINGW's.
#
daemon_cmd = ['git-daemon', temp_dir,
daemon_cmd = ['git-daemon',
'--enable=receive-pack',
'--listen=%s' % ip,
'--port=%s' % port]
'--port=%s' % port,
'--base-path=%s' % base_path,
base_path]
gd = Git().execute(daemon_cmd, as_process=True)
else:
gd = Git().daemon(temp_dir,
gd = Git().daemon(base_path,
enable='receive-pack',
listen=ip,
port=port,
base_path=base_path,
as_process=True)
# yes, I know ... fortunately, this is always going to work if sleep time is just large enough
time.sleep(0.5)
return gd


@@ -212,15 +203,16 @@ def case(self, rw_repo, rw_remote_repo)
See working dir info in with_rw_repo
:note: We attempt to launch our own invocation of git-daemon, which will be shutdown at the end of the test.
"""
from git import Remote, GitCommandError
from git import Git, Remote # To avoid circular deps.

assert isinstance(working_tree_ref, string_types), "Decorator requires ref name for working tree checkout"

def argument_passer(func):

@wraps(func)
def remote_repo_creator(self):
remote_repo_dir = _mktemp("remote_repo_%s" % func.__name__)
repo_dir = _mktemp("remote_clone_non_bare_repo")
remote_repo_dir = tempfile.mktemp("remote_repo_%s" % func.__name__)
repo_dir = tempfile.mktemp("remote_clone_non_bare_repo")

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

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

base_path, rel_repo_dir = osp.split(remote_repo_dir)

remote_repo_url = Git.polish_url("git://localhost:%s/%s" % (GIT_DAEMON_PORT, rel_repo_dir))
with d_remote.config_writer as cw:
cw.set('url', remote_repo_url)

temp_dir = osp(_mktemp())
gd = launch_git_daemon(temp_dir, '127.0.0.1', GIT_DAEMON_PORT)
try:
# yes, I know ... fortunately, this is always going to work if sleep time is just large enough
time.sleep(0.5)
# end

# try to list remotes to diagnoes whether the server is up
try:
rw_repo.git.ls_remote(d_remote)
except GitCommandError as e:
# We assume in good faith that we didn't start the daemon - but make sure we kill it anyway
# Of course we expect it to work here already, but maybe there are timing constraints
# on some platforms ?
try:
gd.proc.terminate()
except Exception as ex:
log.debug("Ignoring %r while terminating proc after %r.", ex, e)
log.warning('git(%s) ls-remote failed due to:%s',
rw_repo.git_dir, e)
if is_win:
msg = textwrap.dedent("""
MINGW yet has problems with paths, and `git-daemon.exe` must be in PATH
(look into .\Git\mingw64\libexec\git-core\);
CYGWIN has no daemon, but if one exists, it gets along fine (has also paths problems)
Anyhow, alternatively try starting `git-daemon` manually:""")
else:
msg = "Please try starting `git-daemon` manually:"

msg += textwrap.dedent("""
git daemon --enable=receive-pack '%s'
You can also run the daemon on a different port by passing --port=<port>"
and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to <port>
""" % temp_dir)
from unittest import SkipTest
raise SkipTest(msg) if is_win else AssertionError(msg)
# END make assertion
# END catch ls remote error
gd = launch_git_daemon(Git.polish_url(base_path), '127.0.0.1', GIT_DAEMON_PORT)
except Exception as ex:
if is_win:
msg = textwrap.dedent("""
The `git-daemon.exe` must be in PATH.
For MINGW, look into .\Git\mingw64\libexec\git-core\), but problems with paths might appear.
CYGWIN has no daemon, but if one exists, it gets along fine (has also paths problems)
Anyhow, alternatively try starting `git-daemon` manually:""")
else:
msg = "Please try starting `git-daemon` manually:"
msg += textwrap.dedent("""
git daemon --enable=receive-pack --base-path=%s %s
You can also run the daemon on a different port by passing --port=<port>"
and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to <port>
""" % (base_path, base_path))
raise AssertionError(ex, msg)
# END make assertion
else:
# Try listing remotes, to diagnose whether the daemon is up.
rw_repo.git.ls_remote(d_remote)

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

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

if not PY3:
assertRaisesRegex = unittest.TestCase.assertRaisesRegexp

def _small_repo_url(self):
""":return" a path to a small, clonable repository"""
return os.path.join(self.rorepo.working_tree_dir, 'git/ext/gitdb/gitdb/ext/smmap')
from git.cmd import Git
return Git.polish_url(osp.join(self.rorepo.working_tree_dir, 'git/ext/gitdb/gitdb/ext/smmap'))

@classmethod
def setUpClass(cls):
@@ -378,7 +359,7 @@ def _make_file(self, rela_path, data, repo=None):
with the given data. Returns absolute path to created file.
"""
repo = repo or self.rorepo
abs_path = os.path.join(repo.working_tree_dir, rela_path)
abs_path = osp.join(repo.working_tree_dir, rela_path)
with open(abs_path, "w") as fp:
fp.write(data)
return abs_path
36 changes: 13 additions & 23 deletions git/test/test_base.py
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ def tearDown(self):
def test_base_object(self):
# test interface of base object classes
types = (Blob, Tree, Commit, TagObject)
assert len(types) == len(self.type_tuples)
self.assertEqual(len(types), len(self.type_tuples))

s = set()
num_objs = 0
@@ -55,12 +55,12 @@ def test_base_object(self):
item = obj_type(self.rorepo, binsha, 0, path)
# END handle index objects
num_objs += 1
assert item.hexsha == hexsha
assert item.type == typename
self.assertEqual(item.hexsha, hexsha)
self.assertEqual(item.type, typename)
assert item.size
assert item == item
assert not item != item
assert str(item) == item.hexsha
self.assertEqual(item, item)
self.assertNotEqual(not item, item)
self.assertEqual(str(item), item.hexsha)
assert repr(item)
s.add(item)

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

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

# each has a unique sha
assert len(s) == num_objs
assert len(s | s) == num_objs
assert num_index_objs == 2
self.assertEqual(len(s), num_objs)
self.assertEqual(len(s | s), num_objs)
self.assertEqual(num_index_objs, 2)

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

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

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

# @skipIf(HIDE_WINDOWS_KNOWN_ERRORS, """
# FIXME: helper.wrapper fails with:
# PermissionError: [WinError 5] Access is denied:
# 'C:\\Users\\appveyor\\AppData\\Local\\Temp\\1\\test_work_tree_unsupportedryfa60di\\
# master_repo\\.git\\objects\\pack\\pack-bc9e0787aef9f69e1591ef38ea0a6f566ec66fe3.idx'
# AND
# FIXME: git-daemon failing with:
# git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
# cmdline: git ls-remote daemon_origin
# stderr: 'fatal: bad config line 15 in file .git/config'
# """)
#@skipIf(HIDE_WINDOWS_FREEZE_ERRORS, "FIXME: Freezes! sometimes...")
@with_rw_and_rw_remote_repo('0.1.6')
def test_with_rw_remote_and_rw_repo(self, rw_repo, rw_remote_repo):
assert not rw_repo.config_reader("repository").getboolean("core", "bare")
2 changes: 1 addition & 1 deletion git/test/test_docs.py
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ def tearDown(self):
import gc
gc.collect()

# @skipIf(HIDE_WINDOWS_KNOWN_ERRORS,
# @skipIf(HIDE_WINDOWS_KNOWN_ERRORS, ## ACTUALLY skipped by `git.submodule.base#L869`.
# "FIXME: helper.wrapper fails with: PermissionError: [WinError 5] Access is denied: "
# "'C:\\Users\\appveyor\\AppData\\Local\\Temp\\1\\test_work_tree_unsupportedryfa60di\\master_repo\\.git\\objects\\pack\\pack-bc9e0787aef9f69e1591ef38ea0a6f566ec66fe3.idx") # noqa E501
@with_rw_directory
21 changes: 9 additions & 12 deletions git/test/test_git.py
Original file line number Diff line number Diff line change
@@ -207,18 +207,15 @@ def test_environment(self, rw_dir):
rw_repo = Repo.init(os.path.join(rw_dir, 'repo'))
remote = rw_repo.create_remote('ssh-origin', "ssh://git@server/foo")

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

def test_handle_process_output(self):
from git.cmd import handle_process_output
226 changes: 122 additions & 104 deletions git/test/test_remote.py

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions git/test/test_repo.py
Original file line number Diff line number Diff line change
@@ -472,12 +472,16 @@ def test_creation_deletion(self):
head = self.rorepo.create_head("new_head", "HEAD~1")
self.rorepo.delete_head(head)

tag = self.rorepo.create_tag("new_tag", "HEAD~2")
self.rorepo.delete_tag(tag)
try:
tag = self.rorepo.create_tag("new_tag", "HEAD~2")
finally:
self.rorepo.delete_tag(tag)
with self.rorepo.config_writer():
pass
remote = self.rorepo.create_remote("new_remote", "git@server:repo.git")
self.rorepo.delete_remote(remote)
try:
remote = self.rorepo.create_remote("new_remote", "git@server:repo.git")
finally:
self.rorepo.delete_remote(remote)

def test_comparison_and_hash(self):
# this is only a preliminary test, more testing done in test_index
76 changes: 39 additions & 37 deletions git/test/test_submodule.py

Large diffs are not rendered by default.

26 changes: 15 additions & 11 deletions git/util.py
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
from functools import wraps

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

#{ Utility Methods

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

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

def __init__(self):
self._seen_ops = list()
self._cur_line = None
self._error_lines = []

def error_lines(self):
"""Returns all lines that started with error: or fatal:"""
return self._error_lines
self.error_lines = []
self.other_lines = []

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

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

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

def new_message_handler(self):

0 comments on commit e12ef59

Please sign in to comment.