Merge ~nacc/git-ubuntu:gu-review into git-ubuntu:master

Proposed by Nish Aravamudan
Status: Superseded
Proposed branch: ~nacc/git-ubuntu:gu-review
Merge into: git-ubuntu:master
Prerequisite: ~nacc/git-ubuntu:refactor-main-v2
Diff against target: 453 lines (+247/-41)
5 files modified
gitubuntu/__main__.py (+38/-11)
gitubuntu/clone.py (+34/-7)
gitubuntu/lint.py (+22/-8)
gitubuntu/remote.py (+50/-15)
gitubuntu/review.py (+103/-0)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Needs Fixing
Andreas Hasenack Pending
Robie Basak Pending
Review via email: mp+330323@code.launchpad.net

This proposal supersedes a proposal from 2017-07-27.

This proposal has been superseded by a proposal from 2017-09-08.

Description of the change

This is probably not perfect, but it's basically (right now) just a wrapper around other commands, after the refactoring to call into them directly without the shell.

Bikesheddable.

Esp.: do we want review to create local repos? Or only with a flag? This was suggested in a recent bug where behaviorally only clone is allowed to create repositories.

To post a comment you must log in.
Revision history for this message
Robie Basak (racb) : Posted in a previous version of this proposal
review: Needs Fixing
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:e0831bb2864a48129d9143f1b0e32831634101e8
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/26/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Tests
    FAILED: Build

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/26/rebuild

review: Needs Fixing (continuous-integration)
~nacc/git-ubuntu:gu-review updated
7af2b70... by Nish Aravamudan

git-ubuntu: change default fetch protocol to https

LP: #1715688

6a15a64... by Nish Aravamudan

git-ubuntu: fix dictionary style

19aa857... by Nish Aravamudan

use environment python3

Rather than a hardcoded path.

ad8c802... by Nish Aravamudan

wip

49b3a96... by Nish Aravamudan

snap: export QUILT_DIR in git-ubuntu wrapper

Reported by powers and rbasak, quilt is looking in a hardcoded
/usr/share/quilt for subcommands. This is in our snap, not in the
root filesystem.

3b2b418... by Nish Aravamudan

lint: drop unused pullfile variable

e269f5a... by Nish Aravamudan

importppa: fix undefined variable UbuntuSourceInformation

87abbad... by Nish Aravamudan

importppa: fix call to add_remote

57c917e... by Nish Aravamudan

importppa: save pkgname to object

This is used by the importer code

bfba350... by Nish Aravamudan

importppa: fix parameters to parse_parentfile

Unmerged commits

ad8c802... by Nish Aravamudan

wip

b6728a8... by Nish Aravamudan

git ubuntu review: add new subcommand

Given a MP URL, this attempts to provide a local directory to use for
reviewing the MP (and allows one to auto-approve with lint results).

LP: #1702960

284e2b1... by Nish Aravamudan

clone: have main return the resulting directory path

6a15a64... by Nish Aravamudan

git-ubuntu: fix dictionary style

48775be... by Nish Aravamudan

lint: drop unused pullfile variable

6b2fe22... by Nish Aravamudan

git ubuntu: refactor main to call module parse_args

This is the final step in the refactor -- rather than using per-module
classes, just import each module in __main__ in order to call its
parse_args and func (which in the current implementation is that
module's cli_main).

Before merging with master, I think we will squash all of these
together.

At this point, all errors from pylint3 are from pygit2 imports.

LP: #1707321

2d4263a... by Nish Aravamudan

submit: refactor class

Leave only parse_args and cli_main in the class. Make cli_main nothing
more than a wrapper to the module's main function.

LP: #1707321

ce50ef5... by Nish Aravamudan

submit: use lint.derive_target_branch

submit can be used for distributed development among team members by
proposing MPs against each other's repositories, via the --target-user
flag. Rather than having submit-specific code for deriving the target
branch, use the existing code from lint, after extending it to take a
namespace argument.

b6762e7... by Nish Aravamudan

tag: refactor class

Leave only parse_args and cli_main in the class. Make cli_main nothing
more than a wrapper to the module's main function.

LP: #1707321

2bb9d17... by Nish Aravamudan

remote: refactor class

Leave only parse_args and cli_main in the class. Make cli_main nothing
more than a wrapper to the module's main function.

LP: #1707321

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/gitubuntu/__main__.py b/gitubuntu/__main__.py
2index 49373a7..77087ff 100644
3--- a/gitubuntu/__main__.py
4+++ b/gitubuntu/__main__.py
5@@ -1,5 +1,19 @@
6 #!/usr/bin/env python3
7
8+from collections import namedtuple
9+TopLevelDefaults = namedtuple(
10+ 'TopLevelDefaults',
11+ [
12+ 'verbose',
13+ 'retries',
14+ 'retry_backoffs',
15+ 'proto',
16+ 'parentfile',
17+ 'pullfile',
18+ ],
19+)
20+top_level_defaults = object()
21+
22 def main():
23 try:
24 import argparse
25@@ -21,6 +35,22 @@ def main():
26 logging.error('Is %s installed?', pkg)
27 sys.exit(1)
28
29+ global top_level_defaults
30+ top_level_defaults = TopLevelDefaults(
31+ verbose=False,
32+ retries=3,
33+ retry_backoffs = [2 ** i for i in range(3)],
34+ proto='https',
35+ parentfile=pkg_resources.resource_filename(
36+ 'gitubuntu',
37+ 'parent_overrides.txt',
38+ ),
39+ pullfile=pkg_resources.resource_filename(
40+ 'gitubuntu',
41+ 'pull_overrides.txt',
42+ ),
43+ )
44+
45 logging.getLogger('keyring').setLevel(logging.WARNING)
46
47 known_subcommands = {
48@@ -36,6 +66,7 @@ def main():
49 'remote': 'gitubuntu.remote',
50 'submit': 'gitubuntu.submit',
51 'lint': 'gitubuntu.lint',
52+ 'review': 'gitubuntu.review',
53 }
54
55 known_network_subcommands = {
56@@ -48,6 +79,7 @@ def main():
57 'queue',
58 'remote',
59 'submit',
60+ 'review',
61 }
62
63 parser = argparse.ArgumentParser(
64@@ -68,7 +100,8 @@ def main():
65 base_subparser.add_argument(
66 '-v', '--verbose',
67 action='store_true',
68- help='Increase verbosity'
69+ help='Increase verbosity',
70+ default=top_level_defaults.verbose,
71 )
72 network_base_subparser = argparse.ArgumentParser(add_help=False)
73 network_base_subparser.add_argument(
74@@ -76,7 +109,7 @@ def main():
75 type=int,
76 help='Number of times to attempt to retry '
77 'downloading from Launchpad.',
78- default=3,
79+ default=top_level_defaults.retries,
80 )
81 network_base_subparser.add_argument(
82 '--retry-backoffs',
83@@ -92,7 +125,7 @@ def main():
84 metavar='[%s]' % '|'.join(known_protos),
85 choices=known_protos,
86 help='Specify protocol to use for fetch. Default: %(default)s',
87- default='https',
88+ default=top_level_defaults.proto,
89 )
90
91 width, _ = shutil.get_terminal_size()
92@@ -133,10 +166,7 @@ def main():
93 '-P', '--parentfile',
94 type=str,
95 help=argparse.SUPPRESS,
96- default=pkg_resources.resource_filename(
97- 'gitubuntu',
98- 'parent_overrides.txt',
99- ),
100+ default=top_level_defaults.parentfile,
101 )
102 help_text = '\n'.join(
103 textwrap.wrap(
104@@ -159,10 +189,7 @@ def main():
105 '-L', '--pullfile',
106 type=str,
107 help=argparse.SUPPRESS,
108- default=pkg_resources.resource_filename(
109- 'gitubuntu',
110- 'pull_overrides.txt',
111- ),
112+ default=top_level_defaults.pullfile,
113 )
114
115 argcomplete.autocomplete(parser)
116diff --git a/gitubuntu/clone.py b/gitubuntu/clone.py
117index e2dcedf..91f63b7 100644
118--- a/gitubuntu/clone.py
119+++ b/gitubuntu/clone.py
120@@ -5,6 +5,7 @@ import re
121 import shutil
122 from subprocess import CalledProcessError
123 import sys
124+from gitubuntu.__main__ import top_level_defaults
125 from gitubuntu.git_repository import (
126 GitUbuntuRepository,
127 GitUbuntuRepositoryFetchError,
128@@ -30,11 +31,35 @@ def copy_hooks(src, dst):
129 dst,
130 )
131
132-def main(directory, lp_user, proto, package):
133+def main(package, directory=None, lp_user=None, proto=None):
134+ """Entry point to clone subcommand
135+
136+ @package: Name of source package
137+ @directory: directory to clone the repository into
138+ @lp_user: user to authenticate to Launchpad as
139+ @proto: protocol to use (one of 'http', 'https', 'git')
140+
141+ If directory is None, a relative directory with the same name as
142+ package will be used.
143+
144+ If lp_user is None, value of `git config gitubuntu.lpuser` will be
145+ used.
146+
147+ If proto is None, the default value from gitubuntu.__main__ will be
148+ used (top_level_defaults.proto).
149+ """
150+ directory = (
151+ os.path.abspath(directory)
152+ if directory
153+ else os.path.join(os.path.abspath(os.getcwd()), package)
154+ )
155 if os.path.isdir(directory):
156 logging.error('directory %s exists' % directory)
157 sys.exit(1)
158
159+ if proto is None:
160+ proto = top_level_defaults.proto
161+
162 local_repo = GitUbuntuRepository(
163 local_dir=directory,
164 lp_user=lp_user,
165@@ -87,6 +112,8 @@ def main(directory, lp_user, proto, package):
166 'flags to git commands (e.g., git status --ignored).'
167 )
168
169+ return local_repo
170+
171 def parse_args(subparsers=None, base_subparsers=None):
172 kwargs = dict(
173 description='Clone a source package git repository to a directory',
174@@ -128,13 +155,13 @@ def cli_main(args):
175 lp_user = args.lp_user
176 except AttributeError:
177 lp_user = None
178- directory = (
179- os.path.abspath(args.directory)
180- if args.directory
181- else os.path.join(os.path.abspath(os.getcwd()), args.package)
182- )
183
184- main(directory, lp_user, args.proto, args.package)
185+ main(
186+ package=args.package,
187+ directory=args.directory,
188+ lp_user=lp_user,
189+ proto=args.proto,
190+ )
191
192
193 # vi: ts=4 expandtab
194diff --git a/gitubuntu/lint.py b/gitubuntu/lint.py
195index 109eb13..fc09aec 100644
196--- a/gitubuntu/lint.py
197+++ b/gitubuntu/lint.py
198@@ -202,8 +202,9 @@ def parse_args(subparsers=None, base_subparsers=None):
199 return "lint - %s" % kwargs["description"]
200
201 def cli_main(args):
202- main(
203- args.directory,
204+ repo = GitUbuntuRepository(args.directory)
205+ do_lint(
206+ repo,
207 args.commitish,
208 args.lint_namespace,
209 args.target_branch,
210@@ -702,15 +703,28 @@ def derive_target_branch(repo, commitish_string, namespace='pkg'):
211 # git checkout <coworker>/<branch>
212 # git ubuntu lint
213 # assume repository already exists
214-def main(
215- directory,
216+def do_lint(
217+ repo,
218 commitish,
219- lint_namespace,
220- target_branch,
221- verbose,
222+ target_branch=None,
223+ lint_namespace=None,
224+ verbose=False,
225 ):
226- repo = GitUbuntuRepository(directory)
227+ """Entry point for lint subcommand
228+
229+ @repo: GitUbuntuRepository containing @commitish to lint
230+ @commitish: commitish to lint
231+ @target_branch: branch to target commitish's changes to. Typically a
232+ remote branch in pkg/.
233+ @lint_namespace: string namespace (without trailing /) in which to
234+ find tags for merges.
235+ @verbose: if True, also emit success statements
236
237+ If target_branch is None, it will be derived from the nearest remote
238+ branch.
239+
240+ If lint_namespace is None, it will be derived from @commitish.
241+ """
242 global _verbose
243 _verbose = verbose
244
245diff --git a/gitubuntu/remote.py b/gitubuntu/remote.py
246index 7318b44..f31dc8a 100644
247--- a/gitubuntu/remote.py
248+++ b/gitubuntu/remote.py
249@@ -61,29 +61,39 @@ def cli_main(args):
250 directory = os.path.abspath(args.directory)
251 else:
252 directory = os.getcwd()
253- if args.remote_name is not None:
254- remote_name = args.remote_name
255- else:
256- remote_name = args.user
257
258 main(
259 args.subsubcommand,
260- args.package,
261 args.user,
262+ args.package,
263 args.url,
264 args.lp_user,
265 directory,
266- remote_name,
267+ args.remote_name,
268 args.no_fetch,
269 args.proto,
270 )
271
272-def do_add(repo, package, user, url, remote_name, no_fetch):
273+def do_add(repo, package, user, url=None, remote_name=None, no_fetch=False):
274+ """add a remote to a repository
275+
276+ @repo: GitUbuntuRepository to modify
277+ @package: source package name (XXX: is this derive-able from @repo?)
278+ @user: remote user's repository to add as remote
279+ @url: URL to add as a remote
280+ @remote_name: specify the name of the new remote
281+ @no_fetch: if True, do not fetch the remote after adding it
282+
283+ If url is None, the remote URL will be derived from @package and
284+ @user.
285+
286+ If @remote_name is not specified, it will be set to @user.
287+ """
288 if url:
289 repo.git_run(['remote', 'add', remote_name, url])
290 else:
291 repo.add_remote(
292- package,
293+ pkgname=package,
294 repo_owner=user,
295 remote_name=remote_name,
296 )
297@@ -102,15 +112,40 @@ def do_add(repo, package, user, url, remote_name, no_fetch):
298
299 def main(
300 subcommand,
301- package,
302 user,
303- url,
304- lp_user,
305- directory,
306- remote_name,
307- no_fetch,
308- proto,
309+ package=None,
310+ url=None,
311+ lp_user=None,
312+ directory=None,
313+ remote_name=None,
314+ no_fetch=False,
315+ proto=None,
316 ):
317+ """Entry point to remote subcommand
318+ @subcommand: what action to take. Currently only 'add' supported.
319+ @user: which Launchpad user's repository to add
320+ @package: name of source package
321+ @url: the URL of the remote to add
322+ @lp_user: user to authenticate to Launchpad as
323+ @proto: protocol to use (one of 'http', 'https', 'git')
324+
325+ If package is None, it will be derived from HEAD's debian/changelog.
326+
327+ If url is None, it will be derived from @user and @package.
328+
329+ If lp_user is None, value of `git config gitubuntu.lpuser` will be
330+ used.
331+
332+ If directory is None, the current directory is used.
333+
334+ If remote_name is None, the remote will be named @user.
335+
336+ If proto is None, the default value from gitubuntu.__main__ will be
337+ used (top_level_defaults.proto).
338+ """
339+ if directory is None:
340+ directory = os.path.abspath(os.getcwd())
341+
342 repo = GitUbuntuRepository(
343 local_dir=directory,
344 lp_user=lp_user,
345diff --git a/gitubuntu/review.py b/gitubuntu/review.py
346new file mode 100644
347index 0000000..3cf9f22
348--- /dev/null
349+++ b/gitubuntu/review.py
350@@ -0,0 +1,103 @@
351+import argparse
352+from contextlib import redirect_stdout
353+import io
354+import os
355+import shutil
356+import tempfile
357+import urllib.parse
358+import gitubuntu.clone
359+import gitubuntu.lint
360+from gitubuntu.source_information import launchpad_login_auth
361+
362+
363+def parse_args(subparsers=None, base_subparsers=None):
364+ kwargs = dict(
365+ description="Given a Launchpad MP URL, perform a review",
366+ formatter_class=argparse.RawTextHelpFormatter,
367+ epilog="An exit code of 0 indicates all checks passed",
368+ )
369+ if base_subparsers:
370+ kwargs["parents"] = base_subparsers
371+ if subparsers:
372+ parser = subparsers.add_parser("review", **kwargs)
373+ parser.set_defaults(func=cli_main)
374+ else:
375+ parser = argparse.ArgumentParser(**kwargs)
376+
377+ parser.add_argument('subsubcommand',
378+ help='start - Begin a Launchpad MP review',
379+ metavar='start',
380+ choices=['start'],
381+ )
382+ parser.add_argument("url", type=str,
383+ help="Full URL of Launchpad Merge Proposal to review"
384+ )
385+ parser.add_argument('--add-comment', action='store_true',
386+ help="Add a comment to the MP with the lint results. "
387+ "This may also change the review state",
388+ )
389+
390+ if not subparsers:
391+ return parser.parse_args()
392+ return "review - %s" % kwargs["description"]
393+
394+
395+def cli_main(args):
396+ main(
397+ args.subsubcommand,
398+ args.url,
399+ args.proto,
400+ args.add_comment,
401+ )
402+
403+def main(subcommand, url, proto, add_comment):
404+ url = urllib.parse.urlparse(url).path
405+ lp = launchpad_login_auth()
406+ mp = lp.load(url)
407+ target_url = mp.target_git_repository.git_https_url
408+ target_branch = mp.target_git_path[len('refs/heads/'):]
409+ source_url = mp.source_git_repository.git_https_url
410+ source_branch = mp.source_git_path[len('refs/heads/'):]
411+ srcpkg = target_url.split('/')[-1]
412+ if subcommand == 'start':
413+ # inherently racy
414+ repo = gitubuntu.clone.main(package=srcpkg)
415+ print('Using git repository at %s' % repo.local_dir)
416+ os.chdir(repo.local_dir)
417+ idx = source_url.index('~')
418+ colleague = source_url[idx+1:source_url.index('/', idx)]
419+ gitubuntu.remote.do_add(
420+ repo=repo,
421+ package=srcpkg,
422+ user=colleague,
423+ )
424+
425+ print(
426+ "Linting merge of %s/%s into pkg/%s" % (
427+ colleague,
428+ source_branch,
429+ target_branch
430+ )
431+ )
432+ try:
433+ f = io.StringIO()
434+ with redirect_stdout(f):
435+ gitubuntu.lint.do_lint(
436+ repo=repo,
437+ commitish='%s/%s' % (colleague, source_branch),
438+ target_branch='pkg/%s' % target_branch,
439+ verbose=True,
440+ )
441+ except SystemExit as e:
442+ if e.code == 0:
443+ vote='Approve'
444+ else:
445+ vote='Needs Fixing'
446+ lint_out = f.getvalue()
447+ if add_comment:
448+ mp.createComment(content=lint_out, vote=vote,
449+ subject='Automated lint result'
450+ )
451+ else:
452+ print('git ubuntu lint result:')
453+ print(lint_out, end='')

Subscribers

People subscribed via source and target branches