diff options
author | Nick Brassel <nick@tzarc.org> | 2021-11-19 04:05:08 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-18 17:05:08 +0000 |
commit | b9148eb1bd00d4cf2fded05d18add3e5198a706d (patch) | |
tree | 946cd868a5d22771402cbf0e1f6c1a7e0101cbe1 /lib/python | |
parent | 88eaf78628056d722734392be56e0624dae779e3 (diff) | |
download | qmk_firmware-b9148eb1bd00d4cf2fded05d18add3e5198a706d.tar.gz qmk_firmware-b9148eb1bd00d4cf2fded05d18add3e5198a706d.zip |
[cli] Export list of `develop` PRs to be merged into `master` (#13944)
* Add developer-only command for exporting the list of PRs associated with a merge to `develop`.
* qmk pytest
* Imports.
* Remove dependencies from requirements file, manually handle.
* Reduce complexity, qmk generate-api taking too long so relying on CI
Diffstat (limited to 'lib/python')
-rw-r--r-- | lib/python/qmk/cli/__init__.py | 1 | ||||
-rwxr-xr-x | lib/python/qmk/cli/generate/develop_pr_list.py | 119 |
2 files changed, 120 insertions, 0 deletions
diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index edf351d62..c51eece95 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py | |||
@@ -47,6 +47,7 @@ subcommands = [ | |||
47 | 'qmk.cli.generate.api', | 47 | 'qmk.cli.generate.api', |
48 | 'qmk.cli.generate.compilation_database', | 48 | 'qmk.cli.generate.compilation_database', |
49 | 'qmk.cli.generate.config_h', | 49 | 'qmk.cli.generate.config_h', |
50 | 'qmk.cli.generate.develop_pr_list', | ||
50 | 'qmk.cli.generate.dfu_header', | 51 | 'qmk.cli.generate.dfu_header', |
51 | 'qmk.cli.generate.docs', | 52 | 'qmk.cli.generate.docs', |
52 | 'qmk.cli.generate.info_json', | 53 | 'qmk.cli.generate.info_json', |
diff --git a/lib/python/qmk/cli/generate/develop_pr_list.py b/lib/python/qmk/cli/generate/develop_pr_list.py new file mode 100755 index 000000000..de4eaa7d8 --- /dev/null +++ b/lib/python/qmk/cli/generate/develop_pr_list.py | |||
@@ -0,0 +1,119 @@ | |||
1 | """Export the initial list of PRs associated with a `develop` merge to `master`. | ||
2 | """ | ||
3 | import os | ||
4 | import re | ||
5 | from pathlib import Path | ||
6 | from subprocess import DEVNULL | ||
7 | |||
8 | from milc import cli | ||
9 | |||
10 | cache_timeout = 7 * 86400 | ||
11 | fix_expr = re.compile(r'fix', flags=re.IGNORECASE) | ||
12 | clean1_expr = re.compile(r'\[(develop|keyboard|keymap|core|cli|bug|docs|feature)\]', flags=re.IGNORECASE) | ||
13 | clean2_expr = re.compile(r'^(develop|keyboard|keymap|core|cli|bug|docs|feature):', flags=re.IGNORECASE) | ||
14 | |||
15 | |||
16 | def _get_pr_info(cache, gh, pr_num): | ||
17 | pull = cache.get(f'pull:{pr_num}') | ||
18 | if pull is None: | ||
19 | print(f'Retrieving info for PR #{pr_num}') | ||
20 | pull = gh.pulls.get(owner='qmk', repo='qmk_firmware', pull_number=pr_num) | ||
21 | cache.set(f'pull:{pr_num}', pull, cache_timeout) | ||
22 | return pull | ||
23 | |||
24 | |||
25 | def _try_open_cache(cli): | ||
26 | # These dependencies are manually handled because people complain. Fun. | ||
27 | try: | ||
28 | from sqlite_cache.sqlite_cache import SqliteCache | ||
29 | except ImportError: | ||
30 | return None | ||
31 | |||
32 | cache_loc = Path(cli.config_file).parent | ||
33 | return SqliteCache(cache_loc) | ||
34 | |||
35 | |||
36 | def _get_github(): | ||
37 | try: | ||
38 | from ghapi.all import GhApi | ||
39 | except ImportError: | ||
40 | return None | ||
41 | |||
42 | return GhApi() | ||
43 | |||
44 | |||
45 | @cli.argument('-f', '--from-ref', default='0.11.0', help='Git revision/tag/reference/branch to begin search') | ||
46 | @cli.argument('-b', '--branch', default='upstream/develop', help='Git branch to iterate (default: "upstream/develop")') | ||
47 | @cli.subcommand('Creates the develop PR list.', hidden=False if cli.config.user.developer else True) | ||
48 | def generate_develop_pr_list(cli): | ||
49 | """Retrieves information from GitHub regarding the list of PRs associated | ||
50 | with a merge of `develop` branch into `master`. | ||
51 | |||
52 | Requires environment variable GITHUB_TOKEN to be set. | ||
53 | """ | ||
54 | |||
55 | if 'GITHUB_TOKEN' not in os.environ or os.environ['GITHUB_TOKEN'] == '': | ||
56 | cli.log.error('Environment variable "GITHUB_TOKEN" is not set.') | ||
57 | return 1 | ||
58 | |||
59 | cache = _try_open_cache(cli) | ||
60 | gh = _get_github() | ||
61 | |||
62 | git_args = ['git', 'rev-list', '--oneline', '--no-merges', '--reverse', f'{cli.args.from_ref}...{cli.args.branch}', '^upstream/master'] | ||
63 | commit_list = cli.run(git_args, capture_output=True, stdin=DEVNULL) | ||
64 | |||
65 | if cache is None or gh is None: | ||
66 | cli.log.error('Missing one or more dependent python packages: "ghapi", "python-sqlite-cache"') | ||
67 | return 1 | ||
68 | |||
69 | pr_list_bugs = [] | ||
70 | pr_list_dependencies = [] | ||
71 | pr_list_core = [] | ||
72 | pr_list_keyboards = [] | ||
73 | pr_list_keyboard_fixes = [] | ||
74 | pr_list_cli = [] | ||
75 | pr_list_others = [] | ||
76 | |||
77 | def _categorise_commit(commit_info): | ||
78 | def fix_or_normal(info, fixes_collection, normal_collection): | ||
79 | if "bug" in info['pr_labels'] or fix_expr.search(info['title']): | ||
80 | fixes_collection.append(info) | ||
81 | else: | ||
82 | normal_collection.append(info) | ||
83 | |||
84 | if "dependencies" in commit_info['pr_labels']: | ||
85 | fix_or_normal(commit_info, pr_list_bugs, pr_list_dependencies) | ||
86 | elif "core" in commit_info['pr_labels']: | ||
87 | fix_or_normal(commit_info, pr_list_bugs, pr_list_core) | ||
88 | elif "keyboard" in commit_info['pr_labels'] or "keymap" in commit_info['pr_labels'] or "via" in commit_info['pr_labels']: | ||
89 | fix_or_normal(commit_info, pr_list_keyboard_fixes, pr_list_keyboards) | ||
90 | elif "cli" in commit_info['pr_labels']: | ||
91 | fix_or_normal(commit_info, pr_list_bugs, pr_list_cli) | ||
92 | else: | ||
93 | fix_or_normal(commit_info, pr_list_bugs, pr_list_others) | ||
94 | |||
95 | git_expr = re.compile(r'^(?P<hash>[a-f0-9]+) (?P<title>.*) \(#(?P<pr>[0-9]+)\)$') | ||
96 | for line in commit_list.stdout.split('\n'): | ||
97 | match = git_expr.search(line) | ||
98 | if match: | ||
99 | pr_info = _get_pr_info(cache, gh, match.group("pr")) | ||
100 | commit_info = {'hash': match.group("hash"), 'title': match.group("title"), 'pr_num': int(match.group("pr")), 'pr_labels': [label.name for label in pr_info.labels.items]} | ||
101 | _categorise_commit(commit_info) | ||
102 | |||
103 | def _dump_commit_list(name, collection): | ||
104 | if len(collection) == 0: | ||
105 | return | ||
106 | print("") | ||
107 | print(f"{name}:") | ||
108 | for commit in sorted(collection, key=lambda x: x['pr_num']): | ||
109 | title = clean1_expr.sub('', clean2_expr.sub('', commit['title'])).strip() | ||
110 | pr_num = commit['pr_num'] | ||
111 | print(f'* {title} ([#{pr_num}](https://github.com/qmk/qmk_firmware/pull/{pr_num}))') | ||
112 | |||
113 | _dump_commit_list("Bugs", pr_list_bugs) | ||
114 | _dump_commit_list("Core", pr_list_core) | ||
115 | _dump_commit_list("CLI", pr_list_cli) | ||
116 | _dump_commit_list("Submodule updates", pr_list_dependencies) | ||
117 | _dump_commit_list("Keyboards", pr_list_keyboards) | ||
118 | _dump_commit_list("Keyboard fixes", pr_list_keyboard_fixes) | ||
119 | _dump_commit_list("Others", pr_list_others) | ||