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
=== modified file 'lib/lp/code/browser/branchmergeproposal.py'
--- lib/lp/code/browser/branchmergeproposal.py 2010-08-24 10:45:57 +0000
+++ lib/lp/code/browser/branchmergeproposal.py 2010-09-21 15:06:00 +0000
@@ -32,7 +32,6 @@
32 'latest_proposals_for_each_branch',32 'latest_proposals_for_each_branch',
33 ]33 ]
3434
35from collections import defaultdict
36import operator35import operator
3736
38from lazr.delegates import delegates37from lazr.delegates import delegates
@@ -200,7 +199,7 @@
200 'Approved [Merge Failed]',199 'Approved [Merge Failed]',
201 BranchMergeProposalStatus.QUEUED : 'Queued',200 BranchMergeProposalStatus.QUEUED : 'Queued',
202 BranchMergeProposalStatus.SUPERSEDED : 'Superseded'201 BranchMergeProposalStatus.SUPERSEDED : 'Superseded'
203 }202 }
204 return friendly_texts[self.context.queue_status]203 return friendly_texts[self.context.queue_status]
205204
206 @property205 @property
@@ -212,8 +211,7 @@
212 result = ''211 result = ''
213 if self.context.queue_status in (212 if self.context.queue_status in (
214 BranchMergeProposalStatus.CODE_APPROVED,213 BranchMergeProposalStatus.CODE_APPROVED,
215 BranchMergeProposalStatus.REJECTED214 BranchMergeProposalStatus.REJECTED):
216 ):
217 formatter = DateTimeFormatterAPI(self.context.date_reviewed)215 formatter = DateTimeFormatterAPI(self.context.date_reviewed)
218 result = '%s %s' % (216 result = '%s %s' % (
219 self.context.reviewer.displayname,217 self.context.reviewer.displayname,
@@ -601,46 +599,15 @@
601 """Location of page for commenting on this proposal."""599 """Location of page for commenting on this proposal."""
602 return canonical_url(self.context, view_name='+comment')600 return canonical_url(self.context, view_name='+comment')
603601
604 @property
605 def revision_end_date(self):
606 """The cutoff date for showing revisions.
607
608 If the proposal has been merged, then we stop at the merged date. If
609 it is rejected, we stop at the reviewed date. For superseded
610 proposals, it should ideally use the non-existant date_last_modified,
611 but could use the last comment date.
612 """
613 status = self.context.queue_status
614 if status == BranchMergeProposalStatus.MERGED:
615 return self.context.date_merged
616 if status == BranchMergeProposalStatus.REJECTED:
617 return self.context.date_reviewed
618 # Otherwise return None representing an open end date.
619 return None
620
621 def _getRevisionsSinceReviewStart(self):
622 """Get the grouped revisions since the review started."""
623 # Work out the start of the review.
624 start_date = self.context.date_review_requested
625 if start_date is None:
626 start_date = self.context.date_created
627 source = DecoratedBranch(self.context.source_branch)
628 resultset = source.getMainlineBranchRevisions(
629 start_date, self.revision_end_date, oldest_first=True)
630 # Now group by date created.
631 groups = defaultdict(list)
632 for branch_revision, revision, revision_author in resultset:
633 groups[revision.date_created].append(branch_revision)
634 return [
635 CodeReviewNewRevisions(revisions, date, source)
636 for date, revisions in groups.iteritems()]
637
638 @cachedproperty602 @cachedproperty
639 def conversation(self):603 def conversation(self):
640 """Return a conversation that is to be rendered."""604 """Return a conversation that is to be rendered."""
641 # Sort the comments by date order.605 # Sort the comments by date order.
642 comments = self._getRevisionsSinceReviewStart()
643 merge_proposal = self.context606 merge_proposal = self.context
607 groups = merge_proposal.getRevisionsSinceReviewStart()
608 source = DecoratedBranch(merge_proposal.source_branch)
609 comments = [CodeReviewNewRevisions(list(revisions), date, source)
610 for date, revisions in groups]
644 while merge_proposal is not None:611 while merge_proposal is not None:
645 from_superseded = merge_proposal != self.context612 from_superseded = merge_proposal != self.context
646 comments.extend(613 comments.extend(
@@ -946,7 +913,6 @@
946 self.cancel_url = self.next_url913 self.cancel_url = self.next_url
947 super(MergeProposalEditView, self).initialize()914 super(MergeProposalEditView, self).initialize()
948915
949
950 def _getRevisionId(self, data):916 def _getRevisionId(self, data):
951 """Translate the revision number that was entered into a revision id.917 """Translate the revision number that was entered into a revision id.
952918
@@ -1473,8 +1439,8 @@
1473 """Render an `IText` as XHTML using the webservice."""1439 """Render an `IText` as XHTML using the webservice."""
1474 formatter = FormattersAPI1440 formatter = FormattersAPI
1475 def renderer(value):1441 def renderer(value):
1476 nomail = formatter(value).obfuscate_email()1442 nomail = formatter(value).obfuscate_email()
1477 html = formatter(nomail).text_to_html()1443 html = formatter(nomail).text_to_html()
1478 return html.encode('utf-8')1444 return html.encode('utf-8')
1479 return renderer1445 return renderer
14801446
14811447
=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
--- lib/lp/code/browser/tests/test_branchmergeproposal.py 2010-08-22 21:34:16 +0000
+++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2010-09-21 15:06:00 +0000
@@ -12,7 +12,6 @@
12 timedelta,12 timedelta,
13 )13 )
14from difflib import unified_diff14from difflib import unified_diff
15import operator
16import unittest15import unittest
1716
18import pytz17import pytz
@@ -46,7 +45,6 @@
46 PreviewDiff,45 PreviewDiff,
47 StaticDiff,46 StaticDiff,
48 )47 )
49from lp.code.tests.helpers import add_revision_to_branch
50from lp.testing import (48from lp.testing import (
51 login_person,49 login_person,
52 TestCaseWithFactory,50 TestCaseWithFactory,
@@ -577,63 +575,6 @@
577 view = create_initialized_view(self.bmp, '+index')575 view = create_initialized_view(self.bmp, '+index')
578 self.assertEqual([], view.linked_bugs)576 self.assertEqual([], view.linked_bugs)
579577
580 def test_revision_end_date_active(self):
581 # An active merge proposal will have None as an end date.
582 bmp = self.factory.makeBranchMergeProposal()
583 view = create_initialized_view(bmp, '+index')
584 self.assertIs(None, view.revision_end_date)
585
586 def test_revision_end_date_merged(self):
587 # An merged proposal will have the date merged as an end date.
588 bmp = self.factory.makeBranchMergeProposal(
589 set_state=BranchMergeProposalStatus.MERGED)
590 view = create_initialized_view(bmp, '+index')
591 self.assertEqual(bmp.date_merged, view.revision_end_date)
592
593 def test_revision_end_date_rejected(self):
594 # An rejected proposal will have the date reviewed as an end date.
595 bmp = self.factory.makeBranchMergeProposal(
596 set_state=BranchMergeProposalStatus.REJECTED)
597 view = create_initialized_view(bmp, '+index')
598 self.assertEqual(bmp.date_reviewed, view.revision_end_date)
599
600 def assertRevisionGroups(self, bmp, expected_groups):
601 """Get the groups for the merge proposal and check them."""
602 view = create_initialized_view(bmp, '+index')
603 groups = view._getRevisionsSinceReviewStart()
604 view_groups = [
605 obj.revisions for obj in sorted(
606 groups, key=operator.attrgetter('date'))]
607 self.assertEqual(expected_groups, view_groups)
608
609 def test_getRevisionsSinceReviewStart_no_revisions(self):
610 # If there have been no revisions pushed since the start of the
611 # review, the method returns an empty list.
612 self.assertRevisionGroups(self.bmp, [])
613
614 def test_getRevisionsSinceReviewStart_groups(self):
615 # Revisions that were scanned at the same time have the same
616 # date_created. These revisions are grouped together.
617 review_date = datetime(2009, 9, 10, tzinfo=pytz.UTC)
618 bmp = self.factory.makeBranchMergeProposal(
619 date_created=review_date)
620 login_person(bmp.registrant)
621 bmp.requestReview(review_date)
622 revision_date = review_date + timedelta(days=1)
623 revisions = []
624 for date in range(2):
625 revisions.append(
626 add_revision_to_branch(
627 self.factory, bmp.source_branch, revision_date))
628 revisions.append(
629 add_revision_to_branch(
630 self.factory, bmp.source_branch, revision_date))
631 revision_date += timedelta(days=1)
632 expected_groups = [
633 [revisions[0], revisions[1]],
634 [revisions[2], revisions[3]]]
635 self.assertRevisionGroups(bmp, expected_groups)
636
637 def test_include_superseded_comments(self):578 def test_include_superseded_comments(self):
638 for x, time in zip(range(3), time_counter()):579 for x, time in zip(range(3), time_counter()):
639 if x != 0:580 if x != 0:
@@ -757,7 +698,6 @@
757698
758 layer = LaunchpadFunctionalLayer699 layer = LaunchpadFunctionalLayer
759700
760
761 def _makeCommentFromEmailWithAttachment(self, attachment_body):701 def _makeCommentFromEmailWithAttachment(self, attachment_body):
762 # Make an email message with an attachment, and create a code702 # Make an email message with an attachment, and create a code
763 # review comment from it.703 # review comment from it.
764704
=== modified file 'lib/lp/code/configure.zcml'
--- lib/lp/code/configure.zcml 2010-09-20 22:16:32 +0000
+++ lib/lp/code/configure.zcml 2010-09-21 15:06:00 +0000
@@ -236,8 +236,10 @@
236 votes236 votes
237 all_comments237 all_comments
238 related_bugs238 related_bugs
239 revision_end_date
239 isMergable240 isMergable
240 getComment241 getComment
242 getRevisionsSinceReviewStart
241 getNotificationRecipients243 getNotificationRecipients
242 getVoteReference244 getVoteReference
243 isValidTransition245 isValidTransition
244246
=== modified file 'lib/lp/code/interfaces/branchmergeproposal.py'
--- lib/lp/code/interfaces/branchmergeproposal.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/interfaces/branchmergeproposal.py 2010-09-21 15:06:00 +0000
@@ -290,6 +290,13 @@
290 def getComment(id):290 def getComment(id):
291 """Return the CodeReviewComment with the specified ID."""291 """Return the CodeReviewComment with the specified ID."""
292292
293 def getRevisionsSinceReviewStart():
294 """Return all the revisions added since the review began.
295
296 Revisions are grouped by creation (i.e. push) time.
297 :return: An iterator of (date, iterator of revision data)
298 """
299
293 def getVoteReference(id):300 def getVoteReference(id):
294 """Return the CodeReviewVoteReference with the specified ID."""301 """Return the CodeReviewVoteReference with the specified ID."""
295302
@@ -518,8 +525,8 @@
518 source branch.525 source branch.
519 :param target_revision_id: The revision id that was used from the526 :param target_revision_id: The revision id that was used from the
520 target branch.527 target branch.
521 :param prerequisite_revision_id: The revision id that was used from the528 :param prerequisite_revision_id: The revision id that was used from
522 prerequisite branch.529 the prerequisite branch.
523 :param conflicts: Text describing the conflicts if any.530 :param conflicts: Text describing the conflicts if any.
524 """531 """
525532
526533
=== modified file 'lib/lp/code/interfaces/revision.py'
--- lib/lp/code/interfaces/revision.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/interfaces/revision.py 2010-09-21 15:06:00 +0000
@@ -73,6 +73,9 @@
73 :return: A `Branch` or None if an appropriate branch cannot be found.73 :return: A `Branch` or None if an appropriate branch cannot be found.
74 """74 """
7575
76 def getLefthandParent():
77 """Return lefthand parent of revision, or None if not in database."""
78
7679
77class IRevisionAuthor(Interface):80class IRevisionAuthor(Interface):
78 """Committer of a Bazaar revision."""81 """Committer of a Bazaar revision."""
7982
=== modified file 'lib/lp/code/model/branchmergeproposal.py'
--- lib/lp/code/model/branchmergeproposal.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/model/branchmergeproposal.py 2010-09-21 15:06:00 +0000
@@ -13,7 +13,7 @@
13 ]13 ]
1414
15from email.Utils import make_msgid15from email.Utils import make_msgid
1616from itertools import groupby
17from sqlobject import (17from sqlobject import (
18 ForeignKey,18 ForeignKey,
19 IntCol,19 IntCol,
@@ -777,6 +777,45 @@
777 Store.of(self).flush()777 Store.of(self).flush()
778 return self.preview_diff778 return self.preview_diff
779779
780 @property
781 def revision_end_date(self):
782 """The cutoff date for showing revisions.
783
784 If the proposal has been merged, then we stop at the merged date. If
785 it is rejected, we stop at the reviewed date. For superseded
786 proposals, it should ideally use the non-existant date_last_modified,
787 but could use the last comment date.
788 """
789 status = self.queue_status
790 if status == BranchMergeProposalStatus.MERGED:
791 return self.date_merged
792 if status == BranchMergeProposalStatus.REJECTED:
793 return self.date_reviewed
794 # Otherwise return None representing an open end date.
795 return None
796
797 def _getNewerRevisions(self):
798 start_date = self.date_review_requested
799 if start_date is None:
800 start_date = self.date_created
801 return self.source_branch.getMainlineBranchRevisions(
802 start_date, self.revision_end_date, oldest_first=True)
803
804 def getRevisionsSinceReviewStart(self):
805 """Get the grouped revisions since the review started."""
806 resultset = self._getNewerRevisions()
807 # Work out the start of the review.
808 branch_revisions = (
809 branch_revision for branch_revision, revision, revision_author
810 in resultset)
811 # Now group by date created.
812 gby = groupby(branch_revisions, lambda r: r.revision.date_created)
813 # Use a generator expression to wrap the custom iterator so it doesn't
814 # get security-proxied.
815 return (
816 (date, (revision for revision in revisions))
817 for date, revisions in gby)
818
780819
781class BranchMergeProposalGetter:820class BranchMergeProposalGetter:
782 """See `IBranchMergeProposalGetter`."""821 """See `IBranchMergeProposalGetter`."""
783822
=== modified file 'lib/lp/code/model/directbranchcommit.py'
--- lib/lp/code/model/directbranchcommit.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/model/directbranchcommit.py 2010-09-21 15:06:00 +0000
@@ -55,7 +55,8 @@
55 is_locked = False55 is_locked = False
56 commit_builder = None56 commit_builder = None
5757
58 def __init__(self, db_branch, committer=None, no_race_check=False):58 def __init__(self, db_branch, committer=None, no_race_check=False,
59 merge_parents=None):
59 """Create context for direct commit to branch.60 """Create context for direct commit to branch.
6061
61 Before constructing a `DirectBranchCommit`, set up a server that62 Before constructing a `DirectBranchCommit`, set up a server that
@@ -107,6 +108,7 @@
107 raise108 raise
108109
109 self.files = set()110 self.files = set()
111 self.merge_parents = merge_parents
110112
111 def _getDir(self, path):113 def _getDir(self, path):
112 """Get trans_id for directory "path." Create if necessary."""114 """Get trans_id for directory "path." Create if necessary."""
@@ -200,7 +202,8 @@
200 # required to generate the revision-id.202 # required to generate the revision-id.
201 with override_environ(BZR_EMAIL=committer_id):203 with override_environ(BZR_EMAIL=committer_id):
202 new_rev_id = self.transform_preview.commit(204 new_rev_id = self.transform_preview.commit(
203 self.bzrbranch, commit_message, committer=committer_id)205 self.bzrbranch, commit_message, self.merge_parents,
206 committer=committer_id)
204 IMasterObject(self.db_branch).branchChanged(207 IMasterObject(self.db_branch).branchChanged(
205 get_stacked_on_url(self.bzrbranch), new_rev_id,208 get_stacked_on_url(self.bzrbranch), new_rev_id,
206 self.db_branch.control_format, self.db_branch.branch_format,209 self.db_branch.control_format, self.db_branch.branch_format,
207210
=== modified file 'lib/lp/code/model/revision.py'
--- lib/lp/code/model/revision.py 2010-08-27 02:11:36 +0000
+++ lib/lp/code/model/revision.py 2010-09-21 15:06:00 +0000
@@ -18,6 +18,7 @@
18 )18 )
19import email19import email
2020
21from bzrlib.revision import NULL_REVISION
21import pytz22import pytz
22from sqlobject import (23from sqlobject import (
23 BoolCol,24 BoolCol,
@@ -39,7 +40,6 @@
39 )40 )
40from storm.locals import (41from storm.locals import (
41 Bool,42 Bool,
42 DateTime,
43 Int,43 Int,
44 Min,44 Min,
45 Reference,45 Reference,
@@ -118,6 +118,13 @@
118 """118 """
119 return [parent.parent_id for parent in self.parents]119 return [parent.parent_id for parent in self.parents]
120120
121 def getLefthandParent(self):
122 if len(self.parent_ids) == 0:
123 parent_id = NULL_REVISION
124 else:
125 parent_id = self.parent_ids[0]
126 return RevisionSet().getByRevisionId(parent_id)
127
121 def getProperties(self):128 def getProperties(self):
122 """See `IRevision`."""129 """See `IRevision`."""
123 return dict((prop.name, prop.value) for prop in self.properties)130 return dict((prop.name, prop.value) for prop in self.properties)
124131
=== modified file 'lib/lp/code/model/tests/test_branchmergeproposal.py'
--- lib/lp/code/model/tests/test_branchmergeproposal.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/model/tests/test_branchmergeproposal.py 2010-09-21 15:06:00 +0000
@@ -7,7 +7,7 @@
77
8__metaclass__ = type8__metaclass__ = type
99
10from datetime import datetime10from datetime import datetime, timedelta
11from difflib import unified_diff11from difflib import unified_diff
12from unittest import (12from unittest import (
13 TestCase,13 TestCase,
@@ -72,6 +72,7 @@
72 MergeProposalCreatedJob,72 MergeProposalCreatedJob,
73 UpdatePreviewDiffJob,73 UpdatePreviewDiffJob,
74 )74 )
75from lp.code.tests.helpers import add_revision_to_branch
75from lp.registry.interfaces.person import IPersonSet76from lp.registry.interfaces.person import IPersonSet
76from lp.registry.interfaces.product import IProductSet77from lp.registry.interfaces.product import IProductSet
77from lp.testing import (78from lp.testing import (
@@ -108,7 +109,8 @@
108 self.assertTrue(url.startswith(source_branch_url))109 self.assertTrue(url.startswith(source_branch_url))
109110
110 def test_BranchMergeProposal_canonical_url_rest(self):111 def test_BranchMergeProposal_canonical_url_rest(self):
111 # The rest of the URL for a merge proposal is +merge followed by the db id.112 # The rest of the URL for a merge proposal is +merge followed by the
113 # db id.
112 bmp = self.factory.makeBranchMergeProposal()114 bmp = self.factory.makeBranchMergeProposal()
113 url = canonical_url(bmp)115 url = canonical_url(bmp)
114 source_branch_url = canonical_url(bmp.source_branch)116 source_branch_url = canonical_url(bmp.source_branch)
@@ -238,7 +240,6 @@
238 self._attemptTransition,240 self._attemptTransition,
239 proposal, to_state)241 proposal, to_state)
240242
241
242 def assertGoodDupeTransition(self, from_state, to_state):243 def assertGoodDupeTransition(self, from_state, to_state):
243 """Trying to go from `from_state` to `to_state` succeeds."""244 """Trying to go from `from_state` to `to_state` succeeds."""
244 proposal = self.prepareDupeTransition(from_state)245 proposal = self.prepareDupeTransition(from_state)
@@ -1049,6 +1050,7 @@
1049 else:1050 else:
1050 self.assertEqual([mp], list(active))1051 self.assertEqual([mp], list(active))
10511052
1053
1052class TestBranchMergeProposalGetterGetProposals(TestCaseWithFactory):1054class TestBranchMergeProposalGetterGetProposals(TestCaseWithFactory):
1053 """Test the getProposalsForContext method."""1055 """Test the getProposalsForContext method."""
10541056
@@ -1118,7 +1120,6 @@
1118 beaver, [BranchMergeProposalStatus.REJECTED], beaver)1120 beaver, [BranchMergeProposalStatus.REJECTED], beaver)
1119 self.assertEqual(beave_proposals.count(), 1)1121 self.assertEqual(beave_proposals.count(), 1)
11201122
1121
1122 def test_created_proposal_default_status(self):1123 def test_created_proposal_default_status(self):
1123 # When we create a merge proposal using the helper method, the default1124 # When we create a merge proposal using the helper method, the default
1124 # status of the proposal is work in progress.1125 # status of the proposal is work in progress.
@@ -1799,5 +1800,67 @@
1799 self.assertIs(None, bmp.next_preview_diff_job)1800 self.assertIs(None, bmp.next_preview_diff_job)
18001801
18011802
1803class TestRevisionEndDate(TestCaseWithFactory):
1804
1805 layer = DatabaseFunctionalLayer
1806
1807 def test_revision_end_date_active(self):
1808 # An active merge proposal will have None as an end date.
1809 bmp = self.factory.makeBranchMergeProposal()
1810 self.assertIs(None, bmp.revision_end_date)
1811
1812 def test_revision_end_date_merged(self):
1813 # An merged proposal will have the date merged as an end date.
1814 bmp = self.factory.makeBranchMergeProposal(
1815 set_state=BranchMergeProposalStatus.MERGED)
1816 self.assertEqual(bmp.date_merged, bmp.revision_end_date)
1817
1818 def test_revision_end_date_rejected(self):
1819 # An rejected proposal will have the date reviewed as an end date.
1820 bmp = self.factory.makeBranchMergeProposal(
1821 set_state=BranchMergeProposalStatus.REJECTED)
1822 self.assertEqual(bmp.date_reviewed, bmp.revision_end_date)
1823
1824
1825class TestGetRevisionsSinceReviewStart(TestCaseWithFactory):
1826
1827 layer = DatabaseFunctionalLayer
1828
1829 def assertRevisionGroups(self, bmp, expected_groups):
1830 """Get the groups for the merge proposal and check them."""
1831 groups = bmp.getRevisionsSinceReviewStart()
1832 revision_groups = [list(revisions) for date, revisions in groups]
1833 self.assertEqual(expected_groups, revision_groups)
1834
1835 def test_getRevisionsSinceReviewStart_no_revisions(self):
1836 # If there have been no revisions pushed since the start of the
1837 # review, the method returns an empty list.
1838 bmp = self.factory.makeBranchMergeProposal()
1839 self.assertRevisionGroups(bmp, [])
1840
1841 def test_getRevisionsSinceReviewStart_groups(self):
1842 # Revisions that were scanned at the same time have the same
1843 # date_created. These revisions are grouped together.
1844 review_date = datetime(2009, 9, 10, tzinfo=UTC)
1845 bmp = self.factory.makeBranchMergeProposal(
1846 date_created=review_date)
1847 login_person(bmp.registrant)
1848 bmp.requestReview(review_date)
1849 revision_date = review_date + timedelta(days=1)
1850 revisions = []
1851 for date in range(2):
1852 revisions.append(
1853 add_revision_to_branch(
1854 self.factory, bmp.source_branch, revision_date))
1855 revisions.append(
1856 add_revision_to_branch(
1857 self.factory, bmp.source_branch, revision_date))
1858 revision_date += timedelta(days=1)
1859 expected_groups = [
1860 [revisions[0], revisions[1]],
1861 [revisions[2], revisions[3]]]
1862 self.assertRevisionGroups(bmp, expected_groups)
1863
1864
1802def test_suite():1865def test_suite():
1803 return TestLoader().loadTestsFromName(__name__)1866 return TestLoader().loadTestsFromName(__name__)
18041867
=== modified file 'lib/lp/code/model/tests/test_diff.py'
--- lib/lp/code/model/tests/test_diff.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/model/tests/test_diff.py 2010-09-21 15:06:00 +0000
@@ -57,13 +57,14 @@
57class DiffTestCase(TestCaseWithFactory):57class DiffTestCase(TestCaseWithFactory):
5858
59 @staticmethod59 @staticmethod
60 def commitFile(branch, path, contents):60 def commitFile(branch, path, contents, merge_parents=None):
61 """Create a commit that updates a file to specified contents.61 """Create a commit that updates a file to specified contents.
6262
63 This will create or modify the file, as needed.63 This will create or modify the file, as needed.
64 """64 """
65 committer = DirectBranchCommit(65 committer = DirectBranchCommit(
66 removeSecurityProxy(branch), no_race_check=True)66 removeSecurityProxy(branch), no_race_check=True,
67 merge_parents=merge_parents)
67 committer.writeFile(path, contents)68 committer.writeFile(path, contents)
68 try:69 try:
69 return committer.commit('committing')70 return committer.commit('committing')
@@ -122,7 +123,6 @@
122 prerequisite)123 prerequisite)
123124
124125
125
126class TestDiff(DiffTestCase):126class TestDiff(DiffTestCase):
127127
128 layer = LaunchpadFunctionalLayer128 layer = LaunchpadFunctionalLayer
@@ -186,19 +186,19 @@
186 self.checkExampleMerge(diff.text)186 self.checkExampleMerge(diff.text)
187187
188 diff_bytes = (188 diff_bytes = (
189 "--- bar 2009-08-26 15:53:34.000000000 -0400\n"189 "--- bar\t2009-08-26 15:53:34.000000000 -0400\n"
190 "+++ bar 1969-12-31 19:00:00.000000000 -0500\n"190 "+++ bar\t1969-12-31 19:00:00.000000000 -0500\n"
191 "@@ -1,3 +0,0 @@\n"191 "@@ -1,3 +0,0 @@\n"
192 "-a\n"192 "-a\n"
193 "-b\n"193 "-b\n"
194 "-c\n"194 "-c\n"
195 "--- baz 1969-12-31 19:00:00.000000000 -0500\n"195 "--- baz\t1969-12-31 19:00:00.000000000 -0500\n"
196 "+++ baz 2009-08-26 15:53:57.000000000 -0400\n"196 "+++ baz\t2009-08-26 15:53:57.000000000 -0400\n"
197 "@@ -0,0 +1,2 @@\n"197 "@@ -0,0 +1,2 @@\n"
198 "+a\n"198 "+a\n"
199 "+b\n"199 "+b\n"
200 "--- foo 2009-08-26 15:53:23.000000000 -0400\n"200 "--- foo\t2009-08-26 15:53:23.000000000 -0400\n"
201 "+++ foo 2009-08-26 15:56:43.000000000 -0400\n"201 "+++ foo\t2009-08-26 15:56:43.000000000 -0400\n"
202 "@@ -1,3 +1,4 @@\n"202 "@@ -1,3 +1,4 @@\n"
203 " a\n"203 " a\n"
204 "-b\n"204 "-b\n"
@@ -207,19 +207,19 @@
207 "+e\n")207 "+e\n")
208208
209 diff_bytes_2 = (209 diff_bytes_2 = (
210 "--- bar 2009-08-26 15:53:34.000000000 -0400\n"210 "--- bar\t2009-08-26 15:53:34.000000000 -0400\n"
211 "+++ bar 1969-12-31 19:00:00.000000000 -0500\n"211 "+++ bar\t1969-12-31 19:00:00.000000000 -0500\n"
212 "@@ -1,3 +0,0 @@\n"212 "@@ -1,3 +0,0 @@\n"
213 "-a\n"213 "-a\n"
214 "-b\n"214 "-b\n"
215 "-c\n"215 "-c\n"
216 "--- baz 1969-12-31 19:00:00.000000000 -0500\n"216 "--- baz\t1969-12-31 19:00:00.000000000 -0500\n"
217 "+++ baz 2009-08-26 15:53:57.000000000 -0400\n"217 "+++ baz\t2009-08-26 15:53:57.000000000 -0400\n"
218 "@@ -0,0 +1,2 @@\n"218 "@@ -0,0 +1,2 @@\n"
219 "+a\n"219 "+a\n"
220 "+b\n"220 "+b\n"
221 "--- foo 2009-08-26 15:53:23.000000000 -0400\n"221 "--- foo\t2009-08-26 15:53:23.000000000 -0400\n"
222 "+++ foo 2009-08-26 15:56:43.000000000 -0400\n"222 "+++ foo\t2009-08-26 15:56:43.000000000 -0400\n"
223 "@@ -1,3 +1,5 @@\n"223 "@@ -1,3 +1,5 @@\n"
224 " a\n"224 " a\n"
225 "-b\n"225 "-b\n"
@@ -467,7 +467,6 @@
467 self.assertEqual('', diff.conflicts)467 self.assertEqual('', diff.conflicts)
468 self.assertFalse(diff.has_conflicts)468 self.assertFalse(diff.has_conflicts)
469469
470
471 def test_fromBranchMergeProposal(self):470 def test_fromBranchMergeProposal(self):
472 # Correctly generates a PreviewDiff from a BranchMergeProposal.471 # Correctly generates a PreviewDiff from a BranchMergeProposal.
473 bmp, source_rev_id, target_rev_id = self.createExampleMerge()472 bmp, source_rev_id, target_rev_id = self.createExampleMerge()
474473
=== modified file 'lib/lp/code/tests/helpers.py'
--- lib/lp/code/tests/helpers.py 2010-08-31 00:16:41 +0000
+++ lib/lp/code/tests/helpers.py 2010-09-21 15:06:00 +0000
@@ -64,9 +64,14 @@
64 """64 """
65 if date_created is None:65 if date_created is None:
66 date_created = revision_date66 date_created = revision_date
67 parent = branch.revision_history.last()
68 if parent is None:
69 parent_ids = []
70 else:
71 parent_ids = [parent.revision.revision_id]
67 revision = factory.makeRevision(72 revision = factory.makeRevision(
68 revision_date=revision_date, date_created=date_created,73 revision_date=revision_date, date_created=date_created,
69 log_body=commit_msg)74 log_body=commit_msg, parent_ids=parent_ids)
70 if mainline:75 if mainline:
71 sequence = branch.revision_count + 176 sequence = branch.revision_count + 1
72 branch_revision = branch.createBranchRevision(sequence, revision)77 branch_revision = branch.createBranchRevision(sequence, revision)
@@ -112,7 +117,7 @@
112 preview.remvoed_lines_count = 13117 preview.remvoed_lines_count = 13
113 preview.diffstat = {'file1': (3, 8), 'file2': (4, 5)}118 preview.diffstat = {'file1': (3, 8), 'file2': (4, 5)}
114 return {119 return {
115 'eric': eric, 'fooix': fooix, 'trunk':trunk, 'feature': feature,120 'eric': eric, 'fooix': fooix, 'trunk': trunk, 'feature': feature,
116 'proposed': proposed, 'fred': fred}121 'proposed': proposed, 'fred': fred}
117122
118123
119124
=== modified file 'lib/lp/code/tests/test_directbranchcommit.py'
--- lib/lp/code/tests/test_directbranchcommit.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/tests/test_directbranchcommit.py 2010-09-21 15:06:00 +0000
@@ -99,6 +99,24 @@
99 branch_revision_id = self.committer.bzrbranch.last_revision()99 branch_revision_id = self.committer.bzrbranch.last_revision()
100 self.assertEqual(branch_revision_id, revision_id)100 self.assertEqual(branch_revision_id, revision_id)
101101
102 def test_commit_uses_merge_parents(self):
103 # DirectBranchCommit.commit returns uses merge parents
104 self._tearDownCommitter()
105 # Merge parents cannot be specified for initial commit, so do an
106 # empty commit.
107 self.tree.commit('foo', committer='foo@bar', rev_id='foo')
108 committer = DirectBranchCommit(
109 self.db_branch, merge_parents=['parent-1', 'parent-2'])
110 committer.last_scanned_id = (
111 committer.bzrbranch.last_revision())
112 committer.writeFile('file.txt', 'contents')
113 revision_id = committer.commit('')
114 branch_revision_id = committer.bzrbranch.last_revision()
115 branch_revision = committer.bzrbranch.repository.get_revision(
116 branch_revision_id)
117 self.assertEqual(
118 ['parent-1', 'parent-2'], branch_revision.parent_ids[1:])
119
102 def test_DirectBranchCommit_aborts_cleanly(self):120 def test_DirectBranchCommit_aborts_cleanly(self):
103 # If a DirectBranchCommit is not committed, its changes do not121 # If a DirectBranchCommit is not committed, its changes do not
104 # go into the branch.122 # go into the branch.
105123
=== modified file 'lib/lp/codehosting/bzrutils.py'
--- lib/lp/codehosting/bzrutils.py 2010-08-20 20:31:18 +0000
+++ lib/lp/codehosting/bzrutils.py 2010-09-21 15:06:00 +0000
@@ -18,11 +18,13 @@
18 'identical_formats',18 'identical_formats',
19 'install_oops_handler',19 'install_oops_handler',
20 'is_branch_stackable',20 'is_branch_stackable',
21 'read_locked',
21 'remove_exception_logging_hook',22 'remove_exception_logging_hook',
22 'safe_open',23 'safe_open',
23 'UnsafeUrlSeen',24 'UnsafeUrlSeen',
24 ]25 ]
2526
27from contextlib import contextmanager
26import os28import os
27import sys29import sys
28import threading30import threading
@@ -363,3 +365,12 @@
363 return branch.get_stacked_on_url()365 return branch.get_stacked_on_url()
364 except (NotStacked, UnstackableBranchFormat):366 except (NotStacked, UnstackableBranchFormat):
365 return None367 return None
368
369
370@contextmanager
371def read_locked(branch):
372 branch.lock_read()
373 try:
374 yield
375 finally:
376 branch.unlock()