~bzr/ubuntu/lucid/bzr/beta-ppa

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Martin Pool
  • Date: 2010-07-02 07:29:40 UTC
  • mfrom: (129.1.7 packaging-karmic)
  • Revision ID: mbp@sourcefrog.net-20100702072940-hpzq5elg8wjve8rh
* PPA rebuild.
* PPA rebuild for Karmic.
* PPA rebuild for Jaunty.
* PPA rebuild for Hardy.
* From postinst, actually remove the example bash completion scripts.
  (LP: #249452)
* New upstream release.
* New upstream release.
* New upstream release.
* Revert change to Build-depends: Dapper does not have python-central.
  Should be python-support..
* Target ppa..
* Target ppa..
* Target ppa..
* Target ppa..
* New upstream release.
* Switch to dpkg-source 3.0 (quilt) format.
* Bump standards version to 3.8.4.
* Remove embedded copy of python-configobj. Closes: #555336
* Remove embedded copy of python-elementtree. Closes: #555343
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* debian/control: Fix obsolete-relation-form-in-source
  lintian warning. 
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Split out docs into bzr-doc package.
* New upstream release.
* Added John Francesco Ferlito to Uploaders.
* Fix install path to quick-reference guide
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to doc paths changing
* New upstream release.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to doc paths changing
* New upstream release.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to doc paths changing
* New upstream release.
* Fix FTBFS due to path changes, again, again.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to path changes.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Bump standards version to 3.8.3.
* Remove unused patch system.
* New upstream release.
* New upstream release.
* New upstream release.
* Fix copy and paste tab error in .install file
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
 + Fixes compatibility with Python 2.4. Closes: #537708
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream version.
* Bump standards version to 3.8.2.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Add python-pyrex to build-deps to ensure C extensions are always build.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Split documentation into bzr-doc package. ((LP: #385074)
* Multiple packaging changes to make us more linitan clean.
* New upstream release.
* Split documentation into bzr-doc package. ((LP: #385074)
* Multiple packaging changes to make us more linitan clean.
* New upstream release.
* Split documentation into bzr-doc package. ((LP: #385074)
* Multiple packaging changes to make us more linitan clean.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Fix API compatibility version. (Closes: #526233)
* New upstream release.
  + Fixes default format for upgrade command. (Closes: #464688)
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Add missing dependency on zlib development library. (Closes:
  #523595)
* Add zlib build-depends.
* Add zlib build-depends.
* Add zlib build-depends.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Move to section vcs.
* Bump standards version to 3.8.1.
* New upstream release.
* Remove temporary patch for missing .c files from distribution
* New upstream release.
* Remove temporary patch for missing .c files from distribution
* New upstream release.
* Remove temporary patch for missing .c files from distribution
* Add temporary patch for missing .c files from distribution
* Add temporary patch for missing .c files from distribution
* Add temporary patch for missing .c files from distribution
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Recommend ca-certificates. (Closes: #452024)
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Update watch file. bazaar now uses launchpad to host its sources.
* Remove patch for inventory root revision copy, applied upstream.
* New upstream release.
* New upstream release.
* New upstream release
* Force removal of files installed in error to /etc/bash_completion.d/
  (LP: #249452)
* New upstream release.
* New upstream release
* New upstream release.
* Bump standards version.
* Include patch for inventory root revision copy, required for bzr-svn.
* New upstream release.
* Remove unused lintian overrides.
* Correct the package version not to be native.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Final 1.5 release.
* New upstream release.
* New upstream release.
* New upstream release.
* Add myself as a co-maintainer.
* Add a Dm-Upload-Allowed: yes header.
* New upstream bugfix release.
* New upstream release.
* Final 1.3 release.
* New upstream release.
* First release candidate of the upcoming 1.3 release.
* Rebuild to fix the problem caused by a build with a broken python-central.
* New upstream release.
* Rebuild for dapper PPA.
* Apply Lamont's patches to fix build-dependencies on dapper.
  (See: https://bugs.launchpad.net/bzr/+bug/189915)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2010 Canonical Ltd
 
2
#
 
3
# 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 by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
import os
 
18
from cStringIO import StringIO
 
19
 
 
20
from bzrlib import (
 
21
    errors,
 
22
    log,
 
23
    registry,
 
24
    revision,
 
25
    revisionspec,
 
26
    tests,
 
27
    )
 
28
 
 
29
 
 
30
class TestCaseForLogFormatter(tests.TestCaseWithTransport):
 
31
 
 
32
    def setUp(self):
 
33
        super(TestCaseForLogFormatter, self).setUp()
 
34
        # keep a reference to the "current" custom prop. handler registry
 
35
        self.properties_handler_registry = log.properties_handler_registry
 
36
        # Use a clean registry for log
 
37
        log.properties_handler_registry = registry.Registry()
 
38
 
 
39
        def restore():
 
40
            log.properties_handler_registry = self.properties_handler_registry
 
41
        self.addCleanup(restore)
 
42
 
 
43
    def assertFormatterResult(self, result, branch, formatter_class,
 
44
                              formatter_kwargs=None, show_log_kwargs=None,
 
45
                              normalize=False):
 
46
        logfile = self.make_utf8_encoded_stringio()
 
47
        if formatter_kwargs is None:
 
48
            formatter_kwargs = {}
 
49
        formatter = formatter_class(to_file=logfile, **formatter_kwargs)
 
50
        if show_log_kwargs is None:
 
51
            show_log_kwargs = {}
 
52
        log.show_log(branch, formatter, **show_log_kwargs)
 
53
        log_content = logfile.getvalue()
 
54
        if normalize:
 
55
            log_content = normalize_log(log_content)
 
56
        self.assertEqualDiff(result, log_content)
 
57
 
 
58
    def make_standard_commit(self, branch_nick, **kwargs):
 
59
        wt = self.make_branch_and_tree('.')
 
60
        wt.lock_write()
 
61
        self.addCleanup(wt.unlock)
 
62
        self.build_tree(['a'])
 
63
        wt.add(['a'])
 
64
        wt.branch.nick = branch_nick
 
65
        kwargs = dict(kwargs)
 
66
        kwargs.setdefault('message', 'add a')
 
67
        kwargs.setdefault('timestamp', 1132711707)
 
68
        kwargs.setdefault('timezone', 36000)
 
69
        kwargs.setdefault('committer', 'Lorem Ipsum <test@example.com>')
 
70
        kwargs.setdefault('authors', ['John Doe <jdoe@example.com>'])
 
71
        wt.commit(**kwargs)
 
72
        return wt
 
73
 
 
74
    def _prepare_tree_with_merges(self, with_tags=False):
 
75
        wt = self.make_branch_and_memory_tree('.')
 
76
        wt.lock_write()
 
77
        self.addCleanup(wt.unlock)
 
78
        wt.add('')
 
79
        wt.commit('rev-1', rev_id='rev-1',
 
80
                  timestamp=1132586655, timezone=36000,
 
81
                  committer='Joe Foo <joe@foo.com>')
 
82
        wt.commit('rev-merged', rev_id='rev-2a',
 
83
                  timestamp=1132586700, timezone=36000,
 
84
                  committer='Joe Foo <joe@foo.com>')
 
85
        wt.set_parent_ids(['rev-1', 'rev-2a'])
 
86
        wt.branch.set_last_revision_info(1, 'rev-1')
 
87
        wt.commit('rev-2', rev_id='rev-2b',
 
88
                  timestamp=1132586800, timezone=36000,
 
89
                  committer='Joe Foo <joe@foo.com>')
 
90
        if with_tags:
 
91
            branch = wt.branch
 
92
            branch.tags.set_tag('v0.2', 'rev-2b')
 
93
            wt.commit('rev-3', rev_id='rev-3',
 
94
                      timestamp=1132586900, timezone=36000,
 
95
                      committer='Jane Foo <jane@foo.com>')
 
96
            branch.tags.set_tag('v1.0rc1', 'rev-3')
 
97
            branch.tags.set_tag('v1.0', 'rev-3')
 
98
        return wt
 
99
 
 
100
        
 
101
 
 
102
 
 
103
class LogCatcher(log.LogFormatter):
 
104
    """Pull log messages into a list rather than displaying them.
 
105
 
 
106
    To simplify testing we save logged revisions here rather than actually
 
107
    formatting anything, so that we can precisely check the result without
 
108
    being dependent on the formatting.
 
109
    """
 
110
 
 
111
    supports_delta = True
 
112
 
 
113
    def __init__(self):
 
114
        super(LogCatcher, self).__init__(to_file=None)
 
115
        self.revisions = []
 
116
 
 
117
    def log_revision(self, revision):
 
118
        self.revisions.append(revision)
 
119
 
 
120
 
 
121
class TestShowLog(tests.TestCaseWithTransport):
 
122
 
 
123
    def checkDelta(self, delta, **kw):
 
124
        """Check the filenames touched by a delta are as expected.
 
125
 
 
126
        Caller only have to pass in the list of files for each part, all
 
127
        unspecified parts are considered empty (and checked as such).
 
128
        """
 
129
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
 
130
            # By default we expect an empty list
 
131
            expected = kw.get(n, [])
 
132
            # strip out only the path components
 
133
            got = [x[0] for x in getattr(delta, n)]
 
134
            self.assertEqual(expected, got)
 
135
 
 
136
    def assertInvalidRevisonNumber(self, br, start, end):
 
137
        lf = LogCatcher()
 
138
        self.assertRaises(errors.InvalidRevisionNumber,
 
139
                          log.show_log, br, lf,
 
140
                          start_revision=start, end_revision=end)
 
141
 
 
142
    def test_cur_revno(self):
 
143
        wt = self.make_branch_and_tree('.')
 
144
        b = wt.branch
 
145
 
 
146
        lf = LogCatcher()
 
147
        wt.commit('empty commit')
 
148
        log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
 
149
 
 
150
        # Since there is a single revision in the branch all the combinations
 
151
        # below should fail.
 
152
        self.assertInvalidRevisonNumber(b, 2, 1)
 
153
        self.assertInvalidRevisonNumber(b, 1, 2)
 
154
        self.assertInvalidRevisonNumber(b, 0, 2)
 
155
        self.assertInvalidRevisonNumber(b, 1, 0)
 
156
        self.assertInvalidRevisonNumber(b, -1, 1)
 
157
        self.assertInvalidRevisonNumber(b, 1, -1)
 
158
 
 
159
    def test_empty_branch(self):
 
160
        wt = self.make_branch_and_tree('.')
 
161
 
 
162
        lf = LogCatcher()
 
163
        log.show_log(wt.branch, lf)
 
164
        # no entries yet
 
165
        self.assertEqual([], lf.revisions)
 
166
 
 
167
    def test_empty_commit(self):
 
168
        wt = self.make_branch_and_tree('.')
 
169
 
 
170
        wt.commit('empty commit')
 
171
        lf = LogCatcher()
 
172
        log.show_log(wt.branch, lf, verbose=True)
 
173
        revs = lf.revisions
 
174
        self.assertEqual(1, len(revs))
 
175
        self.assertEqual('1', revs[0].revno)
 
176
        self.assertEqual('empty commit', revs[0].rev.message)
 
177
        self.checkDelta(revs[0].delta)
 
178
 
 
179
    def test_simple_commit(self):
 
180
        wt = self.make_branch_and_tree('.')
 
181
        wt.commit('empty commit')
 
182
        self.build_tree(['hello'])
 
183
        wt.add('hello')
 
184
        wt.commit('add one file',
 
185
                  committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
 
186
                            u'<test@example.com>')
 
187
        lf = LogCatcher()
 
188
        log.show_log(wt.branch, lf, verbose=True)
 
189
        self.assertEqual(2, len(lf.revisions))
 
190
        # first one is most recent
 
191
        log_entry = lf.revisions[0]
 
192
        self.assertEqual('2', log_entry.revno)
 
193
        self.assertEqual('add one file', log_entry.rev.message)
 
194
        self.checkDelta(log_entry.delta, added=['hello'])
 
195
 
 
196
    def test_commit_message_with_control_chars(self):
 
197
        wt = self.make_branch_and_tree('.')
 
198
        msg = u"All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
 
199
        msg = msg.replace(u'\r', u'\n')
 
200
        wt.commit(msg)
 
201
        lf = LogCatcher()
 
202
        log.show_log(wt.branch, lf, verbose=True)
 
203
        committed_msg = lf.revisions[0].rev.message
 
204
        if wt.branch.repository._serializer.squashes_xml_invalid_characters:
 
205
            self.assertNotEqual(msg, committed_msg)
 
206
            self.assertTrue(len(committed_msg) > len(msg))
 
207
        else:
 
208
            self.assertEqual(msg, committed_msg)
 
209
 
 
210
    def test_commit_message_without_control_chars(self):
 
211
        wt = self.make_branch_and_tree('.')
 
212
        # escaped.  As ElementTree apparently does some kind of
 
213
        # newline conversion, neither LF (\x0A) nor CR (\x0D) are
 
214
        # included in the test commit message, even though they are
 
215
        # valid XML 1.0 characters.
 
216
        msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
 
217
        wt.commit(msg)
 
218
        lf = LogCatcher()
 
219
        log.show_log(wt.branch, lf, verbose=True)
 
220
        committed_msg = lf.revisions[0].rev.message
 
221
        self.assertEqual(msg, committed_msg)
 
222
 
 
223
    def test_deltas_in_merge_revisions(self):
 
224
        """Check deltas created for both mainline and merge revisions"""
 
225
        wt = self.make_branch_and_tree('parent')
 
226
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
 
227
        wt.add('file1')
 
228
        wt.add('file2')
 
229
        wt.commit(message='add file1 and file2')
 
230
        self.run_bzr('branch parent child')
 
231
        os.unlink('child/file1')
 
232
        file('child/file2', 'wb').write('hello\n')
 
233
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
 
234
            'child'])
 
235
        os.chdir('parent')
 
236
        self.run_bzr('merge ../child')
 
237
        wt.commit('merge child branch')
 
238
        os.chdir('..')
 
239
        b = wt.branch
 
240
        lf = LogCatcher()
 
241
        lf.supports_merge_revisions = True
 
242
        log.show_log(b, lf, verbose=True)
 
243
 
 
244
        revs = lf.revisions
 
245
        self.assertEqual(3, len(revs))
 
246
 
 
247
        logentry = revs[0]
 
248
        self.assertEqual('2', logentry.revno)
 
249
        self.assertEqual('merge child branch', logentry.rev.message)
 
250
        self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
 
251
 
 
252
        logentry = revs[1]
 
253
        self.assertEqual('1.1.1', logentry.revno)
 
254
        self.assertEqual('remove file1 and modify file2', logentry.rev.message)
 
255
        self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
 
256
 
 
257
        logentry = revs[2]
 
258
        self.assertEqual('1', logentry.revno)
 
259
        self.assertEqual('add file1 and file2', logentry.rev.message)
 
260
        self.checkDelta(logentry.delta, added=['file1', 'file2'])
 
261
 
 
262
 
 
263
def make_commits_with_trailing_newlines(wt):
 
264
    """Helper method for LogFormatter tests"""
 
265
    b = wt.branch
 
266
    b.nick='test'
 
267
    open('a', 'wb').write('hello moto\n')
 
268
    wt.add('a')
 
269
    wt.commit('simple log message', rev_id='a1',
 
270
              timestamp=1132586655.459960938, timezone=-6*3600,
 
271
              committer='Joe Foo <joe@foo.com>')
 
272
    open('b', 'wb').write('goodbye\n')
 
273
    wt.add('b')
 
274
    wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
 
275
              timestamp=1132586842.411175966, timezone=-6*3600,
 
276
              committer='Joe Foo <joe@foo.com>',
 
277
              authors=['Joe Bar <joe@bar.com>'])
 
278
 
 
279
    open('c', 'wb').write('just another manic monday\n')
 
280
    wt.add('c')
 
281
    wt.commit('single line with trailing newline\n', rev_id='a3',
 
282
              timestamp=1132587176.835228920, timezone=-6*3600,
 
283
              committer = 'Joe Foo <joe@foo.com>')
 
284
    return b
 
285
 
 
286
 
 
287
def normalize_log(log):
 
288
    """Replaces the variable lines of logs with fixed lines"""
 
289
    author = 'author: Dolor Sit <test@example.com>'
 
290
    committer = 'committer: Lorem Ipsum <test@example.com>'
 
291
    lines = log.splitlines(True)
 
292
    for idx,line in enumerate(lines):
 
293
        stripped_line = line.lstrip()
 
294
        indent = ' ' * (len(line) - len(stripped_line))
 
295
        if stripped_line.startswith('author:'):
 
296
            lines[idx] = indent + author + '\n'
 
297
        elif stripped_line.startswith('committer:'):
 
298
            lines[idx] = indent + committer + '\n'
 
299
        elif stripped_line.startswith('timestamp:'):
 
300
            lines[idx] = indent + 'timestamp: Just now\n'
 
301
    return ''.join(lines)
 
302
 
 
303
 
 
304
class TestShortLogFormatter(TestCaseForLogFormatter):
 
305
 
 
306
    def test_trailing_newlines(self):
 
307
        wt = self.make_branch_and_tree('.')
 
308
        b = make_commits_with_trailing_newlines(wt)
 
309
        self.assertFormatterResult("""\
 
310
    3 Joe Foo\t2005-11-21
 
311
      single line with trailing newline
 
312
 
 
313
    2 Joe Bar\t2005-11-21
 
314
      multiline
 
315
      log
 
316
      message
 
317
 
 
318
    1 Joe Foo\t2005-11-21
 
319
      simple log message
 
320
 
 
321
""",
 
322
            b, log.ShortLogFormatter)
 
323
 
 
324
    def test_short_log_with_merges(self):
 
325
        wt = self._prepare_tree_with_merges()
 
326
        self.assertFormatterResult("""\
 
327
    2 Joe Foo\t2005-11-22 [merge]
 
328
      rev-2
 
329
 
 
330
    1 Joe Foo\t2005-11-22
 
331
      rev-1
 
332
 
 
333
""",
 
334
            wt.branch, log.ShortLogFormatter)
 
335
 
 
336
    def test_short_log_with_merges_and_advice(self):
 
337
        wt = self._prepare_tree_with_merges()
 
338
        self.assertFormatterResult("""\
 
339
    2 Joe Foo\t2005-11-22 [merge]
 
340
      rev-2
 
341
 
 
342
    1 Joe Foo\t2005-11-22
 
343
      rev-1
 
344
 
 
345
Use --include-merges or -n0 to see merged revisions.
 
346
""",
 
347
            wt.branch, log.ShortLogFormatter,
 
348
            formatter_kwargs=dict(show_advice=True))
 
349
 
 
350
    def test_short_log_with_merges_and_range(self):
 
351
        wt = self.make_branch_and_memory_tree('.')
 
352
        wt.lock_write()
 
353
        self.addCleanup(wt.unlock)
 
354
        wt.add('')
 
355
        wt.commit('rev-1', rev_id='rev-1',
 
356
                  timestamp=1132586655, timezone=36000,
 
357
                  committer='Joe Foo <joe@foo.com>')
 
358
        wt.commit('rev-merged', rev_id='rev-2a',
 
359
                  timestamp=1132586700, timezone=36000,
 
360
                  committer='Joe Foo <joe@foo.com>')
 
361
        wt.branch.set_last_revision_info(1, 'rev-1')
 
362
        wt.set_parent_ids(['rev-1', 'rev-2a'])
 
363
        wt.commit('rev-2b', rev_id='rev-2b',
 
364
                  timestamp=1132586800, timezone=36000,
 
365
                  committer='Joe Foo <joe@foo.com>')
 
366
        wt.commit('rev-3a', rev_id='rev-3a',
 
367
                  timestamp=1132586800, timezone=36000,
 
368
                  committer='Joe Foo <joe@foo.com>')
 
369
        wt.branch.set_last_revision_info(2, 'rev-2b')
 
370
        wt.set_parent_ids(['rev-2b', 'rev-3a'])
 
371
        wt.commit('rev-3b', rev_id='rev-3b',
 
372
                  timestamp=1132586800, timezone=36000,
 
373
                  committer='Joe Foo <joe@foo.com>')
 
374
        self.assertFormatterResult("""\
 
375
    3 Joe Foo\t2005-11-22 [merge]
 
376
      rev-3b
 
377
 
 
378
    2 Joe Foo\t2005-11-22 [merge]
 
379
      rev-2b
 
380
 
 
381
""",
 
382
            wt.branch, log.ShortLogFormatter,
 
383
            show_log_kwargs=dict(start_revision=2, end_revision=3))
 
384
 
 
385
    def test_short_log_with_tags(self):
 
386
        wt = self._prepare_tree_with_merges(with_tags=True)
 
387
        self.assertFormatterResult("""\
 
388
    3 Jane Foo\t2005-11-22 {v1.0, v1.0rc1}
 
389
      rev-3
 
390
 
 
391
    2 Joe Foo\t2005-11-22 {v0.2} [merge]
 
392
      rev-2
 
393
 
 
394
    1 Joe Foo\t2005-11-22
 
395
      rev-1
 
396
 
 
397
""",
 
398
            wt.branch, log.ShortLogFormatter)
 
399
 
 
400
    def test_short_log_single_merge_revision(self):
 
401
        wt = self.make_branch_and_memory_tree('.')
 
402
        wt.lock_write()
 
403
        self.addCleanup(wt.unlock)
 
404
        wt.add('')
 
405
        wt.commit('rev-1', rev_id='rev-1',
 
406
                  timestamp=1132586655, timezone=36000,
 
407
                  committer='Joe Foo <joe@foo.com>')
 
408
        wt.commit('rev-merged', rev_id='rev-2a',
 
409
                  timestamp=1132586700, timezone=36000,
 
410
                  committer='Joe Foo <joe@foo.com>')
 
411
        wt.set_parent_ids(['rev-1', 'rev-2a'])
 
412
        wt.branch.set_last_revision_info(1, 'rev-1')
 
413
        wt.commit('rev-2', rev_id='rev-2b',
 
414
                  timestamp=1132586800, timezone=36000,
 
415
                  committer='Joe Foo <joe@foo.com>')
 
416
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
417
        rev = revspec.in_history(wt.branch)
 
418
        self.assertFormatterResult("""\
 
419
      1.1.1 Joe Foo\t2005-11-22
 
420
            rev-merged
 
421
 
 
422
""",
 
423
            wt.branch, log.ShortLogFormatter,
 
424
            show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
425
 
 
426
 
 
427
class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
 
428
 
 
429
    def test_short_merge_revs_log_with_merges(self):
 
430
        wt = self.make_branch_and_memory_tree('.')
 
431
        wt.lock_write()
 
432
        self.addCleanup(wt.unlock)
 
433
        wt.add('')
 
434
        wt.commit('rev-1', rev_id='rev-1',
 
435
                  timestamp=1132586655, timezone=36000,
 
436
                  committer='Joe Foo <joe@foo.com>')
 
437
        wt.commit('rev-merged', rev_id='rev-2a',
 
438
                  timestamp=1132586700, timezone=36000,
 
439
                  committer='Joe Foo <joe@foo.com>')
 
440
        wt.set_parent_ids(['rev-1', 'rev-2a'])
 
441
        wt.branch.set_last_revision_info(1, 'rev-1')
 
442
        wt.commit('rev-2', rev_id='rev-2b',
 
443
                  timestamp=1132586800, timezone=36000,
 
444
                  committer='Joe Foo <joe@foo.com>')
 
445
        # Note that the 1.1.1 indenting is in fact correct given that
 
446
        # the revision numbers are right justified within 5 characters
 
447
        # for mainline revnos and 9 characters for dotted revnos.
 
448
        self.assertFormatterResult("""\
 
449
    2 Joe Foo\t2005-11-22 [merge]
 
450
      rev-2
 
451
 
 
452
          1.1.1 Joe Foo\t2005-11-22
 
453
                rev-merged
 
454
 
 
455
    1 Joe Foo\t2005-11-22
 
456
      rev-1
 
457
 
 
458
""",
 
459
            wt.branch, log.ShortLogFormatter,
 
460
            formatter_kwargs=dict(levels=0))
 
461
 
 
462
    def test_short_merge_revs_log_single_merge_revision(self):
 
463
        wt = self.make_branch_and_memory_tree('.')
 
464
        wt.lock_write()
 
465
        self.addCleanup(wt.unlock)
 
466
        wt.add('')
 
467
        wt.commit('rev-1', rev_id='rev-1',
 
468
                  timestamp=1132586655, timezone=36000,
 
469
                  committer='Joe Foo <joe@foo.com>')
 
470
        wt.commit('rev-merged', rev_id='rev-2a',
 
471
                  timestamp=1132586700, timezone=36000,
 
472
                  committer='Joe Foo <joe@foo.com>')
 
473
        wt.set_parent_ids(['rev-1', 'rev-2a'])
 
474
        wt.branch.set_last_revision_info(1, 'rev-1')
 
475
        wt.commit('rev-2', rev_id='rev-2b',
 
476
                  timestamp=1132586800, timezone=36000,
 
477
                  committer='Joe Foo <joe@foo.com>')
 
478
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
479
        rev = revspec.in_history(wt.branch)
 
480
        self.assertFormatterResult("""\
 
481
      1.1.1 Joe Foo\t2005-11-22
 
482
            rev-merged
 
483
 
 
484
""",
 
485
            wt.branch, log.ShortLogFormatter,
 
486
            formatter_kwargs=dict(levels=0),
 
487
            show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
488
 
 
489
 
 
490
class TestLongLogFormatter(TestCaseForLogFormatter):
 
491
 
 
492
    def test_verbose_log(self):
 
493
        """Verbose log includes changed files
 
494
 
 
495
        bug #4676
 
496
        """
 
497
        wt = self.make_standard_commit('test_verbose_log', authors=[])
 
498
        self.assertFormatterResult('''\
 
499
------------------------------------------------------------
 
500
revno: 1
 
501
committer: Lorem Ipsum <test@example.com>
 
502
branch nick: test_verbose_log
 
503
timestamp: Wed 2005-11-23 12:08:27 +1000
 
504
message:
 
505
  add a
 
506
added:
 
507
  a
 
508
''',
 
509
            wt.branch, log.LongLogFormatter,
 
510
            show_log_kwargs=dict(verbose=True))
 
511
 
 
512
    def test_merges_are_indented_by_level(self):
 
513
        wt = self.make_branch_and_tree('parent')
 
514
        wt.commit('first post')
 
515
        self.run_bzr('branch parent child')
 
516
        self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
 
517
        self.run_bzr('branch child smallerchild')
 
518
        self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
 
519
            'smallerchild'])
 
520
        os.chdir('child')
 
521
        self.run_bzr('merge ../smallerchild')
 
522
        self.run_bzr(['commit', '-m', 'merge branch 2'])
 
523
        os.chdir('../parent')
 
524
        self.run_bzr('merge ../child')
 
525
        wt.commit('merge branch 1')
 
526
        self.assertFormatterResult("""\
 
527
------------------------------------------------------------
 
528
revno: 2 [merge]
 
529
committer: Lorem Ipsum <test@example.com>
 
530
branch nick: parent
 
531
timestamp: Just now
 
532
message:
 
533
  merge branch 1
 
534
    ------------------------------------------------------------
 
535
    revno: 1.1.2 [merge]
 
536
    committer: Lorem Ipsum <test@example.com>
 
537
    branch nick: child
 
538
    timestamp: Just now
 
539
    message:
 
540
      merge branch 2
 
541
        ------------------------------------------------------------
 
542
        revno: 1.2.1
 
543
        committer: Lorem Ipsum <test@example.com>
 
544
        branch nick: smallerchild
 
545
        timestamp: Just now
 
546
        message:
 
547
          branch 2
 
548
    ------------------------------------------------------------
 
549
    revno: 1.1.1
 
550
    committer: Lorem Ipsum <test@example.com>
 
551
    branch nick: child
 
552
    timestamp: Just now
 
553
    message:
 
554
      branch 1
 
555
------------------------------------------------------------
 
556
revno: 1
 
557
committer: Lorem Ipsum <test@example.com>
 
558
branch nick: parent
 
559
timestamp: Just now
 
560
message:
 
561
  first post
 
562
""",
 
563
            wt.branch, log.LongLogFormatter,
 
564
            formatter_kwargs=dict(levels=0),
 
565
            show_log_kwargs=dict(verbose=True),
 
566
            normalize=True)
 
567
 
 
568
    def test_verbose_merge_revisions_contain_deltas(self):
 
569
        wt = self.make_branch_and_tree('parent')
 
570
        self.build_tree(['parent/f1', 'parent/f2'])
 
571
        wt.add(['f1','f2'])
 
572
        wt.commit('first post')
 
573
        self.run_bzr('branch parent child')
 
574
        os.unlink('child/f1')
 
575
        file('child/f2', 'wb').write('hello\n')
 
576
        self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
 
577
            'child'])
 
578
        os.chdir('parent')
 
579
        self.run_bzr('merge ../child')
 
580
        wt.commit('merge branch 1')
 
581
        self.assertFormatterResult("""\
 
582
------------------------------------------------------------
 
583
revno: 2 [merge]
 
584
committer: Lorem Ipsum <test@example.com>
 
585
branch nick: parent
 
586
timestamp: Just now
 
587
message:
 
588
  merge branch 1
 
589
removed:
 
590
  f1
 
591
modified:
 
592
  f2
 
593
    ------------------------------------------------------------
 
594
    revno: 1.1.1
 
595
    committer: Lorem Ipsum <test@example.com>
 
596
    branch nick: child
 
597
    timestamp: Just now
 
598
    message:
 
599
      removed f1 and modified f2
 
600
    removed:
 
601
      f1
 
602
    modified:
 
603
      f2
 
604
------------------------------------------------------------
 
605
revno: 1
 
606
committer: Lorem Ipsum <test@example.com>
 
607
branch nick: parent
 
608
timestamp: Just now
 
609
message:
 
610
  first post
 
611
added:
 
612
  f1
 
613
  f2
 
614
""",
 
615
            wt.branch, log.LongLogFormatter,
 
616
            formatter_kwargs=dict(levels=0),
 
617
            show_log_kwargs=dict(verbose=True),
 
618
            normalize=True)
 
619
 
 
620
    def test_trailing_newlines(self):
 
621
        wt = self.make_branch_and_tree('.')
 
622
        b = make_commits_with_trailing_newlines(wt)
 
623
        self.assertFormatterResult("""\
 
624
------------------------------------------------------------
 
625
revno: 3
 
626
committer: Joe Foo <joe@foo.com>
 
627
branch nick: test
 
628
timestamp: Mon 2005-11-21 09:32:56 -0600
 
629
message:
 
630
  single line with trailing newline
 
631
------------------------------------------------------------
 
632
revno: 2
 
633
author: Joe Bar <joe@bar.com>
 
634
committer: Joe Foo <joe@foo.com>
 
635
branch nick: test
 
636
timestamp: Mon 2005-11-21 09:27:22 -0600
 
637
message:
 
638
  multiline
 
639
  log
 
640
  message
 
641
------------------------------------------------------------
 
642
revno: 1
 
643
committer: Joe Foo <joe@foo.com>
 
644
branch nick: test
 
645
timestamp: Mon 2005-11-21 09:24:15 -0600
 
646
message:
 
647
  simple log message
 
648
""",
 
649
        b, log.LongLogFormatter)
 
650
 
 
651
    def test_author_in_log(self):
 
652
        """Log includes the author name if it's set in
 
653
        the revision properties
 
654
        """
 
655
        wt = self.make_standard_commit('test_author_log',
 
656
            authors=['John Doe <jdoe@example.com>',
 
657
                     'Jane Rey <jrey@example.com>'])
 
658
        self.assertFormatterResult("""\
 
659
------------------------------------------------------------
 
660
revno: 1
 
661
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
 
662
committer: Lorem Ipsum <test@example.com>
 
663
branch nick: test_author_log
 
664
timestamp: Wed 2005-11-23 12:08:27 +1000
 
665
message:
 
666
  add a
 
667
""",
 
668
        wt.branch, log.LongLogFormatter)
 
669
 
 
670
    def test_properties_in_log(self):
 
671
        """Log includes the custom properties returned by the registered
 
672
        handlers.
 
673
        """
 
674
        wt = self.make_standard_commit('test_properties_in_log')
 
675
        def trivial_custom_prop_handler(revision):
 
676
            return {'test_prop':'test_value'}
 
677
 
 
678
        # Cleaned up in setUp()
 
679
        log.properties_handler_registry.register(
 
680
            'trivial_custom_prop_handler',
 
681
            trivial_custom_prop_handler)
 
682
        self.assertFormatterResult("""\
 
683
------------------------------------------------------------
 
684
revno: 1
 
685
test_prop: test_value
 
686
author: John Doe <jdoe@example.com>
 
687
committer: Lorem Ipsum <test@example.com>
 
688
branch nick: test_properties_in_log
 
689
timestamp: Wed 2005-11-23 12:08:27 +1000
 
690
message:
 
691
  add a
 
692
""",
 
693
            wt.branch, log.LongLogFormatter)
 
694
 
 
695
    def test_properties_in_short_log(self):
 
696
        """Log includes the custom properties returned by the registered
 
697
        handlers.
 
698
        """
 
699
        wt = self.make_standard_commit('test_properties_in_short_log')
 
700
        def trivial_custom_prop_handler(revision):
 
701
            return {'test_prop':'test_value'}
 
702
 
 
703
        log.properties_handler_registry.register(
 
704
            'trivial_custom_prop_handler',
 
705
            trivial_custom_prop_handler)
 
706
        self.assertFormatterResult("""\
 
707
    1 John Doe\t2005-11-23
 
708
      test_prop: test_value
 
709
      add a
 
710
 
 
711
""",
 
712
            wt.branch, log.ShortLogFormatter)
 
713
 
 
714
    def test_error_in_properties_handler(self):
 
715
        """Log includes the custom properties returned by the registered
 
716
        handlers.
 
717
        """
 
718
        wt = self.make_standard_commit('error_in_properties_handler',
 
719
            revprops={'first_prop':'first_value'})
 
720
        sio = self.make_utf8_encoded_stringio()
 
721
        formatter = log.LongLogFormatter(to_file=sio)
 
722
        def trivial_custom_prop_handler(revision):
 
723
            raise StandardError("a test error")
 
724
 
 
725
        log.properties_handler_registry.register(
 
726
            'trivial_custom_prop_handler',
 
727
            trivial_custom_prop_handler)
 
728
        self.assertRaises(StandardError, log.show_log, wt.branch, formatter,)
 
729
 
 
730
    def test_properties_handler_bad_argument(self):
 
731
        wt = self.make_standard_commit('bad_argument',
 
732
              revprops={'a_prop':'test_value'})
 
733
        sio = self.make_utf8_encoded_stringio()
 
734
        formatter = log.LongLogFormatter(to_file=sio)
 
735
        def bad_argument_prop_handler(revision):
 
736
            return {'custom_prop_name':revision.properties['a_prop']}
 
737
 
 
738
        log.properties_handler_registry.register(
 
739
            'bad_argument_prop_handler',
 
740
            bad_argument_prop_handler)
 
741
 
 
742
        self.assertRaises(AttributeError, formatter.show_properties,
 
743
                          'a revision', '')
 
744
 
 
745
        revision = wt.branch.repository.get_revision(wt.branch.last_revision())
 
746
        formatter.show_properties(revision, '')
 
747
        self.assertEqualDiff('''custom_prop_name: test_value\n''',
 
748
                             sio.getvalue())
 
749
 
 
750
 
 
751
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
 
752
 
 
753
    def test_long_verbose_log(self):
 
754
        """Verbose log includes changed files
 
755
 
 
756
        bug #4676
 
757
        """
 
758
        wt = self.make_standard_commit('test_long_verbose_log', authors=[])
 
759
        self.assertFormatterResult("""\
 
760
------------------------------------------------------------
 
761
revno: 1
 
762
committer: Lorem Ipsum <test@example.com>
 
763
branch nick: test_long_verbose_log
 
764
timestamp: Wed 2005-11-23 12:08:27 +1000
 
765
message:
 
766
  add a
 
767
added:
 
768
  a
 
769
""",
 
770
            wt.branch, log.LongLogFormatter,
 
771
            formatter_kwargs=dict(levels=1),
 
772
            show_log_kwargs=dict(verbose=True))
 
773
 
 
774
    def test_long_verbose_contain_deltas(self):
 
775
        wt = self.make_branch_and_tree('parent')
 
776
        self.build_tree(['parent/f1', 'parent/f2'])
 
777
        wt.add(['f1','f2'])
 
778
        wt.commit('first post')
 
779
        child_wt = wt.bzrdir.sprout('child').open_workingtree()
 
780
        os.unlink('child/f1')
 
781
        self.build_tree_contents([('child/f2', 'hello\n')])
 
782
        child_wt.commit('removed f1 and modified f2')
 
783
        wt.merge_from_branch(child_wt.branch)
 
784
        wt.commit('merge branch 1')
 
785
        self.assertFormatterResult("""\
 
786
------------------------------------------------------------
 
787
revno: 2 [merge]
 
788
committer: Lorem Ipsum <test@example.com>
 
789
branch nick: parent
 
790
timestamp: Just now
 
791
message:
 
792
  merge branch 1
 
793
removed:
 
794
  f1
 
795
modified:
 
796
  f2
 
797
------------------------------------------------------------
 
798
revno: 1
 
799
committer: Lorem Ipsum <test@example.com>
 
800
branch nick: parent
 
801
timestamp: Just now
 
802
message:
 
803
  first post
 
804
added:
 
805
  f1
 
806
  f2
 
807
""",
 
808
            wt.branch, log.LongLogFormatter,
 
809
            formatter_kwargs=dict(levels=1),
 
810
            show_log_kwargs=dict(verbose=True),
 
811
            normalize=True)
 
812
 
 
813
    def test_long_trailing_newlines(self):
 
814
        wt = self.make_branch_and_tree('.')
 
815
        b = make_commits_with_trailing_newlines(wt)
 
816
        self.assertFormatterResult("""\
 
817
------------------------------------------------------------
 
818
revno: 3
 
819
committer: Joe Foo <joe@foo.com>
 
820
branch nick: test
 
821
timestamp: Mon 2005-11-21 09:32:56 -0600
 
822
message:
 
823
  single line with trailing newline
 
824
------------------------------------------------------------
 
825
revno: 2
 
826
author: Joe Bar <joe@bar.com>
 
827
committer: Joe Foo <joe@foo.com>
 
828
branch nick: test
 
829
timestamp: Mon 2005-11-21 09:27:22 -0600
 
830
message:
 
831
  multiline
 
832
  log
 
833
  message
 
834
------------------------------------------------------------
 
835
revno: 1
 
836
committer: Joe Foo <joe@foo.com>
 
837
branch nick: test
 
838
timestamp: Mon 2005-11-21 09:24:15 -0600
 
839
message:
 
840
  simple log message
 
841
""",
 
842
        b, log.LongLogFormatter,
 
843
        formatter_kwargs=dict(levels=1))
 
844
 
 
845
    def test_long_author_in_log(self):
 
846
        """Log includes the author name if it's set in
 
847
        the revision properties
 
848
        """
 
849
        wt = self.make_standard_commit('test_author_log')
 
850
        self.assertFormatterResult("""\
 
851
------------------------------------------------------------
 
852
revno: 1
 
853
author: John Doe <jdoe@example.com>
 
854
committer: Lorem Ipsum <test@example.com>
 
855
branch nick: test_author_log
 
856
timestamp: Wed 2005-11-23 12:08:27 +1000
 
857
message:
 
858
  add a
 
859
""",
 
860
            wt.branch, log.LongLogFormatter,
 
861
            formatter_kwargs=dict(levels=1))
 
862
 
 
863
    def test_long_properties_in_log(self):
 
864
        """Log includes the custom properties returned by the registered
 
865
        handlers.
 
866
        """
 
867
        wt = self.make_standard_commit('test_properties_in_log')
 
868
        def trivial_custom_prop_handler(revision):
 
869
            return {'test_prop':'test_value'}
 
870
 
 
871
        log.properties_handler_registry.register(
 
872
            'trivial_custom_prop_handler',
 
873
            trivial_custom_prop_handler)
 
874
        self.assertFormatterResult("""\
 
875
------------------------------------------------------------
 
876
revno: 1
 
877
test_prop: test_value
 
878
author: John Doe <jdoe@example.com>
 
879
committer: Lorem Ipsum <test@example.com>
 
880
branch nick: test_properties_in_log
 
881
timestamp: Wed 2005-11-23 12:08:27 +1000
 
882
message:
 
883
  add a
 
884
""",
 
885
            wt.branch, log.LongLogFormatter,
 
886
            formatter_kwargs=dict(levels=1))
 
887
 
 
888
 
 
889
class TestLineLogFormatter(TestCaseForLogFormatter):
 
890
 
 
891
    def test_line_log(self):
 
892
        """Line log should show revno
 
893
 
 
894
        bug #5162
 
895
        """
 
896
        wt = self.make_standard_commit('test-line-log',
 
897
                committer='Line-Log-Formatter Tester <test@line.log>',
 
898
                authors=[])
 
899
        self.assertFormatterResult("""\
 
900
1: Line-Log-Formatte... 2005-11-23 add a
 
901
""",
 
902
            wt.branch, log.LineLogFormatter)
 
903
 
 
904
    def test_trailing_newlines(self):
 
905
        wt = self.make_branch_and_tree('.')
 
906
        b = make_commits_with_trailing_newlines(wt)
 
907
        self.assertFormatterResult("""\
 
908
3: Joe Foo 2005-11-21 single line with trailing newline
 
909
2: Joe Bar 2005-11-21 multiline
 
910
1: Joe Foo 2005-11-21 simple log message
 
911
""",
 
912
            b, log.LineLogFormatter)
 
913
 
 
914
    def test_line_log_single_merge_revision(self):
 
915
        wt = self._prepare_tree_with_merges()
 
916
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
917
        rev = revspec.in_history(wt.branch)
 
918
        self.assertFormatterResult("""\
 
919
1.1.1: Joe Foo 2005-11-22 rev-merged
 
920
""",
 
921
            wt.branch, log.LineLogFormatter,
 
922
            show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
923
 
 
924
    def test_line_log_with_tags(self):
 
925
        wt = self._prepare_tree_with_merges(with_tags=True)
 
926
        self.assertFormatterResult("""\
 
927
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
 
928
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
 
929
1: Joe Foo 2005-11-22 rev-1
 
930
""",
 
931
            wt.branch, log.LineLogFormatter)
 
932
 
 
933
 
 
934
class TestLineLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
 
935
 
 
936
    def test_line_merge_revs_log(self):
 
937
        """Line log should show revno
 
938
 
 
939
        bug #5162
 
940
        """
 
941
        wt = self.make_standard_commit('test-line-log',
 
942
                committer='Line-Log-Formatter Tester <test@line.log>',
 
943
                authors=[])
 
944
        self.assertFormatterResult("""\
 
945
1: Line-Log-Formatte... 2005-11-23 add a
 
946
""",
 
947
            wt.branch, log.LineLogFormatter)
 
948
 
 
949
    def test_line_merge_revs_log_single_merge_revision(self):
 
950
        wt = self.make_branch_and_memory_tree('.')
 
951
        wt.lock_write()
 
952
        self.addCleanup(wt.unlock)
 
953
        wt.add('')
 
954
        wt.commit('rev-1', rev_id='rev-1',
 
955
                  timestamp=1132586655, timezone=36000,
 
956
                  committer='Joe Foo <joe@foo.com>')
 
957
        wt.commit('rev-merged', rev_id='rev-2a',
 
958
                  timestamp=1132586700, timezone=36000,
 
959
                  committer='Joe Foo <joe@foo.com>')
 
960
        wt.set_parent_ids(['rev-1', 'rev-2a'])
 
961
        wt.branch.set_last_revision_info(1, 'rev-1')
 
962
        wt.commit('rev-2', rev_id='rev-2b',
 
963
                  timestamp=1132586800, timezone=36000,
 
964
                  committer='Joe Foo <joe@foo.com>')
 
965
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
966
        rev = revspec.in_history(wt.branch)
 
967
        self.assertFormatterResult("""\
 
968
1.1.1: Joe Foo 2005-11-22 rev-merged
 
969
""",
 
970
            wt.branch, log.LineLogFormatter,
 
971
            formatter_kwargs=dict(levels=0),
 
972
            show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
973
 
 
974
    def test_line_merge_revs_log_with_merges(self):
 
975
        wt = self.make_branch_and_memory_tree('.')
 
976
        wt.lock_write()
 
977
        self.addCleanup(wt.unlock)
 
978
        wt.add('')
 
979
        wt.commit('rev-1', rev_id='rev-1',
 
980
                  timestamp=1132586655, timezone=36000,
 
981
                  committer='Joe Foo <joe@foo.com>')
 
982
        wt.commit('rev-merged', rev_id='rev-2a',
 
983
                  timestamp=1132586700, timezone=36000,
 
984
                  committer='Joe Foo <joe@foo.com>')
 
985
        wt.set_parent_ids(['rev-1', 'rev-2a'])
 
986
        wt.branch.set_last_revision_info(1, 'rev-1')
 
987
        wt.commit('rev-2', rev_id='rev-2b',
 
988
                  timestamp=1132586800, timezone=36000,
 
989
                  committer='Joe Foo <joe@foo.com>')
 
990
        self.assertFormatterResult("""\
 
991
2: Joe Foo 2005-11-22 [merge] rev-2
 
992
  1.1.1: Joe Foo 2005-11-22 rev-merged
 
993
1: Joe Foo 2005-11-22 rev-1
 
994
""",
 
995
            wt.branch, log.LineLogFormatter,
 
996
            formatter_kwargs=dict(levels=0))
 
997
 
 
998
 
 
999
class TestGetViewRevisions(tests.TestCaseWithTransport):
 
1000
 
 
1001
    def make_tree_with_commits(self):
 
1002
        """Create a tree with well-known revision ids"""
 
1003
        wt = self.make_branch_and_tree('tree1')
 
1004
        wt.commit('commit one', rev_id='1')
 
1005
        wt.commit('commit two', rev_id='2')
 
1006
        wt.commit('commit three', rev_id='3')
 
1007
        mainline_revs = [None, '1', '2', '3']
 
1008
        rev_nos = {'1': 1, '2': 2, '3': 3}
 
1009
        return mainline_revs, rev_nos, wt
 
1010
 
 
1011
    def make_tree_with_merges(self):
 
1012
        """Create a tree with well-known revision ids and a merge"""
 
1013
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
1014
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
1015
        tree2.commit('four-a', rev_id='4a')
 
1016
        wt.merge_from_branch(tree2.branch)
 
1017
        wt.commit('four-b', rev_id='4b')
 
1018
        mainline_revs.append('4b')
 
1019
        rev_nos['4b'] = 4
 
1020
        # 4a: 3.1.1
 
1021
        return mainline_revs, rev_nos, wt
 
1022
 
 
1023
    def make_branch_with_many_merges(self):
 
1024
        """Create a tree with well-known revision ids"""
 
1025
        builder = self.make_branch_builder('tree1')
 
1026
        builder.start_series()
 
1027
        builder.build_snapshot('1', None, [
 
1028
            ('add', ('', 'TREE_ROOT', 'directory', '')),
 
1029
            ('add', ('f', 'f-id', 'file', '1\n'))])
 
1030
        builder.build_snapshot('2', ['1'], [])
 
1031
        builder.build_snapshot('3a', ['2'], [
 
1032
            ('modify', ('f-id', '1\n2\n3a\n'))])
 
1033
        builder.build_snapshot('3b', ['2', '3a'], [
 
1034
            ('modify', ('f-id', '1\n2\n3a\n'))])
 
1035
        builder.build_snapshot('3c', ['2', '3b'], [
 
1036
            ('modify', ('f-id', '1\n2\n3a\n'))])
 
1037
        builder.build_snapshot('4a', ['3b'], [])
 
1038
        builder.build_snapshot('4b', ['3c', '4a'], [])
 
1039
        builder.finish_series()
 
1040
 
 
1041
        # 1
 
1042
        # |
 
1043
        # 2-.
 
1044
        # |\ \
 
1045
        # | | 3a
 
1046
        # | |/
 
1047
        # | 3b
 
1048
        # |/|
 
1049
        # 3c4a
 
1050
        # |/
 
1051
        # 4b
 
1052
 
 
1053
        mainline_revs = [None, '1', '2', '3c', '4b']
 
1054
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
 
1055
        full_rev_nos_for_reference = {
 
1056
            '1': '1',
 
1057
            '2': '2',
 
1058
            '3a': '2.1.1', #first commit tree 3
 
1059
            '3b': '2.2.1', # first commit tree 2
 
1060
            '3c': '3', #merges 3b to main
 
1061
            '4a': '2.2.2', # second commit tree 2
 
1062
            '4b': '4', # merges 4a to main
 
1063
            }
 
1064
        return mainline_revs, rev_nos, builder.get_branch()
 
1065
 
 
1066
    def test_get_view_revisions_forward(self):
 
1067
        """Test the get_view_revisions method"""
 
1068
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
1069
        wt.lock_read()
 
1070
        self.addCleanup(wt.unlock)
 
1071
        revisions = list(log.get_view_revisions(
 
1072
                mainline_revs, rev_nos, wt.branch, 'forward'))
 
1073
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
 
1074
                         revisions)
 
1075
        revisions2 = list(log.get_view_revisions(
 
1076
                mainline_revs, rev_nos, wt.branch, 'forward',
 
1077
                include_merges=False))
 
1078
        self.assertEqual(revisions, revisions2)
 
1079
 
 
1080
    def test_get_view_revisions_reverse(self):
 
1081
        """Test the get_view_revisions with reverse"""
 
1082
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
1083
        wt.lock_read()
 
1084
        self.addCleanup(wt.unlock)
 
1085
        revisions = list(log.get_view_revisions(
 
1086
                mainline_revs, rev_nos, wt.branch, 'reverse'))
 
1087
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
 
1088
                         revisions)
 
1089
        revisions2 = list(log.get_view_revisions(
 
1090
                mainline_revs, rev_nos, wt.branch, 'reverse',
 
1091
                include_merges=False))
 
1092
        self.assertEqual(revisions, revisions2)
 
1093
 
 
1094
    def test_get_view_revisions_merge(self):
 
1095
        """Test get_view_revisions when there are merges"""
 
1096
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
1097
        wt.lock_read()
 
1098
        self.addCleanup(wt.unlock)
 
1099
        revisions = list(log.get_view_revisions(
 
1100
                mainline_revs, rev_nos, wt.branch, 'forward'))
 
1101
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
1102
                          ('4b', '4', 0), ('4a', '3.1.1', 1)],
 
1103
                         revisions)
 
1104
        revisions = list(log.get_view_revisions(
 
1105
                mainline_revs, rev_nos, wt.branch, 'forward',
 
1106
                include_merges=False))
 
1107
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
1108
                          ('4b', '4', 0)],
 
1109
                         revisions)
 
1110
 
 
1111
    def test_get_view_revisions_merge_reverse(self):
 
1112
        """Test get_view_revisions in reverse when there are merges"""
 
1113
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
1114
        wt.lock_read()
 
1115
        self.addCleanup(wt.unlock)
 
1116
        revisions = list(log.get_view_revisions(
 
1117
                mainline_revs, rev_nos, wt.branch, 'reverse'))
 
1118
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
 
1119
                          ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
 
1120
                         revisions)
 
1121
        revisions = list(log.get_view_revisions(
 
1122
                mainline_revs, rev_nos, wt.branch, 'reverse',
 
1123
                include_merges=False))
 
1124
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
 
1125
                          ('1', '1', 0)],
 
1126
                         revisions)
 
1127
 
 
1128
    def test_get_view_revisions_merge2(self):
 
1129
        """Test get_view_revisions when there are merges"""
 
1130
        mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
 
1131
        b.lock_read()
 
1132
        self.addCleanup(b.unlock)
 
1133
        revisions = list(log.get_view_revisions(
 
1134
                mainline_revs, rev_nos, b, 'forward'))
 
1135
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
1136
                    ('3b', '2.2.1', 1), ('3a', '2.1.1', 2), ('4b', '4', 0),
 
1137
                    ('4a', '2.2.2', 1)]
 
1138
        self.assertEqual(expected, revisions)
 
1139
        revisions = list(log.get_view_revisions(
 
1140
                mainline_revs, rev_nos, b, 'forward',
 
1141
                include_merges=False))
 
1142
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
1143
                          ('4b', '4', 0)],
 
1144
                         revisions)
 
1145
 
 
1146
 
 
1147
    def test_file_id_for_range(self):
 
1148
        mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
 
1149
        b.lock_read()
 
1150
        self.addCleanup(b.unlock)
 
1151
 
 
1152
        def rev_from_rev_id(revid, branch):
 
1153
            revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
 
1154
            return revspec.in_history(branch)
 
1155
 
 
1156
        def view_revs(start_rev, end_rev, file_id, direction):
 
1157
            revs = log.calculate_view_revisions(
 
1158
                b,
 
1159
                start_rev, # start_revision
 
1160
                end_rev, # end_revision
 
1161
                direction, # direction
 
1162
                file_id, # specific_fileid
 
1163
                True, # generate_merge_revisions
 
1164
                )
 
1165
            return revs
 
1166
 
 
1167
        rev_3a = rev_from_rev_id('3a', b)
 
1168
        rev_4b = rev_from_rev_id('4b', b)
 
1169
        self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1), ('3a', '2.1.1', 2)],
 
1170
                          view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
 
1171
        # Note: 3c still appears before 3a here because of depth-based sorting
 
1172
        self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1), ('3a', '2.1.1', 2)],
 
1173
                          view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
 
1174
 
 
1175
 
 
1176
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
 
1177
 
 
1178
    def create_tree_with_single_merge(self):
 
1179
        """Create a branch with a moderate layout.
 
1180
 
 
1181
        The revision graph looks like:
 
1182
 
 
1183
           A
 
1184
           |\
 
1185
           B C
 
1186
           |/
 
1187
           D
 
1188
 
 
1189
        In this graph, A introduced files f1 and f2 and f3.
 
1190
        B modifies f1 and f3, and C modifies f2 and f3.
 
1191
        D merges the changes from B and C and resolves the conflict for f3.
 
1192
        """
 
1193
        # TODO: jam 20070218 This seems like it could really be done
 
1194
        #       with make_branch_and_memory_tree() if we could just
 
1195
        #       create the content of those files.
 
1196
        # TODO: jam 20070218 Another alternative is that we would really
 
1197
        #       like to only create this tree 1 time for all tests that
 
1198
        #       use it. Since 'log' only uses the tree in a readonly
 
1199
        #       fashion, it seems a shame to regenerate an identical
 
1200
        #       tree for each test.
 
1201
        tree = self.make_branch_and_tree('tree')
 
1202
        tree.lock_write()
 
1203
        self.addCleanup(tree.unlock)
 
1204
 
 
1205
        self.build_tree_contents([('tree/f1', 'A\n'),
 
1206
                                  ('tree/f2', 'A\n'),
 
1207
                                  ('tree/f3', 'A\n'),
 
1208
                                 ])
 
1209
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
 
1210
        tree.commit('A', rev_id='A')
 
1211
 
 
1212
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
 
1213
                                  ('tree/f3', 'A\nC\n'),
 
1214
                                 ])
 
1215
        tree.commit('C', rev_id='C')
 
1216
        # Revert back to A to build the other history.
 
1217
        tree.set_last_revision('A')
 
1218
        tree.branch.set_last_revision_info(1, 'A')
 
1219
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
1220
                                  ('tree/f2', 'A\n'),
 
1221
                                  ('tree/f3', 'A\nB\n'),
 
1222
                                 ])
 
1223
        tree.commit('B', rev_id='B')
 
1224
        tree.set_parent_ids(['B', 'C'])
 
1225
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
1226
                                  ('tree/f2', 'A\nC\n'),
 
1227
                                  ('tree/f3', 'A\nB\nC\n'),
 
1228
                                 ])
 
1229
        tree.commit('D', rev_id='D')
 
1230
 
 
1231
        # Switch to a read lock for this tree.
 
1232
        # We still have an addCleanup(tree.unlock) pending
 
1233
        tree.unlock()
 
1234
        tree.lock_read()
 
1235
        return tree
 
1236
 
 
1237
    def check_delta(self, delta, **kw):
 
1238
        """Check the filenames touched by a delta are as expected.
 
1239
 
 
1240
        Caller only have to pass in the list of files for each part, all
 
1241
        unspecified parts are considered empty (and checked as such).
 
1242
        """
 
1243
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
 
1244
            # By default we expect an empty list
 
1245
            expected = kw.get(n, [])
 
1246
            # strip out only the path components
 
1247
            got = [x[0] for x in getattr(delta, n)]
 
1248
            self.assertEqual(expected, got)
 
1249
 
 
1250
    def test_tree_with_single_merge(self):
 
1251
        """Make sure the tree layout is correct."""
 
1252
        tree = self.create_tree_with_single_merge()
 
1253
        rev_A_tree = tree.branch.repository.revision_tree('A')
 
1254
        rev_B_tree = tree.branch.repository.revision_tree('B')
 
1255
        rev_C_tree = tree.branch.repository.revision_tree('C')
 
1256
        rev_D_tree = tree.branch.repository.revision_tree('D')
 
1257
 
 
1258
        self.check_delta(rev_B_tree.changes_from(rev_A_tree),
 
1259
                         modified=['f1', 'f3'])
 
1260
 
 
1261
        self.check_delta(rev_C_tree.changes_from(rev_A_tree),
 
1262
                         modified=['f2', 'f3'])
 
1263
 
 
1264
        self.check_delta(rev_D_tree.changes_from(rev_B_tree),
 
1265
                         modified=['f2', 'f3'])
 
1266
 
 
1267
        self.check_delta(rev_D_tree.changes_from(rev_C_tree),
 
1268
                         modified=['f1', 'f3'])
 
1269
 
 
1270
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
 
1271
        """Ensure _filter_revisions_touching_file_id returns the right values.
 
1272
 
 
1273
        Get the return value from _filter_revisions_touching_file_id and make
 
1274
        sure they are correct.
 
1275
        """
 
1276
        # The api for _filter_revisions_touching_file_id is a little crazy.
 
1277
        # So we do the setup here.
 
1278
        mainline = tree.branch.revision_history()
 
1279
        mainline.insert(0, None)
 
1280
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
 
1281
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
 
1282
                                                'reverse', True)
 
1283
        actual_revs = log._filter_revisions_touching_file_id(
 
1284
                            tree.branch,
 
1285
                            file_id,
 
1286
                            list(view_revs_iter))
 
1287
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
 
1288
 
 
1289
    def test_file_id_f1(self):
 
1290
        tree = self.create_tree_with_single_merge()
 
1291
        # f1 should be marked as modified by revisions A and B
 
1292
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
 
1293
 
 
1294
    def test_file_id_f2(self):
 
1295
        tree = self.create_tree_with_single_merge()
 
1296
        # f2 should be marked as modified by revisions A, C, and D
 
1297
        # because D merged the changes from C.
 
1298
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
1299
 
 
1300
    def test_file_id_f3(self):
 
1301
        tree = self.create_tree_with_single_merge()
 
1302
        # f3 should be marked as modified by revisions A, B, C, and D
 
1303
        self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
 
1304
 
 
1305
    def test_file_id_with_ghosts(self):
 
1306
        # This is testing bug #209948, where having a ghost would cause
 
1307
        # _filter_revisions_touching_file_id() to fail.
 
1308
        tree = self.create_tree_with_single_merge()
 
1309
        # We need to add a revision, so switch back to a write-locked tree
 
1310
        # (still a single addCleanup(tree.unlock) pending).
 
1311
        tree.unlock()
 
1312
        tree.lock_write()
 
1313
        first_parent = tree.last_revision()
 
1314
        tree.set_parent_ids([first_parent, 'ghost-revision-id'])
 
1315
        self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
 
1316
        tree.commit('commit with a ghost', rev_id='XX')
 
1317
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
 
1318
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
1319
 
 
1320
    def test_unknown_file_id(self):
 
1321
        tree = self.create_tree_with_single_merge()
 
1322
        self.assertAllRevisionsForFileID(tree, 'unknown', [])
 
1323
 
 
1324
    def test_empty_branch_unknown_file_id(self):
 
1325
        tree = self.make_branch_and_tree('tree')
 
1326
        self.assertAllRevisionsForFileID(tree, 'unknown', [])
 
1327
 
 
1328
 
 
1329
class TestShowChangedRevisions(tests.TestCaseWithTransport):
 
1330
 
 
1331
    def test_show_changed_revisions_verbose(self):
 
1332
        tree = self.make_branch_and_tree('tree_a')
 
1333
        self.build_tree(['tree_a/foo'])
 
1334
        tree.add('foo')
 
1335
        tree.commit('bar', rev_id='bar-id')
 
1336
        s = self.make_utf8_encoded_stringio()
 
1337
        log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
 
1338
        self.assertContainsRe(s.getvalue(), 'bar')
 
1339
        self.assertNotContainsRe(s.getvalue(), 'foo')
 
1340
 
 
1341
 
 
1342
class TestLogFormatter(tests.TestCase):
 
1343
 
 
1344
    def test_short_committer(self):
 
1345
        rev = revision.Revision('a-id')
 
1346
        rev.committer = 'John Doe <jdoe@example.com>'
 
1347
        lf = log.LogFormatter(None)
 
1348
        self.assertEqual('John Doe', lf.short_committer(rev))
 
1349
        rev.committer = 'John Smith <jsmith@example.com>'
 
1350
        self.assertEqual('John Smith', lf.short_committer(rev))
 
1351
        rev.committer = 'John Smith'
 
1352
        self.assertEqual('John Smith', lf.short_committer(rev))
 
1353
        rev.committer = 'jsmith@example.com'
 
1354
        self.assertEqual('jsmith@example.com', lf.short_committer(rev))
 
1355
        rev.committer = '<jsmith@example.com>'
 
1356
        self.assertEqual('jsmith@example.com', lf.short_committer(rev))
 
1357
        rev.committer = 'John Smith jsmith@example.com'
 
1358
        self.assertEqual('John Smith', lf.short_committer(rev))
 
1359
 
 
1360
    def test_short_author(self):
 
1361
        rev = revision.Revision('a-id')
 
1362
        rev.committer = 'John Doe <jdoe@example.com>'
 
1363
        lf = log.LogFormatter(None)
 
1364
        self.assertEqual('John Doe', lf.short_author(rev))
 
1365
        rev.properties['author'] = 'John Smith <jsmith@example.com>'
 
1366
        self.assertEqual('John Smith', lf.short_author(rev))
 
1367
        rev.properties['author'] = 'John Smith'
 
1368
        self.assertEqual('John Smith', lf.short_author(rev))
 
1369
        rev.properties['author'] = 'jsmith@example.com'
 
1370
        self.assertEqual('jsmith@example.com', lf.short_author(rev))
 
1371
        rev.properties['author'] = '<jsmith@example.com>'
 
1372
        self.assertEqual('jsmith@example.com', lf.short_author(rev))
 
1373
        rev.properties['author'] = 'John Smith jsmith@example.com'
 
1374
        self.assertEqual('John Smith', lf.short_author(rev))
 
1375
        del rev.properties['author']
 
1376
        rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
 
1377
                'Jane Rey <jrey@example.com>')
 
1378
        self.assertEqual('John Smith', lf.short_author(rev))
 
1379
 
 
1380
 
 
1381
class TestReverseByDepth(tests.TestCase):
 
1382
    """Test reverse_by_depth behavior.
 
1383
 
 
1384
    This is used to present revisions in forward (oldest first) order in a nice
 
1385
    layout.
 
1386
 
 
1387
    The tests use lighter revision description to ease reading.
 
1388
    """
 
1389
 
 
1390
    def assertReversed(self, forward, backward):
 
1391
        # Transform the descriptions to suit the API: tests use (revno, depth),
 
1392
        # while the API expects (revid, revno, depth)
 
1393
        def complete_revisions(l):
 
1394
            """Transform the description to suit the API.
 
1395
 
 
1396
            Tests use (revno, depth) whil the API expects (revid, revno, depth).
 
1397
            Since the revid is arbitrary, we just duplicate revno
 
1398
            """
 
1399
            return [ (r, r, d) for r, d in l]
 
1400
        forward = complete_revisions(forward)
 
1401
        backward= complete_revisions(backward)
 
1402
        self.assertEqual(forward, log.reverse_by_depth(backward))
 
1403
 
 
1404
 
 
1405
    def test_mainline_revisions(self):
 
1406
        self.assertReversed([( '1', 0), ('2', 0)],
 
1407
                            [('2', 0), ('1', 0)])
 
1408
 
 
1409
    def test_merged_revisions(self):
 
1410
        self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
 
1411
                            [('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
 
1412
    def test_shifted_merged_revisions(self):
 
1413
        """Test irregular layout.
 
1414
 
 
1415
        Requesting revisions touching a file can produce "holes" in the depths.
 
1416
        """
 
1417
        self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
 
1418
                            [('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
 
1419
 
 
1420
    def test_merged_without_child_revisions(self):
 
1421
        """Test irregular layout.
 
1422
 
 
1423
        Revision ranges can produce "holes" in the depths.
 
1424
        """
 
1425
        # When a revision of higher depth doesn't follow one of lower depth, we
 
1426
        # assume a lower depth one is virtually there
 
1427
        self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
 
1428
                            [('4', 4), ('3', 3), ('2', 2), ('1', 2),])
 
1429
        # So we get the same order after reversing below even if the original
 
1430
        # revisions are not in the same order.
 
1431
        self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
 
1432
                            [('3', 3), ('4', 4), ('2', 2), ('1', 2),])
 
1433
 
 
1434
 
 
1435
class TestHistoryChange(tests.TestCaseWithTransport):
 
1436
 
 
1437
    def setup_a_tree(self):
 
1438
        tree = self.make_branch_and_tree('tree')
 
1439
        tree.lock_write()
 
1440
        self.addCleanup(tree.unlock)
 
1441
        tree.commit('1a', rev_id='1a')
 
1442
        tree.commit('2a', rev_id='2a')
 
1443
        tree.commit('3a', rev_id='3a')
 
1444
        return tree
 
1445
 
 
1446
    def setup_ab_tree(self):
 
1447
        tree = self.setup_a_tree()
 
1448
        tree.set_last_revision('1a')
 
1449
        tree.branch.set_last_revision_info(1, '1a')
 
1450
        tree.commit('2b', rev_id='2b')
 
1451
        tree.commit('3b', rev_id='3b')
 
1452
        return tree
 
1453
 
 
1454
    def setup_ac_tree(self):
 
1455
        tree = self.setup_a_tree()
 
1456
        tree.set_last_revision(revision.NULL_REVISION)
 
1457
        tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
 
1458
        tree.commit('1c', rev_id='1c')
 
1459
        tree.commit('2c', rev_id='2c')
 
1460
        tree.commit('3c', rev_id='3c')
 
1461
        return tree
 
1462
 
 
1463
    def test_all_new(self):
 
1464
        tree = self.setup_ab_tree()
 
1465
        old, new = log.get_history_change('1a', '3a', tree.branch.repository)
 
1466
        self.assertEqual([], old)
 
1467
        self.assertEqual(['2a', '3a'], new)
 
1468
 
 
1469
    def test_all_old(self):
 
1470
        tree = self.setup_ab_tree()
 
1471
        old, new = log.get_history_change('3a', '1a', tree.branch.repository)
 
1472
        self.assertEqual([], new)
 
1473
        self.assertEqual(['2a', '3a'], old)
 
1474
 
 
1475
    def test_null_old(self):
 
1476
        tree = self.setup_ab_tree()
 
1477
        old, new = log.get_history_change(revision.NULL_REVISION,
 
1478
                                          '3a', tree.branch.repository)
 
1479
        self.assertEqual([], old)
 
1480
        self.assertEqual(['1a', '2a', '3a'], new)
 
1481
 
 
1482
    def test_null_new(self):
 
1483
        tree = self.setup_ab_tree()
 
1484
        old, new = log.get_history_change('3a', revision.NULL_REVISION,
 
1485
                                          tree.branch.repository)
 
1486
        self.assertEqual([], new)
 
1487
        self.assertEqual(['1a', '2a', '3a'], old)
 
1488
 
 
1489
    def test_diverged(self):
 
1490
        tree = self.setup_ab_tree()
 
1491
        old, new = log.get_history_change('3a', '3b', tree.branch.repository)
 
1492
        self.assertEqual(old, ['2a', '3a'])
 
1493
        self.assertEqual(new, ['2b', '3b'])
 
1494
 
 
1495
    def test_unrelated(self):
 
1496
        tree = self.setup_ac_tree()
 
1497
        old, new = log.get_history_change('3a', '3c', tree.branch.repository)
 
1498
        self.assertEqual(old, ['1a', '2a', '3a'])
 
1499
        self.assertEqual(new, ['1c', '2c', '3c'])
 
1500
 
 
1501
    def test_show_branch_change(self):
 
1502
        tree = self.setup_ab_tree()
 
1503
        s = StringIO()
 
1504
        log.show_branch_change(tree.branch, s, 3, '3a')
 
1505
        self.assertContainsRe(s.getvalue(),
 
1506
            '[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
 
1507
            '[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
 
1508
 
 
1509
    def test_show_branch_change_no_change(self):
 
1510
        tree = self.setup_ab_tree()
 
1511
        s = StringIO()
 
1512
        log.show_branch_change(tree.branch, s, 3, '3b')
 
1513
        self.assertEqual(s.getvalue(),
 
1514
            'Nothing seems to have changed\n')
 
1515
 
 
1516
    def test_show_branch_change_no_old(self):
 
1517
        tree = self.setup_ab_tree()
 
1518
        s = StringIO()
 
1519
        log.show_branch_change(tree.branch, s, 2, '2b')
 
1520
        self.assertContainsRe(s.getvalue(), 'Added Revisions:')
 
1521
        self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
 
1522
 
 
1523
    def test_show_branch_change_no_new(self):
 
1524
        tree = self.setup_ab_tree()
 
1525
        tree.branch.set_last_revision_info(2, '2b')
 
1526
        s = StringIO()
 
1527
        log.show_branch_change(tree.branch, s, 3, '3b')
 
1528
        self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
 
1529
        self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
 
1530
 
 
1531
 
 
1532
 
 
1533
class TestLogWithBugs(TestCaseForLogFormatter):
 
1534
 
 
1535
    def setUp(self):
 
1536
        TestCaseForLogFormatter.setUp(self)
 
1537
        log.properties_handler_registry.register(
 
1538
            'bugs_properties_handler',
 
1539
            log._bugs_properties_handler)
 
1540
 
 
1541
    def make_commits_with_bugs(self):
 
1542
        """Helper method for LogFormatter tests"""
 
1543
        tree = self.make_branch_and_tree(u'.')
 
1544
        self.build_tree(['a', 'b'])
 
1545
        tree.add('a')
 
1546
        tree.commit('simple log message', rev_id='a1',
 
1547
                    timestamp=1132586655.459960938, timezone=-6*3600,
 
1548
                    committer='Joe Foo <joe@foo.com>',
 
1549
                    revprops={'bugs': 'test://bug/id fixed'})
 
1550
        tree.add('b')
 
1551
        tree.commit('multiline\nlog\nmessage\n', rev_id='a2',
 
1552
                    timestamp=1132586842.411175966, timezone=-6*3600,
 
1553
                    committer='Joe Foo <joe@foo.com>',
 
1554
                    authors=['Joe Bar <joe@bar.com>'],
 
1555
                    revprops={'bugs': 'test://bug/id fixed\n'
 
1556
                                      'test://bug/2 fixed'})
 
1557
        return tree
 
1558
 
 
1559
 
 
1560
    def test_long_bugs(self):
 
1561
        tree = self.make_commits_with_bugs()
 
1562
        self.assertFormatterResult("""\
 
1563
------------------------------------------------------------
 
1564
revno: 2
 
1565
fixes bug(s): test://bug/id test://bug/2
 
1566
author: Joe Bar <joe@bar.com>
 
1567
committer: Joe Foo <joe@foo.com>
 
1568
branch nick: work
 
1569
timestamp: Mon 2005-11-21 09:27:22 -0600
 
1570
message:
 
1571
  multiline
 
1572
  log
 
1573
  message
 
1574
------------------------------------------------------------
 
1575
revno: 1
 
1576
fixes bug(s): test://bug/id
 
1577
committer: Joe Foo <joe@foo.com>
 
1578
branch nick: work
 
1579
timestamp: Mon 2005-11-21 09:24:15 -0600
 
1580
message:
 
1581
  simple log message
 
1582
""",
 
1583
            tree.branch, log.LongLogFormatter)
 
1584
 
 
1585
    def test_short_bugs(self):
 
1586
        tree = self.make_commits_with_bugs()
 
1587
        self.assertFormatterResult("""\
 
1588
    2 Joe Bar\t2005-11-21
 
1589
      fixes bug(s): test://bug/id test://bug/2
 
1590
      multiline
 
1591
      log
 
1592
      message
 
1593
 
 
1594
    1 Joe Foo\t2005-11-21
 
1595
      fixes bug(s): test://bug/id
 
1596
      simple log message
 
1597
 
 
1598
""",
 
1599
            tree.branch, log.ShortLogFormatter)
 
1600
 
 
1601
    def test_wrong_bugs_property(self):
 
1602
        tree = self.make_branch_and_tree(u'.')
 
1603
        self.build_tree(['foo'])
 
1604
        tree.commit('simple log message', rev_id='a1',
 
1605
              timestamp=1132586655.459960938, timezone=-6*3600,
 
1606
              committer='Joe Foo <joe@foo.com>',
 
1607
              revprops={'bugs': 'test://bug/id invalid_value'})
 
1608
        self.assertFormatterResult("""\
 
1609
    1 Joe Foo\t2005-11-21
 
1610
      simple log message
 
1611
 
 
1612
""",
 
1613
            tree.branch, log.ShortLogFormatter)
 
1614
 
 
1615
    def test_bugs_handler_present(self):
 
1616
        self.properties_handler_registry.get('bugs_properties_handler')