Drop merge action

Now at https://github.com/flathub-infra/merge-action
This commit is contained in:
bbhtt
2025-06-11 11:57:54 +05:30
parent 70538cd1c6
commit 3a3823938b
5 changed files with 0 additions and 475 deletions

View File

@@ -1,24 +0,0 @@
FROM python:3.12 AS builder
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libcairo2-dev libgirepository1.0-dev
ADD requirements.txt .
RUN python -m venv /venv && \
/venv/bin/python -m pip install -r requirements.txt PyGObject==3.50.0
FROM python:3.12-slim
ENV PATH="/venv/bin:$PATH"
RUN apt-get update && \
apt-get install -y --no-install-recommends \
git gh jq libcairo2 libgirepository-1.0-1 gir1.2-json-1.0 && \
apt-get clean && rm -rf /var/lib/apt/lists/*
COPY --from=builder /venv /venv
COPY . /app
WORKDIR /app
CMD ["/app/entrypoint.py"]

View File

@@ -1,336 +0,0 @@
#!/usr/bin/env python3
import glob
import json
import os
import re
import subprocess
import sys
import tempfile
import time
import gi
import github
import pygit2
import yaml
from gql import Client, gql
from gql.transport.requests import RequestsHTTPTransport
gi.require_version("Json", "1.0")
from gi.repository import Json # noqa: E402
def set_protected_branch(token, repo, branch):
transport = RequestsHTTPTransport(
url="https://api.github.com/graphql",
headers={"Authorization": f"Bearer {token}"},
)
client = Client(transport=transport, fetch_schema_from_transport=False)
gql_get_repo_id = gql(
"""
query get_repo_id($repo: String!) {
repository(name: $repo, owner: "flathub") {
id
}
}
"""
)
gql_add_branch_protection = gql(
"""
mutation add_branch_protection($repositoryID: ID!, $pattern: String!) {
createBranchProtectionRule(
input: {
allowsDeletions: false
allowsForcePushes: false
dismissesStaleReviews: false
isAdminEnforced: false
pattern: $pattern
repositoryId: $repositoryID
requiresApprovingReviews: true
requiredApprovingReviewCount: 0
requiresCodeOwnerReviews: false
requiresStatusChecks: true
requiresStrictStatusChecks: true
restrictsReviewDismissals: false
requiredStatusCheckContexts: ["builds/x86_64"]
}
) {
branchProtectionRule {
id
}
}
}
"""
)
repo_id = client.execute(gql_get_repo_id, variable_values={"repo": repo})
repo_id = repo_id["repository"]["id"]
result = client.execute(
gql_add_branch_protection,
variable_values={"repositoryID": repo_id, "pattern": branch},
)
return result
def detect_appid(dirname):
files = []
ret = (None, None)
appid = None
for ext in ("yml", "yaml", "json"):
files.extend(glob.glob(f"{dirname}/*.{ext}"))
for filename in files:
print(f"Parsing {filename}")
if os.path.isfile(filename):
ext = filename.split(".")[-1]
with open(filename) as f:
if ext in ("yml", "yaml"):
manifest = yaml.safe_load(f)
if "app-id" in manifest:
appid = manifest["app-id"]
elif "id" in manifest:
appid = manifest["id"]
else:
parser = Json.Parser()
if parser.load_from_file(filename):
root_node = parser.get_root()
if root_node.get_node_type() == Json.NodeType.OBJECT:
json_object = root_node.get_object()
if json_object.has_member("id"):
appid = json_object.get_string_member("id")
elif json_object.has_member("app-id"):
appid = json_object.get_string_member("app-id")
if not appid:
continue
if appid:
manifest_file = os.path.basename(filename)
if os.path.splitext(manifest_file)[0] != appid:
print(f"Skipping {manifest_file}, does not match appid {appid}")
continue
ret = (manifest_file, appid)
return ret
def main():
github_token = os.environ.get("GITHUB_TOKEN")
if not github_token:
print("GITHUB_TOKEN environment variable is not set")
sys.exit(1)
github_event_path = os.environ.get("GITHUB_EVENT_PATH")
with open(github_event_path) as f:
github_event = json.load(f)
if github_event["action"] != "created":
print("The event is not a comment")
sys.exit(0)
if "pull_request" not in github_event["issue"]:
print("The issue is not a pull request")
sys.exit(0)
github_comment = github_event["comment"]["body"]
if not github_comment.startswith("/merge"):
print("The comment does not start with '/merge'")
sys.exit(0)
command_pattern = re.compile(r"^/merge(?::([\w.-]+))? head=([a-fA-F0-9]{40})(.*)$")
matched = command_pattern.search(github_comment)
if not matched:
print(
"The comment is not a valid '/merge' command.\n"
"Format: '/merge:<optional target repo default branch, default: master> "
"head=<pr head commit sha 40 chars> "
"<optional extra collaborators @foo @baz, default: pr author>'"
)
sys.exit(1)
branch_match = matched.group(1) or "master"
if branch_match in ("master", "beta"):
target_repo_default_branch = branch_match
else:
target_repo_default_branch = f"branch/{branch_match}"
print(f"Got target branch {target_repo_default_branch} from comment")
pr_head_sha = str(matched.group(2))
print(f"Got PR HEAD SHA from comment: {pr_head_sha}")
rest_comment = matched.group(3)
# https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/iam-configuration-reference/username-considerations-for-external-authentication#about-username-normalization
# > Usernames for user accounts on GitHub can only contain alphanumeric characters and dashes
# > If the username is longer than 39 characters (including underscore and short code),
# > the provisioning attempt will fail with a 400 error.
additional_colbs = [m[1:] for m in re.findall(r"@[a-zA-Z0-9-]{1,39}", rest_comment)]
print(f"Got additional collaborators {additional_colbs} from comment")
gh = github.Github(github_token)
org = gh.get_organization("flathub")
admins = org.get_team_by_slug("admins")
reviewers = org.get_team_by_slug("reviewers")
comment_author = gh.get_user(github_event["comment"]["user"]["login"])
flathub = org.get_repo("flathub")
pr_id = int(github_event["issue"]["number"])
pr = flathub.get_pull(pr_id)
pr_branch = pr.head.label.split(":")[1]
fork_url = pr.head.repo.clone_url
pr_author = pr.user.login
if not admins.has_in_members(comment_author) and not reviewers.has_in_members(
comment_author
):
print(f"{comment_author} is not a reviewer")
sys.exit(1)
tmpdir = tempfile.TemporaryDirectory()
print(f"Cloning {fork_url} (branch: {pr_branch})")
clone = pygit2.clone_repository(fork_url, tmpdir.name, checkout_branch=pr_branch)
clone_head_sha = str(clone.head.target)
print(f"Clone HEAD SHA: {clone_head_sha}")
clone.submodules.update(init=True)
assert flathub.get_pull(pr_id).state == "open"
assert flathub.get_pull(pr_id).head.sha == pr_head_sha, (
f"Current PR HEAD SHA {flathub.get_pull(pr_id).head.sha} does not match approved SHA"
)
assert clone_head_sha == pr_head_sha
manifest_file, appid = detect_appid(tmpdir.name)
if manifest_file is None or appid is None:
print("Failed to detect appid")
sys.exit(1)
print(f"Detected {appid} as appid from {manifest_file}")
try:
org.get_repo(appid)
print(
f"Repository {appid} already exists in the flathub organisation, exiting."
)
sys.exit(1)
except github.GithubException as err:
if err.status == 404:
print(
f"Repository {appid} does not exist in the flathub organisation, continuing."
)
pass
else:
print(f"Unexpected error while checking for repository {appid}: {err}")
raise
print("Creating new repo on Flathub")
repo = org.create_repo(appid)
repo_name = repo.name
time.sleep(5)
repo.edit(
homepage=f"https://flathub.org/apps/details/{appid}",
delete_branch_on_merge=True,
)
print("Adding flathub remote")
clone.remotes.create(
"flathub", f"https://x-access-token:{github_token}@github.com/flathub/{appid}"
)
print("Pushing changes to the new Flathub repo")
git_push = (
f"cd {tmpdir.name} && git push flathub {pr_branch}:{target_repo_default_branch}"
)
ret = subprocess.run(
git_push,
shell=True,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
if ret.stdout:
print(f"Git push stdout:\n{ret.stdout.decode().strip()}")
if ret.stderr:
print(f"Git push stderr:\n{ret.stderr.decode().strip()}")
repo.remove_from_collaborators("flathubbot")
print("Setting protected branches")
for branch in ("master", "main", "stable", "branch/*", "beta", "beta/*"):
set_protected_branch(github_token, appid, branch)
remote_branch_obj = repo.get_branch(target_repo_default_branch)
remote_head_sha = str(remote_branch_obj.commit.sha)
assert flathub.get_pull(pr_id).state == "open"
print(f"Remote HEAD SHA: {remote_head_sha}")
assert pr_head_sha == remote_head_sha
assert remote_branch_obj.protected is True, (
f"Branch '{target_repo_default_branch}' is not protected"
)
print(f"Adding {pr_author} to collaborators")
repo.add_to_collaborators(pr_author, permission="push")
print("Adding 'trusted-maintainers' to collaborators")
trusted_maintainers = org.get_team_by_slug("trusted-maintainers")
trusted_maintainers.update_team_repository(repo, "push")
if repo_name.startswith("org.kde."):
print("Adding KDE maintainers to collaborators")
kde_maintainers = org.get_team_by_slug("KDE")
kde_maintainers.update_team_repository(repo, "push")
if repo_name.startswith("org.gnome.") and repo_name.count(".") == 2:
print("Adding GNOME maintainers to collaborators")
gnome_maintainers = org.get_team_by_slug("GNOME")
gnome_maintainers.update_team_repository(repo, "push")
for user in additional_colbs:
try:
print(f"Adding mentioned {user} to collaborators")
repo.add_to_collaborators(user, permission="push")
except github.GithubException as err:
print(f"Adding mentioned {user} failed")
print(err)
pass
assert flathub.get_pull(pr_id).state == "open"
final_colbs = [user.login for user in repo.get_collaborators(affiliation="outside")]
print(f"External colloborators added: {final_colbs}")
close_comment = (
f"A repository for this submission has been created: {repo.html_url} and it will be published to Flathub in 4-5 hours.",
"\n",
f"You will receive an [invite]({repo.html_url}/invitations) to be a collaborator to the above repository. Please make sure to enable 2FA on GitHub and accept the invite within one week.",
"\n",
"Please go through the [App maintenance guide](https://docs.flathub.org/docs/for-app-authors/maintenance/) if you have never maintained an app on Flathub before.",
"\n",
"If you're the original developer (or an authorized party), please [verify your app](https://docs.flathub.org/docs/for-app-authors/verification) to let users know it's coming from you.",
"\n",
"Please follow the [Flathub blog](https://docs.flathub.org/blog) for the latest announcements.",
"\n",
"Thanks!",
)
print("Closing the pull request")
pr.create_issue_comment("\n".join(close_comment))
pr.edit(state="closed")
if __name__ == "__main__":
main()

View File

@@ -1,4 +0,0 @@
gql[requests]
pygit2
PyGithub
PyYAML

View File

@@ -1,65 +0,0 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# pip-compile .github/actions/merge/requirements.in
#
anyio==4.4.0
# via gql
backoff==2.2.1
# via gql
certifi==2024.7.4
# via requests
cffi==1.16.0
# via
# cryptography
# pygit2
# pynacl
charset-normalizer==3.3.2
# via requests
cryptography==44.0.1
# via pyjwt
deprecated==1.2.14
# via pygithub
gql[requests]==3.5.0
# via -r requirements.in
graphql-core==3.2.3
# via gql
idna==3.7
# via
# anyio
# requests
# yarl
multidict==6.0.5
# via yarl
pycparser==2.22
# via cffi
pygit2==1.15.0
# via -r requirements.in
pygithub==2.3.0
# via -r requirements.in
pyjwt[crypto]==2.8.0
# via pygithub
pynacl==1.5.0
# via pygithub
pyyaml==6.0.1
# via -r requirements.in
requests==2.32.4
# via
# gql
# pygithub
# requests-toolbelt
requests-toolbelt==1.0.0
# via gql
sniffio==1.3.1
# via anyio
typing-extensions==4.12.2
# via pygithub
urllib3==2.2.2
# via
# pygithub
# requests
wrapt==1.16.0
# via deprecated
yarl==1.9.4
# via gql

View File

@@ -1,46 +0,0 @@
name: 'Build image for merge workflow'
on:
push:
branches: master
paths:
- ".github/actions/merge/*"
- .github/workflows/merge.yml
- .github/workflows/build-merge.yml
workflow_dispatch:
jobs:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
# 4.2.2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- name: Set up Docker Buildx
# 3.10.0
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2
- name: Login to ghcr.io
# 3.4.0
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: docker_build
# 6.15.0
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4
with:
context: .github/actions/merge
file: .github/actions/merge/Dockerfile
tags: ghcr.io/flathub/actions/merge:latest
push: true
cache-from: type=gha
cache-to: type=gha,mode=max