/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Robert Collins
  • Date: 2007-11-26 03:36:30 UTC
  • mto: This revision was merged to the branch mainline in revision 3029.
  • Revision ID: robertc@robertcollins.net-20071126033630-nvz32vtouqcksjdy
Make test_smart use specific formats as needed to exercise locked and unlocked repositories.

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