Merge lp:~abentley/launchpad/partial-ancestry-scanner into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Approved by: Paul Hummer
Approved revision: no longer in the source branch.
Merged at revision: 11757
Proposed branch: lp:~abentley/launchpad/partial-ancestry-scanner
Merge into: lp:launchpad
Diff against target: 553 lines (+193/-117)
7 files modified
lib/lp/codehosting/bzrutils.py (+12/-0)
lib/lp/codehosting/scanner/bzrsync.py (+84/-57)
lib/lp/codehosting/scanner/events.py (+3/-2)
lib/lp/codehosting/scanner/mergedetection.py (+4/-4)
lib/lp/codehosting/scanner/tests/test_bzrsync.py (+83/-33)
lib/lp/codehosting/scanner/tests/test_mergedetection.py (+3/-3)
lib/lp/testing/factory.py (+4/-18)
To merge this branch: bzr merge lp:~abentley/launchpad/partial-ancestry-scanner
Reviewer Review Type Date Requested Status
Paul Hummer (community) Approve
Review via email: mp+38582@code.launchpad.net

Commit message

Stop loading the entire ancestry of a branch during the scan

Description of the change

= Summary =
Fix bug #638637: Stop loading the entire ancestry of a branch during the scan

== Proposed fix ==
Use the set of revisions added to the ancestry, in most cases.

== Pre-implementation notes ==
Pre- and mid- implementation with thumper.

== Implementation details ==
I took the opportunity to refactor how things work a lot. Removing
whole-ancestry traversal made retrieveBranchDetails trivial, so I replaced it
with Branch.revision_history.

I also replaced BzrSync.getRevisions with BzrSync.revisionsToInsert, which
can use a partial ancestry and last-revno to generate the required info.

I also factored code out, e.g. getHistoryDelta.

Along the way, I added a write_locked Context Manager and added parent_ids to
makeBranchRevision so I could test getAncestryDelta.

_getRevisionGraph is designed to handle the case where not only has the tip
changed, but the old tip isn't present in the new repository. This requires
generating a Graph using the Revisions associated with the branch. However,
it's an extremely rare case, so I didn't optimize it.

== Tests ==
bin/test -vv scanner

== Demo and Q/A ==
None.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/codehosting/scanner/tests/test_bzrsync.py
  lib/lp/codehosting/scanner/tests/test_mergedetection.py
  lib/lp/codehosting/scanner/events.py
  lib/lp/codehosting/bzrutils.py
  lib/lp/testing/factory.py
  lib/lp/codehosting/scanner/bzrsync.py
  lib/lp/codehosting/scanner/mergedetection.py

./lib/lp/codehosting/scanner/tests/test_mergedetection.py
     374: E231 missing whitespace after ','
     121: Line exceeds 78 characters.

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/codehosting/bzrutils.py'
--- lib/lp/codehosting/bzrutils.py 2010-09-21 14:39:59 +0000
+++ lib/lp/codehosting/bzrutils.py 2010-10-19 20:46:20 +0000
@@ -162,6 +162,7 @@
162162
163def make_oops_logging_exception_hook(error_utility, request):163def make_oops_logging_exception_hook(error_utility, request):
164 """Make a hook for logging OOPSes."""164 """Make a hook for logging OOPSes."""
165
165 def log_oops():166 def log_oops():
166 error_utility.raising(sys.exc_info(), request)167 error_utility.raising(sys.exc_info(), request)
167 return log_oops168 return log_oops
@@ -340,6 +341,7 @@
340341
341def makeURLChecker(allowed_scheme):342def makeURLChecker(allowed_scheme):
342 """Make a callable that rejects URLs not on the given scheme."""343 """Make a callable that rejects URLs not on the given scheme."""
344
343 def checkURL(url):345 def checkURL(url):
344 """Check that `url` is safe to open."""346 """Check that `url` is safe to open."""
345 if URI(url).scheme != allowed_scheme:347 if URI(url).scheme != allowed_scheme:
@@ -374,3 +376,13 @@
374 yield376 yield
375 finally:377 finally:
376 branch.unlock()378 branch.unlock()
379
380
381@contextmanager
382def write_locked(branch):
383 """Provide a context in which the branch is write-locked."""
384 branch.lock_write()
385 try:
386 yield
387 finally:
388 branch.unlock()
377389
=== modified file 'lib/lp/codehosting/scanner/bzrsync.py'
--- lib/lp/codehosting/scanner/bzrsync.py 2010-09-09 02:26:42 +0000
+++ lib/lp/codehosting/scanner/bzrsync.py 2010-10-19 20:46:20 +0000
@@ -16,8 +16,11 @@
1616
17import logging17import logging
1818
19from bzrlib.graph import DictParentsProvider
20from bzrlib.revision import NULL_REVISION
19import pytz21import pytz
20import transaction22import transaction
23from storm.locals import Store
21from zope.component import getUtility24from zope.component import getUtility
22from zope.event import notify25from zope.event import notify
2326
@@ -25,6 +28,8 @@
2528
26from lp.code.interfaces.branchjob import IRosettaUploadJobSource29from lp.code.interfaces.branchjob import IRosettaUploadJobSource
27from lp.code.interfaces.revision import IRevisionSet30from lp.code.interfaces.revision import IRevisionSet
31from lp.code.model.branchrevision import (BranchRevision)
32from lp.code.model.revision import Revision
28from lp.codehosting import iter_list_chunks33from lp.codehosting import iter_list_chunks
29from lp.codehosting.scanner import events34from lp.codehosting.scanner import events
30from lp.translations.interfaces.translationtemplatesbuildjob import (35from lp.translations.interfaces.translationtemplatesbuildjob import (
@@ -65,7 +70,8 @@
6570
66 * Revision: there must be one Revision row for each revision in the71 * Revision: there must be one Revision row for each revision in the
67 branch ancestry. If the row for a revision that has just been added72 branch ancestry. If the row for a revision that has just been added
68 to the branch is already present, it must be checked for consistency.73 to the branch is already present, it must be checked for
74 consistency.
6975
70 * BranchRevision: there must be one BrancheRevision row for each76 * BranchRevision: there must be one BrancheRevision row for each
71 revision in the branch ancestry. If history revisions became merged77 revision in the branch ancestry. If history revisions became merged
@@ -78,20 +84,21 @@
78 self.logger.info(" from %s", bzr_branch.base)84 self.logger.info(" from %s", bzr_branch.base)
79 # Get the history and ancestry from the branch first, to fail early85 # Get the history and ancestry from the branch first, to fail early
80 # if something is wrong with the branch.86 # if something is wrong with the branch.
81 bzr_ancestry, bzr_history = self.retrieveBranchDetails(bzr_branch)87 self.logger.info("Retrieving history from bzrlib.")
88 bzr_history = bzr_branch.revision_history()
82 # The BranchRevision, Revision and RevisionParent tables are only89 # The BranchRevision, Revision and RevisionParent tables are only
83 # written to by the branch-scanner, so they are not subject to90 # written to by the branch-scanner, so they are not subject to
84 # write-lock contention. Update them all in a single transaction to91 # write-lock contention. Update them all in a single transaction to
85 # improve the performance and allow garbage collection in the future.92 # improve the performance and allow garbage collection in the future.
86 db_ancestry, db_history = self.retrieveDatabaseAncestry()93 db_ancestry, db_history = self.retrieveDatabaseAncestry()
8794
88 (added_ancestry, branchrevisions_to_delete,95 (new_ancestry, branchrevisions_to_delete,
89 revids_to_insert) = self.planDatabaseChanges(96 revids_to_insert) = self.planDatabaseChanges(
90 bzr_branch, bzr_ancestry, bzr_history, db_ancestry, db_history)97 bzr_branch, bzr_history, db_ancestry, db_history)
91 added_ancestry.difference_update(98 new_db_revs = (
92 getUtility(IRevisionSet).onlyPresent(added_ancestry))99 new_ancestry - getUtility(IRevisionSet).onlyPresent(new_ancestry))
93 self.logger.info("Adding %s new revisions.", len(added_ancestry))100 self.logger.info("Adding %s new revisions.", len(new_db_revs))
94 for revids in iter_list_chunks(list(added_ancestry), 1000):101 for revids in iter_list_chunks(list(new_db_revs), 1000):
95 revisions = self.getBazaarRevisions(bzr_branch, revids)102 revisions = self.getBazaarRevisions(bzr_branch, revids)
96 for revision in revisions:103 for revision in revisions:
97 # This would probably go much faster if we found some way to104 # This would probably go much faster if we found some way to
@@ -122,7 +129,7 @@
122 self.updateBranchStatus(bzr_history)129 self.updateBranchStatus(bzr_history)
123 notify(130 notify(
124 events.ScanCompleted(131 events.ScanCompleted(
125 self.db_branch, bzr_branch, bzr_ancestry, self.logger))132 self.db_branch, bzr_branch, self.logger, new_ancestry))
126 transaction.commit()133 transaction.commit()
127134
128 def retrieveDatabaseAncestry(self):135 def retrieveDatabaseAncestry(self):
@@ -131,28 +138,40 @@
131 db_ancestry, db_history = self.db_branch.getScannerData()138 db_ancestry, db_history = self.db_branch.getScannerData()
132 return db_ancestry, db_history139 return db_ancestry, db_history
133140
134 def retrieveBranchDetails(self, bzr_branch):141 def _getRevisionGraph(self, bzr_branch, db_last):
135 """Retrieve ancestry from the the bzr branch on disk."""142 if bzr_branch.repository.has_revision(db_last):
136 self.logger.info("Retrieving ancestry from bzrlib.")143 return bzr_branch.repository.get_graph()
137 last_revision = bzr_branch.last_revision()144 revisions = Store.of(self.db_branch).find(Revision,
138 # Make bzr_ancestry a set for consistency with db_ancestry.145 BranchRevision.branch_id == self.db_branch.id,
139 bzr_ancestry_ordered = (146 Revision.id == BranchRevision.revision_id)
140 bzr_branch.repository.get_ancestry(last_revision))147 parent_map = dict(
141 first_ancestor = bzr_ancestry_ordered.pop(0)148 (r.revision_id, r.parent_ids) for r in revisions)
142 assert first_ancestor is None, 'history horizons are not supported'149 parents_provider = DictParentsProvider(parent_map)
143 bzr_ancestry = set(bzr_ancestry_ordered)150
144 bzr_history = bzr_branch.revision_history()151 class PPSource:
145 return bzr_ancestry, bzr_history152
146153 @staticmethod
147 def planDatabaseChanges(self, bzr_branch, bzr_ancestry, bzr_history,154 def _make_parents_provider():
148 db_ancestry, db_history):155 return parents_provider
149 """Plan database changes to synchronize with bzrlib data.156
150157 return bzr_branch.repository.get_graph(PPSource)
151 Use the data retrieved by `retrieveDatabaseAncestry` and158
152 `retrieveBranchDetails` to plan the changes to apply to the database.159 def getAncestryDelta(self, bzr_branch):
153 """160 bzr_last = bzr_branch.last_revision()
154 self.logger.info("Planning changes.")161 db_last = self.db_branch.last_scanned_id
155 # Find the length of the common history.162 if db_last is None:
163 added_ancestry = set(bzr_branch.repository.get_ancestry(bzr_last))
164 added_ancestry.discard(None)
165 removed_ancestry = set()
166 else:
167 graph = self._getRevisionGraph(bzr_branch, db_last)
168 added_ancestry, removed_ancestry = (
169 graph.find_difference(bzr_last, db_last))
170 added_ancestry.discard(NULL_REVISION)
171 return added_ancestry, removed_ancestry
172
173 def getHistoryDelta(self, bzr_history, db_history):
174 self.logger.info("Calculating history delta.")
156 common_len = min(len(bzr_history), len(db_history))175 common_len = min(len(bzr_history), len(db_history))
157 while common_len > 0:176 while common_len > 0:
158 # The outer conditional improves efficiency. Without it, the177 # The outer conditional improves efficiency. Without it, the
@@ -165,39 +184,44 @@
165 if db_history[:common_len] == bzr_history[:common_len]:184 if db_history[:common_len] == bzr_history[:common_len]:
166 break185 break
167 common_len -= 1186 common_len -= 1
168
169 # Revisions added to the branch's ancestry.
170 added_ancestry = bzr_ancestry.difference(db_ancestry)
171
172 # Revision added or removed from the branch's history. These lists may187 # Revision added or removed from the branch's history. These lists may
173 # include revisions whose history position has merely changed.188 # include revisions whose history position has merely changed.
174 removed_history = db_history[common_len:]189 removed_history = db_history[common_len:]
175 added_history = bzr_history[common_len:]190 added_history = bzr_history[common_len:]
191 return added_history, removed_history
192
193 def planDatabaseChanges(self, bzr_branch, bzr_history, db_ancestry,
194 db_history):
195 """Plan database changes to synchronize with bzrlib data.
196
197 Use the data retrieved by `retrieveDatabaseAncestry` and
198 `retrieveBranchDetails` to plan the changes to apply to the database.
199 """
200 self.logger.info("Planning changes.")
201 # Find the length of the common history.
202 added_history, removed_history = self.getHistoryDelta(
203 bzr_history, db_history)
204 added_ancestry, removed_ancestry = self.getAncestryDelta(bzr_branch)
176205
177 notify(206 notify(
178 events.RevisionsRemoved(207 events.RevisionsRemoved(
179 self.db_branch, bzr_branch, removed_history))208 self.db_branch, bzr_branch, removed_history))
180209
181 # Merged (non-history) revisions in the database and the bzr branch.
182 old_merged = db_ancestry.difference(db_history)
183 new_merged = bzr_ancestry.difference(bzr_history)
184
185 # Revisions added or removed from the set of merged revisions.
186 removed_merged = old_merged.difference(new_merged)
187 added_merged = new_merged.difference(old_merged)
188
189 # We must delete BranchRevision rows for all revisions which where210 # We must delete BranchRevision rows for all revisions which where
190 # removed from the ancestry or whose sequence value has changed.211 # removed from the ancestry or whose sequence value has changed.
191 branchrevisions_to_delete = list(212 branchrevisions_to_delete = set(removed_history)
192 removed_merged.union(removed_history))213 branchrevisions_to_delete.update(removed_ancestry)
214 branchrevisions_to_delete.update(
215 set(added_history).difference(added_ancestry))
193216
194 # We must insert BranchRevision rows for all revisions which were217 # We must insert BranchRevision rows for all revisions which were
195 # added to the ancestry or whose sequence value has changed.218 # added to the ancestry or whose sequence value has changed.
219 last_revno = len(bzr_history)
196 revids_to_insert = dict(220 revids_to_insert = dict(
197 self.getRevisions(221 self.revisionsToInsert(
198 bzr_history, added_merged.union(added_history)))222 added_history, last_revno, added_ancestry))
199223
200 return (added_ancestry, branchrevisions_to_delete,224 return (added_ancestry, list(branchrevisions_to_delete),
201 revids_to_insert)225 revids_to_insert)
202226
203 def getBazaarRevisions(self, bzr_branch, revisions):227 def getBazaarRevisions(self, bzr_branch, revisions):
@@ -228,17 +252,20 @@
228 self.db_branch, bzr_branch, db_revision, bzr_revision,252 self.db_branch, bzr_branch, db_revision, bzr_revision,
229 revids_to_insert[revision_id]))253 revids_to_insert[revision_id]))
230254
231 def getRevisions(self, bzr_history, revision_subset):255 @staticmethod
232 """Iterate over '(revid, revno)' pairs in a branch's ancestry.256 def revisionsToInsert(added_history, last_revno, added_ancestry):
257 """Calculate the revisions to insert and their revnos.
233258
234 Generate a sequence of (revision-id, sequence) pairs to be inserted259 :param added_history: A list of revision ids added to the revision
235 into the branchrevision table.260 history in parent-to-child order.
261 :param last_revno: The revno of the last revision.
262 :param added_ancestry: A set of revisions that have been added to the
263 ancestry of the branch. May overlap with added_history.
236 """264 """
237 for (index, revision_id) in enumerate(bzr_history):265 start_revno = last_revno - len(added_history) + 1
238 if revision_id in revision_subset:266 for (revno, revision_id) in enumerate(added_history, start_revno):
239 # sequence numbers start from 1267 yield revision_id, revno
240 yield revision_id, index + 1268 for revision_id in added_ancestry.difference(added_history):
241 for revision_id in revision_subset.difference(set(bzr_history)):
242 yield revision_id, None269 yield revision_id, None
243270
244 def deleteBranchRevisions(self, revision_ids_to_delete):271 def deleteBranchRevisions(self, revision_ids_to_delete):
245272
=== modified file 'lib/lp/codehosting/scanner/events.py'
--- lib/lp/codehosting/scanner/events.py 2010-08-20 20:31:18 +0000
+++ lib/lp/codehosting/scanner/events.py 2010-10-19 20:46:20 +0000
@@ -111,6 +111,7 @@
111 ScannerEvent.__init__(self, db_branch, bzr_branch)111 ScannerEvent.__init__(self, db_branch, bzr_branch)
112 self.removed_history = removed_history112 self.removed_history = removed_history
113113
114
114class IScanCompleted(IObjectEvent):115class IScanCompleted(IObjectEvent):
115 """The scan has been completed and the database is up-to-date."""116 """The scan has been completed and the database is up-to-date."""
116117
@@ -120,7 +121,7 @@
120121
121 implements(IScanCompleted)122 implements(IScanCompleted)
122123
123 def __init__(self, db_branch, bzr_branch, bzr_ancestry, logger):124 def __init__(self, db_branch, bzr_branch, logger, new_ancestry):
124 """Construct a `ScanCompleted` event.125 """Construct a `ScanCompleted` event.
125126
126 :param db_branch: The database branch.127 :param db_branch: The database branch.
@@ -131,7 +132,7 @@
131 information, such as merges that we find.132 information, such as merges that we find.
132 """133 """
133 ScannerEvent.__init__(self, db_branch, bzr_branch)134 ScannerEvent.__init__(self, db_branch, bzr_branch)
134 self.bzr_ancestry = bzr_ancestry135 self.new_ancestry = new_ancestry
135 # This is kind of ick. In a strict Zope sense, the logger should136 # This is kind of ick. In a strict Zope sense, the logger should
136 # probably be a registered utility.137 # probably be a registered utility.
137 self.logger = logger138 self.logger = logger
138139
=== modified file 'lib/lp/codehosting/scanner/mergedetection.py'
--- lib/lp/codehosting/scanner/mergedetection.py 2010-08-20 20:31:18 +0000
+++ lib/lp/codehosting/scanner/mergedetection.py 2010-10-19 20:46:20 +0000
@@ -78,7 +78,7 @@
78 determine which other branches this branch has been merged into.78 determine which other branches this branch has been merged into.
79 """79 """
80 db_branch = scan_completed.db_branch80 db_branch = scan_completed.db_branch
81 bzr_ancestry = scan_completed.bzr_ancestry81 new_ancestry = scan_completed.new_ancestry
82 logger = scan_completed.logger82 logger = scan_completed.logger
8383
84 # XXX: JonathanLange 2009-05-05 spec=package-branches: Yet another thing84 # XXX: JonathanLange 2009-05-05 spec=package-branches: Yet another thing
@@ -112,7 +112,7 @@
112 # If the tip revisions are the same, then it is the same112 # If the tip revisions are the same, then it is the same
113 # branch, not one merged into the other.113 # branch, not one merged into the other.
114 pass114 pass
115 elif last_scanned in bzr_ancestry:115 elif last_scanned in new_ancestry:
116 merge_detected(logger, branch, db_branch)116 merge_detected(logger, branch, db_branch)
117117
118118
@@ -140,7 +140,7 @@
140def auto_merge_proposals(scan_completed):140def auto_merge_proposals(scan_completed):
141 """Detect merged proposals."""141 """Detect merged proposals."""
142 db_branch = scan_completed.db_branch142 db_branch = scan_completed.db_branch
143 bzr_ancestry = scan_completed.bzr_ancestry143 new_ancestry = scan_completed.new_ancestry
144 logger = scan_completed.logger144 logger = scan_completed.logger
145145
146 # Check landing candidates in non-terminal states to see if their tip146 # Check landing candidates in non-terminal states to see if their tip
@@ -159,7 +159,7 @@
159 scan_completed.bzr_branch.iter_merge_sorted_revisions())159 scan_completed.bzr_branch.iter_merge_sorted_revisions())
160 for proposal in db_branch.landing_candidates:160 for proposal in db_branch.landing_candidates:
161 tip_rev_id = proposal.source_branch.last_scanned_id161 tip_rev_id = proposal.source_branch.last_scanned_id
162 if tip_rev_id in bzr_ancestry:162 if tip_rev_id in new_ancestry:
163 merged_revno = find_merged_revno(merge_sorted, tip_rev_id)163 merged_revno = find_merged_revno(merge_sorted, tip_rev_id)
164 # Remember so we can find the merged revision number.164 # Remember so we can find the merged revision number.
165 merge_detected(165 merge_detected(
166166
=== modified file 'lib/lp/codehosting/scanner/tests/test_bzrsync.py'
--- lib/lp/codehosting/scanner/tests/test_bzrsync.py 2010-10-04 19:50:45 +0000
+++ lib/lp/codehosting/scanner/tests/test_bzrsync.py 2010-10-19 20:46:20 +0000
@@ -38,9 +38,10 @@
38 RevisionAuthor,38 RevisionAuthor,
39 RevisionParent,39 RevisionParent,
40 )40 )
41from lp.codehosting.bzrutils import write_locked
41from lp.codehosting.scanner.bzrsync import BzrSync42from lp.codehosting.scanner.bzrsync import BzrSync
42from lp.services.osutils import override_environ43from lp.services.osutils import override_environ
43from lp.testing import TestCaseWithFactory44from lp.testing import TestCaseWithFactory, temp_dir
44from lp.translations.interfaces.translations import (45from lp.translations.interfaces.translations import (
45 TranslationsBranchImportMode,46 TranslationsBranchImportMode,
46 )47 )
@@ -391,37 +392,99 @@
391 self.assertEqual(rev_1.revision_date, dt)392 self.assertEqual(rev_1.revision_date, dt)
392 self.assertEqual(rev_2.revision_date, dt)393 self.assertEqual(rev_2.revision_date, dt)
393394
394 def test_get_revisions_empty(self):395 def getAncestryDelta_test(self, clean_repository=False):
396 """"Test various ancestry delta calculations.
397
398 :param clean_repository: If True, perform calculations with a branch
399 whose repository contains only revisions in the ancestry of the
400 tip.
401 """
402 (db_branch, bzr_tree), ignored = self.makeBranchWithMerge(
403 'base', 'trunk', 'branch', 'merge')
404 bzr_branch = bzr_tree.branch
405 self.factory.makeBranchRevision(db_branch, 'base', 0)
406 self.factory.makeBranchRevision(
407 db_branch, 'trunk', 1, parent_ids=['base'])
408 self.factory.makeBranchRevision(
409 db_branch, 'branch', None, parent_ids=['base'])
410 self.factory.makeBranchRevision(
411 db_branch, 'merge', 2, parent_ids=['trunk', 'branch'])
412 sync = self.makeBzrSync(db_branch)
413 self.useContext(write_locked(bzr_branch))
414
415 def get_delta(bzr_rev, db_rev):
416 db_branch.last_scanned_id = db_rev
417 graph = bzr_branch.repository.get_graph()
418 revno = graph.find_distance_to_null(bzr_rev, [])
419 if clean_repository:
420 tempdir = self.useContext(temp_dir())
421 delta_branch = self.createBranchAtURL(tempdir)
422 self.useContext(write_locked(delta_branch))
423 delta_branch.pull(bzr_branch, stop_revision=bzr_rev)
424 else:
425 bzr_branch.set_last_revision_info(revno, bzr_rev)
426 delta_branch = bzr_branch
427 return sync.getAncestryDelta(delta_branch)
428
429 added_ancestry, removed_ancestry = get_delta('merge', None)
430 # All revisions are new for an unscanned branch
431 self.assertEqual(
432 set(['base', 'trunk', 'branch', 'merge']), added_ancestry)
433 self.assertEqual(set(), removed_ancestry)
434 added_ancestry, removed_ancestry = get_delta('merge', 'base')
435 self.assertEqual(
436 set(['trunk', 'branch', 'merge']), added_ancestry)
437 self.assertEqual(set(), removed_ancestry)
438 added_ancestry, removed_ancestry = get_delta(NULL_REVISION, 'merge')
439 self.assertEqual(
440 set(), added_ancestry)
441 self.assertEqual(
442 set(['base', 'trunk', 'branch', 'merge']), removed_ancestry)
443 added_ancestry, removed_ancestry = get_delta('base', 'merge')
444 self.assertEqual(
445 set(), added_ancestry)
446 self.assertEqual(
447 set(['trunk', 'branch', 'merge']), removed_ancestry)
448 added_ancestry, removed_ancestry = get_delta('trunk', 'branch')
449 self.assertEqual(set(['trunk']), added_ancestry)
450 self.assertEqual(set(['branch']), removed_ancestry)
451
452 def test_getAncestryDelta(self):
453 """"Test ancestry delta calculations with a dirty repository."""
454 return self.getAncestryDelta_test()
455
456 def test_getAncestryDelta_clean_repository(self):
457 """"Test ancestry delta calculations with a clean repository."""
458 return self.getAncestryDelta_test(clean_repository=True)
459
460 def test_revisionsToInsert_empty(self):
395 # An empty branch should have no revisions.461 # An empty branch should have no revisions.
396 bzrsync = self.makeBzrSync(self.db_branch)
397 bzr_ancestry, bzr_history = (
398 bzrsync.retrieveBranchDetails(self.bzr_branch))
399 self.assertEqual(462 self.assertEqual(
400 [], list(bzrsync.getRevisions(bzr_history, bzr_ancestry)))463 [], list(BzrSync.revisionsToInsert([], 0, set())))
401464
402 def test_get_revisions_linear(self):465 def test_revisionsToInsert_linear(self):
403 # If the branch has a linear ancestry, getRevisions() should yield466 # If the branch has a linear ancestry, revisionsToInsert() should
404 # each revision along with a sequence number, starting at 1.467 # yield each revision along with a sequence number, starting at 1.
405 self.commitRevision(rev_id='rev-1')468 self.commitRevision(rev_id='rev-1')
406 bzrsync = self.makeBzrSync(self.db_branch)469 bzrsync = self.makeBzrSync(self.db_branch)
407 bzr_ancestry, bzr_history = (470 bzr_history = self.bzr_branch.revision_history()
408 bzrsync.retrieveBranchDetails(self.bzr_branch))471 added_ancestry = bzrsync.getAncestryDelta(self.bzr_branch)[0]
409 self.assertEqual(472 result = bzrsync.revisionsToInsert(
410 [('rev-1', 1)],473 bzr_history, self.bzr_branch.revno(), added_ancestry)
411 list(bzrsync.getRevisions(bzr_history, bzr_ancestry)))474 self.assertEqual({'rev-1': 1}, dict(result))
412475
413 def test_get_revisions_branched(self):476 def test_revisionsToInsert_branched(self):
414 # Confirm that these revisions are generated by getRevisions with None477 # Confirm that these revisions are generated by getRevisions with None
415 # as the sequence 'number'.478 # as the sequence 'number'.
416 (db_branch, bzr_tree), ignored = self.makeBranchWithMerge(479 (db_branch, bzr_tree), ignored = self.makeBranchWithMerge(
417 'base', 'trunk', 'branch', 'merge')480 'base', 'trunk', 'branch', 'merge')
418 bzrsync = self.makeBzrSync(db_branch)481 bzrsync = self.makeBzrSync(db_branch)
419 bzr_ancestry, bzr_history = (482 bzr_history = bzr_tree.branch.revision_history()
420 bzrsync.retrieveBranchDetails(bzr_tree.branch))483 added_ancestry = bzrsync.getAncestryDelta(bzr_tree.branch)[0]
421 expected = set(484 expected = {'base': 1, 'trunk': 2, 'merge': 3, 'branch': None}
422 [('base', 1), ('trunk', 2), ('merge', 3), ('branch', None)])
423 self.assertEqual(485 self.assertEqual(
424 expected, set(bzrsync.getRevisions(bzr_history, bzr_ancestry)))486 expected, dict(bzrsync.revisionsToInsert(bzr_history,
487 bzr_tree.branch.revno(), added_ancestry)))
425488
426 def test_sync_with_merged_branches(self):489 def test_sync_with_merged_branches(self):
427 # Confirm that when we syncHistory, all of the revisions are included490 # Confirm that when we syncHistory, all of the revisions are included
@@ -460,19 +523,6 @@
460 expected = set([(1, 'base'), (2, 'branch')])523 expected = set([(1, 'base'), (2, 'branch')])
461 self.assertEqual(self.getBranchRevisions(db_trunk), expected)524 self.assertEqual(self.getBranchRevisions(db_trunk), expected)
462525
463 def test_retrieveBranchDetails(self):
464 # retrieveBranchDetails should set last_revision, bzr_ancestry and
465 # bzr_history on the BzrSync instance to match the information in the
466 # Bazaar branch.
467 (db_trunk, trunk_tree), ignored = self.makeBranchWithMerge(
468 'base', 'trunk', 'branch', 'merge')
469 bzrsync = self.makeBzrSync(db_trunk)
470 bzr_ancestry, bzr_history = (
471 bzrsync.retrieveBranchDetails(trunk_tree.branch))
472 expected_ancestry = set(['base', 'trunk', 'branch', 'merge'])
473 self.assertEqual(expected_ancestry, bzr_ancestry)
474 self.assertEqual(['base', 'trunk', 'merge'], bzr_history)
475
476 def test_retrieveDatabaseAncestry(self):526 def test_retrieveDatabaseAncestry(self):
477 # retrieveDatabaseAncestry should set db_ancestry and db_history to527 # retrieveDatabaseAncestry should set db_ancestry and db_history to
478 # Launchpad's current understanding of the branch state.528 # Launchpad's current understanding of the branch state.
479529
=== modified file 'lib/lp/codehosting/scanner/tests/test_mergedetection.py'
--- lib/lp/codehosting/scanner/tests/test_mergedetection.py 2010-10-04 19:50:45 +0000
+++ lib/lp/codehosting/scanner/tests/test_mergedetection.py 2010-10-19 20:46:20 +0000
@@ -197,11 +197,11 @@
197 mergedetection.merge_detected = self._original_merge_detected197 mergedetection.merge_detected = self._original_merge_detected
198 TestCaseWithFactory.tearDown(self)198 TestCaseWithFactory.tearDown(self)
199199
200 def autoMergeBranches(self, db_branch, bzr_ancestry):200 def autoMergeBranches(self, db_branch, new_ancestry):
201 mergedetection.auto_merge_branches(201 mergedetection.auto_merge_branches(
202 events.ScanCompleted(202 events.ScanCompleted(
203 db_branch=db_branch, bzr_branch=None,203 db_branch=db_branch, bzr_branch=None,
204 bzr_ancestry=bzr_ancestry, logger=None))204 logger=None, new_ancestry=new_ancestry))
205205
206 def mergeDetected(self, logger, source, target):206 def mergeDetected(self, logger, source, target):
207 # Record the merged branches207 # Record the merged branches
@@ -360,7 +360,7 @@
360 target = self.factory.makeBranchTargetBranch(source.target)360 target = self.factory.makeBranchTargetBranch(source.target)
361 target.product.development_focus.branch = target361 target.product.development_focus.branch = target
362 logger = logging.getLogger('test')362 logger = logging.getLogger('test')
363 notify(events.ScanCompleted(target, None, ['23foo'], logger))363 notify(events.ScanCompleted(target, None, logger, ['23foo']))
364 self.assertEqual(364 self.assertEqual(
365 BranchLifecycleStatus.MERGED, source.lifecycle_status)365 BranchLifecycleStatus.MERGED, source.lifecycle_status)
366366
367367
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-10-18 10:19:56 +0000
+++ lib/lp/testing/factory.py 2010-10-19 20:46:20 +0000
@@ -1312,8 +1312,10 @@
1312 '', parent.revision_id, None, None, None)1312 '', parent.revision_id, None, None, None)
1313 branch.updateScannedDetails(parent, sequence)1313 branch.updateScannedDetails(parent, sequence)
13141314
1315 def makeBranchRevision(self, branch, revision_id, sequence=None):1315 def makeBranchRevision(self, branch, revision_id, sequence=None,
1316 revision = self.makeRevision(rev_id=revision_id)1316 parent_ids=None):
1317 revision = self.makeRevision(
1318 rev_id=revision_id, parent_ids=parent_ids)
1317 return branch.createBranchRevision(sequence, revision)1319 return branch.createBranchRevision(sequence, revision)
13181320
1319 def makeBug(self, product=None, owner=None, bug_watch_url=None,1321 def makeBug(self, product=None, owner=None, bug_watch_url=None,
@@ -1846,22 +1848,6 @@
1846 expires=expires, restricted=restricted)1848 expires=expires, restricted=restricted)
1847 return library_file_alias1849 return library_file_alias
18481850
1849 def makePackageDiff(self, from_spr=None, to_spr=None):
1850 """Make a completed package diff."""
1851 if from_spr is None:
1852 from_spr = self.makeSourcePackageRelease()
1853 if to_spr is None:
1854 to_spr = self.makeSourcePackageRelease()
1855
1856 diff = from_spr.requestDiffTo(
1857 from_spr.creator, to_spr)
1858
1859 naked_diff = removeSecurityProxy(diff)
1860 naked_diff.status = PackageDiffStatus.COMPLETED
1861 naked_diff.diff_content = self.makeLibraryFileAlias()
1862 naked_diff.date_fulfilled = UTC_NOW
1863 return diff
1864
1865 def makeDistribution(self, name=None, displayname=None, owner=None,1851 def makeDistribution(self, name=None, displayname=None, owner=None,
1866 members=None, title=None, aliases=None):1852 members=None, title=None, aliases=None):
1867 """Make a new distribution."""1853 """Make a new distribution."""