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
1=== modified file 'lib/lp/codehosting/bzrutils.py'
2--- lib/lp/codehosting/bzrutils.py 2010-09-21 14:39:59 +0000
3+++ lib/lp/codehosting/bzrutils.py 2010-10-19 20:46:20 +0000
4@@ -162,6 +162,7 @@
5
6 def make_oops_logging_exception_hook(error_utility, request):
7 """Make a hook for logging OOPSes."""
8+
9 def log_oops():
10 error_utility.raising(sys.exc_info(), request)
11 return log_oops
12@@ -340,6 +341,7 @@
13
14 def makeURLChecker(allowed_scheme):
15 """Make a callable that rejects URLs not on the given scheme."""
16+
17 def checkURL(url):
18 """Check that `url` is safe to open."""
19 if URI(url).scheme != allowed_scheme:
20@@ -374,3 +376,13 @@
21 yield
22 finally:
23 branch.unlock()
24+
25+
26+@contextmanager
27+def write_locked(branch):
28+ """Provide a context in which the branch is write-locked."""
29+ branch.lock_write()
30+ try:
31+ yield
32+ finally:
33+ branch.unlock()
34
35=== modified file 'lib/lp/codehosting/scanner/bzrsync.py'
36--- lib/lp/codehosting/scanner/bzrsync.py 2010-09-09 02:26:42 +0000
37+++ lib/lp/codehosting/scanner/bzrsync.py 2010-10-19 20:46:20 +0000
38@@ -16,8 +16,11 @@
39
40 import logging
41
42+from bzrlib.graph import DictParentsProvider
43+from bzrlib.revision import NULL_REVISION
44 import pytz
45 import transaction
46+from storm.locals import Store
47 from zope.component import getUtility
48 from zope.event import notify
49
50@@ -25,6 +28,8 @@
51
52 from lp.code.interfaces.branchjob import IRosettaUploadJobSource
53 from lp.code.interfaces.revision import IRevisionSet
54+from lp.code.model.branchrevision import (BranchRevision)
55+from lp.code.model.revision import Revision
56 from lp.codehosting import iter_list_chunks
57 from lp.codehosting.scanner import events
58 from lp.translations.interfaces.translationtemplatesbuildjob import (
59@@ -65,7 +70,8 @@
60
61 * Revision: there must be one Revision row for each revision in the
62 branch ancestry. If the row for a revision that has just been added
63- to the branch is already present, it must be checked for consistency.
64+ to the branch is already present, it must be checked for
65+ consistency.
66
67 * BranchRevision: there must be one BrancheRevision row for each
68 revision in the branch ancestry. If history revisions became merged
69@@ -78,20 +84,21 @@
70 self.logger.info(" from %s", bzr_branch.base)
71 # Get the history and ancestry from the branch first, to fail early
72 # if something is wrong with the branch.
73- bzr_ancestry, bzr_history = self.retrieveBranchDetails(bzr_branch)
74+ self.logger.info("Retrieving history from bzrlib.")
75+ bzr_history = bzr_branch.revision_history()
76 # The BranchRevision, Revision and RevisionParent tables are only
77 # written to by the branch-scanner, so they are not subject to
78 # write-lock contention. Update them all in a single transaction to
79 # improve the performance and allow garbage collection in the future.
80 db_ancestry, db_history = self.retrieveDatabaseAncestry()
81
82- (added_ancestry, branchrevisions_to_delete,
83+ (new_ancestry, branchrevisions_to_delete,
84 revids_to_insert) = self.planDatabaseChanges(
85- bzr_branch, bzr_ancestry, bzr_history, db_ancestry, db_history)
86- added_ancestry.difference_update(
87- getUtility(IRevisionSet).onlyPresent(added_ancestry))
88- self.logger.info("Adding %s new revisions.", len(added_ancestry))
89- for revids in iter_list_chunks(list(added_ancestry), 1000):
90+ bzr_branch, bzr_history, db_ancestry, db_history)
91+ new_db_revs = (
92+ new_ancestry - getUtility(IRevisionSet).onlyPresent(new_ancestry))
93+ self.logger.info("Adding %s new revisions.", len(new_db_revs))
94+ for revids in iter_list_chunks(list(new_db_revs), 1000):
95 revisions = self.getBazaarRevisions(bzr_branch, revids)
96 for revision in revisions:
97 # This would probably go much faster if we found some way to
98@@ -122,7 +129,7 @@
99 self.updateBranchStatus(bzr_history)
100 notify(
101 events.ScanCompleted(
102- self.db_branch, bzr_branch, bzr_ancestry, self.logger))
103+ self.db_branch, bzr_branch, self.logger, new_ancestry))
104 transaction.commit()
105
106 def retrieveDatabaseAncestry(self):
107@@ -131,28 +138,40 @@
108 db_ancestry, db_history = self.db_branch.getScannerData()
109 return db_ancestry, db_history
110
111- def retrieveBranchDetails(self, bzr_branch):
112- """Retrieve ancestry from the the bzr branch on disk."""
113- self.logger.info("Retrieving ancestry from bzrlib.")
114- last_revision = bzr_branch.last_revision()
115- # Make bzr_ancestry a set for consistency with db_ancestry.
116- bzr_ancestry_ordered = (
117- bzr_branch.repository.get_ancestry(last_revision))
118- first_ancestor = bzr_ancestry_ordered.pop(0)
119- assert first_ancestor is None, 'history horizons are not supported'
120- bzr_ancestry = set(bzr_ancestry_ordered)
121- bzr_history = bzr_branch.revision_history()
122- return bzr_ancestry, bzr_history
123-
124- def planDatabaseChanges(self, bzr_branch, bzr_ancestry, bzr_history,
125- db_ancestry, db_history):
126- """Plan database changes to synchronize with bzrlib data.
127-
128- Use the data retrieved by `retrieveDatabaseAncestry` and
129- `retrieveBranchDetails` to plan the changes to apply to the database.
130- """
131- self.logger.info("Planning changes.")
132- # Find the length of the common history.
133+ def _getRevisionGraph(self, bzr_branch, db_last):
134+ if bzr_branch.repository.has_revision(db_last):
135+ return bzr_branch.repository.get_graph()
136+ revisions = Store.of(self.db_branch).find(Revision,
137+ BranchRevision.branch_id == self.db_branch.id,
138+ Revision.id == BranchRevision.revision_id)
139+ parent_map = dict(
140+ (r.revision_id, r.parent_ids) for r in revisions)
141+ parents_provider = DictParentsProvider(parent_map)
142+
143+ class PPSource:
144+
145+ @staticmethod
146+ def _make_parents_provider():
147+ return parents_provider
148+
149+ return bzr_branch.repository.get_graph(PPSource)
150+
151+ def getAncestryDelta(self, bzr_branch):
152+ bzr_last = bzr_branch.last_revision()
153+ db_last = self.db_branch.last_scanned_id
154+ if db_last is None:
155+ added_ancestry = set(bzr_branch.repository.get_ancestry(bzr_last))
156+ added_ancestry.discard(None)
157+ removed_ancestry = set()
158+ else:
159+ graph = self._getRevisionGraph(bzr_branch, db_last)
160+ added_ancestry, removed_ancestry = (
161+ graph.find_difference(bzr_last, db_last))
162+ added_ancestry.discard(NULL_REVISION)
163+ return added_ancestry, removed_ancestry
164+
165+ def getHistoryDelta(self, bzr_history, db_history):
166+ self.logger.info("Calculating history delta.")
167 common_len = min(len(bzr_history), len(db_history))
168 while common_len > 0:
169 # The outer conditional improves efficiency. Without it, the
170@@ -165,39 +184,44 @@
171 if db_history[:common_len] == bzr_history[:common_len]:
172 break
173 common_len -= 1
174-
175- # Revisions added to the branch's ancestry.
176- added_ancestry = bzr_ancestry.difference(db_ancestry)
177-
178 # Revision added or removed from the branch's history. These lists may
179 # include revisions whose history position has merely changed.
180 removed_history = db_history[common_len:]
181 added_history = bzr_history[common_len:]
182+ return added_history, removed_history
183+
184+ def planDatabaseChanges(self, bzr_branch, bzr_history, db_ancestry,
185+ db_history):
186+ """Plan database changes to synchronize with bzrlib data.
187+
188+ Use the data retrieved by `retrieveDatabaseAncestry` and
189+ `retrieveBranchDetails` to plan the changes to apply to the database.
190+ """
191+ self.logger.info("Planning changes.")
192+ # Find the length of the common history.
193+ added_history, removed_history = self.getHistoryDelta(
194+ bzr_history, db_history)
195+ added_ancestry, removed_ancestry = self.getAncestryDelta(bzr_branch)
196
197 notify(
198 events.RevisionsRemoved(
199 self.db_branch, bzr_branch, removed_history))
200
201- # Merged (non-history) revisions in the database and the bzr branch.
202- old_merged = db_ancestry.difference(db_history)
203- new_merged = bzr_ancestry.difference(bzr_history)
204-
205- # Revisions added or removed from the set of merged revisions.
206- removed_merged = old_merged.difference(new_merged)
207- added_merged = new_merged.difference(old_merged)
208-
209 # We must delete BranchRevision rows for all revisions which where
210 # removed from the ancestry or whose sequence value has changed.
211- branchrevisions_to_delete = list(
212- removed_merged.union(removed_history))
213+ branchrevisions_to_delete = set(removed_history)
214+ branchrevisions_to_delete.update(removed_ancestry)
215+ branchrevisions_to_delete.update(
216+ set(added_history).difference(added_ancestry))
217
218 # We must insert BranchRevision rows for all revisions which were
219 # added to the ancestry or whose sequence value has changed.
220+ last_revno = len(bzr_history)
221 revids_to_insert = dict(
222- self.getRevisions(
223- bzr_history, added_merged.union(added_history)))
224+ self.revisionsToInsert(
225+ added_history, last_revno, added_ancestry))
226
227- return (added_ancestry, branchrevisions_to_delete,
228+ return (added_ancestry, list(branchrevisions_to_delete),
229 revids_to_insert)
230
231 def getBazaarRevisions(self, bzr_branch, revisions):
232@@ -228,17 +252,20 @@
233 self.db_branch, bzr_branch, db_revision, bzr_revision,
234 revids_to_insert[revision_id]))
235
236- def getRevisions(self, bzr_history, revision_subset):
237- """Iterate over '(revid, revno)' pairs in a branch's ancestry.
238+ @staticmethod
239+ def revisionsToInsert(added_history, last_revno, added_ancestry):
240+ """Calculate the revisions to insert and their revnos.
241
242- Generate a sequence of (revision-id, sequence) pairs to be inserted
243- into the branchrevision table.
244+ :param added_history: A list of revision ids added to the revision
245+ history in parent-to-child order.
246+ :param last_revno: The revno of the last revision.
247+ :param added_ancestry: A set of revisions that have been added to the
248+ ancestry of the branch. May overlap with added_history.
249 """
250- for (index, revision_id) in enumerate(bzr_history):
251- if revision_id in revision_subset:
252- # sequence numbers start from 1
253- yield revision_id, index + 1
254- for revision_id in revision_subset.difference(set(bzr_history)):
255+ start_revno = last_revno - len(added_history) + 1
256+ for (revno, revision_id) in enumerate(added_history, start_revno):
257+ yield revision_id, revno
258+ for revision_id in added_ancestry.difference(added_history):
259 yield revision_id, None
260
261 def deleteBranchRevisions(self, revision_ids_to_delete):
262
263=== modified file 'lib/lp/codehosting/scanner/events.py'
264--- lib/lp/codehosting/scanner/events.py 2010-08-20 20:31:18 +0000
265+++ lib/lp/codehosting/scanner/events.py 2010-10-19 20:46:20 +0000
266@@ -111,6 +111,7 @@
267 ScannerEvent.__init__(self, db_branch, bzr_branch)
268 self.removed_history = removed_history
269
270+
271 class IScanCompleted(IObjectEvent):
272 """The scan has been completed and the database is up-to-date."""
273
274@@ -120,7 +121,7 @@
275
276 implements(IScanCompleted)
277
278- def __init__(self, db_branch, bzr_branch, bzr_ancestry, logger):
279+ def __init__(self, db_branch, bzr_branch, logger, new_ancestry):
280 """Construct a `ScanCompleted` event.
281
282 :param db_branch: The database branch.
283@@ -131,7 +132,7 @@
284 information, such as merges that we find.
285 """
286 ScannerEvent.__init__(self, db_branch, bzr_branch)
287- self.bzr_ancestry = bzr_ancestry
288+ self.new_ancestry = new_ancestry
289 # This is kind of ick. In a strict Zope sense, the logger should
290 # probably be a registered utility.
291 self.logger = logger
292
293=== modified file 'lib/lp/codehosting/scanner/mergedetection.py'
294--- lib/lp/codehosting/scanner/mergedetection.py 2010-08-20 20:31:18 +0000
295+++ lib/lp/codehosting/scanner/mergedetection.py 2010-10-19 20:46:20 +0000
296@@ -78,7 +78,7 @@
297 determine which other branches this branch has been merged into.
298 """
299 db_branch = scan_completed.db_branch
300- bzr_ancestry = scan_completed.bzr_ancestry
301+ new_ancestry = scan_completed.new_ancestry
302 logger = scan_completed.logger
303
304 # XXX: JonathanLange 2009-05-05 spec=package-branches: Yet another thing
305@@ -112,7 +112,7 @@
306 # If the tip revisions are the same, then it is the same
307 # branch, not one merged into the other.
308 pass
309- elif last_scanned in bzr_ancestry:
310+ elif last_scanned in new_ancestry:
311 merge_detected(logger, branch, db_branch)
312
313
314@@ -140,7 +140,7 @@
315 def auto_merge_proposals(scan_completed):
316 """Detect merged proposals."""
317 db_branch = scan_completed.db_branch
318- bzr_ancestry = scan_completed.bzr_ancestry
319+ new_ancestry = scan_completed.new_ancestry
320 logger = scan_completed.logger
321
322 # Check landing candidates in non-terminal states to see if their tip
323@@ -159,7 +159,7 @@
324 scan_completed.bzr_branch.iter_merge_sorted_revisions())
325 for proposal in db_branch.landing_candidates:
326 tip_rev_id = proposal.source_branch.last_scanned_id
327- if tip_rev_id in bzr_ancestry:
328+ if tip_rev_id in new_ancestry:
329 merged_revno = find_merged_revno(merge_sorted, tip_rev_id)
330 # Remember so we can find the merged revision number.
331 merge_detected(
332
333=== modified file 'lib/lp/codehosting/scanner/tests/test_bzrsync.py'
334--- lib/lp/codehosting/scanner/tests/test_bzrsync.py 2010-10-04 19:50:45 +0000
335+++ lib/lp/codehosting/scanner/tests/test_bzrsync.py 2010-10-19 20:46:20 +0000
336@@ -38,9 +38,10 @@
337 RevisionAuthor,
338 RevisionParent,
339 )
340+from lp.codehosting.bzrutils import write_locked
341 from lp.codehosting.scanner.bzrsync import BzrSync
342 from lp.services.osutils import override_environ
343-from lp.testing import TestCaseWithFactory
344+from lp.testing import TestCaseWithFactory, temp_dir
345 from lp.translations.interfaces.translations import (
346 TranslationsBranchImportMode,
347 )
348@@ -391,37 +392,99 @@
349 self.assertEqual(rev_1.revision_date, dt)
350 self.assertEqual(rev_2.revision_date, dt)
351
352- def test_get_revisions_empty(self):
353+ def getAncestryDelta_test(self, clean_repository=False):
354+ """"Test various ancestry delta calculations.
355+
356+ :param clean_repository: If True, perform calculations with a branch
357+ whose repository contains only revisions in the ancestry of the
358+ tip.
359+ """
360+ (db_branch, bzr_tree), ignored = self.makeBranchWithMerge(
361+ 'base', 'trunk', 'branch', 'merge')
362+ bzr_branch = bzr_tree.branch
363+ self.factory.makeBranchRevision(db_branch, 'base', 0)
364+ self.factory.makeBranchRevision(
365+ db_branch, 'trunk', 1, parent_ids=['base'])
366+ self.factory.makeBranchRevision(
367+ db_branch, 'branch', None, parent_ids=['base'])
368+ self.factory.makeBranchRevision(
369+ db_branch, 'merge', 2, parent_ids=['trunk', 'branch'])
370+ sync = self.makeBzrSync(db_branch)
371+ self.useContext(write_locked(bzr_branch))
372+
373+ def get_delta(bzr_rev, db_rev):
374+ db_branch.last_scanned_id = db_rev
375+ graph = bzr_branch.repository.get_graph()
376+ revno = graph.find_distance_to_null(bzr_rev, [])
377+ if clean_repository:
378+ tempdir = self.useContext(temp_dir())
379+ delta_branch = self.createBranchAtURL(tempdir)
380+ self.useContext(write_locked(delta_branch))
381+ delta_branch.pull(bzr_branch, stop_revision=bzr_rev)
382+ else:
383+ bzr_branch.set_last_revision_info(revno, bzr_rev)
384+ delta_branch = bzr_branch
385+ return sync.getAncestryDelta(delta_branch)
386+
387+ added_ancestry, removed_ancestry = get_delta('merge', None)
388+ # All revisions are new for an unscanned branch
389+ self.assertEqual(
390+ set(['base', 'trunk', 'branch', 'merge']), added_ancestry)
391+ self.assertEqual(set(), removed_ancestry)
392+ added_ancestry, removed_ancestry = get_delta('merge', 'base')
393+ self.assertEqual(
394+ set(['trunk', 'branch', 'merge']), added_ancestry)
395+ self.assertEqual(set(), removed_ancestry)
396+ added_ancestry, removed_ancestry = get_delta(NULL_REVISION, 'merge')
397+ self.assertEqual(
398+ set(), added_ancestry)
399+ self.assertEqual(
400+ set(['base', 'trunk', 'branch', 'merge']), removed_ancestry)
401+ added_ancestry, removed_ancestry = get_delta('base', 'merge')
402+ self.assertEqual(
403+ set(), added_ancestry)
404+ self.assertEqual(
405+ set(['trunk', 'branch', 'merge']), removed_ancestry)
406+ added_ancestry, removed_ancestry = get_delta('trunk', 'branch')
407+ self.assertEqual(set(['trunk']), added_ancestry)
408+ self.assertEqual(set(['branch']), removed_ancestry)
409+
410+ def test_getAncestryDelta(self):
411+ """"Test ancestry delta calculations with a dirty repository."""
412+ return self.getAncestryDelta_test()
413+
414+ def test_getAncestryDelta_clean_repository(self):
415+ """"Test ancestry delta calculations with a clean repository."""
416+ return self.getAncestryDelta_test(clean_repository=True)
417+
418+ def test_revisionsToInsert_empty(self):
419 # An empty branch should have no revisions.
420- bzrsync = self.makeBzrSync(self.db_branch)
421- bzr_ancestry, bzr_history = (
422- bzrsync.retrieveBranchDetails(self.bzr_branch))
423 self.assertEqual(
424- [], list(bzrsync.getRevisions(bzr_history, bzr_ancestry)))
425+ [], list(BzrSync.revisionsToInsert([], 0, set())))
426
427- def test_get_revisions_linear(self):
428- # If the branch has a linear ancestry, getRevisions() should yield
429- # each revision along with a sequence number, starting at 1.
430+ def test_revisionsToInsert_linear(self):
431+ # If the branch has a linear ancestry, revisionsToInsert() should
432+ # yield each revision along with a sequence number, starting at 1.
433 self.commitRevision(rev_id='rev-1')
434 bzrsync = self.makeBzrSync(self.db_branch)
435- bzr_ancestry, bzr_history = (
436- bzrsync.retrieveBranchDetails(self.bzr_branch))
437- self.assertEqual(
438- [('rev-1', 1)],
439- list(bzrsync.getRevisions(bzr_history, bzr_ancestry)))
440+ bzr_history = self.bzr_branch.revision_history()
441+ added_ancestry = bzrsync.getAncestryDelta(self.bzr_branch)[0]
442+ result = bzrsync.revisionsToInsert(
443+ bzr_history, self.bzr_branch.revno(), added_ancestry)
444+ self.assertEqual({'rev-1': 1}, dict(result))
445
446- def test_get_revisions_branched(self):
447+ def test_revisionsToInsert_branched(self):
448 # Confirm that these revisions are generated by getRevisions with None
449 # as the sequence 'number'.
450 (db_branch, bzr_tree), ignored = self.makeBranchWithMerge(
451 'base', 'trunk', 'branch', 'merge')
452 bzrsync = self.makeBzrSync(db_branch)
453- bzr_ancestry, bzr_history = (
454- bzrsync.retrieveBranchDetails(bzr_tree.branch))
455- expected = set(
456- [('base', 1), ('trunk', 2), ('merge', 3), ('branch', None)])
457+ bzr_history = bzr_tree.branch.revision_history()
458+ added_ancestry = bzrsync.getAncestryDelta(bzr_tree.branch)[0]
459+ expected = {'base': 1, 'trunk': 2, 'merge': 3, 'branch': None}
460 self.assertEqual(
461- expected, set(bzrsync.getRevisions(bzr_history, bzr_ancestry)))
462+ expected, dict(bzrsync.revisionsToInsert(bzr_history,
463+ bzr_tree.branch.revno(), added_ancestry)))
464
465 def test_sync_with_merged_branches(self):
466 # Confirm that when we syncHistory, all of the revisions are included
467@@ -460,19 +523,6 @@
468 expected = set([(1, 'base'), (2, 'branch')])
469 self.assertEqual(self.getBranchRevisions(db_trunk), expected)
470
471- def test_retrieveBranchDetails(self):
472- # retrieveBranchDetails should set last_revision, bzr_ancestry and
473- # bzr_history on the BzrSync instance to match the information in the
474- # Bazaar branch.
475- (db_trunk, trunk_tree), ignored = self.makeBranchWithMerge(
476- 'base', 'trunk', 'branch', 'merge')
477- bzrsync = self.makeBzrSync(db_trunk)
478- bzr_ancestry, bzr_history = (
479- bzrsync.retrieveBranchDetails(trunk_tree.branch))
480- expected_ancestry = set(['base', 'trunk', 'branch', 'merge'])
481- self.assertEqual(expected_ancestry, bzr_ancestry)
482- self.assertEqual(['base', 'trunk', 'merge'], bzr_history)
483-
484 def test_retrieveDatabaseAncestry(self):
485 # retrieveDatabaseAncestry should set db_ancestry and db_history to
486 # Launchpad's current understanding of the branch state.
487
488=== modified file 'lib/lp/codehosting/scanner/tests/test_mergedetection.py'
489--- lib/lp/codehosting/scanner/tests/test_mergedetection.py 2010-10-04 19:50:45 +0000
490+++ lib/lp/codehosting/scanner/tests/test_mergedetection.py 2010-10-19 20:46:20 +0000
491@@ -197,11 +197,11 @@
492 mergedetection.merge_detected = self._original_merge_detected
493 TestCaseWithFactory.tearDown(self)
494
495- def autoMergeBranches(self, db_branch, bzr_ancestry):
496+ def autoMergeBranches(self, db_branch, new_ancestry):
497 mergedetection.auto_merge_branches(
498 events.ScanCompleted(
499 db_branch=db_branch, bzr_branch=None,
500- bzr_ancestry=bzr_ancestry, logger=None))
501+ logger=None, new_ancestry=new_ancestry))
502
503 def mergeDetected(self, logger, source, target):
504 # Record the merged branches
505@@ -360,7 +360,7 @@
506 target = self.factory.makeBranchTargetBranch(source.target)
507 target.product.development_focus.branch = target
508 logger = logging.getLogger('test')
509- notify(events.ScanCompleted(target, None, ['23foo'], logger))
510+ notify(events.ScanCompleted(target, None, logger, ['23foo']))
511 self.assertEqual(
512 BranchLifecycleStatus.MERGED, source.lifecycle_status)
513
514
515=== modified file 'lib/lp/testing/factory.py'
516--- lib/lp/testing/factory.py 2010-10-18 10:19:56 +0000
517+++ lib/lp/testing/factory.py 2010-10-19 20:46:20 +0000
518@@ -1312,8 +1312,10 @@
519 '', parent.revision_id, None, None, None)
520 branch.updateScannedDetails(parent, sequence)
521
522- def makeBranchRevision(self, branch, revision_id, sequence=None):
523- revision = self.makeRevision(rev_id=revision_id)
524+ def makeBranchRevision(self, branch, revision_id, sequence=None,
525+ parent_ids=None):
526+ revision = self.makeRevision(
527+ rev_id=revision_id, parent_ids=parent_ids)
528 return branch.createBranchRevision(sequence, revision)
529
530 def makeBug(self, product=None, owner=None, bug_watch_url=None,
531@@ -1846,22 +1848,6 @@
532 expires=expires, restricted=restricted)
533 return library_file_alias
534
535- def makePackageDiff(self, from_spr=None, to_spr=None):
536- """Make a completed package diff."""
537- if from_spr is None:
538- from_spr = self.makeSourcePackageRelease()
539- if to_spr is None:
540- to_spr = self.makeSourcePackageRelease()
541-
542- diff = from_spr.requestDiffTo(
543- from_spr.creator, to_spr)
544-
545- naked_diff = removeSecurityProxy(diff)
546- naked_diff.status = PackageDiffStatus.COMPLETED
547- naked_diff.diff_content = self.makeLibraryFileAlias()
548- naked_diff.date_fulfilled = UTC_NOW
549- return diff
550-
551 def makeDistribution(self, name=None, displayname=None, owner=None,
552 members=None, title=None, aliases=None):
553 """Make a new distribution."""