mirror of
https://github.com/openSUSE/osc.git
synced 2026-03-07 17:46:12 +01:00
Merge pull request #2058 from dmach/git-obs-staging-group-rebase
Change 'git-obs staging group' to rely on the workflow-pr bot
This commit is contained in:
146
behave/features/git-obs-staging-group.feature
Normal file
146
behave/features/git-obs-staging-group.feature
Normal file
@@ -0,0 +1,146 @@
|
||||
Feature: `git-obs staging group --no-ssh-strict-host-key-checking` command
|
||||
|
||||
Background:
|
||||
Given I set working directory to "{context.osc.temp}"
|
||||
# it's meant to work with project pull requests, but we'll work with pool/test-GitPkgA for simplicity
|
||||
And I execute git-obs with args "-G alice repo fork pool/test-GitPkgA"
|
||||
And I execute git-obs with args "-G alice repo clone Alice/test-GitPkgA --no-ssh-strict-host-key-checking"
|
||||
And I set working directory to "{context.osc.temp}/test-GitPkgA"
|
||||
And I execute "git config user.email 'alice@example.com'"
|
||||
And I execute "git config user.name 'Alice'"
|
||||
|
||||
# Create labels
|
||||
And I execute git-obs with args "api -X POST /repos/pool/test-GitPkgA/labels --data='{{"name": "staging/Backlog", "color": "#ffffff"}}'"
|
||||
And I execute git-obs with args "api -X POST /repos/pool/test-GitPkgA/labels --data='{{"name": "staging/In Progress", "color": "#0000ff"}}'"
|
||||
|
||||
# Alice makes a new branch feature/1, no changes on top, and makes a pull request with "PR: foo/bar!1" description
|
||||
And I execute "git checkout -b feature/1"
|
||||
And I execute "git commit --allow-empty -m 'feature/1'"
|
||||
And I execute "git push origin feature/1"
|
||||
And I execute git-obs with args "-G alice pr create --title 'feature/1' --description='PR: foo/bar!1' --target-branch factory"
|
||||
And I execute git-obs with args "api -X POST /repos/pool/test-GitPkgA/issues/1/labels --data='{{"labels": ["staging/Backlog"]}}'"
|
||||
|
||||
# Alice makes a new branch feature/2, no changes on top, and makes a pull request with "PR: foo/bar!2" description
|
||||
And I execute "git checkout factory"
|
||||
And I execute "git checkout -b feature/2"
|
||||
And I execute "git commit --allow-empty -m 'feature/2'"
|
||||
And I execute "git push origin feature/2"
|
||||
And I execute git-obs with args "-G alice pr create --title 'feature/2' --description='PR: foo/bar!2' --target-branch factory"
|
||||
And I execute git-obs with args "api -X POST /repos/pool/test-GitPkgA/issues/2/labels --data='{{"labels": ["staging/Backlog"]}}'"
|
||||
|
||||
@destructive
|
||||
Scenario: Scenario 1: staging group --no-ssh-strict-host-key-checking with --target and existing PR
|
||||
When I execute git-obs with args "-G alice staging group --no-ssh-strict-host-key-checking --target=pool/test-GitPkgA#1 pool/test-GitPkgA#2"
|
||||
Then the exit code is 0
|
||||
And stdout contains "Unable to add the 'staging/In Progress' label to pull request pool/test-GitPkgA#2"
|
||||
And stdout contains "Unable to remove the 'staging/Backlog' label from pull request pool/test-GitPkgA#2"
|
||||
And I execute git-obs with args "-G alice pr get pool/test-GitPkgA#1"
|
||||
Then the exit code is 0
|
||||
And stdout contains "State : open"
|
||||
And stdout contains "Description : PR: foo/bar!1"
|
||||
# It should also contain the reference from #2
|
||||
And stdout contains "PR: foo/bar!2"
|
||||
And stdout doesn't contain "staging/In Progress"
|
||||
And stdout contains "staging/Backlog"
|
||||
And I execute git-obs with args "-G alice pr get pool/test-GitPkgA#2"
|
||||
Then the exit code is 0
|
||||
And stdout contains "State : closed"
|
||||
And stdout contains "Description : PR: foo/bar!2"
|
||||
|
||||
@destructive
|
||||
Scenario: Scenario 2: staging group --no-ssh-strict-host-key-checking with --target and --remove-pr-references
|
||||
When I execute git-obs with args "-G alice staging group --no-ssh-strict-host-key-checking --target=pool/test-GitPkgA#1 pool/test-GitPkgA#2 --remove-pr-references"
|
||||
Then the exit code is 0
|
||||
And stdout contains "Unable to add the 'staging/In Progress' label to pull request pool/test-GitPkgA#2"
|
||||
And stdout contains "Unable to remove the 'staging/Backlog' label from pull request pool/test-GitPkgA#2"
|
||||
And I execute git-obs with args "-G alice pr get pool/test-GitPkgA#1"
|
||||
Then the exit code is 0
|
||||
And stdout contains "State : open"
|
||||
And stdout doesn't contain "staging/In Progress"
|
||||
And stdout contains "staging/Backlog"
|
||||
And I execute git-obs with args "-G alice pr get pool/test-GitPkgA#2"
|
||||
Then the exit code is 0
|
||||
And stdout contains "State : closed"
|
||||
And stdout doesn't contain "PR: foo/bar!2"
|
||||
|
||||
@destructive
|
||||
Scenario: Scenario 3: staging group --no-ssh-strict-host-key-checking without --target (creates new PR)
|
||||
When I execute git-obs with args "-G alice staging group --no-ssh-strict-host-key-checking pool/test-GitPkgA#1 pool/test-GitPkgA#2"
|
||||
Then the exit code is 0
|
||||
And I execute git-obs with args "-G alice pr get pool/test-GitPkgA#3"
|
||||
Then the exit code is 0
|
||||
And stdout contains "State : open"
|
||||
And stdout contains "PR: foo/bar!1"
|
||||
And stdout contains "PR: foo/bar!2"
|
||||
And stdout contains "Labels : staging/In Progress"
|
||||
And stdout doesn't contain "staging/Backlog"
|
||||
And I execute git-obs with args "-G alice pr get pool/test-GitPkgA#1"
|
||||
Then the exit code is 0
|
||||
# PR #1 should be closed because it was merged into #3
|
||||
And stdout contains "State : closed"
|
||||
And I execute git-obs with args "-G alice pr get pool/test-GitPkgA#2"
|
||||
Then the exit code is 0
|
||||
# PR #2 should be closed because it was merged into #3
|
||||
And stdout contains "State : closed"
|
||||
|
||||
@destructive
|
||||
Scenario: Scenario 4: staging group --no-ssh-strict-host-key-checking without --target and --remove-pr-references
|
||||
When I execute git-obs with args "-G alice staging group --no-ssh-strict-host-key-checking pool/test-GitPkgA#1 pool/test-GitPkgA#2 --remove-pr-references"
|
||||
Then the exit code is 0
|
||||
And I execute git-obs with args "-G alice pr get pool/test-GitPkgA#3"
|
||||
Then the exit code is 0
|
||||
And stdout contains "PR: foo/bar!1"
|
||||
And stdout contains "PR: foo/bar!2"
|
||||
And stdout contains "Labels : staging/In Progress"
|
||||
And stdout doesn't contain "staging/Backlog"
|
||||
And I execute git-obs with args "-G alice pr get pool/test-GitPkgA#1"
|
||||
Then the exit code is 0
|
||||
And stdout contains "State : closed"
|
||||
And stdout doesn't contain "PR: foo/bar!1"
|
||||
And I execute git-obs with args "-G alice pr get pool/test-GitPkgA#2"
|
||||
Then the exit code is 0
|
||||
And stdout contains "State : closed"
|
||||
And stdout doesn't contain "PR: foo/bar!2"
|
||||
|
||||
@destructive
|
||||
Scenario: Scenario 5: staging group --no-ssh-strict-host-key-checking by another user with push permissions
|
||||
And I execute git-obs with args "-G alice api -X PUT /repos/Alice/test-GitPkgA/collaborators/Bob"
|
||||
And I set working directory to "{context.osc.temp}"
|
||||
# Bob clones Alice's fork
|
||||
And I execute git-obs with args "-G bob repo clone Alice/test-GitPkgA --directory Alice-test-GitPkgA --no-ssh-strict-host-key-checking"
|
||||
And I set working directory to "{context.osc.temp}/Alice-test-GitPkgA"
|
||||
And I execute "git config user.email 'bob@example.com'"
|
||||
And I execute "git config user.name 'Bob'"
|
||||
When I execute git-obs with args "-G bob staging group --no-ssh-strict-host-key-checking pool/test-GitPkgA#1 pool/test-GitPkgA#2 --fork-owner=Alice"
|
||||
Then the exit code is 0
|
||||
And I execute git-obs with args "-G bob pr get pool/test-GitPkgA#3"
|
||||
Then the exit code is 0
|
||||
And stdout contains "State : open"
|
||||
And stdout contains "Author : Bob \(bob@example.com\)"
|
||||
And stdout contains "PR: foo/bar!1"
|
||||
And stdout contains "PR: foo/bar!2"
|
||||
And stdout contains "Labels : staging/In Progress"
|
||||
And stdout doesn't contain "staging/Backlog"
|
||||
|
||||
@destructive
|
||||
Scenario: Scenario 6: staging group --no-ssh-strict-host-key-checking by another user with push permissions to the target repository
|
||||
And I execute git-obs with args "-G admin api -X PUT /repos/pool/test-GitPkgA/collaborators/Bob"
|
||||
And I set working directory to "{context.osc.temp}"
|
||||
# Bob clones the target repository
|
||||
And I execute git-obs with args "-G bob repo clone pool/test-GitPkgA --directory pool-test-GitPkgA --no-ssh-strict-host-key-checking"
|
||||
And I set working directory to "{context.osc.temp}/pool-test-GitPkgA"
|
||||
# Bob's git identity
|
||||
And I execute "git config user.email 'bob@example.com'"
|
||||
And I execute "git config user.name 'Bob'"
|
||||
When I execute git-obs with args "-G bob staging group --no-ssh-strict-host-key-checking pool/test-GitPkgA#1 pool/test-GitPkgA#2"
|
||||
Then the exit code is 0
|
||||
And I execute git-obs with args "-G bob pr get pool/test-GitPkgA#3"
|
||||
Then the exit code is 0
|
||||
And stdout contains "State : open"
|
||||
# pr get output shows user login
|
||||
And stdout contains "Author : Bob \(bob@example.com\)"
|
||||
And stdout contains "Source : pool/test-GitPkgA"
|
||||
And stdout contains "PR: foo/bar!1"
|
||||
And stdout contains "PR: foo/bar!2"
|
||||
And stdout contains "Labels : staging/In Progress"
|
||||
And stdout doesn't contain "staging/Backlog"
|
||||
@@ -51,14 +51,14 @@ class StagingGroupCommand(osc.commandline_git.GitObsCommand):
|
||||
).completer = osc.commandline_git.complete_pr
|
||||
|
||||
self.add_argument(
|
||||
"--cache-dir",
|
||||
help="Path to a git cache.",
|
||||
"--no-ssh-strict-host-key-checking",
|
||||
action="store_true",
|
||||
help="Set 'StrictHostKeyChecking no' ssh option",
|
||||
)
|
||||
|
||||
self.add_argument(
|
||||
"--keep-temp-dir",
|
||||
action="store_true",
|
||||
help="Don't delete the temporary directory with git checkouts",
|
||||
"--cache-dir",
|
||||
help="Path to a git cache.",
|
||||
)
|
||||
|
||||
def run(self, args):
|
||||
@@ -82,207 +82,190 @@ class StagingGroupCommand(osc.commandline_git.GitObsCommand):
|
||||
|
||||
self.print_gitea_settings()
|
||||
|
||||
with TemporaryDirectory(prefix="git-obs-staging_", dir=".", delete=not args.keep_temp_dir) as temp_dir:
|
||||
user_obj = gitea_api.User.get(self.gitea_conn)
|
||||
pr_obj_list = []
|
||||
pr_references = []
|
||||
target_owner = None
|
||||
target_repo = None
|
||||
target_branch = None
|
||||
for owner, repo, number in args.pr_list:
|
||||
pr_obj = gitea_api.PullRequest.get(self.gitea_conn, owner, repo, number)
|
||||
|
||||
if args.target:
|
||||
target_owner, target_repo, target_number = args.target
|
||||
pr_obj = gitea_api.PullRequest.get(self.gitea_conn, target_owner, target_repo, target_number)
|
||||
# # to update a pull request, we either need to be its creator or an admin in the repo
|
||||
# if not (pr_obj._data["base"]["repo"]["permissions"]["admin"] or pr_obj.user == user_obj.login):
|
||||
# raise gitea_api.GitObsRuntimeError(f"You don't have sufficient permissions to modify pull request {target_owner}/{target_repo}#{target_number}")
|
||||
if pr_obj.state != "open":
|
||||
# we don't care about the state of the package pull requests - they can be merged already
|
||||
raise gitea_api.GitObsRuntimeError(f"Pull request {owner}/{repo}#{number} is not open (the state is '{pr_obj.state}')")
|
||||
|
||||
# get pull request data from gitea
|
||||
pr_map = {}
|
||||
for owner, repo, number in args.pr_list:
|
||||
pr = gitea_api.StagingPullRequestWrapper(self.gitea_conn, owner, repo, number, topdir=temp_dir, cache_directory=cache_dir)
|
||||
pr_map[(owner.lower(), repo.lower(), number)] = pr
|
||||
# test that all PRs go to the same branch
|
||||
if target_owner is None:
|
||||
target_owner = pr_obj.base_owner
|
||||
else:
|
||||
assert target_owner == pr_obj.base_owner, f"{target_owner} != {pr_obj.base_owner}"
|
||||
|
||||
# run checks
|
||||
target_owner = None
|
||||
target_repo = None
|
||||
target_branch = None
|
||||
for owner, repo, number in args.pr_list:
|
||||
pr = pr_map[(owner.lower(), repo.lower(), number)]
|
||||
if target_repo is None:
|
||||
target_repo = pr_obj.base_repo
|
||||
else:
|
||||
assert target_repo == pr_obj.base_repo, f"{target_repo} != {pr_obj.base_repo}"
|
||||
|
||||
if pr.pr_obj.state != "open":
|
||||
# we don't care about the state of the package pull requests - they can be merged already
|
||||
raise gitea_api.GitObsRuntimeError(f"Pull request {owner}/{repo}#{number} is not open (the state is '{pr.pr_obj.state}')")
|
||||
if target_branch is None:
|
||||
target_branch = pr_obj.base_branch
|
||||
else:
|
||||
assert target_branch == pr_obj.base_branch, f"{target_branch} != {pr_obj.base_branch}"
|
||||
|
||||
# if not (pr.pr_obj._data["base"]["repo"]["permissions"]["admin"] or pr.pr_obj.user == user_obj.login):
|
||||
# raise gitea_api.GitObsRuntimeError(f"You don't have sufficient permissions to modify pull request {owner}/{repo}#{number}")
|
||||
if gitea_api.StagingPullRequestWrapper.BACKLOG_LABEL not in pr_obj.labels:
|
||||
raise gitea_api.GitObsRuntimeError(f"Pull request {owner}/{repo}#{number} is missing the '{gitea_api.StagingPullRequestWrapper.BACKLOG_LABEL}' label.")
|
||||
|
||||
if gitea_api.StagingPullRequestWrapper.BACKLOG_LABEL not in pr.pr_obj.labels:
|
||||
raise gitea_api.GitObsRuntimeError(f"Pull request {owner}/{repo}#{number} is missing the '{gitea_api.StagingPullRequestWrapper.BACKLOG_LABEL}' label.")
|
||||
pr_obj_list.append(pr_obj)
|
||||
pr_references.extend(pr_obj.parse_pr_references())
|
||||
|
||||
# test that all PRs go to the same branch
|
||||
if target_owner is None:
|
||||
target_owner = pr.pr_obj.base_owner
|
||||
else:
|
||||
assert target_owner == pr.pr_obj.base_owner, f"{target_owner} != {pr.pr_obj.base_owner}"
|
||||
# deduplicate entries
|
||||
pr_references = sorted(set(pr_references))
|
||||
|
||||
if target_repo is None:
|
||||
target_repo = pr.pr_obj.base_repo
|
||||
else:
|
||||
assert target_repo == pr.pr_obj.base_repo, f"{target_repo} != {pr.pr_obj.base_repo}"
|
||||
# create title
|
||||
if args.title:
|
||||
title = args.title
|
||||
else:
|
||||
updated_packages = sorted([i[1] for i in pr_references])
|
||||
# TODO: it would be nice to mention the target OBS project
|
||||
# we keep only the first ``max_packages``, because the title might get too long quite easily
|
||||
max_packages = 5
|
||||
updated_packages_str = ", ".join(sorted(updated_packages)[:max_packages])
|
||||
if len(updated_packages) > max_packages:
|
||||
updated_packages_str += f" + {len(updated_packages) - max_packages} more"
|
||||
title = f"Update packages: {updated_packages_str}"
|
||||
|
||||
if target_branch is None:
|
||||
target_branch = pr.pr_obj.base_branch
|
||||
else:
|
||||
assert target_branch == pr.pr_obj.base_branch, f"{target_branch} != {pr.pr_obj.base_branch}"
|
||||
if args.target:
|
||||
target_owner, target_repo, target_number = args.target
|
||||
target_pr_obj = gitea_api.PullRequest.get(self.gitea_conn, target_owner, target_repo, target_number)
|
||||
|
||||
# clone the git repos, cache submodule data
|
||||
for owner, repo, number in args.pr_list:
|
||||
pr = pr_map[(owner.lower(), repo.lower(), number)]
|
||||
pr.clone()
|
||||
# extend the 'PR: ' references in the target pull request
|
||||
target_pr_obj._data["body"] = gitea_api.PullRequest.add_pr_references(target_pr_obj.body, pr_references)
|
||||
|
||||
# run checks #2
|
||||
for owner, repo, number in args.pr_list:
|
||||
pr = pr_map[(owner.lower(), repo.lower(), number)]
|
||||
if not pr.package_pr_map:
|
||||
# TODO: we don't know if the submodules are packages or not, we should cross-reference those with _manifest
|
||||
raise gitea_api.GitObsRuntimeError(f"Pull request {owner}/{repo}#{number} doesn't have any submodules changed.")
|
||||
# update description of the target pull request in Gitea
|
||||
# it is crucial to allow maintainer edits so the bot can push the related PRs
|
||||
target_pr_obj = gitea_api.PullRequest.set(
|
||||
self.gitea_conn,
|
||||
target_pr_obj.base_owner,
|
||||
target_pr_obj.base_repo,
|
||||
int(target_pr_obj.number),
|
||||
title=title,
|
||||
description=target_pr_obj.body,
|
||||
allow_maintainer_edit=True,
|
||||
)
|
||||
|
||||
if not args.target:
|
||||
target_repo_full_name = f"{target_owner}/{target_repo}".lower()
|
||||
|
||||
# determine fork_owner and fork_repo for pull request creation
|
||||
has_push_access = False
|
||||
|
||||
user_repos = gitea_api.Repo.list_my_repos(self.gitea_conn)
|
||||
for repo in user_repos:
|
||||
repo_name = repo._data["full_name"]
|
||||
if target_repo_full_name == repo_name.lower() and repo.can_push:
|
||||
has_push_access = True
|
||||
print(f"You have push access to the target repository {target_owner}/{target_repo}, the pull request will be created from a branch in the target repository.")
|
||||
break
|
||||
|
||||
if has_push_access and not args.fork_owner:
|
||||
fork_owner = target_owner
|
||||
fork_repo = target_repo
|
||||
else:
|
||||
fork_owner = args.fork_owner if args.fork_owner else user_obj.login
|
||||
fork_repo = None
|
||||
forks = gitea_api.Fork.list(self.gitea_conn, target_owner, target_repo)
|
||||
for repo in forks:
|
||||
if repo.owner.lower() == fork_owner.lower():
|
||||
fork_repo = repo.repo
|
||||
break
|
||||
if not fork_repo:
|
||||
raise gitea_api.GitObsRuntimeError(f"Cannot find a matching fork of {target_owner}/{target_repo} for user {fork_owner}")
|
||||
|
||||
# dates in ISO 8601 format cannot be part of a valid branch name, we need a custom format
|
||||
fork_branch = args.fork_branch if args.fork_branch else f"for/{target_branch}/group-{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}"
|
||||
|
||||
|
||||
clone_dir = gitea_api.Repo.clone(
|
||||
# update labels
|
||||
try:
|
||||
gitea_api.PullRequest.add_labels(
|
||||
self.gitea_conn,
|
||||
fork_owner,
|
||||
fork_repo,
|
||||
directory=os.path.join(temp_dir, f"{fork_owner}_{fork_repo}"),
|
||||
add_remotes=True,
|
||||
cache_directory=cache_dir,
|
||||
ssh_private_key_path=self.gitea_conn.login.ssh_key,
|
||||
)
|
||||
clone_git = gitea_api.Git(clone_dir)
|
||||
clone_git._run_git(["fetch", "origin", f"{target_branch}:{fork_branch}", "--force", "--update-head-ok", "--depth=1"])
|
||||
clone_git.switch(fork_branch)
|
||||
clone_git.push(remote="origin", branch=fork_branch, set_upstream=True, force=args.force)
|
||||
|
||||
# target project pull request wasn't specified, let's create it
|
||||
desc = ""
|
||||
updated_packages = []
|
||||
for owner, repo, number in args.pr_list:
|
||||
pr = pr_map[(owner.lower(), repo.lower(), number)]
|
||||
for (pkg_owner, pkg_repo, pkg_number), pr_obj in pr.package_pr_map.items():
|
||||
desc += f"PR: {pkg_owner}/{pkg_repo}!{pkg_number}\n"
|
||||
updated_packages.append(os.path.basename(pr.submodules_by_owner_repo[pkg_owner.lower(), pkg_repo.lower()]["path"]))
|
||||
|
||||
# TODO: it would be nice to mention the target OBS project
|
||||
# we keep only the first ``max_packages``, because the title might get too long quite easily
|
||||
max_packages = 5
|
||||
updated_packages_str = ", ".join(sorted(updated_packages)[:max_packages])
|
||||
if len(updated_packages) > max_packages:
|
||||
updated_packages_str += f" + {len(updated_packages) - max_packages} more"
|
||||
title = args.title if args.title else f"Update packages: {updated_packages_str}"
|
||||
|
||||
pr_obj = gitea_api.PullRequest.create(
|
||||
self.gitea_conn,
|
||||
target_owner=target_owner,
|
||||
target_repo=target_repo,
|
||||
target_branch=target_branch,
|
||||
source_owner=fork_owner,
|
||||
# source_repo is not required because the information lives in Gitea database
|
||||
source_branch=fork_branch,
|
||||
title=title,
|
||||
description=desc,
|
||||
target_pr_obj.base_owner,
|
||||
target_pr_obj.base_repo,
|
||||
int(target_pr_obj.number),
|
||||
labels=[gitea_api.StagingPullRequestWrapper.INPROGRESS_LABEL],
|
||||
)
|
||||
target_number = pr_obj.number
|
||||
except Exception as e:
|
||||
print(f"Unable to add the '{gitea_api.StagingPullRequestWrapper.INPROGRESS_LABEL}' label to pull request {pr_obj.id}: {e}")
|
||||
|
||||
# clone the target git repo, cache submodule data
|
||||
target = gitea_api.StagingPullRequestWrapper(self.gitea_conn, target_owner, target_repo, target_number, topdir=temp_dir, cache_directory=cache_dir)
|
||||
target.clone()
|
||||
try:
|
||||
if gitea_api.StagingPullRequestWrapper.BACKLOG_LABEL in target_pr_obj.labels:
|
||||
gitea_api.PullRequest.remove_labels(
|
||||
self.gitea_conn,
|
||||
target_pr_obj.base_owner,
|
||||
target_pr_obj.base_repo,
|
||||
int(target_pr_obj.number),
|
||||
labels=[gitea_api.StagingPullRequestWrapper.BACKLOG_LABEL],
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Unable to remove the '{gitea_api.StagingPullRequestWrapper.BACKLOG_LABEL}' label from pull request {pr_obj.id}: {e}")
|
||||
|
||||
else:
|
||||
has_push_access = False
|
||||
if target.pr_obj.head_can_push:
|
||||
print(f"You have push access to the head repository of the target pull request {target_owner}/{target_repo}#{target_number}, the pull request will be updated by pushing to the head branch.")
|
||||
has_push_access = True
|
||||
if not args.fork_owner:
|
||||
target_repo_obj = gitea_api.Repo.get(self.gitea_conn, target_owner, target_repo)
|
||||
# determine if we have write access to the target repo and create the branch there if possible
|
||||
if target_repo_obj.can_push:
|
||||
print(f"You have push access to the target repository {target_owner}/{target_repo}, the pull request will be created from a branch in the target repository.")
|
||||
has_push_access = True
|
||||
|
||||
if target.pr_obj._data['head']['repo']['fork'] and has_push_access:
|
||||
# if the head repo is a fork and we have push access to it, we can push directly to the head branch
|
||||
target.git._run_git(["remote", "set-url", "fork", target.pr_obj._data['head']['repo']['ssh_url']])
|
||||
fork_branch = args.fork_branch if args.fork_branch else f"for/{target_branch}/group-{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}"
|
||||
|
||||
# locally merge package pull requests to the target project pull request (don't change anything on server yet)
|
||||
updated_packages = []
|
||||
for owner, repo, number in args.pr_list:
|
||||
pr = pr_map[(owner.lower(), repo.lower(), number)]
|
||||
target.merge(pr)
|
||||
for (pkg_owner, pkg_repo, pkg_number), pr_obj in pr.package_pr_map.items():
|
||||
updated_packages.append(os.path.basename(pr.submodules_by_owner_repo[pkg_owner.lower(), pkg_repo.lower()]["path"]))
|
||||
|
||||
if target.pr_obj._data['head']['repo']['fork']:
|
||||
remote="fork"
|
||||
if has_push_access:
|
||||
fork_owner = target_owner
|
||||
fork_repo = target_repo
|
||||
else:
|
||||
remote="origin"
|
||||
user_obj = gitea_api.User.get(self.gitea_conn)
|
||||
fork_owner = args.fork_owner if args.fork_owner else user_obj.login
|
||||
|
||||
# push to git repo associated with the target pull request
|
||||
print(f"Pushing changes to {remote} on pull/{target.pr_obj.number}:{target.pr_obj.head_branch}")
|
||||
target.git.push(remote=remote, branch=f"pull/{target.pr_obj.number}:{target.pr_obj.head_branch}")
|
||||
# update target pull request
|
||||
if args.target:
|
||||
# we keep only the first ``max_packages``, because the title might get too long quite easily
|
||||
max_packages = 5
|
||||
updated_packages_str = ", ".join(sorted(updated_packages)[:max_packages])
|
||||
if len(updated_packages) > max_packages:
|
||||
updated_packages_str += f" + {len(updated_packages) - max_packages} more"
|
||||
title = args.title if args.title else f"{target.pr_obj.title}, {updated_packages_str}"
|
||||
fork_repo = None
|
||||
forks = gitea_api.Fork.list(self.gitea_conn, target_owner, target_repo)
|
||||
for repo in forks:
|
||||
if repo.owner.lower() == fork_owner.lower():
|
||||
fork_repo = repo.repo
|
||||
if not fork_repo:
|
||||
raise gitea_api.GitObsRuntimeError(f"Cannot find a matching fork of {target_owner}/{target_repo} for user {fork_owner}")
|
||||
|
||||
# if args.target is not set, we've created a new pull request with all 'PR:' references included
|
||||
# if args.target is set (which is the case here), we need to update the description with the newly added 'PR:' references
|
||||
target.pr_obj.set(self.gitea_conn, target_owner, target_repo, target_number, title=title, description=target.pr_obj.body)
|
||||
# create the branch
|
||||
if has_push_access:
|
||||
gitea_api.Branch.create(self.gitea_conn, target_owner, target_repo, old_ref_name=target_branch, new_branch_name=fork_branch)
|
||||
else:
|
||||
fork_repo_obj = gitea_api.Repo.get(self.gitea_conn, fork_owner, fork_repo)
|
||||
with TemporaryDirectory(prefix="git-obs-staging_", dir=".") as temp_dir:
|
||||
# we need to create a branch that matches the target branch using git; Gitea doesn't have any API for that
|
||||
clone_dir = gitea_api.Repo.clone(
|
||||
self.gitea_conn,
|
||||
fork_owner,
|
||||
fork_repo,
|
||||
directory=os.path.join(temp_dir, f"{fork_owner}_{fork_repo}"),
|
||||
add_remotes=False,
|
||||
cache_directory=cache_dir,
|
||||
ssh_private_key_path=self.gitea_conn.login.ssh_key,
|
||||
ssh_strict_host_key_checking=not(args.no_ssh_strict_host_key_checking),
|
||||
)
|
||||
clone_git = gitea_api.Git(clone_dir)
|
||||
clone_git._run_git(["fetch", "origin", f"{target_branch}:{fork_branch}", "--force", "--update-head-ok", "--depth=1"])
|
||||
clone_git.switch(fork_branch)
|
||||
clone_git.add_remote("fork", fork_repo_obj.ssh_url)
|
||||
clone_git.push(remote="fork", branch=fork_branch, set_upstream=True, force=args.force)
|
||||
|
||||
for owner, repo, number in args.pr_list:
|
||||
pr = pr_map[(owner.lower(), repo.lower(), number)]
|
||||
if args.remove_pr_references:
|
||||
try:
|
||||
# apply the removed 'PR:' reference to the package pull request
|
||||
pr.pr_obj.set(self.gitea_conn, owner, repo, number, description=pr.pr_obj.body)
|
||||
except Exception as e:
|
||||
print(f"Unable to remove 'PR:' references from pull request {owner}/{repo}#{number}: {e}")
|
||||
# target project pull request wasn't specified, let's create it
|
||||
desc = gitea_api.PullRequest.add_pr_references("", pr_references)
|
||||
target_pr_obj = gitea_api.PullRequest.create(
|
||||
self.gitea_conn,
|
||||
target_owner=target_owner,
|
||||
target_repo=target_repo,
|
||||
target_branch=target_branch,
|
||||
source_owner=fork_owner,
|
||||
# source_repo is not required because the information lives in Gitea database
|
||||
source_branch=fork_branch,
|
||||
title=title,
|
||||
description=desc,
|
||||
labels=[gitea_api.StagingPullRequestWrapper.INPROGRESS_LABEL],
|
||||
)
|
||||
|
||||
# close the pull request that was merged into the target
|
||||
# it is crucial to allow maintainer edits so the bot can push the related PRs
|
||||
target_pr_obj = gitea_api.PullRequest.set(
|
||||
self.gitea_conn,
|
||||
target_pr_obj.base_owner,
|
||||
target_pr_obj.base_repo,
|
||||
int(target_pr_obj.number),
|
||||
allow_maintainer_edit=True,
|
||||
)
|
||||
|
||||
for pr_obj in pr_obj_list:
|
||||
if args.remove_pr_references:
|
||||
try:
|
||||
gitea_api.PullRequest.close(self.gitea_conn, owner, repo, number)
|
||||
# remove the 'PR:' references from the original pull request
|
||||
refs = pr_obj.parse_pr_references()
|
||||
body = gitea_api.PullRequest.remove_pr_references(pr_obj.body, refs)
|
||||
pr_obj.set(self.gitea_conn, pr_obj.base_owner, pr_obj.base_repo, pr_obj.number, description=body)
|
||||
except Exception as e:
|
||||
print(f"Unable to close pull request {owner}/{repo}#{number}: {e}")
|
||||
print(f"Unable to remove 'PR:' references from pull request {pr_obj.id}: {e}")
|
||||
|
||||
try:
|
||||
# close the pull request that was merged into the target
|
||||
gitea_api.PullRequest.close(self.gitea_conn, pr_obj.base_owner, pr_obj.base_repo, pr_obj.number)
|
||||
except Exception as e:
|
||||
print(f"Unable to close pull request {pr_obj.id}: {e}")
|
||||
|
||||
print()
|
||||
print(target.pr_obj.to_human_readable_string())
|
||||
print(target_pr_obj.to_human_readable_string())
|
||||
|
||||
print()
|
||||
print("Staging project pull requests have been successfully merged")
|
||||
|
||||
if args.keep_temp_dir:
|
||||
print()
|
||||
print(f"Temporary files are available here: {temp_dir}")
|
||||
|
||||
Reference in New Issue
Block a user