~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

Compare URLs in RemoteRepository.__eq__, rather than '_client' attributes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
17
import os
18
18
from cStringIO import StringIO
19
19
 
20
 
from bzrlib.tests import BzrTestBase, TestCaseInTempDir
21
 
from bzrlib.log import LogFormatter, show_log, LongLogFormatter, ShortLogFormatter
 
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)
22
29
from bzrlib.branch import Branch
23
30
from bzrlib.errors import InvalidRevisionNumber
24
31
 
25
 
class _LogEntry(object):
26
 
    # should probably move into bzrlib.log?
27
 
    pass
28
 
 
29
32
 
30
33
class LogCatcher(LogFormatter):
31
34
    """Pull log messages into list rather than displaying them.
36
39
 
37
40
    We should also test the LogFormatter.
38
41
    """
 
42
 
 
43
    supports_delta = True
 
44
 
39
45
    def __init__(self):
40
46
        super(LogCatcher, self).__init__(to_file=None)
41
47
        self.logs = []
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):
 
48
 
 
49
    def log_revision(self, revision):
 
50
        self.logs.append(revision)
 
51
 
 
52
 
 
53
class TestShowLog(TestCaseWithTransport):
53
54
 
54
55
    def checkDelta(self, delta, **kw):
55
56
        """Check the filenames touched by a delta are as expected."""
56
57
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
57
58
            expected = kw.get(n, [])
58
 
 
59
 
            # tests are written with unix paths; fix them up for windows
60
 
            #if os.sep != '/':
61
 
            #    expected = [x.replace('/', os.sep) for x in expected]
62
 
 
63
59
            # strip out only the path components
64
60
            got = [x[0] for x in getattr(delta, n)]
65
61
            self.assertEquals(expected, got)
66
62
 
67
63
    def test_cur_revno(self):
68
 
        b = Branch(u'.', init=True)
69
 
 
70
 
        lf = LogCatcher()
71
 
        b.working_tree().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_cur_revno(self):
87
 
        b = Branch.initialize(u'.')
88
 
 
89
 
        lf = LogCatcher()
90
 
        b.working_tree().commit('empty commit')
 
64
        wt = self.make_branch_and_tree('.')
 
65
        b = wt.branch
 
66
 
 
67
        lf = LogCatcher()
 
68
        wt.commit('empty commit')
91
69
        show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
92
70
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
93
71
                          start_revision=2, end_revision=1) 
105
83
    def test_simple_log(self):
106
84
        eq = self.assertEquals
107
85
        
108
 
        b = Branch.initialize(u'.')
 
86
        wt = self.make_branch_and_tree('.')
 
87
        b = wt.branch
109
88
 
110
89
        lf = LogCatcher()
111
90
        show_log(b, lf)
112
91
        # no entries yet
113
92
        eq(lf.logs, [])
114
93
 
115
 
        b.working_tree().commit('empty commit')
 
94
        wt.commit('empty commit')
116
95
        lf = LogCatcher()
117
96
        show_log(b, lf, verbose=True)
118
97
        eq(len(lf.logs), 1)
119
 
        eq(lf.logs[0].revno, 1)
 
98
        eq(lf.logs[0].revno, '1')
120
99
        eq(lf.logs[0].rev.message, 'empty commit')
121
100
        d = lf.logs[0].delta
122
101
        self.log('log delta: %r' % d)
123
102
        self.checkDelta(d)
124
103
 
125
104
        self.build_tree(['hello'])
126
 
        b.working_tree().add('hello')
127
 
        b.working_tree().commit('add one file')
 
105
        wt.add('hello')
 
106
        wt.commit('add one file')
128
107
 
129
108
        lf = StringIO()
130
109
        # log using regular thing
139
118
        eq(len(lf.logs), 2)
140
119
        self.log('log entries:')
141
120
        for logentry in lf.logs:
142
 
            self.log('%4d %s' % (logentry.revno, logentry.rev.message))
 
121
            self.log('%4s %s' % (logentry.revno, logentry.rev.message))
143
122
        
144
123
        # first one is most recent
145
124
        logentry = lf.logs[0]
146
 
        eq(logentry.revno, 2)
 
125
        eq(logentry.revno, '2')
147
126
        eq(logentry.rev.message, 'add one file')
148
127
        d = logentry.delta
149
128
        self.log('log 2 delta: %r' % d)
150
 
        # self.checkDelta(d, added=['hello'])
 
129
        self.checkDelta(d, added=['hello'])
151
130
        
152
131
        # commit a log message with control characters
153
132
        msg = "All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
154
133
        self.log("original commit message: %r", msg)
155
 
        b.working_tree().commit(msg)
 
134
        wt.commit(msg)
156
135
        lf = LogCatcher()
157
136
        show_log(b, lf, verbose=True)
158
137
        committed_msg = lf.logs[0].rev.message
167
146
        # valid XML 1.0 characters.
168
147
        msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
169
148
        self.log("original commit message: %r", msg)
170
 
        b.working_tree().commit(msg)
 
149
        wt.commit(msg)
171
150
        lf = LogCatcher()
172
151
        show_log(b, lf, verbose=True)
173
152
        committed_msg = lf.logs[0].rev.message
174
153
        self.log("escaped commit message: %r", committed_msg)
175
154
        self.assert_(msg == committed_msg)
176
155
 
 
156
    def test_deltas_in_merge_revisions(self):
 
157
        """Check deltas created for both mainline and merge revisions"""
 
158
        eq = self.assertEquals
 
159
        wt = self.make_branch_and_tree('parent')
 
160
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
 
161
        wt.add('file1')
 
162
        wt.add('file2')
 
163
        wt.commit(message='add file1 and file2')
 
164
        self.run_bzr('branch parent child')
 
165
        os.unlink('child/file1')
 
166
        print >> file('child/file2', 'wb'), 'hello'
 
167
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
 
168
            'child'])
 
169
        os.chdir('parent')
 
170
        self.run_bzr('merge ../child')
 
171
        wt.commit('merge child branch')
 
172
        os.chdir('..')
 
173
        b = wt.branch
 
174
        lf = LogCatcher()
 
175
        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'])
 
193
 
 
194
 
 
195
def make_commits_with_trailing_newlines(wt):
 
196
    """Helper method for LogFormatter tests"""    
 
197
    b = wt.branch
 
198
    b.nick='test'
 
199
    open('a', 'wb').write('hello moto\n')
 
200
    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>')
 
204
    open('b', 'wb').write('goodbye\n')
 
205
    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>')
 
209
 
 
210
    open('c', 'wb').write('just another manic monday\n')
 
211
    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>')
 
215
    return b
 
216
 
 
217
 
 
218
class TestShortLogFormatter(TestCaseWithTransport):
 
219
 
177
220
    def test_trailing_newlines(self):
178
 
        b = Branch.initialize(u'.')
179
 
        b.nick='test'
180
 
        wt = b.working_tree()
181
 
        open('a', 'wb').write('hello moto\n')
182
 
        wt.add('a')
183
 
        wt.commit('simple log message', rev_id='a1'
184
 
                , timestamp=1132586655.459960938, timezone=-6*3600
185
 
                , committer='Joe Foo <joe@foo.com>')
186
 
        open('b', 'wb').write('goodbye\n')
187
 
        wt.add('b')
188
 
        wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
189
 
                , timestamp=1132586842.411175966, timezone=-6*3600
190
 
                , committer='Joe Foo <joe@foo.com>')
191
 
 
192
 
        open('c', 'wb').write('just another manic monday\n')
193
 
        wt.add('c')
194
 
        wt.commit('single line with trailing newline\n', rev_id='a3'
195
 
                , timestamp=1132587176.835228920, timezone=-6*3600
196
 
                , committer = 'Joe Foo <joe@foo.com>')
197
 
 
 
221
        wt = self.make_branch_and_tree('.')
 
222
        b = make_commits_with_trailing_newlines(wt)
198
223
        sio = StringIO()
199
224
        lf = ShortLogFormatter(to_file=sio)
200
225
        show_log(b, lf)
212
237
 
213
238
""")
214
239
 
215
 
        sio = StringIO()
216
 
        lf = LongLogFormatter(to_file=sio)
217
 
        show_log(b, lf)
218
 
        self.assertEquals(sio.getvalue(), """\
219
 
------------------------------------------------------------
220
 
revno: 3
221
 
committer: Joe Foo <joe@foo.com>
222
 
branch nick: test
223
 
timestamp: Mon 2005-11-21 09:32:56 -0600
224
 
message:
225
 
  single line with trailing newline
226
 
------------------------------------------------------------
227
 
revno: 2
228
 
committer: Joe Foo <joe@foo.com>
229
 
branch nick: test
230
 
timestamp: Mon 2005-11-21 09:27:22 -0600
231
 
message:
232
 
  multiline
233
 
  log
234
 
  message
235
 
------------------------------------------------------------
236
 
revno: 1
237
 
committer: Joe Foo <joe@foo.com>
238
 
branch nick: test
239
 
timestamp: Mon 2005-11-21 09:24:15 -0600
240
 
message:
241
 
  simple log message
242
 
""")
243
 
        
 
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)
 
255
 
244
256
    def test_verbose_log(self):
245
257
        """Verbose log includes changed files
246
258
        
247
259
        bug #4676
248
260
        """
249
 
        b = Branch.initialize(u'.')
 
261
        wt = self.make_branch_and_tree('.')
 
262
        b = wt.branch
250
263
        self.build_tree(['a'])
251
 
        wt = b.working_tree()
252
264
        wt.add('a')
253
265
        # XXX: why does a longer nick show up?
254
266
        b.nick = 'test_verbose_log'
273
285
added:
274
286
  a
275
287
''')
 
288
 
 
289
    def test_merges_are_indented_by_level(self):
 
290
        wt = self.make_branch_and_tree('parent')
 
291
        wt.commit('first post')
 
292
        self.run_bzr('branch parent child')
 
293
        self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
 
294
        self.run_bzr('branch child smallerchild')
 
295
        self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
 
296
            'smallerchild'])
 
297
        os.chdir('child')
 
298
        self.run_bzr('merge ../smallerchild')
 
299
        self.run_bzr(['commit', '-m', 'merge branch 2'])
 
300
        os.chdir('../parent')
 
301
        self.run_bzr('merge ../child')
 
302
        wt.commit('merge branch 1')
 
303
        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())
 
308
        self.assertEqualDiff("""\
 
309
------------------------------------------------------------
 
310
revno: 2
 
311
committer: Lorem Ipsum <test@example.com>
 
312
branch nick: parent
 
313
timestamp: Just now
 
314
message:
 
315
  merge branch 1
 
316
    ------------------------------------------------------------
 
317
    revno: 1.1.2
 
318
    committer: Lorem Ipsum <test@example.com>
 
319
    branch nick: child
 
320
    timestamp: Just now
 
321
    message:
 
322
      merge branch 2
 
323
        ------------------------------------------------------------
 
324
        revno: 1.1.1.1.1
 
325
        committer: Lorem Ipsum <test@example.com>
 
326
        branch nick: smallerchild
 
327
        timestamp: Just now
 
328
        message:
 
329
          branch 2
 
330
    ------------------------------------------------------------
 
331
    revno: 1.1.1
 
332
    committer: Lorem Ipsum <test@example.com>
 
333
    branch nick: child
 
334
    timestamp: Just now
 
335
    message:
 
336
      branch 1
 
337
------------------------------------------------------------
 
338
revno: 1
 
339
committer: Lorem Ipsum <test@example.com>
 
340
branch nick: parent
 
341
timestamp: Just now
 
342
message:
 
343
  first post
 
344
""", log)
 
345
 
 
346
    def test_verbose_merge_revisions_contain_deltas(self):
 
347
        wt = self.make_branch_and_tree('parent')
 
348
        self.build_tree(['parent/f1', 'parent/f2'])
 
349
        wt.add(['f1','f2'])
 
350
        wt.commit('first post')
 
351
        self.run_bzr('branch parent child')
 
352
        os.unlink('child/f1')
 
353
        print >> file('child/f2', 'wb'), 'hello'
 
354
        self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
 
355
            'child'])
 
356
        os.chdir('parent')
 
357
        self.run_bzr('merge ../child')
 
358
        wt.commit('merge branch 1')
 
359
        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())
 
364
        self.assertEqualDiff("""\
 
365
------------------------------------------------------------
 
366
revno: 2
 
367
committer: Lorem Ipsum <test@example.com>
 
368
branch nick: parent
 
369
timestamp: Just now
 
370
message:
 
371
  merge branch 1
 
372
removed:
 
373
  f1
 
374
modified:
 
375
  f2
 
376
    ------------------------------------------------------------
 
377
    revno: 1.1.1
 
378
    committer: Lorem Ipsum <test@example.com>
 
379
    branch nick: child
 
380
    timestamp: Just now
 
381
    message:
 
382
      removed f1 and modified f2
 
383
    removed:
 
384
      f1
 
385
    modified:
 
386
      f2
 
387
------------------------------------------------------------
 
388
revno: 1
 
389
committer: Lorem Ipsum <test@example.com>
 
390
branch nick: parent
 
391
timestamp: Just now
 
392
message:
 
393
  first post
 
394
added:
 
395
  f1
 
396
  f2
 
397
""", log)
 
398
 
 
399
    def test_trailing_newlines(self):
 
400
        wt = self.make_branch_and_tree('.')
 
401
        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):
 
433
 
 
434
    def test_line_log(self):
 
435
        """Line log should show revno
 
436
        
 
437
        bug #5162
 
438
        """
 
439
        wt = self.make_branch_and_tree('.')
 
440
        b = wt.branch
 
441
        self.build_tree(['a'])
 
442
        wt.add('a')
 
443
        b.nick = 'test-line-log'
 
444
        wt.commit(message='add a', 
 
445
                  timestamp=1132711707, 
 
446
                  timezone=36000,
 
447
                  committer='Line-Log-Formatter Tester <test@line.log>')
 
448
        logfile = file('out.tmp', 'w+')
 
449
        formatter = LineLogFormatter(to_file=logfile)
 
450
        show_log(b, formatter)
 
451
        logfile.flush()
 
452
        logfile.seek(0)
 
453
        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()
 
486
 
 
487
    def test_trailing_newlines(self):
 
488
        wt = self.make_branch_and_tree('.')
 
489
        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(), """\
 
494
3: Joe Foo 2005-11-21 single line with trailing newline
 
495
2: Joe Foo 2005-11-21 multiline
 
496
1: Joe Foo 2005-11-21 simple log message
 
497
""")
 
498
 
 
499
 
 
500
class TestGetViewRevisions(TestCaseWithTransport):
 
501
 
 
502
    def make_tree_with_commits(self):
 
503
        """Create a tree with well-known revision ids"""
 
504
        wt = self.make_branch_and_tree('tree1')
 
505
        wt.commit('commit one', rev_id='1')
 
506
        wt.commit('commit two', rev_id='2')
 
507
        wt.commit('commit three', rev_id='3')
 
508
        mainline_revs = [None, '1', '2', '3']
 
509
        rev_nos = {'1': 1, '2': 2, '3': 3}
 
510
        return mainline_revs, rev_nos, wt
 
511
 
 
512
    def make_tree_with_merges(self):
 
513
        """Create a tree with well-known revision ids and a merge"""
 
514
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
515
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
516
        tree2.commit('four-a', rev_id='4a')
 
517
        wt.merge_from_branch(tree2.branch)
 
518
        wt.commit('four-b', rev_id='4b')
 
519
        mainline_revs.append('4b')
 
520
        rev_nos['4b'] = 4
 
521
        # 4a: 3.1.1
 
522
        return mainline_revs, rev_nos, wt
 
523
 
 
524
    def make_tree_with_many_merges(self):
 
525
        """Create a tree with well-known revision ids"""
 
526
        wt = self.make_branch_and_tree('tree1')
 
527
        wt.commit('commit one', rev_id='1')
 
528
        wt.commit('commit two', rev_id='2')
 
529
        tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
 
530
        tree3.commit('commit three a', rev_id='3a')
 
531
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
532
        tree2.merge_from_branch(tree3.branch)
 
533
        tree2.commit('commit three b', rev_id='3b')
 
534
        wt.merge_from_branch(tree2.branch)
 
535
        wt.commit('commit three c', rev_id='3c')
 
536
        tree2.commit('four-a', rev_id='4a')
 
537
        wt.merge_from_branch(tree2.branch)
 
538
        wt.commit('four-b', rev_id='4b')
 
539
        mainline_revs = [None, '1', '2', '3c', '4b']
 
540
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
 
541
        full_rev_nos_for_reference = {
 
542
            '1': '1',
 
543
            '2': '2',
 
544
            '3a': '2.2.1', #first commit tree 3
 
545
            '3b': '2.1.1', # first commit tree 2
 
546
            '3c': '3', #merges 3b to main
 
547
            '4a': '2.1.2', # second commit tree 2
 
548
            '4b': '4', # merges 4a to main
 
549
            }
 
550
        return mainline_revs, rev_nos, wt
 
551
 
 
552
    def test_get_view_revisions_forward(self):
 
553
        """Test the get_view_revisions method"""
 
554
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
555
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
556
                                            'forward'))
 
557
        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))
 
561
        self.assertEqual(revisions, revisions2)
 
562
 
 
563
    def test_get_view_revisions_reverse(self):
 
564
        """Test the get_view_revisions with reverse"""
 
565
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
566
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
567
                                            'reverse'))
 
568
        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))
 
572
        self.assertEqual(revisions, revisions2)
 
573
 
 
574
    def test_get_view_revisions_merge(self):
 
575
        """Test get_view_revisions when there are merges"""
 
576
        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)
 
587
 
 
588
    def test_get_view_revisions_merge_reverse(self):
 
589
        """Test get_view_revisions in reverse when there are merges"""
 
590
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
591
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
592
                                            'reverse'))
 
593
        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))
 
598
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
 
599
            ('1', '1', 0)],
 
600
            revisions)
 
601
 
 
602
    def test_get_view_revisions_merge2(self):
 
603
        """Test get_view_revisions when there are merges"""
 
604
        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'))
 
607
        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)]
 
610
        self.assertEqual(expected, revisions)
 
611
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
612
                                             'forward', include_merges=False))
 
613
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
614
            ('4b', '4', 0)],
 
615
            revisions)
 
616
 
 
617
 
 
618
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
 
619
 
 
620
    def create_tree_with_single_merge(self):
 
621
        """Create a branch with a moderate layout.
 
622
 
 
623
        The revision graph looks like:
 
624
 
 
625
           A
 
626
           |\
 
627
           B C
 
628
           |/
 
629
           D
 
630
 
 
631
        In this graph, A introduced files f1 and f2 and f3.
 
632
        B modifies f1 and f3, and C modifies f2 and f3.
 
633
        D merges the changes from B and C and resolves the conflict for f3.
 
634
        """
 
635
        # TODO: jam 20070218 This seems like it could really be done
 
636
        #       with make_branch_and_memory_tree() if we could just
 
637
        #       create the content of those files.
 
638
        # TODO: jam 20070218 Another alternative is that we would really
 
639
        #       like to only create this tree 1 time for all tests that
 
640
        #       use it. Since 'log' only uses the tree in a readonly
 
641
        #       fashion, it seems a shame to regenerate an identical
 
642
        #       tree for each test.
 
643
        tree = self.make_branch_and_tree('tree')
 
644
        tree.lock_write()
 
645
        self.addCleanup(tree.unlock)
 
646
 
 
647
        self.build_tree_contents([('tree/f1', 'A\n'),
 
648
                                  ('tree/f2', 'A\n'),
 
649
                                  ('tree/f3', 'A\n'),
 
650
                                 ])
 
651
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
 
652
        tree.commit('A', rev_id='A')
 
653
 
 
654
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
 
655
                                  ('tree/f3', 'A\nC\n'),
 
656
                                 ])
 
657
        tree.commit('C', rev_id='C')
 
658
        # Revert back to A to build the other history.
 
659
        tree.set_last_revision('A')
 
660
        tree.branch.set_last_revision_info(1, 'A')
 
661
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
662
                                  ('tree/f2', 'A\n'),
 
663
                                  ('tree/f3', 'A\nB\n'),
 
664
                                 ])
 
665
        tree.commit('B', rev_id='B')
 
666
        tree.set_parent_ids(['B', 'C'])
 
667
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
668
                                  ('tree/f2', 'A\nC\n'),
 
669
                                  ('tree/f3', 'A\nB\nC\n'),
 
670
                                 ])
 
671
        tree.commit('D', rev_id='D')
 
672
 
 
673
        # Switch to a read lock for this tree.
 
674
        # We still have addCleanup(unlock)
 
675
        tree.unlock()
 
676
        tree.lock_read()
 
677
        return tree
 
678
 
 
679
    def test_tree_with_single_merge(self):
 
680
        """Make sure the tree layout is correct."""
 
681
        tree = self.create_tree_with_single_merge()
 
682
        rev_A_tree = tree.branch.repository.revision_tree('A')
 
683
        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
        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
        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)
 
714
 
 
715
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
 
716
        """Make sure _filter_revisions_touching_file_id returns the right values.
 
717
 
 
718
        Get the return value from _filter_revisions_touching_file_id and make
 
719
        sure they are correct.
 
720
        """
 
721
        # The api for _get_revisions_touching_file_id is a little crazy,
 
722
        # So we do the setup here.
 
723
        mainline = tree.branch.revision_history()
 
724
        mainline.insert(0, None)
 
725
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
 
726
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
 
727
                                                'reverse', True)
 
728
        actual_revs = log._filter_revisions_touching_file_id(
 
729
                            tree.branch, 
 
730
                            file_id,
 
731
                            mainline,
 
732
                            list(view_revs_iter))
 
733
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
 
734
 
 
735
    def test_file_id_f1(self):
 
736
        tree = self.create_tree_with_single_merge()
 
737
        # f1 should be marked as modified by revisions A and B
 
738
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
 
739
 
 
740
    def test_file_id_f2(self):
 
741
        tree = self.create_tree_with_single_merge()
 
742
        # f2 should be marked as modified by revisions A, C, and D
 
743
        # because D merged the changes from C.
 
744
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
745
 
 
746
    def test_file_id_f3(self):
 
747
        tree = self.create_tree_with_single_merge()
 
748
        # f3 should be marked as modified by revisions A, B, C, and D
 
749
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])