Skip to content

Commit 04357d0

Browse files
committed
Intermediate commit: test_config and test_actor works
Kind of tackling the tasks step by step, picking low-hanging fruit first, or the ones that everyone depends on
1 parent bc8c912 commit 04357d0

File tree

8 files changed

+114
-63
lines changed

8 files changed

+114
-63
lines changed

doc/source/changes.rst

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

5+
0.3.4 - python 3 support
6+
========================
7+
* Internally, hexadecimal SHA1 are treated as ascii encoded strings. Binary SHA1 are treated as bytes.
8+
59
0.3.3
610
=====
711
* When fetching, pulling or pushing, and an error occours, it will not be reported on stdout anymore. However, if there is a fatal error, it will still result in a GitCommandError to be thrown. This goes hand in hand with improved fetch result parsing.

git/cmd.py

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919
stream_copy
2020
)
2121
from .exc import GitCommandError
22-
from git.compat import text_type
22+
from git.compat import (
23+
text_type,
24+
string_types,
25+
defenc
26+
)
2327

2428
execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output',
2529
'with_exceptions', 'as_process',
@@ -373,9 +377,9 @@ def execute(self, command,
373377
if output_stream is None:
374378
stdout_value, stderr_value = proc.communicate()
375379
# strip trailing "\n"
376-
if stdout_value.endswith("\n"):
380+
if stdout_value.endswith(b"\n"):
377381
stdout_value = stdout_value[:-1]
378-
if stderr_value.endswith("\n"):
382+
if stderr_value.endswith(b"\n"):
379383
stderr_value = stderr_value[:-1]
380384
status = proc.returncode
381385
else:
@@ -394,9 +398,9 @@ def execute(self, command,
394398
if self.GIT_PYTHON_TRACE == 'full':
395399
cmdstr = " ".join(command)
396400
if stderr_value:
397-
log.info("%s -> %d; stdout: '%s'; stderr: '%s'", cmdstr, status, stdout_value, stderr_value)
401+
log.info("%s -> %d; stdout: '%s'; stderr: '%s'", cmdstr, status, stdout_value.decode(defenc), stderr_value.decode(defenc))
398402
elif stdout_value:
399-
log.info("%s -> %d; stdout: '%s'", cmdstr, status, stdout_value)
403+
log.info("%s -> %d; stdout: '%s'", cmdstr, status, stdout_value.decode(defenc))
400404
else:
401405
log.info("%s -> %d", cmdstr, status)
402406
# END handle debug printing
@@ -436,15 +440,15 @@ def transform_kwargs(self, split_single_char_options=False, **kwargs):
436440
def __unpack_args(cls, arg_list):
437441
if not isinstance(arg_list, (list, tuple)):
438442
if isinstance(arg_list, text_type):
439-
return [arg_list.encode('utf-8')]
443+
return [arg_list.encode(defenc)]
440444
return [str(arg_list)]
441445

442446
outlist = list()
443447
for arg in arg_list:
444448
if isinstance(arg_list, (list, tuple)):
445449
outlist.extend(cls.__unpack_args(arg))
446450
elif isinstance(arg_list, text_type):
447-
outlist.append(arg_list.encode('utf-8'))
451+
outlist.append(arg_list.encode(defenc))
448452
# END recursion
449453
else:
450454
outlist.append(str(arg))
@@ -569,14 +573,20 @@ def _parse_object_header(self, header_line):
569573
raise ValueError("Failed to parse header: %r" % header_line)
570574
return (tokens[0], tokens[1], int(tokens[2]))
571575

572-
def __prepare_ref(self, ref):
573-
# required for command to separate refs on stdin
574-
refstr = str(ref) # could be ref-object
575-
if refstr.endswith("\n"):
576-
return refstr
577-
return refstr + "\n"
576+
def _prepare_ref(self, ref):
577+
# required for command to separate refs on stdin, as bytes
578+
refstr = ref
579+
if isinstance(ref, bytes):
580+
# Assume 40 bytes hexsha - bin-to-ascii for some reason returns bytes, not text
581+
refstr = ref.decode('ascii')
582+
elif not isinstance(ref, string_types):
583+
refstr = str(ref) # could be ref-object
584+
585+
if not refstr.endswith("\n"):
586+
refstr += "\n"
587+
return refstr.encode(defenc)
578588

579-
def __get_persistent_cmd(self, attr_name, cmd_name, *args, **kwargs):
589+
def _get_persistent_cmd(self, attr_name, cmd_name, *args, **kwargs):
580590
cur_val = getattr(self, attr_name)
581591
if cur_val is not None:
582592
return cur_val
@@ -589,7 +599,7 @@ def __get_persistent_cmd(self, attr_name, cmd_name, *args, **kwargs):
589599
return cmd
590600

591601
def __get_object_header(self, cmd, ref):
592-
cmd.stdin.write(self.__prepare_ref(ref))
602+
cmd.stdin.write(self._prepare_ref(ref))
593603
cmd.stdin.flush()
594604
return self._parse_object_header(cmd.stdout.readline())
595605

@@ -601,7 +611,7 @@ def get_object_header(self, ref):
601611
once and reuses the command in subsequent calls.
602612
603613
:return: (hexsha, type_string, size_as_int)"""
604-
cmd = self.__get_persistent_cmd("cat_file_header", "cat_file", batch_check=True)
614+
cmd = self._get_persistent_cmd("cat_file_header", "cat_file", batch_check=True)
605615
return self.__get_object_header(cmd, ref)
606616

607617
def get_object_data(self, ref):
@@ -618,7 +628,7 @@ def stream_object_data(self, ref):
618628
:return: (hexsha, type_string, size_as_int, stream)
619629
:note: This method is not threadsafe, you need one independent Command instance
620630
per thread to be safe !"""
621-
cmd = self.__get_persistent_cmd("cat_file_all", "cat_file", batch=True)
631+
cmd = self._get_persistent_cmd("cat_file_all", "cat_file", batch=True)
622632
hexsha, typename, size = self.__get_object_header(cmd, ref)
623633
return (hexsha, typename, size, self.CatFileContentStream(size, cmd.stdout))
624634

git/compat.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"""utilities to help provide compatibility with python 3"""
88
# flake8: noqa
99

10+
import sys
11+
1012
from gitdb.utils.compat import (
1113
PY3,
1214
xrange,
@@ -17,11 +19,39 @@
1719
from gitdb.utils.encoding import (
1820
string_types,
1921
text_type,
20-
force_bytes
22+
force_bytes,
23+
force_text
2124
)
2225

26+
defenc = sys.getdefaultencoding()
2327
if PY3:
2428
import io
2529
FileType = io.IOBase
2630
else:
2731
FileType = file
32+
# usually, this is just ascii, which might not enough for our encoding needs
33+
# Unless it's set specifically, we override it to be utf-8
34+
if defenc == 'ascii':
35+
defenc = 'utf-8'
36+
37+
38+
def with_metaclass(meta, *bases):
39+
"""copied from https://github.com/Byron/bcore/blob/master/src/python/butility/future.py#L15"""
40+
class metaclass(meta):
41+
__call__ = type.__call__
42+
__init__ = type.__init__
43+
44+
def __new__(cls, name, nbases, d):
45+
if nbases is None:
46+
return type.__new__(cls, name, (), d)
47+
# There may be clients who rely on this attribute to be set to a reasonable value, which is why
48+
# we set the __metaclass__ attribute explicitly
49+
if not PY3 and '___metaclass__' not in d:
50+
d['__metaclass__'] = meta
51+
# end
52+
return meta(name, bases, d)
53+
# end
54+
# end metaclass
55+
return metaclass(meta.__name__ + 'Helper', None, {})
56+
# end handle py2
57+

git/config.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@
1414
import configparser as cp
1515
import inspect
1616
import logging
17+
import abc
1718

1819
from git.odict import OrderedDict
1920
from git.util import LockFile
2021
from git.compat import (
2122
string_types,
22-
FileType
23+
FileType,
24+
defenc,
25+
with_metaclass
2326
)
2427

2528
__all__ = ('GitConfigParser', 'SectionConstraint')
@@ -28,7 +31,7 @@
2831
log = logging.getLogger('git.config')
2932

3033

31-
class MetaParserBuilder(type):
34+
class MetaParserBuilder(abc.ABCMeta):
3235

3336
"""Utlity class wrapping base-class methods into decorators that assure read-only properties"""
3437
def __new__(metacls, name, bases, clsdict):
@@ -39,7 +42,7 @@ def __new__(metacls, name, bases, clsdict):
3942
if kmm in clsdict:
4043
mutating_methods = clsdict[kmm]
4144
for base in bases:
42-
methods = (t for t in inspect.getmembers(base, inspect.ismethod) if not t[0].startswith("_"))
45+
methods = (t for t in inspect.getmembers(base, inspect.isroutine) if not t[0].startswith("_"))
4346
for name, method in methods:
4447
if name in clsdict:
4548
continue
@@ -112,7 +115,7 @@ def config(self):
112115
return self._config
113116

114117

115-
class GitConfigParser(cp.RawConfigParser, object):
118+
class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, object)):
116119

117120
"""Implements specifics required to read git style configuration files.
118121
@@ -128,7 +131,6 @@ class GitConfigParser(cp.RawConfigParser, object):
128131
:note:
129132
The config is case-sensitive even when queried, hence section and option names
130133
must match perfectly."""
131-
__metaclass__ = MetaParserBuilder
132134

133135
#{ Configuration
134136
# The lock type determines the type of lock to use in new configuration readers.
@@ -150,7 +152,6 @@ class GitConfigParser(cp.RawConfigParser, object):
150152

151153
# list of RawConfigParser methods able to change the instance
152154
_mutating_methods_ = ("add_section", "remove_section", "remove_option", "set")
153-
__slots__ = ("_sections", "_defaults", "_file_or_files", "_read_only", "_is_initialized", '_lock')
154155

155156
def __init__(self, file_or_files, read_only=True):
156157
"""Initialize a configuration reader to read the given file_or_files and to
@@ -162,12 +163,12 @@ def __init__(self, file_or_files, read_only=True):
162163
:param read_only:
163164
If True, the ConfigParser may only read the data , but not change it.
164165
If False, only a single file path or file object may be given."""
165-
super(GitConfigParser, self).__init__()
166-
# initialize base with ordered dictionaries to be sure we write the same
167-
# file back
168-
self._sections = OrderedDict()
169-
self._defaults = OrderedDict()
166+
cp.RawConfigParser.__init__(self, dict_type=OrderedDict)
170167

168+
# Used in python 3, needs to stay in sync with sections for underlying implementation to work
169+
if not hasattr(self, '_proxies'):
170+
self._proxies = self._dict()
171+
171172
self._file_or_files = file_or_files
172173
self._read_only = read_only
173174
self._is_initialized = False
@@ -222,7 +223,8 @@ def _read(self, fp, fpname):
222223
lineno = 0
223224
e = None # None, or an exception
224225
while True:
225-
line = fp.readline()
226+
# we assume to read binary !
227+
line = fp.readline().decode(defenc)
226228
if not line:
227229
break
228230
lineno = lineno + 1
@@ -242,9 +244,9 @@ def _read(self, fp, fpname):
242244
elif sectname == cp.DEFAULTSECT:
243245
cursect = self._defaults
244246
else:
245-
# THE ONLY LINE WE CHANGED !
246-
cursect = OrderedDict((('__name__', sectname),))
247+
cursect = self._dict((('__name__', sectname),))
247248
self._sections[sectname] = cursect
249+
self._proxies[sectname] = None
248250
# So sections can't start with a continuation line
249251
optname = None
250252
# no section header in the file?
@@ -295,7 +297,7 @@ def read(self):
295297
# assume a path if it is not a file-object
296298
if not hasattr(file_object, "seek"):
297299
try:
298-
fp = open(file_object)
300+
fp = open(file_object, 'rb')
299301
close_fp = True
300302
except IOError:
301303
continue
@@ -314,16 +316,17 @@ def _write(self, fp):
314316
"""Write an .ini-format representation of the configuration state in
315317
git compatible format"""
316318
def write_section(name, section_dict):
317-
fp.write("[%s]\n" % name)
319+
fp.write(("[%s]\n" % name).encode(defenc))
318320
for (key, value) in section_dict.items():
319321
if key != "__name__":
320-
fp.write("\t%s = %s\n" % (key, str(value).replace('\n', '\n\t')))
322+
fp.write(("\t%s = %s\n" % (key, str(value).replace('\n', '\n\t'))).encode(defenc))
321323
# END if key is not __name__
322324
# END section writing
323325

324326
if self._defaults:
325327
write_section(cp.DEFAULTSECT, self._defaults)
326-
map(lambda t: write_section(t[0], t[1]), self._sections.items())
328+
for name, value in self._sections.items():
329+
write_section(name, value)
327330

328331
@needs_values
329332
def write(self):
@@ -371,8 +374,7 @@ def _assure_writable(self, method_name):
371374
@set_dirty_and_flush_changes
372375
def add_section(self, section):
373376
"""Assures added options will stay in order"""
374-
super(GitConfigParser, self).add_section(section)
375-
self._sections[section] = OrderedDict()
377+
return super(GitConfigParser, self).add_section(section)
376378

377379
@property
378380
def read_only(self):

git/objects/util.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,17 @@ def get_object_type_by_name(object_type_name):
4646
:param object_type_name: Member of TYPES
4747
4848
:raise ValueError: In case object_type_name is unknown"""
49-
if object_type_name == "commit":
50-
import commit
49+
if object_type_name == b"commit":
50+
from . import commit
5151
return commit.Commit
52-
elif object_type_name == "tag":
53-
import tag
52+
elif object_type_name == b"tag":
53+
from . import tag
5454
return tag.TagObject
55-
elif object_type_name == "blob":
56-
import blob
55+
elif object_type_name == b"blob":
56+
from . import blob
5757
return blob.Blob
58-
elif object_type_name == "tree":
59-
import tree
58+
elif object_type_name == b"tree":
59+
from . import tree
6060
return tree.Tree
6161
else:
6262
raise ValueError("Cannot handle unknown object type: %s" % object_type_name)

git/refs/symbolic.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
hex_to_bin,
2020
LockedFD
2121
)
22-
from git.compat import string_types
22+
from git.compat import (
23+
string_types,
24+
)
2325

2426
from .log import RefLog
2527

@@ -79,10 +81,10 @@ def _get_packed_refs_path(cls, repo):
7981

8082
@classmethod
8183
def _iter_packed_refs(cls, repo):
82-
"""Returns an iterator yielding pairs of sha1/path pairs for the corresponding refs.
84+
"""Returns an iterator yielding pairs of sha1/path pairs (as bytes) for the corresponding refs.
8385
:note: The packed refs file will be kept open as long as we iterate"""
8486
try:
85-
fp = open(cls._get_packed_refs_path(repo), 'rb')
87+
fp = open(cls._get_packed_refs_path(repo), 'rt')
8688
for line in fp:
8789
line = line.strip()
8890
if not line:
@@ -123,12 +125,12 @@ def dereference_recursive(cls, repo, ref_path):
123125

124126
@classmethod
125127
def _get_ref_info(cls, repo, ref_path):
126-
"""Return: (sha, target_ref_path) if available, the sha the file at
128+
"""Return: (str(sha), str(target_ref_path)) if available, the sha the file at
127129
rela_path points to, or None. target_ref_path is the reference we
128130
point to, or None"""
129131
tokens = None
130132
try:
131-
fp = open(join(repo.git_dir, ref_path), 'r')
133+
fp = open(join(repo.git_dir, ref_path), 'rt')
132134
value = fp.read().rstrip()
133135
fp.close()
134136
# Don't only split on spaces, but on whitespace, which allows to parse lines like
@@ -141,7 +143,8 @@ def _get_ref_info(cls, repo, ref_path):
141143
for sha, path in cls._iter_packed_refs(repo):
142144
if path != ref_path:
143145
continue
144-
tokens = (sha, path)
146+
# sha will be used as
147+
tokens = sha, path
145148
break
146149
# END for each packed ref
147150
# END handle packed refs

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy