From: Lenormand Julien Date: Sat, 18 Jul 2026 13:40:11 +0000 (+0200) Subject: gh-71896: Add a docs warning about trailing newlines ignored by `difflib.HtmlDiff... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=755d97167f6f5d4361c790f8f18df76cae8f2ad2;p=thirdparty%2FPython%2Fcpython.git gh-71896: Add a docs warning about trailing newlines ignored by `difflib.HtmlDiff` (GH-153930) --- diff --git a/Doc/library/difflib.rst b/Doc/library/difflib.rst index 25edb40e35a6..3ed4768b6a14 100644 --- a/Doc/library/difflib.rst +++ b/Doc/library/difflib.rst @@ -85,6 +85,11 @@ diffs. For comparing directories and files, see also, the :mod:`filecmp` module. with inter-line and intra-line change highlights. The table can be generated in either full or contextual difference mode. + .. warning:: + + The trailing newlines get stripped before the diff, so the result can be + incomplete. See :gh:`71896` for details. + The constructor for this class is: diff --git a/Lib/difflib.py b/Lib/difflib.py index ae8b284b4d36..95ba8fd782c6 100644 --- a/Lib/difflib.py +++ b/Lib/difflib.py @@ -2016,6 +2016,8 @@ class HtmlDiff(object): # change tabs to spaces before it gets more difficult after we insert # markup + # it also removes trailing newlines, causing some diffs to be missed + # see: gh-71896 fromlines,tolines = self._tab_newline_replace(fromlines,tolines) # create diffs iterator which generates side by side from/to data diff --git a/Lib/test/test_difflib.py b/Lib/test/test_difflib.py index 46c9b2c1d8c9..4f99b7c91c65 100644 --- a/Lib/test/test_difflib.py +++ b/Lib/test/test_difflib.py @@ -284,6 +284,29 @@ class TestSFpatches(unittest.TestCase): self.assertIn('charset="us-ascii"', output) self.assertIn('ımplıcıt', output) + def test_strip_trailing_newlines_before_diff(self): + # characterization test for the current buggy behavior + # see: gh-71896 + html_diff = difflib.HtmlDiff() + from_lines = [ + "Line 1: no newline after", + "Line 2: one newline after\n", + "Line 3: several newlines after\n\n\n\n\n", + ] + to_lines = [ + "Line 1: no newline after", + "Line 2: one newline after", # actually no \n + "Line 3: several newlines after", # actually no \n + ] + output = html_diff.make_table(from_lines, to_lines) + # we (currently) expect no line change, so all equal + self.assertNotIn('class="diff_add"', output) + self.assertNotIn('class="diff_chg"', output) + self.assertNotIn('class="diff_sub"', output) + self.assertEqual(output.count('>Line 1: no newline after<'), 2) + self.assertEqual(output.count('>Line 2: one newline after<'), 2) + self.assertEqual(output.count('>Line 3: several newlines after<'), 2) + class TestDiffer(unittest.TestCase): def test_close_matches_aligned(self): # Of the 4 closely matching pairs, we want 1 to match with 3,