~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-03-12 03:39:10 UTC
  • mfrom: (4103.3.5 progress)
  • Revision ID: pqm@pqm.ubuntu.com-20090312033910-9umj7rwjo98zl7up
(mbp) small progress improvements

Show diffs side-by-side

added added

removed removed

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