Newer
Older
#!/usr/bin/env python3
import common
import argparse
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):
"""Update submodule of gitlab project to given revision"""
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)
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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
# 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))
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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
# 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)
# 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,
)
args, _ = parser.parse_known_args()
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()