diff --git a/kallithea/lib/diffs.py b/kallithea/lib/diffs.py
--- a/kallithea/lib/diffs.py
+++ b/kallithea/lib/diffs.py
@@ -445,7 +445,7 @@ class DiffProcessor(object):
return self.adds, self.removes
-_escape_re = re.compile(r'(&)|(<)|(>)|(\t)|(\r)|(?<=.)( \n| $)|(\t\n|\t$)')
+_escape_re = re.compile(r'(&)|(<)|(>)|(\t)|(\r)|(?<=.)( $)|(\t$)')
def _escaper(diff_line):
@@ -571,9 +571,13 @@ def _get_header(vcs, diff_chunk):
raise Exception('diff not recognized as valid %s diff' % vcs)
meta_info = {k: None if v is None else safe_str(v) for k, v in match.groupdict().items()}
rest = diff_chunk[match.end():]
- if rest and _header_next_check.match(rest):
- raise Exception('cannot parse %s diff header: %r followed by %r' % (vcs, safe_str(bytes(diff_chunk[:match.end()])), safe_str(bytes(rest[:1000]))))
- diff_lines = (_escaper(safe_str(m.group(0))) for m in re.finditer(br'.*\n|.+$', rest)) # don't split on \r as str.splitlines do
+ if rest:
+ if _header_next_check.match(rest):
+ raise Exception('cannot parse %s diff header: %r followed by %r' % (vcs, safe_str(bytes(diff_chunk[:match.end()])), safe_str(bytes(rest[:1000]))))
+ if rest[-1:] != b'\n':
+ # The diff will generally already have trailing \n (and be a memoryview). It might also be huge so we don't want to allocate it twice. But in this very rare case, we don't care.
+ rest = bytes(rest) + b'\n'
+ diff_lines = (_escaper(safe_str(m.group(1))) for m in re.finditer(br'(.*)\n', rest))
return meta_info, diff_lines
diff --git a/kallithea/tests/models/test_diff_parsers.py b/kallithea/tests/models/test_diff_parsers.py
--- a/kallithea/tests/models/test_diff_parsers.py
+++ b/kallithea/tests/models/test_diff_parsers.py
@@ -295,20 +295,20 @@ class TestDiffLib(base.TestController):
l.append('%(action)-7s %(new_lineno)3s %(old_lineno)3s %(line)r\n' % d)
s = ''.join(l)
assert s == r'''
-context '@@ -51,6 +51,13 @@\n'
-unmod 51 51 '\tbegin();\n'
-unmod 52 52 '\t\n'
-add 53 '\tint foo;\n'
-add 54 '\tint bar; \n'
-add 55 '\tint baz;\t\n'
+context '@@ -51,6 +51,13 @@'
+unmod 51 51 '\tbegin();'
+unmod 52 52 '\t'
+add 53 '\tint foo;'
+add 54 '\tint bar; '
+add 55 '\tint baz;\t'
add 56 '\tint space; '
-add 57 '\tint tab;\t\n'
-add 58 '\t\n'
+add 57 '\tint tab;\t'
+add 58 '\t'
unmod 59 53 ' '
-del 54 '\t#define MAX_STEPS (48)\n'
-add 60 '\t\n'
-add 61 '\t#define MAX_STEPS (64)\n'
-unmod 62 55 '\n'
-del 56 '\t#define MIN_STEPS (48)\n'
-add 63 '\t#define MIN_STEPS (42)\n'
+del 54 '\t#define MAX_STEPS (48)'
+add 60 '\t'
+add 61 '\t#define MAX_STEPS (64)'
+unmod 62 55 ''
+del 56 '\t#define MIN_STEPS (48)'
+add 63 '\t#define MIN_STEPS (42)'
'''