Merge lp://staging/~abentley/bzr-pipeline/lp-submit-hook into lp://staging/bzr-pipeline

Proposed by Aaron Bentley
Status: Merged
Merged at revision: not available
Proposed branch: lp://staging/~abentley/bzr-pipeline/lp-submit-hook
Merge into: lp://staging/bzr-pipeline
Diff against target: 385 lines (+29/-326)
3 files modified
__init__.py (+29/-1)
commands.py (+0/-38)
lp_submit.py (+0/-287)
To merge this branch: bzr merge lp://staging/~abentley/bzr-pipeline/lp-submit-hook
Reviewer Review Type Date Requested Status
Aaron Bentley code Pending
Review via email: mp+17863@code.staging.launchpad.net
To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :

Land this branch once bzr-core provides lp-submit.

154. By Aaron Bentley

Update to match changes in lpsubmit branch.

155. By Aaron Bentley

Merged trunk into lp-submit-hook.

156. By Aaron Bentley

Merged trunk into lp-submit-hook.

157. By Aaron Bentley

Update support for lp-propose command.

158. By Aaron Bentley

Merge lower pipe.

159. By Aaron Bentley

Remove lp_submit class.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '__init__.py'
2--- __init__.py 2009-11-20 14:13:57 +0000
3+++ __init__.py 2010-02-20 18:43:16 +0000
4@@ -131,7 +131,6 @@
5 register('cmd_merge')
6 register('cmd_reconfigure_pipeline')
7 register('cmd_sync_pipeline')
8-register('cmd_lp_submit')
9 plugin_cmds.register_lazy('cmd_import_loom', [],
10 'bzrlib.plugins.pipeline.loom')
11
12@@ -157,6 +156,35 @@
13 directories.register(':', PipeAliasDirectory,
14 'Easy access to remembered branch locations')
15
16+from bzrlib.plugins.launchpad import lp_api
17+try:
18+ from bzrlib.plugins.launchpad import lp_propose
19+except ImportError:
20+ pass
21+else:
22+ def get_prerequisite_from_pipe(hook_params):
23+ from bzrlib.plugins.pipeline.pipeline import PipeManager
24+ source_branch = hook_params['source_branch']
25+ launchpad = hook_params['launchpad']
26+ manager = PipeManager(source_branch.bzr)
27+ prev_pipe = manager.get_prev_pipe()
28+ if prev_pipe is not None:
29+ prerequisite_branch = lp_api.LaunchpadBranch.from_bzr(launchpad,
30+ prev_pipe)
31+ if (prerequisite_branch.lp.bzr_identity
32+ == hook_params['target_branch'].lp.bzr_identity):
33+ prerequisite_branch = None
34+ else:
35+ prerequisite_branch = None
36+ return prerequisite_branch
37+
38+
39+ # XXX: When we move the pipeline command, we should make sure it
40+ # constructs Submitter correctly.
41+ lp_propose.Proposer.hooks.install_named_hook(
42+ 'get_prerequisite', get_prerequisite_from_pipe,
43+ 'Get the prerequisite from the pipeline')
44+
45
46 def test_suite():
47 from bzrlib.tests.TestUtil import TestLoader, TestSuite
48
49=== modified file 'commands.py'
50--- commands.py 2010-02-04 12:49:34 +0000
51+++ commands.py 2010-02-20 18:43:16 +0000
52@@ -369,41 +369,3 @@
53 raise errors.BzrCommandError(
54 'No location specified and none remembered.')
55 manager.sync_pipeline(location, remote)
56-
57-
58-class cmd_lp_submit(PipeCommand):
59- """Submit the specified pipe to Launchpad."""
60-
61- takes_options = [Option('staging',
62- help='Propose the merge on staging.'),
63- Option('message', short_name='m', type=unicode,
64- help='Commit message.'),
65- ListOption('review', short_name='R', type=unicode,
66- help='Requested reviewer and optional type.')]
67-
68- takes_args = ['submit_branch?']
69-
70- def run(self, submit_branch=None, review=None, staging=False,
71- message=None, open_webpage=True):
72- from bzrlib.plugins.pipeline import lp_submit
73- checkout, manager = self._get_checkout_manager(checkout_optional=True,
74- allow_tree=True)
75- if review is None:
76- reviews = None
77- else:
78- reviews = []
79- for review in review:
80- if '=' in review:
81- reviews.append(review.split('=', 2))
82- else:
83- reviews.append((review, ''))
84- if submit_branch is None:
85- submit_branch = manager.storage.branch.get_submit_branch()
86- if submit_branch is None:
87- target = None
88- else:
89- target = Branch.open(submit_branch)
90- submitter = lp_submit.Submitter(checkout, manager, target, message,
91- reviews, staging)
92- submitter.check_submission()
93- submitter.submit(open_webpage)
94
95=== removed file 'lp_submit.py'
96--- lp_submit.py 2010-02-04 12:49:34 +0000
97+++ lp_submit.py 1970-01-01 00:00:00 +0000
98@@ -1,287 +0,0 @@
99-import errno, re, webbrowser
100-
101-from bzrlib import (
102- branch,
103- config,
104- errors,
105- msgeditor,
106- osutils,
107- trace,
108- transport,
109-)
110-
111-class NoLaunchpadLib(errors.BzrCommandError):
112-
113- _fmt = "LaunchpadLib must be installed for this operation."
114-
115-
116-try:
117- from launchpadlib import (
118- credentials,
119- launchpad,
120- )
121-except ImportError:
122- raise NoLaunchpadLib()
123-
124-from lazr.restfulclient import errors as restful_errors
125-
126-
127-_lp = None
128-def lp(staging=False):
129- if staging:
130- service_root = launchpad.STAGING_SERVICE_ROOT
131- else:
132- service_root = launchpad.EDGE_SERVICE_ROOT
133- global _lp
134- if _lp is not None:
135- return _lp
136- cachedir = osutils.pathjoin(config.config_dir(),
137- 'pipeline-cachedir')
138- credentials_path = osutils.pathjoin(config.config_dir(),
139- 'pipeline-credentials')
140- if staging:
141- credentials_path += '-staging'
142- try:
143- credentials_file = open(credentials_path, 'r')
144- except IOError, e:
145- if e.errno != errno.ENOENT:
146- raise
147- _lp = launchpad.Launchpad.get_token_and_login('bzr-pipeline',
148- service_root, cachedir)
149- _lp.credentials.save(open(credentials_path, 'w'))
150- else:
151- creds = credentials.Credentials()
152- creds.load(open(credentials_path, 'r'))
153- _lp = launchpad.Launchpad(creds, service_root, cachedir)
154- return _lp
155-
156-
157-class MegaBranch(object):
158-
159- def __init__(self, lp_branch, bzr_url, bzr_branch=None, check_update=True):
160- self.bzr_url = bzr_url
161- self._bzr = bzr_branch
162- self._push_bzr = None
163- self._check_update = False
164- self.lp = lp_branch
165-
166- @property
167- def bzr(self):
168- if self._bzr is None:
169- self._bzr = branch.Branch.open(self.bzr_url)
170- return self._bzr
171-
172- @property
173- def push_bzr(self):
174- if self._push_bzr is None:
175- self._push_bzr = branch.Branch.open(self.lp.bzr_identity)
176- return self._push_bzr
177-
178- @staticmethod
179- def plausible_launchpad_url(url):
180- if url is None:
181- return False
182- if url.startswith('lp:'):
183- return True
184- regex = re.compile('([a-z]*\+)*(bzr\+ssh|http)'
185- '://bazaar.*.launchpad.net')
186- return bool(regex.match(url))
187-
188- @staticmethod
189- def candidate_urls(bzr_branch):
190- url = bzr_branch.get_public_branch()
191- if url is not None:
192- yield url
193- url = bzr_branch.get_push_location()
194- if url is not None:
195- yield url
196- yield bzr_branch.base
197-
198- @staticmethod
199- def tweak_url(url, staging):
200- if not staging:
201- return url
202- if url is None:
203- return None
204- return url.replace('bazaar.launchpad.net',
205- 'bazaar.staging.launchpad.net')
206-
207- @classmethod
208- def from_bzr(cls, bzr_branch, staging=False):
209- check_update = True
210- for url in cls.candidate_urls(bzr_branch):
211- url = cls.tweak_url(url, staging)
212- if not cls.plausible_launchpad_url(url):
213- continue
214- lp_branch = lp(staging).branches.getByUrl(url=url)
215- if lp_branch is not None:
216- break
217- else:
218- lp_branch = cls.create_now(bzr_branch, staging)
219- check_update = False
220- return cls(lp_branch, bzr_branch.base, bzr_branch, check_update)
221-
222- @classmethod
223- def create_now(cls, bzr_branch, staging):
224- url = cls.tweak_url(bzr_branch.get_push_location(), staging)
225- if not cls.plausible_launchpad_url(url):
226- raise errors.BzrError('%s is not registered on Launchpad' %
227- bzr_branch.base)
228- bzr_branch.create_clone_on_transport(transport.get_transport(url))
229- lp_branch = lp(staging).branches.getByUrl(url=url)
230- if lp_branch is None:
231- raise errors.BzrError('%s is not registered on Launchpad' % url)
232- return lp_branch
233-
234- @classmethod
235- def from_dev_focus(cls, lp_branch):
236- if lp_branch.project is None:
237- raise errors.BzrError('%s has no product.' %
238- lp_branch.bzr_identity)
239- dev_focus = lp_branch.project.development_focus.branch
240- if dev_focus is None:
241- raise errors.BzrError('%s has no development focus.' %
242- lp_branch.bzr_identity)
243- return cls(dev_focus, dev_focus.bzr_identity)
244-
245- def update_lp(self):
246- if not self._check_update:
247- return
248- self.bzr.lock_read()
249- try:
250- if self.lp.last_scanned_id is not None:
251- if self.bzr.last_revision() == self.lp.last_scanned_id:
252- trace.note('%s is already up-to-date.' %
253- self.lp.bzr_identity)
254- return
255- graph = self.bzr.repository.get_graph()
256- if not graph.is_ancestor(self.bzr.last_revision(),
257- self.lp.last_scanned_id):
258- raise errors.DivergedBranches(self.bzr, self.push_bzr)
259- trace.note('Pushing to %s' % self.lp.bzr_identity)
260- self.bzr.push(self.push_bzr)
261- finally:
262- self.bzr.unlock()
263-
264- def find_lca_tree(self, other):
265- graph = self.bzr.repository.get_graph(other.bzr.repository)
266- lca = graph.find_unique_lca(self.bzr.last_revision(),
267- other.bzr.last_revision())
268- return self.bzr.repository.revision_tree(lca)
269-
270-
271-class Submitter(object):
272-
273- def __init__(self, tree, manager, target_branch, message, reviews,
274- staging=False):
275- self.tree = tree
276- self.manager = manager
277- self.staging = staging
278- self.source_branch = MegaBranch.from_bzr(self.manager.storage.branch,
279- self.staging)
280- if target_branch is None:
281- self.target_branch = MegaBranch.from_dev_focus(
282- self.source_branch.lp)
283- else:
284- self.target_branch = MegaBranch.from_bzr(target_branch,
285- self.staging)
286- self.commit_message = message
287- if reviews == []:
288- target_reviewer = self.target_branch.lp.reviewer
289- if target_reviewer is None:
290- raise errors.BzrCommandError('No reviewer specified')
291- self.reviews = [(target_reviewer, '')]
292- else:
293- self.reviews = [(lp(self.staging).people[reviewer], review_type)
294- for reviewer, review_type in
295- reviews]
296-
297- def get_comment(self, prerequisite_branch):
298- info = ["Source: %s\n" % self.source_branch.lp.bzr_identity]
299- info.append("Target: %s\n" % self.target_branch.lp.bzr_identity)
300- if prerequisite_branch is not None:
301- info.append("Prereq: %s\n" % prerequisite_branch.lp.bzr_identity)
302- for rdata in self.reviews:
303- uniquename = "%s (%s)" % (rdata[0].display_name, rdata[0].name)
304- info.append('Reviewer: %s, type "%s"\n' % (uniquename, rdata[1]))
305- self.source_branch.bzr.lock_read()
306- try:
307- self.target_branch.bzr.lock_read()
308- try:
309- body = self.try_get_body()
310- finally:
311- self.target_branch.bzr.unlock()
312- finally:
313- self.source_branch.bzr.unlock()
314- initial_comment = msgeditor.edit_commit_message(''.join(info),
315- start_message=body)
316- return initial_comment.strip().encode('utf-8')
317-
318- def try_get_body(self):
319- try:
320- from bzrlib.plugins.lpreview_body.body_callback import (
321- get_body,
322- modified_files,
323- )
324- except ImportError:
325- return ''
326- def list_modified_files():
327- lca_tree = self.source_branch.find_lca_tree(
328- self.target_branch)
329- source_tree = self.source_branch.bzr.basis_tree()
330- files = modified_files(lca_tree, source_tree)
331- return list(files)
332- target_loc = ('bzr+ssh://bazaar.launchpad.net/%s' %
333- self.target_branch.lp.unique_name)
334- return get_body(self.tree, target_loc, list_modified_files, '')
335-
336- def check_submission(self):
337- if self.source_branch.lp.self_link == self.target_branch.lp.self_link:
338- raise errors.BzrCommandError(
339- 'Source and target branches must be different.')
340- for mp in self.source_branch.lp.landing_targets:
341- if mp.queue_status in ('Merged', 'Rejected'):
342- continue
343- if mp.target_branch.self_link == self.target_branch.lp.self_link:
344- raise errors.BzrCommandError(
345- 'There is already a branch merge proposal: %s' %
346- canonical_url(mp))
347-
348- def submit(self, open_webpage):
349- prev_pipe = self.manager.get_prev_pipe()
350- if prev_pipe is not None:
351- prerequisite_branch = MegaBranch.from_bzr(prev_pipe)
352- else:
353- prerequisite_branch = None
354- self.source_branch.update_lp()
355- if prerequisite_branch is not None:
356- prerequisite_branch.update_lp()
357- if prerequisite_branch is None:
358- prereq = None
359- else:
360- prereq = prerequisite_branch.lp
361- reviewers = []
362- review_types = []
363- for reviewer, review_type in self.reviews:
364- review_types.append(review_type)
365- reviewers.append(reviewer.self_link)
366- initial_comment = self.get_comment(prerequisite_branch)
367- try:
368- mp = self.source_branch.lp.createMergeProposal(
369- target_branch=self.target_branch.lp,
370- prerequisite_branch=prereq, initial_comment=initial_comment,
371- commit_message=self.commit_message, reviewers=reviewers,
372- review_types=review_types)
373- except restful_errors.HTTPError, e:
374- for line in e.content.splitlines():
375- if line.startswith('Traceback (most recent call last):'):
376- break
377- print line
378- else:
379- if open_webpage:
380- webbrowser.open(canonical_url(mp))
381-
382-def canonical_url(object):
383- url = object.self_link.replace('https://api.', 'https://code.')
384- return url.replace('/beta/', '/')
385-

Subscribers

People subscribed via source and target branches