From d2593a822a112e29c317ac03bb1e10e722b0daff Mon Sep 17 00:00:00 2001
From: nagasrisai <59650078+nagasrisai@users.noreply.github.com>
Date: Wed, 18 Mar 2026 17:40:49 +0530
Subject: [PATCH 1/5] fix: remap dead hg.python.org release-notes URLs to
GitHub
Adds a corrected_release_notes_url property to the Release model that
converts old Mercurial-hosted URLs (hg.python.org, now unreachable) to
their equivalent paths on GitHub, so legacy release pages still have
working changelog links.
Closes #2865
---
apps/downloads/models.py | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/apps/downloads/models.py b/apps/downloads/models.py
index 8af0a1c1d..c3dc00d52 100644
--- a/apps/downloads/models.py
+++ b/apps/downloads/models.py
@@ -114,6 +114,29 @@ def get_absolute_url(self):
return self.release_page.get_absolute_url()
return reverse("download:download_release_detail", kwargs={"release_slug": self.slug})
+ @property
+ def corrected_release_notes_url(self):
+ """Return the release notes URL, converting dead hg.python.org links to GitHub.
+
+ Old Mercurial-hosted URLs (hg.python.org) are no longer reachable.
+ This property remaps them to their equivalent paths on GitHub so that
+ the "Release notes" links on the downloads page still work for legacy
+ releases (3.3.6 and earlier).
+
+ Example::
+
+ http://hg.python.org/cpython/file/v3.3.6/Misc/NEWS
+ → https://github.com/python/cpython/blob/v3.3.6/Misc/NEWS
+ """
+ url = self.release_notes_url
+ if not url:
+ return url
+ match = re.match(r"https?://hg\.python\.org/cpython/file/([^/]+)/(.+)", url)
+ if match:
+ tag, path = match.group(1), match.group(2)
+ return f"https://github.com/python/cpython/blob/{tag}/{path}"
+ return url
+
def download_file_for_os(self, os_slug):
"""Given an OS slug return the appropriate download file."""
try:
@@ -430,3 +453,4 @@ class Meta:
violation_error_message="All file URLs must begin with 'https://www.python.org/'",
),
]
+
From 4830b9ce34d7a692ea8e4bc2dd9851f7667911ef Mon Sep 17 00:00:00 2001
From: nagasrisai <59650078+nagasrisai@users.noreply.github.com>
Date: Wed, 18 Mar 2026 17:40:59 +0530
Subject: [PATCH 2/5] fix(template): use corrected_release_notes_url on
downloads index
Use the new corrected_release_notes_url property so that legacy
hg.python.org links are converted to GitHub URLs before rendering.
Also guard the anchor so it degrades gracefully when the URL is empty.
---
apps/downloads/templates/downloads/index.html | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/apps/downloads/templates/downloads/index.html b/apps/downloads/templates/downloads/index.html
index d87b32823..1c04f5df0 100644
--- a/apps/downloads/templates/downloads/index.html
+++ b/apps/downloads/templates/downloads/index.html
@@ -76,7 +76,7 @@
{{ r.name }}
{{ r.release_date|date }}
Download
- Release notes
+ {% if r.corrected_release_notes_url %}Release notes{% else %}Release notes{% endif %}
{% endfor %}
@@ -127,3 +127,4 @@
{% box 'download-pgp' %}
{% endblock content %}
+
From b0d42208f28d6337b0e5b42a5669aa40c15e79a8 Mon Sep 17 00:00:00 2001
From: nagasrisai <59650078+nagasrisai@users.noreply.github.com>
Date: Wed, 18 Mar 2026 17:41:04 +0530
Subject: [PATCH 3/5] test: add tests for corrected_release_notes_url property
Covers hg URL conversion, https variant, modern URLs left unchanged,
and empty URL passthrough.
---
apps/downloads/tests/test_models.py | 50 +++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/apps/downloads/tests/test_models.py b/apps/downloads/tests/test_models.py
index d1d4c97b3..1426cccd3 100644
--- a/apps/downloads/tests/test_models.py
+++ b/apps/downloads/tests/test_models.py
@@ -311,3 +311,53 @@ def test_release_file_urls_not_python_dot_org(self):
name="Windows installer draft",
**kwargs,
)
+
+class ReleaseNotesURLTests(BaseDownloadTests):
+ """Tests for Release.corrected_release_notes_url property."""
+
+ def test_hg_url_converted_to_github(self):
+ """An hg.python.org URL is remapped to its GitHub equivalent."""
+ release = Release.objects.create(
+ version=Release.PYTHON3,
+ name="Python 3.3.6",
+ is_published=True,
+ release_notes_url="http://hg.python.org/cpython/file/v3.3.6/Misc/NEWS",
+ )
+ self.assertEqual(
+ release.corrected_release_notes_url,
+ "https://github.com/python/cpython/blob/v3.3.6/Misc/NEWS",
+ )
+
+ def test_https_hg_url_also_converted(self):
+ """An https hg.python.org URL is also remapped to GitHub."""
+ release = Release.objects.create(
+ version=Release.PYTHON3,
+ name="Python 3.2.6",
+ is_published=True,
+ release_notes_url="https://hg.python.org/cpython/file/v3.2.6/Misc/NEWS",
+ )
+ self.assertEqual(
+ release.corrected_release_notes_url,
+ "https://github.com/python/cpython/blob/v3.2.6/Misc/NEWS",
+ )
+
+ def test_modern_url_returned_unchanged(self):
+ """A non-hg URL (e.g. already on GitHub) is returned unchanged."""
+ url = "https://github.com/python/cpython/blob/v3.12.0/Misc/NEWS.d"
+ release = Release.objects.create(
+ version=Release.PYTHON3,
+ name="Python 3.12.0",
+ is_published=True,
+ release_notes_url=url,
+ )
+ self.assertEqual(release.corrected_release_notes_url, url)
+
+ def test_empty_url_returns_empty(self):
+ """An empty release_notes_url returns an empty string."""
+ release = Release.objects.create(
+ version=Release.PYTHON3,
+ name="Python 3.11.0",
+ is_published=True,
+ release_notes_url="",
+ )
+ self.assertEqual(release.corrected_release_notes_url, "")
From 3f0c60f18bde98f0666004deee78e37076eeafff Mon Sep 17 00:00:00 2001
From: nagasrisai <59650078+nagasrisai@users.noreply.github.com>
Date: Wed, 1 Apr 2026 15:49:18 +0530
Subject: [PATCH 4/5] refactor: replace regex with str.startswith and add
raw-file URL mapping
Per review suggestion from hugovk:
- Replace re.match with str.startswith for clarity (re.match anchors start
but not end, which can be confusing)
- Also handle hg.python.org/cpython/raw-file/ URLs, mapping them to
raw.githubusercontent.com (e.g. Python 2.7.3 release notes use this form)
---
apps/downloads/models.py | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
diff --git a/apps/downloads/models.py b/apps/downloads/models.py
index c3dc00d52..e3829fcdb 100644
--- a/apps/downloads/models.py
+++ b/apps/downloads/models.py
@@ -123,18 +123,29 @@ def corrected_release_notes_url(self):
the "Release notes" links on the downloads page still work for legacy
releases (3.3.6 and earlier).
- Example::
+ Examples::
http://hg.python.org/cpython/file/v3.3.6/Misc/NEWS
→ https://github.com/python/cpython/blob/v3.3.6/Misc/NEWS
+
+ http://hg.python.org/cpython/raw-file/v2.7.3/Misc/NEWS
+ → https://raw.githubusercontent.com/python/cpython/v2.7.3/Misc/NEWS
"""
url = self.release_notes_url
if not url:
return url
- match = re.match(r"https?://hg\.python\.org/cpython/file/([^/]+)/(.+)", url)
- if match:
- tag, path = match.group(1), match.group(2)
- return f"https://github.com/python/cpython/blob/{tag}/{path}"
+ for prefix in (
+ "http://hg.python.org/cpython/file/",
+ "https://hg.python.org/cpython/file/",
+ ):
+ if url.startswith(prefix):
+ return "https://github.com/python/cpython/blob/" + url[len(prefix):]
+ for prefix in (
+ "http://hg.python.org/cpython/raw-file/",
+ "https://hg.python.org/cpython/raw-file/",
+ ):
+ if url.startswith(prefix):
+ return "https://raw.githubusercontent.com/python/cpython/" + url[len(prefix):]
return url
def download_file_for_os(self, os_slug):
From d69f5bbb04410a3cedd3b2999e6c0bc906b975e9 Mon Sep 17 00:00:00 2001
From: nagasrisai <59650078+nagasrisai@users.noreply.github.com>
Date: Wed, 1 Apr 2026 15:49:36 +0530
Subject: [PATCH 5/5] test: add test cases for raw-file hg.python.org URL
remapping
---
apps/downloads/tests/test_models.py | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/apps/downloads/tests/test_models.py b/apps/downloads/tests/test_models.py
index 1426cccd3..159c17e18 100644
--- a/apps/downloads/tests/test_models.py
+++ b/apps/downloads/tests/test_models.py
@@ -361,3 +361,29 @@ def test_empty_url_returns_empty(self):
release_notes_url="",
)
self.assertEqual(release.corrected_release_notes_url, "")
+
+ def test_raw_file_hg_url_converted_to_github_raw(self):
+ """A raw-file hg.python.org URL is remapped to raw.githubusercontent.com."""
+ release = Release.objects.create(
+ version=Release.PYTHON2,
+ name="Python 2.7.3",
+ is_published=True,
+ release_notes_url="http://hg.python.org/cpython/raw-file/v2.7.3/Misc/NEWS",
+ )
+ self.assertEqual(
+ release.corrected_release_notes_url,
+ "https://raw.githubusercontent.com/python/cpython/v2.7.3/Misc/NEWS",
+ )
+
+ def test_https_raw_file_hg_url_also_converted(self):
+ """An https raw-file hg.python.org URL is also remapped to raw.githubusercontent.com."""
+ release = Release.objects.create(
+ version=Release.PYTHON2,
+ name="Python 2.6.9",
+ is_published=True,
+ release_notes_url="https://hg.python.org/cpython/raw-file/v2.6.9/Misc/NEWS",
+ )
+ self.assertEqual(
+ release.corrected_release_notes_url,
+ "https://raw.githubusercontent.com/python/cpython/v2.6.9/Misc/NEWS",
+ )