This is an automated email from the ASF dual-hosted git repository.
sigram pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new 634be2a1c13 Update release scripts (#4956)
634be2a1c13 is described below
commit 634be2a1c1358363fd6110b6502e094027a704e4
Author: Andrzej BiaĆecki <[email protected]>
AuthorDate: Mon Sep 28 23:26:28 2026 +0200
Update release scripts (#4956)
Co-authored-by: David Smiley <[email protected]>
Co-authored-by: Claude Sonnet 5 <[email protected]>
---
dev-tools/scripts/releaseWizard.py | 6 +-
dev-tools/scripts/releaseWizard.yaml | 67 ++++++++++++++++---
.../documentation/changes-to-html/changes2html.py | 77 +++++++++++++++-------
3 files changed, 119 insertions(+), 31 deletions(-)
diff --git a/dev-tools/scripts/releaseWizard.py
b/dev-tools/scripts/releaseWizard.py
index 6a4dd91c549..26f635651bd 100755
--- a/dev-tools/scripts/releaseWizard.py
+++ b/dev-tools/scripts/releaseWizard.py
@@ -135,6 +135,7 @@ def expand_jinja(text, vars=None):
'docker_version_to_remove': state.get_docker_version_to_remove(),
'editor': get_editor(),
'rename_cmd': 'ren' if is_windows() else 'mv',
+ 'open_cmd': 'start' if is_windows() else ('open' if is_mac() else
'xdg-open'),
'vote_close_72h': vote_close_72h_date().strftime("%Y-%m-%d %H:00 UTC"),
'vote_close_72h_epoch': unix_time_millis(vote_close_72h_date()),
'vote_close_72h_holidays': vote_close_72h_holidays(),
@@ -457,6 +458,8 @@ class ReleaseState:
if v.is_major_release():
return 'main'
elif v.is_minor_release():
+ if self.is_lts_release():
+ return self.get_lts_stable_branch_name()
return self.get_stable_branch_name()
elif v.major == Version.parse(self.latest_version).major:
return self.get_minor_branch_name()
@@ -1871,7 +1874,8 @@ class Command(SecretYamlObject):
return self.jinjaify(self.redirect)
def get_cmd(self):
- return self.jinjaify(self.cmd, join=True)
+ # Run python scripts with this interpreter (e.g. a venv), not whatever
python3 is on PATH
+ return re.sub(r'^python3(?=\s)', lambda _:
shlex.quote(sys.executable), self.jinjaify(self.cmd, join=True))
def get_vars(self):
myvars = {}
diff --git a/dev-tools/scripts/releaseWizard.yaml
b/dev-tools/scripts/releaseWizard.yaml
index a85e558775f..0a3b0201b52 100644
--- a/dev-tools/scripts/releaseWizard.yaml
+++ b/dev-tools/scripts/releaseWizard.yaml
@@ -409,7 +409,7 @@ groups:
commands_text: Run these commands to create a release branch
commands:
- !Command
- cmd: git checkout {{ stable_branch }}
+ cmd: git checkout {{ base_branch }}
tee: true
- !Command
cmd: git pull --ff-only
@@ -524,7 +524,7 @@ groups:
commands_text: Run these commands to add the new minor version {{
next_version }} to the stable and unstable branches
commands:
- !Command
- cmd: git checkout {{ stable_branch }}
+ cmd: git checkout {{ base_branch }}
tee: true
- !Command
cmd: python3 -u dev-tools/scripts/addVersion.py {{ next_version }}
@@ -755,26 +755,77 @@ groups:
description: |
This step first validates all changelogs in `changelog/unreleased/`.
If there are any errors, they must be fixed before proceeding.
-
+
It then moves changelog entries into `changelog/v{{ release_version }}/`
and
regenerates `CHANGELOG.md` without a release date. The date is updated
later,
in the `publish` phase.
- The result is committed to `{{ release_branch }}` only. Cherry-picking to
- `{{ stable_branch }}` and `main` is done in `publish` phase.
+ This step is deliberately run without `--commit`, leaving the result
+ uncommitted in the working tree so it can be spot-checked in the next
+ step before anything is committed or pushed.
Can be run standalone, see `dev-tools/scripts/logchange.py prepare
--help`.
depends: dependency_updates_changes
commands: !Commands
root_folder: '{{ git_checkout_folder }}'
- commands_text: Prepare changelog folder and regenerate CHANGELOG.md for
RC{{ rc_number }}
+ commands_text: Prepare changelog folder and regenerate CHANGELOG.md for
RC{{ rc_number }} (uncommitted)
confirm_each_command: false
commands:
- !Command
- cmd: python3 -u dev-tools/scripts/logchange.py prepare --version {{
release_version }} --release-branch {{ release_branch }} --gradle-cmd {{
gradle_cmd }} --rc-number {{ rc_number }} --commit
- comment: Move unreleased entries to version folder and commit; then
regenerate CHANGELOG.md and commit (two commits total)
+ cmd: python3 -u dev-tools/scripts/logchange.py prepare --version {{
release_version }} --release-branch {{ release_branch }} --gradle-cmd {{
gradle_cmd }} --rc-number {{ rc_number }}
+ comment: Move unreleased entries to version folder and regenerate
CHANGELOG.md, left uncommitted
logfile: changelog-update-rc.log
tee: true
+ - !Todo
+ id: review_changelog_rc
+ title: Spot-check the changelog for RC{{ rc_number }}
+ description: |
+ Before committing, spot-check the changelog that was just regenerated.
+ This is the last easy chance to catch mistakes -- once RC{{ rc_number }}
is
+ built and staged, fixing the changelog means a new RC and restarting the
vote.
+
+ Look at `git diff changelog/ CHANGELOG.md` and the rendered
+ `solr/documentation/build/site/changes/Changes.html` (opened below) for
+ things like: entries in the wrong section, an "Important notes" entry
+ that shouldn't be called out, duplicate/missing issues, and typos.
+
+ If something needs fixing, edit the relevant file(s) under
+ `changelog/v{{ release_version }}/` and re-run this step's commands.
+ depends: changelog_update_rc
+ commands: !Commands
+ root_folder: '{{ git_checkout_folder }}'
+ commands_text: Show the changelog diff and open the rendered
Changes.html for review
+ confirm_each_command: false
+ commands:
+ - !Command
+ cmd: git diff --stat changelog CHANGELOG.md
+ stdout: true
+ - !Command
+ cmd: "{{ gradle_cmd }} :solr:documentation:changesToHtml"
+ logfile: changes-to-html.log
+ tee: true
+ - !Command
+ cmd: "{{ open_cmd }}
solr/documentation/build/site/changes/Changes.html"
+ stdout: true
+ post_description: |
+ Once the changelog looks right, proceed to the next step to commit and
push it.
+ - !Todo
+ id: commit_changelog_rc
+ title: Commit and push changelog for RC{{ rc_number }}
+ description: |
+ Commits the reviewed changelog to `{{ release_branch }}` only.
Cherry-picking to
+ `{{ stable_branch }}` and `main` is done in the `publish` phase.
+ depends: review_changelog_rc
+ commands: !Commands
+ root_folder: '{{ git_checkout_folder }}'
+ commands_text: Commit the reviewed changelog and push it to {{
release_branch }}
+ confirm_each_command: false
+ commands:
+ - !Command
+ cmd: python3 -u dev-tools/scripts/logchange.py prepare --version {{
release_version }} --release-branch {{ release_branch }} --gradle-cmd {{
gradle_cmd }} --rc-number {{ rc_number }} --commit
+ comment: Commit the (already reviewed) changelog entries and
regenerated CHANGELOG.md
+ logfile: commit-changelog-update-rc.log
+ tee: true
- !Command
cmd: git push origin {{ release_branch }}
comment: Push prepared changelog to {{ release_branch }}
diff --git a/gradle/documentation/changes-to-html/changes2html.py
b/gradle/documentation/changes-to-html/changes2html.py
index 1d52618834e..287c3205145 100755
--- a/gradle/documentation/changes-to-html/changes2html.py
+++ b/gradle/documentation/changes-to-html/changes2html.py
@@ -142,21 +142,22 @@ class HTMLGenerator:
"""Format a single issue reference as an HTML anchor tag"""
return f'<a href="{url_prefix}{issue_id}">{label}</a>'
- def _extract_markdown_issue(self, text):
+ def _find_markdown_issue_matches(self, text):
"""
- Extract markdown-formatted JIRA/GitHub issues like [SOLR-123](url) or
[PR#123](url).
- Returns (issue_link_html, text_without_issue) or (None, text) if not
found.
+ Find every markdown-formatted JIRA/GitHub issue reference in text,
+ e.g. [SOLR-123](url) or [PR#123](url) -- an item can reference more
+ than one issue. Returns a list of (start, end, issue_link_html)
+ tuples in the order they appear.
"""
+ matches = []
for pattern, url_prefix, label_fmt in self.issue_patterns:
- match = re.search(pattern, text)
- if match:
+ for match in re.finditer(pattern, text):
issue_id = match.group(1)
label = label_fmt.format(issue_id)
issue_html = self._format_issue_link(url_prefix, issue_id,
label)
- text_without = (text[:match.start()] +
text[match.end():]).strip()
- return issue_html, text_without
-
- return None, text
+ matches.append((match.start(), match.end(), issue_html))
+ matches.sort(key=lambda m: m[0])
+ return matches
def _extract_plain_pr_references(self, text):
"""
@@ -187,17 +188,39 @@ class HTMLGenerator:
def extract_issue_from_text(self, text):
"""
- Extract the first issue reference from text.
- Tries in order: markdown JIRA/GitHub issues, plain GitHub PR
references.
- Returns (issue_link_html, text_without_issue) or (None, text) if not
found.
+ Extract issue reference(s) from text.
+ Tries markdown-formatted JIRA/GitHub issues first -- there may be
+ more than one, e.g. two related JIRA issues on the same change --
+ falling back to a plain GitHub PR reference like "#123" if none
+ are found.
+
+ Returns (issue_link_html_or_None, description_or_None, tail).
+ When markdown issues were found, `description` is the text before
+ the first issue reference and `tail` is everything from there on
+ with the issue reference(s) removed (normally just the trailing
+ author "(...)" groups). Author extraction must be bounded to
+ `tail`, never `description`, so a parenthetical at the end of the
+ description (e.g. an acronym expansion) is never mistaken for an
+ author group. When `description` is None, no markdown issue was
+ found and `tail` is just `text` with any plain PR reference
+ removed, if present.
"""
- # Try markdown-formatted issues first
- issue_html, text_without = self._extract_markdown_issue(text)
- if issue_html:
- return issue_html, text_without
+ matches = self._find_markdown_issue_matches(text)
+ if matches:
+ first_start = matches[0][0]
+ parts = []
+ pieces = []
+ last_end = first_start
+ for start, end, issue_html in matches:
+ pieces.append(text[last_end:start])
+ parts.append(issue_html)
+ last_end = end
+ pieces.append(text[last_end:])
+ return ' '.join(parts), text[:first_start], ''.join(pieces)
# Fall back to plain GitHub PR references
- return self._extract_plain_pr_references(text)
+ issue_html, text_without = self._extract_plain_pr_references(text)
+ return issue_html, None, text_without
def _format_single_author(self, author_text):
"""
@@ -372,11 +395,21 @@ class HTMLGenerator:
Format: [ISSUE](url) description (author1) (author2)
Output: <a href>ISSUE</a>: description<br><span
class="attrib">(authors)</span>
"""
- # Extract the issue
- issue_html, text_after_issue = self.extract_issue_from_text(item_text)
-
- # Always try to extract authors, whether or not we found an issue
- authors_list, description = self.extract_authors(text_after_issue if
issue_html else item_text)
+ # Extract the issue(s)
+ issue_html, description_head, tail =
self.extract_issue_from_text(item_text)
+
+ if description_head is not None:
+ # Markdown issue(s) found: bound author extraction to the tail
+ # so it can't reach back into the description.
+ authors_list, tail_remainder = self.extract_authors(tail)
+ description = description_head.strip()
+ tail_remainder = tail_remainder.strip()
+ if tail_remainder:
+ description = f'{description} {tail_remainder}'.strip()
+ else:
+ # No markdown issue (plain PR reference, or no issue at all):
+ # extract authors from the whole remaining text, as before.
+ authors_list, description = self.extract_authors(tail if
issue_html else item_text)
if issue_html:
# We have an issue link