1
# Copyright (C) 2005 by Canonical Ltd
1
# Copyright (C) 2005, 2006 by Canonical Ltd
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.
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.
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
from cStringIO import StringIO
21
from bzrlib import errors, inventory, osutils
20
22
from bzrlib.branch import Branch
21
import bzrlib.errors as errors
22
23
from bzrlib.diff import internal_diff
23
from bzrlib.inventory import Inventory, ROOT_ID
24
import bzrlib.inventory as inventory
25
from bzrlib.osutils import has_symlinks, rename, pathjoin
24
from bzrlib.inventory import (Inventory, ROOT_ID, InventoryFile,
25
InventoryDirectory, InventoryEntry)
26
from bzrlib.osutils import (has_symlinks, rename, pathjoin, is_inside_any,
27
is_inside_or_parent_of_any)
26
28
from bzrlib.tests import TestCase, TestCaseWithTransport
29
from bzrlib.transform import TreeTransform
30
from bzrlib.uncommit import uncommit
29
33
class TestInventory(TestCase):
31
35
def test_is_within(self):
32
from bzrlib.osutils import is_inside_any
34
37
SRC_FOO_C = pathjoin('src', 'foo.c')
35
38
for dirs, fn in [(['src', 'doc'], SRC_FOO_C),
59
76
self.assert_('src-id' in inv)
78
def test_iter_entries(self):
81
for args in [('src', 'directory', 'src-id'),
82
('doc', 'directory', 'doc-id'),
83
('src/hello.c', 'file', 'hello-id'),
84
('src/bye.c', 'file', 'bye-id'),
85
('Makefile', 'file', 'makefile-id')]:
90
('Makefile', 'makefile-id'),
93
('src/bye.c', 'bye-id'),
94
('src/hello.c', 'hello-id'),
95
], [(path, ie.file_id) for path, ie in inv.iter_entries()])
97
def test_iter_entries_by_dir(self):
100
for args in [('src', 'directory', 'src-id'),
101
('doc', 'directory', 'doc-id'),
102
('src/hello.c', 'file', 'hello-id'),
103
('src/bye.c', 'file', 'bye-id'),
104
('zz', 'file', 'zz-id'),
105
('src/sub/', 'directory', 'sub-id'),
106
('src/zz.c', 'file', 'zzc-id'),
107
('src/sub/a', 'file', 'a-id'),
108
('Makefile', 'file', 'makefile-id')]:
113
('Makefile', 'makefile-id'),
117
('src/bye.c', 'bye-id'),
118
('src/hello.c', 'hello-id'),
119
('src/sub', 'sub-id'),
120
('src/zz.c', 'zzc-id'),
121
('src/sub/a', 'a-id'),
122
], [(path, ie.file_id) for path, ie in inv.iter_entries_by_dir()])
62
124
def test_version(self):
63
125
"""Inventory remembers the text's version."""
131
193
link = inventory.InventoryLink('123', 'hello.c', ROOT_ID)
132
194
self.failIf(link.has_text())
196
def test_make_entry(self):
197
self.assertIsInstance(inventory.make_entry("file", "name", ROOT_ID),
198
inventory.InventoryFile)
199
self.assertIsInstance(inventory.make_entry("symlink", "name", ROOT_ID),
200
inventory.InventoryLink)
201
self.assertIsInstance(inventory.make_entry("directory", "name", ROOT_ID),
202
inventory.InventoryDirectory)
204
def test_make_entry_non_normalized(self):
205
orig_normalized_filename = osutils.normalized_filename
208
osutils.normalized_filename = osutils._accessible_normalized_filename
209
entry = inventory.make_entry("file", u'a\u030a', ROOT_ID)
210
self.assertEqual(u'\xe5', entry.name)
211
self.assertIsInstance(entry, inventory.InventoryFile)
213
osutils.normalized_filename = osutils._inaccessible_normalized_filename
214
self.assertRaises(errors.InvalidNormalization,
215
inventory.make_entry, 'file', u'a\u030a', ROOT_ID)
217
osutils.normalized_filename = orig_normalized_filename
135
220
class TestEntryDiffing(TestCaseWithTransport):
139
224
self.wt = self.make_branch_and_tree('.')
140
225
self.branch = self.wt.branch
141
226
print >> open('file', 'wb'), 'foo'
227
print >> open('binfile', 'wb'), 'foo'
142
228
self.wt.add(['file'], ['fileid'])
229
self.wt.add(['binfile'], ['binfileid'])
143
230
if has_symlinks():
144
231
os.symlink('target1', 'symlink')
145
232
self.wt.add(['symlink'], ['linkid'])
146
233
self.wt.commit('message_1', rev_id = '1')
147
234
print >> open('file', 'wb'), 'bar'
235
print >> open('binfile', 'wb'), 'x' * 1023 + '\x00'
148
236
if has_symlinks():
149
237
os.unlink('symlink')
150
238
os.symlink('target2', 'symlink')
151
239
self.tree_1 = self.branch.repository.revision_tree('1')
152
240
self.inv_1 = self.branch.repository.get_inventory('1')
153
241
self.file_1 = self.inv_1['fileid']
242
self.file_1b = self.inv_1['binfileid']
154
243
self.tree_2 = self.wt
155
244
self.inv_2 = self.tree_2.read_working_inventory()
156
245
self.file_2 = self.inv_2['fileid']
246
self.file_2b = self.inv_2['binfileid']
157
247
if has_symlinks():
158
248
self.link_1 = self.inv_1['linkid']
159
249
self.link_2 = self.inv_2['linkid']
388
475
# TODO: test two inventories with the same file revision
391
class TestExecutable(TestCaseWithTransport):
393
def test_stays_executable(self):
394
basic_inv = """<inventory format="5">
395
<file file_id="a-20051208024829-849e76f7968d7a86" name="a" executable="yes" />
396
<file file_id="b-20051208024829-849e76f7968d7a86" name="b" />
399
wt = self.make_branch_and_tree('b1')
401
open('b1/a', 'wb').write('a test\n')
402
open('b1/b', 'wb').write('b test\n')
403
os.chmod('b1/a', 0755)
404
os.chmod('b1/b', 0644)
405
# Manually writing the inventory, to ensure that
406
# the executable="yes" entry is set for 'a' and not for 'b'
407
open('b1/.bzr/inventory', 'wb').write(basic_inv)
409
a_id = "a-20051208024829-849e76f7968d7a86"
410
b_id = "b-20051208024829-849e76f7968d7a86"
411
wt = wt.bzrdir.open_workingtree()
412
self.assertEqual(['a', 'b'], [cn for cn,ie in wt.inventory.iter_entries()])
414
self.failUnless(wt.is_executable(a_id), "'a' lost the execute bit")
415
self.failIf(wt.is_executable(b_id), "'b' gained an execute bit")
417
wt.commit('adding a,b', rev_id='r1')
419
rev_tree = b.repository.revision_tree('r1')
420
self.failUnless(rev_tree.is_executable(a_id), "'a' lost the execute bit")
421
self.failIf(rev_tree.is_executable(b_id), "'b' gained an execute bit")
423
self.failUnless(rev_tree.inventory[a_id].executable)
424
self.failIf(rev_tree.inventory[b_id].executable)
426
# Make sure the entries are gone
429
self.failIf(wt.has_id(a_id))
430
self.failIf(wt.has_filename('a'))
431
self.failIf(wt.has_id(b_id))
432
self.failIf(wt.has_filename('b'))
434
# Make sure that revert is able to bring them back,
435
# and sets 'a' back to being executable
437
wt.revert(['a', 'b'], rev_tree, backups=False)
438
self.assertEqual(['a', 'b'], [cn for cn,ie in wt.inventory.iter_entries()])
440
self.failUnless(wt.is_executable(a_id), "'a' lost the execute bit")
441
self.failIf(wt.is_executable(b_id), "'b' gained an execute bit")
443
# Now remove them again, and make sure that after a
444
# commit, they are still marked correctly
447
wt.commit('removed', rev_id='r2')
449
self.assertEqual([], [cn for cn,ie in wt.inventory.iter_entries()])
450
self.failIf(wt.has_id(a_id))
451
self.failIf(wt.has_filename('a'))
452
self.failIf(wt.has_id(b_id))
453
self.failIf(wt.has_filename('b'))
455
# Now revert back to the previous commit
456
wt.revert([], rev_tree, backups=False)
457
self.assertEqual(['a', 'b'], [cn for cn,ie in wt.inventory.iter_entries()])
459
self.failUnless(wt.is_executable(a_id), "'a' lost the execute bit")
460
self.failIf(wt.is_executable(b_id), "'b' gained an execute bit")
462
# Now make sure that 'bzr branch' also preserves the
464
# TODO: Maybe this should be a blackbox test
465
d2 = b.bzrdir.clone('b2', revision_id='r1')
466
t2 = d2.open_workingtree()
468
self.assertEquals('r1', b2.last_revision())
470
self.assertEqual(['a', 'b'], [cn for cn,ie in t2.inventory.iter_entries()])
471
self.failUnless(t2.is_executable(a_id), "'a' lost the execute bit")
472
self.failIf(t2.is_executable(b_id), "'b' gained an execute bit")
474
# Make sure pull will delete the files
476
self.assertEquals('r2', b2.last_revision())
477
self.assertEqual([], [cn for cn,ie in t2.inventory.iter_entries()])
479
# Now commit the changes on the first branch
480
# so that the second branch can pull the changes
481
# and make sure that the executable bit has been copied
482
wt.commit('resurrected', rev_id='r3')
485
self.assertEquals('r3', b2.last_revision())
486
self.assertEqual(['a', 'b'], [cn for cn,ie in t2.inventory.iter_entries()])
488
self.failUnless(t2.is_executable(a_id), "'a' lost the execute bit")
489
self.failIf(t2.is_executable(b_id), "'b' gained an execute bit")
478
class TestDescribeChanges(TestCase):
480
def test_describe_change(self):
481
# we need to test the following change combinations:
487
# renamed/reparented and modified
488
# change kind (perhaps can't be done yet?)
489
# also, merged in combination with all of these?
490
old_a = InventoryFile('a-id', 'a_file', ROOT_ID)
491
old_a.text_sha1 = '123132'
493
new_a = InventoryFile('a-id', 'a_file', ROOT_ID)
494
new_a.text_sha1 = '123132'
497
self.assertChangeDescription('unchanged', old_a, new_a)
500
new_a.text_sha1 = 'abcabc'
501
self.assertChangeDescription('modified', old_a, new_a)
503
self.assertChangeDescription('added', None, new_a)
504
self.assertChangeDescription('removed', old_a, None)
505
# perhaps a bit questionable but seems like the most reasonable thing...
506
self.assertChangeDescription('unchanged', None, None)
508
# in this case it's both renamed and modified; show a rename and
510
new_a.name = 'newfilename'
511
self.assertChangeDescription('modified and renamed', old_a, new_a)
513
# reparenting is 'renaming'
514
new_a.name = old_a.name
515
new_a.parent_id = 'somedir-id'
516
self.assertChangeDescription('modified and renamed', old_a, new_a)
518
# reset the content values so its not modified
519
new_a.text_size = old_a.text_size
520
new_a.text_sha1 = old_a.text_sha1
521
new_a.name = old_a.name
523
new_a.name = 'newfilename'
524
self.assertChangeDescription('renamed', old_a, new_a)
526
# reparenting is 'renaming'
527
new_a.name = old_a.name
528
new_a.parent_id = 'somedir-id'
529
self.assertChangeDescription('renamed', old_a, new_a)
531
def assertChangeDescription(self, expected_change, old_ie, new_ie):
532
change = InventoryEntry.describe_change(old_ie, new_ie)
533
self.assertEqual(expected_change, change)
491
536
class TestRevert(TestCaseWithTransport):
492
538
def test_dangling_id(self):
493
539
wt = self.make_branch_and_tree('b1')
494
540
self.assertEqual(len(wt.inventory), 1)