Skip to content

Commit e1a42f3

Browse files
authored
DEV: reduce merge conflicts by sorting whatsnew notes by issue number? (#51715)
1 parent 005e0eb commit e1a42f3

File tree

4 files changed

+117
-4
lines changed

4 files changed

+117
-4
lines changed

.pre-commit-config.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -436,3 +436,10 @@ repos:
436436
types: [python]
437437
files: ^pandas/tests
438438
language: python
439+
- id: sort-whatsnew-items
440+
name: sort whatsnew entries by issue number
441+
entry: python -m scripts.sort_whatsnew_note
442+
types: [rst]
443+
language: python
444+
files: ^doc/source/whatsnew/v
445+
exclude: ^doc/source/whatsnew/v(0|1|2\.0\.0)

doc/source/whatsnew/v2.1.0.rst

+4-4
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,12 @@ Deprecations
100100

101101
Performance improvements
102102
~~~~~~~~~~~~~~~~~~~~~~~~
103-
- Performance improvement in :meth:`DataFrame.where` when ``cond`` is backed by an extension dtype (:issue:`51574`)
104-
- Performance improvement in :meth:`~arrays.ArrowExtensionArray.isna` when array has zero nulls or is all nulls (:issue:`51630`)
105-
- Performance improvement in :meth:`DataFrame.first_valid_index` and :meth:`DataFrame.last_valid_index` for extension array dtypes (:issue:`51549`)
106-
- Performance improvement in :meth:`DataFrame.clip` and :meth:`Series.clip` (:issue:`51472`)
107103
- Performance improvement in :func:`read_parquet` on string columns when using ``use_nullable_dtypes=True`` (:issue:`47345`)
104+
- Performance improvement in :meth:`DataFrame.clip` and :meth:`Series.clip` (:issue:`51472`)
105+
- Performance improvement in :meth:`DataFrame.first_valid_index` and :meth:`DataFrame.last_valid_index` for extension array dtypes (:issue:`51549`)
106+
- Performance improvement in :meth:`DataFrame.where` when ``cond`` is backed by an extension dtype (:issue:`51574`)
108107
- Performance improvement in :meth:`read_orc` when reading a remote URI file path. (:issue:`51609`)
108+
- Performance improvement in :meth:`~arrays.ArrowExtensionArray.isna` when array has zero nulls or is all nulls (:issue:`51630`)
109109

110110
.. ---------------------------------------------------------------------------
111111
.. _whatsnew_210.bug_fixes:

scripts/sort_whatsnew_note.py

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""
2+
Sort whatsnew note blocks by issue number.
3+
4+
NOTE: this assumes that each entry is on its own line, and ends with an issue number.
5+
If that's not the case, then an entry might not get sorted. However, virtually all
6+
recent-enough whatsnew entries follow this pattern. So, although not perfect, this
7+
script should be good enough to significantly reduce merge conflicts.
8+
9+
For example:
10+
11+
- Fixed bug in resample (:issue:`321`)
12+
- Fixed bug in groupby (:issue:`123`)
13+
14+
would become
15+
16+
- Fixed bug in groupby (:issue:`123`)
17+
- Fixed bug in resample (:issue:`321`)
18+
19+
The motivation is to reduce merge conflicts by reducing the chances that multiple
20+
contributors will edit the same line of code.
21+
22+
You can run this manually with
23+
24+
pre-commit run sort-whatsnew-items --all-files
25+
"""
26+
from __future__ import annotations
27+
28+
import argparse
29+
import re
30+
import sys
31+
from typing import Sequence
32+
33+
pattern = re.compile(r"\(:issue:`(\d+)`\)\n$")
34+
35+
36+
def sort_whatsnew_note(content: str) -> int:
37+
new_lines = []
38+
block: list[str] = []
39+
lines = content.splitlines(keepends=True)
40+
for line in lines:
41+
if line.startswith("- ") and pattern.search(line) is not None:
42+
block.append(line)
43+
else:
44+
key = lambda x: int(pattern.search(x).group(1))
45+
block = sorted(block, key=key)
46+
new_lines.extend(block)
47+
new_lines.append(line)
48+
block = []
49+
if sorted(new_lines) != sorted(lines): # pragma: no cover
50+
# Defensive check - this script should only reorder lines, not modify any
51+
# content.
52+
raise AssertionError(
53+
"Script modified content of file. Something is wrong, please don't "
54+
"trust it."
55+
)
56+
return "".join(new_lines)
57+
58+
59+
def main(argv: Sequence[str] | None = None) -> int:
60+
parser = argparse.ArgumentParser()
61+
parser.add_argument("paths", nargs="*")
62+
args = parser.parse_args(argv)
63+
ret = 0
64+
for path in args.paths:
65+
with open(path) as fd:
66+
content = fd.read()
67+
new_content = sort_whatsnew_note(content)
68+
if content != new_content:
69+
ret |= 1
70+
with open(path, "w") as fd:
71+
fd.write(new_content)
72+
return ret
73+
74+
75+
if __name__ == "__main__":
76+
sys.exit(main())
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from scripts.sort_whatsnew_note import sort_whatsnew_note
2+
3+
4+
def test_sort_whatsnew_note():
5+
content = (
6+
".. _whatsnew_200:\n"
7+
"\n"
8+
"What's new in 2.0.0 (March XX, 2023)\n"
9+
"------------------------------------\n"
10+
"\n"
11+
"Timedelta\n"
12+
"^^^^^^^^^\n"
13+
"- Bug in :class:`TimedeltaIndex` (:issue:`51575`)\n"
14+
"- Bug in :meth:`Timedelta.round` (:issue:`51494`)\n"
15+
"\n"
16+
)
17+
expected = (
18+
".. _whatsnew_200:\n"
19+
"\n"
20+
"What's new in 2.0.0 (March XX, 2023)\n"
21+
"------------------------------------\n"
22+
"\n"
23+
"Timedelta\n"
24+
"^^^^^^^^^\n"
25+
"- Bug in :meth:`Timedelta.round` (:issue:`51494`)\n"
26+
"- Bug in :class:`TimedeltaIndex` (:issue:`51575`)\n"
27+
"\n"
28+
)
29+
result = sort_whatsnew_note(content)
30+
assert result == expected

0 commit comments

Comments
 (0)