stable-rt-tools 2.0__tar.gz → 2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/PKG-INFO +1 -1
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/about.py +1 -1
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/commit-tmpl.txt +2 -0
- stable_rt_tools-2.1/stable_rt_tools/srt_announce.py +377 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools.egg-info/PKG-INFO +1 -1
- stable_rt_tools-2.0/stable_rt_tools/srt_announce.py +0 -255
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/README.rst +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/pyproject.toml +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/setup.cfg +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/__init__.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/announce-srt-rc.txt +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/announce-srt.txt +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/py.typed +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt_commit.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt_config.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt_create.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt_prep.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt_push.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt_quilt.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt_setup.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt_sign.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt_status.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt_tag.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt_upload.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt_util.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt_util_context.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools/srt_util_tag.py +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools.egg-info/SOURCES.txt +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools.egg-info/dependency_links.txt +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools.egg-info/entry_points.txt +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools.egg-info/requires.txt +0 -0
- {stable_rt_tools-2.0 → stable_rt_tools-2.1}/stable_rt_tools.egg-info/top_level.txt +0 -0
|
@@ -7,6 +7,8 @@ I'm pleased to announce the {new_version} stable release.
|
|
|
7
7
|
Changes since {old_version}:
|
|
8
8
|
|
|
9
9
|
{series_changes}
|
|
10
|
+
The delta patch against {old_version} is appended below.
|
|
11
|
+
|
|
10
12
|
You can get this release via the git tree at:
|
|
11
13
|
|
|
12
14
|
https://git.kernel.org/pub/scm/linux/kernel/git/rt/linux-stable-rt.git {release_tag}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# srt - stable rt tooling
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) Siemens AG, 2018
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be
|
|
15
|
+
# included in all copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
import argparse
|
|
27
|
+
import os
|
|
28
|
+
import re
|
|
29
|
+
import sys
|
|
30
|
+
from datetime import date, timedelta
|
|
31
|
+
from email.utils import make_msgid
|
|
32
|
+
from subprocess import DEVNULL, PIPE, run
|
|
33
|
+
from time import gmtime, strftime
|
|
34
|
+
|
|
35
|
+
from stable_rt_tools.srt_util import (
|
|
36
|
+
Config,
|
|
37
|
+
SubParsers,
|
|
38
|
+
check_context,
|
|
39
|
+
cmd,
|
|
40
|
+
confirm,
|
|
41
|
+
get_config,
|
|
42
|
+
get_gpg_fingerprint,
|
|
43
|
+
get_remote_branch_name,
|
|
44
|
+
get_suffix,
|
|
45
|
+
get_template_path,
|
|
46
|
+
)
|
|
47
|
+
from stable_rt_tools.srt_util_context import SrtContext
|
|
48
|
+
|
|
49
|
+
REVIEW_TEXT = """\
|
|
50
|
+
{tag} stable review patch.
|
|
51
|
+
If anyone has any objections, please let me know.
|
|
52
|
+
|
|
53
|
+
-----------
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def add_review_text(path: str, tag: str) -> None:
|
|
59
|
+
"""Put the review note at the top of the body of a patch mail."""
|
|
60
|
+
with open(path) as f:
|
|
61
|
+
headers, sep, body = f.read().partition('\n\n')
|
|
62
|
+
with open(path, 'w') as f:
|
|
63
|
+
f.write(headers + sep + REVIEW_TEXT.format(tag=tag) + body)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
MERGE_NOTE = """\
|
|
67
|
+
The resolution of the merge conflicts, as shown by
|
|
68
|
+
git show --remerge-diff: the conflicts git left in the merge on the
|
|
69
|
+
- lines, the merge as committed on the + lines.
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def resolved_merges(ref: str) -> list[str]:
|
|
75
|
+
"""The merges of the release candidate with conflict resolutions.
|
|
76
|
+
|
|
77
|
+
git format-patch leaves out merges, but the conflict resolution of
|
|
78
|
+
a stable merge is a change to the series which needs review.
|
|
79
|
+
"""
|
|
80
|
+
merges = cmd(['git', 'rev-list', '--reverse', '--first-parent',
|
|
81
|
+
'--merges', f'{ref}..']).split()
|
|
82
|
+
# a clean merge has no remerge diff
|
|
83
|
+
return [m for m in merges
|
|
84
|
+
if cmd(['git', 'show', '--remerge-diff', '--format=', m])]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def mail_subject(text: str, prefix: str) -> str:
|
|
88
|
+
"""Number the subject of a mail with prefix, e.g. 'PATCH RT 1/3'."""
|
|
89
|
+
return re.sub(r'^Subject: \[[^]\n]*\]', f'Subject: [{prefix}]', text,
|
|
90
|
+
count=1, flags=re.MULTILINE)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def merge_mail(merge: str) -> tuple[str, str]:
|
|
94
|
+
"""The file name slug and the mail of a merge, like git format-patch."""
|
|
95
|
+
mail = cmd(['git', 'show', '--format=email', '--remerge-diff',
|
|
96
|
+
'--stat', '--patch', merge]) + '\n'
|
|
97
|
+
head, sep, rest = mail.partition('\n---\n')
|
|
98
|
+
subject = cmd(['git', 'show', '-s', '--format=%s', merge])
|
|
99
|
+
slug = re.sub(r'[^A-Za-z0-9_]+', '-', subject).strip('-')[:64]
|
|
100
|
+
return slug, head + sep + MERGE_NOTE + rest
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def series_summary(ref: str, commits: list[str]) -> str:
|
|
104
|
+
"""The shortlog and diffstat of the series for the cover letter.
|
|
105
|
+
|
|
106
|
+
git format-patch neither lists the merges nor gets the diffstat
|
|
107
|
+
right across a merge: it would count the stable update in.
|
|
108
|
+
"""
|
|
109
|
+
authors: dict[str, list[str]] = {}
|
|
110
|
+
for c in commits:
|
|
111
|
+
author, subject = cmd(['git', 'show', '-s', '--format=%an%x00%s',
|
|
112
|
+
c]).split('\0', 1)
|
|
113
|
+
authors.setdefault(author, []).append(subject)
|
|
114
|
+
text = ''
|
|
115
|
+
for author, subjects in authors.items():
|
|
116
|
+
text += f'{author} ({len(subjects)}):\n'
|
|
117
|
+
text += ''.join(f' {s}\n' for s in subjects) + '\n'
|
|
118
|
+
if cmd(['git', 'rev-list', '--first-parent', '--merges', f'{ref}..']):
|
|
119
|
+
# a merge with conflicts has a mail of its own
|
|
120
|
+
return text + 'The diffstat is left out, it would contain the ' \
|
|
121
|
+
'stable update.\n'
|
|
122
|
+
return text + ' ' + cmd(['git', 'diff', '--stat', f'{ref}..']) + '\n'
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def rewrite_cover(cover: str, prefix: str, total: int, summary: str) -> None:
|
|
126
|
+
with open(cover) as f:
|
|
127
|
+
text = f.read()
|
|
128
|
+
text = mail_subject(text, f'{prefix} 0/{total}')
|
|
129
|
+
blurb = '*** BLURB HERE ***\n'
|
|
130
|
+
head, sep, rest = text.partition(blurb)
|
|
131
|
+
signature = rest[rest.rfind('\n-- \n'):] if '\n-- \n' in rest else ''
|
|
132
|
+
with open(cover, 'w') as f:
|
|
133
|
+
f.write(head + sep + '\n' + summary + signature)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def add_merge_mails(mails: list[str], ref: str, merges: list[str],
|
|
137
|
+
prefix: str) -> list[str]:
|
|
138
|
+
"""Put the merge mails into the series in commit order.
|
|
139
|
+
|
|
140
|
+
The patches of git format-patch are numbered again, the series has
|
|
141
|
+
more mails now, and the cover letter lists all of them.
|
|
142
|
+
"""
|
|
143
|
+
order = cmd(['git', 'rev-list', '--reverse', '--first-parent',
|
|
144
|
+
f'{ref}..']).split()
|
|
145
|
+
|
|
146
|
+
cover = mails[0]
|
|
147
|
+
by_commit: dict[str, tuple[str, str]] = {}
|
|
148
|
+
for path in mails[1:]:
|
|
149
|
+
with open(path) as f:
|
|
150
|
+
text = f.read()
|
|
151
|
+
# format-patch starts a mail with 'From <commit> <date>'
|
|
152
|
+
by_commit[text.split()[1]] = (os.path.basename(path)[5:], text)
|
|
153
|
+
os.remove(path)
|
|
154
|
+
for m in merges:
|
|
155
|
+
slug, text = merge_mail(m)
|
|
156
|
+
by_commit[m] = (slug + '.patch', text)
|
|
157
|
+
|
|
158
|
+
commits = [c for c in order if c in by_commit]
|
|
159
|
+
series = [by_commit[c] for c in commits]
|
|
160
|
+
total = len(series)
|
|
161
|
+
directory = os.path.dirname(cover)
|
|
162
|
+
result = [cover]
|
|
163
|
+
for n, (name, text) in enumerate(series, start=1):
|
|
164
|
+
path = os.path.join(directory, f'{n:04d}-{name}')
|
|
165
|
+
with open(path, 'w') as f:
|
|
166
|
+
f.write(mail_subject(text, f'{prefix} {n}/{total}'))
|
|
167
|
+
result.append(path)
|
|
168
|
+
|
|
169
|
+
rewrite_cover(cover, prefix, total, series_summary(ref, commits))
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def create_rc_patches(config: Config, ctx: SrtContext) -> None:
|
|
174
|
+
"""Mail everything added to the series since the last release.
|
|
175
|
+
|
|
176
|
+
These are the commits on the series' own line (--first-parent):
|
|
177
|
+
patches before and after the stable merge, the merge itself if it
|
|
178
|
+
has a conflict resolution, the release commit. The commits of the
|
|
179
|
+
stable releases merged in are reviewed elsewhere.
|
|
180
|
+
"""
|
|
181
|
+
ref = str(ctx.old_tag)
|
|
182
|
+
prefix = f'PATCH {get_suffix(config).upper()}'
|
|
183
|
+
mails = cmd(['git', 'format-patch', '--first-parent', f'{ref}..',
|
|
184
|
+
'-o', ctx.new_dir_mails, '--subject-prefix', prefix,
|
|
185
|
+
'--cover-letter']).splitlines()
|
|
186
|
+
mails = add_merge_mails(mails, ref, resolved_merges(ref), prefix)
|
|
187
|
+
for mail in mails:
|
|
188
|
+
if not mail.endswith('/0000-cover-letter.patch'):
|
|
189
|
+
add_review_text(mail, str(ctx.new_tag))
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def cover_letter_replacements(config: Config,
|
|
193
|
+
ctx: SrtContext) -> dict[str, object]:
|
|
194
|
+
r = {
|
|
195
|
+
"mail_to": config['MAIL_TO'],
|
|
196
|
+
"major": ctx.new_tag.major,
|
|
197
|
+
"minor": ctx.new_tag.minor,
|
|
198
|
+
"patch": ctx.new_tag.patch,
|
|
199
|
+
"new_version": ctx.new_short_tag,
|
|
200
|
+
"old_version": ctx.old_short_tag,
|
|
201
|
+
"prj_dir": config['PRJ_DIR'],
|
|
202
|
+
"message_id": make_msgid(),
|
|
203
|
+
"sender": config['SENDER'],
|
|
204
|
+
"name": config['NAME'],
|
|
205
|
+
# new_tag_rt: the name templates know from before 2.0
|
|
206
|
+
"new_tag_rt": ctx.new_tag.number,
|
|
207
|
+
"new_tag_number": ctx.new_tag.number,
|
|
208
|
+
"gpg_key_fingerprint": get_gpg_fingerprint(config),
|
|
209
|
+
}
|
|
210
|
+
if ctx.is_rc:
|
|
211
|
+
r["new_tag_rc"] = ctx.new_tag.rc
|
|
212
|
+
return r
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def write_rc_cover_letter(config: Config, ctx: SrtContext) -> None:
|
|
216
|
+
with open(ctx.new_dir_mails + '/0000-cover-letter.patch') as f:
|
|
217
|
+
coverletter = f.read()
|
|
218
|
+
|
|
219
|
+
coverletter = coverletter.replace('*** SUBJECT HERE ***',
|
|
220
|
+
'Linux ' + str(ctx.new_tag))
|
|
221
|
+
|
|
222
|
+
rc_text = ''
|
|
223
|
+
with open(get_rc_templ_path(config)) as f:
|
|
224
|
+
rc_text = f.read()
|
|
225
|
+
|
|
226
|
+
r = cover_letter_replacements(config, ctx)
|
|
227
|
+
|
|
228
|
+
today = date.today()
|
|
229
|
+
rc_date = today + timedelta(weeks=1)
|
|
230
|
+
r["release_date"] = rc_date
|
|
231
|
+
|
|
232
|
+
rc_text = rc_text.format(**r)
|
|
233
|
+
|
|
234
|
+
coverletter = coverletter.replace('*** BLURB HERE ***',
|
|
235
|
+
rc_text)
|
|
236
|
+
|
|
237
|
+
with open(ctx.new_dir_mails + '/0000-cover-letter.patch', 'w') as f:
|
|
238
|
+
f.write(coverletter)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def send_rc_patches(config: Config, ctx: SrtContext,
|
|
242
|
+
args: argparse.Namespace) -> None:
|
|
243
|
+
gmd = ['git', 'send-email', '--confirm=never']
|
|
244
|
+
if args.suppress_cc:
|
|
245
|
+
gmd += ['--suppress-cc=all']
|
|
246
|
+
# an argument list, no shell: quotes would end up in the address
|
|
247
|
+
gmd += [f'--to={t.strip()}' for t in config['MAIL_TO'].split(',')]
|
|
248
|
+
gmd += [ctx.new_dir_mails]
|
|
249
|
+
|
|
250
|
+
print('Dry run')
|
|
251
|
+
gmdd = gmd.copy()
|
|
252
|
+
gmdd.insert(2, '--dry-run')
|
|
253
|
+
print(cmd(gmdd, verbose=True))
|
|
254
|
+
if confirm('OK to send patches?'):
|
|
255
|
+
cmd(gmd)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def announce_rc(config: Config, ctx: SrtContext,
|
|
259
|
+
args: argparse.Namespace) -> None:
|
|
260
|
+
create_rc_patches(config, ctx)
|
|
261
|
+
write_rc_cover_letter(config, ctx)
|
|
262
|
+
send_rc_patches(config, ctx, args)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def get_announce_tmpl_path(config: Config) -> str:
|
|
266
|
+
return get_template_path(config, 'ANNOUNCE', 'announce-srt.txt')
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def get_rc_templ_path(config: Config) -> str:
|
|
270
|
+
return get_template_path(config, 'RC_TEXT', 'announce-srt-rc.txt')
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def strip_trailers(msg: str) -> str:
|
|
274
|
+
"""Remove the trailer block (Signed-off-by: ...) of a commit message."""
|
|
275
|
+
paragraphs = msg.rstrip().split('\n\n')
|
|
276
|
+
last = paragraphs[-1].splitlines()
|
|
277
|
+
if len(paragraphs) > 1 and all(
|
|
278
|
+
re.match(r'^[A-Za-z][A-Za-z0-9-]*: ', line) for line in last):
|
|
279
|
+
paragraphs = paragraphs[:-1]
|
|
280
|
+
return '\n\n'.join(paragraphs)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def delta_base(ctx: SrtContext) -> str:
|
|
284
|
+
"""Where the delta of a release starts: the old release with the new
|
|
285
|
+
stable release merged in, as git merges it.
|
|
286
|
+
|
|
287
|
+
The delta is then the series' own change, also across a stable
|
|
288
|
+
update: patches before and after the merge, conflict resolutions
|
|
289
|
+
(as conflict markers which are removed) and the release commit, but
|
|
290
|
+
not the stable update.
|
|
291
|
+
"""
|
|
292
|
+
old, base = str(ctx.old_tag), ctx.new_tag.base
|
|
293
|
+
if ctx.old_tag.base == base:
|
|
294
|
+
return old
|
|
295
|
+
p = run(['git', 'merge-tree', '--write-tree', old, base],
|
|
296
|
+
stdout=PIPE, stderr=DEVNULL, encoding='utf-8')
|
|
297
|
+
# 1: the merge has conflicts, the tree contains the conflict markers
|
|
298
|
+
if p.returncode not in (0, 1):
|
|
299
|
+
sys.exit(f'git merge-tree {old} {base} failed')
|
|
300
|
+
return p.stdout.splitlines()[0]
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def print_changes(ctx: SrtContext) -> None:
|
|
304
|
+
"""The shortlog, diffstat and delta of the release."""
|
|
305
|
+
new = str(ctx.new_tag)
|
|
306
|
+
base = delta_base(ctx)
|
|
307
|
+
print(cmd(['git', '--no-pager', 'shortlog', '--first-parent',
|
|
308
|
+
f'{ctx.old_tag}..{new}']))
|
|
309
|
+
print('---')
|
|
310
|
+
print(cmd(['git', '--no-pager', 'diff', '--stat', base, new]))
|
|
311
|
+
print('---')
|
|
312
|
+
print(cmd(['git', '--no-pager', 'diff', base, new]))
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def quilt_announcement(ctx: SrtContext) -> str:
|
|
316
|
+
"""The announcement of the queue commit, srt quilt queue wrote it."""
|
|
317
|
+
msg = cmd(['git', 'show', '-s', '--format=%B',
|
|
318
|
+
f'{ctx.new_tag}-patches^{{commit}}'])
|
|
319
|
+
subject, _, body = msg.partition('\n')
|
|
320
|
+
if not subject.startswith('[ANNOUNCE]'):
|
|
321
|
+
sys.exit(f'{ctx.new_tag}-patches: the commit message is not an '
|
|
322
|
+
'announcement, it starts with ' + repr(subject))
|
|
323
|
+
return strip_trailers(body.lstrip('\n'))
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def announce(config: Config, ctx: SrtContext,
|
|
327
|
+
args: argparse.Namespace) -> None:
|
|
328
|
+
from stable_rt_tools.srt_util import is_quilt_workflow
|
|
329
|
+
if ctx.is_rc:
|
|
330
|
+
announce_rc(config, ctx, args)
|
|
331
|
+
return
|
|
332
|
+
|
|
333
|
+
# rfc2822.html
|
|
334
|
+
# 3.3. Date and Time Specification
|
|
335
|
+
timestamp = strftime('%a, %d %b %Y %H:%M:%S -0000', gmtime())
|
|
336
|
+
|
|
337
|
+
if is_quilt_workflow(config):
|
|
338
|
+
print(f"From: {config.get('SENDER', '')}")
|
|
339
|
+
print(f"Subject: [ANNOUNCE] {ctx.new_tag}")
|
|
340
|
+
print(f"Date: {timestamp}")
|
|
341
|
+
print(f"Message-ID: {make_msgid()}")
|
|
342
|
+
print(f"To: {config.get('MAIL_TO', '')}")
|
|
343
|
+
print()
|
|
344
|
+
print(quilt_announcement(ctx))
|
|
345
|
+
print()
|
|
346
|
+
print('---')
|
|
347
|
+
print_changes(ctx)
|
|
348
|
+
return
|
|
349
|
+
|
|
350
|
+
stable_rt_text = ''
|
|
351
|
+
with open(get_announce_tmpl_path(config)) as f:
|
|
352
|
+
stable_rt_text = f.read()
|
|
353
|
+
|
|
354
|
+
r = cover_letter_replacements(config, ctx)
|
|
355
|
+
|
|
356
|
+
r["date"] = timestamp
|
|
357
|
+
r["branch_name"] = get_remote_branch_name()
|
|
358
|
+
r["branch_head"] = cmd(['git', 'rev-parse', 'HEAD'])
|
|
359
|
+
|
|
360
|
+
print(stable_rt_text.format(**r))
|
|
361
|
+
print_changes(ctx)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def add_argparser(parser: SubParsers) -> argparse.ArgumentParser:
|
|
365
|
+
prs = parser.add_parser('announce')
|
|
366
|
+
prs.add_argument('OLD_TAG', nargs='?')
|
|
367
|
+
prs.add_argument('NEW_TAG', nargs='?')
|
|
368
|
+
prs.add_argument('--suppress-cc', '-s', action="store_true", default=False,
|
|
369
|
+
help='Don''t auto-cc anyone (for testing)')
|
|
370
|
+
return prs
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def execute(args: argparse.Namespace) -> None:
|
|
374
|
+
ctx = SrtContext(args)
|
|
375
|
+
check_context(ctx)
|
|
376
|
+
|
|
377
|
+
announce(get_config(), ctx, args)
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
#
|
|
3
|
-
# srt - stable rt tooling
|
|
4
|
-
#
|
|
5
|
-
# Copyright (c) Siemens AG, 2018
|
|
6
|
-
#
|
|
7
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
-
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
-
# in the Software without restriction, including without limitation the rights
|
|
10
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
-
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
-
# furnished to do so, subject to the following conditions:
|
|
13
|
-
#
|
|
14
|
-
# The above copyright notice and this permission notice shall be
|
|
15
|
-
# included in all copies or substantial portions of the Software.
|
|
16
|
-
#
|
|
17
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
-
# SOFTWARE
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
import argparse
|
|
27
|
-
import re
|
|
28
|
-
from datetime import date, timedelta
|
|
29
|
-
from email.utils import make_msgid
|
|
30
|
-
from time import gmtime, strftime
|
|
31
|
-
|
|
32
|
-
from stable_rt_tools.srt_util import (
|
|
33
|
-
Config,
|
|
34
|
-
SubParsers,
|
|
35
|
-
check_context,
|
|
36
|
-
cmd,
|
|
37
|
-
confirm,
|
|
38
|
-
get_config,
|
|
39
|
-
get_gpg_fingerprint,
|
|
40
|
-
get_remote_branch_name,
|
|
41
|
-
get_suffix,
|
|
42
|
-
get_template_path,
|
|
43
|
-
)
|
|
44
|
-
from stable_rt_tools.srt_util_context import SrtContext
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def find_starting_ref(ctx: SrtContext) -> str:
|
|
48
|
-
# find starting point. first check for stable update only
|
|
49
|
-
ref = cmd(['git', 'log', '--pretty=format:%H', '--merges', '-n', '1'])
|
|
50
|
-
|
|
51
|
-
# if patches have been added, we find the old tag and should start there
|
|
52
|
-
# and not the last merge
|
|
53
|
-
commits = cmd(['git', 'log', '--simplify-by-decoration',
|
|
54
|
-
'--oneline', str(ref) + '..'])
|
|
55
|
-
if ' '.join(commits.split('\n')).find(str(ctx.old_short_tag)) != -1:
|
|
56
|
-
ref = str(ctx.old_tag)
|
|
57
|
-
|
|
58
|
-
return ref
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
REVIEW_TEXT = """\
|
|
62
|
-
{tag} stable review patch.
|
|
63
|
-
If anyone has any objections, please let me know.
|
|
64
|
-
|
|
65
|
-
-----------
|
|
66
|
-
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def add_review_text(path: str, tag: str) -> None:
|
|
71
|
-
"""Put the review note at the top of the body of a patch mail."""
|
|
72
|
-
with open(path) as f:
|
|
73
|
-
headers, sep, body = f.read().partition('\n\n')
|
|
74
|
-
with open(path, 'w') as f:
|
|
75
|
-
f.write(headers + sep + REVIEW_TEXT.format(tag=tag) + body)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def create_rc_patches(config: Config, ctx: SrtContext) -> None:
|
|
79
|
-
ref = find_starting_ref(ctx)
|
|
80
|
-
mails = cmd(['git', 'format-patch', f'{ref}..',
|
|
81
|
-
'-o', ctx.new_dir_mails, '--subject-prefix',
|
|
82
|
-
f'PATCH {get_suffix(config).upper()}',
|
|
83
|
-
'--cover-letter']).splitlines()
|
|
84
|
-
for mail in mails:
|
|
85
|
-
if not mail.endswith('/0000-cover-letter.patch'):
|
|
86
|
-
add_review_text(mail, str(ctx.new_tag))
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def cover_letter_replacements(config: Config,
|
|
90
|
-
ctx: SrtContext) -> dict[str, object]:
|
|
91
|
-
r = {
|
|
92
|
-
"mail_to": config['MAIL_TO'],
|
|
93
|
-
"major": ctx.new_tag.major,
|
|
94
|
-
"minor": ctx.new_tag.minor,
|
|
95
|
-
"patch": ctx.new_tag.patch,
|
|
96
|
-
"new_version": ctx.new_short_tag,
|
|
97
|
-
"old_version": ctx.old_short_tag,
|
|
98
|
-
"prj_dir": config['PRJ_DIR'],
|
|
99
|
-
"message_id": make_msgid(),
|
|
100
|
-
"sender": config['SENDER'],
|
|
101
|
-
"name": config['NAME'],
|
|
102
|
-
# new_tag_rt: the name templates know from before 2.0
|
|
103
|
-
"new_tag_rt": ctx.new_tag.number,
|
|
104
|
-
"new_tag_number": ctx.new_tag.number,
|
|
105
|
-
"gpg_key_fingerprint": get_gpg_fingerprint(config),
|
|
106
|
-
}
|
|
107
|
-
if ctx.is_rc:
|
|
108
|
-
r["new_tag_rc"] = ctx.new_tag.rc
|
|
109
|
-
return r
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def write_rc_cover_letter(config: Config, ctx: SrtContext) -> None:
|
|
113
|
-
with open(ctx.new_dir_mails + '/0000-cover-letter.patch') as f:
|
|
114
|
-
coverletter = f.read()
|
|
115
|
-
|
|
116
|
-
coverletter = coverletter.replace('*** SUBJECT HERE ***',
|
|
117
|
-
'Linux ' + str(ctx.new_tag))
|
|
118
|
-
|
|
119
|
-
rc_text = ''
|
|
120
|
-
with open(get_rc_templ_path(config)) as f:
|
|
121
|
-
rc_text = f.read()
|
|
122
|
-
|
|
123
|
-
r = cover_letter_replacements(config, ctx)
|
|
124
|
-
|
|
125
|
-
today = date.today()
|
|
126
|
-
rc_date = today + timedelta(weeks=1)
|
|
127
|
-
r["release_date"] = rc_date
|
|
128
|
-
|
|
129
|
-
rc_text = rc_text.format(**r)
|
|
130
|
-
|
|
131
|
-
coverletter = coverletter.replace('*** BLURB HERE ***',
|
|
132
|
-
rc_text)
|
|
133
|
-
|
|
134
|
-
with open(ctx.new_dir_mails + '/0000-cover-letter.patch', 'w') as f:
|
|
135
|
-
f.write(coverletter)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def send_rc_patches(config: Config, ctx: SrtContext,
|
|
139
|
-
args: argparse.Namespace) -> None:
|
|
140
|
-
gmd = ['git', 'send-email', '--confirm=never']
|
|
141
|
-
if args.suppress_cc:
|
|
142
|
-
gmd += ['--suppress-cc=all']
|
|
143
|
-
# an argument list, no shell: quotes would end up in the address
|
|
144
|
-
gmd += [f'--to={t.strip()}' for t in config['MAIL_TO'].split(',')]
|
|
145
|
-
gmd += [ctx.new_dir_mails]
|
|
146
|
-
|
|
147
|
-
print('Dry run')
|
|
148
|
-
gmdd = gmd.copy()
|
|
149
|
-
gmdd.insert(2, '--dry-run')
|
|
150
|
-
print(cmd(gmdd, verbose=True))
|
|
151
|
-
if confirm('OK to send patches?'):
|
|
152
|
-
cmd(gmd)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def announce_rc(config: Config, ctx: SrtContext,
|
|
156
|
-
args: argparse.Namespace) -> None:
|
|
157
|
-
create_rc_patches(config, ctx)
|
|
158
|
-
write_rc_cover_letter(config, ctx)
|
|
159
|
-
send_rc_patches(config, ctx, args)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def get_announce_tmpl_path(config: Config) -> str:
|
|
163
|
-
return get_template_path(config, 'ANNOUNCE', 'announce-srt.txt')
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def get_rc_templ_path(config: Config) -> str:
|
|
167
|
-
return get_template_path(config, 'RC_TEXT', 'announce-srt-rc.txt')
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def strip_trailers(msg: str) -> str:
|
|
171
|
-
"""Remove the trailer block (Signed-off-by: ...) of a commit message."""
|
|
172
|
-
paragraphs = msg.rstrip().split('\n\n')
|
|
173
|
-
last = paragraphs[-1].splitlines()
|
|
174
|
-
if len(paragraphs) > 1 and all(
|
|
175
|
-
re.match(r'^[A-Za-z][A-Za-z0-9-]*: ', line) for line in last):
|
|
176
|
-
paragraphs = paragraphs[:-1]
|
|
177
|
-
return '\n\n'.join(paragraphs)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def announce(config: Config, ctx: SrtContext,
|
|
181
|
-
args: argparse.Namespace) -> None:
|
|
182
|
-
from stable_rt_tools.srt_util import is_quilt_workflow
|
|
183
|
-
if ctx.is_rc:
|
|
184
|
-
announce_rc(config, ctx, args)
|
|
185
|
-
return
|
|
186
|
-
|
|
187
|
-
if is_quilt_workflow(config):
|
|
188
|
-
import subprocess
|
|
189
|
-
tag = str(ctx.new_tag) + '-patches'
|
|
190
|
-
try:
|
|
191
|
-
msg = subprocess.check_output([
|
|
192
|
-
'git', 'show', '-s', '--format=%B', tag
|
|
193
|
-
], encoding='utf-8').strip()
|
|
194
|
-
announce_start = msg.find('[ANNOUNCE]')
|
|
195
|
-
if announce_start != -1:
|
|
196
|
-
lines = msg[announce_start:].splitlines()
|
|
197
|
-
msg = '\n'.join(lines[1:]).lstrip('\n')
|
|
198
|
-
else:
|
|
199
|
-
pass
|
|
200
|
-
msg = strip_trailers(msg)
|
|
201
|
-
timestamp = strftime('%a, %d %b %Y %H:%M:%S -0000', gmtime())
|
|
202
|
-
sender = config.get('SENDER', '')
|
|
203
|
-
mail_to = config.get('MAIL_TO', '')
|
|
204
|
-
print(f"From: {sender}")
|
|
205
|
-
print(f"To: {mail_to}")
|
|
206
|
-
print(f"Subject: [ANNOUNCE] {ctx.new_tag}")
|
|
207
|
-
print(f"Date: {timestamp}\n")
|
|
208
|
-
print(msg)
|
|
209
|
-
except Exception as e:
|
|
210
|
-
print(f"Error extracting announcement from tag {tag}: {e}")
|
|
211
|
-
return
|
|
212
|
-
|
|
213
|
-
# rfc2822.html
|
|
214
|
-
# 3.3. Date and Time Specification
|
|
215
|
-
timestamp = strftime('%a, %d %b %Y %H:%M:%S -0000', gmtime())
|
|
216
|
-
|
|
217
|
-
stable_rt_text = ''
|
|
218
|
-
with open(get_announce_tmpl_path(config)) as f:
|
|
219
|
-
stable_rt_text = f.read()
|
|
220
|
-
|
|
221
|
-
r = cover_letter_replacements(config, ctx)
|
|
222
|
-
|
|
223
|
-
r["date"] = timestamp
|
|
224
|
-
r["branch_name"] = get_remote_branch_name()
|
|
225
|
-
r["branch_head"] = cmd(['git', 'rev-parse', 'HEAD'])
|
|
226
|
-
|
|
227
|
-
print(stable_rt_text.format(**r))
|
|
228
|
-
|
|
229
|
-
ref = find_starting_ref(ctx)
|
|
230
|
-
print(cmd(['git', '--no-pager', 'shortlog', f'{ref}..{ctx.new_tag}']))
|
|
231
|
-
|
|
232
|
-
print('---')
|
|
233
|
-
|
|
234
|
-
print(cmd(['git', '--no-pager', 'diff', '--stat',
|
|
235
|
-
f'{ref}..{ctx.new_tag}']))
|
|
236
|
-
|
|
237
|
-
print('---')
|
|
238
|
-
|
|
239
|
-
print(cmd(['git', '--no-pager', 'diff', f'{ref}..{ctx.new_tag}']))
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def add_argparser(parser: SubParsers) -> argparse.ArgumentParser:
|
|
243
|
-
prs = parser.add_parser('announce')
|
|
244
|
-
prs.add_argument('OLD_TAG', nargs='?')
|
|
245
|
-
prs.add_argument('NEW_TAG', nargs='?')
|
|
246
|
-
prs.add_argument('--suppress-cc', '-s', action="store_true", default=False,
|
|
247
|
-
help='Don''t auto-cc anyone (for testing)')
|
|
248
|
-
return prs
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def execute(args: argparse.Namespace) -> None:
|
|
252
|
-
ctx = SrtContext(args)
|
|
253
|
-
check_context(ctx)
|
|
254
|
-
|
|
255
|
-
announce(get_config(), ctx, args)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|