Merge lp:~vila/bzr/320119-exclude-ancestry into lp:bzr

Proposed by Vincent Ladeuil
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~vila/bzr/320119-exclude-ancestry
Merge into: lp:bzr
Prerequisite: lp:~vila/bzr/cleanup-log-direction
Diff against target: 428 lines (+189/-17)
8 files modified
NEWS (+7/-1)
bzrlib/branch.py (+16/-2)
bzrlib/builtins.py (+14/-2)
bzrlib/log.py (+35/-11)
bzrlib/tests/blackbox/test_log.py (+12/-0)
bzrlib/tests/per_branch/test_iter_merge_sorted_revisions.py (+44/-0)
bzrlib/tests/per_repository_reference/__init__.py (+1/-1)
bzrlib/tests/test_log.py (+60/-0)
To merge this branch: bzr merge lp:~vila/bzr/320119-exclude-ancestry
Reviewer Review Type Date Requested Status
Andrew Bennetts Needs Fixing
Review via email: mp+23394@code.launchpad.net

Commit message

Add --exclude-common-ancestry log option

Description of the change

This patch adds an --exclude-common-anccestry to bzr log so that -rX..Y becomes
a real graph difference (killing two birds with one stone, it also fixes bug #320119).

From now on, I consider that the log has reached the point where it's really
hard to add new features. The major culprit is the optimization to avoid
loading the whole graph, so no need to spend much time on the log code
unless this problem is correctly addressed.

The performance impact may be significant but I'd like real-life feedback on its
usage before trying to replace the is_ancestor() call by any caching of the X ancestry
(which may be quite significant anyway).

Again, having a better lazy-loaded graph ancestry is needed there.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

This seems like something other commands than log will want; perhaps we
should tackle it using an algreba, like git does?

Revision history for this message
Andrew Bennetts (spiv) wrote :

You have a few conflicts because this branch waited so long for a review. Sorry :(

You define a new stop_rule for iter_merge_sorted_revisions, but you haven't added it to the docstring. Please fix that.

It's a shame that make_branch_with_alternate_ancestries is duplicated in test_log and per_branch.test_iter_merge_sorted_revisions. Perhaps make it a function and have the latter import it from the former? Please at least add comments in both places noting the duplication.

(And further, it's a shame that the description of the ancestry is duplicated within that code... if only there were some way to do "make_branch_from_ascii_art_ancestry".)

Other than those, this seems ok to me. Once you fix those issues you can consider my vote upgraded to Approve.

I get the feeling that the next time we are tempted to another parameter to log we should think about refactoring the way we pass log options through the various layers (maybe with a LogOptions object? Or maybe Robert's idea about an algebra...), but this is ok for now.

review: Needs Fixing
Revision history for this message
Vincent Ladeuil (vila) wrote :

>>>>> Andrew Bennetts <email address hidden> writes:

    > Review: Needs Fixing

    > You have a few conflicts because this branch waited so long for a review. Sorry :(

    > You define a new stop_rule for iter_merge_sorted_revisions, but
    > you haven't added it to the docstring. Please fix that.

Done.

    > It's a shame that make_branch_with_alternate_ancestries is
    > duplicated in test_log and
    > per_branch.test_iter_merge_sorted_revisions. Perhaps make it a
    > function and have the latter import it from the former? Please at
    > least add comments in both places noting the duplication.

Since there is a valuable slight variation, I went with adding comments.

    > (And further, it's a shame that the description of the ancestry is
    > duplicated within that code... if only there were some way to do
    > "make_branch_from_ascii_art_ancestry".)

Hehe, ideally I'd prefer some GUI to define the graph and get both the
ascii art and the branchbuilder stuff from that. ascii art is *not* fun
to write ;)

    > Other than those, this seems ok to me. Once you fix those issues
    > you can consider my vote upgraded to Approve.

Thanks.

    > I get the feeling that the next time we are tempted to another
    > parameter to log we should think about refactoring the way we pass
    > log options through the various layers (maybe with a LogOptions
    > object?

Definitely, but we also need to wire that up to the command line as many
log formatters will want their own options, I've punted on that one so
far for lack of time to research how to catch the options not recognized
by the parser and allow plugins or any external code to give it a try.

    > Or maybe Robert's idea about an algebra...), but this is ok for
    > now.

Revision history for this message
bzr PQM (bzr-pqm) wrote :

Successful steps
Failure output:
All lines of log output:
Executing star-merge lp:~vila/bzr/320119-exclude-ancestry at Wed Apr 28 10:41:57 2010
['Nothing to merge.']

Revision history for this message
Vincent Ladeuil (vila) wrote :

Meh, where is that pqm failure coming from ?
I sent a single submission with feed-pqm and it was merged.... (I didn't get a success email for it though).

Revision history for this message
Robert Collins (lifeless) wrote :

This is probably due to lp taking too long to mark the branch as
merged: if it takes longer than pqm takes to try again, then pqm will
see it as still pending. Possibly we should:
mark things we succeed at as approved
adding a comment that it landed ok

or mark it as merged.

I'm a little worried about triggering launchpadlib errors though,
because lp is going to be updating the status at the same time - we
can collide.

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Robert Collins wrote:
> This is probably due to lp taking too long to mark the branch as
> merged: if it takes longer than pqm takes to try again, then pqm will
> see it as still pending. Possibly we should:
> mark things we succeed at as approved
> adding a comment that it landed ok
>
> or mark it as merged.
>
> I'm a little worried about triggering launchpadlib errors though,
> because lp is going to be updating the status at the same time - we
> can collide.

I think it would be reasonable to have your script notice that "Nothing
to be Merged" obviously means that the branch is already merged...

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkvY+TsACgkQJdeBCYSNAAPIMQCgqjpPMmQSCREDGp9/5OdQKQVN
V8YAn34mPZMKIzBYQqYSNCR5lVb6+31+
=hVtp
-----END PGP SIGNATURE-----

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2010-04-27 18:09:12 +0000
+++ NEWS 2010-04-28 07:18:51 +0000
@@ -34,6 +34,11 @@
34 better with sudo.34 better with sudo.
35 (Martin <gzlist@googlemail.com>, Parth Malwankar, #376388)35 (Martin <gzlist@googlemail.com>, Parth Malwankar, #376388)
3636
37* ``bzr log --exclude-common-ancestry -r X..Y`` displays the revisions that
38 are part of Y ancestry but not part of X ancestry (aka the graph
39 difference).
40 (Vincent Ladeuil, #320119)
41
37* ``bzr selftest --parallel=fork`` wait for its children avoiding zombies.42* ``bzr selftest --parallel=fork`` wait for its children avoiding zombies.
38 (Vincent Ladeuil, #566670)43 (Vincent Ladeuil, #566670)
3944
@@ -145,7 +150,8 @@
145 (Andrew Bennetts)150 (Andrew Bennetts)
146151
147* When invoked with a range revision, ``bzr log`` doesn't show revisions152* When invoked with a range revision, ``bzr log`` doesn't show revisions
148 that are not part of the ancestry anymore.153 that are not part of the Y revisions ancestry anymore when invoked with
154 -rX..Y.
149 (Vincent Ladeuil, #474807)155 (Vincent Ladeuil, #474807)
150156
151Improvements157Improvements
152158
=== modified file 'bzrlib/branch.py'
--- bzrlib/branch.py 2010-04-23 07:15:23 +0000
+++ bzrlib/branch.py 2010-04-28 07:18:51 +0000
@@ -420,6 +420,8 @@
420 * 'include' - the stop revision is the last item in the result420 * 'include' - the stop revision is the last item in the result
421 * 'with-merges' - include the stop revision and all of its421 * 'with-merges' - include the stop revision and all of its
422 merged revisions in the result422 merged revisions in the result
423 * 'with-merges-without-common-ancestry' - filter out revisions
424 that are in both ancestries
423 :param direction: either 'reverse' or 'forward':425 :param direction: either 'reverse' or 'forward':
424 * reverse means return the start_revision_id first, i.e.426 * reverse means return the start_revision_id first, i.e.
425 start at the most recent revision and go backwards in history427 start at the most recent revision and go backwards in history
@@ -456,7 +458,7 @@
456 stop_revision_id, stop_rule)458 stop_revision_id, stop_rule)
457 # Make sure we don't return revisions that are not part of the459 # Make sure we don't return revisions that are not part of the
458 # start_revision_id ancestry.460 # start_revision_id ancestry.
459 filtered = self._filter_non_ancestors(filtered)461 filtered = self._filter_start_non_ancestors(filtered)
460 if direction == 'reverse':462 if direction == 'reverse':
461 return filtered463 return filtered
462 if direction == 'forward':464 if direction == 'forward':
@@ -499,6 +501,18 @@
499 node.end_of_merge)501 node.end_of_merge)
500 if rev_id == stop_revision_id:502 if rev_id == stop_revision_id:
501 return503 return
504 elif stop_rule == 'with-merges-without-common-ancestry':
505 # We want to exclude all revisions that are already part of the
506 # stop_revision_id ancestry.
507 graph = self.repository.get_graph()
508 ancestors = graph.find_unique_ancestors(start_revision_id,
509 [stop_revision_id])
510 for node in rev_iter:
511 rev_id = node.key[-1]
512 if rev_id not in ancestors:
513 continue
514 yield (rev_id, node.merge_depth, node.revno,
515 node.end_of_merge)
502 elif stop_rule == 'with-merges':516 elif stop_rule == 'with-merges':
503 stop_rev = self.repository.get_revision(stop_revision_id)517 stop_rev = self.repository.get_revision(stop_revision_id)
504 if stop_rev.parent_ids:518 if stop_rev.parent_ids:
@@ -527,7 +541,7 @@
527 else:541 else:
528 raise ValueError('invalid stop_rule %r' % stop_rule)542 raise ValueError('invalid stop_rule %r' % stop_rule)
529543
530 def _filter_non_ancestors(self, rev_iter):544 def _filter_start_non_ancestors(self, rev_iter):
531 # If we started from a dotted revno, we want to consider it as a tip545 # If we started from a dotted revno, we want to consider it as a tip
532 # and don't want to yield revisions that are not part of its546 # and don't want to yield revisions that are not part of its
533 # ancestry. Given the order guaranteed by the merge sort, we will see547 # ancestry. Given the order guaranteed by the merge sort, we will see
534548
=== modified file 'bzrlib/builtins.py'
--- bzrlib/builtins.py 2010-04-23 11:11:22 +0000
+++ bzrlib/builtins.py 2010-04-28 07:18:51 +0000
@@ -2299,6 +2299,10 @@
2299 help='Show changes made in each revision as a patch.'),2299 help='Show changes made in each revision as a patch.'),
2300 Option('include-merges',2300 Option('include-merges',
2301 help='Show merged revisions like --levels 0 does.'),2301 help='Show merged revisions like --levels 0 does.'),
2302 Option('exclude-common-ancestry',
2303 help='Display only the revisions that are not part'
2304 ' of both ancestries (require -rX..Y)'
2305 )
2302 ]2306 ]
2303 encoding_type = 'replace'2307 encoding_type = 'replace'
23042308
@@ -2314,13 +2318,19 @@
2314 message=None,2318 message=None,
2315 limit=None,2319 limit=None,
2316 show_diff=False,2320 show_diff=False,
2317 include_merges=False):2321 include_merges=False,
2322 exclude_common_ancestry=False,
2323 ):
2318 from bzrlib.log import (2324 from bzrlib.log import (
2319 Logger,2325 Logger,
2320 make_log_request_dict,2326 make_log_request_dict,
2321 _get_info_for_log_files,2327 _get_info_for_log_files,
2322 )2328 )
2323 direction = (forward and 'forward') or 'reverse'2329 direction = (forward and 'forward') or 'reverse'
2330 if (exclude_common_ancestry
2331 and (revision is None or len(revision) != 2)):
2332 raise errors.BzrCommandError(
2333 '--exclude-common-ancestry requires -r with two revisions')
2324 if include_merges:2334 if include_merges:
2325 if levels is None:2335 if levels is None:
2326 levels = 02336 levels = 0
@@ -2419,7 +2429,9 @@
2419 direction=direction, specific_fileids=file_ids,2429 direction=direction, specific_fileids=file_ids,
2420 start_revision=rev1, end_revision=rev2, limit=limit,2430 start_revision=rev1, end_revision=rev2, limit=limit,
2421 message_search=message, delta_type=delta_type,2431 message_search=message, delta_type=delta_type,
2422 diff_type=diff_type, _match_using_deltas=match_using_deltas)2432 diff_type=diff_type, _match_using_deltas=match_using_deltas,
2433 exclude_common_ancestry=exclude_common_ancestry,
2434 )
2423 Logger(b, rqst).show(lf)2435 Logger(b, rqst).show(lf)
24242436
24252437
24262438
=== modified file 'bzrlib/log.py'
--- bzrlib/log.py 2010-04-14 10:38:57 +0000
+++ bzrlib/log.py 2010-04-28 07:18:51 +0000
@@ -220,14 +220,18 @@
220 'direction': 'reverse',220 'direction': 'reverse',
221 'levels': 1,221 'levels': 1,
222 'generate_tags': True,222 'generate_tags': True,
223 'exclude_common_ancestry': False,
223 '_match_using_deltas': True,224 '_match_using_deltas': True,
224 }225 }
225226
226227
227def make_log_request_dict(direction='reverse', specific_fileids=None,228def make_log_request_dict(direction='reverse', specific_fileids=None,
228 start_revision=None, end_revision=None, limit=None,229 start_revision=None, end_revision=None, limit=None,
229 message_search=None, levels=1, generate_tags=True, delta_type=None,230 message_search=None, levels=1, generate_tags=True,
230 diff_type=None, _match_using_deltas=True):231 delta_type=None,
232 diff_type=None, _match_using_deltas=True,
233 exclude_common_ancestry=False,
234 ):
231 """Convenience function for making a logging request dictionary.235 """Convenience function for making a logging request dictionary.
232236
233 Using this function may make code slightly safer by ensuring237 Using this function may make code slightly safer by ensuring
@@ -271,6 +275,9 @@
271 algorithm used for matching specific_fileids. This parameter275 algorithm used for matching specific_fileids. This parameter
272 may be removed in the future so bzrlib client code should NOT276 may be removed in the future so bzrlib client code should NOT
273 use it.277 use it.
278
279 :param exclude_common_ancestry: Whether -rX..Y should be interpreted as a
280 range operator or as a graph difference.
274 """281 """
275 return {282 return {
276 'direction': direction,283 'direction': direction,
@@ -283,6 +290,7 @@
283 'generate_tags': generate_tags,290 'generate_tags': generate_tags,
284 'delta_type': delta_type,291 'delta_type': delta_type,
285 'diff_type': diff_type,292 'diff_type': diff_type,
293 'exclude_common_ancestry': exclude_common_ancestry,
286 # Add 'private' attributes for features that may be deprecated294 # Add 'private' attributes for features that may be deprecated
287 '_match_using_deltas': _match_using_deltas,295 '_match_using_deltas': _match_using_deltas,
288 }296 }
@@ -459,7 +467,8 @@
459 self.branch, self.start_rev_id, self.end_rev_id,467 self.branch, self.start_rev_id, self.end_rev_id,
460 rqst.get('direction'),468 rqst.get('direction'),
461 generate_merge_revisions=generate_merge_revisions,469 generate_merge_revisions=generate_merge_revisions,
462 delayed_graph_generation=delayed_graph_generation)470 delayed_graph_generation=delayed_graph_generation,
471 exclude_common_ancestry=rqst.get('exclude_common_ancestry'))
463472
464 # Apply the other filters473 # Apply the other filters
465 return make_log_rev_iterator(self.branch, view_revisions,474 return make_log_rev_iterator(self.branch, view_revisions,
@@ -474,7 +483,8 @@
474 rqst = self.rqst483 rqst = self.rqst
475 view_revisions = _calc_view_revisions(484 view_revisions = _calc_view_revisions(
476 self.branch, self.start_rev_id, self.end_rev_id,485 self.branch, self.start_rev_id, self.end_rev_id,
477 rqst.get('direction'), generate_merge_revisions=True)486 rqst.get('direction'), generate_merge_revisions=True,
487 exclude_common_ancestry=rqst.get('exclude_common_ancestry'))
478 if not isinstance(view_revisions, list):488 if not isinstance(view_revisions, list):
479 view_revisions = list(view_revisions)489 view_revisions = list(view_revisions)
480 view_revisions = _filter_revisions_touching_file_id(self.branch,490 view_revisions = _filter_revisions_touching_file_id(self.branch,
@@ -485,12 +495,18 @@
485495
486496
487def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,497def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
488 generate_merge_revisions, delayed_graph_generation=False):498 generate_merge_revisions,
499 delayed_graph_generation=False,
500 exclude_common_ancestry=False,
501 ):
489 """Calculate the revisions to view.502 """Calculate the revisions to view.
490503
491 :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR504 :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
492 a list of the same tuples.505 a list of the same tuples.
493 """506 """
507 if (exclude_common_ancestry and start_rev_id == end_rev_id):
508 raise errors.BzrCommandError(
509 '--exclude-common-ancestry requires two different revisions')
494 if direction not in ('reverse', 'forward'):510 if direction not in ('reverse', 'forward'):
495 raise ValueError('invalid direction %r' % direction)511 raise ValueError('invalid direction %r' % direction)
496 br_revno, br_rev_id = branch.last_revision_info()512 br_revno, br_rev_id = branch.last_revision_info()
@@ -511,7 +527,8 @@
511 iter_revs = reversed(iter_revs)527 iter_revs = reversed(iter_revs)
512 else:528 else:
513 iter_revs = _generate_all_revisions(branch, start_rev_id, end_rev_id,529 iter_revs = _generate_all_revisions(branch, start_rev_id, end_rev_id,
514 direction, delayed_graph_generation)530 direction, delayed_graph_generation,
531 exclude_common_ancestry)
515 if direction == 'forward':532 if direction == 'forward':
516 iter_revs = _rebase_merge_depth(reverse_by_depth(list(iter_revs)))533 iter_revs = _rebase_merge_depth(reverse_by_depth(list(iter_revs)))
517 return iter_revs534 return iter_revs
@@ -542,7 +559,8 @@
542559
543560
544def _generate_all_revisions(branch, start_rev_id, end_rev_id, direction,561def _generate_all_revisions(branch, start_rev_id, end_rev_id, direction,
545 delayed_graph_generation):562 delayed_graph_generation,
563 exclude_common_ancestry=False):
546 # On large trees, generating the merge graph can take 30-60 seconds564 # On large trees, generating the merge graph can take 30-60 seconds
547 # so we delay doing it until a merge is detected, incrementally565 # so we delay doing it until a merge is detected, incrementally
548 # returning initial (non-merge) revisions while we can.566 # returning initial (non-merge) revisions while we can.
@@ -594,7 +612,8 @@
594 # indented at the end seems slightly nicer in that case.612 # indented at the end seems slightly nicer in that case.
595 view_revisions = chain(iter(initial_revisions),613 view_revisions = chain(iter(initial_revisions),
596 _graph_view_revisions(branch, start_rev_id, end_rev_id,614 _graph_view_revisions(branch, start_rev_id, end_rev_id,
597 rebase_initial_depths=(direction == 'reverse')))615 rebase_initial_depths=(direction == 'reverse'),
616 exclude_common_ancestry=exclude_common_ancestry))
598 return view_revisions617 return view_revisions
599618
600619
@@ -659,7 +678,8 @@
659678
660679
661def _graph_view_revisions(branch, start_rev_id, end_rev_id,680def _graph_view_revisions(branch, start_rev_id, end_rev_id,
662 rebase_initial_depths=True):681 rebase_initial_depths=True,
682 exclude_common_ancestry=False):
663 """Calculate revisions to view including merges, newest to oldest.683 """Calculate revisions to view including merges, newest to oldest.
664684
665 :param branch: the branch685 :param branch: the branch
@@ -669,9 +689,13 @@
669 revision is found?689 revision is found?
670 :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.690 :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
671 """691 """
692 if exclude_common_ancestry:
693 stop_rule = 'with-merges-without-common-ancestry'
694 else:
695 stop_rule = 'with-merges'
672 view_revisions = branch.iter_merge_sorted_revisions(696 view_revisions = branch.iter_merge_sorted_revisions(
673 start_revision_id=end_rev_id, stop_revision_id=start_rev_id,697 start_revision_id=end_rev_id, stop_revision_id=start_rev_id,
674 stop_rule="with-merges")698 stop_rule=stop_rule)
675 if not rebase_initial_depths:699 if not rebase_initial_depths:
676 for (rev_id, merge_depth, revno, end_of_merge700 for (rev_id, merge_depth, revno, end_of_merge
677 ) in view_revisions:701 ) in view_revisions:
678702
=== modified file 'bzrlib/tests/blackbox/test_log.py'
--- bzrlib/tests/blackbox/test_log.py 2010-03-24 14:15:01 +0000
+++ bzrlib/tests/blackbox/test_log.py 2010-04-28 07:18:51 +0000
@@ -365,6 +365,18 @@
365 'options are "utc", "original", "local".'],365 'options are "utc", "original", "local".'],
366 ['log', '--timezone', 'foo'])366 ['log', '--timezone', 'foo'])
367367
368 def test_log_exclude_ancestry_no_range(self):
369 self.make_linear_branch()
370 self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
371 ' requires -r with two revisions'],
372 ['log', '--exclude-common-ancestry'])
373
374 def test_log_exclude_ancestry_single_revision(self):
375 self.make_merged_branch()
376 self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
377 ' requires two different revisions'],
378 ['log', '--exclude-common-ancestry',
379 '-r1.1.1..1.1.1'])
368380
369class TestLogTags(TestLog):381class TestLogTags(TestLog):
370382
371383
=== modified file 'bzrlib/tests/per_branch/test_iter_merge_sorted_revisions.py'
--- bzrlib/tests/per_branch/test_iter_merge_sorted_revisions.py 2010-04-02 15:05:24 +0000
+++ bzrlib/tests/per_branch/test_iter_merge_sorted_revisions.py 2010-04-28 07:18:51 +0000
@@ -229,6 +229,37 @@
229 self.addCleanup(br.unlock)229 self.addCleanup(br.unlock)
230 return br230 return br
231231
232 def make_branch_with_alternate_ancestries(self, relpath='.'):
233 # See test_merge_sorted_exclude_ancestry below for the difference with
234 # bt.test_log.TestLogExcludeAncestry.
235 # make_branch_with_alternate_ancestries and
236 # test_merge_sorted_exclude_ancestry
237 # See the FIXME in assertLogRevnos there too.
238 builder = self.make_branch_builder(relpath)
239 # 1
240 # |\
241 # | 1.1.1
242 # | /| \
243 # 2 | |
244 # | | 1.2.1
245 # | | /
246 # | 1.1.2
247 # | /
248 # 3
249 builder.start_series()
250 builder.build_snapshot('1', None, [
251 ('add', ('', 'TREE_ROOT', 'directory', '')),])
252 builder.build_snapshot('1.1.1', ['1'], [])
253 builder.build_snapshot('2', ['1', '1.1.1'], [])
254 builder.build_snapshot('1.2.1', ['1.1.1'], [])
255 builder.build_snapshot('1.1.2', ['1.1.1', '1.2.1'], [])
256 builder.build_snapshot('3', ['2', '1.1.2'], [])
257 builder.finish_series()
258 br = builder.get_branch()
259 br.lock_read()
260 self.addCleanup(br.unlock)
261 return br
262
232 def assertIterRevids(self, expected, branch, *args, **kwargs):263 def assertIterRevids(self, expected, branch, *args, **kwargs):
233 # We don't care about depths and revnos here, only about returning the264 # We don't care about depths and revnos here, only about returning the
234 # right revids.265 # right revids.
@@ -259,3 +290,16 @@
259 self.assertIterRevids(['2.2.1', '2.1.1', '2', '1'],290 self.assertIterRevids(['2.2.1', '2.1.1', '2', '1'],
260 branch, start_revision_id='2.2.1',291 branch, start_revision_id='2.2.1',
261 stop_rule='with-merges')292 stop_rule='with-merges')
293
294 def test_merge_sorted_exclude_ancestry(self):
295 branch = self.make_branch_with_alternate_ancestries()
296 self.assertIterRevids(['3', '1.1.2', '1.2.1', '2', '1.1.1', '1'],
297 branch)
298 # '2' is not part of the ancestry even if merge_sort order will make it
299 # appear before 1.1.1
300 self.assertIterRevids(['1.1.2', '1.2.1'],
301 branch,
302 stop_rule='with-merges-without-common-ancestry',
303 start_revision_id='1.1.2',
304 stop_revision_id='1.1.1')
305
262306
=== modified file 'bzrlib/tests/per_repository_reference/__init__.py'
--- bzrlib/tests/per_repository_reference/__init__.py 2010-04-16 06:51:59 +0000
+++ bzrlib/tests/per_repository_reference/__init__.py 2010-04-28 07:18:51 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2008, 2009 Canonical Ltd1# Copyright (C) 2008, 2009, 2010 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
55
=== modified file 'bzrlib/tests/test_log.py'
--- bzrlib/tests/test_log.py 2010-03-25 08:14:04 +0000
+++ bzrlib/tests/test_log.py 2010-04-28 07:18:51 +0000
@@ -18,6 +18,7 @@
18from cStringIO import StringIO18from cStringIO import StringIO
1919
20from bzrlib import (20from bzrlib import (
21 branchbuilder,
21 errors,22 errors,
22 log,23 log,
23 registry,24 registry,
@@ -1541,3 +1542,62 @@
15411542
1542 def test_bugs_handler_present(self):1543 def test_bugs_handler_present(self):
1543 self.properties_handler_registry.get('bugs_properties_handler')1544 self.properties_handler_registry.get('bugs_properties_handler')
1545
1546class TestLogExcludeAncestry(tests.TestCaseWithTransport):
1547
1548 def make_branch_with_alternate_ancestries(self, relpath='.'):
1549 # See test_merge_sorted_exclude_ancestry below for the difference with
1550 # bt.per_branch.test_iter_merge_sorted_revision.
1551 # TestIterMergeSortedRevisionsBushyGraph.
1552 # make_branch_with_alternate_ancestries
1553 # and test_merge_sorted_exclude_ancestry
1554 # See the FIXME in assertLogRevnos too.
1555 builder = branchbuilder.BranchBuilder(self.get_transport(relpath))
1556 # 1
1557 # |\
1558 # 2 \
1559 # | |
1560 # | 1.1.1
1561 # | | \
1562 # | | 1.2.1
1563 # | | /
1564 # | 1.1.2
1565 # | /
1566 # 3
1567 builder.start_series()
1568 builder.build_snapshot('1', None, [
1569 ('add', ('', 'TREE_ROOT', 'directory', '')),])
1570 builder.build_snapshot('1.1.1', ['1'], [])
1571 builder.build_snapshot('2', ['1'], [])
1572 builder.build_snapshot('1.2.1', ['1.1.1'], [])
1573 builder.build_snapshot('1.1.2', ['1.1.1', '1.2.1'], [])
1574 builder.build_snapshot('3', ['2', '1.1.2'], [])
1575 builder.finish_series()
1576 br = builder.get_branch()
1577 br.lock_read()
1578 self.addCleanup(br.unlock)
1579 return br
1580
1581 def assertLogRevnos(self, expected_revnos, b, start, end,
1582 exclude_common_ancestry):
1583 # FIXME: the layering in log makes it hard to test intermediate levels,
1584 # I wish adding filters with their parameters were easier...
1585 # -- vila 20100413
1586 iter_revs = log._calc_view_revisions(
1587 b, start, end, direction='reverse',
1588 generate_merge_revisions=True,
1589 exclude_common_ancestry=exclude_common_ancestry)
1590 self.assertEqual(expected_revnos,
1591 [revid for revid, revno, depth in iter_revs])
1592
1593 def test_merge_sorted_exclude_ancestry(self):
1594 b = self.make_branch_with_alternate_ancestries()
1595 self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2', '1'],
1596 b, '1', '3', False)
1597 # '2' is part of the '3' ancestry but not part of '1.1.1' ancestry so
1598 # it should be mentioned even if merge_sort order will make it appear
1599 # after 1.1.1
1600 self.assertLogRevnos(['3', '1.1.2', '1.2.1', '2'],
1601 b, '1.1.1', '3', True)
1602
1603