8
8
9
9
from pybitbucket .bitbucket import Client , Bitbucket
10
10
from pybitbucket .auth import BasicAuthenticator
11
- from pybitbucket .repository import Repository , RepositoryForkPolicy
12
- from pybitbucket .snippet import Snippet
11
+ from pybitbucket .pullrequest import PullRequest , PullRequestPayload
12
+ from pybitbucket .repository import (
13
+ Repository , RepositoryPayload , RepositoryForkPayload ,
14
+ RepositoryForkPolicy , RepositoryType
15
+ )
16
+ from pybitbucket .snippet import Snippet , SnippetPayload
17
+ from pybitbucket .user import User
13
18
14
19
from requests import Request , Session
15
20
from requests .exceptions import HTTPError
16
- import json
21
+ import os , json
17
22
18
23
19
24
@register_target ('bb' , 'bitbucket' )
20
25
class BitbucketService (RepositoryService ):
21
26
fqdn = 'bitbucket.org'
22
27
23
28
def __init__ (self , * args , ** kwarg ):
24
- self .bb_client = Client ()
25
- self .bb = Bitbucket (self .bb_client )
29
+ self .bb = Bitbucket (Client ())
26
30
super (BitbucketService , self ).__init__ (* args , ** kwarg )
27
31
28
32
def connect (self ):
29
33
if not self ._privatekey :
30
34
raise ConnectionError ('Could not connect to BitBucket. Please configure .gitconfig with your bitbucket credentials.' )
31
35
if not ':' in self ._privatekey :
32
36
raise ConnectionError ('Could not connect to BitBucket. Please setup your private key with login:password' )
33
- self .bb_client .config = BasicAuthenticator (* self ._privatekey .split (':' )+ ['z+git-repo+pub@m0g.net' ])
34
- self .bb_client .session = self .bb_client .config .session
37
+ auth = BasicAuthenticator (* self ._privatekey .split (':' )+ ['z+git-repo+pub@m0g.net' ])
38
+ self .bb .client .config = auth
39
+ self .bb .client .session = self .bb .client .config .session = auth .start_http_session (self .bb .client .session )
35
40
try :
36
- self .user
41
+ _ = self .bb . client . config . who_am_i ()
37
42
except ResourceError as err :
38
43
raise ConnectionError ('Could not connect to BitBucket. Not authorized, wrong credentials.' ) from err
39
44
40
45
def create (self , user , repo , add = False ):
41
46
try :
42
47
repo = Repository .create (
43
- repo ,
44
- fork_policy = RepositoryForkPolicy .ALLOW_FORKS ,
45
- is_private = False ,
46
- client = self .bb_client
48
+ RepositoryPayload (dict (
49
+ fork_policy = RepositoryForkPolicy .ALLOW_FORKS ,
50
+ is_private = False
51
+ )),
52
+ repository_name = repo ,
53
+ owner = user ,
54
+ client = self .bb .client
47
55
)
48
56
if add :
49
- self .add (user = user , repo = repo , tracking = self .name )
57
+ self .add (user = user , repo = repo . name , tracking = self .name )
50
58
except HTTPError as err :
51
59
if '400' in err .args [0 ].split (' ' ):
52
60
raise ResourceExistsError ('Project {} already exists on this account.' .format (repo )) from err
53
61
raise ResourceError ("Couldn't complete creation: {}" .format (err )) from err
54
62
55
63
def fork (self , user , repo ):
56
- raise NotImplementedError ('No support yet by the underlying library.' )
57
- try :
58
- self .get_repository (user , repo ).fork ()
59
- except HTTPError as err :
60
- raise ResourceError ("Couldn't complete fork: {}" .format (err )) from err
64
+ # result = self.get_repository(user, repo).fork(
65
+ # RepositoryForkPayload(dict(name=repo)),
66
+ # owner=user)
67
+ resp = self .bb .client .session .post (
68
+ 'https://api.bitbucket.org/1.0/repositories/{}/{}/fork' .format (user , repo ),
69
+ data = {'name' : repo }
70
+ )
71
+ if 404 == resp .status_code :
72
+ raise ResourceNotFoundError ("Couldn't complete fork: {}" .format (resp .content .decode ('utf-8' )))
73
+ elif 200 != resp .status_code :
74
+ raise ResourceError ("Couldn't complete fork: {}" .format (resp .content .decode ('utf-8' )))
75
+ result = resp .json ()
61
76
return '/' .join ([result ['owner' ], result ['slug' ]])
62
77
63
78
def delete (self , repo , user = None ):
@@ -174,7 +189,7 @@ def gist_fetch(self, gist, fname=None):
174
189
else :
175
190
raise ResourceNotFoundError ('Could not find file within gist.' )
176
191
177
- return self .bb_client .session .get (
192
+ return self .bb . client .session .get (
178
193
'https://bitbucket.org/!api/2.0/snippets/{}/{}/files/{}' .format (user , gist , gist_file )
179
194
).content .decode ('utf-8' )
180
195
@@ -198,8 +213,7 @@ def gist_clone(self, gist):
198
213
199
214
def gist_create (self , gist_pathes , description , secret = False ):
200
215
def load_file (fname , path = '.' ):
201
- with open (os .path .join (path , fname ), 'r' ) as f :
202
- return {'content' : f .read ()}
216
+ return open (os .path .join (path , fname ), 'r' )
203
217
204
218
gist_files = dict ()
205
219
for gist_path in gist_pathes :
@@ -210,66 +224,130 @@ def load_file(fname, path='.'):
210
224
if not os .path .isdir (os .path .join (gist_path , gist_file )) and not gist_file .startswith ('.' ):
211
225
gist_files [gist_file ] = load_file (gist_file , gist_path )
212
226
213
- gist = self . gh . create_gist (
214
- description = description ,
227
+ try :
228
+ snippet = Snippet . create (
215
229
files = gist_files ,
216
- public = not secret # isn't it obvious? ☺
230
+ payload = SnippetPayload (
231
+ payload = dict (
232
+ title = description ,
233
+ scm = RepositoryType .GIT ,
234
+ is_private = secret
235
+ )
236
+ ),
237
+ client = self .bb .client
217
238
)
218
239
219
- return gist .html_url
240
+ return snippet .links ['html' ]['href' ]
241
+ except HTTPError as err :
242
+ raise ResourceError ("Couldn't create snippet: {}" .format (err )) from err
220
243
221
244
def gist_delete (self , gist_id ):
222
- gist = self .gh .gist (self ._format_gist (gist_id ))
223
- if not gist :
224
- raise ResourceNotFoundError ('Could not find gist' )
225
- gist .delete ()
245
+ try :
246
+ snippet = next (self .bb .snippetByOwnerAndSnippetId (owner = self .user , snippet_id = gist_id )).delete ()
247
+ except HTTPError as err :
248
+ if '404' in err .args [0 ].split (' ' ):
249
+ raise ResourceNotFoundError ("Could not find snippet {}." .format (gist_id )) from err
250
+ raise ResourceError ("Couldn't delete snippet: {}" .format (err )) from err
226
251
227
252
def request_create (self , user , repo , local_branch , remote_branch , title , description = None ):
228
- repository = self .gh .repository (user , repo )
229
- if not repository :
230
- raise ResourceNotFoundError ('Could not find repository `{}/{}`!' .format (user , repo ))
231
- if not remote_branch :
232
- remote_branch = self .repository .active_branch .name
233
- if not local_branch :
234
- local_branch = repository .master_branch or 'master'
235
253
try :
236
- request = repository .create_pull (title ,
237
- base = local_branch ,
238
- head = ':' .join ([user , remote_branch ]),
239
- body = description )
240
- except github3 .models .GitHubError as err :
241
- if err .code == 422 :
242
- if err .message == 'Validation Failed' :
243
- for error in err .errors :
244
- if 'message' in error :
245
- raise ResourceError (error ['message' ])
246
- raise ResourceError ("Unhandled formatting error: {}" .format (err .errors ))
247
- raise ResourceError (err .message )
248
-
249
- return {'local' : local_branch , 'remote' : remote_branch , 'ref' : request .number }
254
+ repository = next (self .bb .repositoryByOwnerAndRepositoryName (owner = user , repository_name = repo ))
255
+ if not repository :
256
+ raise ResourceNotFoundError ('Could not find repository `{}/{}`!' .format (user , repo ))
257
+ if not remote_branch :
258
+ try :
259
+ remote_branch = next (repository .branches ()).name
260
+ except StopIteration :
261
+ remote_branch = 'master'
262
+ if not local_branch :
263
+ local_branch = self .repository .active_branch .name
264
+ request = PullRequest .create (
265
+ PullRequestPayload (
266
+ payload = dict (
267
+ title = title ,
268
+ description = description or '' ,
269
+ destination = dict (
270
+ branch = dict (name = remote_branch )
271
+ ),
272
+ source = dict (
273
+ repository = dict (full_name = '/' .join ([self .user , repo ])),
274
+ branch = dict (name = local_branch )
275
+ )
276
+ )
277
+ ),
278
+ repository_name = repo ,
279
+ owner = user ,
280
+ client = self .bb .client
281
+ )
282
+ except HTTPError as err :
283
+ status_code = hasattr (err , 'code' ) and err .code or err .response .status_code
284
+ if 404 == status_code :
285
+ raise ResourceNotFoundError ("Couldn't create request, project not found: {}" .format (repo )) from err
286
+ elif 400 == status_code and 'branch not found' in err .format_message ():
287
+ raise ResourceNotFoundError ("Couldn't create request, branch not found: {}" .format (local_branch )) from err
288
+ raise ResourceError ("Couldn't create request: {}" .format (err )) from err
289
+
290
+ return {'local' : local_branch , 'remote' : remote_branch , 'ref' : str (request .id )}
250
291
251
292
def request_list (self , user , repo ):
252
- repository = self .gh .repository (user , repo )
253
- for pull in repository .iter_pulls ():
254
- yield ( str (pull .number ), pull .title , pull .links ['issue' ] )
293
+ requests = set (
294
+ (
295
+ str (r .id ),
296
+ r .title ,
297
+ r .links ['html' ]['href' ]
298
+ ) for r in self .bb .repositoryPullRequestsInState (
299
+ owner = user ,
300
+ repository_name = repo ,
301
+ state = 'open'
302
+ ) if not isinstance (r , dict ) # if no PR is empty, result is a dict
303
+ )
304
+ for pull in sorted (requests ):
305
+ try :
306
+ yield pull
307
+ except Exception as err :
308
+ log .warn ('Error while fetching request information: {}' .format (pull ))
255
309
256
310
def request_fetch (self , user , repo , request , pull = False ):
311
+ log .warn ('Bitbucket does not support fetching of PR using git. Use this command at your own risk.' )
312
+ if 'y' not in input ('Are you sure to continue? [yN]> ' ):
313
+ raise ResourceError ('Command aborted.' )
257
314
if pull :
258
315
raise NotImplementedError ('Pull operation on requests for merge are not yet supported' )
259
316
try :
260
- for remote in self .repository .remotes :
261
- if remote .name == self .name :
262
- local_branch_name = 'request/{}' .format (request )
263
- self .fetch (
264
- remote ,
265
- 'pull/{}/head' .format (request ),
266
- local_branch_name
267
- )
268
- return local_branch_name
269
- else :
270
- raise ResourceNotFoundError ('Could not find remote {}' .format (self .name ))
317
+ repository = self .get_repository (user , repo )
318
+ if self .repository .is_dirty ():
319
+ raise ResourceError ('Please use this command after stashing your changes.' )
320
+ local_branch_name = 'requests/bitbucket/{}' .format (request )
321
+ index = self .repository .index
322
+ log .info ('» Fetching pull request {}' .format (request ))
323
+ request = next (bb .repositoryPullRequestByPullRequestId (
324
+ owner = user ,
325
+ repository_name = repo ,
326
+ pullrequest_id = request
327
+ ))
328
+ commit = self .repository .rev_parse (request ['destination' ]['commit' ]['hash' ])
329
+ self .repository .head .reference = commit
330
+ log .info ('» Creation of requests branch {}' .format (local_branch_name ))
331
+ # create new branch
332
+ head = self .repository .create_head (local_branch_name )
333
+ head .checkout ()
334
+ # fetch and apply patch
335
+ log .info ('» Fetching and writing the patch in current directory' )
336
+ patch = bb .client .session .get (request ['links' ]['diff' ]['href' ]).content .decode ('utf-8' )
337
+ with open ('.tmp.patch' , 'w' ) as f :
338
+ f .write (patch )
339
+ log .info ('» Applying the patch' )
340
+ git .cmd .Git ().apply ('.tmp.patch' , stat = True )
341
+ os .unlink ('.tmp.patch' )
342
+ log .info ('» Going back to original branch' )
343
+ index .checkout () # back to former branch
344
+ return local_branch_name
345
+ except HTTPError as err :
346
+ if '404' in err .args [0 ].split (' ' ):
347
+ raise ResourceNotFoundError ("Could not find snippet {}." .format (gist_id )) from err
348
+ raise ResourceError ("Couldn't delete snippet: {}" .format (err )) from err
271
349
except GitCommandError as err :
272
- if 'Error when fetching: fatal: Couldn \' t find remote ref ' in err .command [0 ]:
350
+ if 'Error when fetching: fatal: ' in err .command [0 ]:
273
351
raise ResourceNotFoundError ('Could not find opened request #{}' .format (request )) from err
274
352
raise err
275
353
0 commit comments