Newer
Older
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
):
"""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)
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))
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)
return (None, None, None)
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# 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 submodule.hexsha == submodule_revision:
print(
"Submodule is already at %s on branch %s"
% (submodule_revision, integration_branch)
)
return (integration_branch, existing_branch.commit, submodule_project)
else:
print("Replacing outdated integration branch %s" % integration_branch)
submodule = common.get_submodule(repo, submodule_name)
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)
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# 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.""",
)
if args.verbose:
logging.basicConfig(level=logging.DEBUG)