Skip to content

Commit a05e49d

Browse files
committed
test_repo works
1 parent 60e5413 commit a05e49d

File tree

13 files changed

+106
-45
lines changed

13 files changed

+106
-45
lines changed

‎doc/source/changes.rst

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Changelog
66
========================
77
* Internally, hexadecimal SHA1 are treated as ascii encoded strings. Binary SHA1 are treated as bytes.
88
* Id attribute of Commit objects is now `hexsha`, instead of `binsha`. The latter makes no sense in python 3 and I see no application of it anyway besides its artificial usage in test cases.
9+
* **IMPORTANT**: If you were using the config_writer(), you implicitly relied on __del__ to work as expected to flush changes. To be sure changes are flushed under PY3, you will have to call the new `release()` method to trigger a flush. For some reason, __del__ is not called necessarily anymore when a symbol goes out of scope.
910

1011
0.3.3
1112
=====

‎git/cmd.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
from git.compat import (
2323
text_type,
2424
string_types,
25-
defenc
25+
defenc,
26+
PY3
2627
)
2728

2829
execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output',
@@ -372,8 +373,8 @@ def execute(self, command,
372373

373374
# Wait for the process to return
374375
status = 0
375-
stdout_value = ''
376-
stderr_value = ''
376+
stdout_value = b''
377+
stderr_value = b''
377378
try:
378379
if output_stream is None:
379380
stdout_value, stderr_value = proc.communicate()
@@ -388,7 +389,7 @@ def execute(self, command,
388389
stdout_value = output_stream
389390
stderr_value = proc.stderr.read()
390391
# strip trailing "\n"
391-
if stderr_value.endswith("\n"):
392+
if stderr_value.endswith(b"\n"):
392393
stderr_value = stderr_value[:-1]
393394
status = proc.wait()
394395
# END stdout handling
@@ -444,15 +445,17 @@ def transform_kwargs(self, split_single_char_options=False, **kwargs):
444445
@classmethod
445446
def __unpack_args(cls, arg_list):
446447
if not isinstance(arg_list, (list, tuple)):
447-
if isinstance(arg_list, text_type):
448+
# This is just required for unicode conversion, as subprocess can't handle it
449+
# However, in any other case, passing strings (usually utf-8 encoded) is totally fine
450+
if not PY3 and isinstance(arg_list, unicode):
448451
return [arg_list.encode(defenc)]
449452
return [str(arg_list)]
450453

451454
outlist = list()
452455
for arg in arg_list:
453456
if isinstance(arg_list, (list, tuple)):
454457
outlist.extend(cls.__unpack_args(arg))
455-
elif isinstance(arg_list, text_type):
458+
elif not PY3 and isinstance(arg_list, unicode):
456459
outlist.append(arg_list.encode(defenc))
457460
# END recursion
458461
else:
@@ -509,8 +512,8 @@ def _call_process(self, method, *args, **kwargs):
509512

510513
# Prepare the argument list
511514
opt_args = self.transform_kwargs(**kwargs)
512-
513515
ext_args = self.__unpack_args([a for a in args if a is not None])
516+
514517
args = opt_args + ext_args
515518

516519
def make_call():

‎git/config.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,7 @@ def __del__(self):
103103
# Yes, for some reason, we have to call it explicitly for it to work in PY3 !
104104
# Apparently __del__ doesn't get call anymore if refcount becomes 0
105105
# Ridiculous ... .
106-
self._config.__del__()
107-
# del self._config
106+
self._config.release()
108107

109108
def __getattr__(self, attr):
110109
if attr in self._valid_attrs_:
@@ -121,6 +120,10 @@ def config(self):
121120
"""return: Configparser instance we constrain"""
122121
return self._config
123122

123+
def release(self):
124+
"""Equivalent to GitConfigParser.release(), which is called on our underlying parser instance"""
125+
return self._config.release()
126+
124127

125128
class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, object)):
126129

@@ -198,6 +201,13 @@ def __init__(self, file_or_files, read_only=True):
198201

199202
def __del__(self):
200203
"""Write pending changes if required and release locks"""
204+
# NOTE: only consistent in PY2
205+
self.release()
206+
207+
def release(self):
208+
"""Flush changes and release the configuration write lock. This instance must not be used anymore afterwards.
209+
In Python 3, it's required to explicitly release locks and flush changes, as __del__ is not called
210+
deterministically anymore."""
201211
# checking for the lock here makes sure we do not raise during write()
202212
# in case an invalid parser was created who could not get a lock
203213
if self.read_only or (self._lock and not self._lock._has_lock()):

‎git/exc.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
from gitdb.exc import * # NOQA
99

10+
from git.compat import defenc
11+
1012

1113
class InvalidGitRepositoryError(Exception):
1214

@@ -32,9 +34,9 @@ def __str__(self):
3234
ret = "'%s' returned with exit code %i" % \
3335
(' '.join(str(i) for i in self.command), self.status)
3436
if self.stderr:
35-
ret += "\nstderr: '%s'" % self.stderr
37+
ret += "\nstderr: '%s'" % self.stderr.decode(defenc)
3638
if self.stdout:
37-
ret += "\nstdout: '%s'" % self.stdout
39+
ret += "\nstdout: '%s'" % self.stdout.decode(defenc)
3840
return ret
3941

4042

‎git/objects/submodule/base.py

+23-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
rmtree
1818
)
1919

20-
from git.config import SectionConstraint
20+
from git.config import (
21+
SectionConstraint,
22+
cp
23+
)
2124
from git.exc import (
2225
InvalidGitRepositoryError,
2326
NoSuchPathError
@@ -302,6 +305,7 @@ def add(cls, repo, name, path, url=None, branch=None, no_checkout=False):
302305
writer.set_value(cls.k_head_option, br.path)
303306
sm._branch_path = br.path
304307
# END handle path
308+
writer.release()
305309
del(writer)
306310

307311
# we deliberatly assume that our head matches our index !
@@ -419,7 +423,9 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
419423
# the default implementation will be offended and not update the repository
420424
# Maybe this is a good way to assure it doesn't get into our way, but
421425
# we want to stay backwards compatible too ... . Its so redundant !
422-
self.repo.config_writer().set_value(sm_section(self.name), 'url', self.url)
426+
writer = self.repo.config_writer()
427+
writer.set_value(sm_section(self.name), 'url', self.url)
428+
writer.release()
423429
# END handle dry_run
424430
# END handle initalization
425431

@@ -576,6 +582,7 @@ def move(self, module_path, configuration=True, module=True):
576582
writer = self.config_writer(index=index) # auto-write
577583
writer.set_value('path', module_path)
578584
self.path = module_path
585+
writer.release()
579586
del(writer)
580587
# END handle configuration flag
581588
except Exception:
@@ -700,8 +707,12 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
700707

701708
# now git config - need the config intact, otherwise we can't query
702709
# inforamtion anymore
703-
self.repo.config_writer().remove_section(sm_section(self.name))
704-
self.config_writer().remove_section()
710+
writer = self.repo.config_writer()
711+
writer.remove_section(sm_section(self.name))
712+
writer.release()
713+
writer = self.config_writer()
714+
writer.remove_section()
715+
writer.release()
705716
# END delete configuration
706717

707718
# void our data not to delay invalid access
@@ -800,14 +811,18 @@ def exists(self):
800811
"""
801812
:return: True if the submodule exists, False otherwise. Please note that
802813
a submodule may exist (in the .gitmodules file) even though its module
803-
doesn't exist"""
814+
doesn't exist on disk"""
804815
# keep attributes for later, and restore them if we have no valid data
805816
# this way we do not actually alter the state of the object
806817
loc = locals()
807818
for attr in self._cache_attrs:
808-
if hasattr(self, attr):
809-
loc[attr] = getattr(self, attr)
810-
# END if we have the attribute cache
819+
try:
820+
if hasattr(self, attr):
821+
loc[attr] = getattr(self, attr)
822+
# END if we have the attribute cache
823+
except cp.NoSectionError:
824+
# on PY3, this can happen apparently ... don't know why this doesn't happen on PY2
825+
pass
811826
# END for each attr
812827
self._clear_cache()
813828

‎git/refs/head.py

+1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ def set_tracking_branch(self, remote_reference):
148148
writer.set_value(self.k_config_remote, remote_reference.remote_name)
149149
writer.set_value(self.k_config_remote_ref, Head.to_full_path(remote_reference.remote_head))
150150
# END handle ref value
151+
writer.release()
151152

152153
return self
153154

‎git/repo/base.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,7 @@ def untracked_files(self):
551551
prefix = "?? "
552552
untracked_files = list()
553553
for line in proc.stdout:
554+
line = line.decode(defenc)
554555
if not line.startswith(prefix):
555556
continue
556557
filename = line[len(prefix):].rstrip('\n')
@@ -735,7 +736,7 @@ def _clone(cls, git, url, path, odb_default_type, progress, **kwargs):
735736
writer = repo.remotes[0].config_writer
736737
writer.set_value('url', repo.remotes[0].url.replace("\\\\", "\\").replace("\\", "/"))
737738
# PY3: be sure cleanup is performed and lock is released
738-
del writer
739+
writer.release()
739740
# END handle remote repo
740741
return repo
741742

@@ -767,7 +768,7 @@ def clone_from(cls, url, to_path, progress=None, **kwargs):
767768

768769
def archive(self, ostream, treeish=None, prefix=None, **kwargs):
769770
"""Archive the tree at the given revision.
770-
:parm ostream: file compatible stream object to which the archive will be written
771+
:parm ostream: file compatible stream object to which the archive will be written as bytes
771772
:parm treeish: is the treeish name/id, defaults to active branch
772773
:parm prefix: is the optional prefix to prepend to each filename in the archive
773774
:parm kwargs:

‎git/repo/fun.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def to_commit(obj):
150150
def rev_parse(repo, rev):
151151
"""
152152
:return: Object at the given revision, either Commit, Tag, Tree or Blob
153-
:param rev: git-rev-parse compatible revision specification, please see
153+
:param rev: git-rev-parse compatible revision specification as string, please see
154154
http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html
155155
for details
156156
:note: Currently there is no access to the rev-log, rev-specs may only contain

‎git/test/lib/helper.py

+1
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ def remote_repo_creator(self):
179179
pass
180180
crw.set(section, "receivepack", True)
181181
# release lock
182+
crw.release()
182183
del(crw)
183184

184185
# initialize the remote - first do it as local remote and pull, then

‎git/test/test_index.py

+1
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ def test_index_mutation(self, rw_repo):
382382
writer = rw_repo.config_writer()
383383
writer.set_value("user", "name", uname)
384384
writer.set_value("user", "email", umail)
385+
writer.release()
385386

386387
# remove all of the files, provide a wild mix of paths, BaseIndexEntries,
387388
# IndexEntries

‎git/test/test_refs.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,11 @@ def test_heads(self, rwrepo):
105105
tv = "testopt"
106106
writer.set_value(tv, 1)
107107
assert writer.get_value(tv) == 1
108-
del(writer)
108+
writer.release()
109109
assert head.config_reader().get_value(tv) == 1
110-
head.config_writer().remove_option(tv)
110+
writer = head.config_writer()
111+
writer.remove_option(tv)
112+
writer.release()
111113

112114
# after the clone, we might still have a tracking branch setup
113115
head.set_tracking_branch(None)

‎git/test/test_repo.py

+18-10
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030
from git.util import join_path_native
3131
from git.exc import BadObject
3232
from gitdb.util import bin_to_hex
33-
from git.compat import string_types
33+
from git.compat import (
34+
string_types,
35+
defenc
36+
)
3437

3538
import os
3639
import sys
@@ -259,13 +262,16 @@ def test_tag(self):
259262
assert self.rorepo.tag('refs/tags/0.1.5').commit
260263

261264
def test_archive(self):
262-
tmpfile = os.tmpfile()
263-
self.rorepo.archive(tmpfile, '0.1.5')
264-
assert tmpfile.tell()
265+
tmpfile = tempfile.mktemp(suffix='archive-test')
266+
stream = open(tmpfile, 'wb')
267+
self.rorepo.archive(stream, '0.1.5')
268+
assert stream.tell()
269+
stream.close()
270+
os.remove(tmpfile)
265271

266272
@patch.object(Git, '_call_process')
267273
def test_should_display_blame_information(self, git):
268-
git.return_value = fixture('blame')
274+
git.return_value = fixture('blame').decode(defenc)
269275
b = self.rorepo.blame('master', 'lib/git.py')
270276
assert_equal(13, len(b))
271277
assert_equal(2, len(b[0]))
@@ -336,6 +342,7 @@ def test_config_writer(self):
336342
try:
337343
writer = self.rorepo.config_writer(config_level)
338344
assert not writer.read_only
345+
writer.release()
339346
except IOError:
340347
# its okay not to get a writer for some configuration files if we
341348
# have no permissions
@@ -350,7 +357,8 @@ def test_creation_deletion(self):
350357

351358
tag = self.rorepo.create_tag("new_tag", "HEAD~2")
352359
self.rorepo.delete_tag(tag)
353-
self.rorepo.config_writer()
360+
writer = self.rorepo.config_writer()
361+
writer.release()
354362
remote = self.rorepo.create_remote("new_remote", "git@server:repo.git")
355363
self.rorepo.delete_remote(remote)
356364

@@ -365,7 +373,7 @@ def test_git_cmd(self):
365373
l1 = b"0123456789\n"
366374
l2 = b"abcdefghijklmnopqrstxy\n"
367375
l3 = b"z\n"
368-
d = b"%s%s%s\n" % (l1, l2, l3)
376+
d = l1 + l2 + l3 + b"\n"
369377

370378
l1p = l1[:5]
371379

@@ -382,7 +390,7 @@ def mktiny():
382390
# readlines no limit
383391
s = mkfull()
384392
lines = s.readlines()
385-
assert len(lines) == 3 and lines[-1].endswith('\n')
393+
assert len(lines) == 3 and lines[-1].endswith(b'\n')
386394
assert s._stream.tell() == len(d) # must have scrubbed to the end
387395

388396
# realines line limit
@@ -566,7 +574,7 @@ def test_rev_parse(self):
566574
# try partial parsing
567575
max_items = 40
568576
for i, binsha in enumerate(self.rorepo.odb.sha_iter()):
569-
assert rev_parse(bin_to_hex(binsha)[:8 - (i % 2)]).binsha == binsha
577+
assert rev_parse(bin_to_hex(binsha)[:8 - (i % 2)].decode('ascii')).binsha == binsha
570578
if i > max_items:
571579
# this is rather slow currently, as rev_parse returns an object
572580
# which requires accessing packs, it has some additional overhead
@@ -645,6 +653,6 @@ def test_git_file(self, rwrepo):
645653
assert os.path.abspath(git_file_repo.git_dir) == real_path_abs
646654

647655
# Test using an absolute gitdir path in the .git file.
648-
open(git_file_path, 'wb').write('gitdir: %s\n' % real_path_abs)
656+
open(git_file_path, 'wb').write(('gitdir: %s\n' % real_path_abs).encode('ascii'))
649657
git_file_repo = Repo(rwrepo.working_tree_dir)
650658
assert os.path.abspath(git_file_repo.git_dir) == real_path_abs

0 commit comments

Comments
 (0)