Merge lp:~abentley/launchpad/incremental-diff-driveby into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 11592
Proposed branch: lp:~abentley/launchpad/incremental-diff-driveby
Merge into: lp:launchpad
Diff against target: 676 lines (+193/-130)
13 files modified
lib/lp/code/browser/branchmergeproposal.py (+8/-42)
lib/lp/code/browser/tests/test_branchmergeproposal.py (+0/-60)
lib/lp/code/configure.zcml (+2/-0)
lib/lp/code/interfaces/branchmergeproposal.py (+9/-2)
lib/lp/code/interfaces/revision.py (+3/-0)
lib/lp/code/model/branchmergeproposal.py (+40/-1)
lib/lp/code/model/directbranchcommit.py (+5/-2)
lib/lp/code/model/revision.py (+8/-1)
lib/lp/code/model/tests/test_branchmergeproposal.py (+67/-4)
lib/lp/code/model/tests/test_diff.py (+15/-16)
lib/lp/code/tests/helpers.py (+7/-2)
lib/lp/code/tests/test_directbranchcommit.py (+18/-0)
lib/lp/codehosting/bzrutils.py (+11/-0)
To merge this branch: bzr merge lp:~abentley/launchpad/incremental-diff-driveby
Reviewer Review Type Date Requested Status
Paul Hummer (community) Approve
Review via email: mp+36156@code.launchpad.net

Commit message

Drive-bys to support incremental diffs.

Description of the change

= Summary =
A set of drivebys to support incremental diffs. Although incremental diffs
will be merged into db-devel, the drivebys will land in devel to reduce skew.

== Proposed fix ==
N/A

== Pre-implementation notes ==
Moving view code into the model was discussed with thumper.

== Implementation details ==
revision_end_date and getRevisionsSinceReviewStart are moved from the view to
the model. getRevisionsSinceReviewStart uses itertools.groupby instead of
reimplementing it. _getNewerRevisions is extracted from
getRevisionsSinceReviewStart.

bzrutils.read_locked allows branches to be locked using with statement.

DirectBranchCommit allows merge parents to be specified.
DiffTestCase.commitFile allows merge parents to be specified.

add_revision_to_branch produces realistic parent references.

Revision.getLefthandParent provides convenient access to the lefthand parent of
the revision.

Literal tab characters are replaced with \t to please lint.

== Tests ==
bin/test -v -t test_commit_uses_merge_parents -t TestGetRevisionsSinceReviewStart -t TestRevisionEndDate

== Demo and Q/A ==
None

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/code/configure.zcml
  lib/lp/code/interfaces/branchmergeproposal.py
  lib/lp/code/model/branchmergeproposal.py
  lib/lp/code/model/tests/test_branchmergeproposal.py
  lib/lp/code/tests/helpers.py
  lib/lp/code/browser/tests/test_branchmergeproposal.py
  lib/lp/code/model/directbranchcommit.py
  lib/lp/codehosting/bzrutils.py
  lib/lp/code/interfaces/revision.py
  lib/lp/code/model/tests/test_diff.py
  lib/lp/code/browser/branchmergeproposal.py
  lib/lp/code/tests/test_directbranchcommit.py
  lib/lp/code/model/revision.py

./lib/lp/code/interfaces/branchmergeproposal.py
     715: E302 expected 2 blank lines, found 1
./lib/lp/code/model/tests/test_branchmergeproposal.py
     192: E231 missing whitespace after ','
     194: E231 missing whitespace after ','
./lib/lp/codehosting/bzrutils.py
     165: E301 expected 1 blank line, found 0
     343: E301 expected 1 blank line, found 0
./lib/lp/code/browser/branchmergeproposal.py
     168: E301 expected 1 blank line, found 0
     178: E301 expected 1 blank line, found 0
     201: E202 whitespace before '}'
    1441: E301 expected 1 blank line, found 0

To post a comment you must log in.
Revision history for this message
Paul Hummer (rockstar) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/branchmergeproposal.py'
2--- lib/lp/code/browser/branchmergeproposal.py 2010-08-24 10:45:57 +0000
3+++ lib/lp/code/browser/branchmergeproposal.py 2010-09-21 15:06:00 +0000
4@@ -32,7 +32,6 @@
5 'latest_proposals_for_each_branch',
6 ]
7
8-from collections import defaultdict
9 import operator
10
11 from lazr.delegates import delegates
12@@ -200,7 +199,7 @@
13 'Approved [Merge Failed]',
14 BranchMergeProposalStatus.QUEUED : 'Queued',
15 BranchMergeProposalStatus.SUPERSEDED : 'Superseded'
16- }
17+ }
18 return friendly_texts[self.context.queue_status]
19
20 @property
21@@ -212,8 +211,7 @@
22 result = ''
23 if self.context.queue_status in (
24 BranchMergeProposalStatus.CODE_APPROVED,
25- BranchMergeProposalStatus.REJECTED
26- ):
27+ BranchMergeProposalStatus.REJECTED):
28 formatter = DateTimeFormatterAPI(self.context.date_reviewed)
29 result = '%s %s' % (
30 self.context.reviewer.displayname,
31@@ -601,46 +599,15 @@
32 """Location of page for commenting on this proposal."""
33 return canonical_url(self.context, view_name='+comment')
34
35- @property
36- def revision_end_date(self):
37- """The cutoff date for showing revisions.
38-
39- If the proposal has been merged, then we stop at the merged date. If
40- it is rejected, we stop at the reviewed date. For superseded
41- proposals, it should ideally use the non-existant date_last_modified,
42- but could use the last comment date.
43- """
44- status = self.context.queue_status
45- if status == BranchMergeProposalStatus.MERGED:
46- return self.context.date_merged
47- if status == BranchMergeProposalStatus.REJECTED:
48- return self.context.date_reviewed
49- # Otherwise return None representing an open end date.
50- return None
51-
52- def _getRevisionsSinceReviewStart(self):
53- """Get the grouped revisions since the review started."""
54- # Work out the start of the review.
55- start_date = self.context.date_review_requested
56- if start_date is None:
57- start_date = self.context.date_created
58- source = DecoratedBranch(self.context.source_branch)
59- resultset = source.getMainlineBranchRevisions(
60- start_date, self.revision_end_date, oldest_first=True)
61- # Now group by date created.
62- groups = defaultdict(list)
63- for branch_revision, revision, revision_author in resultset:
64- groups[revision.date_created].append(branch_revision)
65- return [
66- CodeReviewNewRevisions(revisions, date, source)
67- for date, revisions in groups.iteritems()]
68-
69 @cachedproperty
70 def conversation(self):
71 """Return a conversation that is to be rendered."""
72 # Sort the comments by date order.
73- comments = self._getRevisionsSinceReviewStart()
74 merge_proposal = self.context
75+ groups = merge_proposal.getRevisionsSinceReviewStart()
76+ source = DecoratedBranch(merge_proposal.source_branch)
77+ comments = [CodeReviewNewRevisions(list(revisions), date, source)
78+ for date, revisions in groups]
79 while merge_proposal is not None:
80 from_superseded = merge_proposal != self.context
81 comments.extend(
82@@ -946,7 +913,6 @@
83 self.cancel_url = self.next_url
84 super(MergeProposalEditView, self).initialize()
85
86-
87 def _getRevisionId(self, data):
88 """Translate the revision number that was entered into a revision id.
89
90@@ -1473,8 +1439,8 @@
91 """Render an `IText` as XHTML using the webservice."""
92 formatter = FormattersAPI
93 def renderer(value):
94- nomail = formatter(value).obfuscate_email()
95- html = formatter(nomail).text_to_html()
96+ nomail = formatter(value).obfuscate_email()
97+ html = formatter(nomail).text_to_html()
98 return html.encode('utf-8')
99 return renderer
100
101
102=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
103--- lib/lp/code/browser/tests/test_branchmergeproposal.py 2010-08-22 21:34:16 +0000
104+++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2010-09-21 15:06:00 +0000
105@@ -12,7 +12,6 @@
106 timedelta,
107 )
108 from difflib import unified_diff
109-import operator
110 import unittest
111
112 import pytz
113@@ -46,7 +45,6 @@
114 PreviewDiff,
115 StaticDiff,
116 )
117-from lp.code.tests.helpers import add_revision_to_branch
118 from lp.testing import (
119 login_person,
120 TestCaseWithFactory,
121@@ -577,63 +575,6 @@
122 view = create_initialized_view(self.bmp, '+index')
123 self.assertEqual([], view.linked_bugs)
124
125- def test_revision_end_date_active(self):
126- # An active merge proposal will have None as an end date.
127- bmp = self.factory.makeBranchMergeProposal()
128- view = create_initialized_view(bmp, '+index')
129- self.assertIs(None, view.revision_end_date)
130-
131- def test_revision_end_date_merged(self):
132- # An merged proposal will have the date merged as an end date.
133- bmp = self.factory.makeBranchMergeProposal(
134- set_state=BranchMergeProposalStatus.MERGED)
135- view = create_initialized_view(bmp, '+index')
136- self.assertEqual(bmp.date_merged, view.revision_end_date)
137-
138- def test_revision_end_date_rejected(self):
139- # An rejected proposal will have the date reviewed as an end date.
140- bmp = self.factory.makeBranchMergeProposal(
141- set_state=BranchMergeProposalStatus.REJECTED)
142- view = create_initialized_view(bmp, '+index')
143- self.assertEqual(bmp.date_reviewed, view.revision_end_date)
144-
145- def assertRevisionGroups(self, bmp, expected_groups):
146- """Get the groups for the merge proposal and check them."""
147- view = create_initialized_view(bmp, '+index')
148- groups = view._getRevisionsSinceReviewStart()
149- view_groups = [
150- obj.revisions for obj in sorted(
151- groups, key=operator.attrgetter('date'))]
152- self.assertEqual(expected_groups, view_groups)
153-
154- def test_getRevisionsSinceReviewStart_no_revisions(self):
155- # If there have been no revisions pushed since the start of the
156- # review, the method returns an empty list.
157- self.assertRevisionGroups(self.bmp, [])
158-
159- def test_getRevisionsSinceReviewStart_groups(self):
160- # Revisions that were scanned at the same time have the same
161- # date_created. These revisions are grouped together.
162- review_date = datetime(2009, 9, 10, tzinfo=pytz.UTC)
163- bmp = self.factory.makeBranchMergeProposal(
164- date_created=review_date)
165- login_person(bmp.registrant)
166- bmp.requestReview(review_date)
167- revision_date = review_date + timedelta(days=1)
168- revisions = []
169- for date in range(2):
170- revisions.append(
171- add_revision_to_branch(
172- self.factory, bmp.source_branch, revision_date))
173- revisions.append(
174- add_revision_to_branch(
175- self.factory, bmp.source_branch, revision_date))
176- revision_date += timedelta(days=1)
177- expected_groups = [
178- [revisions[0], revisions[1]],
179- [revisions[2], revisions[3]]]
180- self.assertRevisionGroups(bmp, expected_groups)
181-
182 def test_include_superseded_comments(self):
183 for x, time in zip(range(3), time_counter()):
184 if x != 0:
185@@ -757,7 +698,6 @@
186
187 layer = LaunchpadFunctionalLayer
188
189-
190 def _makeCommentFromEmailWithAttachment(self, attachment_body):
191 # Make an email message with an attachment, and create a code
192 # review comment from it.
193
194=== modified file 'lib/lp/code/configure.zcml'
195--- lib/lp/code/configure.zcml 2010-09-20 22:16:32 +0000
196+++ lib/lp/code/configure.zcml 2010-09-21 15:06:00 +0000
197@@ -236,8 +236,10 @@
198 votes
199 all_comments
200 related_bugs
201+ revision_end_date
202 isMergable
203 getComment
204+ getRevisionsSinceReviewStart
205 getNotificationRecipients
206 getVoteReference
207 isValidTransition
208
209=== modified file 'lib/lp/code/interfaces/branchmergeproposal.py'
210--- lib/lp/code/interfaces/branchmergeproposal.py 2010-08-20 20:31:18 +0000
211+++ lib/lp/code/interfaces/branchmergeproposal.py 2010-09-21 15:06:00 +0000
212@@ -290,6 +290,13 @@
213 def getComment(id):
214 """Return the CodeReviewComment with the specified ID."""
215
216+ def getRevisionsSinceReviewStart():
217+ """Return all the revisions added since the review began.
218+
219+ Revisions are grouped by creation (i.e. push) time.
220+ :return: An iterator of (date, iterator of revision data)
221+ """
222+
223 def getVoteReference(id):
224 """Return the CodeReviewVoteReference with the specified ID."""
225
226@@ -518,8 +525,8 @@
227 source branch.
228 :param target_revision_id: The revision id that was used from the
229 target branch.
230- :param prerequisite_revision_id: The revision id that was used from the
231- prerequisite branch.
232+ :param prerequisite_revision_id: The revision id that was used from
233+ the prerequisite branch.
234 :param conflicts: Text describing the conflicts if any.
235 """
236
237
238=== modified file 'lib/lp/code/interfaces/revision.py'
239--- lib/lp/code/interfaces/revision.py 2010-08-20 20:31:18 +0000
240+++ lib/lp/code/interfaces/revision.py 2010-09-21 15:06:00 +0000
241@@ -73,6 +73,9 @@
242 :return: A `Branch` or None if an appropriate branch cannot be found.
243 """
244
245+ def getLefthandParent():
246+ """Return lefthand parent of revision, or None if not in database."""
247+
248
249 class IRevisionAuthor(Interface):
250 """Committer of a Bazaar revision."""
251
252=== modified file 'lib/lp/code/model/branchmergeproposal.py'
253--- lib/lp/code/model/branchmergeproposal.py 2010-08-20 20:31:18 +0000
254+++ lib/lp/code/model/branchmergeproposal.py 2010-09-21 15:06:00 +0000
255@@ -13,7 +13,7 @@
256 ]
257
258 from email.Utils import make_msgid
259-
260+from itertools import groupby
261 from sqlobject import (
262 ForeignKey,
263 IntCol,
264@@ -777,6 +777,45 @@
265 Store.of(self).flush()
266 return self.preview_diff
267
268+ @property
269+ def revision_end_date(self):
270+ """The cutoff date for showing revisions.
271+
272+ If the proposal has been merged, then we stop at the merged date. If
273+ it is rejected, we stop at the reviewed date. For superseded
274+ proposals, it should ideally use the non-existant date_last_modified,
275+ but could use the last comment date.
276+ """
277+ status = self.queue_status
278+ if status == BranchMergeProposalStatus.MERGED:
279+ return self.date_merged
280+ if status == BranchMergeProposalStatus.REJECTED:
281+ return self.date_reviewed
282+ # Otherwise return None representing an open end date.
283+ return None
284+
285+ def _getNewerRevisions(self):
286+ start_date = self.date_review_requested
287+ if start_date is None:
288+ start_date = self.date_created
289+ return self.source_branch.getMainlineBranchRevisions(
290+ start_date, self.revision_end_date, oldest_first=True)
291+
292+ def getRevisionsSinceReviewStart(self):
293+ """Get the grouped revisions since the review started."""
294+ resultset = self._getNewerRevisions()
295+ # Work out the start of the review.
296+ branch_revisions = (
297+ branch_revision for branch_revision, revision, revision_author
298+ in resultset)
299+ # Now group by date created.
300+ gby = groupby(branch_revisions, lambda r: r.revision.date_created)
301+ # Use a generator expression to wrap the custom iterator so it doesn't
302+ # get security-proxied.
303+ return (
304+ (date, (revision for revision in revisions))
305+ for date, revisions in gby)
306+
307
308 class BranchMergeProposalGetter:
309 """See `IBranchMergeProposalGetter`."""
310
311=== modified file 'lib/lp/code/model/directbranchcommit.py'
312--- lib/lp/code/model/directbranchcommit.py 2010-08-20 20:31:18 +0000
313+++ lib/lp/code/model/directbranchcommit.py 2010-09-21 15:06:00 +0000
314@@ -55,7 +55,8 @@
315 is_locked = False
316 commit_builder = None
317
318- def __init__(self, db_branch, committer=None, no_race_check=False):
319+ def __init__(self, db_branch, committer=None, no_race_check=False,
320+ merge_parents=None):
321 """Create context for direct commit to branch.
322
323 Before constructing a `DirectBranchCommit`, set up a server that
324@@ -107,6 +108,7 @@
325 raise
326
327 self.files = set()
328+ self.merge_parents = merge_parents
329
330 def _getDir(self, path):
331 """Get trans_id for directory "path." Create if necessary."""
332@@ -200,7 +202,8 @@
333 # required to generate the revision-id.
334 with override_environ(BZR_EMAIL=committer_id):
335 new_rev_id = self.transform_preview.commit(
336- self.bzrbranch, commit_message, committer=committer_id)
337+ self.bzrbranch, commit_message, self.merge_parents,
338+ committer=committer_id)
339 IMasterObject(self.db_branch).branchChanged(
340 get_stacked_on_url(self.bzrbranch), new_rev_id,
341 self.db_branch.control_format, self.db_branch.branch_format,
342
343=== modified file 'lib/lp/code/model/revision.py'
344--- lib/lp/code/model/revision.py 2010-08-27 02:11:36 +0000
345+++ lib/lp/code/model/revision.py 2010-09-21 15:06:00 +0000
346@@ -18,6 +18,7 @@
347 )
348 import email
349
350+from bzrlib.revision import NULL_REVISION
351 import pytz
352 from sqlobject import (
353 BoolCol,
354@@ -39,7 +40,6 @@
355 )
356 from storm.locals import (
357 Bool,
358- DateTime,
359 Int,
360 Min,
361 Reference,
362@@ -118,6 +118,13 @@
363 """
364 return [parent.parent_id for parent in self.parents]
365
366+ def getLefthandParent(self):
367+ if len(self.parent_ids) == 0:
368+ parent_id = NULL_REVISION
369+ else:
370+ parent_id = self.parent_ids[0]
371+ return RevisionSet().getByRevisionId(parent_id)
372+
373 def getProperties(self):
374 """See `IRevision`."""
375 return dict((prop.name, prop.value) for prop in self.properties)
376
377=== modified file 'lib/lp/code/model/tests/test_branchmergeproposal.py'
378--- lib/lp/code/model/tests/test_branchmergeproposal.py 2010-08-20 20:31:18 +0000
379+++ lib/lp/code/model/tests/test_branchmergeproposal.py 2010-09-21 15:06:00 +0000
380@@ -7,7 +7,7 @@
381
382 __metaclass__ = type
383
384-from datetime import datetime
385+from datetime import datetime, timedelta
386 from difflib import unified_diff
387 from unittest import (
388 TestCase,
389@@ -72,6 +72,7 @@
390 MergeProposalCreatedJob,
391 UpdatePreviewDiffJob,
392 )
393+from lp.code.tests.helpers import add_revision_to_branch
394 from lp.registry.interfaces.person import IPersonSet
395 from lp.registry.interfaces.product import IProductSet
396 from lp.testing import (
397@@ -108,7 +109,8 @@
398 self.assertTrue(url.startswith(source_branch_url))
399
400 def test_BranchMergeProposal_canonical_url_rest(self):
401- # The rest of the URL for a merge proposal is +merge followed by the db id.
402+ # The rest of the URL for a merge proposal is +merge followed by the
403+ # db id.
404 bmp = self.factory.makeBranchMergeProposal()
405 url = canonical_url(bmp)
406 source_branch_url = canonical_url(bmp.source_branch)
407@@ -238,7 +240,6 @@
408 self._attemptTransition,
409 proposal, to_state)
410
411-
412 def assertGoodDupeTransition(self, from_state, to_state):
413 """Trying to go from `from_state` to `to_state` succeeds."""
414 proposal = self.prepareDupeTransition(from_state)
415@@ -1049,6 +1050,7 @@
416 else:
417 self.assertEqual([mp], list(active))
418
419+
420 class TestBranchMergeProposalGetterGetProposals(TestCaseWithFactory):
421 """Test the getProposalsForContext method."""
422
423@@ -1118,7 +1120,6 @@
424 beaver, [BranchMergeProposalStatus.REJECTED], beaver)
425 self.assertEqual(beave_proposals.count(), 1)
426
427-
428 def test_created_proposal_default_status(self):
429 # When we create a merge proposal using the helper method, the default
430 # status of the proposal is work in progress.
431@@ -1799,5 +1800,67 @@
432 self.assertIs(None, bmp.next_preview_diff_job)
433
434
435+class TestRevisionEndDate(TestCaseWithFactory):
436+
437+ layer = DatabaseFunctionalLayer
438+
439+ def test_revision_end_date_active(self):
440+ # An active merge proposal will have None as an end date.
441+ bmp = self.factory.makeBranchMergeProposal()
442+ self.assertIs(None, bmp.revision_end_date)
443+
444+ def test_revision_end_date_merged(self):
445+ # An merged proposal will have the date merged as an end date.
446+ bmp = self.factory.makeBranchMergeProposal(
447+ set_state=BranchMergeProposalStatus.MERGED)
448+ self.assertEqual(bmp.date_merged, bmp.revision_end_date)
449+
450+ def test_revision_end_date_rejected(self):
451+ # An rejected proposal will have the date reviewed as an end date.
452+ bmp = self.factory.makeBranchMergeProposal(
453+ set_state=BranchMergeProposalStatus.REJECTED)
454+ self.assertEqual(bmp.date_reviewed, bmp.revision_end_date)
455+
456+
457+class TestGetRevisionsSinceReviewStart(TestCaseWithFactory):
458+
459+ layer = DatabaseFunctionalLayer
460+
461+ def assertRevisionGroups(self, bmp, expected_groups):
462+ """Get the groups for the merge proposal and check them."""
463+ groups = bmp.getRevisionsSinceReviewStart()
464+ revision_groups = [list(revisions) for date, revisions in groups]
465+ self.assertEqual(expected_groups, revision_groups)
466+
467+ def test_getRevisionsSinceReviewStart_no_revisions(self):
468+ # If there have been no revisions pushed since the start of the
469+ # review, the method returns an empty list.
470+ bmp = self.factory.makeBranchMergeProposal()
471+ self.assertRevisionGroups(bmp, [])
472+
473+ def test_getRevisionsSinceReviewStart_groups(self):
474+ # Revisions that were scanned at the same time have the same
475+ # date_created. These revisions are grouped together.
476+ review_date = datetime(2009, 9, 10, tzinfo=UTC)
477+ bmp = self.factory.makeBranchMergeProposal(
478+ date_created=review_date)
479+ login_person(bmp.registrant)
480+ bmp.requestReview(review_date)
481+ revision_date = review_date + timedelta(days=1)
482+ revisions = []
483+ for date in range(2):
484+ revisions.append(
485+ add_revision_to_branch(
486+ self.factory, bmp.source_branch, revision_date))
487+ revisions.append(
488+ add_revision_to_branch(
489+ self.factory, bmp.source_branch, revision_date))
490+ revision_date += timedelta(days=1)
491+ expected_groups = [
492+ [revisions[0], revisions[1]],
493+ [revisions[2], revisions[3]]]
494+ self.assertRevisionGroups(bmp, expected_groups)
495+
496+
497 def test_suite():
498 return TestLoader().loadTestsFromName(__name__)
499
500=== modified file 'lib/lp/code/model/tests/test_diff.py'
501--- lib/lp/code/model/tests/test_diff.py 2010-08-20 20:31:18 +0000
502+++ lib/lp/code/model/tests/test_diff.py 2010-09-21 15:06:00 +0000
503@@ -57,13 +57,14 @@
504 class DiffTestCase(TestCaseWithFactory):
505
506 @staticmethod
507- def commitFile(branch, path, contents):
508+ def commitFile(branch, path, contents, merge_parents=None):
509 """Create a commit that updates a file to specified contents.
510
511 This will create or modify the file, as needed.
512 """
513 committer = DirectBranchCommit(
514- removeSecurityProxy(branch), no_race_check=True)
515+ removeSecurityProxy(branch), no_race_check=True,
516+ merge_parents=merge_parents)
517 committer.writeFile(path, contents)
518 try:
519 return committer.commit('committing')
520@@ -122,7 +123,6 @@
521 prerequisite)
522
523
524-
525 class TestDiff(DiffTestCase):
526
527 layer = LaunchpadFunctionalLayer
528@@ -186,19 +186,19 @@
529 self.checkExampleMerge(diff.text)
530
531 diff_bytes = (
532- "--- bar 2009-08-26 15:53:34.000000000 -0400\n"
533- "+++ bar 1969-12-31 19:00:00.000000000 -0500\n"
534+ "--- bar\t2009-08-26 15:53:34.000000000 -0400\n"
535+ "+++ bar\t1969-12-31 19:00:00.000000000 -0500\n"
536 "@@ -1,3 +0,0 @@\n"
537 "-a\n"
538 "-b\n"
539 "-c\n"
540- "--- baz 1969-12-31 19:00:00.000000000 -0500\n"
541- "+++ baz 2009-08-26 15:53:57.000000000 -0400\n"
542+ "--- baz\t1969-12-31 19:00:00.000000000 -0500\n"
543+ "+++ baz\t2009-08-26 15:53:57.000000000 -0400\n"
544 "@@ -0,0 +1,2 @@\n"
545 "+a\n"
546 "+b\n"
547- "--- foo 2009-08-26 15:53:23.000000000 -0400\n"
548- "+++ foo 2009-08-26 15:56:43.000000000 -0400\n"
549+ "--- foo\t2009-08-26 15:53:23.000000000 -0400\n"
550+ "+++ foo\t2009-08-26 15:56:43.000000000 -0400\n"
551 "@@ -1,3 +1,4 @@\n"
552 " a\n"
553 "-b\n"
554@@ -207,19 +207,19 @@
555 "+e\n")
556
557 diff_bytes_2 = (
558- "--- bar 2009-08-26 15:53:34.000000000 -0400\n"
559- "+++ bar 1969-12-31 19:00:00.000000000 -0500\n"
560+ "--- bar\t2009-08-26 15:53:34.000000000 -0400\n"
561+ "+++ bar\t1969-12-31 19:00:00.000000000 -0500\n"
562 "@@ -1,3 +0,0 @@\n"
563 "-a\n"
564 "-b\n"
565 "-c\n"
566- "--- baz 1969-12-31 19:00:00.000000000 -0500\n"
567- "+++ baz 2009-08-26 15:53:57.000000000 -0400\n"
568+ "--- baz\t1969-12-31 19:00:00.000000000 -0500\n"
569+ "+++ baz\t2009-08-26 15:53:57.000000000 -0400\n"
570 "@@ -0,0 +1,2 @@\n"
571 "+a\n"
572 "+b\n"
573- "--- foo 2009-08-26 15:53:23.000000000 -0400\n"
574- "+++ foo 2009-08-26 15:56:43.000000000 -0400\n"
575+ "--- foo\t2009-08-26 15:53:23.000000000 -0400\n"
576+ "+++ foo\t2009-08-26 15:56:43.000000000 -0400\n"
577 "@@ -1,3 +1,5 @@\n"
578 " a\n"
579 "-b\n"
580@@ -467,7 +467,6 @@
581 self.assertEqual('', diff.conflicts)
582 self.assertFalse(diff.has_conflicts)
583
584-
585 def test_fromBranchMergeProposal(self):
586 # Correctly generates a PreviewDiff from a BranchMergeProposal.
587 bmp, source_rev_id, target_rev_id = self.createExampleMerge()
588
589=== modified file 'lib/lp/code/tests/helpers.py'
590--- lib/lp/code/tests/helpers.py 2010-08-31 00:16:41 +0000
591+++ lib/lp/code/tests/helpers.py 2010-09-21 15:06:00 +0000
592@@ -64,9 +64,14 @@
593 """
594 if date_created is None:
595 date_created = revision_date
596+ parent = branch.revision_history.last()
597+ if parent is None:
598+ parent_ids = []
599+ else:
600+ parent_ids = [parent.revision.revision_id]
601 revision = factory.makeRevision(
602 revision_date=revision_date, date_created=date_created,
603- log_body=commit_msg)
604+ log_body=commit_msg, parent_ids=parent_ids)
605 if mainline:
606 sequence = branch.revision_count + 1
607 branch_revision = branch.createBranchRevision(sequence, revision)
608@@ -112,7 +117,7 @@
609 preview.remvoed_lines_count = 13
610 preview.diffstat = {'file1': (3, 8), 'file2': (4, 5)}
611 return {
612- 'eric': eric, 'fooix': fooix, 'trunk':trunk, 'feature': feature,
613+ 'eric': eric, 'fooix': fooix, 'trunk': trunk, 'feature': feature,
614 'proposed': proposed, 'fred': fred}
615
616
617
618=== modified file 'lib/lp/code/tests/test_directbranchcommit.py'
619--- lib/lp/code/tests/test_directbranchcommit.py 2010-08-20 20:31:18 +0000
620+++ lib/lp/code/tests/test_directbranchcommit.py 2010-09-21 15:06:00 +0000
621@@ -99,6 +99,24 @@
622 branch_revision_id = self.committer.bzrbranch.last_revision()
623 self.assertEqual(branch_revision_id, revision_id)
624
625+ def test_commit_uses_merge_parents(self):
626+ # DirectBranchCommit.commit returns uses merge parents
627+ self._tearDownCommitter()
628+ # Merge parents cannot be specified for initial commit, so do an
629+ # empty commit.
630+ self.tree.commit('foo', committer='foo@bar', rev_id='foo')
631+ committer = DirectBranchCommit(
632+ self.db_branch, merge_parents=['parent-1', 'parent-2'])
633+ committer.last_scanned_id = (
634+ committer.bzrbranch.last_revision())
635+ committer.writeFile('file.txt', 'contents')
636+ revision_id = committer.commit('')
637+ branch_revision_id = committer.bzrbranch.last_revision()
638+ branch_revision = committer.bzrbranch.repository.get_revision(
639+ branch_revision_id)
640+ self.assertEqual(
641+ ['parent-1', 'parent-2'], branch_revision.parent_ids[1:])
642+
643 def test_DirectBranchCommit_aborts_cleanly(self):
644 # If a DirectBranchCommit is not committed, its changes do not
645 # go into the branch.
646
647=== modified file 'lib/lp/codehosting/bzrutils.py'
648--- lib/lp/codehosting/bzrutils.py 2010-08-20 20:31:18 +0000
649+++ lib/lp/codehosting/bzrutils.py 2010-09-21 15:06:00 +0000
650@@ -18,11 +18,13 @@
651 'identical_formats',
652 'install_oops_handler',
653 'is_branch_stackable',
654+ 'read_locked',
655 'remove_exception_logging_hook',
656 'safe_open',
657 'UnsafeUrlSeen',
658 ]
659
660+from contextlib import contextmanager
661 import os
662 import sys
663 import threading
664@@ -363,3 +365,12 @@
665 return branch.get_stacked_on_url()
666 except (NotStacked, UnstackableBranchFormat):
667 return None
668+
669+
670+@contextmanager
671+def read_locked(branch):
672+ branch.lock_read()
673+ try:
674+ yield
675+ finally:
676+ branch.unlock()