| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent c7ce984 commit de50b14
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,29 +9,28 @@ | |||
| 9 | 9 | import re | |
| 10 | 10 | import warnings | |
| 11 | 11 | ||
| 12 | - from git.cmd import Git, handle_process_output | ||
| 13 | - from git.compat import defenc | ||
| 14 | - from git.objects.base import IndexObject | ||
| 15 | - from git.objects.blob import Blob | ||
| 16 | - from git.objects.util import mode_str_to_int | ||
| 17 | - from git.util import finalize_process, hex_to_bin | ||
| 18 | - | ||
| 19 | 12 | # typing ------------------------------------------------------------------ | |
| 20 | - | ||
| 21 | 13 | from typing import ( | |
| 14 | + TYPE_CHECKING, | ||
| 22 | 15 | Any, | |
| 23 | 16 | Iterator, | |
| 24 | 17 | List, | |
| 25 | 18 | Match, | |
| 26 | 19 | Optional, | |
| 27 | 20 | Sequence, | |
| 28 | 21 | Tuple, | |
| 29 | - TYPE_CHECKING, | ||
| 30 | 22 | TypeVar, | |
| 31 | 23 | Union, | |
| 32 | 24 | cast, | |
| 33 | 25 | ) | |
| 34 | - from git.types import PathLike, Literal | ||
| 26 | + | ||
| 27 | + from git.cmd import Git, handle_process_output | ||
| 28 | + from git.compat import defenc | ||
| 29 | + from git.objects.base import IndexObject | ||
| 30 | + from git.objects.blob import Blob | ||
| 31 | + from git.objects.util import mode_str_to_int | ||
| 32 | + from git.types import Literal, PathLike | ||
| 33 | + from git.util import finalize_process, hex_to_bin | ||
| 35 | 34 | ||
| 36 | 35 | if TYPE_CHECKING: | |
| 37 | 36 | from subprocess import Popen | |
@@ -645,19 +644,22 @@ def _same_path_in_header(names: bytes) -> Optional[bytes]: | |||
| 645 | 644 | end = 1 | |
| 646 | 645 | while end < len(names) and names[end : end + 1] != b'"': | |
| 647 | 646 | end += 2 if names[end : end + 1] == b"\\" else 1 | |
| 648 | - splits = [end + 1] | ||
| 647 | + split = end + 1 | ||
| 649 | 648 | else: | |
| 650 | - splits = [index for index in range(len(names)) if names[index : index + 1] == b" "] | ||
| 651 | - for split in splits: | ||
| 652 | - if names[split : split + 1] != b" ": | ||
| 653 | - continue | ||
| 654 | - a_name, b_name = names[:split], names[split + 1 :] | ||
| 655 | - if a_name.startswith(b'"'): | ||
| 656 | - if not (a_name.endswith(b'"') and b_name.startswith(b'"') and b_name.endswith(b'"')): | ||
| 657 | - continue | ||
| 658 | - a_name, b_name = _unquote_path(a_name[1:-1]), _unquote_path(b_name[1:-1]) | ||
| 659 | - if a_name.startswith(b"a/") and b_name.startswith(b"b/") and a_name[2:] == b_name[2:]: | ||
| 660 | - return a_name[2:] | ||
| 649 | + # Equal unquoted paths have equal-length names, so only the midpoint | ||
| 650 | + # can separate them. Trying every space would copy the header quadratically. | ||
| 651 | + if len(names) % 2 == 0: | ||
| 652 | + return None | ||
| 653 | + split = len(names) // 2 | ||
| 654 | + if names[split : split + 1] != b" ": | ||
| 655 | + return None | ||
| 656 | + a_name, b_name = names[:split], names[split + 1 :] | ||
| 657 | + if a_name.startswith(b'"'): | ||
| 658 | + if not (a_name.endswith(b'"') and b_name.startswith(b'"') and b_name.endswith(b'"')): | ||
| 659 | + return None | ||
| 660 | + a_name, b_name = _unquote_path(a_name[1:-1]), _unquote_path(b_name[1:-1]) | ||
| 661 | + if a_name.startswith(b"a/") and b_name.startswith(b"b/") and a_name[2:] == b_name[2:]: | ||
| 662 | + return a_name[2:] | ||
| 661 | 663 | return None | |
| 662 | 664 | ||
| 663 | 665 | @classmethod | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,23 +4,21 @@ | |||
| 4 | 4 | # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ | |
| 5 | 5 | ||
| 6 | 6 | import gc | |
| 7 | - from io import BytesIO | ||
| 8 | 7 | import os.path as osp | |
| 9 | 8 | import shutil | |
| 10 | 9 | import sys | |
| 11 | 10 | import tempfile | |
| 11 | + from io import BytesIO | ||
| 12 | 12 | ||
| 13 | 13 | import ddt | |
| 14 | 14 | import pytest | |
| 15 | - | ||
| 16 | 15 | from gitdb.base import IStream | |
| 17 | 16 | ||
| 18 | - from git import NULL_TREE, Blob, Diff, DiffIndex, Diffable, GitCommandError, Repo, Submodule | ||
| 17 | + from git import NULL_TREE, Blob, Diff, Diffable, DiffIndex, GitCommandError, Repo, Submodule | ||
| 19 | 18 | from git.cmd import Git | |
| 20 | 19 | from git.diff import decode_path | |
| 21 | 20 | from git.exc import UnsafeOptionError | |
| 22 | 21 | from git.index.typ import BaseIndexEntry | |
| 23 | - | ||
| 24 | 22 | from test.lib import StringProcessAdapter, TestBase, fixture, with_rw_directory | |
| 25 | 23 | ||
| 26 | 24 | ||
@@ -306,6 +304,56 @@ def test_diff_patch_paths_named_only_by_the_header_line(self, rw_dir): | |||
| 306 | 304 | {(paths[0], paths[0], False), (paths[0], "copy b/logo.png", True)}, | |
| 307 | 305 | ) | |
| 308 | 306 | ||
| 307 | + @ddt.data( | ||
| 308 | + (b"a/x b/x", b"x"), | ||
| 309 | + (b"a/x b/x ", b"x "), | ||
| 310 | + (b"a/x b/y", None), | ||
| 311 | + (b"a/x b/xx", None), | ||
| 312 | + (b"a/x-b/x", None), | ||
| 313 | + (b"", None), | ||
| 314 | + (b'"a/x\\t" "b/x\\t"', b"x\t"), | ||
| 315 | + (b'"a/x\\t" "b/x\\011"', b"x\t"), | ||
| 316 | + (b'"a/x\\t" "b/y\\t"', None), | ||
| 317 | + (b'"a/x\\t" "b/x\\t', None), | ||
| 318 | + ) | ||
| 319 | + def test_same_path_in_header(self, case): | ||
| 320 | + names, expected = case | ||
| 321 | + self.assertEqual(Diff._same_path_in_header(names), expected) | ||
| 322 | + | ||
| 323 | + def test_same_path_in_header_copies_linear_bytes(self): | ||
| 324 | + class SliceCountingBytes(bytes): | ||
| 325 | + sliced_bytes = 0 | ||
| 326 | + | ||
| 327 | + def __getitem__(self, key): | ||
| 328 | + result = super().__getitem__(key) | ||
| 329 | + if isinstance(key, slice): | ||
| 330 | + self.sliced_bytes += len(result) | ||
| 331 | + return result | ||
| 332 | + | ||
| 333 | + path = b"x " * 1000 | ||
| 334 | + names = SliceCountingBytes(b"a/" + path + b" b/" + path) | ||
| 335 | + self.assertEqual(Diff._same_path_in_header(names), path) | ||
| 336 | + # Count copied bytes rather than asserting a machine-dependent time limit. | ||
| 337 | + self.assertLessEqual(names.sliced_bytes, 2 * len(names)) | ||
| 338 | + | ||
| 339 | + @with_rw_directory | ||
| 340 | + def test_diff_patch_mode_only_with_long_space_heavy_path(self, rw_dir): | ||
| 341 | + repo = Repo.init(rw_dir) | ||
| 342 | + path = b"x " * 250000 | ||
| 343 | + blob = repo.odb.store(IStream(Blob.type, 0, BytesIO(b""))).binsha | ||
| 344 | + trees = [] | ||
| 345 | + # Store trees directly: Git accepts paths longer than filesystem or index limits. | ||
| 346 | + for mode in (b"100644", b"100755"): | ||
| 347 | + data = mode + b" " + path + b"\0" + blob | ||
| 348 | + tree = repo.odb.store(IStream(b"tree", len(data), BytesIO(data))) | ||
| 349 | + trees.append(repo.tree(tree.binsha.hex())) | ||
| 350 | + | ||
| 351 | + diffs = trees[0].diff(trees[1], create_patch=True) | ||
| 352 | + self.assertEqual(len(diffs), 1) | ||
| 353 | + self.assertEqual((diffs[0].a_rawpath, diffs[0].b_rawpath), (path, path)) | ||
| 354 | + self.assertEqual((diffs[0].a_mode, diffs[0].b_mode), (0o100644, 0o100755)) | ||
| 355 | + self.assertEqual(diffs[0].diff, b"") | ||
| 356 | + | ||
| 309 | 357 | def test_empty_diff(self): | |
| 310 | 358 | res = [] | |
| 311 | 359 | Diff._handle_diff_line(b"", None, res) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments