~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/testlog.py

  • Committer: John Arbash Meinel
  • Date: 2005-09-15 21:35:53 UTC
  • mfrom: (907.1.57)
  • mto: (1393.2.1)
  • mto: This revision was merged to the branch mainline in revision 1396.
  • Revision ID: john@arbash-meinel.com-20050915213552-a6c83a5ef1e20897
(broken) Transport work is merged in. Tests do not pass yet.

Show diffs side-by-side

added added

removed removed

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