Skip to content

Commit 18605b4

Browse files
committed
Merge branch feature/better_lists into devel
2 parents 9542185 + a7359b3 commit 18605b4

File tree

9 files changed

+265
-207
lines changed

9 files changed

+265
-207
lines changed

git_repo/repo.py

Lines changed: 62 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616
{self} [--path=<path>] [-v...] <target> add <user>/<repo> [<name>] [--tracking=<branch>] [-a]
1717
{self} [--path=<path>] [-v...] <target> request (list|ls)
1818
{self} [--path=<path>] [-v...] <target> request fetch <request> [-f]
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>]
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>]
2222
{self} [--path=<path>] [-v...] <target> request <user>/<repo> (list|ls)
2323
{self} [--path=<path>] [-v...] <target> request <user>/<repo> fetch <request> [-f]
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>]
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>]
2727
{self} [--path=<path>] [-v...] <target> (gist|snippet) (list|ls) [<gist>]
2828
{self} [--path=<path>] [-v...] <target> (gist|snippet) clone <gist>
2929
{self} [--path=<path>] [-v...] <target> (gist|snippet) fetch <gist> [<gist_file>]
@@ -89,7 +89,7 @@
8989
--secret Do not publicize gist when pushing
9090
9191
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
9393
-m,--message=<message> Description for the request for merge
9494
9595
Configuration options:
@@ -137,31 +137,16 @@
137137
from .exceptions import ArgumentError, ResourceNotFoundError
138138
from .services.service import RepositoryService
139139

140+
from .tools import print_tty, print_iter, loop_input, confirm
140141
from .kwargparse import KeywordArgumentParser, store_parameter, register_action
141142

142143
from git import Repo, Git
143-
from git.exc import InvalidGitRepositoryError, NoSuchPathError
144+
from git.exc import InvalidGitRepositoryError, NoSuchPathError, BadName
144145

145146
import re
146147

147148
EXTRACT_URL_RE = re.compile('[^:]*(://|@)[^/]*/')
148149

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-
165150

166151
class GitRepoRunner(KeywordArgumentParser):
167152

@@ -283,8 +268,7 @@ def store_gitconfig(self, val):
283268
@register_action('ls')
284269
@register_action('list')
285270
def do_list(self):
286-
service = self.get_service(False)
287-
service.list(self.user, self.long)
271+
print_iter(self.get_service(False).list(self.user, self.long))
288272
return 0
289273

290274
@register_action('add')
@@ -403,26 +387,70 @@ def do_open(self):
403387
@register_action('request', 'list')
404388
def do_request_list(self):
405389
service = self.get_service(lookup_repository=self.repo_slug == None)
406-
log.info('List of open requests to merge:')
407-
log.info(" {}\t{}\t{}".format('id', 'title'.ljust(60), 'URL'))
408-
for pr in service.request_list(self.user_name, self.repo_name):
409-
print("{}\t{}\t{}".format(pr[0].rjust(3), pr[1][:60].ljust(60), pr[2]))
390+
print_tty('List of open requests to merge:')
391+
print_iter(service.request_list(self.user_name, self.repo_name))
410392
return 0
411393

412394
@register_action('request', 'create')
413395
def do_request_create(self):
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+
414438
service = self.get_service(resolve_targets=('upstream', '{service}', 'origin'))
439+
415440
new_request = service.request_create(self.user_name,
416441
self.repo_name,
417442
self.local_branch,
418443
self.remote_branch,
419444
self.title,
420445
self.message,
421-
self.repo_slug != None)
446+
self.repo_slug != None,
447+
request_edition)
422448
log.info('Successfully created request of `{local}` onto `{}:{remote}`, with id `{ref}`!'.format(
423449
'/'.join([self.user_name, self.repo_name]),
424450
**new_request)
425451
)
452+
if 'url' in new_request:
453+
log.info('available at: {url}'.format(**new_request))
426454
return 0
427455

428456
@register_action('request', 'fetch')
@@ -442,16 +470,7 @@ def do_request_fetch(self):
442470
@register_action('snippet', 'list')
443471
def do_gist_list(self):
444472
service = self.get_service(lookup_repository=False)
445-
if 'github' == service.name and self.gist_ref:
446-
log.info("{:15}\t{:>7}\t{}".format('language', 'size', 'name'))
447-
else:
448-
log.info("{:56}\t{}".format('id', 'title'.ljust(60)))
449-
if self.gist_ref:
450-
for gist_file in service.gist_list(self.gist_ref):
451-
print("{:15}\t{:7}\t{}".format(*gist_file))
452-
else:
453-
for gist in service.gist_list():
454-
print( "{:56}\t{}".format(gist[0], gist[1]))
473+
print_iter(service.gist_list(self.gist_ref or None))
455474
return 0
456475

457476
@register_action('gist', 'clone')
@@ -496,12 +515,6 @@ def do_gist_delete(self):
496515
def do_config(self):
497516
from getpass import getpass
498517

499-
def loop_input(*args, method=input, **kwarg):
500-
out = ''
501-
while len(out) == 0:
502-
out = method(*args, **kwarg)
503-
return out
504-
505518
def setup_service(service):
506519
new_conf = dict(
507520
fqdn=None,
@@ -592,7 +605,8 @@ def cli(): #pragma: no cover
592605
sys.exit(main(docopt(__doc__.format(self=sys.argv[0].split('/')[-1], version=__version__))))
593606
finally:
594607
# Whatever happens, make sure that the cursor reappears with some ANSI voodoo
595-
sys.stdout.write('\033[?25h')
608+
if sys.stdout.isatty():
609+
sys.stdout.write('\033[?25h')
596610

597611
if __name__ == '__main__': #pragma: no cover
598612
cli()

git_repo/services/ext/github.py

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
log = logging.getLogger('git_repo.github')
55

66
from ..service import register_target, RepositoryService, os
7-
from ...exceptions import ResourceError, ResourceExistsError, ResourceNotFoundError
7+
from ...exceptions import ResourceError, ResourceExistsError, ResourceNotFoundError, ArgumentError
8+
from ...tools import columnize
89

910
import github3
1011

1112
from git.exc import GitCommandError
1213

14+
from datetime import datetime
15+
1316
@register_target('hub', 'github')
1417
class GithubService(RepositoryService):
1518
fqdn = 'github.com'
@@ -75,37 +78,18 @@ def delete(self, repo, user=None):
7578
raise ResourceError('Unhandled exception: {}'.format(err)) from err
7679

7780
def list(self, user, _long=False):
78-
import shutil, sys
79-
from datetime import datetime
80-
term_width = shutil.get_terminal_size((80, 20)).columns
81-
def col_print(lines, indent=0, pad=2):
82-
# prints a list of items in a fashion similar to the dir command
83-
# borrowed from https://gist.github.com/critiqjo/2ca84db26daaeb1715e1
84-
n_lines = len(lines)
85-
if n_lines == 0:
86-
return
87-
col_width = max(len(line) for line in lines)
88-
n_cols = int((term_width + pad - indent)/(col_width + pad))
89-
n_cols = min(n_lines, max(1, n_cols))
90-
col_len = int(n_lines/n_cols) + (0 if n_lines % n_cols == 0 else 1)
91-
if (n_cols - 1) * col_len >= n_lines:
92-
n_cols -= 1
93-
cols = [lines[i*col_len : i*col_len + col_len] for i in range(n_cols)]
94-
rows = list(zip(*cols))
95-
rows_missed = zip(*[col[len(rows):] for col in cols[:-1]])
96-
rows.extend(rows_missed)
97-
for row in rows:
98-
print(" "*indent + (" "*pad).join(line.ljust(col_width) for line in row))
99-
10081
if not self.gh.user(user):
10182
raise ResourceNotFoundError("User {} does not exists.".format(user))
10283

10384
repositories = self.gh.iter_user_repos(user)
10485
if not _long:
105-
repositories = list(repositories)
106-
col_print(["/".join([user, repo.name]) for repo in repositories])
86+
repositories = list(["/".join([user, repo.name]) for repo in repositories])
87+
yield "{}"
88+
yield "Total repositories: {}".format(len(repositories))
89+
yield from columnize(repositories)
10790
else:
108-
print('Status\tCommits\tReqs\tIssues\tForks\tCoders\tWatch\tLikes\tLang\tModif\t\tName', file=sys.stderr)
91+
yield "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t\t{}"
92+
yield ['Status', 'Commits', 'Reqs', 'Issues', 'Forks', 'Coders', 'Watch', 'Likes', 'Lang', 'Modif', 'Name']
10993
for repo in repositories:
11094
try:
11195
if repo.updated_at.year < datetime.now().year:
@@ -119,7 +103,7 @@ def col_print(lines, indent=0, pad=2):
119103
])
120104
nb_pulls = len(list(repo.iter_pulls()))
121105
nb_issues = len(list(repo.iter_issues())) - nb_pulls
122-
print('\t'.join([
106+
yield [
123107
# status
124108
status,
125109
# stats
@@ -134,10 +118,10 @@ def col_print(lines, indent=0, pad=2):
134118
repo.language or '?', # language
135119
repo.updated_at.strftime(date_fmt), # date
136120
'/'.join([user, repo.name]), # name
137-
]))
121+
]
138122
except Exception as err:
139123
if 'Git Repository is empty.' == err.args[0].json()['message']:
140-
print('\t'.join([
124+
yield [
141125
# status
142126
'E',
143127
# stats
@@ -152,7 +136,7 @@ def col_print(lines, indent=0, pad=2):
152136
'?', # language
153137
repo.updated_at.strftime(date_fmt), # date
154138
'/'.join([user, repo.name]), # name
155-
]))
139+
]
156140
else:
157141
print("Cannot show repository {}: {}".format('/'.join([user, repo.name]), err))
158142

@@ -167,12 +151,16 @@ def _format_gist(self, gist):
167151

168152
def gist_list(self, gist=None):
169153
if not gist:
154+
yield "{:45.45} {}"
155+
yield 'title', 'url'
170156
for gist in self.gh.iter_gists(self.gh.user().login):
171-
yield (gist.html_url, gist.description)
157+
yield gist.description, gist.html_url
172158
else:
173159
gist = self.gh.gist(self._format_gist(gist))
174160
if gist is None:
175161
raise ResourceNotFoundError('Gist does not exists.')
162+
yield "{:15}\t{:7}\t{}"
163+
yield 'language', 'size', 'name'
176164
for gist_file in gist.iter_files():
177165
yield (gist_file.language if gist_file.language else 'Raw text',
178166
gist_file.size,
@@ -233,7 +221,7 @@ def gist_delete(self, gist_id):
233221
raise ResourceNotFoundError('Could not find gist')
234222
gist.delete()
235223

236-
def request_create(self, user, repo, from_branch, onto_branch, title, description=None, auto_slug=False):
224+
def request_create(self, user, repo, from_branch, onto_branch, title=None, description=None, auto_slug=False, edit=None):
237225
repository = self.gh.repository(user, repo)
238226
if not repository:
239227
raise ResourceNotFoundError('Could not find repository `{}/{}`!'.format(user, repo))
@@ -255,6 +243,10 @@ def request_create(self, user, repo, from_branch, onto_branch, title, descriptio
255243
onto_branch = repository.default_branch or 'master'
256244
if self.username != repository.owner.login:
257245
from_branch = ':'.join([self.username, from_branch])
246+
if not title and not description and edit:
247+
title, description = edit(self.repository, from_branch)
248+
if not title and not description:
249+
raise ArgumentError('Missing message for request creation')
258250
try:
259251
request = repository.create_pull(title,
260252
base=onto_branch,
@@ -276,12 +268,15 @@ def request_create(self, user, repo, from_branch, onto_branch, title, descriptio
276268
raise ResourceError("Unhandled formatting error: {}".format(err.errors))
277269
raise ResourceError(err.message)
278270

279-
return {'local': from_branch, 'remote': onto_branch, 'ref': request.number}
271+
return {'local': from_branch, 'remote': onto_branch, 'ref': request.number,
272+
'url': request.html_url}
280273

281274
def request_list(self, user, repo):
282275
repository = self.gh.repository(user, repo)
276+
yield "{}\t{:<60}\t{}"
277+
yield 'id', 'title', 'URL'
283278
for pull in repository.iter_pulls():
284-
yield ( str(pull.number), pull.title, pull.links['html'] )
279+
yield str(pull.number), pull.title, pull.links['html']
285280

286281
def request_fetch(self, user, repo, request, pull=False, force=False):
287282
if pull:

0 commit comments

Comments
 (0)