~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

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
17
17
import os
18
18
from cStringIO import StringIO
19
19
 
20
 
from bzrlib.tests import BzrTestBase, TestCaseInTempDir
21
 
from bzrlib.log import LogFormatter, show_log, LongLogFormatter, ShortLogFormatter
 
20
from bzrlib import log, registry
 
21
from bzrlib.tests import TestCase, TestCaseWithTransport
 
22
from bzrlib.log import (show_log,
 
23
                        get_view_revisions,
 
24
                        LogRevision,
 
25
                        LogFormatter,
 
26
                        LongLogFormatter,
 
27
                        ShortLogFormatter,
 
28
                        LineLogFormatter)
22
29
from bzrlib.branch import Branch
23
 
from bzrlib.errors import InvalidRevisionNumber
24
 
 
25
 
class _LogEntry(object):
26
 
    # should probably move into bzrlib.log?
27
 
    pass
 
30
from bzrlib.errors import (
 
31
    BzrCommandError,
 
32
    InvalidRevisionNumber,
 
33
    )
 
34
from bzrlib.revision import Revision
 
35
from bzrlib.revisionspec import (
 
36
    RevisionInfo,
 
37
    RevisionSpec,
 
38
    )
 
39
 
 
40
 
 
41
class TestCaseWithoutPropsHandler(TestCaseWithTransport):
 
42
 
 
43
    def setUp(self):
 
44
        super(TestCaseWithoutPropsHandler, self).setUp()
 
45
        # keep a reference to the "current" custom prop. handler registry
 
46
        self.properties_handler_registry = \
 
47
            log.properties_handler_registry
 
48
        # clean up the registry in log
 
49
        log.properties_handler_registry = registry.Registry()
 
50
        
 
51
    def _cleanup(self):
 
52
        super(TestCaseWithoutPropsHandler, self)._cleanup()
 
53
        # restore the custom properties handler registry
 
54
        log.properties_handler_registry = \
 
55
            self.properties_handler_registry
28
56
 
29
57
 
30
58
class LogCatcher(LogFormatter):
36
64
 
37
65
    We should also test the LogFormatter.
38
66
    """
 
67
 
 
68
    supports_delta = True
 
69
 
39
70
    def __init__(self):
40
71
        super(LogCatcher, self).__init__(to_file=None)
41
72
        self.logs = []
42
 
        
43
 
        
44
 
    def show(self, revno, rev, delta):
45
 
        le = _LogEntry()
46
 
        le.revno = revno
47
 
        le.rev = rev
48
 
        le.delta = delta
49
 
        self.logs.append(le)
50
 
 
51
 
 
52
 
class SimpleLogTest(TestCaseInTempDir):
 
73
 
 
74
    def log_revision(self, revision):
 
75
        self.logs.append(revision)
 
76
 
 
77
 
 
78
class TestShowLog(TestCaseWithTransport):
53
79
 
54
80
    def checkDelta(self, delta, **kw):
55
81
        """Check the filenames touched by a delta are as expected."""
56
82
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
57
83
            expected = kw.get(n, [])
58
 
 
59
 
            # tests are written with unix paths; fix them up for windows
60
 
            #if os.sep != '/':
61
 
            #    expected = [x.replace('/', os.sep) for x in expected]
62
 
 
63
84
            # strip out only the path components
64
85
            got = [x[0] for x in getattr(delta, n)]
65
86
            self.assertEquals(expected, got)
66
87
 
67
88
    def test_cur_revno(self):
68
 
        b = Branch(u'.', init=True)
69
 
 
70
 
        lf = LogCatcher()
71
 
        b.working_tree().commit('empty commit')
72
 
        show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
73
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
74
 
                          start_revision=2, end_revision=1) 
75
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
76
 
                          start_revision=1, end_revision=2) 
77
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
78
 
                          start_revision=0, end_revision=2) 
79
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
80
 
                          start_revision=1, end_revision=0) 
81
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
82
 
                          start_revision=-1, end_revision=1) 
83
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
84
 
                          start_revision=1, end_revision=-1) 
85
 
 
86
 
    def test_cur_revno(self):
87
 
        b = Branch.initialize(u'.')
88
 
 
89
 
        lf = LogCatcher()
90
 
        b.working_tree().commit('empty commit')
 
89
        wt = self.make_branch_and_tree('.')
 
90
        b = wt.branch
 
91
 
 
92
        lf = LogCatcher()
 
93
        wt.commit('empty commit')
91
94
        show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
92
95
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
93
96
                          start_revision=2, end_revision=1) 
105
108
    def test_simple_log(self):
106
109
        eq = self.assertEquals
107
110
        
108
 
        b = Branch.initialize(u'.')
 
111
        wt = self.make_branch_and_tree('.')
 
112
        b = wt.branch
109
113
 
110
114
        lf = LogCatcher()
111
115
        show_log(b, lf)
112
116
        # no entries yet
113
117
        eq(lf.logs, [])
114
118
 
115
 
        b.working_tree().commit('empty commit')
 
119
        wt.commit('empty commit')
116
120
        lf = LogCatcher()
117
121
        show_log(b, lf, verbose=True)
118
122
        eq(len(lf.logs), 1)
119
 
        eq(lf.logs[0].revno, 1)
 
123
        eq(lf.logs[0].revno, '1')
120
124
        eq(lf.logs[0].rev.message, 'empty commit')
121
125
        d = lf.logs[0].delta
122
126
        self.log('log delta: %r' % d)
123
127
        self.checkDelta(d)
124
128
 
125
129
        self.build_tree(['hello'])
126
 
        b.working_tree().add('hello')
127
 
        b.working_tree().commit('add one file')
 
130
        wt.add('hello')
 
131
        wt.commit('add one file',
 
132
                  committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
 
133
                            u'<test@example.com>')
128
134
 
129
 
        lf = StringIO()
 
135
        lf = self.make_utf8_encoded_stringio()
130
136
        # log using regular thing
131
137
        show_log(b, LongLogFormatter(lf))
132
138
        lf.seek(0)
139
145
        eq(len(lf.logs), 2)
140
146
        self.log('log entries:')
141
147
        for logentry in lf.logs:
142
 
            self.log('%4d %s' % (logentry.revno, logentry.rev.message))
 
148
            self.log('%4s %s' % (logentry.revno, logentry.rev.message))
143
149
        
144
150
        # first one is most recent
145
151
        logentry = lf.logs[0]
146
 
        eq(logentry.revno, 2)
 
152
        eq(logentry.revno, '2')
147
153
        eq(logentry.rev.message, 'add one file')
148
154
        d = logentry.delta
149
155
        self.log('log 2 delta: %r' % d)
150
 
        # self.checkDelta(d, added=['hello'])
 
156
        self.checkDelta(d, added=['hello'])
151
157
        
152
158
        # commit a log message with control characters
153
159
        msg = "All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
154
160
        self.log("original commit message: %r", msg)
155
 
        b.working_tree().commit(msg)
 
161
        wt.commit(msg)
156
162
        lf = LogCatcher()
157
163
        show_log(b, lf, verbose=True)
158
164
        committed_msg = lf.logs[0].rev.message
167
173
        # valid XML 1.0 characters.
168
174
        msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
169
175
        self.log("original commit message: %r", msg)
170
 
        b.working_tree().commit(msg)
 
176
        wt.commit(msg)
171
177
        lf = LogCatcher()
172
178
        show_log(b, lf, verbose=True)
173
179
        committed_msg = lf.logs[0].rev.message
174
180
        self.log("escaped commit message: %r", committed_msg)
175
181
        self.assert_(msg == committed_msg)
176
182
 
 
183
    def test_deltas_in_merge_revisions(self):
 
184
        """Check deltas created for both mainline and merge revisions"""
 
185
        eq = self.assertEquals
 
186
        wt = self.make_branch_and_tree('parent')
 
187
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
 
188
        wt.add('file1')
 
189
        wt.add('file2')
 
190
        wt.commit(message='add file1 and file2')
 
191
        self.run_bzr('branch parent child')
 
192
        os.unlink('child/file1')
 
193
        file('child/file2', 'wb').write('hello\n')
 
194
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
 
195
            'child'])
 
196
        os.chdir('parent')
 
197
        self.run_bzr('merge ../child')
 
198
        wt.commit('merge child branch')
 
199
        os.chdir('..')
 
200
        b = wt.branch
 
201
        lf = LogCatcher()
 
202
        lf.supports_merge_revisions = True
 
203
        show_log(b, lf, verbose=True)
 
204
        eq(len(lf.logs),3)
 
205
        logentry = lf.logs[0]
 
206
        eq(logentry.revno, '2')
 
207
        eq(logentry.rev.message, 'merge child branch')
 
208
        d = logentry.delta
 
209
        self.checkDelta(d, removed=['file1'], modified=['file2'])
 
210
        logentry = lf.logs[1]
 
211
        eq(logentry.revno, '1.1.1')
 
212
        eq(logentry.rev.message, 'remove file1 and modify file2')
 
213
        d = logentry.delta
 
214
        self.checkDelta(d, removed=['file1'], modified=['file2'])
 
215
        logentry = lf.logs[2]
 
216
        eq(logentry.revno, '1')
 
217
        eq(logentry.rev.message, 'add file1 and file2')
 
218
        d = logentry.delta
 
219
        self.checkDelta(d, added=['file1', 'file2'])
 
220
 
 
221
    def test_merges_nonsupporting_formatter(self):
 
222
        """Tests that show_log will raise if the formatter doesn't
 
223
        support merge revisions."""
 
224
        wt = self.make_branch_and_memory_tree('.')
 
225
        wt.lock_write()
 
226
        try:
 
227
            wt.add('')
 
228
            wt.commit('rev-1', rev_id='rev-1',
 
229
                      timestamp=1132586655, timezone=36000,
 
230
                      committer='Joe Foo <joe@foo.com>')
 
231
            wt.commit('rev-merged', rev_id='rev-2a',
 
232
                      timestamp=1132586700, timezone=36000,
 
233
                      committer='Joe Foo <joe@foo.com>')
 
234
            wt.set_parent_ids(['rev-1', 'rev-2a'])
 
235
            wt.branch.set_last_revision_info(1, 'rev-1')
 
236
            wt.commit('rev-2', rev_id='rev-2b',
 
237
                      timestamp=1132586800, timezone=36000,
 
238
                      committer='Joe Foo <joe@foo.com>')
 
239
            logfile = self.make_utf8_encoded_stringio()
 
240
            formatter = ShortLogFormatter(to_file=logfile)
 
241
            wtb = wt.branch
 
242
            lf = LogCatcher()
 
243
            revspec = RevisionSpec.from_string('1.1.1')
 
244
            rev = revspec.in_history(wtb)
 
245
            self.assertRaises(BzrCommandError, show_log, wtb, lf,
 
246
                              start_revision=rev, end_revision=rev)
 
247
        finally:
 
248
            wt.unlock()
 
249
 
 
250
 
 
251
def make_commits_with_trailing_newlines(wt):
 
252
    """Helper method for LogFormatter tests"""    
 
253
    b = wt.branch
 
254
    b.nick='test'
 
255
    open('a', 'wb').write('hello moto\n')
 
256
    wt.add('a')
 
257
    wt.commit('simple log message', rev_id='a1',
 
258
              timestamp=1132586655.459960938, timezone=-6*3600,
 
259
              committer='Joe Foo <joe@foo.com>')
 
260
    open('b', 'wb').write('goodbye\n')
 
261
    wt.add('b')
 
262
    wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
 
263
              timestamp=1132586842.411175966, timezone=-6*3600,
 
264
              committer='Joe Foo <joe@foo.com>',
 
265
              author='Joe Bar <joe@bar.com>')
 
266
 
 
267
    open('c', 'wb').write('just another manic monday\n')
 
268
    wt.add('c')
 
269
    wt.commit('single line with trailing newline\n', rev_id='a3',
 
270
              timestamp=1132587176.835228920, timezone=-6*3600,
 
271
              committer = 'Joe Foo <joe@foo.com>')
 
272
    return b
 
273
 
 
274
 
 
275
def normalize_log(log):
 
276
    """Replaces the variable lines of logs with fixed lines"""
 
277
    author = 'author: Dolor Sit <test@example.com>'
 
278
    committer = 'committer: Lorem Ipsum <test@example.com>'
 
279
    lines = log.splitlines(True)
 
280
    for idx,line in enumerate(lines):
 
281
        stripped_line = line.lstrip()
 
282
        indent = ' ' * (len(line) - len(stripped_line))
 
283
        if stripped_line.startswith('author:'):
 
284
            lines[idx] = indent + author + '\n'
 
285
        elif stripped_line.startswith('committer:'):
 
286
            lines[idx] = indent + committer + '\n'
 
287
        elif stripped_line.startswith('timestamp:'):
 
288
            lines[idx] = indent + 'timestamp: Just now\n'
 
289
    return ''.join(lines)
 
290
 
 
291
 
 
292
class TestShortLogFormatter(TestCaseWithTransport):
 
293
 
177
294
    def test_trailing_newlines(self):
178
 
        b = Branch.initialize(u'.')
179
 
        b.nick='test'
180
 
        wt = b.working_tree()
181
 
        open('a', 'wb').write('hello moto\n')
182
 
        wt.add('a')
183
 
        wt.commit('simple log message', rev_id='a1'
184
 
                , timestamp=1132586655.459960938, timezone=-6*3600
185
 
                , committer='Joe Foo <joe@foo.com>')
186
 
        open('b', 'wb').write('goodbye\n')
187
 
        wt.add('b')
188
 
        wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
189
 
                , timestamp=1132586842.411175966, timezone=-6*3600
190
 
                , committer='Joe Foo <joe@foo.com>')
191
 
 
192
 
        open('c', 'wb').write('just another manic monday\n')
193
 
        wt.add('c')
194
 
        wt.commit('single line with trailing newline\n', rev_id='a3'
195
 
                , timestamp=1132587176.835228920, timezone=-6*3600
196
 
                , committer = 'Joe Foo <joe@foo.com>')
197
 
 
198
 
        sio = StringIO()
 
295
        wt = self.make_branch_and_tree('.')
 
296
        b = make_commits_with_trailing_newlines(wt)
 
297
        sio = self.make_utf8_encoded_stringio()
199
298
        lf = ShortLogFormatter(to_file=sio)
200
299
        show_log(b, lf)
201
 
        self.assertEquals(sio.getvalue(), """\
 
300
        self.assertEqualDiff(sio.getvalue(), """\
202
301
    3 Joe Foo\t2005-11-21
203
302
      single line with trailing newline
204
303
 
205
 
    2 Joe Foo\t2005-11-21
 
304
    2 Joe Bar\t2005-11-21
206
305
      multiline
207
306
      log
208
307
      message
212
311
 
213
312
""")
214
313
 
215
 
        sio = StringIO()
216
 
        lf = LongLogFormatter(to_file=sio)
217
 
        show_log(b, lf)
218
 
        self.assertEquals(sio.getvalue(), """\
219
 
------------------------------------------------------------
220
 
revno: 3
221
 
committer: Joe Foo <joe@foo.com>
222
 
branch nick: test
223
 
timestamp: Mon 2005-11-21 09:32:56 -0600
224
 
message:
225
 
  single line with trailing newline
226
 
------------------------------------------------------------
227
 
revno: 2
228
 
committer: Joe Foo <joe@foo.com>
229
 
branch nick: test
230
 
timestamp: Mon 2005-11-21 09:27:22 -0600
231
 
message:
232
 
  multiline
233
 
  log
234
 
  message
235
 
------------------------------------------------------------
236
 
revno: 1
237
 
committer: Joe Foo <joe@foo.com>
238
 
branch nick: test
239
 
timestamp: Mon 2005-11-21 09:24:15 -0600
240
 
message:
241
 
  simple log message
242
 
""")
243
 
        
 
314
    def test_short_log_with_merges(self):
 
315
        wt = self.make_branch_and_memory_tree('.')
 
316
        wt.lock_write()
 
317
        try:
 
318
            wt.add('')
 
319
            wt.commit('rev-1', rev_id='rev-1',
 
320
                      timestamp=1132586655, timezone=36000,
 
321
                      committer='Joe Foo <joe@foo.com>')
 
322
            wt.commit('rev-merged', rev_id='rev-2a',
 
323
                      timestamp=1132586700, timezone=36000,
 
324
                      committer='Joe Foo <joe@foo.com>')
 
325
            wt.set_parent_ids(['rev-1', 'rev-2a'])
 
326
            wt.branch.set_last_revision_info(1, 'rev-1')
 
327
            wt.commit('rev-2', rev_id='rev-2b',
 
328
                      timestamp=1132586800, timezone=36000,
 
329
                      committer='Joe Foo <joe@foo.com>')
 
330
            logfile = self.make_utf8_encoded_stringio()
 
331
            formatter = ShortLogFormatter(to_file=logfile)
 
332
            show_log(wt.branch, formatter)
 
333
            self.assertEqualDiff(logfile.getvalue(), """\
 
334
    2 Joe Foo\t2005-11-22 [merge]
 
335
      rev-2
 
336
 
 
337
    1 Joe Foo\t2005-11-22
 
338
      rev-1
 
339
 
 
340
""")
 
341
        finally:
 
342
            wt.unlock()
 
343
 
 
344
    def test_short_log_single_merge_revision(self):
 
345
        wt = self.make_branch_and_memory_tree('.')
 
346
        wt.lock_write()
 
347
        try:
 
348
            wt.add('')
 
349
            wt.commit('rev-1', rev_id='rev-1',
 
350
                      timestamp=1132586655, timezone=36000,
 
351
                      committer='Joe Foo <joe@foo.com>')
 
352
            wt.commit('rev-merged', rev_id='rev-2a',
 
353
                      timestamp=1132586700, timezone=36000,
 
354
                      committer='Joe Foo <joe@foo.com>')
 
355
            wt.set_parent_ids(['rev-1', 'rev-2a'])
 
356
            wt.branch.set_last_revision_info(1, 'rev-1')
 
357
            wt.commit('rev-2', rev_id='rev-2b',
 
358
                      timestamp=1132586800, timezone=36000,
 
359
                      committer='Joe Foo <joe@foo.com>')
 
360
            logfile = self.make_utf8_encoded_stringio()
 
361
            formatter = ShortLogFormatter(to_file=logfile)
 
362
            revspec = RevisionSpec.from_string('1.1.1')
 
363
            wtb = wt.branch
 
364
            rev = revspec.in_history(wtb)
 
365
            show_log(wtb, formatter, start_revision=rev, end_revision=rev)
 
366
            self.assertEqualDiff(logfile.getvalue(), """\
 
367
1.1.1 Joe Foo\t2005-11-22
 
368
      rev-merged
 
369
 
 
370
""")
 
371
        finally:
 
372
            wt.unlock()
 
373
 
 
374
 
 
375
class TestLongLogFormatter(TestCaseWithoutPropsHandler):
 
376
 
244
377
    def test_verbose_log(self):
245
378
        """Verbose log includes changed files
246
379
        
247
380
        bug #4676
248
381
        """
249
 
        b = Branch.initialize(u'.')
 
382
        wt = self.make_branch_and_tree('.')
 
383
        b = wt.branch
250
384
        self.build_tree(['a'])
251
 
        wt = b.working_tree()
252
385
        wt.add('a')
253
386
        # XXX: why does a longer nick show up?
254
387
        b.nick = 'test_verbose_log'
273
406
added:
274
407
  a
275
408
''')
 
409
 
 
410
    def test_merges_are_indented_by_level(self):
 
411
        wt = self.make_branch_and_tree('parent')
 
412
        wt.commit('first post')
 
413
        self.run_bzr('branch parent child')
 
414
        self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
 
415
        self.run_bzr('branch child smallerchild')
 
416
        self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
 
417
            'smallerchild'])
 
418
        os.chdir('child')
 
419
        self.run_bzr('merge ../smallerchild')
 
420
        self.run_bzr(['commit', '-m', 'merge branch 2'])
 
421
        os.chdir('../parent')
 
422
        self.run_bzr('merge ../child')
 
423
        wt.commit('merge branch 1')
 
424
        b = wt.branch
 
425
        sio = self.make_utf8_encoded_stringio()
 
426
        lf = LongLogFormatter(to_file=sio)
 
427
        show_log(b, lf, verbose=True)
 
428
        log = normalize_log(sio.getvalue())
 
429
        self.assertEqualDiff(log, """\
 
430
------------------------------------------------------------
 
431
revno: 2
 
432
committer: Lorem Ipsum <test@example.com>
 
433
branch nick: parent
 
434
timestamp: Just now
 
435
message:
 
436
  merge branch 1
 
437
    ------------------------------------------------------------
 
438
    revno: 1.1.2
 
439
    committer: Lorem Ipsum <test@example.com>
 
440
    branch nick: child
 
441
    timestamp: Just now
 
442
    message:
 
443
      merge branch 2
 
444
        ------------------------------------------------------------
 
445
        revno: 1.2.1
 
446
        committer: Lorem Ipsum <test@example.com>
 
447
        branch nick: smallerchild
 
448
        timestamp: Just now
 
449
        message:
 
450
          branch 2
 
451
    ------------------------------------------------------------
 
452
    revno: 1.1.1
 
453
    committer: Lorem Ipsum <test@example.com>
 
454
    branch nick: child
 
455
    timestamp: Just now
 
456
    message:
 
457
      branch 1
 
458
------------------------------------------------------------
 
459
revno: 1
 
460
committer: Lorem Ipsum <test@example.com>
 
461
branch nick: parent
 
462
timestamp: Just now
 
463
message:
 
464
  first post
 
465
""")
 
466
 
 
467
    def test_verbose_merge_revisions_contain_deltas(self):
 
468
        wt = self.make_branch_and_tree('parent')
 
469
        self.build_tree(['parent/f1', 'parent/f2'])
 
470
        wt.add(['f1','f2'])
 
471
        wt.commit('first post')
 
472
        self.run_bzr('branch parent child')
 
473
        os.unlink('child/f1')
 
474
        file('child/f2', 'wb').write('hello\n')
 
475
        self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
 
476
            'child'])
 
477
        os.chdir('parent')
 
478
        self.run_bzr('merge ../child')
 
479
        wt.commit('merge branch 1')
 
480
        b = wt.branch
 
481
        sio = self.make_utf8_encoded_stringio()
 
482
        lf = LongLogFormatter(to_file=sio)
 
483
        show_log(b, lf, verbose=True)
 
484
        log = normalize_log(sio.getvalue())
 
485
        self.assertEqualDiff(log, """\
 
486
------------------------------------------------------------
 
487
revno: 2
 
488
committer: Lorem Ipsum <test@example.com>
 
489
branch nick: parent
 
490
timestamp: Just now
 
491
message:
 
492
  merge branch 1
 
493
removed:
 
494
  f1
 
495
modified:
 
496
  f2
 
497
    ------------------------------------------------------------
 
498
    revno: 1.1.1
 
499
    committer: Lorem Ipsum <test@example.com>
 
500
    branch nick: child
 
501
    timestamp: Just now
 
502
    message:
 
503
      removed f1 and modified f2
 
504
    removed:
 
505
      f1
 
506
    modified:
 
507
      f2
 
508
------------------------------------------------------------
 
509
revno: 1
 
510
committer: Lorem Ipsum <test@example.com>
 
511
branch nick: parent
 
512
timestamp: Just now
 
513
message:
 
514
  first post
 
515
added:
 
516
  f1
 
517
  f2
 
518
""")
 
519
 
 
520
    def test_trailing_newlines(self):
 
521
        wt = self.make_branch_and_tree('.')
 
522
        b = make_commits_with_trailing_newlines(wt)
 
523
        sio = self.make_utf8_encoded_stringio()
 
524
        lf = LongLogFormatter(to_file=sio)
 
525
        show_log(b, lf)
 
526
        self.assertEqualDiff(sio.getvalue(), """\
 
527
------------------------------------------------------------
 
528
revno: 3
 
529
committer: Joe Foo <joe@foo.com>
 
530
branch nick: test
 
531
timestamp: Mon 2005-11-21 09:32:56 -0600
 
532
message:
 
533
  single line with trailing newline
 
534
------------------------------------------------------------
 
535
revno: 2
 
536
author: Joe Bar <joe@bar.com>
 
537
committer: Joe Foo <joe@foo.com>
 
538
branch nick: test
 
539
timestamp: Mon 2005-11-21 09:27:22 -0600
 
540
message:
 
541
  multiline
 
542
  log
 
543
  message
 
544
------------------------------------------------------------
 
545
revno: 1
 
546
committer: Joe Foo <joe@foo.com>
 
547
branch nick: test
 
548
timestamp: Mon 2005-11-21 09:24:15 -0600
 
549
message:
 
550
  simple log message
 
551
""")
 
552
 
 
553
    def test_author_in_log(self):
 
554
        """Log includes the author name if it's set in
 
555
        the revision properties
 
556
        """
 
557
        wt = self.make_branch_and_tree('.')
 
558
        b = wt.branch
 
559
        self.build_tree(['a'])
 
560
        wt.add('a')
 
561
        b.nick = 'test_author_log'
 
562
        wt.commit(message='add a',
 
563
                  timestamp=1132711707,
 
564
                  timezone=36000,
 
565
                  committer='Lorem Ipsum <test@example.com>',
 
566
                  author='John Doe <jdoe@example.com>')
 
567
        sio = StringIO()
 
568
        formatter = LongLogFormatter(to_file=sio)
 
569
        show_log(b, formatter)
 
570
        self.assertEqualDiff(sio.getvalue(), '''\
 
571
------------------------------------------------------------
 
572
revno: 1
 
573
author: John Doe <jdoe@example.com>
 
574
committer: Lorem Ipsum <test@example.com>
 
575
branch nick: test_author_log
 
576
timestamp: Wed 2005-11-23 12:08:27 +1000
 
577
message:
 
578
  add a
 
579
''')
 
580
 
 
581
    def test_properties_in_log(self):
 
582
        """Log includes the custom properties returned by the registered 
 
583
        handlers.
 
584
        """
 
585
        wt = self.make_branch_and_tree('.')
 
586
        b = wt.branch
 
587
        self.build_tree(['a'])
 
588
        wt.add('a')
 
589
        b.nick = 'test_properties_in_log'
 
590
        wt.commit(message='add a',
 
591
                  timestamp=1132711707,
 
592
                  timezone=36000,
 
593
                  committer='Lorem Ipsum <test@example.com>',
 
594
                  author='John Doe <jdoe@example.com>')
 
595
        sio = StringIO()
 
596
        formatter = LongLogFormatter(to_file=sio)
 
597
        try:
 
598
            def trivial_custom_prop_handler(revision):
 
599
                return {'test_prop':'test_value'}
 
600
            
 
601
            log.properties_handler_registry.register(
 
602
                'trivial_custom_prop_handler', 
 
603
                trivial_custom_prop_handler)
 
604
            show_log(b, formatter)
 
605
        finally:
 
606
            log.properties_handler_registry.remove(
 
607
                'trivial_custom_prop_handler')
 
608
            self.assertEqualDiff(sio.getvalue(), '''\
 
609
------------------------------------------------------------
 
610
revno: 1
 
611
test_prop: test_value
 
612
author: John Doe <jdoe@example.com>
 
613
committer: Lorem Ipsum <test@example.com>
 
614
branch nick: test_properties_in_log
 
615
timestamp: Wed 2005-11-23 12:08:27 +1000
 
616
message:
 
617
  add a
 
618
''')
 
619
 
 
620
    def test_error_in_properties_handler(self):
 
621
        """Log includes the custom properties returned by the registered 
 
622
        handlers.
 
623
        """
 
624
        wt = self.make_branch_and_tree('.')
 
625
        b = wt.branch
 
626
        self.build_tree(['a'])
 
627
        wt.add('a')
 
628
        b.nick = 'test_author_log'
 
629
        wt.commit(message='add a',
 
630
                  timestamp=1132711707,
 
631
                  timezone=36000,
 
632
                  committer='Lorem Ipsum <test@example.com>',
 
633
                  author='John Doe <jdoe@example.com>',
 
634
                  revprops={'first_prop':'first_value'})
 
635
        sio = StringIO()
 
636
        formatter = LongLogFormatter(to_file=sio)
 
637
        try:
 
638
            def trivial_custom_prop_handler(revision):
 
639
                raise StandardError("a test error")
 
640
            
 
641
            log.properties_handler_registry.register(
 
642
                'trivial_custom_prop_handler', 
 
643
                trivial_custom_prop_handler)
 
644
            self.assertRaises(StandardError, show_log, b, formatter,)
 
645
        finally:
 
646
            log.properties_handler_registry.remove(
 
647
                'trivial_custom_prop_handler')
 
648
                
 
649
    def test_properties_handler_bad_argument(self):
 
650
        wt = self.make_branch_and_tree('.')
 
651
        b = wt.branch
 
652
        self.build_tree(['a'])
 
653
        wt.add('a')
 
654
        b.nick = 'test_author_log'
 
655
        wt.commit(message='add a',
 
656
                  timestamp=1132711707,
 
657
                  timezone=36000,
 
658
                  committer='Lorem Ipsum <test@example.com>',
 
659
                  author='John Doe <jdoe@example.com>',
 
660
                  revprops={'a_prop':'test_value'})
 
661
        sio = StringIO()
 
662
        formatter = LongLogFormatter(to_file=sio)
 
663
        try:
 
664
            def bad_argument_prop_handler(revision):
 
665
                return {'custom_prop_name':revision.properties['a_prop']}
 
666
                
 
667
            log.properties_handler_registry.register(
 
668
                'bad_argument_prop_handler', 
 
669
                bad_argument_prop_handler)
 
670
            
 
671
            self.assertRaises(AttributeError, formatter.show_properties, 
 
672
                'a revision', '')
 
673
            
 
674
            revision = b.repository.get_revision(b.last_revision())
 
675
            formatter.show_properties(revision, '')
 
676
            self.assertEqualDiff(sio.getvalue(),
 
677
                '''custom_prop_name: test_value\n''')
 
678
        finally:
 
679
            log.properties_handler_registry.remove(
 
680
                'bad_argument_prop_handler')
 
681
 
 
682
 
 
683
class TestLineLogFormatter(TestCaseWithTransport):
 
684
 
 
685
    def test_line_log(self):
 
686
        """Line log should show revno
 
687
        
 
688
        bug #5162
 
689
        """
 
690
        wt = self.make_branch_and_tree('.')
 
691
        b = wt.branch
 
692
        self.build_tree(['a'])
 
693
        wt.add('a')
 
694
        b.nick = 'test-line-log'
 
695
        wt.commit(message='add a',
 
696
                  timestamp=1132711707,
 
697
                  timezone=36000,
 
698
                  committer='Line-Log-Formatter Tester <test@line.log>')
 
699
        logfile = file('out.tmp', 'w+')
 
700
        formatter = LineLogFormatter(to_file=logfile)
 
701
        show_log(b, formatter)
 
702
        logfile.flush()
 
703
        logfile.seek(0)
 
704
        log_contents = logfile.read()
 
705
        self.assertEqualDiff(log_contents,
 
706
            '1: Line-Log-Formatte... 2005-11-23 add a\n')
 
707
 
 
708
    def test_trailing_newlines(self):
 
709
        wt = self.make_branch_and_tree('.')
 
710
        b = make_commits_with_trailing_newlines(wt)
 
711
        sio = self.make_utf8_encoded_stringio()
 
712
        lf = LineLogFormatter(to_file=sio)
 
713
        show_log(b, lf)
 
714
        self.assertEqualDiff(sio.getvalue(), """\
 
715
3: Joe Foo 2005-11-21 single line with trailing newline
 
716
2: Joe Bar 2005-11-21 multiline
 
717
1: Joe Foo 2005-11-21 simple log message
 
718
""")
 
719
 
 
720
    def test_line_log_single_merge_revision(self):
 
721
        wt = self.make_branch_and_memory_tree('.')
 
722
        wt.lock_write()
 
723
        try:
 
724
            wt.add('')
 
725
            wt.commit('rev-1', rev_id='rev-1',
 
726
                      timestamp=1132586655, timezone=36000,
 
727
                      committer='Joe Foo <joe@foo.com>')
 
728
            wt.commit('rev-merged', rev_id='rev-2a',
 
729
                      timestamp=1132586700, timezone=36000,
 
730
                      committer='Joe Foo <joe@foo.com>')
 
731
            wt.set_parent_ids(['rev-1', 'rev-2a'])
 
732
            wt.branch.set_last_revision_info(1, 'rev-1')
 
733
            wt.commit('rev-2', rev_id='rev-2b',
 
734
                      timestamp=1132586800, timezone=36000,
 
735
                      committer='Joe Foo <joe@foo.com>')
 
736
            logfile = self.make_utf8_encoded_stringio()
 
737
            formatter = LineLogFormatter(to_file=logfile)
 
738
            revspec = RevisionSpec.from_string('1.1.1')
 
739
            wtb = wt.branch
 
740
            rev = revspec.in_history(wtb)
 
741
            show_log(wtb, formatter, start_revision=rev, end_revision=rev)
 
742
            self.assertEqualDiff(logfile.getvalue(), """\
 
743
1.1.1: Joe Foo 2005-11-22 rev-merged
 
744
""")
 
745
        finally:
 
746
            wt.unlock()
 
747
 
 
748
 
 
749
 
 
750
class TestGetViewRevisions(TestCaseWithTransport):
 
751
 
 
752
    def make_tree_with_commits(self):
 
753
        """Create a tree with well-known revision ids"""
 
754
        wt = self.make_branch_and_tree('tree1')
 
755
        wt.commit('commit one', rev_id='1')
 
756
        wt.commit('commit two', rev_id='2')
 
757
        wt.commit('commit three', rev_id='3')
 
758
        mainline_revs = [None, '1', '2', '3']
 
759
        rev_nos = {'1': 1, '2': 2, '3': 3}
 
760
        return mainline_revs, rev_nos, wt
 
761
 
 
762
    def make_tree_with_merges(self):
 
763
        """Create a tree with well-known revision ids and a merge"""
 
764
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
765
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
766
        tree2.commit('four-a', rev_id='4a')
 
767
        wt.merge_from_branch(tree2.branch)
 
768
        wt.commit('four-b', rev_id='4b')
 
769
        mainline_revs.append('4b')
 
770
        rev_nos['4b'] = 4
 
771
        # 4a: 3.1.1
 
772
        return mainline_revs, rev_nos, wt
 
773
 
 
774
    def make_tree_with_many_merges(self):
 
775
        """Create a tree with well-known revision ids"""
 
776
        wt = self.make_branch_and_tree('tree1')
 
777
        wt.commit('commit one', rev_id='1')
 
778
        wt.commit('commit two', rev_id='2')
 
779
        tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
 
780
        tree3.commit('commit three a', rev_id='3a')
 
781
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
782
        tree2.merge_from_branch(tree3.branch)
 
783
        tree2.commit('commit three b', rev_id='3b')
 
784
        wt.merge_from_branch(tree2.branch)
 
785
        wt.commit('commit three c', rev_id='3c')
 
786
        tree2.commit('four-a', rev_id='4a')
 
787
        wt.merge_from_branch(tree2.branch)
 
788
        wt.commit('four-b', rev_id='4b')
 
789
        mainline_revs = [None, '1', '2', '3c', '4b']
 
790
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
 
791
        full_rev_nos_for_reference = {
 
792
            '1': '1',
 
793
            '2': '2',
 
794
            '3a': '2.1.1', #first commit tree 3
 
795
            '3b': '2.2.1', # first commit tree 2
 
796
            '3c': '3', #merges 3b to main
 
797
            '4a': '2.2.2', # second commit tree 2
 
798
            '4b': '4', # merges 4a to main
 
799
            }
 
800
        return mainline_revs, rev_nos, wt
 
801
 
 
802
    def test_get_view_revisions_forward(self):
 
803
        """Test the get_view_revisions method"""
 
804
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
805
        wt.lock_read()
 
806
        self.addCleanup(wt.unlock)
 
807
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
808
                                            'forward'))
 
809
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
 
810
            revisions)
 
811
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
812
                                             'forward', include_merges=False))
 
813
        self.assertEqual(revisions, revisions2)
 
814
 
 
815
    def test_get_view_revisions_reverse(self):
 
816
        """Test the get_view_revisions with reverse"""
 
817
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
818
        wt.lock_read()
 
819
        self.addCleanup(wt.unlock)
 
820
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
821
                                            'reverse'))
 
822
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
 
823
            revisions)
 
824
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
825
                                             'reverse', include_merges=False))
 
826
        self.assertEqual(revisions, revisions2)
 
827
 
 
828
    def test_get_view_revisions_merge(self):
 
829
        """Test get_view_revisions when there are merges"""
 
830
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
831
        wt.lock_read()
 
832
        self.addCleanup(wt.unlock)
 
833
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
834
                                            'forward'))
 
835
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
836
            ('4b', '4', 0), ('4a', '3.1.1', 1)],
 
837
            revisions)
 
838
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
839
                                             'forward', include_merges=False))
 
840
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
841
            ('4b', '4', 0)],
 
842
            revisions)
 
843
 
 
844
    def test_get_view_revisions_merge_reverse(self):
 
845
        """Test get_view_revisions in reverse when there are merges"""
 
846
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
847
        wt.lock_read()
 
848
        self.addCleanup(wt.unlock)
 
849
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
850
                                            'reverse'))
 
851
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
 
852
            ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
 
853
            revisions)
 
854
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
855
                                             'reverse', include_merges=False))
 
856
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
 
857
            ('1', '1', 0)],
 
858
            revisions)
 
859
 
 
860
    def test_get_view_revisions_merge2(self):
 
861
        """Test get_view_revisions when there are merges"""
 
862
        mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
 
863
        wt.lock_read()
 
864
        self.addCleanup(wt.unlock)
 
865
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
866
                                            'forward'))
 
867
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
868
            ('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
 
869
            ('4a', '2.2.2', 1)]
 
870
        self.assertEqual(expected, revisions)
 
871
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
872
                                             'forward', include_merges=False))
 
873
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
874
            ('4b', '4', 0)],
 
875
            revisions)
 
876
 
 
877
 
 
878
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
 
879
 
 
880
    def create_tree_with_single_merge(self):
 
881
        """Create a branch with a moderate layout.
 
882
 
 
883
        The revision graph looks like:
 
884
 
 
885
           A
 
886
           |\
 
887
           B C
 
888
           |/
 
889
           D
 
890
 
 
891
        In this graph, A introduced files f1 and f2 and f3.
 
892
        B modifies f1 and f3, and C modifies f2 and f3.
 
893
        D merges the changes from B and C and resolves the conflict for f3.
 
894
        """
 
895
        # TODO: jam 20070218 This seems like it could really be done
 
896
        #       with make_branch_and_memory_tree() if we could just
 
897
        #       create the content of those files.
 
898
        # TODO: jam 20070218 Another alternative is that we would really
 
899
        #       like to only create this tree 1 time for all tests that
 
900
        #       use it. Since 'log' only uses the tree in a readonly
 
901
        #       fashion, it seems a shame to regenerate an identical
 
902
        #       tree for each test.
 
903
        tree = self.make_branch_and_tree('tree')
 
904
        tree.lock_write()
 
905
        self.addCleanup(tree.unlock)
 
906
 
 
907
        self.build_tree_contents([('tree/f1', 'A\n'),
 
908
                                  ('tree/f2', 'A\n'),
 
909
                                  ('tree/f3', 'A\n'),
 
910
                                 ])
 
911
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
 
912
        tree.commit('A', rev_id='A')
 
913
 
 
914
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
 
915
                                  ('tree/f3', 'A\nC\n'),
 
916
                                 ])
 
917
        tree.commit('C', rev_id='C')
 
918
        # Revert back to A to build the other history.
 
919
        tree.set_last_revision('A')
 
920
        tree.branch.set_last_revision_info(1, 'A')
 
921
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
922
                                  ('tree/f2', 'A\n'),
 
923
                                  ('tree/f3', 'A\nB\n'),
 
924
                                 ])
 
925
        tree.commit('B', rev_id='B')
 
926
        tree.set_parent_ids(['B', 'C'])
 
927
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
928
                                  ('tree/f2', 'A\nC\n'),
 
929
                                  ('tree/f3', 'A\nB\nC\n'),
 
930
                                 ])
 
931
        tree.commit('D', rev_id='D')
 
932
 
 
933
        # Switch to a read lock for this tree.
 
934
        # We still have addCleanup(unlock)
 
935
        tree.unlock()
 
936
        tree.lock_read()
 
937
        return tree
 
938
 
 
939
    def test_tree_with_single_merge(self):
 
940
        """Make sure the tree layout is correct."""
 
941
        tree = self.create_tree_with_single_merge()
 
942
        rev_A_tree = tree.branch.repository.revision_tree('A')
 
943
        rev_B_tree = tree.branch.repository.revision_tree('B')
 
944
 
 
945
        f1_changed = (u'f1', 'f1-id', 'file', True, False)
 
946
        f2_changed = (u'f2', 'f2-id', 'file', True, False)
 
947
        f3_changed = (u'f3', 'f3-id', 'file', True, False)
 
948
 
 
949
        delta = rev_B_tree.changes_from(rev_A_tree)
 
950
        self.assertEqual([f1_changed, f3_changed], delta.modified)
 
951
        self.assertEqual([], delta.renamed)
 
952
        self.assertEqual([], delta.added)
 
953
        self.assertEqual([], delta.removed)
 
954
 
 
955
        rev_C_tree = tree.branch.repository.revision_tree('C')
 
956
        delta = rev_C_tree.changes_from(rev_A_tree)
 
957
        self.assertEqual([f2_changed, f3_changed], delta.modified)
 
958
        self.assertEqual([], delta.renamed)
 
959
        self.assertEqual([], delta.added)
 
960
        self.assertEqual([], delta.removed)
 
961
 
 
962
        rev_D_tree = tree.branch.repository.revision_tree('D')
 
963
        delta = rev_D_tree.changes_from(rev_B_tree)
 
964
        self.assertEqual([f2_changed, f3_changed], delta.modified)
 
965
        self.assertEqual([], delta.renamed)
 
966
        self.assertEqual([], delta.added)
 
967
        self.assertEqual([], delta.removed)
 
968
 
 
969
        delta = rev_D_tree.changes_from(rev_C_tree)
 
970
        self.assertEqual([f1_changed, f3_changed], delta.modified)
 
971
        self.assertEqual([], delta.renamed)
 
972
        self.assertEqual([], delta.added)
 
973
        self.assertEqual([], delta.removed)
 
974
 
 
975
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
 
976
        """Make sure _filter_revisions_touching_file_id returns the right values.
 
977
 
 
978
        Get the return value from _filter_revisions_touching_file_id and make
 
979
        sure they are correct.
 
980
        """
 
981
        # The api for _get_revisions_touching_file_id is a little crazy,
 
982
        # So we do the setup here.
 
983
        mainline = tree.branch.revision_history()
 
984
        mainline.insert(0, None)
 
985
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
 
986
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
 
987
                                                'reverse', True)
 
988
        actual_revs = log._filter_revisions_touching_file_id(
 
989
                            tree.branch, 
 
990
                            file_id,
 
991
                            mainline,
 
992
                            list(view_revs_iter))
 
993
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
 
994
 
 
995
    def test_file_id_f1(self):
 
996
        tree = self.create_tree_with_single_merge()
 
997
        # f1 should be marked as modified by revisions A and B
 
998
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
 
999
 
 
1000
    def test_file_id_f2(self):
 
1001
        tree = self.create_tree_with_single_merge()
 
1002
        # f2 should be marked as modified by revisions A, C, and D
 
1003
        # because D merged the changes from C.
 
1004
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
1005
 
 
1006
    def test_file_id_f3(self):
 
1007
        tree = self.create_tree_with_single_merge()
 
1008
        # f3 should be marked as modified by revisions A, B, C, and D
 
1009
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
1010
 
 
1011
    def test_file_id_with_ghosts(self):
 
1012
        # This is testing bug #209948, where having a ghost would cause
 
1013
        # _filter_revisions_touching_file_id() to fail.
 
1014
        tree = self.create_tree_with_single_merge()
 
1015
        # We need to add a revision, so switch back to a write-locked tree
 
1016
        tree.unlock()
 
1017
        tree.lock_write()
 
1018
        first_parent = tree.last_revision()
 
1019
        tree.set_parent_ids([first_parent, 'ghost-revision-id'])
 
1020
        self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
 
1021
        tree.commit('commit with a ghost', rev_id='XX')
 
1022
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
 
1023
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
1024
 
 
1025
 
 
1026
class TestShowChangedRevisions(TestCaseWithTransport):
 
1027
 
 
1028
    def test_show_changed_revisions_verbose(self):
 
1029
        tree = self.make_branch_and_tree('tree_a')
 
1030
        self.build_tree(['tree_a/foo'])
 
1031
        tree.add('foo')
 
1032
        tree.commit('bar', rev_id='bar-id')
 
1033
        s = self.make_utf8_encoded_stringio()
 
1034
        log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
 
1035
        self.assertContainsRe(s.getvalue(), 'bar')
 
1036
        self.assertNotContainsRe(s.getvalue(), 'foo')
 
1037
 
 
1038
 
 
1039
class TestLogFormatter(TestCase):
 
1040
 
 
1041
    def test_short_committer(self):
 
1042
        rev = Revision('a-id')
 
1043
        rev.committer = 'John Doe <jdoe@example.com>'
 
1044
        lf = LogFormatter(None)
 
1045
        self.assertEqual('John Doe', lf.short_committer(rev))
 
1046
        rev.committer = 'John Smith <jsmith@example.com>'
 
1047
        self.assertEqual('John Smith', lf.short_committer(rev))
 
1048
        rev.committer = 'John Smith'
 
1049
        self.assertEqual('John Smith', lf.short_committer(rev))
 
1050
        rev.committer = 'jsmith@example.com'
 
1051
        self.assertEqual('jsmith@example.com', lf.short_committer(rev))
 
1052
        rev.committer = '<jsmith@example.com>'
 
1053
        self.assertEqual('jsmith@example.com', lf.short_committer(rev))
 
1054
        rev.committer = 'John Smith jsmith@example.com'
 
1055
        self.assertEqual('John Smith', lf.short_committer(rev))
 
1056
 
 
1057
    def test_short_author(self):
 
1058
        rev = Revision('a-id')
 
1059
        rev.committer = 'John Doe <jdoe@example.com>'
 
1060
        lf = LogFormatter(None)
 
1061
        self.assertEqual('John Doe', lf.short_author(rev))
 
1062
        rev.properties['author'] = 'John Smith <jsmith@example.com>'
 
1063
        self.assertEqual('John Smith', lf.short_author(rev))
 
1064
        rev.properties['author'] = 'John Smith'
 
1065
        self.assertEqual('John Smith', lf.short_author(rev))
 
1066
        rev.properties['author'] = 'jsmith@example.com'
 
1067
        self.assertEqual('jsmith@example.com', lf.short_author(rev))
 
1068
        rev.properties['author'] = '<jsmith@example.com>'
 
1069
        self.assertEqual('jsmith@example.com', lf.short_author(rev))
 
1070
        rev.properties['author'] = 'John Smith jsmith@example.com'
 
1071
        self.assertEqual('John Smith', lf.short_author(rev))