~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_inv.py

Late bind to PatienceSequenceMatcher in merge3.py

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 by Canonical Ltd
 
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
 
17
from cStringIO import StringIO
 
18
import os
17
19
 
18
 
from bzrlib import errors, inventory, osutils
 
20
from bzrlib.branch import Branch
 
21
import bzrlib.errors as errors
 
22
from bzrlib.diff import internal_diff
19
23
from bzrlib.inventory import (Inventory, ROOT_ID, InventoryFile,
20
 
    InventoryDirectory, InventoryEntry, TreeReference)
21
 
from bzrlib.tests import TestCase
 
24
    InventoryDirectory, InventoryEntry)
 
25
import bzrlib.inventory as inventory
 
26
from bzrlib.osutils import has_symlinks, rename, pathjoin
 
27
from bzrlib.tests import TestCase, TestCaseWithTransport
 
28
from bzrlib.transform import TreeTransform
 
29
from bzrlib.uncommit import uncommit
 
30
 
 
31
 
 
32
class TestInventory(TestCase):
 
33
 
 
34
    def test_is_within(self):
 
35
        from bzrlib.osutils import is_inside_any
 
36
 
 
37
        SRC_FOO_C = pathjoin('src', 'foo.c')
 
38
        for dirs, fn in [(['src', 'doc'], SRC_FOO_C),
 
39
                         (['src'], SRC_FOO_C),
 
40
                         (['src'], 'src'),
 
41
                         ]:
 
42
            self.assert_(is_inside_any(dirs, fn))
 
43
            
 
44
        for dirs, fn in [(['src'], 'srccontrol'),
 
45
                         (['src'], 'srccontrol/foo')]:
 
46
            self.assertFalse(is_inside_any(dirs, fn))
 
47
            
 
48
    def test_ids(self):
 
49
        """Test detection of files within selected directories."""
 
50
        inv = Inventory()
 
51
        
 
52
        for args in [('src', 'directory', 'src-id'), 
 
53
                     ('doc', 'directory', 'doc-id'), 
 
54
                     ('src/hello.c', 'file'),
 
55
                     ('src/bye.c', 'file', 'bye-id'),
 
56
                     ('Makefile', 'file')]:
 
57
            inv.add_path(*args)
 
58
            
 
59
        self.assertEqual(inv.path2id('src'), 'src-id')
 
60
        self.assertEqual(inv.path2id('src/bye.c'), 'bye-id')
 
61
        
 
62
        self.assert_('src-id' in inv)
 
63
 
 
64
    def test_version(self):
 
65
        """Inventory remembers the text's version."""
 
66
        inv = Inventory()
 
67
        ie = inv.add_path('foo.txt', 'file')
 
68
        ## XXX
22
69
 
23
70
 
24
71
class TestInventoryEntry(TestCase):
94
141
        self.assertIsInstance(inventory.make_entry("directory", "name", ROOT_ID),
95
142
            inventory.InventoryDirectory)
96
143
 
97
 
    def test_make_entry_non_normalized(self):
98
 
        orig_normalized_filename = osutils.normalized_filename
99
 
 
100
 
        try:
101
 
            osutils.normalized_filename = osutils._accessible_normalized_filename
102
 
            entry = inventory.make_entry("file", u'a\u030a', ROOT_ID)
103
 
            self.assertEqual(u'\xe5', entry.name)
104
 
            self.assertIsInstance(entry, inventory.InventoryFile)
105
 
 
106
 
            osutils.normalized_filename = osutils._inaccessible_normalized_filename
107
 
            self.assertRaises(errors.InvalidNormalization,
108
 
                    inventory.make_entry, 'file', u'a\u030a', ROOT_ID)
109
 
        finally:
110
 
            osutils.normalized_filename = orig_normalized_filename
 
144
class TestEntryDiffing(TestCaseWithTransport):
 
145
 
 
146
    def setUp(self):
 
147
        super(TestEntryDiffing, self).setUp()
 
148
        self.wt = self.make_branch_and_tree('.')
 
149
        self.branch = self.wt.branch
 
150
        print >> open('file', 'wb'), 'foo'
 
151
        print >> open('binfile', 'wb'), 'foo'
 
152
        self.wt.add(['file'], ['fileid'])
 
153
        self.wt.add(['binfile'], ['binfileid'])
 
154
        if has_symlinks():
 
155
            os.symlink('target1', 'symlink')
 
156
            self.wt.add(['symlink'], ['linkid'])
 
157
        self.wt.commit('message_1', rev_id = '1')
 
158
        print >> open('file', 'wb'), 'bar'
 
159
        print >> open('binfile', 'wb'), 'x' * 1023 + '\x00'
 
160
        if has_symlinks():
 
161
            os.unlink('symlink')
 
162
            os.symlink('target2', 'symlink')
 
163
        self.tree_1 = self.branch.repository.revision_tree('1')
 
164
        self.inv_1 = self.branch.repository.get_inventory('1')
 
165
        self.file_1 = self.inv_1['fileid']
 
166
        self.file_1b = self.inv_1['binfileid']
 
167
        self.tree_2 = self.wt
 
168
        self.inv_2 = self.tree_2.read_working_inventory()
 
169
        self.file_2 = self.inv_2['fileid']
 
170
        self.file_2b = self.inv_2['binfileid']
 
171
        if has_symlinks():
 
172
            self.link_1 = self.inv_1['linkid']
 
173
            self.link_2 = self.inv_2['linkid']
 
174
 
 
175
    def test_file_diff_deleted(self):
 
176
        output = StringIO()
 
177
        self.file_1.diff(internal_diff, 
 
178
                          "old_label", self.tree_1,
 
179
                          "/dev/null", None, None,
 
180
                          output)
 
181
        self.assertEqual(output.getvalue(), "--- old_label\t\n"
 
182
                                            "+++ /dev/null\t\n"
 
183
                                            "@@ -1,1 +0,0 @@\n"
 
184
                                            "-foo\n"
 
185
                                            "\n")
 
186
 
 
187
    def test_file_diff_added(self):
 
188
        output = StringIO()
 
189
        self.file_1.diff(internal_diff, 
 
190
                          "new_label", self.tree_1,
 
191
                          "/dev/null", None, None,
 
192
                          output, reverse=True)
 
193
        self.assertEqual(output.getvalue(), "--- /dev/null\t\n"
 
194
                                            "+++ new_label\t\n"
 
195
                                            "@@ -0,0 +1,1 @@\n"
 
196
                                            "+foo\n"
 
197
                                            "\n")
 
198
 
 
199
    def test_file_diff_changed(self):
 
200
        output = StringIO()
 
201
        self.file_1.diff(internal_diff, 
 
202
                          "/dev/null", self.tree_1, 
 
203
                          "new_label", self.file_2, self.tree_2,
 
204
                          output)
 
205
        self.assertEqual(output.getvalue(), "--- /dev/null\t\n"
 
206
                                            "+++ new_label\t\n"
 
207
                                            "@@ -1,1 +1,1 @@\n"
 
208
                                            "-foo\n"
 
209
                                            "+bar\n"
 
210
                                            "\n")
 
211
        
 
212
    def test_file_diff_binary(self):
 
213
        output = StringIO()
 
214
        self.file_1.diff(internal_diff, 
 
215
                          "/dev/null", self.tree_1, 
 
216
                          "new_label", self.file_2b, self.tree_2,
 
217
                          output)
 
218
        self.assertEqual(output.getvalue(), 
 
219
                         "Binary files /dev/null and new_label differ\n")
 
220
    def test_link_diff_deleted(self):
 
221
        if not has_symlinks():
 
222
            return
 
223
        output = StringIO()
 
224
        self.link_1.diff(internal_diff, 
 
225
                          "old_label", self.tree_1,
 
226
                          "/dev/null", None, None,
 
227
                          output)
 
228
        self.assertEqual(output.getvalue(),
 
229
                         "=== target was 'target1'\n")
 
230
 
 
231
    def test_link_diff_added(self):
 
232
        if not has_symlinks():
 
233
            return
 
234
        output = StringIO()
 
235
        self.link_1.diff(internal_diff, 
 
236
                          "new_label", self.tree_1,
 
237
                          "/dev/null", None, None,
 
238
                          output, reverse=True)
 
239
        self.assertEqual(output.getvalue(),
 
240
                         "=== target is 'target1'\n")
 
241
 
 
242
    def test_link_diff_changed(self):
 
243
        if not has_symlinks():
 
244
            return
 
245
        output = StringIO()
 
246
        self.link_1.diff(internal_diff, 
 
247
                          "/dev/null", self.tree_1, 
 
248
                          "new_label", self.link_2, self.tree_2,
 
249
                          output)
 
250
        self.assertEqual(output.getvalue(),
 
251
                         "=== target changed 'target1' => 'target2'\n")
 
252
 
 
253
 
 
254
class TestSnapshot(TestCaseWithTransport):
 
255
 
 
256
    def setUp(self):
 
257
        # for full testing we'll need a branch
 
258
        # with a subdir to test parent changes.
 
259
        # and a file, link and dir under that.
 
260
        # but right now I only need one attribute
 
261
        # to change, and then test merge patterns
 
262
        # with fake parent entries.
 
263
        super(TestSnapshot, self).setUp()
 
264
        self.wt = self.make_branch_and_tree('.')
 
265
        self.branch = self.wt.branch
 
266
        self.build_tree(['subdir/', 'subdir/file'], line_endings='binary')
 
267
        self.wt.add(['subdir', 'subdir/file'],
 
268
                                       ['dirid', 'fileid'])
 
269
        if has_symlinks():
 
270
            pass
 
271
        self.wt.commit('message_1', rev_id = '1')
 
272
        self.tree_1 = self.branch.repository.revision_tree('1')
 
273
        self.inv_1 = self.branch.repository.get_inventory('1')
 
274
        self.file_1 = self.inv_1['fileid']
 
275
        self.file_active = self.wt.inventory['fileid']
 
276
 
 
277
    def test_snapshot_new_revision(self):
 
278
        # This tests that a simple commit with no parents makes a new
 
279
        # revision value in the inventory entry
 
280
        self.file_active.snapshot('2', 'subdir/file', {}, self.wt, 
 
281
                                  self.branch.repository.weave_store,
 
282
                                  self.branch.get_transaction())
 
283
        # expected outcome - file_1 has a revision id of '2', and we can get
 
284
        # its text of 'file contents' out of the weave.
 
285
        self.assertEqual(self.file_1.revision, '1')
 
286
        self.assertEqual(self.file_active.revision, '2')
 
287
        # this should be a separate test probably, but lets check it once..
 
288
        lines = self.branch.repository.weave_store.get_weave(
 
289
            'fileid', 
 
290
            self.branch.get_transaction()).get_lines('2')
 
291
        self.assertEqual(lines, ['contents of subdir/file\n'])
 
292
 
 
293
    def test_snapshot_unchanged(self):
 
294
        #This tests that a simple commit does not make a new entry for
 
295
        # an unchanged inventory entry
 
296
        self.file_active.snapshot('2', 'subdir/file', {'1':self.file_1},
 
297
                                  self.wt, 
 
298
                                  self.branch.repository.weave_store,
 
299
                                  self.branch.get_transaction())
 
300
        self.assertEqual(self.file_1.revision, '1')
 
301
        self.assertEqual(self.file_active.revision, '1')
 
302
        vf = self.branch.repository.weave_store.get_weave(
 
303
            'fileid', 
 
304
            self.branch.repository.get_transaction())
 
305
        self.assertRaises(errors.RevisionNotPresent,
 
306
                          vf.get_lines,
 
307
                          '2')
 
308
 
 
309
    def test_snapshot_merge_identical_different_revid(self):
 
310
        # This tests that a commit with two identical parents, one of which has
 
311
        # a different revision id, results in a new revision id in the entry.
 
312
        # 1->other, commit a merge of other against 1, results in 2.
 
313
        other_ie = inventory.InventoryFile('fileid', 'newname', self.file_1.parent_id)
 
314
        other_ie = inventory.InventoryFile('fileid', 'file', self.file_1.parent_id)
 
315
        other_ie.revision = '1'
 
316
        other_ie.text_sha1 = self.file_1.text_sha1
 
317
        other_ie.text_size = self.file_1.text_size
 
318
        self.assertEqual(self.file_1, other_ie)
 
319
        other_ie.revision = 'other'
 
320
        self.assertNotEqual(self.file_1, other_ie)
 
321
        versionfile = self.branch.repository.weave_store.get_weave(
 
322
            'fileid', self.branch.repository.get_transaction())
 
323
        versionfile.clone_text('other', '1', ['1'])
 
324
        self.file_active.snapshot('2', 'subdir/file', 
 
325
                                  {'1':self.file_1, 'other':other_ie},
 
326
                                  self.wt, 
 
327
                                  self.branch.repository.weave_store,
 
328
                                  self.branch.get_transaction())
 
329
        self.assertEqual(self.file_active.revision, '2')
 
330
 
 
331
    def test_snapshot_changed(self):
 
332
        # This tests that a commit with one different parent results in a new
 
333
        # revision id in the entry.
 
334
        self.file_active.name='newname'
 
335
        rename('subdir/file', 'subdir/newname')
 
336
        self.file_active.snapshot('2', 'subdir/newname', {'1':self.file_1}, 
 
337
                                  self.wt,
 
338
                                  self.branch.repository.weave_store,
 
339
                                  self.branch.get_transaction())
 
340
        # expected outcome - file_1 has a revision id of '2'
 
341
        self.assertEqual(self.file_active.revision, '2')
 
342
 
 
343
 
 
344
class TestPreviousHeads(TestCaseWithTransport):
 
345
 
 
346
    def setUp(self):
 
347
        # we want several inventories, that respectively
 
348
        # give use the following scenarios:
 
349
        # A) fileid not in any inventory (A),
 
350
        # B) fileid present in one inventory (B) and (A,B)
 
351
        # C) fileid present in two inventories, and they
 
352
        #   are not mutual descendents (B, C)
 
353
        # D) fileid present in two inventories and one is
 
354
        #   a descendent of the other. (B, D)
 
355
        super(TestPreviousHeads, self).setUp()
 
356
        self.wt = self.make_branch_and_tree('.')
 
357
        self.branch = self.wt.branch
 
358
        self.build_tree(['file'])
 
359
        self.wt.commit('new branch', allow_pointless=True, rev_id='A')
 
360
        self.inv_A = self.branch.repository.get_inventory('A')
 
361
        self.wt.add(['file'], ['fileid'])
 
362
        self.wt.commit('add file', rev_id='B')
 
363
        self.inv_B = self.branch.repository.get_inventory('B')
 
364
        uncommit(self.branch, tree=self.wt)
 
365
        self.assertEqual(self.branch.revision_history(), ['A'])
 
366
        self.wt.commit('another add of file', rev_id='C')
 
367
        self.inv_C = self.branch.repository.get_inventory('C')
 
368
        self.wt.add_pending_merge('B')
 
369
        self.wt.commit('merge in B', rev_id='D')
 
370
        self.inv_D = self.branch.repository.get_inventory('D')
 
371
        self.file_active = self.wt.inventory['fileid']
 
372
        self.weave = self.branch.repository.weave_store.get_weave('fileid',
 
373
            self.branch.repository.get_transaction())
 
374
        
 
375
    def get_previous_heads(self, inventories):
 
376
        return self.file_active.find_previous_heads(
 
377
            inventories, 
 
378
            self.branch.repository.weave_store,
 
379
            self.branch.repository.get_transaction())
 
380
        
 
381
    def test_fileid_in_no_inventory(self):
 
382
        self.assertEqual({}, self.get_previous_heads([self.inv_A]))
 
383
 
 
384
    def test_fileid_in_one_inventory(self):
 
385
        self.assertEqual({'B':self.inv_B['fileid']},
 
386
                         self.get_previous_heads([self.inv_B]))
 
387
        self.assertEqual({'B':self.inv_B['fileid']},
 
388
                         self.get_previous_heads([self.inv_A, self.inv_B]))
 
389
        self.assertEqual({'B':self.inv_B['fileid']},
 
390
                         self.get_previous_heads([self.inv_B, self.inv_A]))
 
391
 
 
392
    def test_fileid_in_two_inventories_gives_both_entries(self):
 
393
        self.assertEqual({'B':self.inv_B['fileid'],
 
394
                          'C':self.inv_C['fileid']},
 
395
                          self.get_previous_heads([self.inv_B, self.inv_C]))
 
396
        self.assertEqual({'B':self.inv_B['fileid'],
 
397
                          'C':self.inv_C['fileid']},
 
398
                          self.get_previous_heads([self.inv_C, self.inv_B]))
 
399
 
 
400
    def test_fileid_in_two_inventories_already_merged_gives_head(self):
 
401
        self.assertEqual({'D':self.inv_D['fileid']},
 
402
                         self.get_previous_heads([self.inv_B, self.inv_D]))
 
403
        self.assertEqual({'D':self.inv_D['fileid']},
 
404
                         self.get_previous_heads([self.inv_D, self.inv_B]))
 
405
 
 
406
    # TODO: test two inventories with the same file revision 
111
407
 
112
408
 
113
409
class TestDescribeChanges(TestCase):
166
462
    def assertChangeDescription(self, expected_change, old_ie, new_ie):
167
463
        change = InventoryEntry.describe_change(old_ie, new_ie)
168
464
        self.assertEqual(expected_change, change)
 
465
 
 
466
 
 
467
class TestExecutable(TestCaseWithTransport):
 
468
 
 
469
    def test_stays_executable(self):
 
470
        a_id = "a-20051208024829-849e76f7968d7a86"
 
471
        b_id = "b-20051208024829-849e76f7968d7a86"
 
472
        wt = self.make_branch_and_tree('b1')
 
473
        b = wt.branch
 
474
        tt = TreeTransform(wt)
 
475
        tt.new_file('a', tt.root, 'a test\n', a_id, True)
 
476
        tt.new_file('b', tt.root, 'b test\n', b_id, False)
 
477
        tt.apply()
 
478
 
 
479
        self.failUnless(wt.is_executable(a_id), "'a' lost the execute bit")
 
480
 
 
481
        # reopen the tree and ensure it stuck.
 
482
        wt = wt.bzrdir.open_workingtree()
 
483
        self.assertEqual(['a', 'b'], [cn for cn,ie in wt.inventory.iter_entries()])
 
484
 
 
485
        self.failUnless(wt.is_executable(a_id), "'a' lost the execute bit")
 
486
        self.failIf(wt.is_executable(b_id), "'b' gained an execute bit")
 
487
 
 
488
        wt.commit('adding a,b', rev_id='r1')
 
489
 
 
490
        rev_tree = b.repository.revision_tree('r1')
 
491
        self.failUnless(rev_tree.is_executable(a_id), "'a' lost the execute bit")
 
492
        self.failIf(rev_tree.is_executable(b_id), "'b' gained an execute bit")
 
493
 
 
494
        self.failUnless(rev_tree.inventory[a_id].executable)
 
495
        self.failIf(rev_tree.inventory[b_id].executable)
 
496
 
 
497
        # Make sure the entries are gone
 
498
        os.remove('b1/a')
 
499
        os.remove('b1/b')
 
500
        self.failIf(wt.has_id(a_id))
 
501
        self.failIf(wt.has_filename('a'))
 
502
        self.failIf(wt.has_id(b_id))
 
503
        self.failIf(wt.has_filename('b'))
 
504
 
 
505
        # Make sure that revert is able to bring them back,
 
506
        # and sets 'a' back to being executable
 
507
 
 
508
        wt.revert(['a', 'b'], rev_tree, backups=False)
 
509
        self.assertEqual(['a', 'b'], [cn for cn,ie in wt.inventory.iter_entries()])
 
510
 
 
511
        self.failUnless(wt.is_executable(a_id), "'a' lost the execute bit")
 
512
        self.failIf(wt.is_executable(b_id), "'b' gained an execute bit")
 
513
 
 
514
        # Now remove them again, and make sure that after a
 
515
        # commit, they are still marked correctly
 
516
        os.remove('b1/a')
 
517
        os.remove('b1/b')
 
518
        wt.commit('removed', rev_id='r2')
 
519
 
 
520
        self.assertEqual([], [cn for cn,ie in wt.inventory.iter_entries()])
 
521
        self.failIf(wt.has_id(a_id))
 
522
        self.failIf(wt.has_filename('a'))
 
523
        self.failIf(wt.has_id(b_id))
 
524
        self.failIf(wt.has_filename('b'))
 
525
 
 
526
        # Now revert back to the previous commit
 
527
        wt.revert([], rev_tree, backups=False)
 
528
        self.assertEqual(['a', 'b'], [cn for cn,ie in wt.inventory.iter_entries()])
 
529
 
 
530
        self.failUnless(wt.is_executable(a_id), "'a' lost the execute bit")
 
531
        self.failIf(wt.is_executable(b_id), "'b' gained an execute bit")
 
532
 
 
533
        # Now make sure that 'bzr branch' also preserves the
 
534
        # executable bit
 
535
        # TODO: Maybe this should be a blackbox test
 
536
        d2 = b.bzrdir.clone('b2', revision_id='r1')
 
537
        t2 = d2.open_workingtree()
 
538
        b2 = t2.branch
 
539
        self.assertEquals('r1', b2.last_revision())
 
540
 
 
541
        self.assertEqual(['a', 'b'], [cn for cn,ie in t2.inventory.iter_entries()])
 
542
        self.failUnless(t2.is_executable(a_id), "'a' lost the execute bit")
 
543
        self.failIf(t2.is_executable(b_id), "'b' gained an execute bit")
 
544
 
 
545
        # Make sure pull will delete the files
 
546
        t2.pull(b)
 
547
        self.assertEquals('r2', b2.last_revision())
 
548
        self.assertEqual([], [cn for cn,ie in t2.inventory.iter_entries()])
 
549
 
 
550
        # Now commit the changes on the first branch
 
551
        # so that the second branch can pull the changes
 
552
        # and make sure that the executable bit has been copied
 
553
        wt.commit('resurrected', rev_id='r3')
 
554
 
 
555
        t2.pull(b)
 
556
        self.assertEquals('r3', b2.last_revision())
 
557
        self.assertEqual(['a', 'b'], [cn for cn,ie in t2.inventory.iter_entries()])
 
558
 
 
559
        self.failUnless(t2.is_executable(a_id), "'a' lost the execute bit")
 
560
        self.failIf(t2.is_executable(b_id), "'b' gained an execute bit")
 
561
 
 
562
 
 
563
class TestRevert(TestCaseWithTransport):
 
564
 
 
565
    def test_dangling_id(self):
 
566
        wt = self.make_branch_and_tree('b1')
 
567
        self.assertEqual(len(wt.inventory), 1)
 
568
        open('b1/a', 'wb').write('a test\n')
 
569
        wt.add('a')
 
570
        self.assertEqual(len(wt.inventory), 2)
 
571
        os.unlink('b1/a')
 
572
        wt.revert([])
 
573
        self.assertEqual(len(wt.inventory), 1)