Mercurial > hg
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/relnotes Wed Jul 25 13:28:36 2018 -0400 @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +"""Generate release notes from our commit log. + +This uses the relnotes extension directives when they're available, +and falls back to our old pre-relnotes logic that used to live in the +release-tools repo. +""" +import argparse +import re +import subprocess + +# Regenerate this list with +# hg export 'grep("\.\. [a-z]+::")' | grep '^\.\.' | \ +# sed 's/.. //;s/::.*//' | sort -u +rnsections = ["api", "bc", "container", "feature", "fix", "note", "perf"] + +rules = { + # keep + r"\(issue": 100, + r"\(BC\)": 100, + r"\(API\)": 100, + # core commands, bump up + r"(commit|files|log|pull|push|patch|status|tag|summary)(|s|es):": 20, + r"(annotate|alias|branch|bookmark|clone|graft|import|verify).*:": 20, + # extensions, bump up + r"(mq|shelve|rebase):": 20, + # newsy + r": deprecate": 20, + r"(option|feature|command|support)": 10, + # bug-like? + r"(fix|don't break|improve)": 7, + # boring stuff, bump down + r"^contrib": -5, + r"debug": -5, + r"help": -5, + r"(doc|bundle2|obsolete|obsmarker|rpm|setup|debug\S+:)": -15, + r"(check-code|check-commit|import-checker)": -20, + # cleanups and refactoring + r"(cleanup|whitespace|nesting|indent|spelling|comment)": -20, + r"(typo|hint|note|style:|correct doc)": -20, + r"_": -10, + r"(argument|absolute_import|attribute|assignment|mutable)": -15, + r"(unused|useless|unnecessary|duplicate|deprecated|scope|True|False)": -10, + r"(redundant|pointless|confusing|uninitialized|meaningless|dead)": -10, + r": (drop|remove|inherit|rename|simplify|naming|inline)": -10, + r"(docstring|document .* method)": -20, + r"(factor|extract|prepare|split|replace| import)": -20, + r": add.*(function|method|implementation|test|example)": -10, + r": (move|extract) .* (to|into|from)": -20, + r": implement ": -5, + r": use .* implementation": -20, + r"\S\S\S+\.\S\S\S\S+": -5, + r": use .* instead of": -20, + r"__": -5, + # dumb keywords + r"\S+/\S+:": -10, + r"\S+\.\S+:": -10, + # drop + r"^i18n-": -50, + r"^i18n:.*(hint|comment)": -50, + r"perf:": -50, + r"check-code:": -50, + r"Added.*for changeset": -50, + r"tests?:": -50, + r"test-": -50, + r"add.* tests": -50, + r"^_": -50, +} + +cutoff = 10 +commits = [] + +groupings = [ + (r"util|parsers|repo|ctx|context|revlog|filelog|alias|cmdutil", "core"), + (r"revset|templater|ui|dirstate|hook|i18n|transaction|wire", "core"), + (r"color|pager", "core"), + (r"hgweb|paper|coal|gitweb", "hgweb"), + (r"pull|push|revert|resolve|annotate|bookmark|branch|clone", "commands"), + (r"commands|commit|config|files|graft|import|log|merge|patch", "commands"), + (r"phases|status|summary|amend|tag|help|verify", "commands"), + (r"rebase|mq|convert|eol|histedit|largefiles", "extensions"), + (r"shelve|unshelve", "extensions"), +] + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument( + "startrev", + metavar="REV", + type=str, + nargs=1, + help=( + "Starting revision for the release notes. This revision " + "won't be included, but later revisions will." + ), + ) + ap.add_argument( + "--stoprev", + metavar="REV", + type=str, + default="@", + nargs=1, + help=( + "Stop revision for release notes. This revision will be included," + " but no later revisions will. This revision needs to be " + "a descendant of startrev." + ), + ) + args = ap.parse_args() + fromext = subprocess.check_output( + [ + "hg", + "releasenotes", + "-r", + "%s::%s" % (args.startrev[0], args.stoprev[0]), + ] + ).decode("utf-8") + # Find all release notes from un-relnotes-flagged commits. + for entry in sorted( + subprocess.check_output( + [ + "hg", + "log", + "-r", + r'%s::%s - merge() - grep("\n\.\. (%s)::")' + % (args.startrev[0], args.stoprev[0], "|".join(rnsections)), + "-T", + r"{desc|firstline}\n", + ] + ) + .decode("utf-8") + .splitlines() + ): + desc = entry.replace("`", "'") + + score = 0 + for rule, val in rules.items(): + if re.search(rule, desc): + score += val + + desc = desc.replace("(issue", "(Bts:issue") + + if score >= cutoff: + commits.append(desc) + # Group unflagged notes. + groups = {} + bcs = [] + apis = [] + + for d in commits: + if "(BC)" in d: + bcs.append(d) + if "(API)" in d: + apis.append(d) + for rule, g in groupings: + if re.match(rule, d): + groups.setdefault(g, []).append(d) + break + else: + groups.setdefault("unsorted", []).append(d) + print(fromext) + # print legacy release notes sections + for g in sorted(groups): + print("\n=== %s ===" % g) + for d in sorted(groups[g]): + print(" * %s" % d) + + print("\n=== BC ===\n") + + for d in sorted(bcs): + print(" * %s" % d) + + print("\n=== API Changes ===\n") + + for d in sorted(apis): + print(" * %s" % d) + +if __name__ == "__main__": + main()