-
Jonas Höppner authored
As all projects are commited in the same branch the 'up-to-date' check may not only check if the first parent commit points to the master/dunfell branch. Now it is needed to loop through the history until the integration branch's commit is found. On fail a message is displayed which merge request needs to be retriggered manually. This can now also be the 'parent'-MR that triggered the complete chain. The check job is used pipeline again. The retrigger job also looks in the .gitlab-ci project for check jobs to retrigger.
4f194e21
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
update_submodule.py 8.19 KiB
#!/usr/bin/env python3
import common
import argparse
import logging
import os
import sys
import tempfile
from furl import furl
from git import GitCommandError, Repo
from gitlab import Gitlab
def update_submodule(
project,
submodule_name,
submodule_revision,
branch=None,
pre_commit_hook=None,
replace_existing_branch=False,
):
"""Update submodule of gitlab project to given revision
Parameters:
project (gitlab project): The project which's submodule should be updated
submodule_name (string): The name of the submodule to pull
submodule_revision (hex string): The sha hash of the commit to update the submodule to
branch (string): branch to update, if None, the projects default branch is used
pre_commit_hook: Function to be called before the actual commit is done, to add additional changes.
Arguments passed: ( repo, submodule_project, submodule_revision)
replace_existing_branch: When an existing integration branch is found it is always replaced.
Returns: tuple of:
branch (string): Name of the newly created integration branch
revision (string): hexsha of the new commit
submodule_project ( gitlab project): The submodule as gitlab priject instance
"""
gitlab = project.manager.gitlab
# If no branch is given, use project's default branch
if branch is None:
branch = project.default_branch
with tempfile.TemporaryDirectory() as project_dir:
# Construct clone url containing access token
clone_url = furl(project.http_url_to_repo)
clone_url.username = "gitlab-ci"
clone_url.password = gitlab.private_token
# Checkout project
try:
repo = Repo.clone_from(clone_url.url, project_dir, branch=branch)
except GitCommandError as e:
sys.exit("ERROR: could not clone repository\n" + str(e))
except IndexError:
sys.exit("ERROR: branch '%s' not found" % branch)
# Find submodule
submodule = common.get_submodule(repo, submodule_name)
# Check if revisions are different
if submodule.hexsha == submodule_revision:
print("Submodule is already at %s" % submodule_revision)
if not replace_existing_branch:
# TODO test this
return (None, None, None)
# Check for relative path
if not submodule.url.startswith(".."):
sys.exit(
"ERROR: absolute submodule paths are not supported (%s)" % submodule.url
)
# Get absolute project path
# This cannot be done with gitpython directly due to issue:
# https://github.com/gitpython-developers/GitPython/issues/730
relative_path = os.path.splitext(submodule.url)[0] # remove .git
project_path = project.path_with_namespace
while relative_path.startswith(".."):
relative_path = relative_path[3:] # strip off '../'
project_path, _ = os.path.split(project_path) # remove last part
submodule_project_path = os.path.join(project_path, relative_path)
# Get submodule project
submodule_project = common.get_project(gitlab, submodule_project_path)
# Get commits between current and new revision
revision_range = submodule.hexsha + ".." + submodule_revision
commits = submodule_project.commits.list(ref_name=revision_range)
if not commits:
sys.exit("ERROR: no commits found in range %s" % revision_range)
# Find out if top commit is part of a merge request
# If so, use source branch of this MR as integration branch name
# Else use commit sha instead
integration_branch_suffix = submodule_revision
for mr in commits[0].merge_requests():
if mr["target_branch"] == submodule_project.default_branch:
integration_branch_suffix = mr["source_branch"]
break
# Initialize submodule
# Hack due to issue above: change to absolute path and switch back afterwards
submodule_clone_url = furl(submodule_project.http_url_to_repo)
submodule_clone_url.username = "gitlab-ci"
submodule_clone_url.password = gitlab.private_token
submodule_relative_url = submodule.url
with submodule.config_writer() as writer:
writer.set("url", submodule_clone_url.url)
submodule.update(init=True)
with submodule.config_writer() as writer:
writer.set("url", submodule_relative_url)
# Check if integration branch already exists and if it is up to date
integration_branch = common.integration_branch_name(
submodule_project.name, integration_branch_suffix
)
existing_branch = None
for ref in repo.references:
if "origin/" + integration_branch == ref.name:
existing_branch = ref
if existing_branch:
repo.head.set_reference(existing_branch)
submodule = common.get_submodule(repo, submodule_name)
if replace_existing_branch or submodule.hexsha != submodule_revision:
print("Replacing outdated integration branch %s" % integration_branch)
repo.head.set_reference(branch)
submodule = common.get_submodule(repo, submodule_name)
else:
print(
"Submodule is already at %s on branch %s"
% (submodule_revision, integration_branch)
)
return (integration_branch, existing_branch.commit, submodule_project)
else:
print("Creating integration branch %s" % integration_branch)
# Create integration branch
repo.head.set_reference(repo.create_head(integration_branch))
# Update submodule
try:
submodule.module().git.checkout(submodule_revision)
except GitCommandError as e:
sys.exit("ERROR: could not checkout commit\n" + str(e))
repo.git.add(submodule.path)
if pre_commit_hook is not None:
pre_commit_hook(repo, submodule_project, submodule_revision)
# Make an API request to create the gitlab.user object
gitlab.auth()
# Construct commit message and commit the change
message = "Integrate %s/%s%s\n%s" % (
submodule_project.name,
integration_branch_suffix,
" and %d more" % (len(commits) - 1) if len(commits) > 1 else "",
common.list_commits(commits),
)
project_revision = common.commit_and_push(
project,
repo,
integration_branch,
message,
gitlab.user.username,
gitlab.user.email,
)
return (integration_branch, project_revision, submodule_project)
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"--gitlab-url",
help="""URL to the GitLab instance""",
dest="gitlab_url",
required=True,
)
parser.add_argument(
"--token",
help="""GitLab REST API private access token""",
dest="token",
required=True,
)
parser.add_argument(
"--project",
help="""name of the GitLab project""",
dest="project",
required=True,
)
parser.add_argument(
"--submodule",
help="""submodule to update""",
dest="submodule",
required=True,
)
parser.add_argument(
"--revision",
help="""new revision for submodule""",
dest="revision",
required=True,
)
parser.add_argument(
"--branch",
help="""project branch (if not default branch)""",
dest="branch",
required=False,
default=None,
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="""Increase verbosity.""",
)
args, _ = parser.parse_known_args()
if args.verbose:
logging.basicConfig(level=logging.DEBUG)
gitlab = Gitlab(args.gitlab_url, private_token=args.token)
project = common.get_project(gitlab, args.project)
update_submodule(project, args.submodule, args.revision, args.branch)
if __name__ == "__main__":
main()