Merge lp:~ubuntuone-hackers/tarmac/trunk into lp:tarmac

Proposed by Jonathan Lange on 2012-12-18
Status: Rejected
Rejected by: dobey on 2013-11-04
Proposed branch: lp:~ubuntuone-hackers/tarmac/trunk
Merge into: lp:tarmac
Diff against target: 1069 lines (+532/-80) (has conflicts)
13 files modified
bin/tarmac (+3/-1)
docs/introduction.txt (+5/-0)
tarmac/bin/__init__.py (+1/-1)
tarmac/bin/commands.py (+311/-15)
tarmac/bin/options.py (+12/-3)
tarmac/bin/registry.py (+2/-2)
tarmac/branch.py (+111/-20)
tarmac/plugins/command.py (+4/-3)
tarmac/plugins/commitmessage.py (+10/-6)
tarmac/plugins/tests/test_commitmessage.py (+7/-2)
tarmac/tests/mock.py (+2/-0)
tarmac/tests/test_branch.py (+19/-14)
tarmac/tests/test_commands.py (+45/-13)
Text conflict in tarmac/bin/commands.py
Text conflict in tarmac/bin/options.py
Text conflict in tarmac/branch.py
To merge this branch: bzr merge lp:~ubuntuone-hackers/tarmac/trunk
Reviewer Review Type Date Requested Status
Paul Hummer 2012-12-18 Pending
Review via email: mp+140435@code.launchpad.net

Description of the change

WIP MP to make the delta visible.

To post a comment you must log in.
lp:~ubuntuone-hackers/tarmac/trunk updated on 2013-03-13
415. By Jonathan Lange on 2012-12-19

Fix the test failures in u1's tarmac fork

416. By Jonathan Lange on 2012-12-19

Oops. Really fix the tests.

417. By Jonathan Lange on 2012-12-19

Handle unicode output from tests. (~james-w)

418. By Jonathan Lange on 2012-12-19

Commit message plugin that sets review & bugs fixed (james-w)

419. By Jonathan Lange on 2012-12-20

Make the tests pass.

420. By Jonathan Lange on 2012-12-20

Merge James's branch to break up _do_merges, plus changes from my review.

421. By Jonathan Lange on 2012-12-20

Merge exit-status

422. By Jonathan Lange on 2012-12-20

Add "check" command.

423. By Jonathan Lange on 2012-12-20

Allow verify command to be specified on command line.

424. By Sidnei da Silva on 2012-12-20

- Fix to not turn 'proposals' into a single Entry object.

425. By James Westby on 2013-01-03

Also rename the other call to set_up.

426. By dobey on 2013-01-04

Remove the broken commit plug-in.

427. By Jonathan Lange on 2013-01-15

Better error message when private branches attack

428. By Sidnei da Silva on 2013-01-16

Use HTTPError directly to make it work in Lucid.

429. By James Westby on 2013-01-23

Merge trunk.

430. By James Westby on 2013-03-13

Allow the branch name to be used in the commit message template. (David Britton)

Unmerged revisions

431. By Vincent Ladeuil on 2016-07-11

Set the MP status to Merged after committing instead of waiting for lp to do so.

430. By James Westby on 2013-03-13

Allow the branch name to be used in the commit message template. (David Britton)

429. By James Westby on 2013-01-23

Merge trunk.

428. By Sidnei da Silva on 2013-01-16

Use HTTPError directly to make it work in Lucid.

427. By Jonathan Lange on 2013-01-15

Better error message when private branches attack

426. By dobey on 2013-01-04

Remove the broken commit plug-in.

425. By James Westby on 2013-01-03

Also rename the other call to set_up.

424. By Sidnei da Silva on 2012-12-20

- Fix to not turn 'proposals' into a single Entry object.

423. By Jonathan Lange on 2012-12-20

Allow verify command to be specified on command line.

422. By Jonathan Lange on 2012-12-20

Add "check" command.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/tarmac'
2--- bin/tarmac 2010-06-17 17:23:37 +0000
3+++ bin/tarmac 2013-03-13 16:57:22 +0000
4@@ -2,5 +2,7 @@
5 # vim:filetype=python
6 '''Main tarmac script.'''
7
8+import sys
9+
10 from tarmac.bin import main
11-main()
12+sys.exit(main())
13
14=== modified file 'docs/introduction.txt'
15--- docs/introduction.txt 2012-06-25 21:05:28 +0000
16+++ docs/introduction.txt 2013-03-13 16:57:22 +0000
17@@ -151,6 +151,11 @@
18 **reviewer**
19 The display name of the merge proposal reviewer.
20
21+**branch_name**
22+ The short branch name. i.e.: branch_name in "lp:~user/project/branch_name"
23+
24+**\n**
25+ A \n in the commit message template will be rendered as a newline character.
26
27 Command
28 =======
29
30=== modified file 'tarmac/bin/__init__.py'
31--- tarmac/bin/__init__.py 2010-09-02 15:18:07 +0000
32+++ tarmac/bin/__init__.py 2013-03-13 16:57:22 +0000
33@@ -27,4 +27,4 @@
34 args = sys.argv[1:]
35 if not args:
36 args = ['help']
37- registry.run(args)
38+ return registry.run(args)
39
40=== modified file 'tarmac/bin/commands.py'
41--- tarmac/bin/commands.py 2013-01-31 20:03:27 +0000
42+++ tarmac/bin/commands.py 2013-03-13 16:57:22 +0000
43@@ -10,6 +10,7 @@
44 from launchpadlib.launchpad import Launchpad
45 from launchpadlib.uris import (LPNET_SERVICE_ROOT,
46 STAGING_SERVICE_ROOT)
47+from lazr.restfulclient.errors import HTTPError
48
49 from tarmac.bin import options
50 from tarmac.branch import Branch
51@@ -134,6 +135,237 @@
52 help_commands(self.outf)
53
54
55+def _get_login_name(lp):
56+ """Return the name of the user `lp` is logged in as.
57+
58+ `None` if `lp` is an anonymous connection.
59+ """
60+ try:
61+ me = lp.me
62+ except HTTPError, e:
63+ # XXX Newer lazr.restfulclient has a proper Unauthorized exception, but
64+ # the version in Lucid does not.
65+ if e.status == 401:
66+ return None
67+ raise
68+ if me:
69+ return me.name
70+ return None
71+
72+
73+def _get_mergable_proposals_for_branch(lp_branch, logger, imply_commit_message=False):
74+ """Return a list of the mergable proposals for the given branch."""
75+ proposals = []
76+ for entry in lp_branch.landing_candidates:
77+ logger.debug("Considering merge proposal: {0}".format(entry.web_link))
78+
79+ if entry.queue_status != u'Approved':
80+ logger.debug(
81+ " Skipping proposal: status is {0}, not "
82+ "'Approved'".format(entry.queue_status))
83+ continue
84+
85+ if (not imply_commit_message and not entry.commit_message):
86+ logger.debug(
87+ " Skipping proposal: proposal has no commit message")
88+ continue
89+
90+ proposals.append(entry)
91+ return proposals
92+
93+
94+def _get_branch(branch_url, launchpad, logger):
95+ lp_branch = launchpad.branches.getByUrl(url=branch_url)
96+ if lp_branch is None:
97+ logger.info(
98+ 'User {0} could not find {1} branch on Launchpad'.format(
99+ _get_login_name(launchpad), branch_url))
100+ return lp_branch
101+
102+
103+def _get_branch_and_mps(branch_url, launchpad, logger,
104+ imply_commit_message=False):
105+ """Get branch and merge proposals from Launchpad.
106+
107+ :param branch_url: The Launchpad URL of the branch. e.g `lp:foo`.
108+ :param launchpad: A Launchpad API object.
109+ :param logger: A Python logger.
110+ :param imply_commit_message: Whether to make up a commit message
111+ if the merge proposal lacks an explicit one. Defaults to False.
112+ :return: `(lp_branch, [mergable_mp, ...])`
113+ """
114+ lp_branch = _get_branch(branch_url, launchpad, logger)
115+ proposals = []
116+
117+ if lp_branch:
118+ proposals = _get_mergable_proposals_for_branch(
119+ lp_branch, logger,
120+ imply_commit_message=imply_commit_message)
121+
122+ if not proposals:
123+ logger.info(
124+ 'No approved proposals found for %(branch_url)s' % {
125+ 'branch_url': branch_url})
126+
127+ return lp_branch, proposals
128+
129+
130+
131+def _get_reviews(proposal):
132+ """Get the set of reviews from the proposal."""
133+ votes = [vote for vote in proposal.votes if vote.comment]
134+ if not votes:
135+ return None
136+ return [
137+ '%s;%s' % (vote.reviewer.display_name, vote.comment.vote)
138+ for vote in votes]
139+
140+
141+def set_up(logger, debug=False, http_debug=False):
142+ if debug:
143+ set_up_debug_logging()
144+ logger.debug('Debug logging enabled')
145+ if http_debug:
146+ httplib2.debuglevel = 1
147+ logger.debug('HTTP debugging enabled.')
148+ logger.debug('Loading plugins')
149+ load_plugins()
150+ logger.debug('Plugins loaded')
151+
152+
153+def merge_proposals(target, proposals, logger, config, command):
154+ logger.debug('Firing tarmac_pre_merge hook')
155+ tarmac_hooks.fire('tarmac_pre_merge',
156+ command, target)
157+ try:
158+ statuses = [
159+ merge_proposal(target, proposal, logger, config, command)
160+ for proposal in proposals]
161+ logger.debug('Firing tarmac_post_merge hook')
162+ tarmac_hooks.fire('tarmac_post_merge',
163+ command, target, success_count=sum(statuses))
164+ finally:
165+ target.cleanup()
166+ return statuses
167+
168+
169+def merge_proposal(target, proposal, logger, config, command):
170+ target.cleanup()
171+ logger.debug(
172+ u'Preparing to merge %(source_branch)s' % {
173+ 'source_branch': proposal.source_branch.web_link})
174+ try:
175+ prerequisite = proposal.prerequisite_branch
176+ if prerequisite:
177+ merges = [x for x in prerequisite.landing_targets
178+ if x.target_branch == target.lp_branch and
179+ x.queue_status != u'Superseded']
180+ if len(merges) == 0:
181+ raise TarmacMergeError(
182+ u'No proposals of prerequisite branch.',
183+ u'No proposals found for merge of %s '
184+ u'into %s.' % (
185+ prerequisite.web_link,
186+ target.lp_branch.web_link))
187+ elif len(merges) > 1:
188+ raise TarmacMergeError(
189+ u'Too many proposals of prerequisite.',
190+ u'More than one proposal found for merge '
191+ u'of %s into %s, which is not Superseded.' % (
192+ prerequisite.web_link,
193+ target.lp_branch.web_link))
194+ elif len(merges) == 1:
195+ if merges[0].queue_status != u'Merged':
196+ raise TarmacMergeError(
197+ u'Prerequisite not yet merged.',
198+ u'The prerequisite %s has not yet been '
199+ u'merged into %s.' % (
200+ prerequisite.web_link,
201+ target.lp_branch.web_link))
202+
203+ if not proposal.reviewed_revid:
204+ raise TarmacMergeError(
205+ u'No approved revision specified.')
206+
207+
208+ source = Branch.create(
209+ proposal.source_branch, config, target=target)
210+
211+ source_bzr_branch = source.get_bzr_branch()
212+ approved = source_bzr_branch.revision_id_to_revno(
213+ str(proposal.reviewed_revid))
214+ tip = source_bzr_branch.revno()
215+
216+ if tip > approved:
217+ message = u'Unapproved changes made after approval'
218+ lp_comment = (
219+ u'There are additional revisions which have not '
220+ u'been approved in review. Please seek review and '
221+ u'approval of these new revisions.')
222+ raise UnapprovedChanges(message, lp_comment)
223+
224+ logger.debug(
225+ 'Merging %(source)s at revision %(revision)s' % {
226+ 'source': proposal.source_branch.web_link,
227+ 'revision': proposal.reviewed_revid})
228+
229+ target.merge(source, str(proposal.reviewed_revid))
230+
231+ logger.debug('Firing tarmac_pre_commit hook')
232+ tarmac_hooks.fire('tarmac_pre_commit',
233+ command, target, source, proposal)
234+
235+ except TarmacMergeError, failure:
236+ logger.warn(
237+ u'Merging %(source)s into %(target)s failed: %(msg)s' %
238+ {'source': proposal.source_branch.web_link,
239+ 'target': proposal.target_branch.web_link,
240+ 'msg': str(failure)})
241+
242+ subject = u'Re: [Merge] %(source)s into %(target)s' % {
243+ "source": proposal.source_branch.display_name,
244+ "target": proposal.target_branch.display_name}
245+
246+ if failure.comment:
247+ comment = failure.comment
248+ else:
249+ comment = str(failure)
250+
251+ proposal.createComment(subject=subject, content=comment)
252+ try:
253+ proposal.setStatus(
254+ status=config.rejected_branch_status)
255+ except AttributeError:
256+ proposal.setStatus(status=u'Needs review')
257+ proposal.lp_save()
258+ return False
259+
260+ except PointlessMerge:
261+ logger.warn(
262+ 'Merging %(source)s into %(target)s would be '
263+ 'pointless.' % {
264+ 'source': proposal.source_branch.web_link,
265+ 'target': proposal.target_branch.web_link})
266+ return False
267+
268+ merge_url = get_review_url(proposal)
269+ revprops = {'merge_url': merge_url}
270+
271+ commit_message = proposal.commit_message
272+ if commit_message is None and config.imply_commit_message:
273+ commit_message = proposal.description
274+
275+ target.commit(commit_message,
276+ revprops=revprops,
277+ authors=source.authors,
278+ reviews=_get_reviews(proposal))
279+
280+ logger.debug('Firing tarmac_post_commit hook')
281+ tarmac_hooks.fire('tarmac_post_commit',
282+ command, target, source, proposal)
283+ return True
284+
285+
286 class cmd_merge(TarmacCommand):
287 '''Automatically merge approved merge proposal branches.'''
288
289@@ -142,6 +374,7 @@
290 takes_options = [
291 options.http_debug_option,
292 options.debug_option,
293+<<<<<<< TREE
294 options.imply_commit_message_option,
295 options.one_option]
296
297@@ -153,14 +386,26 @@
298 return
299
300 proposals = self._get_mergable_proposals_for_branch(lp_branch)
301+=======
302+ options.imply_commit_message_option,
303+ options.one_option,
304+ options.verify_command_option,
305+ ]
306+
307+ def _do_merges(self, branch_url, verify_command=None):
308+ lp_branch, proposals = _get_branch_and_mps(
309+ branch_url, self.launchpad, self.logger,
310+ self.config.imply_commit_message)
311+>>>>>>> MERGE-SOURCE
312
313 if not proposals:
314- self.logger.info(
315- 'No approved proposals found for %(branch_url)s' % {
316- 'branch_url': branch_url})
317- return
318+ return []
319+
320+ if self.config.one:
321+ proposals = [proposals[0]]
322
323 target = Branch.create(lp_branch, self.config, create_tree=True)
324+<<<<<<< TREE
325
326 self.logger.debug('Firing tarmac_pre_merge hook')
327 tarmac_hooks.fire('tarmac_pre_merge',
328@@ -337,18 +582,19 @@
329 return reviews
330
331 def run(self, branch_url=None, launchpad=None, **kwargs):
332+=======
333+ if verify_command is not None:
334+ setattr(target.config, 'verify_command', verify_command)
335+ statuses = merge_proposals(target, proposals, self.logger, self.config, self)
336+ return statuses
337+
338+ def run(self, branch_url=None, launchpad=None, verify_command=None, **kwargs):
339+>>>>>>> MERGE-SOURCE
340 for key, value in kwargs.iteritems():
341 self.config.set('Tarmac', key, value)
342
343- if self.config.debug:
344- set_up_debug_logging()
345- self.logger.debug('Debug logging enabled')
346- if self.config.http_debug:
347- httplib2.debuglevel = 1
348- self.logger.debug('HTTP debugging enabled.')
349- self.logger.debug('Loading plugins')
350- load_plugins()
351- self.logger.debug('Plugins loaded')
352+ set_up(self.logger, debug=self.config.debug,
353+ http_debug=self.config.http_debug)
354
355 self.launchpad = launchpad
356 if self.launchpad is None:
357@@ -356,19 +602,21 @@
358 self.launchpad = self.get_launchpad_object()
359 self.logger.debug('launchpad object loaded')
360
361+ statuses = []
362+
363 if branch_url:
364 self.logger.debug('%(branch_url)s specified as branch_url' % {
365 'branch_url': branch_url})
366 if not branch_url.startswith('lp:'):
367 raise TarmacCommandError('Branch urls must start with lp:')
368- self._do_merges(branch_url)
369-
370+ statuses.extend(self._do_merges(branch_url, verify_command=verify_command))
371 else:
372 for branch in self.config.branches:
373 self.logger.debug(
374 'Merging approved branches against %(branch)s' % {
375 'branch': branch})
376 try:
377+<<<<<<< TREE
378 merged = self._do_merges(branch)
379
380 # If we've been asked to only merge one branch, then exit.
381@@ -376,8 +624,56 @@
382 break
383 except LockContention:
384 continue
385+=======
386+ statuses.extend(self._do_merges(branch, verify_command=verify_command))
387+
388+ # If we've been asked to only merge one branch, then exit.
389+ if statuses and self.config.one:
390+ break
391+ except LockContention:
392+ continue
393+>>>>>>> MERGE-SOURCE
394 except Exception, error:
395 self.logger.error(
396 'An error occurred trying to merge %s: %s',
397 branch, error)
398 raise
399+ if not all(statuses):
400+ return 2
401+ return 0
402+
403+
404+class cmd_check(TarmacCommand):
405+ '''Check whether there are any merge proposals ready to land.'''
406+
407+ takes_args = ['branch_url']
408+ takes_options = [
409+ options.http_debug_option,
410+ options.debug_option]
411+
412+ def _any_merges(self, branch_url):
413+ lp_branch, proposals = _get_branch_and_mps(
414+ branch_url, self.launchpad, self.logger)
415+ return bool(proposals)
416+
417+ def run(self, branch_url, launchpad=None, **kwargs):
418+ for key, value in kwargs.iteritems():
419+ self.config.set('Tarmac', key, value)
420+
421+ set_up(self.logger, debug=self.config.debug,
422+ http_debug=self.config.http_debug)
423+
424+ self.launchpad = launchpad
425+ if self.launchpad is None:
426+ self.logger.debug('Loading launchpad object')
427+ self.launchpad = self.get_launchpad_object()
428+ self.logger.debug('launchpad object loaded')
429+
430+ self.logger.debug('%(branch_url)s specified as branch_url' % {
431+ 'branch_url': branch_url})
432+ if not branch_url.startswith('lp:'):
433+ raise TarmacCommandError('Branch urls must start with lp:')
434+ ret = self._any_merges(branch_url)
435+ if ret:
436+ return 0
437+ return 1
438
439=== modified file 'tarmac/bin/options.py'
440--- tarmac/bin/options.py 2012-05-26 04:14:45 +0000
441+++ tarmac/bin/options.py 2013-03-13 16:57:22 +0000
442@@ -15,6 +15,15 @@
443 'imply-commit-message',
444 help=("Use the description as a commit message if the branch "
445 "doesn't have a message"))
446-one_option = Option(
447- 'one', short_name='1',
448- help='Merge only one branch and exit.')
449+<<<<<<< TREE
450+one_option = Option(
451+ 'one', short_name='1',
452+ help='Merge only one branch and exit.')
453+=======
454+one_option = Option(
455+ 'one', short_name='1',
456+ help='Merge only one branch and exit.')
457+verify_command_option = Option(
458+ 'verify-command', type=str,
459+ help='The verify command to run.')
460+>>>>>>> MERGE-SOURCE
461
462=== modified file 'tarmac/bin/registry.py'
463--- tarmac/bin/registry.py 2010-10-25 20:20:18 +0000
464+++ tarmac/bin/registry.py 2013-03-13 16:57:22 +0000
465@@ -45,7 +45,7 @@
466
467 def _run(self, args):
468 '''Execute the command.'''
469- run_bzr(args)
470+ return run_bzr(args)
471
472 def install_hooks(self):
473 '''Use the bzrlib Command support for running commands.'''
474@@ -57,7 +57,7 @@
475 def run(self, args):
476 '''Execute the command.'''
477 try:
478- self._run(args)
479+ return self._run(args)
480 except BzrCommandError, e:
481 sys.exit('tarmac: ERROR: ' + str(e))
482
483
484=== modified file 'tarmac/branch.py'
485--- tarmac/branch.py 2013-02-04 21:07:51 +0000
486+++ tarmac/branch.py 2013-03-13 16:57:22 +0000
487@@ -37,7 +37,6 @@
488
489 def __init__(self, lp_branch, config=False, target=None):
490 self.lp_branch = lp_branch
491- self.bzr_branch = bzr_branch.Branch.open(self.lp_branch.bzr_identity)
492 if config:
493 self.config = BranchConfig(lp_branch.bzr_identity, config)
494 else:
495@@ -45,15 +44,27 @@
496
497 self.target = target
498 self.logger = logging.getLogger('tarmac')
499+ self.temp_tree_dir = None
500+
501+ def get_bzr_branch(self):
502+ return bzr_branch.Branch.open(self.lp_branch.bzr_identity)
503+
504+ # For backwards compatibility
505+ bzr_branch = property(get_bzr_branch)
506+
507+ def get_tree(self):
508+ if self.temp_tree_dir is not None:
509+ return WorkingTree.open(self.temp_tree_dir)
510+ if os.path.exists(self.config.tree_dir):
511+ return WorkingTree.open(self.config.tree_dir)
512+
513+ # For backwards compatibility
514+ tree = property(get_tree)
515
516 def __del__(self):
517 """Do some potenetially necessary cleanup during deletion."""
518- try:
519- # If we were using a temp directory, then remove it
520+ if self.temp_tree_dir is not None:
521 shutil.rmtree(self.temp_tree_dir)
522- except AttributeError:
523- # Not using a tempdir
524- pass
525
526 @classmethod
527 def create(cls, lp_branch, config, create_tree=False, target=None):
528@@ -62,12 +73,25 @@
529 clazz.create_tree()
530 return clazz
531
532+
533 def create_tree(self):
534 '''Create the dir and working tree.'''
535+ bzr_branch = self.get_bzr_branch()
536 try:
537+ tree = self.get_tree()
538+ if tree is None:
539+ self.logger.debug('Tree does not exist. Creating dir')
540+ # Create the path up to but not including tree_dir if it does
541+ # not exist.
542+ parent_dir = os.path.dirname(self.config.tree_dir)
543+ if not os.path.exists(parent_dir):
544+ os.makedirs(parent_dir)
545+ tree = bzr_branch.create_checkout(
546+ self.config.tree_dir, lightweight=False)
547 self.logger.debug(
548 'Using tree in %(tree_dir)s' % {
549 'tree_dir': self.config.tree_dir})
550+<<<<<<< TREE
551 if os.path.exists(self.config.tree_dir):
552 self.tree = WorkingTree.open(self.config.tree_dir)
553 else:
554@@ -79,33 +103,43 @@
555 os.makedirs(parent_dir)
556 self.tree = self.bzr_branch.create_checkout(
557 self.config.tree_dir, lightweight=True)
558+=======
559+>>>>>>> MERGE-SOURCE
560 except AttributeError:
561 # Store this so we can rmtree later
562 self.temp_tree_dir = tempfile.mkdtemp()
563 self.logger.debug(
564 'Using temp dir at %(tree_dir)s' % {
565 'tree_dir': self.temp_tree_dir})
566- self.tree = self.bzr_branch.create_checkout(self.temp_tree_dir)
567+ tree = bzr_branch.create_checkout(self.temp_tree_dir)
568
569 self.cleanup()
570
571 def cleanup(self):
572 '''Remove the working tree from the temp dir.'''
573- assert self.tree
574- self.tree.revert()
575- for filename in [self.tree.abspath(f) for f in self.unmanaged_files]:
576+ tree = self.get_tree()
577+ assert tree
578+ self.logger.info("Running cleanup in %s." % (
579+ self.lp_branch.bzr_identity))
580+ tree.revert()
581+ self.logger.info("Reverted changes in %s." % (
582+ self.lp_branch.bzr_identity))
583+ for filename in [tree.abspath(f) for f in self.unmanaged_files]:
584 if os.path.isdir(filename) and not os.path.islink(filename):
585 shutil.rmtree(filename)
586 else:
587 os.remove(filename)
588
589- self.tree.update()
590+ self.logger.info("Successfully removed extra files from %s." % (
591+ self.lp_branch.bzr_identity))
592+ tree.update()
593
594 def merge(self, branch, revid=None):
595 '''Merge from another tarmac.branch.Branch instance.'''
596- assert self.tree
597- conflict_list = self.tree.merge_from_branch(
598- branch.bzr_branch, to_revision=revid)
599+ tree = self.get_tree()
600+ assert tree
601+ conflict_list = tree.merge_from_branch(
602+ branch.get_bzr_branch(), to_revision=revid)
603 if conflict_list:
604 message = u'Conflicts merging branch.'
605 lp_comment = (
606@@ -118,6 +152,7 @@
607 @property
608 def unmanaged_files(self):
609 """Get the list of ignored and unknown files in the tree."""
610+<<<<<<< TREE
611 unmanaged = []
612 try:
613 self.tree.lock_read()
614@@ -125,20 +160,31 @@
615 unmanaged.extend([x[0] for x in self.tree.ignored_files()])
616 finally:
617 self.tree.unlock()
618+=======
619+ tree = self.get_tree()
620+ assert tree
621+ tree.lock_read()
622+ unmanaged = [x for x in tree.unknowns()]
623+ unmanaged.extend([x[0] for x in tree.ignored_files()])
624+ tree.unlock()
625+>>>>>>> MERGE-SOURCE
626 return unmanaged
627
628 @property
629 def conflicts(self):
630 '''Print the conflicts.'''
631- assert self.tree.conflicts()
632+ tree = self.get_tree()
633+ assert tree
634 conflicts = []
635- for conflict in self.tree.conflicts():
636+ for conflict in tree.conflicts():
637 conflicts.append(
638 u'%s in %s' % (conflict.typestring, conflict.path))
639 return '\n'.join(conflicts)
640
641 def commit(self, commit_message, revprops=None, **kwargs):
642 '''Commit changes.'''
643+ tree = self.get_tree()
644+ assert tree
645 if not revprops:
646 revprops = {}
647
648@@ -155,8 +201,8 @@
649 'review identity or vote.')
650 revprops['reviews'] = '\n'.join(reviews)
651
652- self.tree.commit(commit_message, committer='Tarmac',
653- revprops=revprops, authors=authors)
654+ tree.commit(commit_message, committer='Tarmac',
655+ revprops=revprops, authors=authors)
656
657 @property
658 def landing_candidates(self):
659@@ -167,7 +213,9 @@
660 def authors(self):
661 author_list = []
662
663+ bzr_branch = self.get_bzr_branch()
664 if self.target:
665+<<<<<<< TREE
666 try:
667 self.bzr_branch.lock_read()
668 self.target.bzr_branch.lock_read()
669@@ -190,10 +238,33 @@
670 finally:
671 self.target.bzr_branch.unlock()
672 self.bzr_branch.unlock()
673+=======
674+ target_bzr_branch = self.target.get_bzr_branch()
675+ bzr_branch.lock_read()
676+ target_bzr_branch.lock_read()
677+
678+ graph = bzr_branch.repository.get_graph(
679+ target_bzr_branch.repository)
680+
681+ unique_ids = graph.find_unique_ancestors(
682+ bzr_branch.last_revision(),
683+ [target_bzr_branch.last_revision()])
684+
685+ revs = bzr_branch.repository.get_revisions(unique_ids)
686+ for rev in revs:
687+ apparent_authors = rev.get_apparent_authors()
688+ for author in apparent_authors:
689+ author.replace('\n', '')
690+ if author not in author_list:
691+ author_list.append(author)
692+
693+ target_bzr_branch.unlock()
694+ bzr_branch.unlock()
695+>>>>>>> MERGE-SOURCE
696 else:
697- last_rev = self.bzr_branch.last_revision()
698+ last_rev = bzr_branch.last_revision()
699 if last_rev != 'null:':
700- rev = self.bzr_branch.repository.get_revision(last_rev)
701+ rev = bzr_branch.repository.get_revision(last_rev)
702 apparent_authors = rev.get_apparent_authors()
703 author_list.extend(
704 [a.replace('\n', '') for a in apparent_authors])
705@@ -205,6 +276,7 @@
706 """Return the list of bugs fixed by the branch."""
707 bugs_list = []
708
709+<<<<<<< TREE
710 try:
711 self.bzr_branch.lock_read()
712 oldrevid = self.bzr_branch.get_rev_id(self.lp_branch.revision_count)
713@@ -218,7 +290,26 @@
714 'https://launchpad.net/bugs/', ''))
715 except NoSuchRevision:
716 continue
717+=======
718+ bzr_branch = self.get_bzr_branch()
719+ bzr_branch.lock_read()
720+ oldrevid = bzr_branch.get_rev_id(self.lp_branch.revision_count)
721+ for rev_info in bzr_branch.iter_merge_sorted_revisions(
722+ stop_revision_id=oldrevid):
723+ try:
724+ rev = bzr_branch.repository.get_revision(rev_info[0])
725+ for bug in rev.iter_bugs():
726+ if bug[0].startswith('https://launchpad.net/bugs/'):
727+ bugs_list.append(bug[0].replace(
728+ 'https://launchpad.net/bugs/', ''))
729+ except NoSuchRevision:
730+ continue
731+>>>>>>> MERGE-SOURCE
732
733+<<<<<<< TREE
734 finally:
735 self.bzr_branch.unlock()
736+=======
737+ bzr_branch.unlock()
738+>>>>>>> MERGE-SOURCE
739 return bugs_list
740
741=== modified file 'tarmac/plugins/command.py'
742--- tarmac/plugins/command.py 2011-09-02 04:06:25 +0000
743+++ tarmac/plugins/command.py 2013-03-13 16:57:22 +0000
744@@ -103,7 +103,8 @@
745 shell=True,
746 stdin=subprocess.PIPE,
747 stdout=subprocess.PIPE,
748- stderr=subprocess.PIPE)
749+ stderr=subprocess.PIPE,
750+ preexec_fn=os.setsid)
751 proc.stdin.close()
752 stdout = tempfile.TemporaryFile()
753 stderr = tempfile.TemporaryFile()
754@@ -125,7 +126,7 @@
755 killem(proc.pid, signal.SIGTERM)
756 time.sleep(5)
757
758- if proc.poll() is not None:
759+ if proc.poll() is None:
760 self.logger.debug("SIGTERM did not work. Sending SIGKILL.")
761 killem(proc.pid, signal.SIGKILL)
762
763@@ -184,7 +185,7 @@
764 u'%(output)s') % {
765 'source': self.proposal.source_branch.display_name,
766 'target': self.proposal.target_branch.display_name,
767- 'output': u'\n'.join([stdout_value, stderr_value]),
768+ 'output': u'\n'.join([stdout_value.decode('utf-8', 'ignore'), stderr_value.decode('utf-8', 'ignore')]),
769 }
770 raise VerifyCommandFailed(message, comment)
771
772
773=== modified file 'tarmac/plugins/commitmessage.py'
774--- tarmac/plugins/commitmessage.py 2010-10-25 21:27:39 +0000
775+++ tarmac/plugins/commitmessage.py 2013-03-13 16:57:22 +0000
776@@ -30,18 +30,17 @@
777
778 def run(self, command, target, source, proposal):
779 # pylint: disable-msg=W0613
780-
781 try:
782- template = target.config.commit_message_template
783- template = template.replace('<', '%(').replace('>', ')s')
784+ proposal.commit_message = self.render(
785+ target.config.commit_message_template,
786+ CommitMessageTemplateInfo(proposal))
787 except AttributeError:
788 return
789
790- proposal.commit_message = self.render(
791- template, CommitMessageTemplateInfo(proposal))
792-
793 def render(self, template, info):
794 """Render a template using the given information."""
795+ template = template.replace('<', '%(').replace('>', ')s')
796+ template = template.replace('\\n', '\n')
797 return template % info
798
799
800@@ -77,6 +76,11 @@
801 return self._proposal.commit_message
802
803 @property
804+ def branch_name(self):
805+ """The branch name under review."""
806+ return self._proposal.source_branch.name
807+
808+ @property
809 def reviewer(self):
810 """The display name of the merge proposal reviewer.
811
812
813=== modified file 'tarmac/plugins/tests/test_commitmessage.py'
814--- tarmac/plugins/tests/test_commitmessage.py 2010-10-25 21:27:39 +0000
815+++ tarmac/plugins/tests/test_commitmessage.py 2013-03-13 16:57:22 +0000
816@@ -12,6 +12,7 @@
817 super(TestCommitMessageTemplateInfo, self).setUp()
818 self.proposal = Thing(
819 source_branch=Thing(
820+ name="name",
821 owner=Thing(display_name="Arthur Author", name="arthur"),
822 linked_bugs=[Thing(id=1234), Thing(id=5678)]),
823 commit_message="Awesome",
824@@ -83,8 +84,8 @@
825 return "{info:%s}" % name
826
827
828-class TestCommitMessageTemplate(TarmacTestCase):
829-
830+class TestCommitMessageTemplate(TestCommitMessageTemplateInfo):
831+
832 def test_render(self):
833 message_template = CommitMessageTemplate()
834 message_info = FakeCommitMessageTemplateInfo()
835@@ -99,3 +100,7 @@
836 self.assertEqual(
837 "{info:author} {info:reviewer}",
838 render("%(author)s %(reviewer)s", message_info))
839+ self.assertEqual(
840+ "{info:author} {info:branch_name} {info:reviewer}",
841+ render("<author> <branch_name> <reviewer>", message_info))
842+ self.assertEqual("one\ntwo", render("one\\ntwo", message_info))
843
844=== modified file 'tarmac/tests/mock.py'
845--- tarmac/tests/mock.py 2010-11-19 18:11:56 +0000
846+++ tarmac/tests/mock.py 2013-03-13 16:57:22 +0000
847@@ -50,6 +50,8 @@
848 self.revision_count = 0
849 self.bzr_identity = 'lp:%s' % os.path.basename(self.tree_dir)
850 self.project = MockLPProject()
851+ self.web_link = 'http://code.launchpad.net/+branch/%s' % (
852+ os.path.basename(self.tree_dir),)
853
854
855 class cmd_mock(TarmacCommand):
856
857=== modified file 'tarmac/tests/test_branch.py'
858--- tarmac/tests/test_branch.py 2012-06-29 20:03:55 +0000
859+++ tarmac/tests/test_branch.py 2013-03-13 16:57:22 +0000
860@@ -38,7 +38,25 @@
861 a_branch = branch.Branch.create(MockLPBranch(tree_dir), self.config)
862 self.assertTrue(isinstance(a_branch, branch.Branch))
863 self.assertTrue(a_branch.lp_branch.bzr_identity is not None)
864- self.assertFalse(hasattr(a_branch, 'tree'))
865+ self.remove_branch_config(tree_dir)
866+
867+ def test_create_missing_parent_dir(self):
868+ '''Test the creation of a TarmacBranch instance in a path that does
869+ not fully exist, with a tree'''
870+ branch_name = 'test_branch'
871+ parent_dir = os.path.join(self.TEST_ROOT, 'missing')
872+ tree_dir = os.path.join(parent_dir, branch_name)
873+ self.add_branch_config(tree_dir)
874+ # Create the mock somewhere other than where the tarmac branch will be
875+ # located. Keep it right under TEST_ROOT so the
876+ # TarmacDirectoryFactory mocking will work.
877+ mock = MockLPBranch(os.path.join(self.TEST_ROOT, branch_name))
878+ self.assertFalse(os.path.exists(parent_dir))
879+ a_branch = branch.Branch.create(mock, self.config, create_tree=True)
880+ self.assertTrue(os.path.exists(parent_dir))
881+ self.assertTrue(isinstance(a_branch, branch.Branch))
882+ self.assertTrue(a_branch.lp_branch.bzr_identity is not None)
883+ self.assertTrue(hasattr(a_branch, 'tree'))
884 self.remove_branch_config(tree_dir)
885
886 def test_create_missing_parent_dir(self):
887@@ -66,19 +84,6 @@
888 self.assertTrue(self.branch1.lp_branch.bzr_identity is not None)
889 self.assertTrue(hasattr(self.branch1, 'tree'))
890
891- def test_merge_raises_exception_with_no_tree(self):
892- '''A merge on a branch with no tree will raise an exception.'''
893- branch3_dir = os.path.join(self.TEST_ROOT, 'branch3')
894- self.add_branch_config(branch3_dir)
895- branch3 = branch.Branch.create(MockLPBranch(
896- branch3_dir, source_branch=self.branch1.lp_branch),
897- self.config)
898-
899- self.assertRaises(
900- AttributeError, branch3.merge, self.branch2)
901- self.remove_branch_config(branch3_dir)
902- shutil.rmtree(branch3_dir)
903-
904 def test_merge_no_changes(self):
905 '''A merge on a branch with a tree will raise an exception if no
906 changes are present.'''
907
908=== modified file 'tarmac/tests/test_commands.py'
909--- tarmac/tests/test_commands.py 2012-06-29 20:03:55 +0000
910+++ tarmac/tests/test_commands.py 2013-03-13 16:57:22 +0000
911@@ -1,5 +1,6 @@
912 '''Tests for tarmac.bin.commands.py.'''
913 from cStringIO import StringIO
914+import logging
915 import os
916 import shutil
917 import sys
918@@ -105,15 +106,20 @@
919 display_name=self.branch2.lp_branch.bzr_identity,
920 name='source',
921 revision_count=self.branch2.lp_branch.revision_count,
922- landing_candidates=[]),
923+ landing_candidates=[],
924+ linked_bugs=[],
925+ web_link=u'http://launchpad.net/branches/source'),
926 Thing(
927 bzr_identity=self.branch1.lp_branch.bzr_identity,
928 display_name=self.branch1.lp_branch.bzr_identity,
929 name='target',
930 revision_count=self.branch1.lp_branch.revision_count,
931- landing_candidates=None)]
932+ landing_candidates=None,
933+ linked_bugs=[],
934+ web_link=u'http://launchpad.net/branches/target')]
935 self.proposals = [Thing(
936 self_link=u'http://api.edge.launchpad.net/devel/proposal0',
937+ web_link=u'http://edge.launchpad.net/proposal0',
938 queue_status=u'Needs Review',
939 commit_message=u'Commitable.',
940 source_branch=self.branches[0],
941@@ -128,6 +134,7 @@
942 reviewer=Thing(display_name=u'Reviewer'))]),
943 Thing(
944 self_link=u'https://api.launchpad.net/1.0/proposal1',
945+ web_link=u'https://launchpad.net/proposal1',
946 queue_status=u'Approved',
947 commit_message=u'Commit this.',
948 source_branch=self.branches[0],
949@@ -137,6 +144,7 @@
950 setStatus=self.lp_save,
951 lp_save=self.lp_save,
952 reviewed_revid=None,
953+ reviewer=Thing(name=u'reviewer', display_name=u'Reviewer'),
954 votes=[Thing(
955 comment=Thing(vote=u'Approve'),
956 reviewer=Thing(display_name=u'Reviewer')),
957@@ -145,7 +153,8 @@
958 reviewer=Thing(display_name=u'Reviewer2'))])]
959 self.branches[1].landing_candidates = self.proposals
960
961- self.launchpad = Thing(branches=Thing(getByUrl=self.getBranchByUrl))
962+ self.launchpad = Thing(branches=Thing(getByUrl=self.getBranchByUrl),
963+ me=None)
964 self.error = None
965 registry = CommandRegistry(config=self.config)
966 registry.register_command('merge', commands.cmd_merge)
967@@ -167,6 +176,20 @@
968 except IndexError:
969 return None
970
971+ def test__get_branch_exists(self):
972+ # _get_branch returns the lp_branch if it exists.
973+ branch = commands._get_branch(
974+ self.branch2.lp_branch.bzr_identity,
975+ self.launchpad,
976+ logging.getLogger('tarmac'))
977+ self.assertIs(self.branches[0], branch)
978+
979+ def test__get_branch_doesnt_exist(self):
980+ # _get_branch returns None if it doesn't exist.
981+ branch = commands._get_branch(
982+ 'doesntexist', self.launchpad, logging.getLogger('tarmac'))
983+ self.assertIs(None, branch)
984+
985 def test_run(self):
986 """Test that the merge command merges a branch successfully."""
987 self.proposals[1].reviewed_revid = \
988@@ -191,10 +214,12 @@
989
990 def test_get_reviews(self):
991 """Test that the _get_reviews method gives the right lists."""
992- self.assertEqual(self.command._get_reviews(self.proposals[0]),
993- [u'Reviewer;Needs Fixing'])
994- self.assertEqual(self.command._get_reviews(self.proposals[1]),
995- [u'Reviewer;Approve', u'Reviewer2;Abstain'])
996+ self.assertEqual(
997+ commands._get_reviews(self.proposals[0]),
998+ [u'Reviewer;Needs Fixing'])
999+ self.assertEqual(
1000+ commands._get_reviews(self.proposals[1]),
1001+ [u'Reviewer;Approve', u'Reviewer2;Abstain'])
1002
1003 def test_run_merge_url_substitution(self):
1004 """Test that the merge urls get substituted correctly."""
1005@@ -212,6 +237,7 @@
1006 # Make a new commit, approve the propsoal, merge, and verify
1007 self.branch2.commit('New commit to merge.')
1008 self.proposals[0].queue_status = u'Approved'
1009+ self.proposals[0].reviewer = Thing(name=u'reviewer')
1010 self.proposals[0].reviewed_revid = \
1011 self.branch2.bzr_branch.last_revision()
1012 self.command.run(launchpad=self.launchpad)
1013@@ -242,6 +268,7 @@
1014 branch3.lp_branch.landing_candidates = []
1015 b3_proposal = Thing(
1016 self_link=u'http://api.edge.launchpad.net/devel/proposal3',
1017+ web_link=u'http://edge.launchpad.net/proposal3',
1018 queue_status=u'Work in Progress',
1019 commit_message=u'Commitable.',
1020 source_branch=branch3.lp_branch,
1021@@ -264,8 +291,9 @@
1022 self.command.run(launchpad=self.launchpad)
1023 shutil.rmtree(branch3_dir)
1024 self.assertEqual(self.error.comment,
1025- u'The prerequisite lp:branch3 has not yet been '
1026- u'merged into lp:branch1.')
1027+ u'The prerequisite '
1028+ u'http://code.launchpad.net/+branch/branch3 has not '
1029+ u'yet been merged into http://launchpad.net/branches/target.')
1030
1031 def test_run_merge_with_unproposed_prerequisite_fails(self):
1032 """Test that mereging a branch with an unmerged prerequisite fails."""
1033@@ -289,6 +317,7 @@
1034 branch3.lp_branch.landing_candidates = []
1035 b3_proposal = Thing(
1036 self_link=u'http://api.edge.launchpad.net/devel/proposal3',
1037+ web_link=u'http://edge.launchpadnet/proposal3',
1038 queue_status=u'Work in Progress',
1039 commit_message=u'Commitable.',
1040 source_branch=branch3.lp_branch,
1041@@ -311,8 +340,9 @@
1042 self.command.run(launchpad=self.launchpad)
1043 shutil.rmtree(branch3_dir)
1044 self.assertEqual(self.error.comment,
1045- u'No proposals found for merge of lp:branch3 '
1046- u'into lp:branch1.')
1047+ u'No proposals found for merge of '
1048+ u'http://code.launchpad.net/+branch/branch3 '
1049+ u'into http://launchpad.net/branches/target.')
1050
1051 def test_run_merge_with_prerequisite_with_multiple_proposals_fails(self):
1052 """Test that mereging a branch with an unmerged prerequisite fails."""
1053@@ -336,6 +366,7 @@
1054 branch3.lp_branch.landing_candidates = []
1055 b3_proposal = Thing(
1056 self_link=u'http://api.edge.launchpad.net/devel/proposal3',
1057+ web_link=u'http://edge.launchpadnet/proposal3',
1058 queue_status=u'Work in Progress',
1059 commit_message=u'Commitable.',
1060 source_branch=branch3.lp_branch,
1061@@ -363,5 +394,6 @@
1062 shutil.rmtree(branch3_dir)
1063 self.assertEqual(self.error.comment,
1064 u'More than one proposal found for merge of '
1065- u'lp:branch3 into lp:branch1, which is not '
1066- u'Superseded.')
1067+ u'http://code.launchpad.net/+branch/branch3 into '
1068+ u'http://launchpad.net/branches/target, which '
1069+ u'is not Superseded.')

Subscribers

People subscribed via source and target branches