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: 873 lines (+230/-348)
9 files modified
gitubuntu/__main__.py (+2/-0)
gitubuntu/clone.py (+0/-20)
gitubuntu/git_repository.py (+62/-36)
gitubuntu/importer.py (+0/-87)
gitubuntu/merge.py (+0/-149)
gitubuntu/remote.py (+0/-18)
gitubuntu/review.py (+140/-0)
gitubuntu/submit.py (+0/-23)
gitubuntu/versioning.py (+26/-15)
Reviewer Review Type Date Requested Status
Andreas Hasenack Needs Fixing
Server Team CI bot continuous-integration Approve
Robie Basak Pending
Review via email: mp+330463@code.launchpad.net

This proposal supersedes a proposal from 2017-09-08.

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

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.

Even less mergeable now, but shows how the interplay between modules can work after further refactoring.

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 : Posted in a previous version of this proposal

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)
Revision history for this message
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:ad8c802eddc58f40d22c304e5ad3468a2ebf1ce5
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/39/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Style Check

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:ad8c802eddc58f40d22c304e5ad3468a2ebf1ce5
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/43/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Style Check
    SUCCESS: Unit Tests
    FAILED: Integration Tests

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:d672a163d7eb95e7385049daa1da11bdd777205a
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/56/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Style Check
    SUCCESS: Unit Tests
    SUCCESS: Integration Tests
    IN_PROGRESS: Declarative: Post Actions

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

review: Approve (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:1b6612100ceab79c8fefded7075d3beb263f6bb1
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/60/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Style Check
    SUCCESS: Unit Tests
    SUCCESS: Integration Tests
    IN_PROGRESS: Declarative: Post Actions

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

review: Approve (continuous-integration)
Revision history for this message
Andreas Hasenack (ahasenack) wrote :

A few comments inline, just one needs-fixing really. The other one about making it work with an existing local repository is future work I think, for another branch.

There is no outstanding merge MP, so I tried running this review command against an open MP that is not a debian merge (cpaelzer's libvirt one) and also against an already-merged MP of mine (samba take 4), and got crashes in both cases. I understand this is somewhat work-in-progress, being the first new command to take advantage of the latest refactoring that is about to land. But I wonder: what do you think about having a way to hide these wip commands from --help?

review: Needs Fixing
~nacc/git-ubuntu:gu-review updated
68ddcab... by Nish Aravamudan

git_repository: use top-level-default for fetch_proto if not specified

This should not result in any functional change, but makes eventual
git-ubuntu review easier to implement.

b32d24b... by Nish Aravamudan

git_repository::fetch*: set GIT_TERMINAL_PROMPT=0 in env

When using http/https, LP will prompt for auth if a repository
is not found, because LP cannot distinguish between a private
repository (permission issue) and a non-existent repository until
the user is authenticated. This causes git itself to prompt
for authentication on the command-line, which we do not want. Since
we are only using http/https for fetch, disable the Git terminal
prompt via an environment variable. This does make clone and
review noisier, but that can be resolved in a future commit.

LP: #1716745

3f2aa06... by Nish Aravamudan

versioning: add support for non-series information in the version string

libvirt uploads (SRUs) have versions like 1.2.2-0ubuntu13.1.23 which break
our parsing, which assumes anything between ubuntu_maj and ubuntu_min
is either a series or invalid. Extend our parsing to allow for an "other"
field, which is set when the substring between maj and min cannot be
parsed as a version.

LP: #1716737

Unmerged commits

78e9c71... 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

3f2aa06... by Nish Aravamudan

versioning: add support for non-series information in the version string

libvirt uploads (SRUs) have versions like 1.2.2-0ubuntu13.1.23 which break
our parsing, which assumes anything between ubuntu_maj and ubuntu_min
is either a series or invalid. Extend our parsing to allow for an "other"
field, which is set when the substring between maj and min cannot be
parsed as a version.

LP: #1716737

b32d24b... by Nish Aravamudan

git_repository::fetch*: set GIT_TERMINAL_PROMPT=0 in env

When using http/https, LP will prompt for auth if a repository
is not found, because LP cannot distinguish between a private
repository (permission issue) and a non-existent repository until
the user is authenticated. This causes git itself to prompt
for authentication on the command-line, which we do not want. Since
we are only using http/https for fetch, disable the Git terminal
prompt via an environment variable. This does make clone and
review noisier, but that can be resolved in a future commit.

LP: #1716745

68ddcab... by Nish Aravamudan

git_repository: use top-level-default for fetch_proto if not specified

This should not result in any functional change, but makes eventual
git-ubuntu review easier to implement.

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 edeee43..27fbe14 100644
3--- a/gitubuntu/__main__.py
4+++ b/gitubuntu/__main__.py
5@@ -66,6 +66,7 @@ def main():
6 'remote': 'gitubuntu.remote',
7 'submit': 'gitubuntu.submit',
8 'lint': 'gitubuntu.lint',
9+ 'review': 'gitubuntu.review',
10 }
11
12 known_network_subcommands = {
13@@ -78,6 +79,7 @@ def main():
14 'queue',
15 'remote',
16 'submit',
17+ 'review',
18 }
19
20 parser = argparse.ArgumentParser(
21diff --git a/gitubuntu/clone.py b/gitubuntu/clone.py
22index 832945e..c4d8dbd 100644
23--- a/gitubuntu/clone.py
24+++ b/gitubuntu/clone.py
25@@ -31,26 +31,18 @@ def copy_hooks(src, dst):
26 dst,
27 )
28
29-<<<<<<< gitubuntu/clone.py
30 def main(
31 package,
32 directory=None,
33 lp_user=None,
34 proto=top_level_defaults.proto,
35 ):
36-=======
37-def main(package, directory=None, lp_user=None, proto=None):
38->>>>>>> gitubuntu/clone.py
39 """Entry point to clone subcommand
40
41 @package: Name of source package
42 @directory: directory to clone the repository into
43 @lp_user: user to authenticate to Launchpad as
44-<<<<<<< gitubuntu/clone.py
45 @proto: string protocol to use (one of 'http', 'https', 'git')
46-=======
47- @proto: protocol to use (one of 'http', 'https', 'git')
48->>>>>>> gitubuntu/clone.py
49
50 If directory is None, a relative directory with the same name as
51 package will be used.
52@@ -58,12 +50,6 @@ def main(package, directory=None, lp_user=None, proto=None):
53 If lp_user is None, value of `git config gitubuntu.lpuser` will be
54 used.
55
56-<<<<<<< gitubuntu/clone.py
57-=======
58- If proto is None, the default value from gitubuntu.__main__ will be
59- used (top_level_defaults.proto).
60-
61->>>>>>> gitubuntu/clone.py
62 Returns the resulting GitUbuntuRepository object
63 """
64 directory = (
65@@ -75,12 +61,6 @@ def main(package, directory=None, lp_user=None, proto=None):
66 logging.error('directory %s exists' % directory)
67 sys.exit(1)
68
69-<<<<<<< gitubuntu/clone.py
70-=======
71- if proto is None:
72- proto = top_level_defaults.proto
73-
74->>>>>>> gitubuntu/clone.py
75 local_repo = GitUbuntuRepository(
76 local_dir=directory,
77 lp_user=lp_user,
78diff --git a/gitubuntu/git_repository.py b/gitubuntu/git_repository.py
79index 9fdf7bb..e97a0ea 100644
80--- a/gitubuntu/git_repository.py
81+++ b/gitubuntu/git_repository.py
82@@ -16,6 +16,7 @@ from subprocess import CalledProcessError
83 import sys
84 import tempfile
85 import time
86+from gitubuntu.__main__ import top_level_defaults
87 from gitubuntu.dsc import component_tarball_matches
88 from gitubuntu.run import run, runq, decode_binary
89 try:
90@@ -426,9 +427,14 @@ class GitUbuntuRepository:
91
92 To access the underlying pygit2.Repository object, use the raw_repo
93 property.
94+
95 """
96
97 def __init__(self, local_dir, lp_user=None, fetch_proto=None):
98+ """
99+ If fetch_proto is None, the default value from
100+ gitubuntu.__main__ will be used (top_level_defaults.proto).
101+ """
102 if local_dir is None:
103 self._local_dir = tempfile.mkdtemp()
104 else:
105@@ -465,6 +471,9 @@ class GitUbuntuRepository:
106 logging.error("Unable to determine Launchpad user")
107 sys.exit(1)
108
109+ if fetch_proto is None:
110+ fetch_proto = top_level_defaults.proto
111+
112 self._fetch_proto = fetch_proto
113
114 def create_orphan_branch(self, branch_name, msg):
115@@ -764,51 +773,68 @@ class GitUbuntuRepository:
116 # self.git_run(['config', 'url.%s.insteadof' % self.lp_user, 'lpme'])
117
118 def fetch_remote(self, remote_name, verbose=False):
119+ # Does not seem to be working with https
120+ # https://github.com/libgit2/pygit2/issues/573
121+ # https://github.com/libgit2/libgit2/issues/3786
122+ # self.raw_repo.remotes[remote_name].fetch()
123+ kwargs = {}
124+ kwargs['verbose_on_failure'] = True
125+ if verbose:
126+ # If we are redirecting stdout/stderr to the console, we
127+ # do not need to have run() also emit it
128+ kwargs['verbose_on_failure'] = False
129+ kwargs['stdout'] = None
130+ kwargs['stderr'] = None
131 try:
132- # Does not seem to be working with https
133- # https://github.com/libgit2/pygit2/issues/573
134- # https://github.com/libgit2/libgit2/issues/3786
135- # self.raw_repo.remotes[remote_name].fetch()
136- logging.debug('Fetching remote %s' % remote_name)
137- kwargs = {}
138- kwargs['verbose_on_failure'] = True
139- if verbose:
140- # If we are redirecting stdout/stderr to the console, we
141- # do not need to have run() also emit it
142- kwargs['verbose_on_failure'] = False
143- kwargs['stdout'] = None
144- kwargs['stderr'] = None
145- self.git_run(['fetch', remote_name], **kwargs)
146+ logging.debug("Fetching remote %s", remote_name)
147+ cp = self.git_run(
148+ args=['fetch', remote_name],
149+ env={'GIT_TERMINAL_PROMPT': '0',},
150+ **kwargs
151+ )
152 except CalledProcessError:
153 raise GitUbuntuRepositoryFetchError(
154- 'Unable to fetch remote %s' % remote_name
155+ "Unable to fetch remote %s" % remote_name
156 )
157
158 def fetch_base_remotes(self, verbose=False):
159 self.fetch_remote(remote_name='pkg', verbose=verbose)
160
161 def fetch_remote_refspecs(self, remote_name, refspecs, verbose=False):
162- try:
163- # Does not seem to be working with https
164- # https://github.com/libgit2/pygit2/issues/573
165- # https://github.com/libgit2/libgit2/issues/3786
166- # self.raw_repo.remotes[remote_name].fetch()
167- for refspec in refspecs:
168- logging.debug('Fetching refspec %s from remote %s' %
169- (refspec, remote_name))
170- kwargs = {}
171- kwargs['verbose_on_failure'] = True
172- if verbose:
173- # If we are redirecting stdout/stderr to the console, we
174- # do not need to have run() also emit it
175- kwargs['verbose_on_failure'] = False
176- kwargs['stdout'] = None
177- kwargs['stderr'] = None
178- self.git_run(['fetch', remote_name, refspec], **kwargs)
179- except CalledProcessError:
180- raise GitUbuntuRepositoryFetchError(
181- 'Unable to fetch %s from remote %s' % (refspecs, remote_name)
182- )
183+ # Does not seem to be working with https
184+ # https://github.com/libgit2/pygit2/issues/573
185+ # https://github.com/libgit2/libgit2/issues/3786
186+ # self.raw_repo.remotes[remote_name].fetch()
187+ for refspec in refspecs:
188+ kwargs = {}
189+ kwargs['verbose_on_failure'] = True
190+ if verbose:
191+ # If we are redirecting stdout/stderr to the console, we
192+ # do not need to have run() also emit it
193+ kwargs['verbose_on_failure'] = False
194+ kwargs['stdout'] = None
195+ kwargs['stderr'] = None
196+ kwargs['env'] = {
197+ 'GIT_TERMINAL_PROMPT': '0',
198+ }
199+ try:
200+ logging.debug(
201+ "Fetching refspec %s from remote %s",
202+ refspec,
203+ remote_name,
204+ )
205+ self.git_run(
206+ args=['fetch', remote_name, refspec],
207+ env={'GIT_TERMINAL_PROMPT': '0',},
208+ **kwargs,
209+ )
210+ except CalledProcessError:
211+ raise GitUbuntuRepositoryFetchError(
212+ "Unable to fetch %s from remote %s" % (
213+ refspecs,
214+ remote_name,
215+ )
216+ )
217
218 def fetch_lpuser_remote(self, verbose=False):
219 if not self._fetch_proto:
220diff --git a/gitubuntu/importer.py b/gitubuntu/importer.py
221index 201f31e..6311047 100644
222--- a/gitubuntu/importer.py
223+++ b/gitubuntu/importer.py
224@@ -115,7 +115,6 @@ def dsc_to_tree_hash(repo, dsc_path):
225
226 def cleanup(no_clean, local_dir):
227 '''Remove a local directory, conditionally
228-<<<<<<< gitubuntu/importer.py
229
230 no_clean: if True, do not remove @local_dir and print a message instead
231 local_dir: directory to remove
232@@ -188,31 +187,6 @@ def main(
233
234 logging.info('Ubuntu Server Team importer v%s' % VERSION)
235
236-=======
237-
238- no_clean: if True, do not remove @local_dir and print a message instead
239- local_dir: directory to remove
240- '''
241- if not no_clean:
242- shutil.rmtree(local_dir)
243- else:
244- logging.info('Leaving %s as directed' % local_dir)
245-
246-
247-# XXX: need a namedtuple to hold common arguments
248-def main(pkgname, owner, no_clean, directory, user, proto, no_fetch,
249- no_push, dl_cache, fixup_devel, active_series_only, skip_orig,
250- pullfile, parentfile, retries, retry_backoffs, skip_applied,
251- reimport,
252-):
253- if owner == 'usd-import-team':
254- namespace = 'importer'
255- else:
256- namespace = owner
257-
258- logging.info('Ubuntu Server Team importer v%s' % VERSION)
259-
260->>>>>>> gitubuntu/importer.py
261 repo = GitUbuntuRepository(directory, user, proto)
262 if not directory:
263 logging.info('Using git repository at %s', repo.local_dir)
264@@ -1210,45 +1184,6 @@ def import_unapplied_spi(repo, spi, namespace, skip_orig, ubuntu_sinfo):
265 )
266 except AttributeError:
267 pass
268-<<<<<<< gitubuntu/importer.py
269-
270- if version_compare(str(spi.version), unapplied_tip_version) <= 0:
271- logging.warn('Version to import (%s) is not after %s tip (%s)',
272- spi.version, pretty_head_name, unapplied_tip_version
273- )
274-
275- # Walk changelog backwards until we find an imported version
276- for version in import_tree_versions:
277- unapplied_changelog_parent_tag = repo.get_import_tag(version, namespace)
278- if unapplied_changelog_parent_tag is not None:
279- # sanity check that version from d/changelog of the
280- # tagged parent matches ours
281- parent_changelog_version, _ = \
282- repo.get_changelog_versions_from_treeish(
283- str(unapplied_changelog_parent_tag.peel(pygit2.Tree).id),
284- )
285- # if the parent was imported via a parent override
286- # itself, assume it is valid without checking its
287- # tree's debian/changelog, as it wasn't used
288- if (version not in _PARENT_OVERRIDES and
289- parent_changelog_version != version
290- ):
291- logging.error('Found a tag corresponding to '
292- 'parent version %s, but d/changelog '
293- 'version (%s) differs. Will '
294- 'orphan commit.' % (
295- version,
296- parent_changelog_version
297- )
298- )
299- else:
300- unapplied_changelog_parent_commit = str(unapplied_changelog_parent_tag.peel().id)
301- logging.debug('Changelog parent (tag) is %s',
302- repo.tag_to_pretty_name(unapplied_changelog_parent_tag)
303- )
304- break
305-
306-=======
307
308 if version_compare(str(spi.version), unapplied_tip_version) <= 0:
309 logging.warn('Version to import (%s) is not after %s tip (%s)',
310@@ -1286,7 +1221,6 @@ def import_unapplied_spi(repo, spi, namespace, skip_orig, ubuntu_sinfo):
311 )
312 break
313
314->>>>>>> gitubuntu/importer.py
315 # If the two parents are tree-identical, then favor publication
316 # history. This deals with copy-forwards between series and with
317 # syncs.
318@@ -1668,7 +1602,6 @@ def cli_main(args):
319 dl_cache = None
320
321 main(
322-<<<<<<< gitubuntu/importer.py
323 pkgname=args.package,
324 owner=args.lp_owner,
325 no_clean=no_clean,
326@@ -1687,24 +1620,4 @@ def cli_main(args):
327 parentfile=args.parentfile,
328 retries=args.retries,
329 retry_backoffs=args.retry_backoffs,
330-=======
331- args.package,
332- args.lp_owner,
333- no_clean,
334- directory,
335- user,
336- args.proto,
337- args.no_fetch,
338- no_push,
339- dl_cache,
340- args.fixup_devel,
341- args.active_series_only,
342- args.skip_orig,
343- args.pullfile,
344- args.parentfile,
345- args.retries,
346- args.retry_backoffs,
347- args.skip_applied,
348- args.reimport,
349->>>>>>> gitubuntu/importer.py
350 )
351diff --git a/gitubuntu/merge.py b/gitubuntu/merge.py
352index b6098b7..cc16cc0 100644
353--- a/gitubuntu/merge.py
354+++ b/gitubuntu/merge.py
355@@ -98,7 +98,6 @@ def parse_args(subparsers=None, base_subparsers=None):
356
357 def cli_main(args):
358 main(
359-<<<<<<< gitubuntu/merge.py
360 directory=args.directory,
361 commitish=args.commitish,
362 onto=args.onto,
363@@ -107,17 +106,6 @@ def cli_main(args):
364 bug=args.bug,
365 release=args.release,
366 subcommand=args.subsubcommand,
367-=======
368- args.directory,
369- args.commitish,
370- args.onto,
371- args.force,
372- args.tag_only,
373- args.pullfile,
374- args.bug,
375- args.release,
376- args.subsubcommand,
377->>>>>>> gitubuntu/merge.py
378 )
379
380
381@@ -327,7 +315,6 @@ def do_finish(repo, tag_prefix, commitish, merge_base_id, onto, release, bug, fo
382 ancestor_check = True
383 except subprocess.CalledProcessError as e:
384 pass
385-<<<<<<< gitubuntu/merge.py
386
387 if not ancestor_check:
388 if len(ancestors_checked) > 1:
389@@ -474,142 +461,6 @@ def main(
390 merge_base_id,
391 )
392
393-=======
394-
395- if not ancestor_check:
396- if len(ancestors_checked) > 1:
397- msg = ' and '.join(ancestors_checked) + ' are not ancestors'
398- else:
399- msg = '%s is not an ancestor' % ancestors_checked.pop()
400- if force:
401- logging.error("%s of the HEAD commit, but --force passed.", msg)
402- else:
403- logging.error(
404- "%s of the HEAD commit. Did you run `git-ubuntu merge "
405- "start` first? (Pass -f to force the merge).",
406- msg
407- )
408- sys.exit(1)
409-
410- # 1) git merge-changelogs old/ubuntu old/debian new/debian
411- do_merge_changelogs(repo, commitish, merge_base_id, onto)
412- # 2) git reconstruct-changelog <onto>
413- do_reconstruct_changelog(repo, onto, release, bug)
414- # TODO elide from each entry any lines outside of changelog-markers
415- # (add flag to specify it?)
416- # 3) update-maintainer
417- do_update_maintainer(repo)
418-
419-
420-def main(
421- directory,
422- commitish,
423- onto,
424- force,
425- tag_only,
426- pullfile,
427- bug,
428- release,
429- subcommand,
430-):
431- repo = GitUbuntuRepository(directory)
432- tag_prefix = ''
433- if bug:
434- tag_prefix = 'lp%s/' % bug
435-
436- try:
437- commitish_obj = repo.get_commitish(commitish)
438- except KeyError:
439- logging.error(
440- "%s is not a defined object in this git repository.",
441- commitish
442- )
443- sys.exit(1)
444-
445- commitish_version, _ = repo.get_changelog_versions_from_treeish(
446- str(commitish_obj.id),
447- )
448-
449- try:
450- onto_obj = repo.get_commitish(onto)
451- onto_version, _ = repo.get_changelog_versions_from_treeish(onto)
452- if version_compare(commitish_version, onto_version) >= 0:
453- if force:
454- logging.info(
455- "%s version (%s) is after %s version (%s), "
456- "but --force passed.",
457- commitish,
458- commitish_version,
459- onto,
460- onto_version,
461- )
462- else:
463- logging.error(
464- "%s version (%s) is after %s version (%s). "
465- "Are you sure you want to merge? "
466- "(Pass -f to force the merge).",
467- commitish,
468- commitish_version,
469- onto,
470- onto_version,
471- )
472- sys.exit(1)
473- except KeyError:
474- logging.info("%s is not a defined object in this git repository.", onto)
475- logging.info(
476- "Creating a local branch named %s tracking pkg/%s.",
477- onto,
478- onto,
479- )
480- try:
481- branch = repo.create_tracking_branch(
482- onto,
483- 'pkg/%s' % onto,
484- force=force,
485- )
486- except:
487- logging.error(
488- "Failed to create local branch (%s). "
489- "Does it already exist (pass -f)?",
490- onto,
491- )
492- sys.exit(1)
493- onto_obj = repo.get_commitish(onto)
494-
495- cp = run(['git', 'status', '--porcelain'])
496- if len(cp.stdout) > 0:
497- logging.error('Working tree must be clean to continue:')
498- logging.error(decode_binary(cp.stdout))
499- sys.exit(1)
500-
501- merge_base_id = repo.raw_repo.merge_base(
502- onto_obj.id,
503- commitish_obj.id,
504- )
505- if merge_base_id is None:
506- logging.error(
507- "Unable to find a common ancestor for %s and %s.",
508- onto,
509- commitish,
510- )
511- sys.exit(1)
512-
513- if merge_base_id == onto_obj.id:
514- logging.error(
515- "The common ancestor of %s and %s is "
516- "identical to %s. No merge is necessary.",
517- onto,
518- commitish,
519- onto,
520- )
521- sys.exit(1)
522-
523- merge_base_id = str(merge_base_id)
524- merge_base_version, _ = repo.get_changelog_versions_from_treeish(
525- merge_base_id,
526- )
527-
528->>>>>>> gitubuntu/merge.py
529 if subcommand == 'start':
530 do_start(
531 repo,
532diff --git a/gitubuntu/remote.py b/gitubuntu/remote.py
533index 41981ee..db42168 100644
534--- a/gitubuntu/remote.py
535+++ b/gitubuntu/remote.py
536@@ -120,7 +120,6 @@ def main(
537 directory=None,
538 remote_name=None,
539 no_fetch=False,
540-<<<<<<< gitubuntu/remote.py
541 proto=top_level_defaults.proto,
542 ):
543 """Entry point to remote subcommand
544@@ -130,17 +129,6 @@ def main(
545 @url: the string URL of the remote to add
546 @lp_user: string user to authenticate to Launchpad as
547 @proto: string protocol to use (one of 'http', 'https', 'git')
548-=======
549- proto=None,
550-):
551- """Entry point to remote subcommand
552- @subcommand: what action to take. Currently only 'add' supported.
553- @user: which Launchpad user's repository to add
554- @package: name of source package
555- @url: the URL of the remote to add
556- @lp_user: user to authenticate to Launchpad as
557- @proto: protocol to use (one of 'http', 'https', 'git')
558->>>>>>> gitubuntu/remote.py
559
560 If package is None, it will be derived from HEAD's debian/changelog.
561
562@@ -152,12 +140,6 @@ def main(
563 If directory is None, the current directory is used.
564
565 If remote_name is None, the remote will be named @user.
566-<<<<<<< gitubuntu/remote.py
567-=======
568-
569- If proto is None, the default value from gitubuntu.__main__ will be
570- used (top_level_defaults.proto).
571->>>>>>> gitubuntu/remote.py
572 """
573 if directory is None:
574 directory = os.path.abspath(os.getcwd())
575diff --git a/gitubuntu/review.py b/gitubuntu/review.py
576new file mode 100644
577index 0000000..0acdbd3
578--- /dev/null
579+++ b/gitubuntu/review.py
580@@ -0,0 +1,140 @@
581+import argparse
582+from contextlib import redirect_stdout
583+import io
584+import logging
585+import os
586+import shutil
587+import sys
588+import tempfile
589+import urllib.parse
590+import gitubuntu.clone
591+import gitubuntu.lint
592+from gitubuntu.__main__ import top_level_defaults
593+from gitubuntu.git_repository import (
594+ GitUbuntuRepository,
595+ GitUbuntuRepositoryFetchError,
596+)
597+from gitubuntu.source_information import launchpad_login_auth
598+
599+
600+def parse_args(subparsers=None, base_subparsers=None):
601+ kwargs = dict(
602+ description="Given a Launchpad MP URL, perform a review (EXPERIMENTAL)",
603+ formatter_class=argparse.RawTextHelpFormatter,
604+ epilog="An exit code of 0 indicates all checks passed",
605+ )
606+ if base_subparsers:
607+ kwargs["parents"] = base_subparsers
608+ if subparsers:
609+ parser = subparsers.add_parser("review", **kwargs)
610+ parser.set_defaults(func=cli_main)
611+ else:
612+ parser = argparse.ArgumentParser(**kwargs)
613+
614+ parser.add_argument(
615+ "url",
616+ type=str,
617+ help="Full URL of Launchpad Merge Proposal to review",
618+ )
619+ parser.add_argument(
620+ '--clone',
621+ action='store_true',
622+ help="Create a new local repository by cloning, rather than "
623+ "using the repository at the current directory.",
624+ )
625+ parser.add_argument(
626+ '--add-comment',
627+ action='store_true',
628+ help="Add a comment to the MP with the lint results. "
629+ "This may also change the review state",
630+ )
631+
632+ if not subparsers:
633+ return parser.parse_args()
634+ return "review - %s" % kwargs["description"]
635+
636+
637+def cli_main(args):
638+ main(
639+ args.clone,
640+ args.url,
641+ args.add_comment,
642+ )
643+
644+def main(clone, url, add_comment):
645+ """Entry point to review
646+
647+ Arguments:
648+ @clone: boolean to indicate a new repository should be cloned.
649+ @url: string URL of MP to review
650+ @add_comment: boolean to indicate the MP should be updated with the
651+ review results.
652+ """
653+ url = urllib.parse.urlparse(url).path
654+ lp = launchpad_login_auth()
655+ mp = lp.load(url)
656+ target_url = mp.target_git_repository.git_https_url
657+ target_branch = mp.target_git_path[len('refs/heads/'):]
658+ idx = target_url.index('~')
659+ target_user = target_url[idx+1:target_url.index('/', idx)]
660+ if target_user != 'usd-import-team':
661+ logging.error(
662+ "Performing arbitrary reviews is not yet supported (user %s "
663+ "specified).",
664+ target_user
665+ )
666+ sys.exit(1)
667+
668+ source_url = mp.source_git_repository.git_https_url
669+ source_branch = mp.source_git_path[len('refs/heads/'):]
670+ idx = source_url.index('~')
671+ source_user = source_url[idx+1:source_url.index('/', idx)]
672+ srcpkg = target_url.split('/')[-1]
673+ if clone:
674+ repo = gitubuntu.clone.main(package=srcpkg)
675+ else:
676+ repo = GitUbuntuRepository('.')
677+ try:
678+ repo.fetch_base_remotes()
679+ except GitUbuntuRepositoryFetchError:
680+ sys.exit(1)
681+ os.chdir(repo.local_dir)
682+ repo.add_remote(
683+ pkgname=srcpkg,
684+ repo_owner=source_user,
685+ remote_name=source_user,
686+ )
687+ try:
688+ repo.fetch_remote(remote_name=source_user)
689+ except GitUbuntuRepositoryFetchError:
690+ sys.exit(1)
691+
692+ logging.info(
693+ "Linting merge of %s/%s into pkg/%s" % (
694+ source_user,
695+ source_branch,
696+ target_branch
697+ )
698+ )
699+ try:
700+ f = io.StringIO()
701+ with redirect_stdout(f):
702+ gitubuntu.lint.do_lint(
703+ repo=repo,
704+ commitish='%s/%s' % (source_user, source_branch),
705+ target_branch='pkg/%s' % target_branch,
706+ verbose=True,
707+ )
708+ except SystemExit as e:
709+ if e.code == 0:
710+ vote='Approve'
711+ else:
712+ vote='Needs Fixing'
713+ lint_out = f.getvalue()
714+ if add_comment:
715+ mp.createComment(content=lint_out, vote=vote,
716+ subject='Automated lint result'
717+ )
718+ else:
719+ logging.info('git ubuntu lint result:')
720+ print(lint_out, end='')
721diff --git a/gitubuntu/submit.py b/gitubuntu/submit.py
722index 0bf1319..fb080f3 100644
723--- a/gitubuntu/submit.py
724+++ b/gitubuntu/submit.py
725@@ -75,7 +75,6 @@ def cli_main(args):
726 user = None
727
728 main(
729-<<<<<<< gitubuntu/submit.py
730 directory=args.directory,
731 force=args.force,
732 target_user=args.target_user,
733@@ -85,23 +84,11 @@ def cli_main(args):
734 user=user,
735 branch=args.branch,
736 proto=args.proto,
737-=======
738- args.directory,
739- args.force,
740- user,
741- args.branch,
742- args.target_user,
743- args.target_branch,
744- args.no_push,
745- args.proto,
746- args.reviewer,
747->>>>>>> gitubuntu/submit.py
748 )
749
750 def main(
751 directory,
752 force,
753-<<<<<<< gitubuntu/submit.py
754 target_user,
755 target_branch,
756 no_push,
757@@ -130,16 +117,6 @@ def main(
758
759 If branch is None, HEAD is used.
760 """
761-=======
762- user,
763- branch,
764- target_user,
765- target_branch,
766- no_push,
767- proto,
768- reviewers
769-):
770->>>>>>> gitubuntu/submit.py
771 repo = GitUbuntuRepository(directory, user, proto)
772
773 if branch:
774diff --git a/gitubuntu/versioning.py b/gitubuntu/versioning.py
775index c3187f5..3a5dc53 100644
776--- a/gitubuntu/versioning.py
777+++ b/gitubuntu/versioning.py
778@@ -27,7 +27,7 @@ __all__ = [
779 # see _decompose_version_string for explanation of members
780 _VersionComps = namedtuple(
781 "_VersionComps",
782- ["prefix_parts", "ubuntu_maj", "ubuntu_series", "ubuntu_min"]
783+ ["prefix_parts", "ubuntu_maj", "ubuntu_series", "ubuntu_other", "ubuntu_min"]
784 )
785
786 def _decompose_version_string(version_string):
787@@ -52,10 +52,19 @@ def _decompose_version_string(version_string):
788 "1.0.2ubuntu3.17.04.4", 17.04 is the Ubuntu series string. In both
789 "1.0-2ubuntu3.4" and "1.0-2", the Ubuntu series is not present.
790
791+ ubuntu_other is any non-series information as a string between the
792+ major and minor version numbers. For example, in the version strings
793+ "1.0-2ubuntu3.4" and "1.0-2ubuntu3.17.04.4", the other Ubuntu
794+ version string is not present. In both "1.0-2" and "1.0-2ubuntu3",
795+ the other Ubuntu version number is not present. In
796+ "1.2.2-0ubuntu13.1.23", "1" is the Ubuntu other version string.
797+
798 ubuntu_min is the "minor" Ubuntu version number as an integer, or None if
799 not present. For example, in the version strings "1.0-2ubuntu3.4" and
800 "1.0-2ubuntu3.17.04.4", 4 is the Ubuntu minor version number. In both
801- "1.0-2" and "1.0-2ubuntu3", the minor Ubuntu version number is not present.
802+ "1.0-2" and "1.0-2ubuntu3", the minor Ubuntu version number is not
803+ present. In "1.2.2-0ubuntu13.1.23", 23 is the Ubuntu minor version
804+ number.
805
806 For version strings not following these standard forms, the result is
807 undefined except where specific test cases exist for them.
808@@ -73,7 +82,7 @@ def _decompose_version_string(version_string):
809 )
810 elif 'build' in parts:
811 return _VersionComps._make(
812- [parts[:parts.index('build')], None, None, None]
813+ [parts[:parts.index('build')], None, None, None, None]
814 )
815 elif 'ubuntu' in parts:
816 ubuntu_idx = parts.index('ubuntu')
817@@ -85,6 +94,7 @@ def _decompose_version_string(version_string):
818 # nothing after "Xubuntu", treated as 1
819 ubuntu_maj = 1
820 ubuntu_series = None
821+ ubuntu_other = None
822 if len(suffix_parts) < 2:
823 ubuntu_min = None
824 else:
825@@ -95,15 +105,13 @@ def _decompose_version_string(version_string):
826 if len(suffix_parts[1:-1]) > 2:
827 ubuntu_series = ''.join(suffix_parts[2:-2])
828 if not re.match(r'\d\d\.\d\d', ubuntu_series):
829- raise ValueError("Not sure how to decompose %s: "
830- "series %s not in expected format" %
831- (version_string, ubuntu_series)
832- )
833+ ubuntu_series = None
834+ ubuntu_other = ''.join(suffix_parts[2:-2])
835 return _VersionComps._make(
836- [static_parts, ubuntu_maj, ubuntu_series, ubuntu_min]
837+ [static_parts, ubuntu_maj, ubuntu_series, ubuntu_other, ubuntu_min]
838 )
839 else:
840- return _VersionComps._make([parts, None, None, None])
841+ return _VersionComps._make([parts, None, None, None, None])
842
843
844 def _bump_version_object(old_version_object, bump_function):
845@@ -169,6 +177,8 @@ def _bump_sru_version_suffix_string(befores, series_string, afters,
846 bumped_parts = ['ubuntu', str(new_maj), '.']
847 if old_suffix_comps.ubuntu_series:
848 bumped_parts.extend([old_suffix_comps.ubuntu_series, '.'])
849+ elif old_suffix_comps.ubuntu_other:
850+ bumped_parts.extend([old_suffix_comps.ubuntu_other, '.'])
851 elif str(version) in [str(v) for v in befores + afters]:
852 bumped_parts.extend([series_string, '.'])
853 elif any(
854@@ -251,12 +261,13 @@ def version_compare(a, b):
855
856
857 @pytest.mark.parametrize('test_input, expected', [
858- ('2', _VersionComps._make([['2'], None, None, None])),
859- ('2ubuntu1', _VersionComps._make([['2'], 1, None, None])),
860- ('2ubuntu1.3', _VersionComps._make([['2'], 1, None, 3])),
861- ('2ubuntu0.16.04.3', _VersionComps._make([['2'], 0, '16.04', 3])),
862- ('2build1', _VersionComps._make([['2'], None, None, None])),
863- ('2build1.3', _VersionComps._make([['2'], None, None, None])),
864+ ('2', _VersionComps._make([['2'], None, None, None, None])),
865+ ('2ubuntu1', _VersionComps._make([['2'], 1, None, None, None])),
866+ ('2ubuntu1.3', _VersionComps._make([['2'], 1, None, None, 3])),
867+ ('2ubuntu0.16.04.3', _VersionComps._make([['2'], 0, '16.04', None, 3])),
868+ ('2build1', _VersionComps._make([['2'], None, None, None, None])),
869+ ('2build1.3', _VersionComps._make([['2'], None, None, None, None])),
870+ ('0ubuntu13.1.22', _VersionComps._make([['0'], 13, None, '1', 22])),
871 ])
872 def test_decompose_version_string(test_input, expected):
873 assert _decompose_version_string(test_input) == expected

Subscribers

People subscribed via source and target branches