~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: 2007-11-03 23:02:16 UTC
  • mfrom: (2951.1.1 pack)
  • Revision ID: pqm@pqm.ubuntu.com-20071103230216-mnmwuxm413lyhjdv
(robertc) Fix data-refresh logic for packs not to refresh mid-transaction when a names write lock is held. (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import os
 
18
from cStringIO import StringIO
 
19
 
 
20
from bzrlib import log
 
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)
 
29
from bzrlib.branch import Branch
 
30
from bzrlib.errors import InvalidRevisionNumber
 
31
from bzrlib.revision import Revision
 
32
 
 
33
 
 
34
class LogCatcher(LogFormatter):
 
35
    """Pull log messages into list rather than displaying them.
 
36
 
 
37
    For ease of testing we save log messages here rather than actually
 
38
    formatting them, so that we can precisely check the result without
 
39
    being too dependent on the exact formatting.
 
40
 
 
41
    We should also test the LogFormatter.
 
42
    """
 
43
 
 
44
    supports_delta = True
 
45
 
 
46
    def __init__(self):
 
47
        super(LogCatcher, self).__init__(to_file=None)
 
48
        self.logs = []
 
49
 
 
50
    def log_revision(self, revision):
 
51
        self.logs.append(revision)
 
52
 
 
53
 
 
54
class TestShowLog(TestCaseWithTransport):
 
55
 
 
56
    def checkDelta(self, delta, **kw):
 
57
        """Check the filenames touched by a delta are as expected."""
 
58
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
 
59
            expected = kw.get(n, [])
 
60
            # strip out only the path components
 
61
            got = [x[0] for x in getattr(delta, n)]
 
62
            self.assertEquals(expected, got)
 
63
 
 
64
    def test_cur_revno(self):
 
65
        wt = self.make_branch_and_tree('.')
 
66
        b = wt.branch
 
67
 
 
68
        lf = LogCatcher()
 
69
        wt.commit('empty commit')
 
70
        show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
 
71
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
72
                          start_revision=2, end_revision=1) 
 
73
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
74
                          start_revision=1, end_revision=2) 
 
75
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
76
                          start_revision=0, end_revision=2) 
 
77
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
78
                          start_revision=1, end_revision=0) 
 
79
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
80
                          start_revision=-1, end_revision=1) 
 
81
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
82
                          start_revision=1, end_revision=-1) 
 
83
 
 
84
    def test_simple_log(self):
 
85
        eq = self.assertEquals
 
86
        
 
87
        wt = self.make_branch_and_tree('.')
 
88
        b = wt.branch
 
89
 
 
90
        lf = LogCatcher()
 
91
        show_log(b, lf)
 
92
        # no entries yet
 
93
        eq(lf.logs, [])
 
94
 
 
95
        wt.commit('empty commit')
 
96
        lf = LogCatcher()
 
97
        show_log(b, lf, verbose=True)
 
98
        eq(len(lf.logs), 1)
 
99
        eq(lf.logs[0].revno, '1')
 
100
        eq(lf.logs[0].rev.message, 'empty commit')
 
101
        d = lf.logs[0].delta
 
102
        self.log('log delta: %r' % d)
 
103
        self.checkDelta(d)
 
104
 
 
105
        self.build_tree(['hello'])
 
106
        wt.add('hello')
 
107
        wt.commit('add one file',
 
108
                  committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
 
109
                            u'<test@example.com>')
 
110
 
 
111
        lf = self.make_utf8_encoded_stringio()
 
112
        # log using regular thing
 
113
        show_log(b, LongLogFormatter(lf))
 
114
        lf.seek(0)
 
115
        for l in lf.readlines():
 
116
            self.log(l)
 
117
 
 
118
        # get log as data structure
 
119
        lf = LogCatcher()
 
120
        show_log(b, lf, verbose=True)
 
121
        eq(len(lf.logs), 2)
 
122
        self.log('log entries:')
 
123
        for logentry in lf.logs:
 
124
            self.log('%4s %s' % (logentry.revno, logentry.rev.message))
 
125
        
 
126
        # first one is most recent
 
127
        logentry = lf.logs[0]
 
128
        eq(logentry.revno, '2')
 
129
        eq(logentry.rev.message, 'add one file')
 
130
        d = logentry.delta
 
131
        self.log('log 2 delta: %r' % d)
 
132
        self.checkDelta(d, added=['hello'])
 
133
        
 
134
        # commit a log message with control characters
 
135
        msg = "All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
 
136
        self.log("original commit message: %r", msg)
 
137
        wt.commit(msg)
 
138
        lf = LogCatcher()
 
139
        show_log(b, lf, verbose=True)
 
140
        committed_msg = lf.logs[0].rev.message
 
141
        self.log("escaped commit message: %r", committed_msg)
 
142
        self.assert_(msg != committed_msg)
 
143
        self.assert_(len(committed_msg) > len(msg))
 
144
 
 
145
        # Check that log message with only XML-valid characters isn't
 
146
        # escaped.  As ElementTree apparently does some kind of
 
147
        # newline conversion, neither LF (\x0A) nor CR (\x0D) are
 
148
        # included in the test commit message, even though they are
 
149
        # valid XML 1.0 characters.
 
150
        msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
 
151
        self.log("original commit message: %r", msg)
 
152
        wt.commit(msg)
 
153
        lf = LogCatcher()
 
154
        show_log(b, lf, verbose=True)
 
155
        committed_msg = lf.logs[0].rev.message
 
156
        self.log("escaped commit message: %r", committed_msg)
 
157
        self.assert_(msg == committed_msg)
 
158
 
 
159
    def test_deltas_in_merge_revisions(self):
 
160
        """Check deltas created for both mainline and merge revisions"""
 
161
        eq = self.assertEquals
 
162
        wt = self.make_branch_and_tree('parent')
 
163
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
 
164
        wt.add('file1')
 
165
        wt.add('file2')
 
166
        wt.commit(message='add file1 and file2')
 
167
        self.run_bzr('branch parent child')
 
168
        os.unlink('child/file1')
 
169
        file('child/file2', 'wb').write('hello\n')
 
170
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
 
171
            'child'])
 
172
        os.chdir('parent')
 
173
        self.run_bzr('merge ../child')
 
174
        wt.commit('merge child branch')
 
175
        os.chdir('..')
 
176
        b = wt.branch
 
177
        lf = LogCatcher()
 
178
        lf.supports_merge_revisions = True
 
179
        show_log(b, lf, verbose=True)
 
180
        eq(len(lf.logs),3)
 
181
        logentry = lf.logs[0]
 
182
        eq(logentry.revno, '2')
 
183
        eq(logentry.rev.message, 'merge child branch')
 
184
        d = logentry.delta
 
185
        self.checkDelta(d, removed=['file1'], modified=['file2'])
 
186
        logentry = lf.logs[1]
 
187
        eq(logentry.revno, '1.1.1')
 
188
        eq(logentry.rev.message, 'remove file1 and modify file2')
 
189
        d = logentry.delta
 
190
        self.checkDelta(d, removed=['file1'], modified=['file2'])
 
191
        logentry = lf.logs[2]
 
192
        eq(logentry.revno, '1')
 
193
        eq(logentry.rev.message, 'add file1 and file2')
 
194
        d = logentry.delta
 
195
        self.checkDelta(d, added=['file1', 'file2'])
 
196
 
 
197
 
 
198
def make_commits_with_trailing_newlines(wt):
 
199
    """Helper method for LogFormatter tests"""    
 
200
    b = wt.branch
 
201
    b.nick='test'
 
202
    open('a', 'wb').write('hello moto\n')
 
203
    wt.add('a')
 
204
    wt.commit('simple log message', rev_id='a1',
 
205
              timestamp=1132586655.459960938, timezone=-6*3600,
 
206
              committer='Joe Foo <joe@foo.com>')
 
207
    open('b', 'wb').write('goodbye\n')
 
208
    wt.add('b')
 
209
    wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
 
210
              timestamp=1132586842.411175966, timezone=-6*3600,
 
211
              committer='Joe Foo <joe@foo.com>',
 
212
              author='Joe Bar <joe@bar.com>')
 
213
 
 
214
    open('c', 'wb').write('just another manic monday\n')
 
215
    wt.add('c')
 
216
    wt.commit('single line with trailing newline\n', rev_id='a3',
 
217
              timestamp=1132587176.835228920, timezone=-6*3600,
 
218
              committer = 'Joe Foo <joe@foo.com>')
 
219
    return b
 
220
 
 
221
 
 
222
class TestShortLogFormatter(TestCaseWithTransport):
 
223
 
 
224
    def test_trailing_newlines(self):
 
225
        wt = self.make_branch_and_tree('.')
 
226
        b = make_commits_with_trailing_newlines(wt)
 
227
        sio = self.make_utf8_encoded_stringio()
 
228
        lf = ShortLogFormatter(to_file=sio)
 
229
        show_log(b, lf)
 
230
        self.assertEquals(sio.getvalue(), """\
 
231
    3 Joe Foo\t2005-11-21
 
232
      single line with trailing newline
 
233
 
 
234
    2 Joe Bar\t2005-11-21
 
235
      multiline
 
236
      log
 
237
      message
 
238
 
 
239
    1 Joe Foo\t2005-11-21
 
240
      simple log message
 
241
 
 
242
""")
 
243
 
 
244
 
 
245
class TestLongLogFormatter(TestCaseWithTransport):
 
246
 
 
247
    def normalize_log(self,log):
 
248
        """Replaces the variable lines of logs with fixed lines"""
 
249
        author = 'author: Dolor Sit <test@example.com>'
 
250
        committer = 'committer: Lorem Ipsum <test@example.com>'
 
251
        lines = log.splitlines(True)
 
252
        for idx,line in enumerate(lines):
 
253
            stripped_line = line.lstrip()
 
254
            indent = ' ' * (len(line) - len(stripped_line))
 
255
            if stripped_line.startswith('author:'):
 
256
                lines[idx] = indent + author + '\n'
 
257
            elif stripped_line.startswith('committer:'):
 
258
                lines[idx] = indent + committer + '\n'
 
259
            elif stripped_line.startswith('timestamp:'):
 
260
                lines[idx] = indent + 'timestamp: Just now\n'
 
261
        return ''.join(lines)
 
262
 
 
263
    def test_verbose_log(self):
 
264
        """Verbose log includes changed files
 
265
        
 
266
        bug #4676
 
267
        """
 
268
        wt = self.make_branch_and_tree('.')
 
269
        b = wt.branch
 
270
        self.build_tree(['a'])
 
271
        wt.add('a')
 
272
        # XXX: why does a longer nick show up?
 
273
        b.nick = 'test_verbose_log'
 
274
        wt.commit(message='add a', 
 
275
                  timestamp=1132711707, 
 
276
                  timezone=36000,
 
277
                  committer='Lorem Ipsum <test@example.com>')
 
278
        logfile = file('out.tmp', 'w+')
 
279
        formatter = LongLogFormatter(to_file=logfile)
 
280
        show_log(b, formatter, verbose=True)
 
281
        logfile.flush()
 
282
        logfile.seek(0)
 
283
        log_contents = logfile.read()
 
284
        self.assertEqualDiff(log_contents, '''\
 
285
------------------------------------------------------------
 
286
revno: 1
 
287
committer: Lorem Ipsum <test@example.com>
 
288
branch nick: test_verbose_log
 
289
timestamp: Wed 2005-11-23 12:08:27 +1000
 
290
message:
 
291
  add a
 
292
added:
 
293
  a
 
294
''')
 
295
 
 
296
    def test_merges_are_indented_by_level(self):
 
297
        wt = self.make_branch_and_tree('parent')
 
298
        wt.commit('first post')
 
299
        self.run_bzr('branch parent child')
 
300
        self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
 
301
        self.run_bzr('branch child smallerchild')
 
302
        self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
 
303
            'smallerchild'])
 
304
        os.chdir('child')
 
305
        self.run_bzr('merge ../smallerchild')
 
306
        self.run_bzr(['commit', '-m', 'merge branch 2'])
 
307
        os.chdir('../parent')
 
308
        self.run_bzr('merge ../child')
 
309
        wt.commit('merge branch 1')
 
310
        b = wt.branch
 
311
        sio = self.make_utf8_encoded_stringio()
 
312
        lf = LongLogFormatter(to_file=sio)
 
313
        show_log(b, lf, verbose=True)
 
314
        log = self.normalize_log(sio.getvalue())
 
315
        self.assertEqualDiff("""\
 
316
------------------------------------------------------------
 
317
revno: 2
 
318
committer: Lorem Ipsum <test@example.com>
 
319
branch nick: parent
 
320
timestamp: Just now
 
321
message:
 
322
  merge branch 1
 
323
    ------------------------------------------------------------
 
324
    revno: 1.1.2
 
325
    committer: Lorem Ipsum <test@example.com>
 
326
    branch nick: child
 
327
    timestamp: Just now
 
328
    message:
 
329
      merge branch 2
 
330
        ------------------------------------------------------------
 
331
        revno: 1.1.1.1.1
 
332
        committer: Lorem Ipsum <test@example.com>
 
333
        branch nick: smallerchild
 
334
        timestamp: Just now
 
335
        message:
 
336
          branch 2
 
337
    ------------------------------------------------------------
 
338
    revno: 1.1.1
 
339
    committer: Lorem Ipsum <test@example.com>
 
340
    branch nick: child
 
341
    timestamp: Just now
 
342
    message:
 
343
      branch 1
 
344
------------------------------------------------------------
 
345
revno: 1
 
346
committer: Lorem Ipsum <test@example.com>
 
347
branch nick: parent
 
348
timestamp: Just now
 
349
message:
 
350
  first post
 
351
""", log)
 
352
 
 
353
    def test_verbose_merge_revisions_contain_deltas(self):
 
354
        wt = self.make_branch_and_tree('parent')
 
355
        self.build_tree(['parent/f1', 'parent/f2'])
 
356
        wt.add(['f1','f2'])
 
357
        wt.commit('first post')
 
358
        self.run_bzr('branch parent child')
 
359
        os.unlink('child/f1')
 
360
        file('child/f2', 'wb').write('hello\n')
 
361
        self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
 
362
            'child'])
 
363
        os.chdir('parent')
 
364
        self.run_bzr('merge ../child')
 
365
        wt.commit('merge branch 1')
 
366
        b = wt.branch
 
367
        sio = self.make_utf8_encoded_stringio()
 
368
        lf = LongLogFormatter(to_file=sio)
 
369
        show_log(b, lf, verbose=True)
 
370
        log = self.normalize_log(sio.getvalue())
 
371
        self.assertEqualDiff("""\
 
372
------------------------------------------------------------
 
373
revno: 2
 
374
committer: Lorem Ipsum <test@example.com>
 
375
branch nick: parent
 
376
timestamp: Just now
 
377
message:
 
378
  merge branch 1
 
379
removed:
 
380
  f1
 
381
modified:
 
382
  f2
 
383
    ------------------------------------------------------------
 
384
    revno: 1.1.1
 
385
    committer: Lorem Ipsum <test@example.com>
 
386
    branch nick: child
 
387
    timestamp: Just now
 
388
    message:
 
389
      removed f1 and modified f2
 
390
    removed:
 
391
      f1
 
392
    modified:
 
393
      f2
 
394
------------------------------------------------------------
 
395
revno: 1
 
396
committer: Lorem Ipsum <test@example.com>
 
397
branch nick: parent
 
398
timestamp: Just now
 
399
message:
 
400
  first post
 
401
added:
 
402
  f1
 
403
  f2
 
404
""", log)
 
405
 
 
406
    def test_trailing_newlines(self):
 
407
        wt = self.make_branch_and_tree('.')
 
408
        b = make_commits_with_trailing_newlines(wt)
 
409
        sio = self.make_utf8_encoded_stringio()
 
410
        lf = LongLogFormatter(to_file=sio)
 
411
        show_log(b, lf)
 
412
        self.assertEqualDiff(sio.getvalue(), """\
 
413
------------------------------------------------------------
 
414
revno: 3
 
415
committer: Joe Foo <joe@foo.com>
 
416
branch nick: test
 
417
timestamp: Mon 2005-11-21 09:32:56 -0600
 
418
message:
 
419
  single line with trailing newline
 
420
------------------------------------------------------------
 
421
revno: 2
 
422
author: Joe Bar <joe@bar.com>
 
423
committer: Joe Foo <joe@foo.com>
 
424
branch nick: test
 
425
timestamp: Mon 2005-11-21 09:27:22 -0600
 
426
message:
 
427
  multiline
 
428
  log
 
429
  message
 
430
------------------------------------------------------------
 
431
revno: 1
 
432
committer: Joe Foo <joe@foo.com>
 
433
branch nick: test
 
434
timestamp: Mon 2005-11-21 09:24:15 -0600
 
435
message:
 
436
  simple log message
 
437
""")
 
438
 
 
439
    def test_author_in_log(self):
 
440
        """Log includes the author name if it's set in
 
441
        the revision properties
 
442
        """
 
443
        wt = self.make_branch_and_tree('.')
 
444
        b = wt.branch
 
445
        self.build_tree(['a'])
 
446
        wt.add('a')
 
447
        b.nick = 'test_author_log'
 
448
        wt.commit(message='add a',
 
449
                  timestamp=1132711707,
 
450
                  timezone=36000,
 
451
                  committer='Lorem Ipsum <test@example.com>',
 
452
                  author='John Doe <jdoe@example.com>')
 
453
        sio = StringIO()
 
454
        formatter = LongLogFormatter(to_file=sio)
 
455
        show_log(b, formatter)
 
456
        self.assertEqualDiff(sio.getvalue(), '''\
 
457
------------------------------------------------------------
 
458
revno: 1
 
459
author: John Doe <jdoe@example.com>
 
460
committer: Lorem Ipsum <test@example.com>
 
461
branch nick: test_author_log
 
462
timestamp: Wed 2005-11-23 12:08:27 +1000
 
463
message:
 
464
  add a
 
465
''')
 
466
 
 
467
 
 
468
 
 
469
class TestLineLogFormatter(TestCaseWithTransport):
 
470
 
 
471
    def test_line_log(self):
 
472
        """Line log should show revno
 
473
        
 
474
        bug #5162
 
475
        """
 
476
        wt = self.make_branch_and_tree('.')
 
477
        b = wt.branch
 
478
        self.build_tree(['a'])
 
479
        wt.add('a')
 
480
        b.nick = 'test-line-log'
 
481
        wt.commit(message='add a', 
 
482
                  timestamp=1132711707, 
 
483
                  timezone=36000,
 
484
                  committer='Line-Log-Formatter Tester <test@line.log>')
 
485
        logfile = file('out.tmp', 'w+')
 
486
        formatter = LineLogFormatter(to_file=logfile)
 
487
        show_log(b, formatter)
 
488
        logfile.flush()
 
489
        logfile.seek(0)
 
490
        log_contents = logfile.read()
 
491
        self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
 
492
 
 
493
    def test_short_log_with_merges(self):
 
494
        wt = self.make_branch_and_memory_tree('.')
 
495
        wt.lock_write()
 
496
        try:
 
497
            wt.add('')
 
498
            wt.commit('rev-1', rev_id='rev-1',
 
499
                      timestamp=1132586655, timezone=36000,
 
500
                      committer='Joe Foo <joe@foo.com>')
 
501
            wt.commit('rev-merged', rev_id='rev-2a',
 
502
                      timestamp=1132586700, timezone=36000,
 
503
                      committer='Joe Foo <joe@foo.com>')
 
504
            wt.set_parent_ids(['rev-1', 'rev-2a'])
 
505
            wt.branch.set_last_revision_info(1, 'rev-1')
 
506
            wt.commit('rev-2', rev_id='rev-2b',
 
507
                      timestamp=1132586800, timezone=36000,
 
508
                      committer='Joe Foo <joe@foo.com>')
 
509
            logfile = self.make_utf8_encoded_stringio()
 
510
            formatter = ShortLogFormatter(to_file=logfile)
 
511
            show_log(wt.branch, formatter)
 
512
            logfile.flush()
 
513
            self.assertEqualDiff("""\
 
514
    2 Joe Foo\t2005-11-22 [merge]
 
515
      rev-2
 
516
 
 
517
    1 Joe Foo\t2005-11-22
 
518
      rev-1
 
519
 
 
520
""", logfile.getvalue())
 
521
        finally:
 
522
            wt.unlock()
 
523
 
 
524
    def test_trailing_newlines(self):
 
525
        wt = self.make_branch_and_tree('.')
 
526
        b = make_commits_with_trailing_newlines(wt)
 
527
        sio = self.make_utf8_encoded_stringio()
 
528
        lf = LineLogFormatter(to_file=sio)
 
529
        show_log(b, lf)
 
530
        self.assertEqualDiff(sio.getvalue(), """\
 
531
3: Joe Foo 2005-11-21 single line with trailing newline
 
532
2: Joe Bar 2005-11-21 multiline
 
533
1: Joe Foo 2005-11-21 simple log message
 
534
""")
 
535
 
 
536
 
 
537
class TestGetViewRevisions(TestCaseWithTransport):
 
538
 
 
539
    def make_tree_with_commits(self):
 
540
        """Create a tree with well-known revision ids"""
 
541
        wt = self.make_branch_and_tree('tree1')
 
542
        wt.commit('commit one', rev_id='1')
 
543
        wt.commit('commit two', rev_id='2')
 
544
        wt.commit('commit three', rev_id='3')
 
545
        mainline_revs = [None, '1', '2', '3']
 
546
        rev_nos = {'1': 1, '2': 2, '3': 3}
 
547
        return mainline_revs, rev_nos, wt
 
548
 
 
549
    def make_tree_with_merges(self):
 
550
        """Create a tree with well-known revision ids and a merge"""
 
551
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
552
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
553
        tree2.commit('four-a', rev_id='4a')
 
554
        wt.merge_from_branch(tree2.branch)
 
555
        wt.commit('four-b', rev_id='4b')
 
556
        mainline_revs.append('4b')
 
557
        rev_nos['4b'] = 4
 
558
        # 4a: 3.1.1
 
559
        return mainline_revs, rev_nos, wt
 
560
 
 
561
    def make_tree_with_many_merges(self):
 
562
        """Create a tree with well-known revision ids"""
 
563
        wt = self.make_branch_and_tree('tree1')
 
564
        wt.commit('commit one', rev_id='1')
 
565
        wt.commit('commit two', rev_id='2')
 
566
        tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
 
567
        tree3.commit('commit three a', rev_id='3a')
 
568
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
569
        tree2.merge_from_branch(tree3.branch)
 
570
        tree2.commit('commit three b', rev_id='3b')
 
571
        wt.merge_from_branch(tree2.branch)
 
572
        wt.commit('commit three c', rev_id='3c')
 
573
        tree2.commit('four-a', rev_id='4a')
 
574
        wt.merge_from_branch(tree2.branch)
 
575
        wt.commit('four-b', rev_id='4b')
 
576
        mainline_revs = [None, '1', '2', '3c', '4b']
 
577
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
 
578
        full_rev_nos_for_reference = {
 
579
            '1': '1',
 
580
            '2': '2',
 
581
            '3a': '2.2.1', #first commit tree 3
 
582
            '3b': '2.1.1', # first commit tree 2
 
583
            '3c': '3', #merges 3b to main
 
584
            '4a': '2.1.2', # second commit tree 2
 
585
            '4b': '4', # merges 4a to main
 
586
            }
 
587
        return mainline_revs, rev_nos, wt
 
588
 
 
589
    def test_get_view_revisions_forward(self):
 
590
        """Test the get_view_revisions method"""
 
591
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
592
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
593
                                            'forward'))
 
594
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
 
595
            revisions)
 
596
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
597
                                             'forward', include_merges=False))
 
598
        self.assertEqual(revisions, revisions2)
 
599
 
 
600
    def test_get_view_revisions_reverse(self):
 
601
        """Test the get_view_revisions with reverse"""
 
602
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
603
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
604
                                            'reverse'))
 
605
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
 
606
            revisions)
 
607
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
608
                                             'reverse', include_merges=False))
 
609
        self.assertEqual(revisions, revisions2)
 
610
 
 
611
    def test_get_view_revisions_merge(self):
 
612
        """Test get_view_revisions when there are merges"""
 
613
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
614
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
615
                                            'forward'))
 
616
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
617
            ('4b', '4', 0), ('4a', '3.1.1', 1)],
 
618
            revisions)
 
619
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
620
                                             'forward', include_merges=False))
 
621
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
622
            ('4b', '4', 0)],
 
623
            revisions)
 
624
 
 
625
    def test_get_view_revisions_merge_reverse(self):
 
626
        """Test get_view_revisions in reverse when there are merges"""
 
627
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
628
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
629
                                            'reverse'))
 
630
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
 
631
            ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
 
632
            revisions)
 
633
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
634
                                             'reverse', include_merges=False))
 
635
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
 
636
            ('1', '1', 0)],
 
637
            revisions)
 
638
 
 
639
    def test_get_view_revisions_merge2(self):
 
640
        """Test get_view_revisions when there are merges"""
 
641
        mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
 
642
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
643
                                            'forward'))
 
644
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
645
            ('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
 
646
            ('4a', '2.1.2', 1)]
 
647
        self.assertEqual(expected, revisions)
 
648
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
649
                                             'forward', include_merges=False))
 
650
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
651
            ('4b', '4', 0)],
 
652
            revisions)
 
653
 
 
654
 
 
655
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
 
656
 
 
657
    def create_tree_with_single_merge(self):
 
658
        """Create a branch with a moderate layout.
 
659
 
 
660
        The revision graph looks like:
 
661
 
 
662
           A
 
663
           |\
 
664
           B C
 
665
           |/
 
666
           D
 
667
 
 
668
        In this graph, A introduced files f1 and f2 and f3.
 
669
        B modifies f1 and f3, and C modifies f2 and f3.
 
670
        D merges the changes from B and C and resolves the conflict for f3.
 
671
        """
 
672
        # TODO: jam 20070218 This seems like it could really be done
 
673
        #       with make_branch_and_memory_tree() if we could just
 
674
        #       create the content of those files.
 
675
        # TODO: jam 20070218 Another alternative is that we would really
 
676
        #       like to only create this tree 1 time for all tests that
 
677
        #       use it. Since 'log' only uses the tree in a readonly
 
678
        #       fashion, it seems a shame to regenerate an identical
 
679
        #       tree for each test.
 
680
        tree = self.make_branch_and_tree('tree')
 
681
        tree.lock_write()
 
682
        self.addCleanup(tree.unlock)
 
683
 
 
684
        self.build_tree_contents([('tree/f1', 'A\n'),
 
685
                                  ('tree/f2', 'A\n'),
 
686
                                  ('tree/f3', 'A\n'),
 
687
                                 ])
 
688
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
 
689
        tree.commit('A', rev_id='A')
 
690
 
 
691
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
 
692
                                  ('tree/f3', 'A\nC\n'),
 
693
                                 ])
 
694
        tree.commit('C', rev_id='C')
 
695
        # Revert back to A to build the other history.
 
696
        tree.set_last_revision('A')
 
697
        tree.branch.set_last_revision_info(1, 'A')
 
698
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
699
                                  ('tree/f2', 'A\n'),
 
700
                                  ('tree/f3', 'A\nB\n'),
 
701
                                 ])
 
702
        tree.commit('B', rev_id='B')
 
703
        tree.set_parent_ids(['B', 'C'])
 
704
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
705
                                  ('tree/f2', 'A\nC\n'),
 
706
                                  ('tree/f3', 'A\nB\nC\n'),
 
707
                                 ])
 
708
        tree.commit('D', rev_id='D')
 
709
 
 
710
        # Switch to a read lock for this tree.
 
711
        # We still have addCleanup(unlock)
 
712
        tree.unlock()
 
713
        tree.lock_read()
 
714
        return tree
 
715
 
 
716
    def test_tree_with_single_merge(self):
 
717
        """Make sure the tree layout is correct."""
 
718
        tree = self.create_tree_with_single_merge()
 
719
        rev_A_tree = tree.branch.repository.revision_tree('A')
 
720
        rev_B_tree = tree.branch.repository.revision_tree('B')
 
721
 
 
722
        f1_changed = (u'f1', 'f1-id', 'file', True, False)
 
723
        f2_changed = (u'f2', 'f2-id', 'file', True, False)
 
724
        f3_changed = (u'f3', 'f3-id', 'file', True, False)
 
725
 
 
726
        delta = rev_B_tree.changes_from(rev_A_tree)
 
727
        self.assertEqual([f1_changed, f3_changed], delta.modified)
 
728
        self.assertEqual([], delta.renamed)
 
729
        self.assertEqual([], delta.added)
 
730
        self.assertEqual([], delta.removed)
 
731
 
 
732
        rev_C_tree = tree.branch.repository.revision_tree('C')
 
733
        delta = rev_C_tree.changes_from(rev_A_tree)
 
734
        self.assertEqual([f2_changed, f3_changed], delta.modified)
 
735
        self.assertEqual([], delta.renamed)
 
736
        self.assertEqual([], delta.added)
 
737
        self.assertEqual([], delta.removed)
 
738
 
 
739
        rev_D_tree = tree.branch.repository.revision_tree('D')
 
740
        delta = rev_D_tree.changes_from(rev_B_tree)
 
741
        self.assertEqual([f2_changed, f3_changed], delta.modified)
 
742
        self.assertEqual([], delta.renamed)
 
743
        self.assertEqual([], delta.added)
 
744
        self.assertEqual([], delta.removed)
 
745
 
 
746
        delta = rev_D_tree.changes_from(rev_C_tree)
 
747
        self.assertEqual([f1_changed, f3_changed], delta.modified)
 
748
        self.assertEqual([], delta.renamed)
 
749
        self.assertEqual([], delta.added)
 
750
        self.assertEqual([], delta.removed)
 
751
 
 
752
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
 
753
        """Make sure _filter_revisions_touching_file_id returns the right values.
 
754
 
 
755
        Get the return value from _filter_revisions_touching_file_id and make
 
756
        sure they are correct.
 
757
        """
 
758
        # The api for _get_revisions_touching_file_id is a little crazy,
 
759
        # So we do the setup here.
 
760
        mainline = tree.branch.revision_history()
 
761
        mainline.insert(0, None)
 
762
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
 
763
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
 
764
                                                'reverse', True)
 
765
        actual_revs = log._filter_revisions_touching_file_id(
 
766
                            tree.branch, 
 
767
                            file_id,
 
768
                            mainline,
 
769
                            list(view_revs_iter))
 
770
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
 
771
 
 
772
    def test_file_id_f1(self):
 
773
        tree = self.create_tree_with_single_merge()
 
774
        # f1 should be marked as modified by revisions A and B
 
775
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
 
776
 
 
777
    def test_file_id_f2(self):
 
778
        tree = self.create_tree_with_single_merge()
 
779
        # f2 should be marked as modified by revisions A, C, and D
 
780
        # because D merged the changes from C.
 
781
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
782
 
 
783
    def test_file_id_f3(self):
 
784
        tree = self.create_tree_with_single_merge()
 
785
        # f3 should be marked as modified by revisions A, B, C, and D
 
786
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
787
 
 
788
 
 
789
class TestShowChangedRevisions(TestCaseWithTransport):
 
790
 
 
791
    def test_show_changed_revisions_verbose(self):
 
792
        tree = self.make_branch_and_tree('tree_a')
 
793
        self.build_tree(['tree_a/foo'])
 
794
        tree.add('foo')
 
795
        tree.commit('bar', rev_id='bar-id')
 
796
        s = self.make_utf8_encoded_stringio()
 
797
        log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
 
798
        self.assertContainsRe(s.getvalue(), 'bar')
 
799
        self.assertNotContainsRe(s.getvalue(), 'foo')
 
800
 
 
801
 
 
802
class TestLogFormatter(TestCase):
 
803
 
 
804
    def test_short_committer(self):
 
805
        rev = Revision('a-id')
 
806
        rev.committer = 'John Doe <jdoe@example.com>'
 
807
        lf = LogFormatter(None)
 
808
        self.assertEqual('John Doe', lf.short_committer(rev))
 
809
 
 
810
    def test_short_author(self):
 
811
        rev = Revision('a-id')
 
812
        rev.committer = 'John Doe <jdoe@example.com>'
 
813
        lf = LogFormatter(None)
 
814
        self.assertEqual('John Doe', lf.short_author(rev))
 
815
        rev.properties['author'] = 'John Smith <jsmith@example.com>'
 
816
        self.assertEqual('John Smith', lf.short_author(rev))