~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Vincent Ladeuil
  • Date: 2008-01-29 15:16:31 UTC
  • mto: (3206.1.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 3207.
  • Revision ID: v.ladeuil+lp@free.fr-20080129151631-vqjd13tb405mobx6
Fix two more leaking tmp dirs, by reworking TransformPreview lock handling.

* bzrlib/tests/test_transform.py:
(TestTransformMerge): Revert previous patch and cleanly call
preview.finalize now that we can.

* bzrlib/tests/test_merge.py:
(TestMerge.test_make_preview_transform): Catch TransformPreview
leak.

* bzrlib/builtins.py:
(cmd_merge._do_preview): Finalize the TransformPreview or the
limbodir will stay in /tmp.

* bzrlib/transform.py:
(TreeTransformBase.__init__): Create the _deletiondir since it's
reffered to by finalize.
(TreeTransformBase.finalize): Delete the dir only if _deletiondir
is set.
(TreeTransform.__init__): Use a temp var for deletiondir and set
the attribute after the base class __init__ has been called.
(TransformPreview.__init__): Read locks the tree since finalize
wants to unlock it (as suggested by Aaron).

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, TestCaseWithTransport
21
 
from bzrlib.log import (LogFormatter, show_log, LongLogFormatter,
22
 
                        ShortLogFormatter, LineLogFormatter)
 
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)
23
29
from bzrlib.branch import Branch
24
 
from bzrlib.errors import InvalidRevisionNumber
25
 
 
26
 
class _LogEntry(object):
27
 
    # should probably move into bzrlib.log?
28
 
    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
    )
29
39
 
30
40
 
31
41
class LogCatcher(LogFormatter):
37
47
 
38
48
    We should also test the LogFormatter.
39
49
    """
 
50
 
 
51
    supports_delta = True
 
52
 
40
53
    def __init__(self):
41
54
        super(LogCatcher, self).__init__(to_file=None)
42
55
        self.logs = []
43
56
 
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(TestCaseWithTransport):
 
57
    def log_revision(self, revision):
 
58
        self.logs.append(revision)
 
59
 
 
60
 
 
61
class TestShowLog(TestCaseWithTransport):
53
62
 
54
63
    def checkDelta(self, delta, **kw):
55
64
        """Check the filenames touched by a delta are as expected."""
56
65
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
57
66
            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
67
            # strip out only the path components
64
68
            got = [x[0] for x in getattr(delta, n)]
65
69
            self.assertEquals(expected, got)
84
88
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
85
89
                          start_revision=1, end_revision=-1) 
86
90
 
87
 
    def test_cur_revno(self):
88
 
        wt = self.make_branch_and_tree('.')
89
 
        b = wt.branch
90
 
 
91
 
        lf = LogCatcher()
92
 
        wt.commit('empty commit')
93
 
        show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
94
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
95
 
                          start_revision=2, end_revision=1) 
96
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
97
 
                          start_revision=1, end_revision=2) 
98
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
99
 
                          start_revision=0, end_revision=2) 
100
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
101
 
                          start_revision=1, end_revision=0) 
102
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
103
 
                          start_revision=-1, end_revision=1) 
104
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
105
 
                          start_revision=1, end_revision=-1) 
106
 
 
107
91
    def test_simple_log(self):
108
92
        eq = self.assertEquals
109
93
        
119
103
        lf = LogCatcher()
120
104
        show_log(b, lf, verbose=True)
121
105
        eq(len(lf.logs), 1)
122
 
        eq(lf.logs[0].revno, 1)
 
106
        eq(lf.logs[0].revno, '1')
123
107
        eq(lf.logs[0].rev.message, 'empty commit')
124
108
        d = lf.logs[0].delta
125
109
        self.log('log delta: %r' % d)
127
111
 
128
112
        self.build_tree(['hello'])
129
113
        wt.add('hello')
130
 
        wt.commit('add one file')
 
114
        wt.commit('add one file',
 
115
                  committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
 
116
                            u'<test@example.com>')
131
117
 
132
 
        lf = StringIO()
 
118
        lf = self.make_utf8_encoded_stringio()
133
119
        # log using regular thing
134
120
        show_log(b, LongLogFormatter(lf))
135
121
        lf.seek(0)
142
128
        eq(len(lf.logs), 2)
143
129
        self.log('log entries:')
144
130
        for logentry in lf.logs:
145
 
            self.log('%4d %s' % (logentry.revno, logentry.rev.message))
 
131
            self.log('%4s %s' % (logentry.revno, logentry.rev.message))
146
132
        
147
133
        # first one is most recent
148
134
        logentry = lf.logs[0]
149
 
        eq(logentry.revno, 2)
 
135
        eq(logentry.revno, '2')
150
136
        eq(logentry.rev.message, 'add one file')
151
137
        d = logentry.delta
152
138
        self.log('log 2 delta: %r' % d)
153
 
        # self.checkDelta(d, added=['hello'])
 
139
        self.checkDelta(d, added=['hello'])
154
140
        
155
141
        # commit a log message with control characters
156
142
        msg = "All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
177
163
        self.log("escaped commit message: %r", committed_msg)
178
164
        self.assert_(msg == committed_msg)
179
165
 
 
166
    def test_deltas_in_merge_revisions(self):
 
167
        """Check deltas created for both mainline and merge revisions"""
 
168
        eq = self.assertEquals
 
169
        wt = self.make_branch_and_tree('parent')
 
170
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
 
171
        wt.add('file1')
 
172
        wt.add('file2')
 
173
        wt.commit(message='add file1 and file2')
 
174
        self.run_bzr('branch parent child')
 
175
        os.unlink('child/file1')
 
176
        file('child/file2', 'wb').write('hello\n')
 
177
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
 
178
            'child'])
 
179
        os.chdir('parent')
 
180
        self.run_bzr('merge ../child')
 
181
        wt.commit('merge child branch')
 
182
        os.chdir('..')
 
183
        b = wt.branch
 
184
        lf = LogCatcher()
 
185
        lf.supports_merge_revisions = True
 
186
        show_log(b, lf, verbose=True)
 
187
        eq(len(lf.logs),3)
 
188
        logentry = lf.logs[0]
 
189
        eq(logentry.revno, '2')
 
190
        eq(logentry.rev.message, 'merge child branch')
 
191
        d = logentry.delta
 
192
        self.checkDelta(d, removed=['file1'], modified=['file2'])
 
193
        logentry = lf.logs[1]
 
194
        eq(logentry.revno, '1.1.1')
 
195
        eq(logentry.rev.message, 'remove file1 and modify file2')
 
196
        d = logentry.delta
 
197
        self.checkDelta(d, removed=['file1'], modified=['file2'])
 
198
        logentry = lf.logs[2]
 
199
        eq(logentry.revno, '1')
 
200
        eq(logentry.rev.message, 'add file1 and file2')
 
201
        d = logentry.delta
 
202
        self.checkDelta(d, added=['file1', 'file2'])
 
203
 
 
204
    def test_merges_nonsupporting_formatter(self):
 
205
        """Tests that show_log will raise if the formatter doesn't
 
206
        support merge revisions."""
 
207
        wt = self.make_branch_and_memory_tree('.')
 
208
        wt.lock_write()
 
209
        try:
 
210
            wt.add('')
 
211
            wt.commit('rev-1', rev_id='rev-1',
 
212
                      timestamp=1132586655, timezone=36000,
 
213
                      committer='Joe Foo <joe@foo.com>')
 
214
            wt.commit('rev-merged', rev_id='rev-2a',
 
215
                      timestamp=1132586700, timezone=36000,
 
216
                      committer='Joe Foo <joe@foo.com>')
 
217
            wt.set_parent_ids(['rev-1', 'rev-2a'])
 
218
            wt.branch.set_last_revision_info(1, 'rev-1')
 
219
            wt.commit('rev-2', rev_id='rev-2b',
 
220
                      timestamp=1132586800, timezone=36000,
 
221
                      committer='Joe Foo <joe@foo.com>')
 
222
            logfile = self.make_utf8_encoded_stringio()
 
223
            formatter = ShortLogFormatter(to_file=logfile)
 
224
            wtb = wt.branch
 
225
            lf = LogCatcher()
 
226
            revspec = RevisionSpec.from_string('1.1.1')
 
227
            rev = revspec.in_history(wtb)
 
228
            self.assertRaises(BzrCommandError, show_log, wtb, lf,
 
229
                              start_revision=rev, end_revision=rev)
 
230
        finally:
 
231
            wt.unlock()
 
232
 
 
233
 
 
234
def make_commits_with_trailing_newlines(wt):
 
235
    """Helper method for LogFormatter tests"""    
 
236
    b = wt.branch
 
237
    b.nick='test'
 
238
    open('a', 'wb').write('hello moto\n')
 
239
    wt.add('a')
 
240
    wt.commit('simple log message', rev_id='a1',
 
241
              timestamp=1132586655.459960938, timezone=-6*3600,
 
242
              committer='Joe Foo <joe@foo.com>')
 
243
    open('b', 'wb').write('goodbye\n')
 
244
    wt.add('b')
 
245
    wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
 
246
              timestamp=1132586842.411175966, timezone=-6*3600,
 
247
              committer='Joe Foo <joe@foo.com>',
 
248
              author='Joe Bar <joe@bar.com>')
 
249
 
 
250
    open('c', 'wb').write('just another manic monday\n')
 
251
    wt.add('c')
 
252
    wt.commit('single line with trailing newline\n', rev_id='a3',
 
253
              timestamp=1132587176.835228920, timezone=-6*3600,
 
254
              committer = 'Joe Foo <joe@foo.com>')
 
255
    return b
 
256
 
 
257
 
 
258
def normalize_log(log):
 
259
    """Replaces the variable lines of logs with fixed lines"""
 
260
    author = 'author: Dolor Sit <test@example.com>'
 
261
    committer = 'committer: Lorem Ipsum <test@example.com>'
 
262
    lines = log.splitlines(True)
 
263
    for idx,line in enumerate(lines):
 
264
        stripped_line = line.lstrip()
 
265
        indent = ' ' * (len(line) - len(stripped_line))
 
266
        if stripped_line.startswith('author:'):
 
267
            lines[idx] = indent + author + '\n'
 
268
        elif stripped_line.startswith('committer:'):
 
269
            lines[idx] = indent + committer + '\n'
 
270
        elif stripped_line.startswith('timestamp:'):
 
271
            lines[idx] = indent + 'timestamp: Just now\n'
 
272
    return ''.join(lines)
 
273
 
 
274
 
 
275
class TestShortLogFormatter(TestCaseWithTransport):
 
276
 
180
277
    def test_trailing_newlines(self):
181
278
        wt = self.make_branch_and_tree('.')
182
 
        b = wt.branch
183
 
        b.nick='test'
184
 
        open('a', 'wb').write('hello moto\n')
185
 
        wt.add('a')
186
 
        wt.commit('simple log message', rev_id='a1'
187
 
                , timestamp=1132586655.459960938, timezone=-6*3600
188
 
                , committer='Joe Foo <joe@foo.com>')
189
 
        open('b', 'wb').write('goodbye\n')
190
 
        wt.add('b')
191
 
        wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
192
 
                , timestamp=1132586842.411175966, timezone=-6*3600
193
 
                , committer='Joe Foo <joe@foo.com>')
194
 
 
195
 
        open('c', 'wb').write('just another manic monday\n')
196
 
        wt.add('c')
197
 
        wt.commit('single line with trailing newline\n', rev_id='a3'
198
 
                , timestamp=1132587176.835228920, timezone=-6*3600
199
 
                , committer = 'Joe Foo <joe@foo.com>')
200
 
 
201
 
        sio = StringIO()
 
279
        b = make_commits_with_trailing_newlines(wt)
 
280
        sio = self.make_utf8_encoded_stringio()
202
281
        lf = ShortLogFormatter(to_file=sio)
203
282
        show_log(b, lf)
204
 
        self.assertEquals(sio.getvalue(), """\
 
283
        self.assertEqualDiff(sio.getvalue(), """\
205
284
    3 Joe Foo\t2005-11-21
206
285
      single line with trailing newline
207
286
 
208
 
    2 Joe Foo\t2005-11-21
 
287
    2 Joe Bar\t2005-11-21
209
288
      multiline
210
289
      log
211
290
      message
215
294
 
216
295
""")
217
296
 
218
 
        sio = StringIO()
219
 
        lf = LongLogFormatter(to_file=sio)
220
 
        show_log(b, lf)
221
 
        self.assertEquals(sio.getvalue(), """\
222
 
------------------------------------------------------------
223
 
revno: 3
224
 
committer: Joe Foo <joe@foo.com>
225
 
branch nick: test
226
 
timestamp: Mon 2005-11-21 09:32:56 -0600
227
 
message:
228
 
  single line with trailing newline
229
 
------------------------------------------------------------
230
 
revno: 2
231
 
committer: Joe Foo <joe@foo.com>
232
 
branch nick: test
233
 
timestamp: Mon 2005-11-21 09:27:22 -0600
234
 
message:
235
 
  multiline
236
 
  log
237
 
  message
238
 
------------------------------------------------------------
239
 
revno: 1
240
 
committer: Joe Foo <joe@foo.com>
241
 
branch nick: test
242
 
timestamp: Mon 2005-11-21 09:24:15 -0600
243
 
message:
244
 
  simple log message
245
 
""")
246
 
        
 
297
    def test_short_log_with_merges(self):
 
298
        wt = self.make_branch_and_memory_tree('.')
 
299
        wt.lock_write()
 
300
        try:
 
301
            wt.add('')
 
302
            wt.commit('rev-1', rev_id='rev-1',
 
303
                      timestamp=1132586655, timezone=36000,
 
304
                      committer='Joe Foo <joe@foo.com>')
 
305
            wt.commit('rev-merged', rev_id='rev-2a',
 
306
                      timestamp=1132586700, timezone=36000,
 
307
                      committer='Joe Foo <joe@foo.com>')
 
308
            wt.set_parent_ids(['rev-1', 'rev-2a'])
 
309
            wt.branch.set_last_revision_info(1, 'rev-1')
 
310
            wt.commit('rev-2', rev_id='rev-2b',
 
311
                      timestamp=1132586800, timezone=36000,
 
312
                      committer='Joe Foo <joe@foo.com>')
 
313
            logfile = self.make_utf8_encoded_stringio()
 
314
            formatter = ShortLogFormatter(to_file=logfile)
 
315
            show_log(wt.branch, formatter)
 
316
            self.assertEqualDiff(logfile.getvalue(), """\
 
317
    2 Joe Foo\t2005-11-22 [merge]
 
318
      rev-2
 
319
 
 
320
    1 Joe Foo\t2005-11-22
 
321
      rev-1
 
322
 
 
323
""")
 
324
        finally:
 
325
            wt.unlock()
 
326
 
 
327
    def test_short_log_single_merge_revision(self):
 
328
        wt = self.make_branch_and_memory_tree('.')
 
329
        wt.lock_write()
 
330
        try:
 
331
            wt.add('')
 
332
            wt.commit('rev-1', rev_id='rev-1',
 
333
                      timestamp=1132586655, timezone=36000,
 
334
                      committer='Joe Foo <joe@foo.com>')
 
335
            wt.commit('rev-merged', rev_id='rev-2a',
 
336
                      timestamp=1132586700, timezone=36000,
 
337
                      committer='Joe Foo <joe@foo.com>')
 
338
            wt.set_parent_ids(['rev-1', 'rev-2a'])
 
339
            wt.branch.set_last_revision_info(1, 'rev-1')
 
340
            wt.commit('rev-2', rev_id='rev-2b',
 
341
                      timestamp=1132586800, timezone=36000,
 
342
                      committer='Joe Foo <joe@foo.com>')
 
343
            logfile = self.make_utf8_encoded_stringio()
 
344
            formatter = ShortLogFormatter(to_file=logfile)
 
345
            revspec = RevisionSpec.from_string('1.1.1')
 
346
            wtb = wt.branch
 
347
            rev = revspec.in_history(wtb)
 
348
            show_log(wtb, formatter, start_revision=rev, end_revision=rev)
 
349
            self.assertEqualDiff(logfile.getvalue(), """\
 
350
1.1.1 Joe Foo\t2005-11-22
 
351
      rev-merged
 
352
 
 
353
""")
 
354
        finally:
 
355
            wt.unlock()
 
356
 
 
357
 
 
358
class TestLongLogFormatter(TestCaseWithTransport):
 
359
 
247
360
    def test_verbose_log(self):
248
361
        """Verbose log includes changed files
249
362
        
277
390
  a
278
391
''')
279
392
 
 
393
    def test_merges_are_indented_by_level(self):
 
394
        wt = self.make_branch_and_tree('parent')
 
395
        wt.commit('first post')
 
396
        self.run_bzr('branch parent child')
 
397
        self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
 
398
        self.run_bzr('branch child smallerchild')
 
399
        self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
 
400
            'smallerchild'])
 
401
        os.chdir('child')
 
402
        self.run_bzr('merge ../smallerchild')
 
403
        self.run_bzr(['commit', '-m', 'merge branch 2'])
 
404
        os.chdir('../parent')
 
405
        self.run_bzr('merge ../child')
 
406
        wt.commit('merge branch 1')
 
407
        b = wt.branch
 
408
        sio = self.make_utf8_encoded_stringio()
 
409
        lf = LongLogFormatter(to_file=sio)
 
410
        show_log(b, lf, verbose=True)
 
411
        log = normalize_log(sio.getvalue())
 
412
        self.assertEqualDiff(log, """\
 
413
------------------------------------------------------------
 
414
revno: 2
 
415
committer: Lorem Ipsum <test@example.com>
 
416
branch nick: parent
 
417
timestamp: Just now
 
418
message:
 
419
  merge branch 1
 
420
    ------------------------------------------------------------
 
421
    revno: 1.1.2
 
422
    committer: Lorem Ipsum <test@example.com>
 
423
    branch nick: child
 
424
    timestamp: Just now
 
425
    message:
 
426
      merge branch 2
 
427
        ------------------------------------------------------------
 
428
        revno: 1.2.1
 
429
        committer: Lorem Ipsum <test@example.com>
 
430
        branch nick: smallerchild
 
431
        timestamp: Just now
 
432
        message:
 
433
          branch 2
 
434
    ------------------------------------------------------------
 
435
    revno: 1.1.1
 
436
    committer: Lorem Ipsum <test@example.com>
 
437
    branch nick: child
 
438
    timestamp: Just now
 
439
    message:
 
440
      branch 1
 
441
------------------------------------------------------------
 
442
revno: 1
 
443
committer: Lorem Ipsum <test@example.com>
 
444
branch nick: parent
 
445
timestamp: Just now
 
446
message:
 
447
  first post
 
448
""")
 
449
 
 
450
    def test_verbose_merge_revisions_contain_deltas(self):
 
451
        wt = self.make_branch_and_tree('parent')
 
452
        self.build_tree(['parent/f1', 'parent/f2'])
 
453
        wt.add(['f1','f2'])
 
454
        wt.commit('first post')
 
455
        self.run_bzr('branch parent child')
 
456
        os.unlink('child/f1')
 
457
        file('child/f2', 'wb').write('hello\n')
 
458
        self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
 
459
            'child'])
 
460
        os.chdir('parent')
 
461
        self.run_bzr('merge ../child')
 
462
        wt.commit('merge branch 1')
 
463
        b = wt.branch
 
464
        sio = self.make_utf8_encoded_stringio()
 
465
        lf = LongLogFormatter(to_file=sio)
 
466
        show_log(b, lf, verbose=True)
 
467
        log = normalize_log(sio.getvalue())
 
468
        self.assertEqualDiff(log, """\
 
469
------------------------------------------------------------
 
470
revno: 2
 
471
committer: Lorem Ipsum <test@example.com>
 
472
branch nick: parent
 
473
timestamp: Just now
 
474
message:
 
475
  merge branch 1
 
476
removed:
 
477
  f1
 
478
modified:
 
479
  f2
 
480
    ------------------------------------------------------------
 
481
    revno: 1.1.1
 
482
    committer: Lorem Ipsum <test@example.com>
 
483
    branch nick: child
 
484
    timestamp: Just now
 
485
    message:
 
486
      removed f1 and modified f2
 
487
    removed:
 
488
      f1
 
489
    modified:
 
490
      f2
 
491
------------------------------------------------------------
 
492
revno: 1
 
493
committer: Lorem Ipsum <test@example.com>
 
494
branch nick: parent
 
495
timestamp: Just now
 
496
message:
 
497
  first post
 
498
added:
 
499
  f1
 
500
  f2
 
501
""")
 
502
 
 
503
    def test_trailing_newlines(self):
 
504
        wt = self.make_branch_and_tree('.')
 
505
        b = make_commits_with_trailing_newlines(wt)
 
506
        sio = self.make_utf8_encoded_stringio()
 
507
        lf = LongLogFormatter(to_file=sio)
 
508
        show_log(b, lf)
 
509
        self.assertEqualDiff(sio.getvalue(), """\
 
510
------------------------------------------------------------
 
511
revno: 3
 
512
committer: Joe Foo <joe@foo.com>
 
513
branch nick: test
 
514
timestamp: Mon 2005-11-21 09:32:56 -0600
 
515
message:
 
516
  single line with trailing newline
 
517
------------------------------------------------------------
 
518
revno: 2
 
519
author: Joe Bar <joe@bar.com>
 
520
committer: Joe Foo <joe@foo.com>
 
521
branch nick: test
 
522
timestamp: Mon 2005-11-21 09:27:22 -0600
 
523
message:
 
524
  multiline
 
525
  log
 
526
  message
 
527
------------------------------------------------------------
 
528
revno: 1
 
529
committer: Joe Foo <joe@foo.com>
 
530
branch nick: test
 
531
timestamp: Mon 2005-11-21 09:24:15 -0600
 
532
message:
 
533
  simple log message
 
534
""")
 
535
 
 
536
    def test_author_in_log(self):
 
537
        """Log includes the author name if it's set in
 
538
        the revision properties
 
539
        """
 
540
        wt = self.make_branch_and_tree('.')
 
541
        b = wt.branch
 
542
        self.build_tree(['a'])
 
543
        wt.add('a')
 
544
        b.nick = 'test_author_log'
 
545
        wt.commit(message='add a',
 
546
                  timestamp=1132711707,
 
547
                  timezone=36000,
 
548
                  committer='Lorem Ipsum <test@example.com>',
 
549
                  author='John Doe <jdoe@example.com>')
 
550
        sio = StringIO()
 
551
        formatter = LongLogFormatter(to_file=sio)
 
552
        show_log(b, formatter)
 
553
        self.assertEqualDiff(sio.getvalue(), '''\
 
554
------------------------------------------------------------
 
555
revno: 1
 
556
author: John Doe <jdoe@example.com>
 
557
committer: Lorem Ipsum <test@example.com>
 
558
branch nick: test_author_log
 
559
timestamp: Wed 2005-11-23 12:08:27 +1000
 
560
message:
 
561
  add a
 
562
''')
 
563
 
 
564
 
 
565
 
 
566
class TestLineLogFormatter(TestCaseWithTransport):
 
567
 
280
568
    def test_line_log(self):
281
569
        """Line log should show revno
282
570
        
297
585
        logfile.flush()
298
586
        logfile.seek(0)
299
587
        log_contents = logfile.read()
300
 
        self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
 
588
        self.assertEqualDiff(log_contents,
 
589
            '1: Line-Log-Formatte... 2005-11-23 add a\n')
 
590
 
 
591
    def test_trailing_newlines(self):
 
592
        wt = self.make_branch_and_tree('.')
 
593
        b = make_commits_with_trailing_newlines(wt)
 
594
        sio = self.make_utf8_encoded_stringio()
 
595
        lf = LineLogFormatter(to_file=sio)
 
596
        show_log(b, lf)
 
597
        self.assertEqualDiff(sio.getvalue(), """\
 
598
3: Joe Foo 2005-11-21 single line with trailing newline
 
599
2: Joe Bar 2005-11-21 multiline
 
600
1: Joe Foo 2005-11-21 simple log message
 
601
""")
 
602
 
 
603
    def test_line_log_single_merge_revision(self):
 
604
        wt = self.make_branch_and_memory_tree('.')
 
605
        wt.lock_write()
 
606
        try:
 
607
            wt.add('')
 
608
            wt.commit('rev-1', rev_id='rev-1',
 
609
                      timestamp=1132586655, timezone=36000,
 
610
                      committer='Joe Foo <joe@foo.com>')
 
611
            wt.commit('rev-merged', rev_id='rev-2a',
 
612
                      timestamp=1132586700, timezone=36000,
 
613
                      committer='Joe Foo <joe@foo.com>')
 
614
            wt.set_parent_ids(['rev-1', 'rev-2a'])
 
615
            wt.branch.set_last_revision_info(1, 'rev-1')
 
616
            wt.commit('rev-2', rev_id='rev-2b',
 
617
                      timestamp=1132586800, timezone=36000,
 
618
                      committer='Joe Foo <joe@foo.com>')
 
619
            logfile = self.make_utf8_encoded_stringio()
 
620
            formatter = LineLogFormatter(to_file=logfile)
 
621
            revspec = RevisionSpec.from_string('1.1.1')
 
622
            wtb = wt.branch
 
623
            rev = revspec.in_history(wtb)
 
624
            show_log(wtb, formatter, start_revision=rev, end_revision=rev)
 
625
            self.assertEqualDiff(logfile.getvalue(), """\
 
626
1.1.1: Joe Foo 2005-11-22 rev-merged
 
627
""")
 
628
        finally:
 
629
            wt.unlock()
 
630
 
 
631
 
 
632
 
 
633
class TestGetViewRevisions(TestCaseWithTransport):
 
634
 
 
635
    def make_tree_with_commits(self):
 
636
        """Create a tree with well-known revision ids"""
 
637
        wt = self.make_branch_and_tree('tree1')
 
638
        wt.commit('commit one', rev_id='1')
 
639
        wt.commit('commit two', rev_id='2')
 
640
        wt.commit('commit three', rev_id='3')
 
641
        mainline_revs = [None, '1', '2', '3']
 
642
        rev_nos = {'1': 1, '2': 2, '3': 3}
 
643
        return mainline_revs, rev_nos, wt
 
644
 
 
645
    def make_tree_with_merges(self):
 
646
        """Create a tree with well-known revision ids and a merge"""
 
647
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
648
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
649
        tree2.commit('four-a', rev_id='4a')
 
650
        wt.merge_from_branch(tree2.branch)
 
651
        wt.commit('four-b', rev_id='4b')
 
652
        mainline_revs.append('4b')
 
653
        rev_nos['4b'] = 4
 
654
        # 4a: 3.1.1
 
655
        return mainline_revs, rev_nos, wt
 
656
 
 
657
    def make_tree_with_many_merges(self):
 
658
        """Create a tree with well-known revision ids"""
 
659
        wt = self.make_branch_and_tree('tree1')
 
660
        wt.commit('commit one', rev_id='1')
 
661
        wt.commit('commit two', rev_id='2')
 
662
        tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
 
663
        tree3.commit('commit three a', rev_id='3a')
 
664
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
665
        tree2.merge_from_branch(tree3.branch)
 
666
        tree2.commit('commit three b', rev_id='3b')
 
667
        wt.merge_from_branch(tree2.branch)
 
668
        wt.commit('commit three c', rev_id='3c')
 
669
        tree2.commit('four-a', rev_id='4a')
 
670
        wt.merge_from_branch(tree2.branch)
 
671
        wt.commit('four-b', rev_id='4b')
 
672
        mainline_revs = [None, '1', '2', '3c', '4b']
 
673
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
 
674
        full_rev_nos_for_reference = {
 
675
            '1': '1',
 
676
            '2': '2',
 
677
            '3a': '2.1.1', #first commit tree 3
 
678
            '3b': '2.2.1', # first commit tree 2
 
679
            '3c': '3', #merges 3b to main
 
680
            '4a': '2.2.2', # second commit tree 2
 
681
            '4b': '4', # merges 4a to main
 
682
            }
 
683
        return mainline_revs, rev_nos, wt
 
684
 
 
685
    def test_get_view_revisions_forward(self):
 
686
        """Test the get_view_revisions method"""
 
687
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
688
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
689
                                            'forward'))
 
690
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
 
691
            revisions)
 
692
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
693
                                             'forward', include_merges=False))
 
694
        self.assertEqual(revisions, revisions2)
 
695
 
 
696
    def test_get_view_revisions_reverse(self):
 
697
        """Test the get_view_revisions with reverse"""
 
698
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
699
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
700
                                            'reverse'))
 
701
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
 
702
            revisions)
 
703
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
704
                                             'reverse', include_merges=False))
 
705
        self.assertEqual(revisions, revisions2)
 
706
 
 
707
    def test_get_view_revisions_merge(self):
 
708
        """Test get_view_revisions when there are merges"""
 
709
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
710
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
711
                                            'forward'))
 
712
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
713
            ('4b', '4', 0), ('4a', '3.1.1', 1)],
 
714
            revisions)
 
715
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
716
                                             'forward', include_merges=False))
 
717
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
718
            ('4b', '4', 0)],
 
719
            revisions)
 
720
 
 
721
    def test_get_view_revisions_merge_reverse(self):
 
722
        """Test get_view_revisions in reverse when there are merges"""
 
723
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
724
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
725
                                            'reverse'))
 
726
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
 
727
            ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
 
728
            revisions)
 
729
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
730
                                             'reverse', include_merges=False))
 
731
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
 
732
            ('1', '1', 0)],
 
733
            revisions)
 
734
 
 
735
    def test_get_view_revisions_merge2(self):
 
736
        """Test get_view_revisions when there are merges"""
 
737
        mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
 
738
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
739
                                            'forward'))
 
740
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
741
            ('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
 
742
            ('4a', '2.2.2', 1)]
 
743
        self.assertEqual(expected, revisions)
 
744
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
745
                                             'forward', include_merges=False))
 
746
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
747
            ('4b', '4', 0)],
 
748
            revisions)
 
749
 
 
750
 
 
751
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
 
752
 
 
753
    def create_tree_with_single_merge(self):
 
754
        """Create a branch with a moderate layout.
 
755
 
 
756
        The revision graph looks like:
 
757
 
 
758
           A
 
759
           |\
 
760
           B C
 
761
           |/
 
762
           D
 
763
 
 
764
        In this graph, A introduced files f1 and f2 and f3.
 
765
        B modifies f1 and f3, and C modifies f2 and f3.
 
766
        D merges the changes from B and C and resolves the conflict for f3.
 
767
        """
 
768
        # TODO: jam 20070218 This seems like it could really be done
 
769
        #       with make_branch_and_memory_tree() if we could just
 
770
        #       create the content of those files.
 
771
        # TODO: jam 20070218 Another alternative is that we would really
 
772
        #       like to only create this tree 1 time for all tests that
 
773
        #       use it. Since 'log' only uses the tree in a readonly
 
774
        #       fashion, it seems a shame to regenerate an identical
 
775
        #       tree for each test.
 
776
        tree = self.make_branch_and_tree('tree')
 
777
        tree.lock_write()
 
778
        self.addCleanup(tree.unlock)
 
779
 
 
780
        self.build_tree_contents([('tree/f1', 'A\n'),
 
781
                                  ('tree/f2', 'A\n'),
 
782
                                  ('tree/f3', 'A\n'),
 
783
                                 ])
 
784
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
 
785
        tree.commit('A', rev_id='A')
 
786
 
 
787
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
 
788
                                  ('tree/f3', 'A\nC\n'),
 
789
                                 ])
 
790
        tree.commit('C', rev_id='C')
 
791
        # Revert back to A to build the other history.
 
792
        tree.set_last_revision('A')
 
793
        tree.branch.set_last_revision_info(1, 'A')
 
794
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
795
                                  ('tree/f2', 'A\n'),
 
796
                                  ('tree/f3', 'A\nB\n'),
 
797
                                 ])
 
798
        tree.commit('B', rev_id='B')
 
799
        tree.set_parent_ids(['B', 'C'])
 
800
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
801
                                  ('tree/f2', 'A\nC\n'),
 
802
                                  ('tree/f3', 'A\nB\nC\n'),
 
803
                                 ])
 
804
        tree.commit('D', rev_id='D')
 
805
 
 
806
        # Switch to a read lock for this tree.
 
807
        # We still have addCleanup(unlock)
 
808
        tree.unlock()
 
809
        tree.lock_read()
 
810
        return tree
 
811
 
 
812
    def test_tree_with_single_merge(self):
 
813
        """Make sure the tree layout is correct."""
 
814
        tree = self.create_tree_with_single_merge()
 
815
        rev_A_tree = tree.branch.repository.revision_tree('A')
 
816
        rev_B_tree = tree.branch.repository.revision_tree('B')
 
817
 
 
818
        f1_changed = (u'f1', 'f1-id', 'file', True, False)
 
819
        f2_changed = (u'f2', 'f2-id', 'file', True, False)
 
820
        f3_changed = (u'f3', 'f3-id', 'file', True, False)
 
821
 
 
822
        delta = rev_B_tree.changes_from(rev_A_tree)
 
823
        self.assertEqual([f1_changed, f3_changed], delta.modified)
 
824
        self.assertEqual([], delta.renamed)
 
825
        self.assertEqual([], delta.added)
 
826
        self.assertEqual([], delta.removed)
 
827
 
 
828
        rev_C_tree = tree.branch.repository.revision_tree('C')
 
829
        delta = rev_C_tree.changes_from(rev_A_tree)
 
830
        self.assertEqual([f2_changed, f3_changed], delta.modified)
 
831
        self.assertEqual([], delta.renamed)
 
832
        self.assertEqual([], delta.added)
 
833
        self.assertEqual([], delta.removed)
 
834
 
 
835
        rev_D_tree = tree.branch.repository.revision_tree('D')
 
836
        delta = rev_D_tree.changes_from(rev_B_tree)
 
837
        self.assertEqual([f2_changed, f3_changed], delta.modified)
 
838
        self.assertEqual([], delta.renamed)
 
839
        self.assertEqual([], delta.added)
 
840
        self.assertEqual([], delta.removed)
 
841
 
 
842
        delta = rev_D_tree.changes_from(rev_C_tree)
 
843
        self.assertEqual([f1_changed, f3_changed], delta.modified)
 
844
        self.assertEqual([], delta.renamed)
 
845
        self.assertEqual([], delta.added)
 
846
        self.assertEqual([], delta.removed)
 
847
 
 
848
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
 
849
        """Make sure _filter_revisions_touching_file_id returns the right values.
 
850
 
 
851
        Get the return value from _filter_revisions_touching_file_id and make
 
852
        sure they are correct.
 
853
        """
 
854
        # The api for _get_revisions_touching_file_id is a little crazy,
 
855
        # So we do the setup here.
 
856
        mainline = tree.branch.revision_history()
 
857
        mainline.insert(0, None)
 
858
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
 
859
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
 
860
                                                'reverse', True)
 
861
        actual_revs = log._filter_revisions_touching_file_id(
 
862
                            tree.branch, 
 
863
                            file_id,
 
864
                            mainline,
 
865
                            list(view_revs_iter))
 
866
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
 
867
 
 
868
    def test_file_id_f1(self):
 
869
        tree = self.create_tree_with_single_merge()
 
870
        # f1 should be marked as modified by revisions A and B
 
871
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
 
872
 
 
873
    def test_file_id_f2(self):
 
874
        tree = self.create_tree_with_single_merge()
 
875
        # f2 should be marked as modified by revisions A, C, and D
 
876
        # because D merged the changes from C.
 
877
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
878
 
 
879
    def test_file_id_f3(self):
 
880
        tree = self.create_tree_with_single_merge()
 
881
        # f3 should be marked as modified by revisions A, B, C, and D
 
882
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
883
 
 
884
 
 
885
class TestShowChangedRevisions(TestCaseWithTransport):
 
886
 
 
887
    def test_show_changed_revisions_verbose(self):
 
888
        tree = self.make_branch_and_tree('tree_a')
 
889
        self.build_tree(['tree_a/foo'])
 
890
        tree.add('foo')
 
891
        tree.commit('bar', rev_id='bar-id')
 
892
        s = self.make_utf8_encoded_stringio()
 
893
        log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
 
894
        self.assertContainsRe(s.getvalue(), 'bar')
 
895
        self.assertNotContainsRe(s.getvalue(), 'foo')
 
896
 
 
897
 
 
898
class TestLogFormatter(TestCase):
 
899
 
 
900
    def test_short_committer(self):
 
901
        rev = Revision('a-id')
 
902
        rev.committer = 'John Doe <jdoe@example.com>'
 
903
        lf = LogFormatter(None)
 
904
        self.assertEqual('John Doe', lf.short_committer(rev))
 
905
        rev.committer = 'John Smith <jsmith@example.com>'
 
906
        self.assertEqual('John Smith', lf.short_committer(rev))
 
907
        rev.committer = 'John Smith'
 
908
        self.assertEqual('John Smith', lf.short_committer(rev))
 
909
        rev.committer = 'jsmith@example.com'
 
910
        self.assertEqual('jsmith@example.com', lf.short_committer(rev))
 
911
        rev.committer = '<jsmith@example.com>'
 
912
        self.assertEqual('jsmith@example.com', lf.short_committer(rev))
 
913
        rev.committer = 'John Smith jsmith@example.com'
 
914
        self.assertEqual('John Smith', lf.short_committer(rev))
 
915
 
 
916
    def test_short_author(self):
 
917
        rev = Revision('a-id')
 
918
        rev.committer = 'John Doe <jdoe@example.com>'
 
919
        lf = LogFormatter(None)
 
920
        self.assertEqual('John Doe', lf.short_author(rev))
 
921
        rev.properties['author'] = 'John Smith <jsmith@example.com>'
 
922
        self.assertEqual('John Smith', lf.short_author(rev))
 
923
        rev.properties['author'] = 'John Smith'
 
924
        self.assertEqual('John Smith', lf.short_author(rev))
 
925
        rev.properties['author'] = 'jsmith@example.com'
 
926
        self.assertEqual('jsmith@example.com', lf.short_author(rev))
 
927
        rev.properties['author'] = '<jsmith@example.com>'
 
928
        self.assertEqual('jsmith@example.com', lf.short_author(rev))
 
929
        rev.properties['author'] = 'John Smith jsmith@example.com'
 
930
        self.assertEqual('John Smith', lf.short_author(rev))