Skip to content

Commit 41e38e6

Browse files
committed
Merge branch features/gitlab-gists-requests into devel
2 parents 6dc0c76 + e58373e commit 41e38e6

File tree

43 files changed

+2879
-67
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2879
-67
lines changed

buildout.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ develop-eggs-directory = ${buildout:directory}/var/develop-eggs
99
parts-directory = ${buildout:directory}/var/parts
1010
# develop-dir = ${buildout:directory}/var/clone/
1111
# extensions=gp.vcsdevelop
12-
# vcs-extend-develop=git+https://github.com/gitpython-developers/GitPython#egg=GitPython
12+
# vcs-extend-develop=git+https://github.com/…@…#egg=
1313

1414
[git_repo]
1515
recipe = zc.recipe.egg

git_repo/repo.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,12 +438,14 @@ def do_request_fetch(self):
438438
@register_action('snippet', 'list')
439439
def do_gist_list(self):
440440
service = self.get_service(lookup_repository=False)
441-
if self.gist_ref:
441+
if 'github' == service.name and self.gist_ref:
442442
log.info("{:15}\t{:>7}\t{}".format('language', 'size', 'name'))
443+
else:
444+
log.info("{:56}\t{}".format('id', 'title'.ljust(60)))
445+
if self.gist_ref:
443446
for gist_file in service.gist_list(self.gist_ref):
444447
print("{:15}\t{:7}\t{}".format(*gist_file))
445448
else:
446-
log.info("{:56}\t{}".format('id', 'title'.ljust(60)))
447449
for gist in service.gist_list():
448450
print( "{:56}\t{}".format(gist[0], gist[1]))
449451
return 0

git_repo/services/ext/github.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ def request_fetch(self, user, repo, request, pull=False):
266266
try:
267267
for remote in self.repository.remotes:
268268
if remote.name == self.name:
269-
local_branch_name = 'request/{}'.format(request)
269+
local_branch_name = 'requests/github/{}'.format(request)
270270
self.fetch(
271271
remote,
272272
'pull/{}/head'.format(request),

git_repo/services/ext/gitlab.py

Lines changed: 189 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77
from ...exceptions import ArgumentError, ResourceError, ResourceExistsError, ResourceNotFoundError
88

99
import gitlab
10-
from gitlab.exceptions import GitlabCreateError, GitlabGetError
10+
from gitlab.exceptions import GitlabListError, GitlabCreateError, GitlabGetError
1111

12+
from git.exc import GitCommandError
13+
14+
import os
1215
import json, time
1316

1417
@register_target('lab', 'gitlab')
@@ -131,14 +134,198 @@ def get_repository(self, user, repo):
131134
return self.gl.projects.get('{}/{}'.format(user, repo))
132135
except GitlabGetError as err:
133136
if err.response_code == 404:
134-
raise ResourceNotFoundError("Cannot delete: repository {}/{} does not exists.".format(user, repo)) from err
137+
raise ResourceNotFoundError("Cannot get: repository {}/{} does not exists.".format(user, repo)) from err
135138

136139
@classmethod
137140
def get_auth_token(cls, login, password, prompt=None):
138141
gl = gitlab.Gitlab(url='https://{}'.format(cls.fqdn), email=login, password=password)
139142
gl.auth()
140143
return gl.user.private_token
141144

145+
def _deconstruct_snippet_uri(self, uri):
146+
path = uri.split('https://{}/'.format(self.fqdn))[-1].split('/')
147+
if 4 == len(path):
148+
user, project_name, _, snippet_id = path
149+
elif 2 == len(path):
150+
_, snippet_id = path
151+
project_name = None
152+
user = None
153+
elif 1 == len(path):
154+
snippet_id = path[0]
155+
project_name = None
156+
user = None
157+
else:
158+
raise ResourceNotFoundError('URL is not of a snippet')
159+
return (user, project_name, snippet_id)
160+
161+
def gist_list(self, project=None):
162+
if not project:
163+
try:
164+
for snippet in self.gl.snippets.list():
165+
yield (snippet.web_url, snippet.title)
166+
except GitlabListError as err:
167+
if err.response_code == 404:
168+
raise ResourceNotFoundError('Feature not available, please upgrade your gitlab instance.') from err
169+
raise ResourceError('Cannot list snippet') from err
170+
else:
171+
if '/' not in project:
172+
project = '/'.join([self.username, project])
173+
try:
174+
project = self.gl.projects.get(project)
175+
for snippet in project.snippets.list():
176+
yield (snippet.web_url, 0, snippet.title)
177+
except GitlabGetError as err:
178+
raise ResourceNotFoundError('Could not retrieve project "{}".'.format(project)) from err
179+
180+
def gist_fetch(self, snippet, fname=None):
181+
if fname:
182+
raise ArgumentError('Snippets contain only single file in gitlab.')
183+
try:
184+
*_, snippet_id = self._deconstruct_snippet_uri(snippet)
185+
snippet = self.gl.snippets.get(id=snippet_id)
186+
except GitlabGetError as err:
187+
if err.response_code == 404:
188+
if "The page you're looking for could not be found." in err.response_body.decode('utf-8'):
189+
raise ResourceNotFoundError('Feature not available, please upgrade your gitlab instance.') from err
190+
raise ResourceNotFoundError('Cannot fetch snippet') from err
191+
raise ResourceError('Cannot fetch snippet') from err
192+
except Exception as err:
193+
raise ResourceNotFoundError('Could not find snippet') from err
194+
195+
return snippet.raw().decode('utf-8')
196+
197+
def gist_clone(self, gist):
198+
raise ArgumentError('Snippets cannot be cloned in gitlab.')
199+
200+
def gist_create(self, gist_pathes, description, secret=False):
201+
def load_file(fname, path='.'):
202+
with open(os.path.join(path, fname), 'r') as f:
203+
return f.read()
204+
205+
if len(gist_pathes) > 2:
206+
raise ArgumentError('Snippets contain only single file in gitlab.')
207+
208+
data = {
209+
'title': description,
210+
'visibility_level': 0 if secret else 20
211+
}
212+
213+
try:
214+
215+
if len(gist_pathes) == 2:
216+
project = gist_pathes[0]
217+
if '/' in project:
218+
*namespace, project = project.split('/')
219+
namespace = '/'.join(namespace)
220+
else:
221+
namespace = self.username
222+
gist_path = gist_pathes[1]
223+
data.update({
224+
'project_id': '/'.join([namespace, project]),
225+
'code': load_file(gist_path),
226+
'file_name': os.path.basename(gist_path),
227+
}
228+
)
229+
gist = self.gl.project_snippets.create(data)
230+
231+
elif len(gist_pathes) == 1:
232+
gist_path = gist_pathes[0]
233+
data.update({
234+
'content': load_file(gist_path),
235+
'file_name': os.path.basename(gist_path),
236+
}
237+
)
238+
gist = self.gl.snippets.create(data)
239+
240+
return gist.web_url
241+
except GitlabCreateError as err:
242+
if err.response_code == 422:
243+
raise ResourceNotFoundError('Feature not available, please upgrade your gitlab instance.') from err
244+
raise ResourceError('Cannot create snippet') from err
245+
246+
def gist_delete(self, snippet):
247+
try:
248+
_, project, snippet_id = self._deconstruct_snippet_uri(snippet)
249+
if project:
250+
if '/' in project:
251+
*namespace, project = project.split('/')
252+
namespace = '/'.join(namespace)
253+
else:
254+
namespace = self.username
255+
snippet = self.gl.projects.get(
256+
'/'.join([namespace, project])
257+
).snippets.get(id=snippet_id)
258+
else:
259+
snippet = self.gl.snippets.get(id=snippet_id)
260+
except GitlabCreateError as err:
261+
if err.response_code == 422:
262+
raise ResourceNotFoundError('Cannot delete snippet, please upgrade your gitlab instance.') from err
263+
raise ResourceError('Cannot delete snippet') from err
264+
except Exception as err:
265+
raise ResourceNotFoundError('Could not find snippet') from err
266+
267+
return snippet.delete()
268+
269+
def request_create(self, user, repo, local_branch, remote_branch, title, description=None):
270+
try:
271+
repository = self.gl.projects.get('/'.join([user, repo]))
272+
if not repository:
273+
raise ResourceNotFoundError('Could not find repository `{}/{}`!'.format(user, repo))
274+
if not local_branch:
275+
remote_branch = self.repository.active_branch.name or self.repository.active_branch.name
276+
if not remote_branch:
277+
local_branch = repository.master_branch or 'master'
278+
request = self.gl.project_mergerequests.create(
279+
project_id=repository.id,
280+
data= {
281+
'source_branch':local_branch,
282+
'target_branch':remote_branch,
283+
'title':title,
284+
'description':description
285+
}
286+
)
287+
except GitlabGetError as err:
288+
raise ResourceNotFoundError(err) from err
289+
except Exception as err:
290+
raise ResourceError("Unhandled error: {}".format(err)) from err
291+
292+
return {'local': local_branch,
293+
'remote': remote_branch,
294+
'ref': request.iid}
295+
296+
def request_list(self, user, repo):
297+
project = self.gl.projects.get('/'.join([user, repo]))
298+
for mr in self.gl.project_mergerequests.list(project_id=project.id):
299+
yield ( str(mr.iid),
300+
mr.title,
301+
'https://{}/{}/{}/merge_requests/{}'.format(
302+
self.fqdn,
303+
project.namespace.name,
304+
project.name,
305+
mr.iid
306+
)
307+
)
308+
309+
def request_fetch(self, user, repo, request, pull=False):
310+
if pull:
311+
raise NotImplementedError('Pull operation on requests for merge are not yet supported')
312+
try:
313+
for remote in self.repository.remotes:
314+
if remote.name == self.name:
315+
local_branch_name = 'requests/gitlab/{}'.format(request)
316+
self.fetch(
317+
remote,
318+
'merge_requests/{}/head'.format(request),
319+
local_branch_name
320+
)
321+
return local_branch_name
322+
else:
323+
raise ResourceNotFoundError('Could not find remote {}'.format(self.name))
324+
except GitCommandError as err:
325+
if 'Error when fetching: fatal: Couldn\'t find remote ref' in err.command[0]:
326+
raise ResourceNotFoundError('Could not find opened request #{}'.format(request)) from err
327+
raise err
328+
142329
@property
143330
def user(self):
144331
return self.gl.user.username

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ progress
33
GitPython>=2.1.0
44
uritemplate.py==2.0.0
55
github3.py==0.9.5
6-
python-gitlab>=0.13
6+
python-gitlab>=0.18
77
bitbucket-api

tests/helpers.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ def action_request_list(self, namespace, repository, rq_list_data=[]):
618618
for i, rq in enumerate(rq_list_data):
619619
assert requests[i] == rq
620620

621-
def action_request_fetch(self, namespace, repository, request, pull=False, fail=False):
621+
def action_request_fetch(self, namespace, repository, request, pull=False, fail=False, remote_branch='pull', local_branch='requests'):
622622
local_slug = self.service.format_path(namespace=namespace, repository=repository, rw=False)
623623
with self.recorder.use_cassette(self._make_cassette_name()):
624624
with self.mockup_git(namespace, repository):
@@ -635,10 +635,14 @@ def action_request_fetch(self, namespace, repository, request, pull=False, fail=
635635
'Resolving deltas: 100% (5126/5126), done.',
636636
'From {}:{}/{}'.format(self.service.fqdn, namespace, repository),
637637
' * branch master -> FETCH_HEAD',
638-
' * [new branch] master -> {}/master'.format(self.service.name)]).encode('utf-8'),
638+
' * [new branch] master -> {1}/{0}'.format(request, local_branch)]).encode('utf-8'),
639639
0),
640640
('git version', b'git version 2.8.0', b'', 0),
641-
('git fetch --progress -v {0} pull/{1}/head:request/{1}'.format(self.service.name, request), b'', '\n'.join([
641+
('git fetch --progress -v {0} {2}/{1}/head:{3}/{1}'.format(
642+
self.service.name,
643+
request,
644+
remote_branch,
645+
local_branch), b'', '\n'.join([
642646
'POST git-upload-pack (140 bytes)',
643647
'remote: Counting objects: 8318, done.',
644648
'remote: Compressing objects: 100% (3/3), done.',
@@ -657,22 +661,26 @@ def action_request_fetch(self, namespace, repository, request, pull=False, fail=
657661
with self.mockup_git(namespace, repository):
658662
self.set_mock_popen_commands([
659663
('git version', b'git version 2.8.0', b'', 0),
660-
('git fetch --progress -v {0} pull/{1}/head:request/{1}'.format(self.service.name, request), b'', '\n'.join([
664+
('git fetch --progress -v {0} {2}/{1}/head:{3}/{1}'.format(
665+
self.service.name,
666+
request,
667+
remote_branch,
668+
local_branch), b'', '\n'.join([
661669
'POST git-upload-pack (140 bytes)',
662670
'remote: Counting objects: 8318, done.',
663671
'remote: Compressing objects: 100% (3/3), done.',
664672
'remote: Total 8318 (delta 0), reused 0 (delta 0), pack-reused 8315',
665673
'Receiving objects: 100% (8318/8318), 3.59 MiB | 974.00 KiB/s, done.',
666674
'Resolving deltas: 100% (5126/5126), done.',
667675
'From {}:{}/{}'.format(self.service.fqdn, namespace, repository),
668-
' * [new branch] master -> request/{}'.format(request)]).encode('utf-8'),
676+
' * [new branch] master -> {1}/{0}'.format(request, local_branch)]).encode('utf-8'),
669677
0)
670678
])
671679
self.service.request_fetch(repository, namespace, request)
672680

673681
def action_request_create(self,
674682
namespace, repository, branch,
675-
title, description,
683+
title, description, service,
676684
create_repository='test_create_requests',
677685
create_branch='pr-test'):
678686
'''
@@ -723,7 +731,7 @@ def prepare_project_for_test():
723731
test.write('La meilleure façon de ne pas avancer est de suivre une idée fixe. J.Prévert')
724732
self.repository.git.add('second_file')
725733
self.repository.git.commit(message='Second commit')
726-
self.repository.git.push('github', create_branch)
734+
self.repository.git.push(service, create_branch)
727735
yield
728736
if will_record:
729737
self.service.delete(create_repository)
@@ -749,7 +757,7 @@ def action_gist_list(self, gist=None, gist_list_data=[]):
749757
for i, g in enumerate(gist_list_data):
750758
assert gists[i] == g
751759
else:
752-
gist_files = list(self.service.gist_list())
760+
gist_files = list(self.service.gist_list(gist))
753761
for i, gf in enumerate(gist_list_data):
754762
assert gist_files[i] == gf
755763

0 commit comments

Comments
 (0)