forked from tgstation/tgstation
-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Changelog Validation & Merge Upstream Workflow (#992)
## Что этот PR делает Портирует с пары валидацию чейнджлога и мерге апстрим. Предложения по эмодзи и контексту перевода приветствуются. В боте думаю оставить только отправку в дис без сохранения в БД. ТГ чейнджлог в файле скорее всего может быть использован для игры. rscadd="✨" bugfix="🩹" rscdel="🗑️" qol="🌿" sound="🎶" image="🖼️" map="🗺️" spellcheck="📝" balance="⚖️" code_imp="🔨" refactor="🛠️" config="⚙️" admin="🪄" server="🛡️" ## Тестирование ![image](https://github.com/user-attachments/assets/4c9c67a1-cfb9-4d57-a225-1e32c955a943) Тестовый мерге: m-dzianishchyts#6 Актион: https://github.com/m-dzianishchyts/BandaStation/actions/runs/12772336604/job/35601875054 Публикация в дисе: https://discord.com/channels/1097181193939730453/1097880873992454164/1328770007169237122 ## Summary by Sourcery Update the changelog validation process and implement a workflow for merging upstream changes. New Features: - Add automatic changelog translation using OpenAI API. Enhancements: - Improve changelog format and validation rules. - Standardize changelog tags and their meanings. Build: - Add scripts for merging upstream changes and validating changelogs. CI: - Add a continuous integration workflow to automatically merge upstream changes and validate changelogs.
- Loading branch information
1 parent
e045a4c
commit 56cbc9d
Showing
9 changed files
with
855 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
name: Changelog validation | ||
|
||
permissions: | ||
contents: read | ||
pull-requests: write | ||
issues: write | ||
|
||
on: | ||
pull_request_target: | ||
types: [opened, reopened, edited, labeled, unlabeled, ready_for_review] | ||
|
||
jobs: | ||
CheckCL: | ||
runs-on: ubuntu-latest | ||
if: github.base_ref == 'master' && github.event.pull_request.draft == false | ||
|
||
steps: | ||
- id: create_token | ||
uses: actions/create-github-app-token@v1 | ||
with: | ||
app-id: ${{ secrets.APP_ID }} | ||
private-key: ${{ secrets.PRIVATE_KEY }} | ||
|
||
- run: echo "GH_TOKEN=${{ steps.create_token.outputs.token }}" >> "$GITHUB_ENV" | ||
|
||
- name: Downloading scripts | ||
run: | | ||
wget https://raw.githubusercontent.com/${{ github.repository }}/${{ github.base_ref }}/tools/changelog/changelog_utils.py | ||
wget https://raw.githubusercontent.com/${{ github.repository }}/${{ github.base_ref }}/tools/changelog/check_changelog.py | ||
wget https://raw.githubusercontent.com/${{ github.repository }}/${{ github.base_ref }}/tools/changelog/tags.yml | ||
- name: Installing Python | ||
uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 | ||
with: | ||
python-version: '3.x' | ||
|
||
- name: Installing deps | ||
run: | | ||
python -m pip install --upgrade pip | ||
pip install ruamel.yaml PyGithub | ||
- name: Changelog validation | ||
env: | ||
GITHUB_TOKEN: ${{ env.GH_TOKEN }} | ||
run: python check_changelog.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
name: Merge Upstream | ||
|
||
on: | ||
workflow_dispatch: | ||
|
||
jobs: | ||
merge-upstream: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- id: create_token | ||
uses: actions/create-github-app-token@v1 | ||
with: | ||
app-id: ${{ secrets.APP_ID }} | ||
private-key: ${{ secrets.PRIVATE_KEY }} | ||
|
||
- run: echo "GH_TOKEN=${{ steps.create_token.outputs.token }}" >> "$GITHUB_ENV" | ||
|
||
- name: Set up Python | ||
uses: actions/setup-python@v4 | ||
with: | ||
python-version: 3.x | ||
|
||
- name: Install dependencies | ||
run: | | ||
python -m pip install --upgrade pip | ||
pip install PyGithub openai | ||
- name: Download the script | ||
run: | | ||
wget https://raw.githubusercontent.com/${{ github.repository }}/${{ github.ref_name }}/tools/changelog/changelog_utils.py | ||
wget https://raw.githubusercontent.com/${{ github.repository }}/${{ github.ref_name }}/tools/merge-upstream/merge_upstream.py | ||
wget https://raw.githubusercontent.com/${{ github.repository }}/${{ github.ref_name }}/tools/merge-upstream/translation_context.txt | ||
- name: Run the script | ||
env: | ||
GITHUB_TOKEN: ${{ env.GH_TOKEN }} | ||
TARGET_REPO: 'ss220club/BandaStation' | ||
TARGET_BRANCH: 'master' | ||
UPSTREAM_REPO: 'tgstation/tgstation' | ||
UPSTREAM_BRANCH: 'master' | ||
MERGE_BRANCH: 'merge-upstream' | ||
CHANGELOG_AUTHOR: 'tgstation' | ||
TRANSLATE_CHANGES: 'true' | ||
OPENAI_API_KEY: ${{ secrets.ORG_EMPTY_TOKEN }} | ||
LOG_LEVEL: ${{ runner.debug && 'DEBUG' || 'INFO' }} | ||
run: | | ||
git config --global user.email "[email protected]" | ||
git config --global user.name "Upstream Sync" | ||
python3 -u merge_upstream.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import re | ||
import copy | ||
|
||
CL_INVALID = ":scroll: CL невалиден" | ||
CL_VALID = ":scroll: CL валиден" | ||
CL_NOT_NEEDED = ":scroll: CL не требуется" | ||
|
||
CL_BODY = re.compile(r"(:cl:|🆑)[ \t]*(?P<author>.+?)?\s*\n(?P<content>(.|\n)*?)\n/(:cl:|🆑)", re.MULTILINE) | ||
CL_SPLIT = re.compile(r"\s*(?:(?P<tag>\w+)\s*:)?\s*(?P<message>.*)") | ||
|
||
DISCORD_TAG_EMOJI = { | ||
"rscadd": ":sparkles:", | ||
"bugfix": ":adhesive_bandage:", | ||
"rscdel": ":wastebasket:", | ||
"qol": ":herb:", | ||
"sound": ":notes:", | ||
"image": ":frame_photo:", | ||
"map": ":map:", | ||
"spellcheck": ":pencil:", | ||
"balance": ":scales:", | ||
"code_imp": ":hammer:", | ||
"refactor": ":tools:", | ||
"config": ":gear:", | ||
"admin": ":magic_wand:", | ||
"server": ":shield:" | ||
} | ||
|
||
|
||
def build_changelog(pr: dict, tags_config: dict) -> dict: | ||
changelog = parse_changelog(pr.body, tags_config) | ||
if changelog is None: | ||
raise Exception("Failed to parse the changelog. Check changelog format.") | ||
changelog["author"] = changelog["author"] or pr.user.login | ||
return changelog | ||
|
||
|
||
def emojify_changelog(changelog: dict): | ||
changelog_copy = copy.deepcopy(changelog) | ||
for change in changelog_copy["changes"]: | ||
if change["tag"] in DISCORD_TAG_EMOJI: | ||
change["tag"] = DISCORD_TAG_EMOJI[change["tag"]] | ||
else: | ||
raise Exception(f"Invalid tag for emoji: {change}") | ||
return changelog_copy | ||
|
||
|
||
def validate_changelog(changelog: dict): | ||
if not changelog: | ||
raise Exception("No changelog.") | ||
if not changelog["author"]: | ||
raise Exception("The changelog has no author.") | ||
if len(changelog["changes"]) == 0: | ||
raise Exception("No changes found in the changelog. Use special label if changelog is not expected.") | ||
|
||
|
||
def parse_changelog(pr_body: str, tags_config: dict | None = None) -> dict | None: | ||
clean_pr_body = re.sub(r"<!--.*?-->", "", pr_body, flags=re.DOTALL) | ||
cl_parse_result = CL_BODY.search(clean_pr_body) | ||
if cl_parse_result is None: | ||
return None | ||
|
||
cl_changes = [] | ||
for cl_line in cl_parse_result.group("content").splitlines(): | ||
if not cl_line: | ||
continue | ||
change_parse_result = CL_SPLIT.search(cl_line) | ||
if not change_parse_result: | ||
raise Exception(f"Invalid change: '{cl_line}'") | ||
tag = change_parse_result["tag"] | ||
message = change_parse_result["message"] | ||
|
||
if tags_config and tag and tag not in tags_config['tags'].keys(): | ||
raise Exception(f"Invalid tag: '{cl_line}'. Valid tags: {', '.join(tags_config['tags'].keys())}") | ||
if not message: | ||
raise Exception(f"No message for change: '{cl_line}'") | ||
|
||
message = message.strip() | ||
|
||
if tags_config and message in list(tags_config['defaults'].values()): # Check to see if the tags are associated with something that isn't the default text | ||
raise Exception(f"Don't use default message for change: '{cl_line}'") | ||
if tag: | ||
cl_changes.append({ | ||
"tag": tags_config['tags'][tag] if tags_config else tag, | ||
"message": message | ||
}) | ||
# Append line without tag to the previous change | ||
else: | ||
if len(cl_changes): | ||
prev_change = cl_changes[-1] | ||
prev_change["message"] += f" {message}" | ||
else: | ||
raise Exception(f"Change with no tag: {cl_line}") | ||
|
||
if len(cl_changes) == 0: | ||
raise Exception("No changes found in the changelog. Use special label if changelog is not expected.") | ||
return { | ||
"author": str.strip(cl_parse_result.group("author") or "") or None, # I want this to be None, not empty | ||
"changes": cl_changes | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
""" | ||
DO NOT MANUALLY RUN THIS SCRIPT. | ||
--------------------------------- | ||
Expected environmental variables: | ||
----------------------------------- | ||
GITHUB_REPOSITORY: Github action variable representing the active repo (Action provided) | ||
GITHUB_TOKEN: A repository account token, this will allow the action to push the changes (Action provided) | ||
GITHUB_EVENT_PATH: path to JSON file containing the event info (Action provided) | ||
""" | ||
import os | ||
from pathlib import Path | ||
from ruamel.yaml import YAML | ||
from github import Github | ||
import json | ||
|
||
import changelog_utils | ||
|
||
# Blessed is the GoOnStAtIoN birb ZeWaKa for thinking of this first | ||
repo = os.getenv("GITHUB_REPOSITORY") | ||
token = os.getenv("GITHUB_TOKEN") | ||
event_path = os.getenv("GITHUB_EVENT_PATH") | ||
|
||
with open(event_path, 'r') as f: | ||
event_data = json.load(f) | ||
|
||
git = Github(token) | ||
repo = git.get_repo(repo) | ||
pr = repo.get_pull(event_data['number']) | ||
|
||
pr_body = pr.body or "" | ||
pr_author = pr.user.login | ||
pr_labels = pr.labels | ||
|
||
pr_is_mirror = pr.title.startswith("[MIRROR]") | ||
|
||
has_valid_label = False | ||
has_invalid_label = False | ||
cl_required = True | ||
for label in pr_labels: | ||
print("Found label: ", label.name) | ||
if label.name == changelog_utils.CL_NOT_NEEDED: | ||
print("No CL needed!") | ||
cl_required = False | ||
if label.name == changelog_utils.CL_VALID: | ||
has_valid_label = True | ||
if label.name == changelog_utils.CL_INVALID: | ||
has_invalid_label = True | ||
|
||
if pr_is_mirror: | ||
cl_required = False | ||
|
||
if not cl_required: | ||
# remove invalid, remove valid | ||
if has_invalid_label: | ||
pr.remove_from_labels(changelog_utils.CL_INVALID) | ||
if has_valid_label: | ||
pr.remove_from_labels(changelog_utils.CL_VALID) | ||
exit(0) | ||
|
||
try: | ||
with open(Path.cwd().joinpath("tags.yml")) as file: | ||
yaml = YAML(typ = 'safe', pure = True) | ||
tags_config = yaml.load(file) | ||
cl = changelog_utils.build_changelog(pr, tags_config) | ||
cl_emoji = changelog_utils.emojify_changelog(cl) | ||
cl_emoji["author"] = cl_emoji["author"] or pr_author | ||
changelog_utils.validate_changelog(cl_emoji) | ||
except Exception as e: | ||
print("Changelog parsing error:") | ||
print(e) | ||
|
||
# add invalid, remove valid | ||
if not has_invalid_label: | ||
pr.add_to_labels(changelog_utils.CL_INVALID) | ||
if has_valid_label: | ||
pr.remove_from_labels(changelog_utils.CL_VALID) | ||
exit(1) | ||
|
||
# remove invalid, add valid | ||
if has_invalid_label: | ||
pr.remove_from_labels(changelog_utils.CL_INVALID) | ||
if not has_valid_label: | ||
pr.add_to_labels(changelog_utils.CL_VALID) | ||
print("Changelog is valid.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
tags: | ||
rscadd: 'rscadd' | ||
add: 'rscadd' | ||
adds: 'rscadd' | ||
bugfix: 'bugfix' | ||
fix: 'bugfix' | ||
fixes: 'bugfix' | ||
rscdel: 'rscdel' | ||
del: 'rscdel' | ||
dels: 'rscdel' | ||
qol: 'qol' | ||
sound: 'sound' | ||
image: 'image' | ||
map: 'map' | ||
spellcheck: 'spellcheck' | ||
typo: 'spellcheck' | ||
balance: 'balance' | ||
code_imp: 'code_imp' | ||
code: 'code_imp' | ||
refactor: 'refactor' | ||
config: 'config' | ||
admin: 'admin' | ||
server: 'server' | ||
|
||
defaults: | ||
rscadd: 'Изменил геймплей или добавил новую механику' | ||
add: 'Изменил геймплей или добавил новую механику' | ||
adds: 'Изменил геймплей или добавил новую механику' | ||
bugfix: 'Что-то починил' | ||
fix: 'Что-то починил' | ||
fixes: 'Что-то починил' | ||
rscdel: 'Что-то удалил' | ||
del: 'Что-то удалил' | ||
dels: 'Что-то удалил' | ||
qol: 'Сделал что-то удобнее' | ||
sound: 'Добавил, изменил или удалил звук' | ||
image: 'Добавил, изменил или удалил картинку' | ||
map: 'Добавил, изменил или удалил что-то на карте' | ||
spellcheck: 'Исправил опечатку' | ||
typo: 'Исправил опечатку' | ||
code_imp: 'Незначительно улучшил качество кода' | ||
code: 'Незначительно улучшил качество кода' | ||
balance: 'Сделал правки в балансе' | ||
refactor: 'Значительно улучшил качество кода' | ||
config: 'Изменил что-то в конфиге' | ||
admin: 'Поменял кнопки админам' | ||
server: 'Изменил что-то серверное, о чем должен знать хост' |
Oops, something went wrong.