summaryrefslogtreecommitdiff
path: root/tools/dist
diff options
context:
space:
mode:
Diffstat (limited to 'tools/dist')
-rw-r--r--tools/dist/README.backport36
-rwxr-xr-xtools/dist/backport.pl15
-rw-r--r--tools/dist/backport/status.py10
-rw-r--r--tools/dist/backport_tests.py22
-rw-r--r--tools/dist/backport_tests_data/backport_logsummary_colon.dump522
-rwxr-xr-xtools/dist/changes-to-html.py88
-rwxr-xr-xtools/dist/create-minor-release-branch.py328
-rwxr-xr-xtools/dist/edit-N-log-messages94
-rwxr-xr-xtools/dist/nightly.sh2
-rwxr-xr-xtools/dist/release.py555
-rw-r--r--tools/dist/security/parser.py13
-rw-r--r--tools/dist/templates/download.ezt6
-rw-r--r--tools/dist/templates/rc-news.ezt4
-rw-r--r--tools/dist/templates/rc-release-ann.ezt22
-rw-r--r--tools/dist/templates/stable-news.ezt6
-rw-r--r--tools/dist/templates/stable-release-ann.ezt22
16 files changed, 1573 insertions, 172 deletions
diff --git a/tools/dist/README.backport b/tools/dist/README.backport
index 0b9c66e..c3fdc68 100644
--- a/tools/dist/README.backport
+++ b/tools/dist/README.backport
@@ -7,20 +7,23 @@ F1. Auto-merge bot; the nightly svn-role commits.
F2. Conflicts detector bot; the svn-backport-conflicts-1.9.x buildbot task.
-And two interactive functions, described later.
+And two interactive functions¹:
+
+F3. Reviewing STATUS nominations and casting votes.
+
+F4. Adding new entries to STATUS.
The scripts are:
backport.pl:
- oldest script, implements both [F1] and [F2], plus two interactive
- functions¹. As of March 2015, used in production by svn-role and
- by svn-backport-conflicts-1.9.x.
+ oldest script, implements [F1], [F2], and [F3]. As of Feb 2018, used in
+ production by svn-role (running on svn-qavm3) and by svn-backport-conflicts-1.9.x
+ (a buildbot job).
nominate.pl:
- Symlink to backport.pl. Implements one of the two interactive features.
- Not used by bots.
+ Symlink to backport.pl. Implements [F4]. (The script inspects its argv[0].)
backport_tests_pl.py:
Regression tests for backport.pl.
@@ -39,9 +42,11 @@ backport/*.py:
detect-conflicting-backports.py:
Implementation of [F2] using backport.py.
+ Not currently used in production.
merge-approved-backports.py:
Implementation of [F1] using backport.py.
+ Not currently used in production.
backport_tests_py.py:
Regression tests for detect-conflicting-backports.py and merge-approved-backports.py
@@ -52,13 +57,28 @@ backport_tests.py:
svntest framework (../../subversion/tests/cmdline/svntest/), which is
written in Python 2.
-backport*.dump:
+ Note that backport_tests.py and backport/*.py are written in different
+ languages, so they never 'import' each other. backport_tests.py invokes
+ detect-conflicting-backports.py, merge-approved-backports.py, and
+ backport.pl in the same manner: through subprocess.check_call().
+
+backport_tests_data/backport*.dump:
Expected output files for backport_tests.py; see the BackportTest
- decorator.
+ decorator in backport_tests.py.
All scripts can be run with '--help' to display their usage messages.
+backport.pl is considered deprecated since backport.py is better architected
+and is written in a language that many more active developers are comfortable
+with. The unattended jobs [F1] and [F2] should be converted to using
+backport.py whenever someone gets around to do the legwork. The interactive
+versions [F3] and [F4] are still in active use, however, so the physical
+backport.pl script should be kept around until Python versions of these are
+available.
+
+
+TODO: document that "Notes: ... --accept=foo ..." is parsed, see backport_tests.py #3.
¹ For backport.pl's interactive features, see:
diff --git a/tools/dist/backport.pl b/tools/dist/backport.pl
index 67f8313..df3da22 100755
--- a/tools/dist/backport.pl
+++ b/tools/dist/backport.pl
@@ -9,11 +9,11 @@ use v5.10.0; # needed for $^V
# experimental and "subject to change" in v5.18 (see perl5180delta). Every
# use of it now triggers a warning.
#
-# As of Perl v5.24.1, the semantics of given/when provided by Perl are
+# As of Perl v5.26.1, the semantics of given/when provided by Perl are
# compatible with those expected by the script, so disable the warning for
# those Perls. But don't try to disable the the warning category on Perls
# that don't know that category, since that breaks compilation.
-no if (v5.17.0 le $^V and $^V le v5.24.1),
+no if (v5.17.0 le $^V and $^V le v5.26.1),
warnings => 'experimental::smartmatch';
# Licensed to the Apache Software Foundation (ASF) under one
@@ -791,7 +791,7 @@ sub vote {
# Add to state votes that aren't '+0' or 'edit'
$state->{$_->{digest}}++ for grep
- +{ qw/-1 t -0 t +1 t/ }->{$_->{vote}},
+ +($_->{approval} or $_->{vote} =~ /^(-1|-0|[+]1)$/),
@votesarray;
}
}
@@ -1027,7 +1027,7 @@ sub handle_entry {
# the "next PROMPT;" is; there's a "last;" at the end of the loop body.
PROMPT: while (1) {
say "";
- say "\n>>> $entry{header_start}:";
+ say "\n\e\x5b32m>>> $entry{header_start}:\e\x5b0m";
say join ", ", map { "r$_" } @{$entry{revisions}} if @{$entry{revisions}};
say "$BRANCHES/$entry{branch}" if $entry{branch};
say "--accept=$entry{accept}" if $entry{accept};
@@ -1196,7 +1196,7 @@ sub backport_main {
given ($lines[0]) {
# Section header
when (/^[A-Z].*:$/i) {
- say "\n\n=== $lines[0]" unless $YES;
+ say "\n\n\e\x5b33m\e\x5b1m=== $lines[0]\e\x5b0m" unless $YES;
$in_approved = $lines[0] =~ /^Approved changes/;
}
# Comment
@@ -1279,7 +1279,7 @@ sub nominate_main {
# Open the file in line-mode (not paragraph-mode).
my @STATUS;
tie @STATUS, "Tie::File", $STATUS, recsep => "\n";
- my ($index) = grep { $STATUS[$_] =~ /^Veto/ } (0..$#STATUS);
+ my ($index) = grep { $STATUS[$_] =~ /^Veto|^Approved/ } (0..$#STATUS);
die "Couldn't find where to add an entry" unless $index;
# Add an empty line if needed.
@@ -1297,7 +1297,8 @@ sub nominate_main {
# Done!
system "$SVN diff -- $STATUS";
if (prompt "Commit this nomination? ") {
- system "$SVN commit -m '* STATUS: Nominate r$revnums[0].' -- $STATUS";
+ my $header = join ', ', map "r$_", @revnums;
+ system "$SVN commit -m '* STATUS: Nominate $header.' -- $STATUS";
exit $?;
}
elsif (!$had_local_mods or prompt "Revert STATUS (destroying local mods)? ") {
diff --git a/tools/dist/backport/status.py b/tools/dist/backport/status.py
index 727939d..7ec378d 100644
--- a/tools/dist/backport/status.py
+++ b/tools/dist/backport/status.py
@@ -194,10 +194,10 @@ class StatusFile:
try:
entry = StatusEntry(para_text, status_file=self)
kind = Kind.nomination
- except ParseException:
+ except ParseException as e:
kind = Kind.unknown
- logger.warning("Failed to parse entry {!r} in {!r}".format(
- para_text, status_fp))
+ logger.warning("Failed to parse entry {!r} in {!r}: {}".format(
+ para_text, status_fp, e))
else:
kind = Kind.preamble
@@ -379,9 +379,11 @@ class StatusEntry:
raise ParseException("Entry found with neither branch nor revisions")
# Parse the logsummary.
- while lines and not self._is_subheader(lines[0]):
+ while True:
self.logsummary.append(lines[0])
lines = lines[1:]
+ if (not lines) or self._is_subheader(lines[0]):
+ break
# Parse votes.
if "Votes:" in lines:
diff --git a/tools/dist/backport_tests.py b/tools/dist/backport_tests.py
index ec483a7..27df294 100644
--- a/tools/dist/backport_tests.py
+++ b/tools/dist/backport_tests.py
@@ -53,6 +53,12 @@ import sys
@contextlib.contextmanager
def chdir(dir):
+ """This is a context manager that saves the current working directory's
+ pathname. Upon entry it chdir's to the argument DIR; upon exit it chdir's
+ back to the saved pathname.
+
+ The current working directory is restored using os.chdir(), not os.fchdir().
+ """
try:
saved_dir = os.getcwd()
os.chdir(dir)
@@ -660,6 +666,21 @@ def backport_unicode_entry(sbox):
# Run it.
run_backport(sbox)
+#----------------------------------------------------------------------
+@BackportTest('76cee987-25c9-4d6c-ad40-000000000013')
+def backport_logsummary_colon(sbox):
+ "a logsummary that looks like a header"
+
+ # r6: nominate r4
+ approved_entries = [
+ make_entry([4], logsummary="HTTPv2: Add comments."),
+ ]
+ sbox.simple_append(STATUS, serialize_STATUS(approved_entries))
+ sbox.simple_commit(message='Nominate r4')
+
+ # Run it.
+ run_backport(sbox)
+
#----------------------------------------------------------------------
@@ -680,6 +701,7 @@ test_list = [ None,
backport_otherproject_change,
backport_STATUS_mods,
backport_unicode_entry,
+ backport_logsummary_colon,
# When adding a new test, include the test number in the last
# 6 bytes of the UUID, in decimal.
]
diff --git a/tools/dist/backport_tests_data/backport_logsummary_colon.dump b/tools/dist/backport_tests_data/backport_logsummary_colon.dump
new file mode 100644
index 0000000..db9d6ce
--- /dev/null
+++ b/tools/dist/backport_tests_data/backport_logsummary_colon.dump
@@ -0,0 +1,522 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 76cee987-25c9-4d6c-ad40-000000000013
+
+Revision-number: 0
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 83
+Content-length: 83
+
+K 10
+svn:author
+V 7
+jrandom
+K 7
+svn:log
+V 27
+Log message for revision 1.
+PROPS-END
+
+Node-path: A
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: A/B
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: A/B/E
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: A/B/E/alpha
+Node-kind: file
+Node-action: add
+Text-content-md5: d1fa4a3ced98961674a441930a51f2d3
+Text-content-sha1: b347d1da69df9a6a70433ceeaa0d46c8483e8c03
+Prop-content-length: 10
+Text-content-length: 26
+Content-length: 36
+
+PROPS-END
+This is the file 'alpha'.
+
+
+Node-path: A/B/E/beta
+Node-kind: file
+Node-action: add
+Text-content-md5: 67c756078f24f946f6ec2d00d02f50e1
+Text-content-sha1: d001710ac8e622c6d1fe59b1e265a3908acdd2a3
+Prop-content-length: 10
+Text-content-length: 25
+Content-length: 35
+
+PROPS-END
+This is the file 'beta'.
+
+
+Node-path: A/B/F
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: A/B/lambda
+Node-kind: file
+Node-action: add
+Text-content-md5: 911c7a8d869b8c1e566f57da54d889c6
+Text-content-sha1: 784a9298366863da2b65ebf82b4e1123755a2421
+Prop-content-length: 10
+Text-content-length: 27
+Content-length: 37
+
+PROPS-END
+This is the file 'lambda'.
+
+
+Node-path: A/C
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: A/D
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: A/D/G
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: A/D/G/pi
+Node-kind: file
+Node-action: add
+Text-content-md5: adddfc3e6b605b5f90ceeab11b4e8ab6
+Text-content-sha1: 411e258dc14b42701fdc29b75f653e93f8686415
+Prop-content-length: 10
+Text-content-length: 23
+Content-length: 33
+
+PROPS-END
+This is the file 'pi'.
+
+
+Node-path: A/D/G/rho
+Node-kind: file
+Node-action: add
+Text-content-md5: 82f2211cf4ab22e3555fc7b835fbc604
+Text-content-sha1: 56388a031dffbf9df7c32e1f299b1d5d7ef60881
+Prop-content-length: 10
+Text-content-length: 24
+Content-length: 34
+
+PROPS-END
+This is the file 'rho'.
+
+
+Node-path: A/D/G/tau
+Node-kind: file
+Node-action: add
+Text-content-md5: 9936e2716e469bb686deb98c280ead58
+Text-content-sha1: 62e8c07d56bee94ea4577e80414fa8805aaf0175
+Prop-content-length: 10
+Text-content-length: 24
+Content-length: 34
+
+PROPS-END
+This is the file 'tau'.
+
+
+Node-path: A/D/H
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: A/D/H/chi
+Node-kind: file
+Node-action: add
+Text-content-md5: 8f5ebad6d1f7775c2682e54417cbe4d3
+Text-content-sha1: abeac1bf62099ab66b44779198dc19f40e3244f4
+Prop-content-length: 10
+Text-content-length: 24
+Content-length: 34
+
+PROPS-END
+This is the file 'chi'.
+
+
+Node-path: A/D/H/omega
+Node-kind: file
+Node-action: add
+Text-content-md5: fe4ec8bdd3d2056db4f55b474a10fadc
+Text-content-sha1: c06e671bf15a6af55086176a0931d3b5034c82e6
+Prop-content-length: 10
+Text-content-length: 26
+Content-length: 36
+
+PROPS-END
+This is the file 'omega'.
+
+
+Node-path: A/D/H/psi
+Node-kind: file
+Node-action: add
+Text-content-md5: e81f8f68ba50e749c200cb3c9ce5d2b1
+Text-content-sha1: 9c438bde39e8ccbbd366df2638e3cb6700950204
+Prop-content-length: 10
+Text-content-length: 24
+Content-length: 34
+
+PROPS-END
+This is the file 'psi'.
+
+
+Node-path: A/D/gamma
+Node-kind: file
+Node-action: add
+Text-content-md5: 412138bd677d64cd1c32fafbffe6245d
+Text-content-sha1: 74b75d7f2e1a0292f17d5a57c570bd89783f5d1c
+Prop-content-length: 10
+Text-content-length: 26
+Content-length: 36
+
+PROPS-END
+This is the file 'gamma'.
+
+
+Node-path: A/mu
+Node-kind: file
+Node-action: add
+Text-content-md5: baf78ae06a2d5b7d9554c5f1280d3fa8
+Text-content-sha1: b4d00c56351d1a752e24d839d41a362d8da4a4c7
+Prop-content-length: 10
+Text-content-length: 23
+Content-length: 33
+
+PROPS-END
+This is the file 'mu'.
+
+
+Node-path: iota
+Node-kind: file
+Node-action: add
+Text-content-md5: 2d18c5e57e84c5b8a5e9a6e13fa394dc
+Text-content-sha1: 2c0aa9014a0cd07f01795a333d82485ef6d083e2
+Prop-content-length: 10
+Text-content-length: 25
+Content-length: 35
+
+PROPS-END
+This is the file 'iota'.
+
+
+Revision-number: 2
+Prop-content-length: 68
+Content-length: 68
+
+K 10
+svn:author
+V 7
+jrandom
+K 7
+svn:log
+V 12
+Create trunk
+PROPS-END
+
+Node-path: subversion
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: subversion/branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: subversion/tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: subversion/trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: subversion/trunk/A
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: A
+
+
+Node-path: subversion/trunk/iota
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: iota
+Text-copy-source-md5: 2d18c5e57e84c5b8a5e9a6e13fa394dc
+Text-copy-source-sha1: 2c0aa9014a0cd07f01795a333d82485ef6d083e2
+
+
+Node-path: A
+Node-action: delete
+
+
+Node-path: iota
+Node-action: delete
+
+
+Revision-number: 3
+Prop-content-length: 87
+Content-length: 87
+
+K 10
+svn:author
+V 7
+jrandom
+K 7
+svn:log
+V 31
+Create branch, with STATUS file
+PROPS-END
+
+Node-path: branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: subversion/trunk
+
+
+Node-path: branch/STATUS
+Node-kind: file
+Node-action: add
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Prop-content-length: 10
+Text-content-length: 0
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 4
+Prop-content-length: 68
+Content-length: 68
+
+K 10
+svn:author
+V 7
+jrandom
+K 7
+svn:log
+V 12
+First change
+PROPS-END
+
+Node-path: subversion/trunk/iota
+Node-kind: file
+Node-action: change
+Text-content-md5: 67f471c2ecc2c9e561d122d6e6b0f847
+Text-content-sha1: 750accb6e7f880a1d05ce725c19eb60183bb4b26
+Text-content-length: 38
+Content-length: 38
+
+This is the file 'iota'.
+First change
+
+
+Revision-number: 5
+Prop-content-length: 69
+Content-length: 69
+
+K 10
+svn:author
+V 7
+jrandom
+K 7
+svn:log
+V 13
+Second change
+PROPS-END
+
+Node-path: subversion/trunk/A/mu
+Node-kind: file
+Node-action: change
+Text-content-md5: eab751301b4e650c83324dfef4aad667
+Text-content-sha1: ab36cad564c7c50dec5ac1eb0bf879cf4e3a5f99
+Text-content-length: 37
+Content-length: 37
+
+This is the file 'mu'.
+Second change
+
+
+Revision-number: 6
+Prop-content-length: 67
+Content-length: 67
+
+K 10
+svn:author
+V 7
+jrandom
+K 7
+svn:log
+V 11
+Nominate r4
+PROPS-END
+
+Node-path: branch/STATUS
+Node-kind: file
+Node-action: change
+Text-content-md5: 21cf72d59d9e71a49260e3cf31f78b76
+Text-content-sha1: 9938559a403d5a18099aace3db6719bdb8940449
+Text-content-length: 245
+Content-length: 245
+
+Status of 1.8.x:
+
+Candidate changes:
+==================
+
+Random new subheading:
+======================
+
+Veto-blocked changes:
+=====================
+
+Approved changes:
+=================
+
+ * r4
+ HTTPv2: Add comments.
+ Votes:
+ +1: jrandom
+
+
+
+Revision-number: 7
+Prop-content-length: 135
+Content-length: 135
+
+K 10
+svn:author
+V 6
+daniel
+K 7
+svn:log
+V 80
+Merge r4 from trunk:
+
+ * r4
+ HTTPv2: Add comments.
+ Votes:
+ +1: jrandom
+
+PROPS-END
+
+Node-path: branch
+Node-kind: dir
+Node-action: change
+Prop-content-length: 54
+Content-length: 54
+
+K 13
+svn:mergeinfo
+V 19
+/subversion/trunk:4
+PROPS-END
+
+
+Node-path: branch/STATUS
+Node-kind: file
+Node-action: change
+Text-content-md5: 6f71fec92afeaa5c1ebe02349f548ca9
+Text-content-sha1: eece02003d9c51610249e3fdd0d4e191e02ba3b7
+Text-content-length: 185
+Content-length: 185
+
+Status of 1.8.x:
+
+Candidate changes:
+==================
+
+Random new subheading:
+======================
+
+Veto-blocked changes:
+=====================
+
+Approved changes:
+=================
+
+
+Node-path: branch/iota
+Node-kind: file
+Node-action: change
+Text-content-md5: 67f471c2ecc2c9e561d122d6e6b0f847
+Text-content-sha1: 750accb6e7f880a1d05ce725c19eb60183bb4b26
+Text-content-length: 38
+Content-length: 38
+
+This is the file 'iota'.
+First change
+
+
diff --git a/tools/dist/changes-to-html.py b/tools/dist/changes-to-html.py
new file mode 100755
index 0000000..73ec19f
--- /dev/null
+++ b/tools/dist/changes-to-html.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+# python: coding=utf-8
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import re
+import sys
+
+
+HEADER = """<html>
+<head>
+<title>CHANGES</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+</head>
+<body>
+<pre>
+"""
+
+FOOTER = """</pre>
+</body>
+</html>
+"""
+
+
+url_rx = re.compile(r'(?:^|(?<=\W))(https?://[\w\d./?&=]+)')
+url_sub = r'<a href="\1">\1</a>'
+
+issue_rx = re.compile(r'(?<=\W)(#(\d+))')
+issue_sub = r'<a href="https://issues.apache.org/jira/browse/SVN-\2">\1</a>'
+
+revision_rx = re.compile(r'(?:^|(?<=\W))(r\d+)')
+revision_sub = r'<a href="https://svn.apache.org/\1">\1</a>'
+
+branchtag_rx = re.compile(r'(?<=\W)(/(?:branches|tags)/[\w\d.]+)')
+branchtag_sub = r'<a href="https://svn.apache.org/repos/asf/subversion\1">\1</a>'
+
+
+def generate(stream):
+ sys.stdout.write(HEADER)
+
+ beginning = True
+ for n in stream.readlines():
+ # Skip initial comments and empty lines in the CHANGES file.
+ n = n.rstrip()
+ if beginning and (not n or n.startswith('#')):
+ continue
+ beginning = False
+
+ n = url_rx.sub(url_sub, n)
+ n = issue_rx.sub(issue_sub, n)
+ n = revision_rx.sub(revision_sub, n)
+ n = branchtag_rx.sub(branchtag_sub, n)
+
+ sys.stdout.write(n)
+ sys.stdout.write('\n')
+
+ sys.stdout.write(FOOTER)
+
+
+def generate_from(filenme):
+ with open(filenme, 'rt') as stream:
+ return generate(stream)
+
+
+def main():
+ if len(sys.argv) < 2 or sys.argv[1] == '-':
+ return generate(sys.stdin)
+ else:
+ return generate_from(sys.argv[1])
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/dist/create-minor-release-branch.py b/tools/dist/create-minor-release-branch.py
new file mode 100755
index 0000000..a68fa23
--- /dev/null
+++ b/tools/dist/create-minor-release-branch.py
@@ -0,0 +1,328 @@
+#!/usr/bin/env python
+# python: coding=utf-8
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+
+# About this script:
+# This script is intended to automate steps in creating a new Subversion
+# minor release.
+
+import os
+import re
+import sys
+import logging
+import subprocess
+import argparse # standard in Python 2.7
+
+from release import Version
+
+
+# Some constants
+repos = 'https://svn.apache.org/repos/asf/subversion'
+secure_repos = 'https://svn.apache.org/repos/asf/subversion'
+buildbot_repos = 'https://svn.apache.org/repos/infra/infrastructure/buildbot/aegis/buildmaster'
+
+# Parameters
+dry_run = False
+
+# Local working copies
+base_dir = None # set by main()
+
+def get_trunk_wc_path(path=None):
+ trunk_wc_path = os.path.join(base_dir, 'svn-trunk')
+ if path is None: return trunk_wc_path
+ return os.path.join(trunk_wc_path, path)
+def get_branch_wc_path(ver, path=None):
+ branch_wc_path = os.path.join(base_dir, ver.branch + '.x')
+ if path is None: return branch_wc_path
+ return os.path.join(branch_wc_path, path)
+def get_buildbot_wc_path(path=None):
+ buildbot_wc_path = os.path.join(base_dir, 'svn-buildmaster')
+ if path is None: return buildbot_wc_path
+ return os.path.join(buildbot_wc_path, path)
+
+def get_trunk_url():
+ return secure_repos + '/trunk'
+def get_branch_url(ver):
+ return secure_repos + '/branches/' + ver.branch + '.x'
+def get_tag_url(ver):
+ return secure_repos + '/tags/' + ver.base
+def get_buildbot_url():
+ return buildbot_repos
+
+#----------------------------------------------------------------------
+# Utility functions
+
+def run(cmd, dry_run=False):
+ print('+ ' + ' '.join(cmd))
+ if not dry_run:
+ stdout = subprocess.check_output(cmd)
+ print(stdout)
+ else:
+ print(' ## dry-run; not executed')
+
+def run_svn(cmd, dry_run=False):
+ run(['svn'] + cmd, dry_run)
+
+def svn_commit(cmd):
+ run_svn(['commit'] + cmd, dry_run=dry_run)
+
+def svn_copy_branch(src, dst, message):
+ args = ['copy', src, dst, '-m', message]
+ run_svn(args, dry_run=dry_run)
+
+def svn_checkout(url, wc, *args):
+ args = ['checkout', url, wc] + list(args)
+ run_svn(args)
+
+#----------------------------------------------------------------------
+def edit_file(path, pattern, replacement):
+ print("Editing '%s'" % (path,))
+ print(" pattern='%s'" % (pattern,))
+ print(" replace='%s'" % (replacement,))
+ old_text = open(path, 'r').read()
+ new_text = re.sub(pattern, replacement, old_text)
+ assert new_text != old_text
+ open(path, 'w').write(new_text)
+
+def edit_changes_file(path, newtext):
+ """Insert NEWTEXT in the 'CHANGES' file found at PATH,
+ just before the first line that starts with 'Version '.
+ """
+ print("Prepending to '%s'" % (path,))
+ print(" text='%s'" % (newtext,))
+ lines = open(path, 'r').readlines()
+ for i, line in enumerate(lines):
+ if line.startswith('Version '):
+ with open(path, 'w') as newfile:
+ newfile.writelines(lines[:i])
+ newfile.write(newtext)
+ newfile.writelines(lines[i:])
+ break
+
+#----------------------------------------------------------------------
+def make_release_branch(ver, revnum):
+ svn_copy_branch(get_trunk_url() + '@' + (str(revnum) if revnum else ''),
+ get_branch_url(ver),
+ 'Create the ' + ver.branch + '.x release branch.')
+
+#----------------------------------------------------------------------
+def update_minor_ver_in_trunk(ver, revnum):
+ """Change the minor version in trunk to the next (future) minor version.
+ """
+ trunk_wc = get_trunk_wc_path()
+ trunk_url = get_trunk_url()
+ svn_checkout(trunk_url + '@' + (str(revnum) if revnum else ''),
+ trunk_wc)
+
+ prev_ver = Version('1.%d.0' % (ver.minor - 1,))
+ next_ver = Version('1.%d.0' % (ver.minor + 1,))
+ relpaths = []
+
+ relpath = 'subversion/include/svn_version.h'
+ relpaths.append(relpath)
+ edit_file(get_trunk_wc_path(relpath),
+ r'(#define SVN_VER_MINOR *)%s' % (ver.minor,),
+ r'\g<1>%s' % (next_ver.minor,))
+
+ relpath = 'subversion/tests/cmdline/svntest/main.py'
+ relpaths.append(relpath)
+ edit_file(get_trunk_wc_path(relpath),
+ r'(SVN_VER_MINOR = )%s' % (ver.minor,),
+ r'\g<1>%s' % (next_ver.minor,))
+
+ relpath = 'subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeResources.java'
+ relpaths.append(relpath)
+ try:
+ # since r1817921 (just after branching 1.10)
+ edit_file(get_trunk_wc_path(relpath),
+ r'SVN_VER_MINOR = %s;' % (ver.minor,),
+ r'SVN_VER_MINOR = %s;' % (next_ver.minor,))
+ except:
+ # before r1817921: two separate places
+ edit_file(get_trunk_wc_path(relpath),
+ r'version.isAtLeast\(1, %s, 0\)' % (ver.minor,),
+ r'version.isAtLeast\(1, %s, 0\)' % (next_ver.minor,))
+ edit_file(get_trunk_wc_path(relpath),
+ r'1.%s.0, but' % (ver.minor,),
+ r'1.%s.0, but' % (next_ver.minor,))
+
+ relpath = 'CHANGES'
+ relpaths.append(relpath)
+ # insert at beginning of CHANGES file
+ edit_changes_file(get_trunk_wc_path(relpath),
+ 'Version ' + next_ver.base + '\n'
+ + '(?? ??? 20XX, from /branches/' + next_ver.branch + '.x)\n'
+ + get_tag_url(next_ver) + '\n'
+ + '\n')
+
+ log_msg = '''\
+Increment the trunk version number to %s, and introduce a new CHANGES
+section, following the creation of the %s.x release branch.
+
+* subversion/include/svn_version.h,
+ subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeResources.java,
+ subversion/tests/cmdline/svntest/main.py
+ (SVN_VER_MINOR): Increment to %s.
+
+* CHANGES: New section for %s.0.
+''' % (next_ver.branch, ver.branch, next_ver.minor, next_ver.branch)
+ commit_paths = [get_trunk_wc_path(p) for p in relpaths]
+ svn_commit(commit_paths + ['-m', log_msg])
+
+#----------------------------------------------------------------------
+def create_status_file_on_branch(ver):
+ branch_wc = get_branch_wc_path(ver)
+ branch_url = get_branch_url(ver)
+ svn_checkout(branch_url, branch_wc, '--depth=immediates')
+
+ status_local_path = os.path.join(branch_wc, 'STATUS')
+ text='''\
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * THIS RELEASE STREAM IS OPEN FOR STABILIZATION. *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+This file tracks the status of releases in the %s.x line.
+
+See http://subversion.apache.org/docs/community-guide/releasing.html#release-stabilization
+for details on how release lines and voting work, what kinds of bugs can
+delay a release, etc.
+
+Status of %s:
+
+Candidate changes:
+==================
+
+
+Veto-blocked changes:
+=====================
+
+
+Approved changes:
+=================
+''' % (ver.branch, ver.base)
+ open(status_local_path, 'wx').write(text)
+ run_svn(['add', status_local_path])
+ svn_commit([status_local_path,
+ '-m', '* branches/' + ver.branch + '.x/STATUS: New file.'])
+
+#----------------------------------------------------------------------
+def update_backport_bot(ver):
+ print("""MANUAL STEP: Fork & edit & pull-request on GitHub:
+https://github.com/apache/infrastructure-puppet/blob/deployment/modules/svnqavm_pvm_asf/manifests/init.pp
+"Add new %s.x branch to list of backport branches"
+""" % (ver.branch,))
+ print("""Someone needs to run the 'svn checkout' manually.
+The exact checkout command is documented in machines/svn-qavm2/notes.txt
+in the private repository (need to use a trunk client and the svn-master.a.o
+hostname).
+""")
+
+#----------------------------------------------------------------------
+def update_buildbot_config(ver):
+ """Add the new branch to the list of branches monitored by the buildbot
+ master.
+ """
+ buildbot_wc = get_buildbot_wc_path()
+ buildbot_url = get_buildbot_url()
+ svn_checkout(buildbot_url, buildbot_wc)
+
+ prev_ver = Version('1.%d.0' % (ver.minor - 1,))
+ next_ver = Version('1.%d.0' % (ver.minor + 1,))
+
+ relpath = 'master1/projects/subversion.conf'
+ edit_file(get_buildbot_wc_path(relpath),
+ r'(MINOR_LINES=\[.*%s)(\])' % (prev_ver.minor,),
+ r'\1, %s\2' % (ver.minor,))
+
+ log_msg = '''\
+Subversion: start monitoring the %s branch.
+''' % (ver.branch)
+ commit_paths = [get_buildbot_wc_path(relpath)]
+ svn_commit(commit_paths + ['-m', log_msg])
+
+#----------------------------------------------------------------------
+def create_release_branch(args):
+ make_release_branch(args.version, args.revnum)
+ update_minor_ver_in_trunk(args.version, args.revnum)
+ create_status_file_on_branch(args.version)
+ update_backport_bot(args.version)
+ update_buildbot_config(args.version)
+
+
+#----------------------------------------------------------------------
+# Main entry point for argument parsing and handling
+
+def main():
+ 'Parse arguments, and drive the appropriate subcommand.'
+
+ # Setup our main parser
+ parser = argparse.ArgumentParser(
+ description='Create an Apache Subversion release branch.')
+ subparsers = parser.add_subparsers(title='subcommands')
+
+ # Setup the parser for the create-release-branch subcommand
+ subparser = subparsers.add_parser('create-release-branch',
+ help='''Create a minor release branch: branch from trunk,
+ update version numbers on trunk, create status
+ file on branch, update backport bot,
+ update buildbot config.''')
+ subparser.set_defaults(func=create_release_branch)
+ subparser.add_argument('version', type=Version,
+ help='''A version number to indicate the branch, such as
+ '1.7.0' (the '.0' is required).''')
+ subparser.add_argument('revnum', type=lambda arg: int(arg.lstrip('r')),
+ nargs='?', default=None,
+ help='''The trunk revision number to base the branch on.
+ Default is HEAD.''')
+ subparser.add_argument('--dry-run', action='store_true', default=False,
+ help='Avoid committing any changes to repositories.')
+ subparser.add_argument('--verbose', action='store_true', default=False,
+ help='Increase output verbosity')
+ subparser.add_argument('--base-dir', default=os.getcwd(),
+ help='''The directory in which to create needed files and
+ folders. The default is the current working
+ directory.''')
+
+ # Parse the arguments
+ args = parser.parse_args()
+
+ global base_dir, dry_run
+ base_dir = args.base_dir
+ dry_run = args.dry_run
+
+ # Set up logging
+ logger = logging.getLogger()
+ if args.verbose:
+ logger.setLevel(logging.DEBUG)
+ else:
+ logger.setLevel(logging.INFO)
+
+ # Make timestamps in tarballs independent of local timezone
+ os.environ['TZ'] = 'UTC'
+
+ # finally, run the subcommand, and give it the parsed arguments
+ args.func(args)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/dist/edit-N-log-messages b/tools/dist/edit-N-log-messages
new file mode 100755
index 0000000..fa6a2a2
--- /dev/null
+++ b/tools/dist/edit-N-log-messages
@@ -0,0 +1,94 @@
+#!/usr/bin/env zsh
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -e
+
+# setup
+0=${0:t}; NAME=$0
+svn=${SVN:-svn}
+svnadmin=${SVNADMIN:-svnadmin}
+
+usage() {
+ echo "usage: $NAME -r N:M"
+ echo " will retrieve all log messages in the range rN:rM, open them in"
+ echo " \$EDITOR, and commit any edits to them."
+ echo ""
+ echo " Run this with cwd being a working copy of ^/subversion/trunk."
+ # TODO: We only need the cwd in case SVN_CLIENT__REVKIND_NEEDS_WC() is true
+ # for N or M. When N and M are both numeric or HEAD, teach this script
+ # to disregard its cwd and contact ^/subversion/trunk directly.
+}
+
+# parse argv
+while getopts r: letter; do
+ if [[ $letter == r ]]; then
+ revision_range=$OPTARG
+ else
+ usage >&2
+ exit 1
+ fi
+done
+shift $((OPTIND - 1))
+[[ -n $revision_range ]] || { usage >&2; exit 1; }
+
+# get log messages
+cd "$(mktemp -d)"
+echo "${0}: Temporary directory: ${(q-)PWD}"
+revisions=( ${(f)"$(
+ # We use $OLDPWD rather than a URL in case $revision_range is, say,
+ # "BASE:HEAD".
+ $svn log -q -r $revision_range -- $OLDPWD |
+ grep '^r' | cut -d' ' -f1 | grep '^r[0-9]*$'
+)"} )
+for rN in $revisions; do
+ $svn propget --revprop -r $rN --strict 'svn:log' -- $OLDPWD > ./$rN &
+done
+wait
+
+# set up to detect which ones have changed
+$svnadmin create ./.r
+$svn checkout -q file://$PWD/.r ./
+$svn add -q ./r*
+$svn commit -q -m "Import revisions $revision_range"
+rm -rf ./.r # (!)
+
+# edit
+case $EDITOR in
+ (*vim*) $EDITOR -p ./r*;;
+ (*) $EDITOR ./r*;;
+esac
+
+# prompt for permission to continue
+if $svn status -q | grep -q ''; then
+ $svn status -q
+ read -q "?Commit the above propedits? " || { echo "${0}: exiting"; exit 0; }
+ echo ""
+else
+ echo "${0}: No changes."
+ echo "${0}: You can remove ${(q-)PWD} now."
+ exit 0
+fi
+
+# make propedits
+# TODO: make these changes atomically
+# 'svn propedit' is atomic and could drive a non-interactive $EDITOR, but for
+# now, we just trust the committer to read commits@.
+targets=( ${(f)"$($svn status -q | grep '^M' | cut -c9-)"} )
+for i in $targets; do
+ $svn propset --revprop -r $i -F ./$i -- 'svn:log' $OLDPWD
+done
diff --git a/tools/dist/nightly.sh b/tools/dist/nightly.sh
index b167ab3..c4f8ebf 100755
--- a/tools/dist/nightly.sh
+++ b/tools/dist/nightly.sh
@@ -21,7 +21,7 @@
#
set -e
-repo=http://svn.apache.org/repos/asf/subversion
+repo=https://svn.apache.org/repos/asf/subversion
svn=svn
olds=7
diff --git a/tools/dist/release.py b/tools/dist/release.py
index 5518d6f..5b12c00 100755
--- a/tools/dist/release.py
+++ b/tools/dist/release.py
@@ -51,6 +51,9 @@ import operator
import itertools
import subprocess
import argparse # standard in Python 2.7
+import io
+
+import backport.status
# Find ezt, using Subversion's copy, if there isn't one on the system.
try:
@@ -71,16 +74,40 @@ tool_versions = {
'954bd69b391edc12d6a4a51a2dd1476543da5c6bbf05a95b59dc0dd6fd4c2969'],
'libtool' : ['2.4.6',
'e3bd4d5d3d025a36c21dd6af7ea818a2afcd4dfc1ea5a17b39d7854bcd0c06e3'],
- 'swig' : ['3.0.10',
- '2939aae39dec06095462f1b95ce1c958ac80d07b926e48871046d17c0094f44c'],
+ 'swig' : ['3.0.12',
+ '7cf9f447ae7ed1c51722efc45e7f14418d15d7a1e143ac9f09a668999f4fc94d'],
+ },
+ '1.13' : {
+ 'autoconf' : ['2.69',
+ '954bd69b391edc12d6a4a51a2dd1476543da5c6bbf05a95b59dc0dd6fd4c2969'],
+ 'libtool' : ['2.4.6',
+ 'e3bd4d5d3d025a36c21dd6af7ea818a2afcd4dfc1ea5a17b39d7854bcd0c06e3'],
+ 'swig' : ['3.0.12',
+ '7cf9f447ae7ed1c51722efc45e7f14418d15d7a1e143ac9f09a668999f4fc94d'],
+ },
+ '1.12' : {
+ 'autoconf' : ['2.69',
+ '954bd69b391edc12d6a4a51a2dd1476543da5c6bbf05a95b59dc0dd6fd4c2969'],
+ 'libtool' : ['2.4.6',
+ 'e3bd4d5d3d025a36c21dd6af7ea818a2afcd4dfc1ea5a17b39d7854bcd0c06e3'],
+ 'swig' : ['3.0.12',
+ '7cf9f447ae7ed1c51722efc45e7f14418d15d7a1e143ac9f09a668999f4fc94d'],
+ },
+ '1.11' : {
+ 'autoconf' : ['2.69',
+ '954bd69b391edc12d6a4a51a2dd1476543da5c6bbf05a95b59dc0dd6fd4c2969'],
+ 'libtool' : ['2.4.6',
+ 'e3bd4d5d3d025a36c21dd6af7ea818a2afcd4dfc1ea5a17b39d7854bcd0c06e3'],
+ 'swig' : ['3.0.12',
+ '7cf9f447ae7ed1c51722efc45e7f14418d15d7a1e143ac9f09a668999f4fc94d'],
},
'1.10' : {
'autoconf' : ['2.69',
'954bd69b391edc12d6a4a51a2dd1476543da5c6bbf05a95b59dc0dd6fd4c2969'],
'libtool' : ['2.4.6',
'e3bd4d5d3d025a36c21dd6af7ea818a2afcd4dfc1ea5a17b39d7854bcd0c06e3'],
- 'swig' : ['3.0.10',
- '2939aae39dec06095462f1b95ce1c958ac80d07b926e48871046d17c0094f44c'],
+ 'swig' : ['3.0.12',
+ '7cf9f447ae7ed1c51722efc45e7f14418d15d7a1e143ac9f09a668999f4fc94d'],
},
'1.9' : {
'autoconf' : ['2.69',
@@ -102,14 +129,16 @@ tool_versions = {
# The version that is our current recommended release
# ### TODO: derive this from svn_version.h; see ../../build/getversion.py
-recommended_release = '1.9'
+recommended_release = '1.12'
+# For clean-dist, a whitelist of artifacts to keep, by version.
+supported_release_lines = frozenset({"1.9", "1.10", "1.12", "1.13"})
# Some constants
-repos = 'https://svn.apache.org/repos/asf/subversion'
-secure_repos = 'https://svn.apache.org/repos/asf/subversion'
+svn_repos = 'https://svn.apache.org/repos/asf/subversion'
dist_repos = 'https://dist.apache.org/repos/dist'
dist_dev_url = dist_repos + '/dev/subversion'
dist_release_url = dist_repos + '/release/subversion'
+dist_archive_url = 'https://archive.apache.org/dist/subversion'
KEYS = 'https://people.apache.org/keys/group/subversion.asc'
extns = ['zip', 'tar.gz', 'tar.bz2']
@@ -174,7 +203,7 @@ class Version(object):
ver_tag = '" (Alpha %d)"' % self.pre_num
ver_numtag = '"-alpha%d"' % self.pre_num
elif self.pre == 'beta':
- ver_tag = '" (Beta %d)"' % args.version.pre_num
+ ver_tag = '" (Beta %d)"' % self.pre_num
ver_numtag = '"-beta%d"' % self.pre_num
elif self.pre == 'rc':
ver_tag = '" (Release Candidate %d)"' % self.pre_num
@@ -263,6 +292,15 @@ def get_target(args):
else:
return get_deploydir(args.base_dir)
+def get_branch_path(args):
+ if not args.branch:
+ try:
+ args.branch = 'branches/%d.%d.x' % (args.version.major, args.version.minor)
+ except AttributeError:
+ raise RuntimeError("Please specify the release version label or --branch-path")
+
+ return args.branch.rstrip('/') # canonicalize for later comparisons
+
def get_tmpldir():
return os.path.join(os.path.abspath(sys.path[0]), 'templates')
@@ -271,7 +309,7 @@ def get_tmplfile(filename):
return open(os.path.join(get_tmpldir(), filename))
except IOError:
# Hmm, we had a problem with the local version, let's try the repo
- return urllib2.urlopen(repos + '/trunk/tools/dist/templates/' + filename)
+ return urllib2.urlopen(svn_repos + '/trunk/tools/dist/templates/' + filename)
def get_nullfile():
return open(os.path.devnull, 'w')
@@ -531,16 +569,12 @@ def replace_lines(path, actions):
def roll_tarballs(args):
'Create the release artifacts.'
- if not args.branch:
- args.branch = 'branches/%d.%d.x' % (args.version.major, args.version.minor)
-
- branch = args.branch # shorthand
- branch = branch.rstrip('/') # canonicalize for later comparisons
+ branch = get_branch_path(args)
logging.info('Rolling release %s from branch %s@%d' % (args.version,
branch, args.revnum))
- check_copyright_year(repos, args.branch, args.revnum)
+ check_copyright_year(svn_repos, branch, args.revnum)
# Ensure we've got the appropriate rolling dependencies available
autoconf = AutoconfDep(args.base_dir, False, args.verbose,
@@ -559,7 +593,7 @@ def roll_tarballs(args):
if branch != 'trunk':
# Make sure CHANGES is sync'd.
- compare_changes(repos, branch, args.revnum)
+ compare_changes(svn_repos, branch, args.revnum)
# Ensure the output directory doesn't already exist
if os.path.exists(get_deploydir(args.base_dir)):
@@ -571,7 +605,7 @@ def roll_tarballs(args):
logging.info('Preparing working copy source')
shutil.rmtree(get_workdir(args.base_dir), True)
run_script(args.verbose, 'svn checkout %s %s'
- % (repos + '/' + branch + '@' + str(args.revnum),
+ % (svn_repos + '/' + branch + '@' + str(args.revnum),
get_workdir(args.base_dir)))
# Exclude stuff we don't want in the tarball, it will not be present
@@ -712,9 +746,13 @@ def roll_tarballs(args):
filepath = os.path.join(get_tempdir(args.base_dir), filename)
shutil.move(filepath, get_deploydir(args.base_dir))
filepath = os.path.join(get_deploydir(args.base_dir), filename)
- m = hashlib.sha1()
- m.update(open(filepath, 'r').read())
- open(filepath + '.sha1', 'w').write(m.hexdigest())
+ if args.version < Version("1.11.0-alpha1"):
+ # 1.10 and earlier generate *.sha1 files for compatibility reasons.
+ # They are deprecated, however, so we don't publicly link them in
+ # the announcements any more.
+ m = hashlib.sha1()
+ m.update(open(filepath, 'r').read())
+ open(filepath + '.sha1', 'w').write(m.hexdigest())
m = hashlib.sha512()
m.update(open(filepath, 'r').read())
open(filepath + '.sha512', 'w').write(m.hexdigest())
@@ -737,8 +775,12 @@ def sign_candidates(args):
def sign_file(filename):
asc_file = open(filename + '.asc', 'a')
logging.info("Signing %s" % filename)
- proc = subprocess.check_call(['gpg', '-ba', '-o', '-', filename],
- stdout=asc_file)
+ if args.userid:
+ proc = subprocess.check_call(['gpg', '-ba', '-u', args.userid,
+ '-o', '-', filename], stdout=asc_file)
+ else:
+ proc = subprocess.check_call(['gpg', '-ba', '-o', '-', filename],
+ stdout=asc_file)
asc_file.close()
target = get_target(args)
@@ -746,7 +788,7 @@ def sign_candidates(args):
for e in extns:
filename = os.path.join(target, 'subversion-%s.%s' % (args.version, e))
sign_file(filename)
- if args.version.major >= 1 and args.version.minor <= 6:
+ if args.version.major == 1 and args.version.minor <= 6:
filename = os.path.join(target,
'subversion-deps-%s.%s' % (args.version, e))
sign_file(filename)
@@ -773,26 +815,24 @@ def post_candidates(args):
#----------------------------------------------------------------------
# Create tag
+# Bump versions on branch
-def create_tag(args):
+def create_tag_only(args):
'Create tag in the repository'
target = get_target(args)
logging.info('Creating tag for %s' % str(args.version))
- if not args.branch:
- args.branch = 'branches/%d.%d.x' % (args.version.major, args.version.minor)
-
- branch = secure_repos + '/' + args.branch.rstrip('/')
+ branch_url = svn_repos + '/' + get_branch_path(args)
- tag = secure_repos + '/tags/' + str(args.version)
+ tag = svn_repos + '/tags/' + str(args.version)
svnmucc_cmd = ['svnmucc', '-m',
'Tagging release ' + str(args.version)]
if (args.username):
svnmucc_cmd += ['--username', args.username]
- svnmucc_cmd += ['cp', str(args.revnum), branch, tag]
+ svnmucc_cmd += ['cp', str(args.revnum), branch_url, tag]
svnmucc_cmd += ['put', os.path.join(target, 'svn_version.h.dist' + '-' +
str(args.version)),
tag + '/subversion/include/svn_version.h']
@@ -805,62 +845,84 @@ def create_tag(args):
logging.error("Do you need to pass --branch=trunk?")
raise
+def bump_versions_on_branch(args):
+ 'Bump version numbers on branch'
+
+ logging.info('Bumping version numbers on the branch')
+
+ branch_url = svn_repos + '/' + get_branch_path(args)
+
+ def replace_in_place(fd, startofline, flat, spare):
+ """In file object FD, replace FLAT with SPARE in the first line
+ starting with regex STARTOFLINE."""
+
+ pattern = r'^(%s)%s' % (startofline, re.escape(flat))
+ repl = r'\g<1>%s' % (spare,)
+ fd.seek(0, os.SEEK_SET)
+ lines = fd.readlines()
+ for i, line in enumerate(lines):
+ replacement = re.sub(pattern, repl, line)
+ if replacement != line:
+ lines[i] = replacement
+ break
+ else:
+ raise RuntimeError("Could not replace r'%s' with r'%s' in '%s'"
+ % (pattern, repl, fd.url))
+
+ fd.seek(0, os.SEEK_SET)
+ fd.writelines(lines)
+ fd.truncate() # for current callers, new value is never shorter.
+
+ new_version = Version('%d.%d.%d' %
+ (args.version.major, args.version.minor,
+ args.version.patch + 1))
+
+ HEAD = subprocess.check_output(['svn', 'info', '--show-item=revision',
+ '--', branch_url]).strip()
+ HEAD = int(HEAD)
+ def file_object_for(relpath):
+ fd = tempfile.NamedTemporaryFile()
+ url = branch_url + '/' + relpath
+ fd.url = url
+ subprocess.check_call(['svn', 'cat', '%s@%d' % (url, HEAD)],
+ stdout=fd)
+ return fd
+
+ svn_version_h = file_object_for('subversion/include/svn_version.h')
+ replace_in_place(svn_version_h, '#define SVN_VER_PATCH *',
+ str(args.version.patch), str(new_version.patch))
+
+ STATUS = file_object_for('STATUS')
+ replace_in_place(STATUS, 'Status of ',
+ str(args.version), str(new_version))
+
+ svn_version_h.seek(0, os.SEEK_SET)
+ STATUS.seek(0, os.SEEK_SET)
+ subprocess.check_call(['svnmucc', '-r', str(HEAD),
+ '-m', 'Post-release housekeeping: '
+ 'bump the %s branch to %s.'
+ % (branch_url.split('/')[-1], str(new_version)),
+ 'put', svn_version_h.name, svn_version_h.url,
+ 'put', STATUS.name, STATUS.url,
+ ])
+ del svn_version_h
+ del STATUS
+
+def create_tag_and_bump_versions(args):
+ '''Create tag in the repository and, if not a prerelease version,
+ bump version numbers on the branch'''
+
+ create_tag_only(args)
+
if not args.version.is_prerelease():
- logging.info('Bumping revisions on the branch')
- def replace_in_place(fd, startofline, flat, spare):
- """In file object FD, replace FLAT with SPARE in the first line
- starting with STARTOFLINE."""
-
- fd.seek(0, os.SEEK_SET)
- lines = fd.readlines()
- for i, line in enumerate(lines):
- if line.startswith(startofline):
- lines[i] = line.replace(flat, spare)
- break
- else:
- raise RuntimeError('Definition of %r not found' % startofline)
-
- fd.seek(0, os.SEEK_SET)
- fd.writelines(lines)
- fd.truncate() # for current callers, new value is never shorter.
-
- new_version = Version('%d.%d.%d' %
- (args.version.major, args.version.minor,
- args.version.patch + 1))
-
- def file_object_for(relpath):
- fd = tempfile.NamedTemporaryFile()
- url = branch + '/' + relpath
- fd.url = url
- subprocess.check_call(['svn', 'cat', '%s@%d' % (url, args.revnum)],
- stdout=fd)
- return fd
-
- svn_version_h = file_object_for('subversion/include/svn_version.h')
- replace_in_place(svn_version_h, '#define SVN_VER_PATCH ',
- str(args.version.patch), str(new_version.patch))
-
- STATUS = file_object_for('STATUS')
- replace_in_place(STATUS, 'Status of ',
- str(args.version), str(new_version))
-
- svn_version_h.seek(0, os.SEEK_SET)
- STATUS.seek(0, os.SEEK_SET)
- subprocess.check_call(['svnmucc', '-r', str(args.revnum),
- '-m', 'Post-release housekeeping: '
- 'bump the %s branch to %s.'
- % (branch.split('/')[-1], str(new_version)),
- 'put', svn_version_h.name, svn_version_h.url,
- 'put', STATUS.name, STATUS.url,
- ])
- del svn_version_h
- del STATUS
+ bump_versions_on_branch(args)
#----------------------------------------------------------------------
# Clean dist
def clean_dist(args):
- 'Clean the distribution directory of all but the most recent artifacts.'
+ '''Clean the distribution directory of release artifacts of
+ no-longer-supported minor lines.'''
stdout = subprocess.check_output(['svn', 'list', dist_release_url])
@@ -872,21 +934,20 @@ def clean_dist(args):
filenames = stdout.split('\n')
filenames = filter(lambda x: x.startswith('subversion-'), filenames)
versions = set(map(Version, filenames))
- minor_lines = set(map(minor, versions))
to_keep = set()
- # Keep 3 minor lines: 1.10.0-alpha3, 1.9.7, 1.8.19.
# TODO: When we release 1.A.0 GA we'll have to manually remove 1.(A-2).* artifacts.
- for recent_line in sorted(minor_lines, reverse=True)[:3]:
- to_keep.add(max(
+ for line_to_keep in [minor(Version(x + ".0")) for x in supported_release_lines]:
+ candidates = list(
x for x in versions
- if minor(x) == recent_line
- ))
+ if minor(x) == line_to_keep
+ )
+ if candidates:
+ to_keep.add(max(candidates))
for i in sorted(to_keep):
logging.info("Saving release '%s'", i)
svnmucc_cmd = ['svnmucc', '-m', 'Remove old Subversion releases.\n' +
- 'They are still available at ' +
- 'https://archive.apache.org/dist/subversion/']
+ 'They are still available at ' + dist_archive_url]
if (args.username):
svnmucc_cmd += ['--username', args.username]
for filename in filenames:
@@ -938,6 +999,7 @@ def write_news(args):
'version_base' : args.version.base,
'anchor': args.version.get_download_anchor(),
'is_recommended': ezt_bool(args.version.is_recommended()),
+ 'announcement_url': args.announcement_url,
}
if args.version.is_prerelease():
@@ -947,38 +1009,53 @@ def write_news(args):
template = ezt.Template()
template.parse(get_tmplfile(template_filename).read())
- template.generate(sys.stdout, data)
+
+ # Insert the output into an existing file if requested, else print it
+ if args.edit_html_file:
+ tmp_name = args.edit_html_file + '.tmp'
+ with open(args.edit_html_file, 'r') as f, open(tmp_name, 'w') as g:
+ inserted = False
+ for line in f:
+ if not inserted and line.startswith('<div class="h3" id="news-'):
+ template.generate(g, data)
+ g.write('\n')
+ inserted = True
+ g.write(line)
+ os.remove(args.edit_html_file)
+ os.rename(tmp_name, args.edit_html_file)
+ else:
+ template.generate(sys.stdout, data)
-def get_sha1info(args):
- 'Return a list of sha1 info for the release'
+def get_fileinfo(args):
+ 'Return a list of file info (filenames) for the release tarballs'
target = get_target(args)
- sha1s = glob.glob(os.path.join(target, 'subversion*-%s*.sha1' % args.version))
+ files = glob.glob(os.path.join(target, 'subversion*-%s.*.asc' % args.version))
+ files.sort()
class info(object):
pass
- sha1info = []
- for s in sha1s:
+ fileinfo = []
+ for f in files:
i = info()
- # strip ".sha1"
- i.filename = os.path.basename(s)[:-5]
- i.sha1 = open(s, 'r').read()
- sha1info.append(i)
+ # strip ".asc"
+ i.filename = os.path.basename(f)[:-4]
+ fileinfo.append(i)
- return sha1info
+ return fileinfo
def write_announcement(args):
'Write the release announcement.'
- sha1info = get_sha1info(args)
- siginfo = "\n".join(get_siginfo(args, True)) + "\n"
+ siginfo = get_siginfo(args, True)
+ if not siginfo:
+ raise RuntimeError("No signatures found for %s at %s" % (args.version, args.target))
data = { 'version' : str(args.version),
- 'sha1info' : sha1info,
- 'siginfo' : siginfo,
+ 'siginfo' : "\n".join(siginfo) + "\n",
'major-minor' : args.version.branch,
'major-minor-patch' : args.version.base,
'anchor' : args.version.get_download_anchor(),
@@ -1007,10 +1084,10 @@ def write_announcement(args):
def write_downloads(args):
'Output the download section of the website.'
- sha1info = get_sha1info(args)
+ fileinfo = get_fileinfo(args)
data = { 'version' : str(args.version),
- 'fileinfo' : sha1info,
+ 'fileinfo' : fileinfo,
}
template = ezt.Template(compress_whitespace = False)
@@ -1046,14 +1123,12 @@ def get_siginfo(args, quiet=False):
import security._gnupg as gnupg
gpg = gnupg.GPG()
- target = get_target(args)
-
good_sigs = {}
fingerprints = {}
output = []
- glob_pattern = os.path.join(target, 'subversion*-%s*.asc' % args.version)
- for filename in glob.glob(glob_pattern):
+ for fileinfo in get_fileinfo(args):
+ filename = os.path.join(get_target(args), fileinfo.filename + '.asc')
text = open(filename).read()
keys = text.split(key_start)
@@ -1174,6 +1249,206 @@ def get_keys(args):
fd.seek(0)
subprocess.check_call(['gpg', '--import'], stdin=fd)
+def add_to_changes_dict(changes_dict, audience, section, change, revision):
+ # Normalize arguments
+ if audience:
+ audience = audience.upper()
+ if section:
+ section = section.lower()
+ change = change.strip()
+
+ if not audience in changes_dict:
+ changes_dict[audience] = dict()
+ if not section in changes_dict[audience]:
+ changes_dict[audience][section] = dict()
+
+ changes = changes_dict[audience][section]
+ if change in changes:
+ changes[change].add(revision)
+ else:
+ changes[change] = set([revision])
+
+def print_section(changes_dict, audience, section, title, mandatory=False):
+ if audience in changes_dict:
+ audience_changes = changes_dict[audience]
+ if mandatory or (section in audience_changes):
+ if title:
+ print(' - %s:' % title)
+ if section in audience_changes:
+ print_changes(audience_changes[section])
+ elif mandatory:
+ print(' (none)')
+
+def print_changes(changes):
+ # Print in alphabetical order, so entries with the same prefix are together
+ for change in sorted(changes):
+ revs = changes[change]
+ rev_string = 'r' + str(min(revs)) + (' et al' if len(revs) > 1 else '')
+ print(' * %s (%s)' % (change, rev_string))
+
+def write_changelog(args):
+ 'Write changelog, parsed from commit messages'
+ # Changelog lines are lines with the following format:
+ # '['[audience[:section]]']' <message>
+ # or:
+ # <message> '['[audience[:section]]']'
+ # where audience = U (User-visible) or D (Developer-visible)
+ # section = general|major|minor|client|server|clientserver|other|api|bindings
+ # (section is optional and is treated case-insensitively)
+ # message = the actual text for CHANGES
+ #
+ # This means the "changes label" can be used as prefix or suffix, and it
+ # can also be left empty (which results in an uncategorized changes entry),
+ # if the committer isn't sure where the changelog entry belongs.
+ #
+ # Putting [skip], [ignore], [c:skip] or [c:ignore] somewhere in the
+ # log message means this commit must be ignored for Changelog processing
+ # (ignored even with the --include-unlabeled-summaries option).
+ #
+ # If there is no changes label anywhere in the commit message, and the
+ # --include-unlabeled-summaries option is used, we'll consider the summary
+ # line of the commit message (= first line except if it starts with a *)
+ # as an uncategorized changes entry, except if it contains "status",
+ # "changes", "post-release housekeeping" or "follow-up".
+ #
+ # Examples:
+ # [U:major] Better interactive conflict resolution for tree conflicts
+ # ra_serf: Adjustments for serf versions with HTTP/2 support [U:minor]
+ # [U] Fix 'svn diff URL@REV WC' wrongly looks up URL@HEAD (issue #4597)
+ # Fix bug with canonicalizing Window-specific drive-relative URL []
+ # New svn_ra_list() API function [D:api]
+ # [D:bindings] JavaHL: Allow access to constructors of a couple JavaHL classes
+
+ branch_url = svn_repos + '/' + get_branch_path(args)
+ previous = svn_repos + '/' + args.previous
+ include_unlabeled = args.include_unlabeled
+ separator_line = ('-' * 72) + '\n'
+
+ mergeinfo = subprocess.check_output(['svn', 'mergeinfo', '--show-revs',
+ 'eligible', '--log', branch_url, previous])
+ log_messages_dict = {
+ # This is a dictionary mapping revision numbers to their respective
+ # log messages. The expression in the "key:" part of the dict
+ # comprehension extracts the revision number, as integer, from the
+ # 'svn log' output.
+ int(log_message.splitlines()[0].split()[0][1:]): log_message
+ # The [1:-1] ignores the empty first and last element of the split().
+ for log_message in mergeinfo.split(separator_line)[1:-1]
+ }
+ mergeinfo = mergeinfo.splitlines()
+
+ separator_pattern = re.compile('^-{72}$')
+ revline_pattern = re.compile('^r(\d+) \| [^\|]+ \| [^\|]+ \| \d+ lines?$')
+ changes_prefix_pattern = re.compile(r'^\[(U|D)?:?([^\]]+)?\](.+)$')
+ changes_suffix_pattern = re.compile(r'^(.+)\[(U|D)?:?([^\]]+)?\]$')
+ # TODO: push this into backport.status as a library function
+ auto_merge_pattern = \
+ re.compile(r'^Merge (r\d+,? |the r\d+ group |the \S+ branch:)')
+
+ changes_dict = dict() # audience -> (section -> (change -> set(revision)))
+ revision = -1
+ got_firstline = False
+ unlabeled_summary = None
+ changes_ignore = False
+ audience = None
+ section = None
+ message = None
+
+ for line in mergeinfo:
+ if separator_pattern.match(line):
+ # New revision section. Reset variables.
+ # If there's an unlabeled summary from a previous section, and
+ # include_unlabeled is True, put it into uncategorized_changes.
+ if include_unlabeled and unlabeled_summary and not changes_ignore:
+ if auto_merge_pattern.match(unlabeled_summary):
+ # 1. Parse revision numbers from the first line
+ merged_revisions = [
+ int(x) for x in
+ re.compile(r'(?<=\br)\d+\b').findall(unlabeled_summary)
+ ]
+ # TODO pass each revnum in MERGED_REVISIONS through this
+ # logic, in order to extract CHANGES_PREFIX_PATTERN
+ # and CHANGES_SUFFIX_PATTERN lines from the trunk log
+ # message.
+
+ # 2. Parse the STATUS entry
+ this_log_message = log_messages_dict[revision]
+ status_paragraph = this_log_message.split('\n\n')[2]
+ logsummary = \
+ backport.status.StatusEntry(status_paragraph).logsummary
+ add_to_changes_dict(changes_dict, None, None,
+ ' '.join(logsummary), revision)
+ else:
+ add_to_changes_dict(changes_dict, None, None,
+ unlabeled_summary, revision)
+ revision = -1
+ got_firstline = False
+ unlabeled_summary = None
+ changes_ignore = False
+ audience = None
+ section = None
+ message = None
+ continue
+
+ revmatch = revline_pattern.match(line)
+ if revmatch and (revision == -1):
+ # A revision line: get the revision number
+ revision = int(revmatch.group(1))
+ logging.debug('Changelog processing revision r%d' % revision)
+ continue
+
+ if line.strip() == '':
+ # Skip empty / whitespace lines
+ continue
+
+ if not got_firstline:
+ got_firstline = True
+ if (not re.search(r'status|changes|post-release housekeeping|follow-up|^\*',
+ line, re.IGNORECASE)
+ and not changes_prefix_pattern.match(line)
+ and not changes_suffix_pattern.match(line)):
+ unlabeled_summary = line
+
+ if re.search(r'\[(c:)?(skip|ignore)\]', line, re.IGNORECASE):
+ changes_ignore = True
+
+ prefix_match = changes_prefix_pattern.match(line)
+ if prefix_match:
+ audience = prefix_match.group(1)
+ section = prefix_match.group(2)
+ message = prefix_match.group(3)
+ add_to_changes_dict(changes_dict, audience, section, message, revision)
+
+ suffix_match = changes_suffix_pattern.match(line)
+ if suffix_match:
+ message = suffix_match.group(1)
+ audience = suffix_match.group(2)
+ section = suffix_match.group(3)
+ add_to_changes_dict(changes_dict, audience, section, message, revision)
+
+ # Output the sorted changelog entries
+ # 1) Uncategorized changes
+ print_section(changes_dict, None, None, None)
+ print
+ # 2) User-visible changes
+ print(' User-visible changes:')
+ print_section(changes_dict, 'U', None, None)
+ print_section(changes_dict, 'U', 'general', 'General')
+ print_section(changes_dict, 'U', 'major', 'Major new features')
+ print_section(changes_dict, 'U', 'minor', 'Minor new features and improvements')
+ print_section(changes_dict, 'U', 'client', 'Client-side bugfixes', mandatory=True)
+ print_section(changes_dict, 'U', 'server', 'Server-side bugfixes', mandatory=True)
+ print_section(changes_dict, 'U', 'clientserver', 'Client-side and server-side bugfixes')
+ print_section(changes_dict, 'U', 'other', 'Other tool improvements and bugfixes')
+ print_section(changes_dict, 'U', 'bindings', 'Bindings bugfixes', mandatory=True)
+ print
+ # 3) Developer-visible changes
+ print(' Developer-visible changes:')
+ print_section(changes_dict, 'D', None, None)
+ print_section(changes_dict, 'D', 'general', 'General', mandatory=True)
+ print_section(changes_dict, 'D', 'api', 'API changes', mandatory=True)
+ print_section(changes_dict, 'D', 'bindings', 'Bindings')
+
#----------------------------------------------------------------------
# Main entry point for argument parsing and handling
@@ -1191,6 +1466,10 @@ def main():
help='''The directory in which to create needed files and
folders. The default is the current working
directory.''')
+ parser.add_argument('--branch',
+ help='''The branch to base the release on,
+ as a path relative to ^/subversion/.
+ Default: 'branches/MAJOR.MINOR.x'.''')
subparsers = parser.add_subparsers(title='subcommands')
# Setup the parser for the build-env subcommand
@@ -1216,9 +1495,6 @@ def main():
help='''The release label, such as '1.7.0-alpha1'.''')
subparser.add_argument('revnum', type=lambda arg: int(arg.lstrip('r')),
help='''The revision number to base the release on.''')
- subparser.add_argument('--branch',
- help='''The branch to base the release on,
- relative to ^/subversion/.''')
subparser.add_argument('--patches',
help='''The path to the directory containing patches.''')
@@ -1231,6 +1507,10 @@ def main():
subparser.add_argument('--target',
help='''The full path to the directory containing
release artifacts.''')
+ subparser.add_argument('--userid',
+ help='''The (optional) USER-ID specifying the key to be
+ used for signing, such as '110B1C95' (Key-ID). If
+ omitted, uses the default key.''')
# Setup the parser for the post-candidates subcommand
subparser = subparsers.add_parser('post-candidates',
@@ -1247,25 +1527,36 @@ def main():
# Setup the parser for the create-tag subcommand
subparser = subparsers.add_parser('create-tag',
- help='''Create the release tag.''')
- subparser.set_defaults(func=create_tag)
+ help='''Create the release tag and, if not a prerelease
+ version, bump version numbers on the branch.''')
+ subparser.set_defaults(func=create_tag_and_bump_versions)
+ subparser.add_argument('version', type=Version,
+ help='''The release label, such as '1.7.0-alpha1'.''')
+ subparser.add_argument('revnum', type=lambda arg: int(arg.lstrip('r')),
+ help='''The revision number to base the release on.''')
+ subparser.add_argument('--username',
+ help='''Username for ''' + svn_repos + '''.''')
+ subparser.add_argument('--target',
+ help='''The full path to the directory containing
+ release artifacts.''')
+
+ # Setup the parser for the bump-versions-on-branch subcommand
+ subparser = subparsers.add_parser('bump-versions-on-branch',
+ help='''Bump version numbers on branch.''')
+ subparser.set_defaults(func=bump_versions_on_branch)
subparser.add_argument('version', type=Version,
help='''The release label, such as '1.7.0-alpha1'.''')
subparser.add_argument('revnum', type=lambda arg: int(arg.lstrip('r')),
help='''The revision number to base the release on.''')
- subparser.add_argument('--branch',
- help='''The branch to base the release on,
- relative to ^/subversion/.''')
subparser.add_argument('--username',
- help='''Username for ''' + secure_repos + '''.''')
+ help='''Username for ''' + svn_repos + '''.''')
subparser.add_argument('--target',
help='''The full path to the directory containing
release artifacts.''')
# The clean-dist subcommand
subparser = subparsers.add_parser('clean-dist',
- help='''Clean the distribution directory (and mirrors) of
- all but the most recent MAJOR.MINOR release.''')
+ help=clean_dist.__doc__.split('\n\n')[0])
subparser.set_defaults(func=clean_dist)
subparser.add_argument('--dist-dir',
help='''The directory to clean.''')
@@ -1288,6 +1579,11 @@ def main():
help='''Output to stdout template text for use in the news
section of the Subversion website.''')
subparser.set_defaults(func=write_news)
+ subparser.add_argument('--announcement-url',
+ help='''The URL to the archived announcement email.''')
+ subparser.add_argument('--edit-html-file',
+ help='''Insert the text into this file
+ news.html, index.html).''')
subparser.add_argument('version', type=Version,
help='''The release label, such as '1.7.0-alpha1'.''')
@@ -1338,6 +1634,25 @@ def main():
separate subcommand.''')
subparser.set_defaults(func=cleanup)
+ # write-changelog
+ subparser = subparsers.add_parser('write-changelog',
+ help='''Output to stdout changelog entries parsed from
+ commit messages, optionally labeled with a category
+ like [U:client], [D:api], [U], ...''')
+ subparser.set_defaults(func=write_changelog)
+ subparser.add_argument('previous',
+ help='''The "previous" branch or tag, relative to
+ ^/subversion/, to compare "branch" against.''')
+ subparser.add_argument('--include-unlabeled-summaries',
+ dest='include_unlabeled',
+ action='store_true', default=False,
+ help='''Include summary lines that do not have a changes
+ label, unless an explicit [c:skip] or [c:ignore]
+ is part of the commit message (except if the
+ summary line contains 'STATUS', 'CHANGES',
+ 'Post-release housekeeping', 'Follow-up' or starts
+ with '*').''')
+
# Parse the arguments
args = parser.parse_args()
diff --git a/tools/dist/security/parser.py b/tools/dist/security/parser.py
index 2f1b883..2827e94 100644
--- a/tools/dist/security/parser.py
+++ b/tools/dist/security/parser.py
@@ -50,9 +50,16 @@ class Notification(object):
CULPRIT_SERVER = 'server'
CULPRIT_CLIENT = 'client'
- __CULPRITS = ((CULPRIT_SERVER, CULPRIT_CLIENT,
- (CULPRIT_SERVER, CULPRIT_CLIENT),
- (CULPRIT_CLIENT, CULPRIT_SERVER)))
+ # For compatibility, 'client' and 'server' may be specified either with
+ # or without a tuple.
+ __CULPRITS = (
+ CULPRIT_SERVER,
+ CULPRIT_CLIENT,
+ (CULPRIT_SERVER,)
+ (CULPRIT_CLIENT,)
+ (CULPRIT_SERVER, CULPRIT_CLIENT),
+ (CULPRIT_CLIENT, CULPRIT_SERVER),
+ )
def __init__(self, basedir, tracking_id,
title, culprit, advisory, patches):
diff --git a/tools/dist/templates/download.ezt b/tools/dist/templates/download.ezt
index 4c6fda8..19dac3d 100644
--- a/tools/dist/templates/download.ezt
+++ b/tools/dist/templates/download.ezt
@@ -2,16 +2,14 @@
<table class="centered">
<tr>
<th>File</th>
- <th>Checksum (SHA1)</th>
<th>Checksum (SHA512)</th>
<th>Signatures</th>
</tr>
[for fileinfo]<tr>
<td><a href="[[]preferred]subversion/[fileinfo.filename]">[fileinfo.filename]</a></td>
- <td class="checksum">[fileinfo.sha1]</td>
<!-- The sha512 line does not have a class="checksum" since the link needn't
be rendered in monospace. -->
- <td>[<a href="http://www.apache.org/dist/subversion/[fileinfo.filename].sha512">SHA-512</a>]</td>
- <td>[<a href="http://www.apache.org/dist/subversion/[fileinfo.filename].asc">PGP</a>]</td>
+ <td>[<a href="https://www.apache.org/dist/subversion/[fileinfo.filename].sha512">SHA-512</a>]</td>
+ <td>[<a href="https://www.apache.org/dist/subversion/[fileinfo.filename].asc">PGP</a>]</td>
</tr>[end]
</table>
diff --git a/tools/dist/templates/rc-news.ezt b/tools/dist/templates/rc-news.ezt
index a645ffa..04f094d 100644
--- a/tools/dist/templates/rc-news.ezt
+++ b/tools/dist/templates/rc-news.ezt
@@ -8,10 +8,10 @@
release is not intended for production use, but is provided as a milestone
to encourage wider testing and feedback from intrepid users and maintainers.
Please see the
- <a href="">release
+ <a href="[announcement_url]">release
announcement</a> for more information about this release, and the
<a href="/docs/release-notes/[major-minor].html">release notes</a> and
- <a href="http://svn.apache.org/repos/asf/subversion/tags/[version]/CHANGES">
+ <a href="https://svn.apache.org/repos/asf/subversion/tags/[version]/CHANGES">
change log</a> for information about what will eventually be
in the [version_base] release.</p>
diff --git a/tools/dist/templates/rc-release-ann.ezt b/tools/dist/templates/rc-release-ann.ezt
index ca5f4d0..a8bb0f5 100644
--- a/tools/dist/templates/rc-release-ann.ezt
+++ b/tools/dist/templates/rc-release-ann.ezt
@@ -1,16 +1,13 @@
From: ...@apache.org
To: announce@subversion.apache.org, users@subversion.apache.org, dev@subversion.apache.org, announce@apache.org
+Reply-To: users@subversion.apache.org
Subject: [[]ANNOUNCE] Apache Subversion [version] released
I'm happy to announce the release of Apache Subversion [version].
Please choose the mirror closest to you by visiting:
- http://subversion.apache.org/download.cgi#[anchor]
+ https://subversion.apache.org/download.cgi#[anchor]
-The SHA1 checksums are:
-
-[for sha1info] [sha1info.sha1] [sha1info.filename]
-[end]
SHA-512 checksums are available at:
https://www.apache.org/dist/subversion/subversion-[version].tar.bz2.sha512
@@ -19,9 +16,9 @@ SHA-512 checksums are available at:
PGP Signatures are available at:
- http://www.apache.org/dist/subversion/subversion-[version].tar.bz2.asc
- http://www.apache.org/dist/subversion/subversion-[version].tar.gz.asc
- http://www.apache.org/dist/subversion/subversion-[version].zip.asc
+ https://www.apache.org/dist/subversion/subversion-[version].tar.bz2.asc
+ https://www.apache.org/dist/subversion/subversion-[version].tar.gz.asc
+ https://www.apache.org/dist/subversion/subversion-[version].zip.asc
For this release, the following people have provided PGP signatures:
@@ -57,13 +54,18 @@ end users please.
Release notes for the [major-minor].x release series may be found at:
- http://subversion.apache.org/docs/release-notes/[major-minor].html
+ https://subversion.apache.org/docs/release-notes/[major-minor].html
You can find the list of changes between [version] and earlier versions at:
- http://svn.apache.org/repos/asf/subversion/tags/[version]/CHANGES
+ https://svn.apache.org/repos/asf/subversion/tags/[version]/CHANGES
Questions, comments, and bug reports to users@subversion.apache.org.
Thanks,
- The Subversion Team
+
+--
+To unsubscribe, please see:
+
+ https://subversion.apache.org/mailing-lists.html#unsubscribing
diff --git a/tools/dist/templates/stable-news.ezt b/tools/dist/templates/stable-news.ezt
index 8fcaae9..4cd82ac 100644
--- a/tools/dist/templates/stable-news.ezt
+++ b/tools/dist/templates/stable-news.ezt
@@ -10,10 +10,10 @@
[else] This is the most complete release of the [major-minor].x line to date,
and we encourage all users to upgrade as soon as reasonable.
[end] Please see the
- <a href=""
+ <a href="[announcement_url]"
>release announcement</a> and the
- <a href="http://svn.apache.org/repos/asf/subversion/tags/[version]/CHANGES"
- >change log</a> for more information about this release.</p>
+ <a href="/docs/release-notes/[major-minor]"
+ >release notes</a> for more information about this release.</p>
<p>To get this release from the nearest mirror, please visit our
<a href="/download.cgi#[anchor]">download page</a>.</p>
diff --git a/tools/dist/templates/stable-release-ann.ezt b/tools/dist/templates/stable-release-ann.ezt
index 2aec041..dea8c88 100644
--- a/tools/dist/templates/stable-release-ann.ezt
+++ b/tools/dist/templates/stable-release-ann.ezt
@@ -1,5 +1,6 @@
From: ...@apache.org
To: announce@subversion.apache.org, users@subversion.apache.org, dev@subversion.apache.org, announce@apache.org
+Reply-To: users@subversion.apache.org
[if-any security]Cc: security@apache.org, oss-security@lists.openwall.com, bugtraq@securityfocus.com
[end][if-any security]Subject: [[]SECURITY][[]ANNOUNCE] Apache Subversion [version] released
[else]Subject: [[]ANNOUNCE] Apache Subversion [version] released
@@ -7,7 +8,7 @@ To: announce@subversion.apache.org, users@subversion.apache.org, dev@subversion.
I'm happy to announce the release of Apache Subversion [version].
Please choose the mirror closest to you by visiting:
- http://subversion.apache.org/download.cgi#[anchor]
+ https://subversion.apache.org/download.cgi#[anchor]
[if-any dot-zero]
This is a stable feature release of the Apache Subversion open source
version control system.
@@ -18,10 +19,6 @@ open source version control system.
This is a stable bugfix release of the Apache Subversion open source
version control system.
[end][end]
-The SHA1 checksums are:
-
-[for sha1info] [sha1info.sha1] [sha1info.filename]
-[end]
SHA-512 checksums are available at:
https://www.apache.org/dist/subversion/subversion-[version].tar.bz2.sha512
@@ -30,22 +27,27 @@ SHA-512 checksums are available at:
PGP Signatures are available at:
- http://www.apache.org/dist/subversion/subversion-[version].tar.bz2.asc
- http://www.apache.org/dist/subversion/subversion-[version].tar.gz.asc
- http://www.apache.org/dist/subversion/subversion-[version].zip.asc
+ https://www.apache.org/dist/subversion/subversion-[version].tar.bz2.asc
+ https://www.apache.org/dist/subversion/subversion-[version].tar.gz.asc
+ https://www.apache.org/dist/subversion/subversion-[version].zip.asc
For this release, the following people have provided PGP signatures:
[siginfo]
Release notes for the [major-minor].x release series may be found at:
- http://subversion.apache.org/docs/release-notes/[major-minor].html
+ https://subversion.apache.org/docs/release-notes/[major-minor].html
You can find the list of changes between [version] and earlier versions at:
- http://svn.apache.org/repos/asf/subversion/tags/[version]/CHANGES
+ https://svn.apache.org/repos/asf/subversion/tags/[version]/CHANGES
Questions, comments, and bug reports to users@subversion.apache.org.
Thanks,
- The Subversion Team
+
+--
+To unsubscribe, please see:
+
+ https://subversion.apache.org/mailing-lists.html#unsubscribing