[RFC] _show_log supporting unmerged revisions (first step towards lp #4663)
Wouter van Heyst
larstiq at larstiq.dyndns.org
Tue Jun 13 16:45:51 BST 2006
Struggling with #4663 Robert proposed two views of doing the right
thing.
One is showing the log of the mainline, from the revision that *merged*
BEGIN to the revision that *merged* END
Initially I thought this was not the right thing. If you want to see the
log of a subset from a large merge, you don't want to drown in all the
other numerous revisions.
The other view is along the mainline *of END*. Same start as above, but
stop at END and treat revisions since the last merged revision as
mainline also. This allows viewing the log as it would look from an
arbitrary HEAD. Necessarily you will not see mainline revisions that are
commited before the merge of END but are not in its ancestry.
3
|\
2 child
|/
1
log(1, child) will give you
child
|
1
I'm not so sure anymore if that is better than the first case, what do
others think?
Other comments on the code also welcome, especially better ways of doing
the 3 functions ismerged, last_mainline_ancestor and find_merging.
Wouter van Heyst
-------------- next part --------------
# Bazaar revision bundle v0.7
#
# message:
# Make _show_log handle merged revisions
# committer: Wouter van Heyst <larstiq at larstiq.dyndns.org>
# date: Tue 2006-06-13 17:12:38.152195930 +0200
=== modified file bzrlib/log.py
--- bzrlib/log.py
+++ bzrlib/log.py
@@ -189,50 +189,100 @@
else:
searchRE = None
- which_revs = _enumerate_history(branch)
-
+ revision_history = branch.revision_history()
+
+ def ismerged(rev):
+ """Check if a revision is in the mainline of a branch."""
+
+ return rev in revision_history
+
+ def last_mainline_ancestor(rev):
+ """Return the last mainline ancestor of rev."""
+
+ an = reversed(branch.repository.get_ancestry(rev))
+ revno = None
+ for ancestor in an:
+ if ismerged(ancestor):
+ revno = branch.revision_id_to_revno(ancestor)
+ if revno is None:
+ raise errors.RevisionNotPresent(rev, branch)
+ return revno
+
+ def find_merging(merged, revno=None):
+ """Find the mainline revno that merged us."""
+
+ for merging in revision_history[revno:]:
+ if merged in branch.repository.get_ancestry(merging):
+ return branch.revision_id_to_revno(merging)
+
+
if start_revision is None:
- start_revision = 1
+ start_revno = 1
else:
- branch.check_real_revno(start_revision)
+ if isinstance(start_revision, basestring):
+ merged = ismerged(start_revision)
+ # we do some slicing later on, so the revno of the start revision,
+ # or its mainline parent should be used.
+ if merged:
+ start_revno = branch.revision_id_to_revno(start_revision)
+ else:
+ start_revno = last_mainline_ancestor(start_revision)
+ else:
+ # if we get passed a revno, check that it is real
+ start_revno = start_revision
+ branch.check_real_revno(start_revno)
if end_revision is None:
- end_revision = len(which_revs)
- else:
- branch.check_real_revno(end_revision)
-
- # list indexes are 0-based; revisions are 1-based
- cut_revs = which_revs[(start_revision-1):(end_revision)]
- if not cut_revs:
- return
- # override the mainline to look like the revision history.
- mainline_revs = [revision_id for index, revision_id in cut_revs]
- if cut_revs[0][0] == 1:
- mainline_revs.insert(0, None)
- else:
- mainline_revs.insert(0, which_revs[start_revision-2][1])
+ end_revno = len(revision_history)
+ if end_revno == 0:
+ return
+ tip = revision_history[-1]
+ else:
+ if isinstance(end_revision, basestring):
+ # make sure it is in the ancestry
+ tip = end_revision
+ else:
+ # if we get passed a revno, check that it is real
+ branch.check_real_revno(end_revision)
+ tip = branch.get_rev_id(end_revision)
merge_sorted_revisions = merge_sort(
- branch.repository.get_revision_graph(mainline_revs[-1]),
- mainline_revs[-1],
- mainline_revs)
+ branch.repository.get_revision_graph(tip),
+ tip
+ )
+
+ if start_revision:
+ sorted_merge = []
+ hit = None
+ for rev in merge_sorted_revisions:
+ if hit:
+ if ismerged(rev[1]):
+ break
+
+ sorted_merge.append(rev)
+ if rev[1] == start_revision:
+ if ismerged(start_revision):
+ hit = True
+ else:
+ break
+
+ merge_sorted_revisions = sorted_merge
+
+ zerodepths = sum(1 for pack in merge_sorted_revisions if pack[2] == 0)
+ rev_nos = range(start_revno, start_revno + zerodepths)
if direction == 'reverse':
- cut_revs.reverse()
+ rev_nos.reverse()
elif direction == 'forward':
# forward means oldest first.
merge_sorted_revisions.reverse()
else:
raise ValueError('invalid direction %r' % direction)
- revision_history = branch.revision_history()
-
- # convert the revision history to a dictionary:
- rev_nos = {}
- for index, rev_id in cut_revs:
- rev_nos[rev_id] = index
-
+
+ # revisions in the branch
# now we just print all the revisions
+ rev_index = 0
for sequence, rev_id, merge_depth, end_of_merge in merge_sorted_revisions:
rev = branch.repository.get_revision(rev_id)
@@ -243,7 +293,7 @@
if merge_depth == 0:
# a mainline revision.
if verbose or specific_fileid:
- delta = _get_revision_delta(branch, rev_nos[rev_id])
+ delta = _get_revision_delta(branch, branch.revision_id_to_revno(rev_id))
if specific_fileid:
if not delta.touches_file_id(specific_fileid):
@@ -253,7 +303,8 @@
# although we calculated it, throw it away without display
delta = None
- lf.show(rev_nos[rev_id], rev, delta)
+ lf.show(rev_nos[rev_index], rev, delta)
+ rev_index += 1
elif hasattr(lf, 'show_merge'):
lf.show_merge(rev, merge_depth)
=== modified file bzrlib/tests/test_log.py
--- bzrlib/tests/test_log.py
+++ bzrlib/tests/test_log.py
@@ -301,3 +301,114 @@
logfile.seek(0)
log_contents = logfile.read()
self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
+
+ def test_revision_id_log(self):
+ wt = self.make_branch_and_tree('parent')
+ b = wt.branch
+ os.chdir('parent')
+ self.build_tree(['a', 'b'])
+ wt.add('a')
+ wt.update()
+
+ wt.commit(message='add a',
+ timestamp=1132711707,
+ timezone=36000,
+ rev_id='rev1',
+ committer='merged revision tester <test at merged.rev>')
+
+ wt2 = self.make_branch_and_tree('../child')
+ wt2.pull(wt.branch)
+
+ wt.add('b')
+ wt.commit(message='add b',
+ timestamp=1132711708,
+ timezone=36000,
+ rev_id='rev2',
+ committer='merged revision tester <test at merged.rev>')
+
+ os.chdir('../child')
+ self.build_tree(['c', 'd'])
+ #wt2.update()
+ wt2.add('c')
+ wt2.commit(message='add c',
+ timestamp=1132711709,
+ timezone=36000,
+ rev_id='revchild1',
+ committer='merged revision tester <test at merged.rev>')
+
+ wt2.add('d')
+ wt2.commit(message='add d',
+ timestamp=1132711710,
+ timezone=36000,
+ rev_id='revchild2',
+ committer='merged revision tester <test at merged.rev>')
+
+ os.chdir('../parent')
+ self.run_bzr('merge', '../child')
+ wt.commit(message='[merge] child',
+ timestamp=1132711710,
+ timezone=36000,
+ rev_id='rev3',
+ committer='merged revision tester <test at merged.rev>')
+
+ wt.commit(message='top revision',
+ timestamp=1132711711,
+ timezone=36000,
+ rev_id='rev4',
+ committer='merged revision tester <test at merged.rev>')
+
+ revisions = b.repository.all_revision_ids()
+ rev1, rev2, rev3, rev4 = map(b.get_rev_id, [1, 2, 3, 4])
+ childrev1, childrev2 = set(revisions) - set([rev1, rev2, rev3, rev4])
+
+ def test(expected, rev1, rev2):
+ logfile = file('out.tmp', 'wb')
+ try:
+ lf = LongLogFormatter(to_file=logfile, show_ids=True)
+ show_log(b, lf, start_revision=rev1, end_revision=rev2)
+ finally:
+ logfile.close()
+
+ logfile = file('out.tmp', 'rb')
+ logtext = logfile.read()
+ logfile.close()
+
+ actual = []
+ for rev in revisions:
+ if ((('revision-id: ' + rev) in logtext) or (('merged: ' + rev) in logtext)):
+ actual.append(rev)
+
+ unwanted = set(revisions) - set(expected)
+
+ for rev in expected:
+ self.assertTrue(rev in actual)
+
+ if unwanted:
+ for rev in unwanted:
+ self.assertFalse(rev in actual)
+
+ from bzrlib.trace import mutter
+
+ test(revisions, None, None)
+ test(revisions, rev1, None)
+ test([rev1], None, rev1)
+
+ test([rev1, rev2], None, rev2)
+ test([rev2, rev3, rev4, childrev1, childrev2], rev2, None)
+
+ test([childrev1, childrev2, rev3], rev3, rev3)
+ test([rev2], rev2, rev2)
+
+ # we look at the mainline of the branch childrev1 was in, not the
+ # mainline of this branch *but up to chilredv1*.
+ # So that does not include rev2, as it is not in the ancestry of
+ # childrev1
+ test([rev1, childrev1], None, childrev1)
+ test([childrev1, childrev2, rev3, rev4], childrev1, None)
+
+ test([childrev1], childrev1, childrev1)
+ test([childrev2, rev3, rev4], childrev2, None)
+ test([rev1, childrev1, childrev2], None, childrev2)
+ test([childrev1, childrev2], childrev1, childrev2)
+
+ # TODO: multiple branches that get merged
# revision id: larstiq at larstiq.dyndns.org-20060613151238-be9d6beaa7e8aae5
# sha1: 3a527da1614b5a46bb7c86f965f3a728128e6dd5
# inventory sha1: 4681221714dfa348ed3279274d50d4be87cfe402
# parent ids:
# pqm at pqm.ubuntu.com-20060613130303-e9de5dbda2e80da6
# base id: pqm at pqm.ubuntu.com-20060613130303-e9de5dbda2e80da6
# properties:
# branch-nick: bzr.lq
More information about the bazaar
mailing list