Mercurial > hg
comparison contrib/relnotes @ 39352:035517d48865
contrib: import the relnotes script from the release-tools repo
I figure this makes more sense to keep in the main repo, as it's a
guide of sorts on how to use the releasenotes extension in the
presence of commits that don't get relnotes annotations.
Ported to Python 3, cleaned up some logic in a few places, but for the
most part it's what we've been using for years.
Differential Revision: https://phab.mercurial-scm.org/D4291
author | Augie Fackler <augie@google.com> |
---|---|
date | Wed, 25 Jul 2018 13:28:36 -0400 |
parents | |
children | 659e2bbd0c20 |
comparison
equal
deleted
inserted
replaced
39351:4cfd1eebe6aa | 39352:035517d48865 |
---|---|
1 #!/usr/bin/env python3 | |
2 """Generate release notes from our commit log. | |
3 | |
4 This uses the relnotes extension directives when they're available, | |
5 and falls back to our old pre-relnotes logic that used to live in the | |
6 release-tools repo. | |
7 """ | |
8 import argparse | |
9 import re | |
10 import subprocess | |
11 | |
12 # Regenerate this list with | |
13 # hg export 'grep("\.\. [a-z]+::")' | grep '^\.\.' | \ | |
14 # sed 's/.. //;s/::.*//' | sort -u | |
15 rnsections = ["api", "bc", "container", "feature", "fix", "note", "perf"] | |
16 | |
17 rules = { | |
18 # keep | |
19 r"\(issue": 100, | |
20 r"\(BC\)": 100, | |
21 r"\(API\)": 100, | |
22 # core commands, bump up | |
23 r"(commit|files|log|pull|push|patch|status|tag|summary)(|s|es):": 20, | |
24 r"(annotate|alias|branch|bookmark|clone|graft|import|verify).*:": 20, | |
25 # extensions, bump up | |
26 r"(mq|shelve|rebase):": 20, | |
27 # newsy | |
28 r": deprecate": 20, | |
29 r"(option|feature|command|support)": 10, | |
30 # bug-like? | |
31 r"(fix|don't break|improve)": 7, | |
32 # boring stuff, bump down | |
33 r"^contrib": -5, | |
34 r"debug": -5, | |
35 r"help": -5, | |
36 r"(doc|bundle2|obsolete|obsmarker|rpm|setup|debug\S+:)": -15, | |
37 r"(check-code|check-commit|import-checker)": -20, | |
38 # cleanups and refactoring | |
39 r"(cleanup|whitespace|nesting|indent|spelling|comment)": -20, | |
40 r"(typo|hint|note|style:|correct doc)": -20, | |
41 r"_": -10, | |
42 r"(argument|absolute_import|attribute|assignment|mutable)": -15, | |
43 r"(unused|useless|unnecessary|duplicate|deprecated|scope|True|False)": -10, | |
44 r"(redundant|pointless|confusing|uninitialized|meaningless|dead)": -10, | |
45 r": (drop|remove|inherit|rename|simplify|naming|inline)": -10, | |
46 r"(docstring|document .* method)": -20, | |
47 r"(factor|extract|prepare|split|replace| import)": -20, | |
48 r": add.*(function|method|implementation|test|example)": -10, | |
49 r": (move|extract) .* (to|into|from)": -20, | |
50 r": implement ": -5, | |
51 r": use .* implementation": -20, | |
52 r"\S\S\S+\.\S\S\S\S+": -5, | |
53 r": use .* instead of": -20, | |
54 r"__": -5, | |
55 # dumb keywords | |
56 r"\S+/\S+:": -10, | |
57 r"\S+\.\S+:": -10, | |
58 # drop | |
59 r"^i18n-": -50, | |
60 r"^i18n:.*(hint|comment)": -50, | |
61 r"perf:": -50, | |
62 r"check-code:": -50, | |
63 r"Added.*for changeset": -50, | |
64 r"tests?:": -50, | |
65 r"test-": -50, | |
66 r"add.* tests": -50, | |
67 r"^_": -50, | |
68 } | |
69 | |
70 cutoff = 10 | |
71 commits = [] | |
72 | |
73 groupings = [ | |
74 (r"util|parsers|repo|ctx|context|revlog|filelog|alias|cmdutil", "core"), | |
75 (r"revset|templater|ui|dirstate|hook|i18n|transaction|wire", "core"), | |
76 (r"color|pager", "core"), | |
77 (r"hgweb|paper|coal|gitweb", "hgweb"), | |
78 (r"pull|push|revert|resolve|annotate|bookmark|branch|clone", "commands"), | |
79 (r"commands|commit|config|files|graft|import|log|merge|patch", "commands"), | |
80 (r"phases|status|summary|amend|tag|help|verify", "commands"), | |
81 (r"rebase|mq|convert|eol|histedit|largefiles", "extensions"), | |
82 (r"shelve|unshelve", "extensions"), | |
83 ] | |
84 | |
85 def main(): | |
86 ap = argparse.ArgumentParser() | |
87 ap.add_argument( | |
88 "startrev", | |
89 metavar="REV", | |
90 type=str, | |
91 nargs=1, | |
92 help=( | |
93 "Starting revision for the release notes. This revision " | |
94 "won't be included, but later revisions will." | |
95 ), | |
96 ) | |
97 ap.add_argument( | |
98 "--stoprev", | |
99 metavar="REV", | |
100 type=str, | |
101 default="@", | |
102 nargs=1, | |
103 help=( | |
104 "Stop revision for release notes. This revision will be included," | |
105 " but no later revisions will. This revision needs to be " | |
106 "a descendant of startrev." | |
107 ), | |
108 ) | |
109 args = ap.parse_args() | |
110 fromext = subprocess.check_output( | |
111 [ | |
112 "hg", | |
113 "releasenotes", | |
114 "-r", | |
115 "%s::%s" % (args.startrev[0], args.stoprev[0]), | |
116 ] | |
117 ).decode("utf-8") | |
118 # Find all release notes from un-relnotes-flagged commits. | |
119 for entry in sorted( | |
120 subprocess.check_output( | |
121 [ | |
122 "hg", | |
123 "log", | |
124 "-r", | |
125 r'%s::%s - merge() - grep("\n\.\. (%s)::")' | |
126 % (args.startrev[0], args.stoprev[0], "|".join(rnsections)), | |
127 "-T", | |
128 r"{desc|firstline}\n", | |
129 ] | |
130 ) | |
131 .decode("utf-8") | |
132 .splitlines() | |
133 ): | |
134 desc = entry.replace("`", "'") | |
135 | |
136 score = 0 | |
137 for rule, val in rules.items(): | |
138 if re.search(rule, desc): | |
139 score += val | |
140 | |
141 desc = desc.replace("(issue", "(Bts:issue") | |
142 | |
143 if score >= cutoff: | |
144 commits.append(desc) | |
145 # Group unflagged notes. | |
146 groups = {} | |
147 bcs = [] | |
148 apis = [] | |
149 | |
150 for d in commits: | |
151 if "(BC)" in d: | |
152 bcs.append(d) | |
153 if "(API)" in d: | |
154 apis.append(d) | |
155 for rule, g in groupings: | |
156 if re.match(rule, d): | |
157 groups.setdefault(g, []).append(d) | |
158 break | |
159 else: | |
160 groups.setdefault("unsorted", []).append(d) | |
161 print(fromext) | |
162 # print legacy release notes sections | |
163 for g in sorted(groups): | |
164 print("\n=== %s ===" % g) | |
165 for d in sorted(groups[g]): | |
166 print(" * %s" % d) | |
167 | |
168 print("\n=== BC ===\n") | |
169 | |
170 for d in sorted(bcs): | |
171 print(" * %s" % d) | |
172 | |
173 print("\n=== API Changes ===\n") | |
174 | |
175 for d in sorted(apis): | |
176 print(" * %s" % d) | |
177 | |
178 if __name__ == "__main__": | |
179 main() |