Skip to content

Commit 3565888

Browse files
committed
Merge remote-tracking branch 'upstream/master' into pr-cmd-raise-with-stderr-on-error
2 parents 08a0fad + e0b21f4 commit 3565888

13 files changed

+71
-19
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ It provides abstractions of git objects for easy access of repository data, and
66

77
The object database implementation is optimized for handling large quantities of objects and large datasets, which is achieved by using low-level structures and data streaming.
88

9+
910
### REQUIREMENTS
1011

1112
GitPython needs the `git` executable to be installed on the system and available in your `PATH` for most operations. If it is not in your `PATH`, you can help GitPython find it by setting the `GIT_PYTHON_GIT_EXECUTABLE=<path/to/git>` environment variable.
1213

1314
* Git (1.7.x or newer)
15+
* Python 2.7 to 3.5, while python 2.6 is supported on a *best-effort basis*.
1416

1517
The list of dependencies are listed in `./requirements.txt` and `./test-requirements.txt`. The installer takes care of installing them for you.
1618

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.0.3
1+
2.0.6dev0

doc/source/changes.rst

+16
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,25 @@
22
Changelog
33
=========
44

5+
2.0.6 - Fixes
6+
=============
7+
8+
* Fix: TypeError about passing keyword argument to string decode() on
9+
Python 2.6.
10+
11+
2.0.5 - Fixes
12+
=============
13+
14+
* Fix: parser of fetch info lines choked on some legitimate lines
15+
516
2.0.4 - Fixes
617
=============
718

19+
* Fix: parser of commit object data is now robust against cases where
20+
commit object contains invalid bytes. The invalid characters are now
21+
replaced rather than choked on.
22+
* Fix: non-ASCII paths are now properly decoded and returned in
23+
``.diff()`` output
824
* Fix: `RemoteProgress` will now strip the ', ' prefix or suffix from messages.
925
* API: Remote.[fetch|push|pull](...) methods now allow the ``progress`` argument to
1026
be a callable. This saves you from creating a custom type with usually just one

git/cmd.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ def __del__(self):
287287
return
288288

289289
# can be that nothing really exists anymore ...
290-
if os is None:
290+
if os is None or os.kill is None:
291291
return
292292

293293
# try to kill it

git/diff.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,23 @@
1515
PY3
1616
)
1717

18-
1918
__all__ = ('Diffable', 'DiffIndex', 'Diff', 'NULL_TREE')
2019

2120
# Special object to compare against the empty tree in diffs
2221
NULL_TREE = object()
2322

23+
_octal_byte_re = re.compile(b'\\\\([0-9]{3})')
24+
25+
26+
def _octal_repl(matchobj):
27+
value = matchobj.group(1)
28+
value = int(value, 8)
29+
if PY3:
30+
value = bytes(bytearray((value,)))
31+
else:
32+
value = chr(value)
33+
return value
34+
2435

2536
def decode_path(path, has_ab_prefix=True):
2637
if path == b'/dev/null':
@@ -32,6 +43,8 @@ def decode_path(path, has_ab_prefix=True):
3243
.replace(b'\\"', b'"')
3344
.replace(b'\\\\', b'\\'))
3445

46+
path = _octal_byte_re.sub(_octal_repl, path)
47+
3548
if has_ab_prefix:
3649
assert path.startswith(b'a/') or path.startswith(b'b/')
3750
path = path[2:]
@@ -337,7 +350,7 @@ def renamed(self):
337350
:note: This property is deprecated, please use ``renamed_file`` instead.
338351
"""
339352
return self.renamed_file
340-
353+
341354
@property
342355
def renamed_file(self):
343356
""":returns: True if the blob of our diff has been renamed

git/objects/commit.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -501,14 +501,14 @@ def _deserialize(self, stream):
501501

502502
try:
503503
self.author, self.authored_date, self.author_tz_offset = \
504-
parse_actor_and_date(author_line.decode(self.encoding))
504+
parse_actor_and_date(author_line.decode(self.encoding, 'replace'))
505505
except UnicodeDecodeError:
506506
log.error("Failed to decode author line '%s' using encoding %s", author_line, self.encoding,
507507
exc_info=True)
508508

509509
try:
510510
self.committer, self.committed_date, self.committer_tz_offset = \
511-
parse_actor_and_date(committer_line.decode(self.encoding))
511+
parse_actor_and_date(committer_line.decode(self.encoding, 'replace'))
512512
except UnicodeDecodeError:
513513
log.error("Failed to decode committer line '%s' using encoding %s", committer_line, self.encoding,
514514
exc_info=True)
@@ -518,7 +518,7 @@ def _deserialize(self, stream):
518518
# The end of our message stream is marked with a newline that we strip
519519
self.message = stream.read()
520520
try:
521-
self.message = self.message.decode(self.encoding)
521+
self.message = self.message.decode(self.encoding, 'replace')
522522
except UnicodeDecodeError:
523523
log.error("Failed to decode message '%s' using encoding %s", self.message, self.encoding, exc_info=True)
524524
# END exception handling

git/remote.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ class FetchInfo(object):
204204
NEW_TAG, NEW_HEAD, HEAD_UPTODATE, TAG_UPDATE, REJECTED, FORCED_UPDATE, \
205205
FAST_FORWARD, ERROR = [1 << x for x in range(8)]
206206

207-
re_fetch_result = re.compile("^\s*(.) (\[?[\w\s\.$@]+\]?)\s+(.+) -> ([/\w_\+\.\-$@#]+)( \(.*\)?$)?")
207+
re_fetch_result = re.compile("^\s*(.) (\[?[\w\s\.$@]+\]?)\s+(.+) -> ([/\w_\+\.\-$@#()]+)( \(.*\)?$)?")
208208

209209
_flag_map = {'!': ERROR,
210210
'+': FORCED_UPDATE,

git/test/fixtures/commit_invalid_data

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
tree 9f1a495d7d9692d24f5caedaa89f5c2c32d59368
2+
parent 492ace2ffce0e426ebeb55e364e987bcf024dd3b
3+
author E.Azer Ko�o�o�oculu <azer@kodfabrik.com> 1306710073 +0300
4+
committer E.Azer Ko�o�o�oculu <azer@kodfabrik.com> 1306710073 +0300
5+
6+
add environjs

git/test/fixtures/diff_patch_unsafe_paths

+7
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94
6161
+++ "b/path/¯\\_(ツ)_|¯"
6262
@@ -0,0 +1 @@
6363
+dummy content
64+
diff --git "a/path/\360\237\222\251.txt" "b/path/\360\237\222\251.txt"
65+
new file mode 100644
66+
index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94d8859a54
67+
--- /dev/null
68+
+++ "b/path/\360\237\222\251.txt"
69+
@@ -0,0 +1 @@
70+
+dummy content
6471
diff --git a/a/with spaces b/b/with some spaces
6572
similarity index 100%
6673
rename from a/with spaces

git/test/test_commit.py

+7
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,13 @@ def test_serialization_unicode_support(self):
306306
# it appears
307307
cmt.author.__repr__()
308308

309+
def test_invalid_commit(self):
310+
cmt = self.rorepo.commit()
311+
cmt._deserialize(open(fixture_path('commit_invalid_data'), 'rb'))
312+
313+
assert cmt.author.name == u'E.Azer Ko�o�o�oculu', cmt.author.name
314+
assert cmt.author.email == 'azer@kodfabrik.com', cmt.author.email
315+
309316
def test_gpgsig(self):
310317
cmt = self.rorepo.commit()
311318
cmt._deserialize(open(fixture_path('commit_with_gpgsig'), 'rb'))

git/test/test_diff.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -161,16 +161,17 @@ def test_diff_unsafe_paths(self):
161161
self.assertEqual(res[6].b_path, u'path/with spaces')
162162
self.assertEqual(res[7].b_path, u'path/with-question-mark?')
163163
self.assertEqual(res[8].b_path, u'path/¯\\_(ツ)_|¯')
164+
self.assertEqual(res[9].b_path, u'path/💩.txt')
164165

165166
# The "Moves"
166167
# NOTE: The path prefixes a/ and b/ here are legit! We're actually
167168
# verifying that it's not "a/a/" that shows up, see the fixture data.
168-
self.assertEqual(res[9].a_path, u'a/with spaces') # NOTE: path a/ here legit!
169-
self.assertEqual(res[9].b_path, u'b/with some spaces') # NOTE: path b/ here legit!
170-
self.assertEqual(res[10].a_path, u'a/ending in a space ')
171-
self.assertEqual(res[10].b_path, u'b/ending with space ')
172-
self.assertEqual(res[11].a_path, u'a/"with-quotes"')
173-
self.assertEqual(res[11].b_path, u'b/"with even more quotes"')
169+
self.assertEqual(res[10].a_path, u'a/with spaces') # NOTE: path a/ here legit!
170+
self.assertEqual(res[10].b_path, u'b/with some spaces') # NOTE: path b/ here legit!
171+
self.assertEqual(res[11].a_path, u'a/ending in a space ')
172+
self.assertEqual(res[11].b_path, u'b/ending with space ')
173+
self.assertEqual(res[12].a_path, u'a/"with-quotes"')
174+
self.assertEqual(res[12].b_path, u'b/"with even more quotes"')
174175

175176
def test_diff_patch_format(self):
176177
# test all of the 'old' format diffs for completness - it should at least

git/test/test_docs.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212

1313
class Tutorials(TestBase):
14-
1514
@with_rw_directory
1615
def test_init_repo_object(self, rw_dir):
1716
# [1-test_init_repo_object]
@@ -165,7 +164,7 @@ def update(self, op_code, cur_count, max_count=None, message=''):
165164
for sm in cloned_repo.submodules:
166165
assert not sm.remove().exists() # after removal, the sm doesn't exist anymore
167166
sm = cloned_repo.create_submodule('mysubrepo', 'path/to/subrepo', url=bare_repo.git_dir, branch='master')
168-
167+
169168
# .gitmodules was written and added to the index, which is now being committed
170169
cloned_repo.index.commit("Added submodule")
171170
assert sm.exists() and sm.module_exists() # this submodule is defintely available
@@ -395,7 +394,7 @@ def test_references_and_objects(self, rw_dir):
395394
hcommit.diff() # diff tree against index
396395
hcommit.diff('HEAD~1') # diff tree against previous tree
397396
hcommit.diff(None) # diff tree against working tree
398-
397+
399398
index = repo.index
400399
index.diff() # diff index against itself yielding empty diff
401400
index.diff(None) # diff index against working copy
@@ -446,7 +445,7 @@ def test_submodules(self):
446445
sm = sms[0]
447446
assert sm.name == 'gitdb' # git-python has gitdb as single submodule ...
448447
assert sm.children()[0].name == 'smmap' # ... which has smmap as single submodule
449-
448+
450449
# The module is the repository referenced by the submodule
451450
assert sm.module_exists() # the module is available, which doesn't have to be the case.
452451
assert sm.module().working_tree_dir.endswith('gitdb')
@@ -458,7 +457,7 @@ def test_submodules(self):
458457
assert sm.config_reader().get_value('path') == sm.path
459458
assert len(sm.children()) == 1 # query the submodule hierarchy
460459
# ![1-test_submodules]
461-
460+
462461
@with_rw_directory
463462
def test_add_file_and_commit(self, rw_dir):
464463
import git

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ def _stamp_version(filename):
110110
"Operating System :: MacOS :: MacOS X",
111111
"Programming Language :: Python",
112112
"Programming Language :: Python :: 2",
113+
"Programming Language :: Python :: 2.6",
113114
"Programming Language :: Python :: 2.7",
114115
"Programming Language :: Python :: 3",
115116
"Programming Language :: Python :: 3.3",

0 commit comments

Comments
 (0)