4 files changed
@@ -5,6 +5,9 @@ Changelog | |||
| 5 | 5 | 2.0.6 - Fixes and Features | |
| 6 | 6 | ========================== | |
| 7 | 7 | ||
| 8 | + * API: Diffs now have `a_rawpath`, `b_rawpath`, `raw_rename_from`, | ||
| 9 | + `raw_rename_to` properties, which are the raw-bytes equivalents of their | ||
| 10 | + unicode path counterparts. | ||
| 8 | 11 | * Fix: TypeError about passing keyword argument to string decode() on | |
| 9 | 12 | Python 2.6. | |
| 10 | 13 | * Feature: `setUrl API on Remotes <https://github.com/gitpython-developers/GitPython/pull/446#issuecomment-224670539>`_ | |
@@ -35,6 +35,7 @@ def mviter(d): | |||
| 35 | 35 | return d.values() | |
| 36 | 36 | range = xrange | |
| 37 | 37 | unicode = str | |
| 38 | + binary_type = bytes | ||
| 38 | 39 | else: | |
| 39 | 40 | FileType = file | |
| 40 | 41 | # usually, this is just ascii, which might not enough for our encoding needs | |
@@ -44,6 +45,7 @@ def mviter(d): | |||
| 44 | 45 | byte_ord = ord | |
| 45 | 46 | bchr = chr | |
| 46 | 47 | unicode = unicode | |
| 48 | + binary_type = str | ||
| 47 | 49 | range = xrange | |
| 48 | 50 | def mviter(d): | |
| 49 | 51 | return d.itervalues() | |
@@ -7,6 +7,7 @@ | |||
| 7 | 7 | ||
| 8 | 8 | from gitdb.util import hex_to_bin | |
| 9 | 9 | ||
| 10 | + from .compat import binary_type | ||
| 10 | 11 | from .objects.blob import Blob | |
| 11 | 12 | from .objects.util import mode_str_to_int | |
| 12 | 13 | ||
@@ -245,18 +246,20 @@ class Diff(object): | |||
| 245 | 246 | NULL_HEX_SHA = "0" * 40 | |
| 246 | 247 | NULL_BIN_SHA = b"\0" * 20 | |
| 247 | 248 | ||
| 248 | - __slots__ = ("a_blob", "b_blob", "a_mode", "b_mode", "a_path", "b_path", | ||
| 249 | - "new_file", "deleted_file", "rename_from", "rename_to", "diff") | ||
| 249 | + __slots__ = ("a_blob", "b_blob", "a_mode", "b_mode", "a_rawpath", "b_rawpath", | ||
| 250 | + "new_file", "deleted_file", "raw_rename_from", "raw_rename_to", "diff") | ||
| 250 | 251 | ||
| 251 | - def __init__(self, repo, a_path, b_path, a_blob_id, b_blob_id, a_mode, | ||
| 252 | - b_mode, new_file, deleted_file, rename_from, | ||
| 253 | - rename_to, diff): | ||
| 252 | + def __init__(self, repo, a_rawpath, b_rawpath, a_blob_id, b_blob_id, a_mode, | ||
| 253 | + b_mode, new_file, deleted_file, raw_rename_from, | ||
| 254 | + raw_rename_to, diff): | ||
| 254 | 255 | ||
| 255 | 256 | self.a_mode = a_mode | |
| 256 | 257 | self.b_mode = b_mode | |
| 257 | 258 | ||
| 258 | - self.a_path = a_path | ||
| 259 | - self.b_path = b_path | ||
| 259 | + assert a_rawpath is None or isinstance(a_rawpath, binary_type) | ||
| 260 | + assert b_rawpath is None or isinstance(b_rawpath, binary_type) | ||
| 261 | + self.a_rawpath = a_rawpath | ||
| 262 | + self.b_rawpath = b_rawpath | ||
| 260 | 263 | ||
| 261 | 264 | if self.a_mode: | |
| 262 | 265 | self.a_mode = mode_str_to_int(self.a_mode) | |
@@ -266,19 +269,21 @@ def __init__(self, repo, a_path, b_path, a_blob_id, b_blob_id, a_mode, | |||
| 266 | 269 | if a_blob_id is None or a_blob_id == self.NULL_HEX_SHA: | |
| 267 | 270 | self.a_blob = None | |
| 268 | 271 | else: | |
| 269 | - self.a_blob = Blob(repo, hex_to_bin(a_blob_id), mode=self.a_mode, path=a_path) | ||
| 272 | + self.a_blob = Blob(repo, hex_to_bin(a_blob_id), mode=self.a_mode, path=self.a_path) | ||
| 270 | 273 | ||
| 271 | 274 | if b_blob_id is None or b_blob_id == self.NULL_HEX_SHA: | |
| 272 | 275 | self.b_blob = None | |
| 273 | 276 | else: | |
| 274 | - self.b_blob = Blob(repo, hex_to_bin(b_blob_id), mode=self.b_mode, path=b_path) | ||
| 277 | + self.b_blob = Blob(repo, hex_to_bin(b_blob_id), mode=self.b_mode, path=self.b_path) | ||
| 275 | 278 | ||
| 276 | 279 | self.new_file = new_file | |
| 277 | 280 | self.deleted_file = deleted_file | |
| 278 | 281 | ||
| 279 | 282 | # be clear and use None instead of empty strings | |
| 280 | - self.rename_from = rename_from or None | ||
| 281 | - self.rename_to = rename_to or None | ||
| 283 | + assert raw_rename_from is None or isinstance(raw_rename_from, binary_type) | ||
| 284 | + assert raw_rename_to is None or isinstance(raw_rename_to, binary_type) | ||
| 285 | + self.raw_rename_from = raw_rename_from or None | ||
| 286 | + self.raw_rename_to = raw_rename_to or None | ||
| 282 | 287 | ||
| 283 | 288 | self.diff = diff | |
| 284 | 289 | ||
@@ -344,6 +349,22 @@ def __str__(self): | |||
| 344 | 349 | # end | |
| 345 | 350 | return res | |
| 346 | 351 | ||
| 352 | + @property | ||
| 353 | + def a_path(self): | ||
| 354 | + return self.a_rawpath.decode(defenc, 'replace') if self.a_rawpath else None | ||
| 355 | + | ||
| 356 | + @property | ||
| 357 | + def b_path(self): | ||
| 358 | + return self.b_rawpath.decode(defenc, 'replace') if self.b_rawpath else None | ||
| 359 | + | ||
| 360 | + @property | ||
| 361 | + def rename_from(self): | ||
| 362 | + return self.raw_rename_from.decode(defenc, 'replace') if self.raw_rename_from else None | ||
| 363 | + | ||
| 364 | + @property | ||
| 365 | + def rename_to(self): | ||
| 366 | + return self.raw_rename_to.decode(defenc, 'replace') if self.raw_rename_to else None | ||
| 367 | + | ||
| 347 | 368 | @property | |
| 348 | 369 | def renamed(self): | |
| 349 | 370 | """:returns: True if the blob of our diff has been renamed | |
@@ -388,6 +409,7 @@ def _index_from_patch_format(cls, repo, stream): | |||
| 388 | 409 | new_file_mode, deleted_file_mode, \ | |
| 389 | 410 | a_blob_id, b_blob_id, b_mode, \ | |
| 390 | 411 | a_path, b_path = header.groups() | |
| 412 | + | ||
| 391 | 413 | new_file, deleted_file = bool(new_file_mode), bool(deleted_file_mode) | |
| 392 | 414 | ||
| 393 | 415 | a_path = cls._pick_best_path(a_path, rename_from, a_path_fallback) | |
@@ -404,15 +426,15 @@ def _index_from_patch_format(cls, repo, stream): | |||
| 404 | 426 | a_mode = old_mode or deleted_file_mode or (a_path and (b_mode or new_mode or new_file_mode)) | |
| 405 | 427 | b_mode = b_mode or new_mode or new_file_mode or (b_path and a_mode) | |
| 406 | 428 | index.append(Diff(repo, | |
| 407 | - a_path and a_path.decode(defenc, 'replace'), | ||
| 408 | - b_path and b_path.decode(defenc, 'replace'), | ||
| 429 | + a_path, | ||
| 430 | + b_path, | ||
| 409 | 431 | a_blob_id and a_blob_id.decode(defenc), | |
| 410 | 432 | b_blob_id and b_blob_id.decode(defenc), | |
| 411 | 433 | a_mode and a_mode.decode(defenc), | |
| 412 | 434 | b_mode and b_mode.decode(defenc), | |
| 413 | 435 | new_file, deleted_file, | |
| 414 | - rename_from and rename_from.decode(defenc, 'replace'), | ||
| 415 | - rename_to and rename_to.decode(defenc, 'replace'), | ||
| 436 | + rename_from, | ||
| 437 | + rename_to, | ||
| 416 | 438 | None)) | |
| 417 | 439 | ||
| 418 | 440 | previous_header = header | |
@@ -438,8 +460,8 @@ def _index_from_raw_format(cls, repo, stream): | |||
| 438 | 460 | meta, _, path = line[1:].partition('\t') | |
| 439 | 461 | old_mode, new_mode, a_blob_id, b_blob_id, change_type = meta.split(None, 4) | |
| 440 | 462 | path = path.strip() | |
| 441 | - a_path = path | ||
| 442 | - b_path = path | ||
| 463 | + a_path = path.encode(defenc) | ||
| 464 | + b_path = path.encode(defenc) | ||
| 443 | 465 | deleted_file = False | |
| 444 | 466 | new_file = False | |
| 445 | 467 | rename_from = None | |
@@ -455,6 +477,8 @@ def _index_from_raw_format(cls, repo, stream): | |||
| 455 | 477 | new_file = True | |
| 456 | 478 | elif change_type[0] == 'R': # parses RXXX, where XXX is a confidence value | |
| 457 | 479 | a_path, b_path = path.split('\t', 1) | |
| 480 | + a_path = a_path.encode(defenc) | ||
| 481 | + b_path = b_path.encode(defenc) | ||
| 458 | 482 | rename_from, rename_to = a_path, b_path | |
| 459 | 483 | # END add/remove handling | |
| 460 | 484 | ||
@@ -90,6 +90,8 @@ def test_diff_with_rename(self): | |||
| 90 | 90 | assert_true(diff.renamed) | |
| 91 | 91 | assert_equal(diff.rename_from, u'Jérôme') | |
| 92 | 92 | assert_equal(diff.rename_to, u'müller') | |
| 93 | + assert_equal(diff.raw_rename_from, b'J\xc3\xa9r\xc3\xb4me') | ||
| 94 | + assert_equal(diff.raw_rename_to, b'm\xc3\xbcller') | ||
| 93 | 95 | assert isinstance(str(diff), str) | |
| 94 | 96 | ||
| 95 | 97 | output = StringProcessAdapter(fixture('diff_rename_raw')) | |
@@ -129,7 +131,7 @@ def test_diff_index_raw_format(self): | |||
| 129 | 131 | output = StringProcessAdapter(fixture('diff_index_raw')) | |
| 130 | 132 | res = Diff._index_from_raw_format(None, output.stdout) | |
| 131 | 133 | assert res[0].deleted_file | |
| 132 | - assert res[0].b_path == '' | ||
| 134 | + assert res[0].b_path is None | ||
| 133 | 135 | ||
| 134 | 136 | def test_diff_initial_commit(self): | |
| 135 | 137 | initial_commit = self.rorepo.commit('33ebe7acec14b25c5f84f35a664803fcab2f7781') | |
@@ -162,7 +164,9 @@ def test_diff_unsafe_paths(self): | |||
| 162 | 164 | self.assertEqual(res[7].b_path, u'path/with-question-mark?') | |
| 163 | 165 | self.assertEqual(res[8].b_path, u'path/¯\\_(ツ)_|¯') | |
| 164 | 166 | self.assertEqual(res[9].b_path, u'path/💩.txt') | |
| 167 | + self.assertEqual(res[9].b_rawpath, b'path/\xf0\x9f\x92\xa9.txt') | ||
| 165 | 168 | self.assertEqual(res[10].b_path, u'path/�-invalid-unicode-path.txt') | |
| 169 | + self.assertEqual(res[10].b_rawpath, b'path/\x80-invalid-unicode-path.txt') | ||
| 166 | 170 | ||
| 167 | 171 | # The "Moves" | |
| 168 | 172 | # NOTE: The path prefixes a/ and b/ here are legit! We're actually | |
0 commit comments