From 7725e035411a28924b4e776da7283fa3bb4d5e66 Mon Sep 17 00:00:00 2001 From: George Hickman Date: Mon, 6 Mar 2017 16:17:47 +0000 Subject: [PATCH 1/3] Ignore all lines of subsequent hunks until last one is found Git version 2.11.1+ introduced extra lines into the subsequent hunk sections for incremental blame output. The documentation notes that parsers of this output should ignore all lines between the start and end for robust parsing. --- git/repo/base.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/git/repo/base.py b/git/repo/base.py index b889da710..fa9cb5965 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -713,11 +713,14 @@ def blame_incremental(self, rev, file, **kwargs): committed_date=int(props[b'committer-time'])) commits[hexsha] = c else: - # Discard the next line (it's a filename end tag) - line = next(stream) - tag, value = line.split(b' ', 1) - assert tag == b'filename', 'Unexpected git blame output' - orig_filename = value + # Discard all lines until we find "filename" which is + # guaranteed to be the last line + while True: + line = next(stream) + tag, value = line.split(b' ', 1) + if tag == b'filename': + orig_filename = value + break yield BlameEntry(commits[hexsha], range(lineno, lineno + num_lines), From 77b20beebe15fc32ac4733ebf79e628281444526 Mon Sep 17 00:00:00 2001 From: George Hickman Date: Tue, 7 Mar 2017 12:16:12 +0000 Subject: [PATCH 2/3] Document the use of next to throw an exception when hitting EOF --- git/repo/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/repo/base.py b/git/repo/base.py index fa9cb5965..7820fd668 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -716,7 +716,7 @@ def blame_incremental(self, rev, file, **kwargs): # Discard all lines until we find "filename" which is # guaranteed to be the last line while True: - line = next(stream) + line = next(stream) # will fail if we reach the EOF unexpectedly tag, value = line.split(b' ', 1) if tag == b'filename': orig_filename = value From 73a5926bbfe087e5a972a910ec984686b6047a12 Mon Sep 17 00:00:00 2001 From: George Hickman Date: Tue, 7 Mar 2017 16:40:13 +0000 Subject: [PATCH 3/3] Add a fixture to test incremental blame output for git 2.11.1+ --- .../fixtures/blame_incremental_2.11.1_plus | 33 ++++++++++++++++ git/test/test_repo.py | 38 ++++++++++--------- 2 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 git/test/fixtures/blame_incremental_2.11.1_plus diff --git a/git/test/fixtures/blame_incremental_2.11.1_plus b/git/test/fixtures/blame_incremental_2.11.1_plus new file mode 100644 index 000000000..beee7011f --- /dev/null +++ b/git/test/fixtures/blame_incremental_2.11.1_plus @@ -0,0 +1,33 @@ +82b8902e033430000481eb355733cd7065342037 2 2 1 +author Sebastian Thiel +author-mail +author-time 1270634931 +author-tz +0200 +committer Sebastian Thiel +committer-mail +committer-time 1270634931 +committer-tz +0200 +summary Used this release for a first beta of the 0.2 branch of development +previous 501bf602abea7d21c3dbb409b435976e92033145 AUTHORS +filename AUTHORS +82b8902e033430000481eb355733cd7065342037 14 14 1 +previous 501bf602abea7d21c3dbb409b435976e92033145 AUTHORS +filename AUTHORS +c76852d0bff115720af3f27acdb084c59361e5f6 1 1 1 +author Michael Trier +author-mail +author-time 1232829627 +author-tz -0500 +committer Michael Trier +committer-mail +committer-time 1232829627 +committer-tz -0500 +summary Lots of spring cleaning and added in Sphinx documentation. +previous bcd57e349c08bd7f076f8d6d2f39b702015358c1 AUTHORS +filename AUTHORS +c76852d0bff115720af3f27acdb084c59361e5f6 2 3 11 +previous bcd57e349c08bd7f076f8d6d2f39b702015358c1 AUTHORS +filename AUTHORS +c76852d0bff115720af3f27acdb084c59361e5f6 13 15 2 +previous bcd57e349c08bd7f076f8d6d2f39b702015358c1 AUTHORS +filename AUTHORS diff --git a/git/test/test_repo.py b/git/test/test_repo.py index 4f9be4fc7..91c780dde 100644 --- a/git/test/test_repo.py +++ b/git/test/test_repo.py @@ -387,24 +387,26 @@ def test_blame_real(self): @patch.object(Git, '_call_process') def test_blame_incremental(self, git): - git.return_value = fixture('blame_incremental') - blame_output = self.rorepo.blame_incremental('9debf6b0aafb6f7781ea9d1383c86939a1aacde3', 'AUTHORS') - blame_output = list(blame_output) - self.assertEqual(len(blame_output), 5) - - # Check all outputted line numbers - ranges = flatten([entry.linenos for entry in blame_output]) - self.assertEqual(ranges, flatten([range(2, 3), range(14, 15), range(1, 2), range(3, 14), range(15, 17)])) - - commits = [entry.commit.hexsha[:7] for entry in blame_output] - self.assertEqual(commits, ['82b8902', '82b8902', 'c76852d', 'c76852d', 'c76852d']) - - # Original filenames - self.assertSequenceEqual([entry.orig_path for entry in blame_output], [u'AUTHORS'] * len(blame_output)) - - # Original line numbers - orig_ranges = flatten([entry.orig_linenos for entry in blame_output]) - self.assertEqual(orig_ranges, flatten([range(2, 3), range(14, 15), range(1, 2), range(2, 13), range(13, 15)])) # noqa E501 + # loop over two fixtures, create a test fixture for 2.11.1+ syntax + for git_fixture in ('blame_incremental', 'blame_incremental_2.11.1_plus'): + git.return_value = fixture(git_fixture) + blame_output = self.rorepo.blame_incremental('9debf6b0aafb6f7781ea9d1383c86939a1aacde3', 'AUTHORS') + blame_output = list(blame_output) + self.assertEqual(len(blame_output), 5) + + # Check all outputted line numbers + ranges = flatten([entry.linenos for entry in blame_output]) + self.assertEqual(ranges, flatten([range(2, 3), range(14, 15), range(1, 2), range(3, 14), range(15, 17)])) + + commits = [entry.commit.hexsha[:7] for entry in blame_output] + self.assertEqual(commits, ['82b8902', '82b8902', 'c76852d', 'c76852d', 'c76852d']) + + # Original filenames + self.assertSequenceEqual([entry.orig_path for entry in blame_output], [u'AUTHORS'] * len(blame_output)) + + # Original line numbers + orig_ranges = flatten([entry.orig_linenos for entry in blame_output]) + self.assertEqual(orig_ranges, flatten([range(2, 3), range(14, 15), range(1, 2), range(2, 13), range(13, 15)])) # noqa E501 @patch.object(Git, '_call_process') def test_blame_complex_revision(self, git):