This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git
The following commit(s) were added to refs/heads/main by this push:
new a1eaef0 Allow users to set any revision as the latest
a1eaef0 is described below
commit a1eaef046af6b53c47abfe7aa186a7b06949a438
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Apr 9 17:12:48 2025 +0100
Allow users to set any revision as the latest
---
atr/revision.py | 11 +++++----
atr/routes/draft.py | 48 +++++++++++++++++++++++++++++++++++++-
atr/templates/draft-evaluate.html | 5 +++-
atr/templates/draft-revisions.html | 20 ++++++++++++----
4 files changed, 72 insertions(+), 12 deletions(-)
diff --git a/atr/revision.py b/atr/revision.py
index 6a58875..d3b1b66 100644
--- a/atr/revision.py
+++ b/atr/revision.py
@@ -109,8 +109,9 @@ async def create_and_manage(
await aioshutil.rmtree(new_revision_dir) # type:
ignore[call-arg]
-async def latest_info(project_name: str, version_name: str) -> tuple[str |
None, datetime.datetime | None]:
- """Get the editor and timestamp of the latest revision from the
filesystem."""
+async def latest_info(project_name: str, version_name: str) -> tuple[str |
None, str | None, datetime.datetime | None]:
+ """Get the name, editor, and timestamp of the latest revision."""
+ revision_name_from_link: str | None = None
editor: str | None = None
timestamp: datetime.datetime | None = None
@@ -120,14 +121,14 @@ async def latest_info(project_name: str, version_name:
str) -> tuple[str | None,
latest_symlink_path = release_dir / "latest"
if await aiofiles.os.path.islink(latest_symlink_path):
- revision_name = await
aiofiles.os.readlink(str(latest_symlink_path))
- parts = revision_name.split("@", 1)
+ revision_name_from_link = await
aiofiles.os.readlink(str(latest_symlink_path))
+ parts = revision_name_from_link.split("@", 1)
if len(parts) == 2:
editor = parts[0]
dt_obj = datetime.datetime.strptime(parts[1][:-1],
"%Y-%m-%dT%H.%M.%S.%f")
timestamp = dt_obj.replace(tzinfo=datetime.UTC)
- return editor, timestamp
+ return revision_name_from_link, editor, timestamp
async def _manage_draft_revision_find_parent(
diff --git a/atr/routes/draft.py b/atr/routes/draft.py
index 375d566..57a4695 100644
--- a/atr/routes/draft.py
+++ b/atr/routes/draft.py
@@ -413,7 +413,7 @@ async def evaluate(session: routes.CommitterSession,
project_name: str, version_
# if (latest_check_result is None) or (check_result.created >
latest_check_result):
# latest_check_result = check_result.created
- revision_editor, revision_time = await revision.latest_info(project_name,
version_name)
+ revision_name_from_link, revision_editor, revision_time = await
revision.latest_info(project_name, version_name)
delete_file_form = await DeleteFileForm.create_form()
return await quart.render_template(
@@ -438,6 +438,7 @@ async def evaluate(session: routes.CommitterSession,
project_name: str, version_
delete_file_form=delete_file_form,
revision_editor=revision_editor,
revision_time=revision_time,
+ revision_name_from_link=revision_name_from_link,
)
@@ -614,6 +615,45 @@ async def promote(session: routes.CommitterSession) -> str
| response.Response:
)
[email protected]("/draft/revision/set/<project_name>/<version_name>",
methods=["POST"])
+async def revision_set(session: routes.CommitterSession, project_name: str,
version_name: str) -> response.Response:
+ """Set a specific revision as the latest for a candidate draft."""
+ if not any((p.name == project_name) for p in (await
session.user_projects)):
+ raise base.ASFQuartException("You do not have access to this project",
errorcode=403)
+
+ form_data = await quart.request.form
+ revision_name = form_data.get("revision_name")
+ if not revision_name:
+ raise base.ASFQuartException("Missing revision name", errorcode=400)
+
+ release_dir = util.get_release_candidate_draft_dir() / project_name /
version_name
+ target_revision_dir = release_dir / revision_name
+ latest_symlink_path = release_dir / "latest"
+
+ # Check that the target revision directory exists
+ if not await aiofiles.os.path.isdir(target_revision_dir):
+ raise base.ASFQuartException("Target revision directory not found",
errorcode=404)
+
+ try:
+ # Target must be relative for the symlink
+ await util.update_atomic_symlink(latest_symlink_path, revision_name)
+ except Exception as e:
+ logging.exception("Error updating latest symlink:")
+ return await session.redirect(
+ revisions,
+ error=f"Failed to set revision {revision_name} as latest: {e!s}",
+ project_name=project_name,
+ version_name=version_name,
+ )
+
+ return await session.redirect(
+ revisions,
+ success=f"Revision {revision_name} set as latest",
+ project_name=project_name,
+ version_name=version_name,
+ )
+
+
@routes.committer("/draft/revisions/<project_name>/<version_name>")
async def revisions(session: routes.CommitterSession, project_name: str,
version_name: str) -> str:
"""Show the revision history for a release candidate draft."""
@@ -657,6 +697,11 @@ async def revisions(session: routes.CommitterSession,
project_name: str, version
parent_links_result = await data.execute(query)
parent_map = {link.key: link.value for link in
parent_links_result.scalars().all()}
+ # Determine the current latest revision
+ latest_revision_name: str | None = None
+ with contextlib.suppress(FileNotFoundError, OSError):
+ latest_revision_name = await aiofiles.os.readlink(str(release_dir
/ "latest"))
+
revision_history = []
prev_revision_files: set[pathlib.Path] | None = None
prev_revision_name: str | None = None
@@ -681,6 +726,7 @@ async def revisions(session: routes.CommitterSession,
project_name: str, version
version_name=version_name,
release=release,
revision_history=list(reversed(revision_history)),
+ latest_revision_name=latest_revision_name,
)
diff --git a/atr/templates/draft-evaluate.html
b/atr/templates/draft-evaluate.html
index c392491..6ac89d9 100644
--- a/atr/templates/draft-evaluate.html
+++ b/atr/templates/draft-evaluate.html
@@ -50,7 +50,10 @@
</p>
{% if revision_time %}
<p>
- <strong>Revision:</strong> {{ revision_time.strftime("%Y-%m-%d
%H:%M:%S") }}
+ <strong>Revision:</strong>
+ <a href="{{ as_url(routes.draft.revisions,
project_name=project_name, version_name=version_name) }}#{{
revision_name_from_link }}">
+ {{ revision_time.strftime("%Y-%m-%d %H:%M:%S") }}
+ </a>
</p>
{% endif %}
</div>
diff --git a/atr/templates/draft-revisions.html
b/atr/templates/draft-revisions.html
index 71c2145..22ac1a6 100644
--- a/atr/templates/draft-revisions.html
+++ b/atr/templates/draft-revisions.html
@@ -17,9 +17,10 @@
{% for revision in revision_history %}
<div id="{{ revision.name }}" class="card mb-3">
<div class="card-header d-flex justify-content-between
align-items-center">
- <h2 class="fs-6 my-2 mx-0 p-0 border-0">
+ <h2 class="fs-6 my-2 mx-0 p-0 border-0 atr-sans">
<a href="#{{ revision.name }}"
- class="fw-bold atr-sans text-decoration-none text-body">{{
revision.name }}</a>
+ class="fw-bold text-decoration-none text-body">{{ revision.name
}}</a>
+ {% if revision.name == latest_revision_name %}<span class="badge
bg-primary ms-2">Latest</span>{% endif %}
</h2>
<span class="fs-6 text-muted">
{% if revision.timestamp %}
@@ -43,7 +44,7 @@
<p class="fst-italic text-muted">No file changes detected in this
revision.</p>
{% else %}
{% if revision.added %}
- <p class="fs-6 fw-bold mt-3">Added files</p>
+ <h3 class="fs-6 fw-semibold mt-3 atr-sans">Added files</h3>
<ul class="list-group list-group-flush mb-2">
{% for file in revision.added %}
<li class="list-group-item list-group-item-success py-1 px-3
small rounded-2">{{ file }}</li>
@@ -52,7 +53,7 @@
{% endif %}
{% if revision.removed %}
- <p class="fs-6 fw-bold mt-3">Removed files</p>
+ <h3 class="fs-6 fw-semibold mt-3 atr-sans">Removed files</h3>
<ul class="list-group list-group-flush mb-2">
{% for file in revision.removed %}
<li class="list-group-item list-group-item-danger py-1 px-3
small rounded-2">{{ file }}</li>
@@ -61,7 +62,7 @@
{% endif %}
{% if revision.modified %}
- <p class="fs-6 fw-bold mt-3">Modified files</p>
+ <h3 class="fs-6 fw-semibold mt-3 atr-sans">Modified files</h3>
<ul class="list-group list-group-flush mb-2">
{% for file in revision.modified %}
<li class="list-group-item list-group-item-warning py-1 px-3
small rounded-2">{{ file }}</li>
@@ -69,6 +70,15 @@
</ul>
{% endif %}
{% endif %}
+
+ <h3 class="fs-6 fw-semibold mt-3 atr-sans">Actions</h3>
+ <div class="mt-3">
+ <form method="post"
+ action="{{ as_url(routes.draft.revision_set,
project_name=project_name, version_name=version_name) }}">
+ <input type="hidden" name="revision_name" value="{{
revision.name }}" />
+ <button type="submit" class="btn btn-sm btn-outline-danger">Set
this revision as latest</button>
+ </form>
+ </div>
</div>
</div>
{% endfor %}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]