~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Vincent Ladeuil
  • Date: 2009-06-22 14:32:48 UTC
  • mto: (4471.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 4472.
  • Revision ID: v.ladeuil+lp@free.fr-20090622143248-pe4av866hxgzn60e
Use the same method or function names for _dirstate_helpers in pyrex and
python modules.

Show diffs side-by-side

added added

removed removed

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