15
15
{self} [--path=<path>] [-v...] <target> clone <user>/<repo> [<repo> [<branch>]]
16
16
{self} [--path=<path>] [-v...] <target> add <user>/<repo> [<name>] [--tracking=<branch>] [-a]
17
17
{self} [--path=<path>] [-v...] <target> request (list|ls)
18
- {self} [--path=<path>] [-v...] <target> request fetch <request>
19
- {self} [--path=<path>] [-v...] <target> request create <title> [--message=<message>]
20
- {self} [--path=<path>] [-v...] <target> request create <local_branch> <title> [--message=<message>]
21
- {self} [--path=<path>] [-v...] <target> request create <remote_branch> <local_branch> <title> [--message=<message>]
18
+ {self} [--path=<path>] [-v...] <target> request fetch <request> [-f]
19
+ {self} [--path=<path>] [-v...] <target> request create [--title= <title>] [--message=<message>]
20
+ {self} [--path=<path>] [-v...] <target> request create <local_branch> [--title= <title>] [--message=<message>]
21
+ {self} [--path=<path>] [-v...] <target> request create <remote_branch> <local_branch> [--title= <title>] [--message=<message>]
22
22
{self} [--path=<path>] [-v...] <target> request <user>/<repo> (list|ls)
23
- {self} [--path=<path>] [-v...] <target> request <user>/<repo> fetch <request>
24
- {self} [--path=<path>] [-v...] <target> request <user>/<repo> create <title> [--branch=<remote>] [--message=<message>]
25
- {self} [--path=<path>] [-v...] <target> request <user>/<repo> create <local_branch> <title> [--branch=<remote>] [--message=<message>]
26
- {self} [--path=<path>] [-v...] <target> request <user>/<repo> create <remote_branch> <local_branch> <title> [--branch=<remote>] [--message=<message>]
23
+ {self} [--path=<path>] [-v...] <target> request <user>/<repo> fetch <request> [-f]
24
+ {self} [--path=<path>] [-v...] <target> request <user>/<repo> create [--title= <title>] [--branch=<remote>] [--message=<message>]
25
+ {self} [--path=<path>] [-v...] <target> request <user>/<repo> create <local_branch> [--title= <title>] [--branch=<remote>] [--message=<message>]
26
+ {self} [--path=<path>] [-v...] <target> request <user>/<repo> create <remote_branch> <local_branch> [--title= <title>] [--branch=<remote>] [--message=<message>]
27
27
{self} [--path=<path>] [-v...] <target> (gist|snippet) (list|ls) [<gist>]
28
28
{self} [--path=<path>] [-v...] <target> (gist|snippet) clone <gist>
29
29
{self} [--path=<path>] [-v...] <target> (gist|snippet) fetch <gist> [<gist_file>]
89
89
--secret Do not publicize gist when pushing
90
90
91
91
Options for request:
92
- <title> Title to give to the request for merge
92
+ -t,--title= <title> Title to give to the request for merge
93
93
-m,--message=<message> Description for the request for merge
94
94
95
95
Configuration options:
137
137
from .exceptions import ArgumentError , ResourceNotFoundError
138
138
from .services .service import RepositoryService
139
139
140
+ from .tools import print_tty , print_iter , loop_input , confirm
140
141
from .kwargparse import KeywordArgumentParser , store_parameter , register_action
141
142
142
143
from git import Repo , Git
143
- from git .exc import InvalidGitRepositoryError , NoSuchPathError
144
+ from git .exc import InvalidGitRepositoryError , NoSuchPathError , BadName
144
145
145
146
import re
146
147
147
148
EXTRACT_URL_RE = re .compile ('[^:]*(://|@)[^/]*/' )
148
149
149
- def confirm (what , where ):
150
- '''
151
- Method to show a CLI based confirmation message, waiting for a yes/no answer.
152
- "what" and "where" are used to better define the message.
153
- '''
154
- ans = input ('Are you sure you want to delete the '
155
- '{} {} from the service?\n [yN]> ' .format (what , where ))
156
- if 'y' in ans :
157
- ans = input ('Are you really sure? there\' s no coming back!\n '
158
- '[type \' burn!\' to proceed]> ' )
159
- if 'burn!' != ans :
160
- return False
161
- else :
162
- return False
163
- return True
164
-
165
150
166
151
class GitRepoRunner (KeywordArgumentParser ):
167
152
168
153
def init (self ): # pragma: no cover
169
154
if 'GIT_WORK_TREE' in os .environ .keys () or 'GIT_DIR' in os .environ .keys ():
170
155
del os .environ ['GIT_WORK_TREE' ]
171
156
172
- def _guess_repo_slug (self , repository , service ):
157
+ def _guess_repo_slug (self , repository , service , resolve_targets = None ):
173
158
config = repository .config_reader ()
174
- target = service .name
159
+ if resolve_targets :
160
+ targets = [target .format (service = service .name ) for target in resolve_targets ]
161
+ else :
162
+ targets = (service .name , 'upstream' , 'origin' )
175
163
for remote in repository .remotes :
176
- if remote .name in ( target , 'upstream' , 'origin' ) :
164
+ if remote .name in targets :
177
165
for url in remote .urls :
178
166
if url .startswith ('https' ):
179
167
if url .endswith ('.git' ):
180
168
url = url [:- 4 ]
181
169
* _ , user , name = url .split ('/' )
182
170
self .set_repo_slug ('/' .join ([user , name ]))
183
- break
171
+ return
184
172
elif url .startswith ('git@' ):
185
173
if url .endswith ('.git' ):
186
174
url = url [:- 4 ]
187
175
_ , repo_slug = url .split (':' )
188
176
self .set_repo_slug (repo_slug )
189
- break
177
+ return
190
178
191
- def get_service (self , lookup_repository = True ):
179
+ def get_service (self , lookup_repository = True , resolve_targets = None ):
192
180
if not lookup_repository :
193
181
service = RepositoryService .get_service (None , self .target )
194
182
service .connect ()
@@ -203,7 +191,7 @@ def get_service(self, lookup_repository=True):
203
191
raise FileNotFoundError ('Cannot find path to the repository.' )
204
192
service = RepositoryService .get_service (repository , self .target )
205
193
if not self .repo_name :
206
- self ._guess_repo_slug (repository , service )
194
+ self ._guess_repo_slug (repository , service , resolve_targets )
207
195
return service
208
196
209
197
'''Argument storage'''
@@ -273,15 +261,14 @@ def set_gist_ref(self, gist):
273
261
274
262
@store_parameter ('--config' )
275
263
def store_gitconfig (self , val ):
276
- self .config = val or os . path . join ( os . environ [ 'HOME' ], '.gitconfig' )
264
+ self .config = val or RepositoryService . get_config_path ( )
277
265
278
266
'''Actions'''
279
267
280
268
@register_action ('ls' )
281
269
@register_action ('list' )
282
270
def do_list (self ):
283
- service = self .get_service (False )
284
- service .list (self .user , self .long )
271
+ print_iter (self .get_service (False ).list (self .user , self .long ))
285
272
return 0
286
273
287
274
@register_action ('add' )
@@ -399,32 +386,77 @@ def do_open(self):
399
386
@register_action ('request' , 'ls' )
400
387
@register_action ('request' , 'list' )
401
388
def do_request_list (self ):
402
- service = self .get_service ()
403
- log .info ('List of open requests to merge:' )
404
- log .info (" {}\t {}\t {}" .format ('id' , 'title' .ljust (60 ), 'URL' ))
405
- for pr in service .request_list (self .user_name , self .repo_name ):
406
- print ("{}\t {}\t {}" .format (pr [0 ].rjust (3 ), pr [1 ][:60 ].ljust (60 ), pr [2 ]))
389
+ service = self .get_service (lookup_repository = self .repo_slug == None )
390
+ print_tty ('List of open requests to merge:' )
391
+ print_iter (service .request_list (self .user_name , self .repo_name ))
407
392
return 0
408
393
409
394
@register_action ('request' , 'create' )
410
395
def do_request_create (self ):
411
- service = self .get_service ()
396
+ def request_edition (repository , from_branch ):
397
+ try :
398
+ commit = repository .commit (from_branch )
399
+ title , * body = commit .message .split ('\n ' )
400
+ except BadName :
401
+ log .error ('Couldn\' t find local source branch {}' .format (from_branch ))
402
+ return None
403
+ from tempfile import NamedTemporaryFile
404
+ from subprocess import call
405
+ with NamedTemporaryFile (
406
+ prefix = 'git-repo-issue-' ,
407
+ suffix = '.md' ,
408
+ mode = 'w+b' ) as request_file :
409
+ request_file .write ((
410
+ '# Request for Merge Title ##########################\n '
411
+ '{}\n '
412
+ '\n '
413
+ '# Request for Merge Body ###########################\n '
414
+ '{}\n '
415
+ '####################################################\n '
416
+ '## Filled with commit:\n '
417
+ '## {}\n '
418
+ '####################################################\n '
419
+ '## * All lines starting with # will be ignored.\n '
420
+ '## * First non-ignored line is the title of the request.\n '
421
+ ).format (title , '\n ' .join (body ), commit .name_rev ).encode ('utf-8' ))
422
+ request_file .flush ()
423
+ rv = call ("{} {}" .format (os .environ ['EDITOR' ], request_file .name ), shell = True )
424
+ if rv != 0 :
425
+ raise ArgumentError ("Aborting request, as editor exited abnormally." )
426
+ request_file .seek (0 )
427
+ request_message = map (lambda l : l .decode ('utf-8' ),
428
+ filter (lambda l : not l .strip ().startswith (b'#' ), request_file .readlines ()))
429
+ try :
430
+ title = next (request_message )
431
+ body = '' .join (request_message )
432
+ except Exception :
433
+ raise ResourceError ("Format of the request message cannot be parsed." )
434
+
435
+ return title , body
436
+
437
+
438
+ service = self .get_service (resolve_targets = ('upstream' , '{service}' , 'origin' ))
439
+
412
440
new_request = service .request_create (self .user_name ,
413
441
self .repo_name ,
414
442
self .local_branch ,
415
443
self .remote_branch ,
416
444
self .title ,
417
- self .message )
445
+ self .message ,
446
+ self .repo_slug != None ,
447
+ request_edition )
418
448
log .info ('Successfully created request of `{local}` onto `{}:{remote}`, with id `{ref}`!' .format (
419
449
'/' .join ([self .user_name , self .repo_name ]),
420
450
** new_request )
421
451
)
452
+ if 'url' in new_request :
453
+ log .info ('available at: {url}' .format (** new_request ))
422
454
return 0
423
455
424
456
@register_action ('request' , 'fetch' )
425
457
def do_request_fetch (self ):
426
458
service = self .get_service ()
427
- new_branch = service .request_fetch (self .user_name , self .repo_name , self .request )
459
+ new_branch = service .request_fetch (self .user_name , self .repo_name , self .request , force = self . force )
428
460
log .info ('Successfully fetched request id `{}` of `{}` into `{}`!' .format (
429
461
self .request ,
430
462
self .repo_slug ,
@@ -438,16 +470,7 @@ def do_request_fetch(self):
438
470
@register_action ('snippet' , 'list' )
439
471
def do_gist_list (self ):
440
472
service = self .get_service (lookup_repository = False )
441
- if 'github' == service .name and self .gist_ref :
442
- 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 :
446
- for gist_file in service .gist_list (self .gist_ref ):
447
- print ("{:15}\t {:7}\t {}" .format (* gist_file ))
448
- else :
449
- for gist in service .gist_list ():
450
- print ( "{:56}\t {}" .format (gist [0 ], gist [1 ]))
473
+ print_iter (service .gist_list (self .gist_ref or None ))
451
474
return 0
452
475
453
476
@register_action ('gist' , 'clone' )
@@ -492,28 +515,55 @@ def do_gist_delete(self):
492
515
def do_config (self ):
493
516
from getpass import getpass
494
517
495
- def loop_input (* args , method = input , ** kwarg ):
496
- out = ''
497
- while len (out ) == 0 :
498
- out = method (* args , ** kwarg )
499
- return out
500
-
501
518
def setup_service (service ):
519
+ new_conf = dict (
520
+ fqdn = None ,
521
+ remote = None ,
522
+ )
502
523
conf = service .get_config (self .config )
503
524
if 'token' in conf :
504
525
raise Exception ('A token has been generated for this service. Please revoke and delete before proceeding.' )
505
526
527
+ print ('Is your service self-hosted?' )
528
+ if 'y' in input (' [yN]> ' ).lower ():
529
+ new_conf ['type' ] = service .name
530
+ print ('What name do you want to give this service?' )
531
+ new_conf ['name' ] = input ('[{}]> ' .format (service .name ))
532
+ new_conf ['command' ] = new_conf ['name' ]
533
+ service .name , service .command = new_conf ['name' ], new_conf ['command' ]
534
+ print ('Enter the service\' s domain name:' )
535
+ new_conf ['fqdn' ] = input ('[{}]> ' .format (service .fqdn ))
536
+ print ('Enter the service\' s port:' )
537
+ new_conf ['port' ] = input ('[443]> ' ) or '443'
538
+ print ('Are you connecting using HTTPS? (you should):' )
539
+ if 'n' in input (' [Yn]> ' ).lower ():
540
+ new_conf ['scheme' ] = 'http'
541
+ else :
542
+ new_conf ['scheme' ] = 'https'
543
+ print ('Do you need to use an insecure connection? (you shouldn\' t):' )
544
+ new_conf ['insecure' ] = 'y' in input (' [yN]> ' ).lower ()
545
+ service .session_insecure = new_conf ['insecure' ]
546
+ if not new_conf ['insecure' ]:
547
+ print ('Do you want to setup the path to custom certificate?:' )
548
+ if 'y' in input (' [yN]> ' ).lower ():
549
+ new_conf ['server-cert' ] = loop_input ('/path/to/certbundle.pem []> ' )
550
+ service .session_certificate = new_conf ['server-cert' ]
551
+
552
+ service .fqdn = new_conf ['fqdn' ]
553
+ service .port = new_conf ['port' ]
554
+ service .scheme = new_conf ['scheme' ]
555
+
506
556
print ('Please enter your credentials to connect to the service:' )
507
557
username = loop_input ('username> ' )
508
558
password = loop_input ('password> ' , method = getpass )
509
559
510
- token = service .get_auth_token (username , password , prompt = loop_input )
560
+ new_conf [ ' token' ] = service .get_auth_token (username , password , prompt = loop_input )
511
561
print ('Great! You\' ve been identified 🍻' )
512
562
513
563
print ('Do you want to give a custom name for this service\' s remote?' )
514
564
if 'y' in input (' [yN]> ' ).lower ():
515
565
print ('Enter the remote\' s name:' )
516
- loop_input ('[{}]> ' .format (service .name ))
566
+ new_conf [ 'remote' ] = loop_input ('[{}]> ' .format (service .name ))
517
567
518
568
print ('Do you want to configure a git alias?' )
519
569
print ('N.B.: instead of typing `git repo {0}` you\' ll be able to type `git {0}`' .format (service .command ))
@@ -522,7 +572,7 @@ def setup_service(service):
522
572
else :
523
573
set_alias = True
524
574
525
- service .store_config (self .config , token = token )
575
+ service .store_config (self .config , ** new_conf )
526
576
if set_alias :
527
577
service .set_alias (self .config )
528
578
@@ -555,7 +605,8 @@ def cli(): #pragma: no cover
555
605
sys .exit (main (docopt (__doc__ .format (self = sys .argv [0 ].split ('/' )[- 1 ], version = __version__ ))))
556
606
finally :
557
607
# Whatever happens, make sure that the cursor reappears with some ANSI voodoo
558
- sys .stdout .write ('\033 [?25h' )
608
+ if sys .stdout .isatty ():
609
+ sys .stdout .write ('\033 [?25h' )
559
610
560
611
if __name__ == '__main__' : #pragma: no cover
561
612
cli ()
0 commit comments