Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
305 changes: 305 additions & 0 deletions .github/workflows/update-changelog.yml
Comment thread
mhduiy marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
name: update changelog

on:
workflow_dispatch:
inputs:
version:
description: "Target version; leave empty to auto bump from debian/changelog"
required: false
type: string
name:
description: "Maintainer name used in debian/changelog"
required: true
type: string
email:
description: "Maintainer email used in debian/changelog"
required: true
type: string
base_branch:
description: "Base branch to read and target"
required: false
default: master
type: string
distribution:
description: "Changelog distribution"
required: false
default: unstable
type: string
create_pr:
description: "Create a pull request after generating changelog"
required: false
default: true
type: boolean
workflow_call:
inputs:
version:
required: false
type: string
name:
required: true
type: string
email:
required: true
type: string
base_branch:
required: false
type: string
default: master
distribution:
required: false
type: string
default: unstable
create_pr:
required: false
type: boolean
default: true
secrets:
APP_ID:
required: false
APP_PRIVATE_KEY:
required: false
outputs:
pr_url:
value: ${{ jobs.update_changelog.outputs.pr_url }}
pr_number:
value: ${{ jobs.update_changelog.outputs.pr_number }}
version:
value: ${{ jobs.update_changelog.outputs.version }}

permissions:
contents: write
pull-requests: write

jobs:
update_changelog:
runs-on: ubuntu-latest
outputs:
pr_url: ${{ steps.create_pr.outputs.pull-request-url }}
pr_number: ${{ steps.create_pr.outputs.pull-request-number }}
version: ${{ steps.prepare.outputs.version }}
steps:
- name: Create GitHub App token
id: app_token
if: ${{ inputs.create_pr }}
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
permission-contents: write
permission-pull-requests: write

- name: Checkout base branch
uses: actions/checkout@v7
with:
ref: ${{ inputs.base_branch }}
fetch-depth: 1
persist-credentials: false

- name: Install changelog tools
run: |
sudo apt-get update
sudo apt-get install -y devscripts

- name: Prepare changelog data
id: prepare
shell: python
env:
INPUT_VERSION: ${{ inputs.version }}
INPUT_BASE_BRANCH: ${{ inputs.base_branch }}
INPUT_DISTRIBUTION: ${{ inputs.distribution }}
GITHUB_TOKEN: ${{ steps.app_token.outputs.token || github.token }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
import json
import os
import pathlib
import re
import subprocess
import urllib.error
import urllib.parse
import urllib.request

def output_line(name, value):
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
fh.write(f"{name}={value}\n")

def get_current_version(changelog_path):
try:
result = subprocess.run(
["dpkg-parsechangelog", "-l", str(changelog_path), "-S", "Version"],
capture_output=True,
text=True,
check=True,
)
return result.stdout.strip()
except Exception:
first_line = changelog_path.read_text(encoding="utf-8").splitlines()[0].strip()
match = re.match(r"^[^(]+\(([^)]+)\)", first_line)
if not match:
raise RuntimeError(f"cannot parse version from {changelog_path}")
return match.group(1)

def bump_version(version):
epoch = ""
value = version
if ":" in value:
epoch_part, value = value.split(":", 1)
if epoch_part.isdigit():
epoch = epoch_part + ":"
if "-" in value:
value = value.rsplit("-", 1)[0]
parts = value.split(".")
if len(parts) < 2 or any(not part.isdigit() for part in parts):
raise RuntimeError(
"cannot auto bump complex Debian version "
f"{version!r}; pass the workflow input 'version' explicitly"
)
parts[-1] = str(int(parts[-1]) + 1)
return epoch + ".".join(parts)

def github_get(url):
headers = {
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "deepin-autopack-update-changelog",
"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}",
}
req = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
body = resp.read().decode("utf-8")
data = json.loads(body) if body else None
resp_headers = {k.lower(): v for k, v in resp.headers.items()}
return data, resp_headers
except urllib.error.HTTPError as exc:
body = exc.read().decode("utf-8", errors="replace")
if len(body) > 2000:
body = body[:2000] + "...<truncated>"
raise RuntimeError(
f"GitHub API request failed: status={exc.code}, url={url}, body={body}"
) from exc

def parse_link_header(value):
links = {}
if not value:
return links
for item in value.split(","):
item = item.strip()
match = re.match(r'<([^>]+)>;\s*rel="([^"]+)"', item)
if match:
links[match.group(2)] = match.group(1)
return links

def commit_titles_since_changelog(owner, repo, base_branch, head_sha):
base_url = f"https://api.github.com/repos/{owner}/{repo}"
commits_url = f"{base_url}/commits?{urllib.parse.urlencode({'sha': base_branch, 'path': 'debian/changelog', 'per_page': 1})}"
commits, _ = github_get(commits_url)
if not commits:
raise RuntimeError(f"no commit found for debian/changelog on {base_branch}")

base_sha = commits[0]["sha"]
compare_url = f"{base_url}/compare/{urllib.parse.quote(base_sha, safe='')}...{urllib.parse.quote(head_sha, safe='')}?per_page=100"
titles = []
next_url = compare_url
while next_url:
page, page_headers = github_get(next_url)
for item in page.get("commits", []):
message = item.get("commit", {}).get("message", "")
title = message.splitlines()[0].strip()
if title:
titles.append(title)
next_url = parse_link_header(page_headers.get("link")).get("next")
return base_sha, titles

repo_root = pathlib.Path(os.environ["GITHUB_WORKSPACE"])
changelog_path = repo_root / "debian" / "changelog"
if not changelog_path.exists():
raise RuntimeError(f"missing changelog: {changelog_path}")

requested_version = os.environ.get("INPUT_VERSION", "").strip()
distribution = os.environ.get("INPUT_DISTRIBUTION", "").strip() or "unstable"
base_branch = os.environ.get("INPUT_BASE_BRANCH", "").strip() or "master"
owner, repo = os.environ["GITHUB_REPOSITORY"].split("/", 1)
head_sha = subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip()

current_version = get_current_version(changelog_path)
version = requested_version or bump_version(current_version)
if not version:
raise RuntimeError(f"unable to determine version from {current_version!r}")

base_sha, titles = commit_titles_since_changelog(owner, repo, base_branch, head_sha)
safe_version = re.sub(r"[^A-Za-z0-9._-]+", "-", version).strip("-")

output_line("version", version)
output_line("safe_version", safe_version)
output_line("distribution", distribution)
output_line("base_branch", base_branch)
output_line("base_sha", base_sha)
output_line("head_sha", head_sha)
output_line("titles_json", json.dumps(titles, ensure_ascii=False))

- name: Generate changelog
shell: python
env:
VERSION: ${{ steps.prepare.outputs.version }}
DISTRIBUTION: ${{ steps.prepare.outputs.distribution }}
TITLES_JSON: ${{ steps.prepare.outputs.titles_json }}
DEBFULLNAME: ${{ inputs.name }}
DEBEMAIL: ${{ inputs.email }}
TZ: Asia/Shanghai
run: |
import json
import os
import subprocess

version = os.environ["VERSION"].strip()
distribution = os.environ["DISTRIBUTION"].strip() or "unstable"
titles = json.loads(os.environ["TITLES_JSON"])
if not titles:
titles = [f"Release {version}"]

subprocess.run(
["dch", "-v", version, "-D", distribution, titles[0]],
check=True,
)
for title in titles[1:]:
subprocess.run(["dch", "-a", title], check=True)

- name: Show changelog diff
if: ${{ !inputs.create_pr }}
run: git diff -- debian/changelog

- name: Upload generated changelog
if: ${{ !inputs.create_pr }}
uses: actions/upload-artifact@v4
with:
name: generated-changelog
path: debian/changelog

- name: Create pull request
id: create_pr
if: ${{ inputs.create_pr }}
uses: peter-evans/create-pull-request@v6
with:
token: ${{ steps.app_token.outputs.token }}
branch: automation/update-changelog/${{ steps.prepare.outputs.safe_version }}
title: "chore: update changelog to ${{ steps.prepare.outputs.version }}"
commit-message: "chore: update changelog to ${{ steps.prepare.outputs.version }}"
body: |
Automated changelog update.

- Version: `${{ steps.prepare.outputs.version }}`
- Base branch: `${{ steps.prepare.outputs.base_branch }}`
- Base changelog commit: `${{ steps.prepare.outputs.base_sha }}`
- Head commit: `${{ steps.prepare.outputs.head_sha }}`
add-paths: |
debian/changelog
delete-branch: true

- name: Show pull request result
if: ${{ inputs.create_pr }}
run: |
echo "Version: ${{ steps.prepare.outputs.version }}"
echo "Pull Request Number: ${{ steps.create_pr.outputs.pull-request-number }}"
echo "Pull Request URL: ${{ steps.create_pr.outputs.pull-request-url }}"
7 changes: 7 additions & 0 deletions repos/linuxdeepin/dcc-insider-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,12 @@
".github/workflows/call-tag-build.yml"
],
"dest": "linuxdeepin/dcc-insider-plugin/.github/workflows/call-tag-build.yml"
},
{
"branch": [
"master"
],
"src": "workflow-templates/call-update-changelog.yml",
"dest": "linuxdeepin/dcc-insider-plugin/.github/workflows/call-update-changelog.yml"
}
]
7 changes: 7 additions & 0 deletions repos/linuxdeepin/dde-account-faces.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,12 @@
".github/workflows/call-tag-build.yml"
],
"dest": "linuxdeepin/dde-account-faces/.github/workflows/call-tag-build.yml"
},
{
"branch": [
"master"
],
"src": "workflow-templates/call-update-changelog.yml",
"dest": "linuxdeepin/dde-account-faces/.github/workflows/call-update-changelog.yml"
}
]
7 changes: 7 additions & 0 deletions repos/linuxdeepin/dde-api-proxy.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,12 @@
".github/workflows/call-tag-build.yml"
],
"dest": "linuxdeepin/dde-api-proxy/.github/workflows/call-tag-build.yml"
},
{
"branch": [
"master"
],
"src": "workflow-templates/call-update-changelog.yml",
"dest": "linuxdeepin/dde-api-proxy/.github/workflows/call-update-changelog.yml"
}
]
7 changes: 7 additions & 0 deletions repos/linuxdeepin/dde-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,12 @@
".github/workflows/call-tag-build.yml"
],
"dest": "linuxdeepin/dde-api/.github/workflows/call-tag-build.yml"
},
{
"branch": [
"master"
],
"src": "workflow-templates/call-update-changelog.yml",
"dest": "linuxdeepin/dde-api/.github/workflows/call-update-changelog.yml"
}
]
7 changes: 7 additions & 0 deletions repos/linuxdeepin/dde-app-services.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,12 @@
],
"src": "issue-templates/unit-test-report.md",
"dest": "linuxdeepin/dde-app-services/.github/ISSUE_TEMPLATE/unit-test-report.md"
},
{
"branch": [
"master"
],
"src": "workflow-templates/call-update-changelog.yml",
"dest": "linuxdeepin/dde-app-services/.github/workflows/call-update-changelog.yml"
}
]
7 changes: 7 additions & 0 deletions repos/linuxdeepin/dde-appearance.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,12 @@
".github/workflows/call-tag-build.yml"
],
"dest": "linuxdeepin/dde-appearance/.github/workflows/call-tag-build.yml"
},
{
"branch": [
"master"
],
"src": "workflow-templates/call-update-changelog.yml",
"dest": "linuxdeepin/dde-appearance/.github/workflows/call-update-changelog.yml"
}
]
Loading
Loading