~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_inv.py

  • Committer: Aaron Bentley
  • Date: 2006-06-21 14:30:57 UTC
  • mfrom: (1801.1.1 bzr.dev)
  • mto: This revision was merged to the branch mainline in revision 1803.
  • Revision ID: abentley@panoramicfeedback.com-20060621143057-776e4b8d707e430e
Install benchmarks. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 by Canonical Ltd
 
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
 
18
 
from bzrlib import (
19
 
    chk_map,
20
 
    bzrdir,
21
 
    errors,
22
 
    inventory,
23
 
    osutils,
24
 
    repository,
25
 
    revision,
26
 
    )
27
 
from bzrlib.inventory import (CHKInventory, Inventory, ROOT_ID, InventoryFile,
28
 
    InventoryDirectory, InventoryEntry, TreeReference)
29
 
from bzrlib.tests import (
30
 
    TestCase,
31
 
    TestCaseWithTransport,
32
 
    condition_isinstance,
33
 
    multiply_tests,
34
 
    split_suite_by_condition,
35
 
    )
36
 
from bzrlib.tests.per_workingtree import workingtree_formats
37
 
 
38
 
 
39
 
def load_tests(standard_tests, module, loader):
40
 
    """Parameterise some inventory tests."""
41
 
    to_adapt, result = split_suite_by_condition(standard_tests,
42
 
        condition_isinstance(TestDeltaApplication))
43
 
    scenarios = [
44
 
        ('Inventory', {'apply_delta':apply_inventory_Inventory}),
45
 
        ]
46
 
    # Working tree basis delta application
47
 
    # Repository add_inv_by_delta.
48
 
    # Reduce form of the per_repository test logic - that logic needs to be
49
 
    # be able to get /just/ repositories whereas these tests are fine with
50
 
    # just creating trees.
51
 
    formats = set()
52
 
    for _, format in repository.format_registry.iteritems():
53
 
        scenarios.append((str(format.__name__), {
54
 
            'apply_delta':apply_inventory_Repository_add_inventory_by_delta,
55
 
            'format':format}))
56
 
    for format in workingtree_formats():
57
 
        scenarios.append(
58
 
            (str(format.__class__.__name__) + ".update_basis_by_delta", {
59
 
            'apply_delta':apply_inventory_WT_basis,
60
 
            'format':format}))
61
 
        scenarios.append(
62
 
            (str(format.__class__.__name__) + ".apply_inventory_delta", {
63
 
            'apply_delta':apply_inventory_WT,
64
 
            'format':format}))
65
 
    return multiply_tests(to_adapt, scenarios, result)
66
 
 
67
 
 
68
 
def apply_inventory_Inventory(self, basis, delta):
69
 
    """Apply delta to basis and return the result.
70
 
    
71
 
    :param basis: An inventory to be used as the basis.
72
 
    :param delta: The inventory delta to apply:
73
 
    :return: An inventory resulting from the application.
74
 
    """
75
 
    basis.apply_delta(delta)
76
 
    return basis
77
 
 
78
 
 
79
 
def apply_inventory_WT(self, basis, delta):
80
 
    """Apply delta to basis and return the result.
81
 
 
82
 
    This sets the tree state to be basis, and then calls apply_inventory_delta.
83
 
    
84
 
    :param basis: An inventory to be used as the basis.
85
 
    :param delta: The inventory delta to apply:
86
 
    :return: An inventory resulting from the application.
87
 
    """
88
 
    control = self.make_bzrdir('tree', format=self.format._matchingbzrdir)
89
 
    control.create_repository()
90
 
    control.create_branch()
91
 
    tree = self.format.initialize(control)
92
 
    tree.lock_write()
93
 
    try:
94
 
        tree._write_inventory(basis)
95
 
    finally:
96
 
        tree.unlock()
97
 
    # Fresh object, reads disk again.
98
 
    tree = tree.bzrdir.open_workingtree()
99
 
    tree.lock_write()
100
 
    try:
101
 
        tree.apply_inventory_delta(delta)
102
 
    finally:
103
 
        tree.unlock()
104
 
    # reload tree - ensure we get what was written.
105
 
    tree = tree.bzrdir.open_workingtree()
106
 
    tree.lock_read()
107
 
    self.addCleanup(tree.unlock)
108
 
    # One could add 'tree._validate' here but that would cause 'early' failues 
109
 
    # as far as higher level code is concerned. Possibly adding an
110
 
    # expect_fail parameter to this function and if that is False then do a
111
 
    # validate call.
112
 
    return tree.inventory
113
 
 
114
 
 
115
 
def apply_inventory_WT_basis(self, basis, delta):
116
 
    """Apply delta to basis and return the result.
117
 
 
118
 
    This sets the parent and then calls update_basis_by_delta.
119
 
    It also puts the basis in the repository under both 'basis' and 'result' to
120
 
    allow safety checks made by the WT to succeed, and finally ensures that all
121
 
    items in the delta with a new path are present in the WT before calling
122
 
    update_basis_by_delta.
123
 
    
124
 
    :param basis: An inventory to be used as the basis.
125
 
    :param delta: The inventory delta to apply:
126
 
    :return: An inventory resulting from the application.
127
 
    """
128
 
    control = self.make_bzrdir('tree', format=self.format._matchingbzrdir)
129
 
    control.create_repository()
130
 
    control.create_branch()
131
 
    tree = self.format.initialize(control)
132
 
    tree.lock_write()
133
 
    try:
134
 
        repo = tree.branch.repository
135
 
        repo.start_write_group()
136
 
        try:
137
 
            rev = revision.Revision('basis', timestamp=0, timezone=None,
138
 
                message="", committer="foo@example.com")
139
 
            basis.revision_id = 'basis'
140
 
            repo.add_revision('basis', rev, basis)
141
 
            # Add a revision for the result, with the basis content - 
142
 
            # update_basis_by_delta doesn't check that the delta results in
143
 
            # result, and we want inconsistent deltas to get called on the
144
 
            # tree, or else the code isn't actually checked.
145
 
            rev = revision.Revision('result', timestamp=0, timezone=None,
146
 
                message="", committer="foo@example.com")
147
 
            basis.revision_id = 'result'
148
 
            repo.add_revision('result', rev, basis)
149
 
        except:
150
 
            repo.abort_write_group()
151
 
            raise
152
 
        else:
153
 
            repo.commit_write_group()
154
 
        # Set the basis state as the trees current state
155
 
        tree._write_inventory(basis)
156
 
        # This reads basis from the repo and puts it into the tree's local
157
 
        # cache, if it has one.
158
 
        tree.set_parent_ids(['basis'])
159
 
        paths = {}
160
 
        parents = set()
161
 
        for old, new, id, entry in delta:
162
 
            if None in (new, entry):
163
 
                continue
164
 
            paths[new] = (entry.file_id, entry.kind)
165
 
            parents.add(osutils.dirname(new))
166
 
        parents = osutils.minimum_path_selection(parents)
167
 
        parents.discard('')
168
 
        # Put place holders in the tree to permit adding the other entries.
169
 
        for pos, parent in enumerate(parents):
170
 
            if not tree.path2id(parent):
171
 
                # add a synthetic directory in the tree so we can can put the
172
 
                # tree0 entries in place for dirstate.
173
 
                tree.add([parent], ["id%d" % pos], ["directory"])
174
 
        if paths:
175
 
            # Many deltas may cause this mini-apply to fail, but we want to see what
176
 
            # the delta application code says, not the prep that we do to deal with 
177
 
            # limitations of dirstate's update_basis code.
178
 
            for path, (file_id, kind) in sorted(paths.items()):
179
 
                try:
180
 
                    tree.add([path], [file_id], [kind])
181
 
                except (KeyboardInterrupt, SystemExit):
182
 
                    raise
183
 
                except:
184
 
                    pass
185
 
    finally:
186
 
        tree.unlock()
187
 
    # Fresh lock, reads disk again.
188
 
    tree.lock_write()
189
 
    try:
190
 
        tree.update_basis_by_delta('result', delta)
191
 
    finally:
192
 
        tree.unlock()
193
 
    # reload tree - ensure we get what was written.
194
 
    tree = tree.bzrdir.open_workingtree()
195
 
    basis_tree = tree.basis_tree()
196
 
    basis_tree.lock_read()
197
 
    self.addCleanup(basis_tree.unlock)
198
 
    # Note, that if the tree does not have a local cache, the trick above of
199
 
    # setting the result as the basis, will come back to bite us. That said,
200
 
    # all the implementations in bzr do have a local cache.
201
 
    return basis_tree.inventory
202
 
 
203
 
 
204
 
def apply_inventory_Repository_add_inventory_by_delta(self, basis, delta):
205
 
    """Apply delta to basis and return the result.
206
 
    
207
 
    This inserts basis as a whole inventory and then uses
208
 
    add_inventory_by_delta to add delta.
209
 
 
210
 
    :param basis: An inventory to be used as the basis.
211
 
    :param delta: The inventory delta to apply:
212
 
    :return: An inventory resulting from the application.
213
 
    """
214
 
    format = self.format()
215
 
    control = self.make_bzrdir('tree', format=format._matchingbzrdir)
216
 
    repo = format.initialize(control)
217
 
    repo.lock_write()
218
 
    try:
219
 
        repo.start_write_group()
220
 
        try:
221
 
            rev = revision.Revision('basis', timestamp=0, timezone=None,
222
 
                message="", committer="foo@example.com")
223
 
            basis.revision_id = 'basis'
224
 
            repo.add_revision('basis', rev, basis)
225
 
        except:
226
 
            repo.abort_write_group()
227
 
            raise
228
 
        else:
229
 
            repo.commit_write_group()
230
 
    finally:
231
 
        repo.unlock()
232
 
    repo.lock_write()
233
 
    try:
234
 
        repo.start_write_group()
235
 
        try:
236
 
            inv_sha1 = repo.add_inventory_by_delta('basis', delta,
237
 
                'result', ['basis'])
238
 
        except:
239
 
            repo.abort_write_group()
240
 
            raise
241
 
        else:
242
 
            repo.commit_write_group()
243
 
    finally:
244
 
        repo.unlock()
245
 
    # Fresh lock, reads disk again.
246
 
    repo = repo.bzrdir.open_repository()
247
 
    repo.lock_read()
248
 
    self.addCleanup(repo.unlock)
249
 
    return repo.get_inventory('result')
250
 
 
251
 
 
252
 
class TestDeltaApplication(TestCaseWithTransport):
253
 
 
254
 
    def get_empty_inventory(self, reference_inv=None):
255
 
        """Get an empty inventory.
256
 
 
257
 
        Note that tests should not depend on the revision of the root for
258
 
        setting up test conditions, as it has to be flexible to accomodate non
259
 
        rich root repositories.
260
 
 
261
 
        :param reference_inv: If not None, get the revision for the root from
262
 
            this inventory. This is useful for dealing with older repositories
263
 
            that routinely discarded the root entry data. If None, the root's
264
 
            revision is set to 'basis'.
265
 
        """
266
 
        inv = inventory.Inventory()
267
 
        if reference_inv is not None:
268
 
            inv.root.revision = reference_inv.root.revision
269
 
        else:
270
 
            inv.root.revision = 'basis'
271
 
        return inv
272
 
 
273
 
    def test_empty_delta(self):
274
 
        inv = self.get_empty_inventory()
275
 
        delta = []
276
 
        inv = self.apply_delta(self, inv, delta)
277
 
        inv2 = self.get_empty_inventory(inv)
278
 
        self.assertEqual([], inv2._make_delta(inv))
279
 
 
280
 
    def test_None_file_id(self):
281
 
        inv = self.get_empty_inventory()
282
 
        dir1 = inventory.InventoryDirectory(None, 'dir1', inv.root.file_id)
283
 
        dir1.revision = 'result'
284
 
        delta = [(None, u'dir1', None, dir1)]
285
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
286
 
            inv, delta)
287
 
 
288
 
    def test_unicode_file_id(self):
289
 
        inv = self.get_empty_inventory()
290
 
        dir1 = inventory.InventoryDirectory(u'dirid', 'dir1', inv.root.file_id)
291
 
        dir1.revision = 'result'
292
 
        delta = [(None, u'dir1', dir1.file_id, dir1)]
293
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
294
 
            inv, delta)
295
 
 
296
 
    def test_repeated_file_id(self):
297
 
        inv = self.get_empty_inventory()
298
 
        file1 = inventory.InventoryFile('id', 'path1', inv.root.file_id)
299
 
        file1.revision = 'result'
300
 
        file1.text_size = 0
301
 
        file1.text_sha1 = ""
302
 
        file2 = inventory.InventoryFile('id', 'path2', inv.root.file_id)
303
 
        file2.revision = 'result'
304
 
        file2.text_size = 0
305
 
        file2.text_sha1 = ""
306
 
        delta = [(None, u'path1', 'id', file1), (None, u'path2', 'id', file2)]
307
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
308
 
            inv, delta)
309
 
 
310
 
    def test_repeated_new_path(self):
311
 
        inv = self.get_empty_inventory()
312
 
        file1 = inventory.InventoryFile('id1', 'path', inv.root.file_id)
313
 
        file1.revision = 'result'
314
 
        file1.text_size = 0
315
 
        file1.text_sha1 = ""
316
 
        file2 = inventory.InventoryFile('id2', 'path', inv.root.file_id)
317
 
        file2.revision = 'result'
318
 
        file2.text_size = 0
319
 
        file2.text_sha1 = ""
320
 
        delta = [(None, u'path', 'id1', file1), (None, u'path', 'id2', file2)]
321
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
322
 
            inv, delta)
323
 
 
324
 
    def test_repeated_old_path(self):
325
 
        inv = self.get_empty_inventory()
326
 
        file1 = inventory.InventoryFile('id1', 'path', inv.root.file_id)
327
 
        file1.revision = 'result'
328
 
        file1.text_size = 0
329
 
        file1.text_sha1 = ""
330
 
        # We can't *create* a source inventory with the same path, but
331
 
        # a badly generated partial delta might claim the same source twice.
332
 
        # This would be buggy in two ways: the path is repeated in the delta,
333
 
        # And the path for one of the file ids doesn't match the source
334
 
        # location. Alternatively, we could have a repeated fileid, but that
335
 
        # is separately checked for.
336
 
        file2 = inventory.InventoryFile('id2', 'path2', inv.root.file_id)
337
 
        file2.revision = 'result'
338
 
        file2.text_size = 0
339
 
        file2.text_sha1 = ""
340
 
        inv.add(file1)
341
 
        inv.add(file2)
342
 
        delta = [(u'path', None, 'id1', None), (u'path', None, 'id2', None)]
343
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
344
 
            inv, delta)
345
 
 
346
 
    def test_mismatched_id_entry_id(self):
347
 
        inv = self.get_empty_inventory()
348
 
        file1 = inventory.InventoryFile('id1', 'path', inv.root.file_id)
349
 
        file1.revision = 'result'
350
 
        file1.text_size = 0
351
 
        file1.text_sha1 = ""
352
 
        delta = [(None, u'path', 'id', file1)]
353
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
354
 
            inv, delta)
355
 
 
356
 
    def test_mismatched_new_path_entry_None(self):
357
 
        inv = self.get_empty_inventory()
358
 
        delta = [(None, u'path', 'id', None)]
359
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
360
 
            inv, delta)
361
 
 
362
 
    def test_mismatched_new_path_None_entry(self):
363
 
        inv = self.get_empty_inventory()
364
 
        file1 = inventory.InventoryFile('id1', 'path', inv.root.file_id)
365
 
        file1.revision = 'result'
366
 
        file1.text_size = 0
367
 
        file1.text_sha1 = ""
368
 
        delta = [(u"path", None, 'id1', file1)]
369
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
370
 
            inv, delta)
371
 
 
372
 
    def test_parent_is_not_directory(self):
373
 
        inv = self.get_empty_inventory()
374
 
        file1 = inventory.InventoryFile('id1', 'path', inv.root.file_id)
375
 
        file1.revision = 'result'
376
 
        file1.text_size = 0
377
 
        file1.text_sha1 = ""
378
 
        file2 = inventory.InventoryFile('id2', 'path2', 'id1')
379
 
        file2.revision = 'result'
380
 
        file2.text_size = 0
381
 
        file2.text_sha1 = ""
382
 
        inv.add(file1)
383
 
        delta = [(None, u'path/path2', 'id2', file2)]
384
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
385
 
            inv, delta)
386
 
 
387
 
    def test_parent_is_missing(self):
388
 
        inv = self.get_empty_inventory()
389
 
        file2 = inventory.InventoryFile('id2', 'path2', 'missingparent')
390
 
        file2.revision = 'result'
391
 
        file2.text_size = 0
392
 
        file2.text_sha1 = ""
393
 
        delta = [(None, u'path/path2', 'id2', file2)]
394
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
395
 
            inv, delta)
396
 
 
397
 
    def test_new_parent_path_has_wrong_id(self):
398
 
        inv = self.get_empty_inventory()
399
 
        parent1 = inventory.InventoryDirectory('p-1', 'dir', inv.root.file_id)
400
 
        parent1.revision = 'result'
401
 
        parent2 = inventory.InventoryDirectory('p-2', 'dir2', inv.root.file_id)
402
 
        parent2.revision = 'result'
403
 
        file1 = inventory.InventoryFile('id', 'path', 'p-2')
404
 
        file1.revision = 'result'
405
 
        file1.text_size = 0
406
 
        file1.text_sha1 = ""
407
 
        inv.add(parent1)
408
 
        inv.add(parent2)
409
 
        # This delta claims that file1 is at dir/path, but actually its at
410
 
        # dir2/path if you follow the inventory parent structure.
411
 
        delta = [(None, u'dir/path', 'id', file1)]
412
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
413
 
            inv, delta)
414
 
 
415
 
    def test_old_parent_path_is_wrong(self):
416
 
        inv = self.get_empty_inventory()
417
 
        parent1 = inventory.InventoryDirectory('p-1', 'dir', inv.root.file_id)
418
 
        parent1.revision = 'result'
419
 
        parent2 = inventory.InventoryDirectory('p-2', 'dir2', inv.root.file_id)
420
 
        parent2.revision = 'result'
421
 
        file1 = inventory.InventoryFile('id', 'path', 'p-2')
422
 
        file1.revision = 'result'
423
 
        file1.text_size = 0
424
 
        file1.text_sha1 = ""
425
 
        inv.add(parent1)
426
 
        inv.add(parent2)
427
 
        inv.add(file1)
428
 
        # This delta claims that file1 was at dir/path, but actually it was at
429
 
        # dir2/path if you follow the inventory parent structure.
430
 
        delta = [(u'dir/path', None, 'id', None)]
431
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
432
 
            inv, delta)
433
 
 
434
 
    def test_old_parent_path_is_for_other_id(self):
435
 
        inv = self.get_empty_inventory()
436
 
        parent1 = inventory.InventoryDirectory('p-1', 'dir', inv.root.file_id)
437
 
        parent1.revision = 'result'
438
 
        parent2 = inventory.InventoryDirectory('p-2', 'dir2', inv.root.file_id)
439
 
        parent2.revision = 'result'
440
 
        file1 = inventory.InventoryFile('id', 'path', 'p-2')
441
 
        file1.revision = 'result'
442
 
        file1.text_size = 0
443
 
        file1.text_sha1 = ""
444
 
        file2 = inventory.InventoryFile('id2', 'path', 'p-1')
445
 
        file2.revision = 'result'
446
 
        file2.text_size = 0
447
 
        file2.text_sha1 = ""
448
 
        inv.add(parent1)
449
 
        inv.add(parent2)
450
 
        inv.add(file1)
451
 
        inv.add(file2)
452
 
        # This delta claims that file1 was at dir/path, but actually it was at
453
 
        # dir2/path if you follow the inventory parent structure. At dir/path
454
 
        # is another entry we should not delete.
455
 
        delta = [(u'dir/path', None, 'id', None)]
456
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
457
 
            inv, delta)
458
 
 
459
 
    def test_add_existing_id_new_path(self):
460
 
        inv = self.get_empty_inventory()
461
 
        parent1 = inventory.InventoryDirectory('p-1', 'dir1', inv.root.file_id)
462
 
        parent1.revision = 'result'
463
 
        parent2 = inventory.InventoryDirectory('p-1', 'dir2', inv.root.file_id)
464
 
        parent2.revision = 'result'
465
 
        inv.add(parent1)
466
 
        delta = [(None, u'dir2', 'p-1', parent2)]
467
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
468
 
            inv, delta)
469
 
 
470
 
    def test_add_new_id_existing_path(self):
471
 
        inv = self.get_empty_inventory()
472
 
        parent1 = inventory.InventoryDirectory('p-1', 'dir1', inv.root.file_id)
473
 
        parent1.revision = 'result'
474
 
        parent2 = inventory.InventoryDirectory('p-2', 'dir1', inv.root.file_id)
475
 
        parent2.revision = 'result'
476
 
        inv.add(parent1)
477
 
        delta = [(None, u'dir1', 'p-2', parent2)]
478
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
479
 
            inv, delta)
480
 
 
481
 
    def test_remove_dir_leaving_dangling_child(self):
482
 
        inv = self.get_empty_inventory()
483
 
        dir1 = inventory.InventoryDirectory('p-1', 'dir1', inv.root.file_id)
484
 
        dir1.revision = 'result'
485
 
        dir2 = inventory.InventoryDirectory('p-2', 'child1', 'p-1')
486
 
        dir2.revision = 'result'
487
 
        dir3 = inventory.InventoryDirectory('p-3', 'child2', 'p-1')
488
 
        dir3.revision = 'result'
489
 
        inv.add(dir1)
490
 
        inv.add(dir2)
491
 
        inv.add(dir3)
492
 
        delta = [(u'dir1', None, 'p-1', None),
493
 
            (u'dir1/child2', None, 'p-3', None)]
494
 
        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
495
 
            inv, delta)
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
from cStringIO import StringIO
 
18
import os
 
19
import time
 
20
 
 
21
from bzrlib.branch import Branch
 
22
import bzrlib.errors as errors
 
23
from bzrlib.diff import internal_diff
 
24
from bzrlib.inventory import (Inventory, ROOT_ID, InventoryFile,
 
25
    InventoryDirectory, InventoryEntry)
 
26
import bzrlib.inventory as inventory
 
27
from bzrlib.osutils import (has_symlinks, rename, pathjoin, is_inside_any, 
 
28
    is_inside_or_parent_of_any)
 
29
from bzrlib.tests import TestCase, TestCaseWithTransport
 
30
from bzrlib.transform import TreeTransform
 
31
from bzrlib.uncommit import uncommit
 
32
 
 
33
 
 
34
class TestInventory(TestCase):
 
35
 
 
36
    def test_is_within(self):
 
37
 
 
38
        SRC_FOO_C = pathjoin('src', 'foo.c')
 
39
        for dirs, fn in [(['src', 'doc'], SRC_FOO_C),
 
40
                         (['src'], SRC_FOO_C),
 
41
                         (['src'], 'src'),
 
42
                         ]:
 
43
            self.assert_(is_inside_any(dirs, fn))
 
44
            
 
45
        for dirs, fn in [(['src'], 'srccontrol'),
 
46
                         (['src'], 'srccontrol/foo')]:
 
47
            self.assertFalse(is_inside_any(dirs, fn))
 
48
 
 
49
    def test_is_within_or_parent(self):
 
50
        for dirs, fn in [(['src', 'doc'], 'src/foo.c'),
 
51
                         (['src'], 'src/foo.c'),
 
52
                         (['src/bar.c'], 'src'),
 
53
                         (['src/bar.c', 'bla/foo.c'], 'src'),
 
54
                         (['src'], 'src'),
 
55
                         ]:
 
56
            self.assert_(is_inside_or_parent_of_any(dirs, fn))
 
57
            
 
58
        for dirs, fn in [(['src'], 'srccontrol'),
 
59
                         (['srccontrol/foo.c'], 'src'),
 
60
                         (['src'], 'srccontrol/foo')]:
 
61
            self.assertFalse(is_inside_or_parent_of_any(dirs, fn))
 
62
 
 
63
    def test_ids(self):
 
64
        """Test detection of files within selected directories."""
 
65
        inv = Inventory()
 
66
        
 
67
        for args in [('src', 'directory', 'src-id'), 
 
68
                     ('doc', 'directory', 'doc-id'), 
 
69
                     ('src/hello.c', 'file'),
 
70
                     ('src/bye.c', 'file', 'bye-id'),
 
71
                     ('Makefile', 'file')]:
 
72
            inv.add_path(*args)
 
73
            
 
74
        self.assertEqual(inv.path2id('src'), 'src-id')
 
75
        self.assertEqual(inv.path2id('src/bye.c'), 'bye-id')
 
76
        
 
77
        self.assert_('src-id' in inv)
 
78
 
 
79
    def test_iter_entries(self):
 
80
        inv = Inventory()
 
81
        
 
82
        for args in [('src', 'directory', 'src-id'), 
 
83
                     ('doc', 'directory', 'doc-id'), 
 
84
                     ('src/hello.c', 'file', 'hello-id'),
 
85
                     ('src/bye.c', 'file', 'bye-id'),
 
86
                     ('Makefile', 'file', 'makefile-id')]:
 
87
            inv.add_path(*args)
 
88
 
 
89
        self.assertEqual([
 
90
            ('Makefile', 'makefile-id'),
 
91
            ('doc', 'doc-id'),
 
92
            ('src', 'src-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()])
 
96
            
 
97
    def test_iter_entries_by_dir(self):
 
98
        inv = Inventory()
 
99
        
 
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')]:
 
109
            inv.add_path(*args)
 
110
 
 
111
        self.assertEqual([
 
112
            ('Makefile', 'makefile-id'),
 
113
            ('doc', 'doc-id'),
 
114
            ('src', 'src-id'),
 
115
            ('zz', 'zz-id'),
 
116
            ('src/bye.c', 'bye-id'),
 
117
            ('src/hello.c', 'hello-id'),
 
118
            ('src/sub', 'sub-id'),
 
119
            ('src/zz.c', 'zzc-id'),
 
120
            ('src/sub/a', 'a-id'),
 
121
            ], [(path, ie.file_id) for path, ie in inv.iter_entries_by_dir()])
 
122
            
 
123
    def test_version(self):
 
124
        """Inventory remembers the text's version."""
 
125
        inv = Inventory()
 
126
        ie = inv.add_path('foo.txt', 'file')
 
127
        ## XXX
496
128
 
497
129
 
498
130
class TestInventoryEntry(TestCase):
568
200
        self.assertIsInstance(inventory.make_entry("directory", "name", ROOT_ID),
569
201
            inventory.InventoryDirectory)
570
202
 
571
 
    def test_make_entry_non_normalized(self):
572
 
        orig_normalized_filename = osutils.normalized_filename
573
 
 
574
 
        try:
575
 
            osutils.normalized_filename = osutils._accessible_normalized_filename
576
 
            entry = inventory.make_entry("file", u'a\u030a', ROOT_ID)
577
 
            self.assertEqual(u'\xe5', entry.name)
578
 
            self.assertIsInstance(entry, inventory.InventoryFile)
579
 
 
580
 
            osutils.normalized_filename = osutils._inaccessible_normalized_filename
581
 
            self.assertRaises(errors.InvalidNormalization,
582
 
                    inventory.make_entry, 'file', u'a\u030a', ROOT_ID)
583
 
        finally:
584
 
            osutils.normalized_filename = orig_normalized_filename
 
203
class TestEntryDiffing(TestCaseWithTransport):
 
204
 
 
205
    def setUp(self):
 
206
        super(TestEntryDiffing, self).setUp()
 
207
        self.wt = self.make_branch_and_tree('.')
 
208
        self.branch = self.wt.branch
 
209
        print >> open('file', 'wb'), 'foo'
 
210
        print >> open('binfile', 'wb'), 'foo'
 
211
        self.wt.add(['file'], ['fileid'])
 
212
        self.wt.add(['binfile'], ['binfileid'])
 
213
        if has_symlinks():
 
214
            os.symlink('target1', 'symlink')
 
215
            self.wt.add(['symlink'], ['linkid'])
 
216
        self.wt.commit('message_1', rev_id = '1')
 
217
        print >> open('file', 'wb'), 'bar'
 
218
        print >> open('binfile', 'wb'), 'x' * 1023 + '\x00'
 
219
        if has_symlinks():
 
220
            os.unlink('symlink')
 
221
            os.symlink('target2', 'symlink')
 
222
        self.tree_1 = self.branch.repository.revision_tree('1')
 
223
        self.inv_1 = self.branch.repository.get_inventory('1')
 
224
        self.file_1 = self.inv_1['fileid']
 
225
        self.file_1b = self.inv_1['binfileid']
 
226
        self.tree_2 = self.wt
 
227
        self.inv_2 = self.tree_2.read_working_inventory()
 
228
        self.file_2 = self.inv_2['fileid']
 
229
        self.file_2b = self.inv_2['binfileid']
 
230
        if has_symlinks():
 
231
            self.link_1 = self.inv_1['linkid']
 
232
            self.link_2 = self.inv_2['linkid']
 
233
 
 
234
    def test_file_diff_deleted(self):
 
235
        output = StringIO()
 
236
        self.file_1.diff(internal_diff, 
 
237
                          "old_label", self.tree_1,
 
238
                          "/dev/null", None, None,
 
239
                          output)
 
240
        self.assertEqual(output.getvalue(), "--- old_label\n"
 
241
                                            "+++ /dev/null\n"
 
242
                                            "@@ -1,1 +0,0 @@\n"
 
243
                                            "-foo\n"
 
244
                                            "\n")
 
245
 
 
246
    def test_file_diff_added(self):
 
247
        output = StringIO()
 
248
        self.file_1.diff(internal_diff, 
 
249
                          "new_label", self.tree_1,
 
250
                          "/dev/null", None, None,
 
251
                          output, reverse=True)
 
252
        self.assertEqual(output.getvalue(), "--- /dev/null\n"
 
253
                                            "+++ new_label\n"
 
254
                                            "@@ -0,0 +1,1 @@\n"
 
255
                                            "+foo\n"
 
256
                                            "\n")
 
257
 
 
258
    def test_file_diff_changed(self):
 
259
        output = StringIO()
 
260
        self.file_1.diff(internal_diff, 
 
261
                          "/dev/null", self.tree_1, 
 
262
                          "new_label", self.file_2, self.tree_2,
 
263
                          output)
 
264
        self.assertEqual(output.getvalue(), "--- /dev/null\n"
 
265
                                            "+++ new_label\n"
 
266
                                            "@@ -1,1 +1,1 @@\n"
 
267
                                            "-foo\n"
 
268
                                            "+bar\n"
 
269
                                            "\n")
 
270
        
 
271
    def test_file_diff_binary(self):
 
272
        output = StringIO()
 
273
        self.file_1.diff(internal_diff, 
 
274
                          "/dev/null", self.tree_1, 
 
275
                          "new_label", self.file_2b, self.tree_2,
 
276
                          output)
 
277
        self.assertEqual(output.getvalue(), 
 
278
                         "Binary files /dev/null and new_label differ\n")
 
279
    def test_link_diff_deleted(self):
 
280
        if not has_symlinks():
 
281
            return
 
282
        output = StringIO()
 
283
        self.link_1.diff(internal_diff, 
 
284
                          "old_label", self.tree_1,
 
285
                          "/dev/null", None, None,
 
286
                          output)
 
287
        self.assertEqual(output.getvalue(),
 
288
                         "=== target was 'target1'\n")
 
289
 
 
290
    def test_link_diff_added(self):
 
291
        if not has_symlinks():
 
292
            return
 
293
        output = StringIO()
 
294
        self.link_1.diff(internal_diff, 
 
295
                          "new_label", self.tree_1,
 
296
                          "/dev/null", None, None,
 
297
                          output, reverse=True)
 
298
        self.assertEqual(output.getvalue(),
 
299
                         "=== target is 'target1'\n")
 
300
 
 
301
    def test_link_diff_changed(self):
 
302
        if not has_symlinks():
 
303
            return
 
304
        output = StringIO()
 
305
        self.link_1.diff(internal_diff, 
 
306
                          "/dev/null", self.tree_1, 
 
307
                          "new_label", self.link_2, self.tree_2,
 
308
                          output)
 
309
        self.assertEqual(output.getvalue(),
 
310
                         "=== target changed 'target1' => 'target2'\n")
 
311
 
 
312
 
 
313
class TestSnapshot(TestCaseWithTransport):
 
314
 
 
315
    def setUp(self):
 
316
        # for full testing we'll need a branch
 
317
        # with a subdir to test parent changes.
 
318
        # and a file, link and dir under that.
 
319
        # but right now I only need one attribute
 
320
        # to change, and then test merge patterns
 
321
        # with fake parent entries.
 
322
        super(TestSnapshot, self).setUp()
 
323
        self.wt = self.make_branch_and_tree('.')
 
324
        self.branch = self.wt.branch
 
325
        self.build_tree(['subdir/', 'subdir/file'], line_endings='binary')
 
326
        self.wt.add(['subdir', 'subdir/file'],
 
327
                                       ['dirid', 'fileid'])
 
328
        if has_symlinks():
 
329
            pass
 
330
        self.wt.commit('message_1', rev_id = '1')
 
331
        self.tree_1 = self.branch.repository.revision_tree('1')
 
332
        self.inv_1 = self.branch.repository.get_inventory('1')
 
333
        self.file_1 = self.inv_1['fileid']
 
334
        self.file_active = self.wt.inventory['fileid']
 
335
        self.builder = self.branch.get_commit_builder([], timestamp=time.time(), revision_id='2')
 
336
 
 
337
    def test_snapshot_new_revision(self):
 
338
        # This tests that a simple commit with no parents makes a new
 
339
        # revision value in the inventory entry
 
340
        self.file_active.snapshot('2', 'subdir/file', {}, self.wt, self.builder)
 
341
        # expected outcome - file_1 has a revision id of '2', and we can get
 
342
        # its text of 'file contents' out of the weave.
 
343
        self.assertEqual(self.file_1.revision, '1')
 
344
        self.assertEqual(self.file_active.revision, '2')
 
345
        # this should be a separate test probably, but lets check it once..
 
346
        lines = self.branch.repository.weave_store.get_weave(
 
347
            'fileid', 
 
348
            self.branch.get_transaction()).get_lines('2')
 
349
        self.assertEqual(lines, ['contents of subdir/file\n'])
 
350
 
 
351
    def test_snapshot_unchanged(self):
 
352
        #This tests that a simple commit does not make a new entry for
 
353
        # an unchanged inventory entry
 
354
        self.file_active.snapshot('2', 'subdir/file', {'1':self.file_1},
 
355
                                  self.wt, self.builder)
 
356
        self.assertEqual(self.file_1.revision, '1')
 
357
        self.assertEqual(self.file_active.revision, '1')
 
358
        vf = self.branch.repository.weave_store.get_weave(
 
359
            'fileid', 
 
360
            self.branch.repository.get_transaction())
 
361
        self.assertRaises(errors.RevisionNotPresent,
 
362
                          vf.get_lines,
 
363
                          '2')
 
364
 
 
365
    def test_snapshot_merge_identical_different_revid(self):
 
366
        # This tests that a commit with two identical parents, one of which has
 
367
        # a different revision id, results in a new revision id in the entry.
 
368
        # 1->other, commit a merge of other against 1, results in 2.
 
369
        other_ie = inventory.InventoryFile('fileid', 'newname', self.file_1.parent_id)
 
370
        other_ie = inventory.InventoryFile('fileid', 'file', self.file_1.parent_id)
 
371
        other_ie.revision = '1'
 
372
        other_ie.text_sha1 = self.file_1.text_sha1
 
373
        other_ie.text_size = self.file_1.text_size
 
374
        self.assertEqual(self.file_1, other_ie)
 
375
        other_ie.revision = 'other'
 
376
        self.assertNotEqual(self.file_1, other_ie)
 
377
        versionfile = self.branch.repository.weave_store.get_weave(
 
378
            'fileid', self.branch.repository.get_transaction())
 
379
        versionfile.clone_text('other', '1', ['1'])
 
380
        self.file_active.snapshot('2', 'subdir/file', 
 
381
                                  {'1':self.file_1, 'other':other_ie},
 
382
                                  self.wt, self.builder)
 
383
        self.assertEqual(self.file_active.revision, '2')
 
384
 
 
385
    def test_snapshot_changed(self):
 
386
        # This tests that a commit with one different parent results in a new
 
387
        # revision id in the entry.
 
388
        self.file_active.name='newname'
 
389
        rename('subdir/file', 'subdir/newname')
 
390
        self.file_active.snapshot('2', 'subdir/newname', {'1':self.file_1}, 
 
391
                                  self.wt, self.builder)
 
392
        # expected outcome - file_1 has a revision id of '2'
 
393
        self.assertEqual(self.file_active.revision, '2')
 
394
 
 
395
 
 
396
class TestPreviousHeads(TestCaseWithTransport):
 
397
 
 
398
    def setUp(self):
 
399
        # we want several inventories, that respectively
 
400
        # give use the following scenarios:
 
401
        # A) fileid not in any inventory (A),
 
402
        # B) fileid present in one inventory (B) and (A,B)
 
403
        # C) fileid present in two inventories, and they
 
404
        #   are not mutual descendents (B, C)
 
405
        # D) fileid present in two inventories and one is
 
406
        #   a descendent of the other. (B, D)
 
407
        super(TestPreviousHeads, self).setUp()
 
408
        self.wt = self.make_branch_and_tree('.')
 
409
        self.branch = self.wt.branch
 
410
        self.build_tree(['file'])
 
411
        self.wt.commit('new branch', allow_pointless=True, rev_id='A')
 
412
        self.inv_A = self.branch.repository.get_inventory('A')
 
413
        self.wt.add(['file'], ['fileid'])
 
414
        self.wt.commit('add file', rev_id='B')
 
415
        self.inv_B = self.branch.repository.get_inventory('B')
 
416
        uncommit(self.branch, tree=self.wt)
 
417
        self.assertEqual(self.branch.revision_history(), ['A'])
 
418
        self.wt.commit('another add of file', rev_id='C')
 
419
        self.inv_C = self.branch.repository.get_inventory('C')
 
420
        self.wt.add_pending_merge('B')
 
421
        self.wt.commit('merge in B', rev_id='D')
 
422
        self.inv_D = self.branch.repository.get_inventory('D')
 
423
        self.file_active = self.wt.inventory['fileid']
 
424
        self.weave = self.branch.repository.weave_store.get_weave('fileid',
 
425
            self.branch.repository.get_transaction())
 
426
        
 
427
    def get_previous_heads(self, inventories):
 
428
        return self.file_active.find_previous_heads(
 
429
            inventories, 
 
430
            self.branch.repository.weave_store,
 
431
            self.branch.repository.get_transaction())
 
432
        
 
433
    def test_fileid_in_no_inventory(self):
 
434
        self.assertEqual({}, self.get_previous_heads([self.inv_A]))
 
435
 
 
436
    def test_fileid_in_one_inventory(self):
 
437
        self.assertEqual({'B':self.inv_B['fileid']},
 
438
                         self.get_previous_heads([self.inv_B]))
 
439
        self.assertEqual({'B':self.inv_B['fileid']},
 
440
                         self.get_previous_heads([self.inv_A, self.inv_B]))
 
441
        self.assertEqual({'B':self.inv_B['fileid']},
 
442
                         self.get_previous_heads([self.inv_B, self.inv_A]))
 
443
 
 
444
    def test_fileid_in_two_inventories_gives_both_entries(self):
 
445
        self.assertEqual({'B':self.inv_B['fileid'],
 
446
                          'C':self.inv_C['fileid']},
 
447
                          self.get_previous_heads([self.inv_B, self.inv_C]))
 
448
        self.assertEqual({'B':self.inv_B['fileid'],
 
449
                          'C':self.inv_C['fileid']},
 
450
                          self.get_previous_heads([self.inv_C, self.inv_B]))
 
451
 
 
452
    def test_fileid_in_two_inventories_already_merged_gives_head(self):
 
453
        self.assertEqual({'D':self.inv_D['fileid']},
 
454
                         self.get_previous_heads([self.inv_B, self.inv_D]))
 
455
        self.assertEqual({'D':self.inv_D['fileid']},
 
456
                         self.get_previous_heads([self.inv_D, self.inv_B]))
 
457
 
 
458
    # TODO: test two inventories with the same file revision 
585
459
 
586
460
 
587
461
class TestDescribeChanges(TestCase):
614
488
        # perhaps a bit questionable but seems like the most reasonable thing...
615
489
        self.assertChangeDescription('unchanged', None, None)
616
490
 
617
 
        # in this case it's both renamed and modified; show a rename and
 
491
        # in this case it's both renamed and modified; show a rename and 
618
492
        # modification:
619
493
        new_a.name = 'newfilename'
620
494
        self.assertChangeDescription('modified and renamed', old_a, new_a)
642
516
        self.assertEqual(expected_change, change)
643
517
 
644
518
 
645
 
class TestCHKInventory(TestCaseWithTransport):
646
 
 
647
 
    def get_chk_bytes(self):
648
 
        # The easiest way to get a CHK store is a development6 repository and
649
 
        # then work with the chk_bytes attribute directly.
650
 
        repo = self.make_repository(".", format="development6-rich-root")
651
 
        repo.lock_write()
652
 
        self.addCleanup(repo.unlock)
653
 
        repo.start_write_group()
654
 
        self.addCleanup(repo.abort_write_group)
655
 
        return repo.chk_bytes
656
 
 
657
 
    def read_bytes(self, chk_bytes, key):
658
 
        stream = chk_bytes.get_record_stream([key], 'unordered', True)
659
 
        return stream.next().get_bytes_as("fulltext")
660
 
 
661
 
    def test_deserialise_gives_CHKInventory(self):
662
 
        inv = Inventory()
663
 
        inv.revision_id = "revid"
664
 
        inv.root.revision = "rootrev"
665
 
        chk_bytes = self.get_chk_bytes()
666
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv)
667
 
        bytes = ''.join(chk_inv.to_lines())
668
 
        new_inv = CHKInventory.deserialise(chk_bytes, bytes, ("revid",))
669
 
        self.assertEqual("revid", new_inv.revision_id)
670
 
        self.assertEqual("directory", new_inv.root.kind)
671
 
        self.assertEqual(inv.root.file_id, new_inv.root.file_id)
672
 
        self.assertEqual(inv.root.parent_id, new_inv.root.parent_id)
673
 
        self.assertEqual(inv.root.name, new_inv.root.name)
674
 
        self.assertEqual("rootrev", new_inv.root.revision)
675
 
        self.assertEqual('plain', new_inv._search_key_name)
676
 
 
677
 
    def test_deserialise_wrong_revid(self):
678
 
        inv = Inventory()
679
 
        inv.revision_id = "revid"
680
 
        inv.root.revision = "rootrev"
681
 
        chk_bytes = self.get_chk_bytes()
682
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv)
683
 
        bytes = ''.join(chk_inv.to_lines())
684
 
        self.assertRaises(ValueError, CHKInventory.deserialise, chk_bytes,
685
 
            bytes, ("revid2",))
686
 
 
687
 
    def test_captures_rev_root_byid(self):
688
 
        inv = Inventory()
689
 
        inv.revision_id = "foo"
690
 
        inv.root.revision = "bar"
691
 
        chk_bytes = self.get_chk_bytes()
692
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv)
693
 
        lines = chk_inv.to_lines()
694
 
        self.assertEqual([
695
 
            'chkinventory:\n',
696
 
            'revision_id: foo\n',
697
 
            'root_id: TREE_ROOT\n',
698
 
            'parent_id_basename_to_file_id: sha1:eb23f0ad4b07f48e88c76d4c94292be57fb2785f\n',
699
 
            'id_to_entry: sha1:debfe920f1f10e7929260f0534ac9a24d7aabbb4\n',
700
 
            ], lines)
701
 
        chk_inv = CHKInventory.deserialise(chk_bytes, ''.join(lines), ('foo',))
702
 
        self.assertEqual('plain', chk_inv._search_key_name)
703
 
 
704
 
    def test_captures_parent_id_basename_index(self):
705
 
        inv = Inventory()
706
 
        inv.revision_id = "foo"
707
 
        inv.root.revision = "bar"
708
 
        chk_bytes = self.get_chk_bytes()
709
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv)
710
 
        lines = chk_inv.to_lines()
711
 
        self.assertEqual([
712
 
            'chkinventory:\n',
713
 
            'revision_id: foo\n',
714
 
            'root_id: TREE_ROOT\n',
715
 
            'parent_id_basename_to_file_id: sha1:eb23f0ad4b07f48e88c76d4c94292be57fb2785f\n',
716
 
            'id_to_entry: sha1:debfe920f1f10e7929260f0534ac9a24d7aabbb4\n',
717
 
            ], lines)
718
 
        chk_inv = CHKInventory.deserialise(chk_bytes, ''.join(lines), ('foo',))
719
 
        self.assertEqual('plain', chk_inv._search_key_name)
720
 
 
721
 
    def test_captures_search_key_name(self):
722
 
        inv = Inventory()
723
 
        inv.revision_id = "foo"
724
 
        inv.root.revision = "bar"
725
 
        chk_bytes = self.get_chk_bytes()
726
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv,
727
 
                                              search_key_name='hash-16-way')
728
 
        lines = chk_inv.to_lines()
729
 
        self.assertEqual([
730
 
            'chkinventory:\n',
731
 
            'search_key_name: hash-16-way\n',
732
 
            'root_id: TREE_ROOT\n',
733
 
            'parent_id_basename_to_file_id: sha1:eb23f0ad4b07f48e88c76d4c94292be57fb2785f\n',
734
 
            'revision_id: foo\n',
735
 
            'id_to_entry: sha1:debfe920f1f10e7929260f0534ac9a24d7aabbb4\n',
736
 
            ], lines)
737
 
        chk_inv = CHKInventory.deserialise(chk_bytes, ''.join(lines), ('foo',))
738
 
        self.assertEqual('hash-16-way', chk_inv._search_key_name)
739
 
 
740
 
    def test_directory_children_on_demand(self):
741
 
        inv = Inventory()
742
 
        inv.revision_id = "revid"
743
 
        inv.root.revision = "rootrev"
744
 
        inv.add(InventoryFile("fileid", "file", inv.root.file_id))
745
 
        inv["fileid"].revision = "filerev"
746
 
        inv["fileid"].executable = True
747
 
        inv["fileid"].text_sha1 = "ffff"
748
 
        inv["fileid"].text_size = 1
749
 
        chk_bytes = self.get_chk_bytes()
750
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv)
751
 
        bytes = ''.join(chk_inv.to_lines())
752
 
        new_inv = CHKInventory.deserialise(chk_bytes, bytes, ("revid",))
753
 
        root_entry = new_inv[inv.root.file_id]
754
 
        self.assertEqual(None, root_entry._children)
755
 
        self.assertEqual(['file'], root_entry.children.keys())
756
 
        file_direct = new_inv["fileid"]
757
 
        file_found = root_entry.children['file']
758
 
        self.assertEqual(file_direct.kind, file_found.kind)
759
 
        self.assertEqual(file_direct.file_id, file_found.file_id)
760
 
        self.assertEqual(file_direct.parent_id, file_found.parent_id)
761
 
        self.assertEqual(file_direct.name, file_found.name)
762
 
        self.assertEqual(file_direct.revision, file_found.revision)
763
 
        self.assertEqual(file_direct.text_sha1, file_found.text_sha1)
764
 
        self.assertEqual(file_direct.text_size, file_found.text_size)
765
 
        self.assertEqual(file_direct.executable, file_found.executable)
766
 
 
767
 
    def test_from_inventory_maximum_size(self):
768
 
        # from_inventory supports the maximum_size parameter.
769
 
        inv = Inventory()
770
 
        inv.revision_id = "revid"
771
 
        inv.root.revision = "rootrev"
772
 
        chk_bytes = self.get_chk_bytes()
773
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv, 120)
774
 
        chk_inv.id_to_entry._ensure_root()
775
 
        self.assertEqual(120, chk_inv.id_to_entry._root_node.maximum_size)
776
 
        self.assertEqual(1, chk_inv.id_to_entry._root_node._key_width)
777
 
        p_id_basename = chk_inv.parent_id_basename_to_file_id
778
 
        p_id_basename._ensure_root()
779
 
        self.assertEqual(120, p_id_basename._root_node.maximum_size)
780
 
        self.assertEqual(2, p_id_basename._root_node._key_width)
781
 
 
782
 
    def test___iter__(self):
783
 
        inv = Inventory()
784
 
        inv.revision_id = "revid"
785
 
        inv.root.revision = "rootrev"
786
 
        inv.add(InventoryFile("fileid", "file", inv.root.file_id))
787
 
        inv["fileid"].revision = "filerev"
788
 
        inv["fileid"].executable = True
789
 
        inv["fileid"].text_sha1 = "ffff"
790
 
        inv["fileid"].text_size = 1
791
 
        chk_bytes = self.get_chk_bytes()
792
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv)
793
 
        bytes = ''.join(chk_inv.to_lines())
794
 
        new_inv = CHKInventory.deserialise(chk_bytes, bytes, ("revid",))
795
 
        fileids = list(new_inv.__iter__())
796
 
        fileids.sort()
797
 
        self.assertEqual([inv.root.file_id, "fileid"], fileids)
798
 
 
799
 
    def test__len__(self):
800
 
        inv = Inventory()
801
 
        inv.revision_id = "revid"
802
 
        inv.root.revision = "rootrev"
803
 
        inv.add(InventoryFile("fileid", "file", inv.root.file_id))
804
 
        inv["fileid"].revision = "filerev"
805
 
        inv["fileid"].executable = True
806
 
        inv["fileid"].text_sha1 = "ffff"
807
 
        inv["fileid"].text_size = 1
808
 
        chk_bytes = self.get_chk_bytes()
809
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv)
810
 
        self.assertEqual(2, len(chk_inv))
811
 
 
812
 
    def test___getitem__(self):
813
 
        inv = Inventory()
814
 
        inv.revision_id = "revid"
815
 
        inv.root.revision = "rootrev"
816
 
        inv.add(InventoryFile("fileid", "file", inv.root.file_id))
817
 
        inv["fileid"].revision = "filerev"
818
 
        inv["fileid"].executable = True
819
 
        inv["fileid"].text_sha1 = "ffff"
820
 
        inv["fileid"].text_size = 1
821
 
        chk_bytes = self.get_chk_bytes()
822
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv)
823
 
        bytes = ''.join(chk_inv.to_lines())
824
 
        new_inv = CHKInventory.deserialise(chk_bytes, bytes, ("revid",))
825
 
        root_entry = new_inv[inv.root.file_id]
826
 
        file_entry = new_inv["fileid"]
827
 
        self.assertEqual("directory", root_entry.kind)
828
 
        self.assertEqual(inv.root.file_id, root_entry.file_id)
829
 
        self.assertEqual(inv.root.parent_id, root_entry.parent_id)
830
 
        self.assertEqual(inv.root.name, root_entry.name)
831
 
        self.assertEqual("rootrev", root_entry.revision)
832
 
        self.assertEqual("file", file_entry.kind)
833
 
        self.assertEqual("fileid", file_entry.file_id)
834
 
        self.assertEqual(inv.root.file_id, file_entry.parent_id)
835
 
        self.assertEqual("file", file_entry.name)
836
 
        self.assertEqual("filerev", file_entry.revision)
837
 
        self.assertEqual("ffff", file_entry.text_sha1)
838
 
        self.assertEqual(1, file_entry.text_size)
839
 
        self.assertEqual(True, file_entry.executable)
840
 
        self.assertRaises(errors.NoSuchId, new_inv.__getitem__, 'missing')
841
 
 
842
 
    def test_has_id_true(self):
843
 
        inv = Inventory()
844
 
        inv.revision_id = "revid"
845
 
        inv.root.revision = "rootrev"
846
 
        inv.add(InventoryFile("fileid", "file", inv.root.file_id))
847
 
        inv["fileid"].revision = "filerev"
848
 
        inv["fileid"].executable = True
849
 
        inv["fileid"].text_sha1 = "ffff"
850
 
        inv["fileid"].text_size = 1
851
 
        chk_bytes = self.get_chk_bytes()
852
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv)
853
 
        self.assertTrue(chk_inv.has_id('fileid'))
854
 
        self.assertTrue(chk_inv.has_id(inv.root.file_id))
855
 
 
856
 
    def test_has_id_not(self):
857
 
        inv = Inventory()
858
 
        inv.revision_id = "revid"
859
 
        inv.root.revision = "rootrev"
860
 
        chk_bytes = self.get_chk_bytes()
861
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv)
862
 
        self.assertFalse(chk_inv.has_id('fileid'))
863
 
 
864
 
    def test_id2path(self):
865
 
        inv = Inventory()
866
 
        inv.revision_id = "revid"
867
 
        inv.root.revision = "rootrev"
868
 
        direntry = InventoryDirectory("dirid", "dir", inv.root.file_id)
869
 
        fileentry = InventoryFile("fileid", "file", "dirid")
870
 
        inv.add(direntry)
871
 
        inv.add(fileentry)
872
 
        inv["fileid"].revision = "filerev"
873
 
        inv["fileid"].executable = True
874
 
        inv["fileid"].text_sha1 = "ffff"
875
 
        inv["fileid"].text_size = 1
876
 
        inv["dirid"].revision = "filerev"
877
 
        chk_bytes = self.get_chk_bytes()
878
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv)
879
 
        bytes = ''.join(chk_inv.to_lines())
880
 
        new_inv = CHKInventory.deserialise(chk_bytes, bytes, ("revid",))
881
 
        self.assertEqual('', new_inv.id2path(inv.root.file_id))
882
 
        self.assertEqual('dir', new_inv.id2path('dirid'))
883
 
        self.assertEqual('dir/file', new_inv.id2path('fileid'))
884
 
 
885
 
    def test_path2id(self):
886
 
        inv = Inventory()
887
 
        inv.revision_id = "revid"
888
 
        inv.root.revision = "rootrev"
889
 
        direntry = InventoryDirectory("dirid", "dir", inv.root.file_id)
890
 
        fileentry = InventoryFile("fileid", "file", "dirid")
891
 
        inv.add(direntry)
892
 
        inv.add(fileentry)
893
 
        inv["fileid"].revision = "filerev"
894
 
        inv["fileid"].executable = True
895
 
        inv["fileid"].text_sha1 = "ffff"
896
 
        inv["fileid"].text_size = 1
897
 
        inv["dirid"].revision = "filerev"
898
 
        chk_bytes = self.get_chk_bytes()
899
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv)
900
 
        bytes = ''.join(chk_inv.to_lines())
901
 
        new_inv = CHKInventory.deserialise(chk_bytes, bytes, ("revid",))
902
 
        self.assertEqual(inv.root.file_id, new_inv.path2id(''))
903
 
        self.assertEqual('dirid', new_inv.path2id('dir'))
904
 
        self.assertEqual('fileid', new_inv.path2id('dir/file'))
905
 
 
906
 
    def test_create_by_apply_delta_sets_root(self):
907
 
        inv = Inventory()
908
 
        inv.revision_id = "revid"
909
 
        chk_bytes = self.get_chk_bytes()
910
 
        base_inv = CHKInventory.from_inventory(chk_bytes, inv)
911
 
        inv.add_path("", "directory", "myrootid", None)
912
 
        inv.revision_id = "expectedid"
913
 
        reference_inv = CHKInventory.from_inventory(chk_bytes, inv)
914
 
        delta = [("", None, base_inv.root.file_id, None),
915
 
            (None, "",  "myrootid", inv.root)]
916
 
        new_inv = base_inv.create_by_apply_delta(delta, "expectedid")
917
 
        self.assertEquals(reference_inv.root, new_inv.root)
918
 
 
919
 
    def test_create_by_apply_delta_empty_add_child(self):
920
 
        inv = Inventory()
921
 
        inv.revision_id = "revid"
922
 
        inv.root.revision = "rootrev"
923
 
        chk_bytes = self.get_chk_bytes()
924
 
        base_inv = CHKInventory.from_inventory(chk_bytes, inv)
925
 
        a_entry = InventoryFile("A-id", "A", inv.root.file_id)
926
 
        a_entry.revision = "filerev"
927
 
        a_entry.executable = True
928
 
        a_entry.text_sha1 = "ffff"
929
 
        a_entry.text_size = 1
930
 
        inv.add(a_entry)
931
 
        inv.revision_id = "expectedid"
932
 
        reference_inv = CHKInventory.from_inventory(chk_bytes, inv)
933
 
        delta = [(None, "A",  "A-id", a_entry)]
934
 
        new_inv = base_inv.create_by_apply_delta(delta, "expectedid")
935
 
        # new_inv should be the same as reference_inv.
936
 
        self.assertEqual(reference_inv.revision_id, new_inv.revision_id)
937
 
        self.assertEqual(reference_inv.root_id, new_inv.root_id)
938
 
        reference_inv.id_to_entry._ensure_root()
939
 
        new_inv.id_to_entry._ensure_root()
940
 
        self.assertEqual(reference_inv.id_to_entry._root_node._key,
941
 
            new_inv.id_to_entry._root_node._key)
942
 
 
943
 
    def test_create_by_apply_delta_empty_add_child_updates_parent_id(self):
944
 
        inv = Inventory()
945
 
        inv.revision_id = "revid"
946
 
        inv.root.revision = "rootrev"
947
 
        chk_bytes = self.get_chk_bytes()
948
 
        base_inv = CHKInventory.from_inventory(chk_bytes, inv)
949
 
        a_entry = InventoryFile("A-id", "A", inv.root.file_id)
950
 
        a_entry.revision = "filerev"
951
 
        a_entry.executable = True
952
 
        a_entry.text_sha1 = "ffff"
953
 
        a_entry.text_size = 1
954
 
        inv.add(a_entry)
955
 
        inv.revision_id = "expectedid"
956
 
        reference_inv = CHKInventory.from_inventory(chk_bytes, inv)
957
 
        delta = [(None, "A",  "A-id", a_entry)]
958
 
        new_inv = base_inv.create_by_apply_delta(delta, "expectedid")
959
 
        reference_inv.id_to_entry._ensure_root()
960
 
        reference_inv.parent_id_basename_to_file_id._ensure_root()
961
 
        new_inv.id_to_entry._ensure_root()
962
 
        new_inv.parent_id_basename_to_file_id._ensure_root()
963
 
        # new_inv should be the same as reference_inv.
964
 
        self.assertEqual(reference_inv.revision_id, new_inv.revision_id)
965
 
        self.assertEqual(reference_inv.root_id, new_inv.root_id)
966
 
        self.assertEqual(reference_inv.id_to_entry._root_node._key,
967
 
            new_inv.id_to_entry._root_node._key)
968
 
        self.assertEqual(reference_inv.parent_id_basename_to_file_id._root_node._key,
969
 
            new_inv.parent_id_basename_to_file_id._root_node._key)
970
 
 
971
 
    def test_iter_changes(self):
972
 
        # Low level bootstrapping smoke test; comprehensive generic tests via
973
 
        # InterTree are coming.
974
 
        inv = Inventory()
975
 
        inv.revision_id = "revid"
976
 
        inv.root.revision = "rootrev"
977
 
        inv.add(InventoryFile("fileid", "file", inv.root.file_id))
978
 
        inv["fileid"].revision = "filerev"
979
 
        inv["fileid"].executable = True
980
 
        inv["fileid"].text_sha1 = "ffff"
981
 
        inv["fileid"].text_size = 1
982
 
        inv2 = Inventory()
983
 
        inv2.revision_id = "revid2"
984
 
        inv2.root.revision = "rootrev"
985
 
        inv2.add(InventoryFile("fileid", "file", inv.root.file_id))
986
 
        inv2["fileid"].revision = "filerev2"
987
 
        inv2["fileid"].executable = False
988
 
        inv2["fileid"].text_sha1 = "bbbb"
989
 
        inv2["fileid"].text_size = 2
990
 
        # get fresh objects.
991
 
        chk_bytes = self.get_chk_bytes()
992
 
        chk_inv = CHKInventory.from_inventory(chk_bytes, inv)
993
 
        bytes = ''.join(chk_inv.to_lines())
994
 
        inv_1 = CHKInventory.deserialise(chk_bytes, bytes, ("revid",))
995
 
        chk_inv2 = CHKInventory.from_inventory(chk_bytes, inv2)
996
 
        bytes = ''.join(chk_inv2.to_lines())
997
 
        inv_2 = CHKInventory.deserialise(chk_bytes, bytes, ("revid2",))
998
 
        self.assertEqual([('fileid', (u'file', u'file'), True, (True, True),
999
 
            ('TREE_ROOT', 'TREE_ROOT'), (u'file', u'file'), ('file', 'file'),
1000
 
            (False, True))],
1001
 
            list(inv_1.iter_changes(inv_2)))
1002
 
 
1003
 
    def test_parent_id_basename_to_file_id_index_enabled(self):
1004
 
        inv = Inventory()
1005
 
        inv.revision_id = "revid"
1006
 
        inv.root.revision = "rootrev"
1007
 
        inv.add(InventoryFile("fileid", "file", inv.root.file_id))
1008
 
        inv["fileid"].revision = "filerev"
1009
 
        inv["fileid"].executable = True
1010
 
        inv["fileid"].text_sha1 = "ffff"
1011
 
        inv["fileid"].text_size = 1
1012
 
        # get fresh objects.
1013
 
        chk_bytes = self.get_chk_bytes()
1014
 
        tmp_inv = CHKInventory.from_inventory(chk_bytes, inv)
1015
 
        bytes = ''.join(tmp_inv.to_lines())
1016
 
        chk_inv = CHKInventory.deserialise(chk_bytes, bytes, ("revid",))
1017
 
        self.assertIsInstance(chk_inv.parent_id_basename_to_file_id, chk_map.CHKMap)
1018
 
        self.assertEqual(
1019
 
            {('', ''): 'TREE_ROOT', ('TREE_ROOT', 'file'): 'fileid'},
1020
 
            dict(chk_inv.parent_id_basename_to_file_id.iteritems()))
1021
 
 
1022
 
    def test_file_entry_to_bytes(self):
1023
 
        inv = CHKInventory(None)
1024
 
        ie = inventory.InventoryFile('file-id', 'filename', 'parent-id')
1025
 
        ie.executable = True
1026
 
        ie.revision = 'file-rev-id'
1027
 
        ie.text_sha1 = 'abcdefgh'
1028
 
        ie.text_size = 100
1029
 
        bytes = inv._entry_to_bytes(ie)
1030
 
        self.assertEqual('file: file-id\nparent-id\nfilename\n'
1031
 
                         'file-rev-id\nabcdefgh\n100\nY', bytes)
1032
 
        ie2 = inv._bytes_to_entry(bytes)
1033
 
        self.assertEqual(ie, ie2)
1034
 
        self.assertIsInstance(ie2.name, unicode)
1035
 
        self.assertEqual(('filename', 'file-id', 'file-rev-id'),
1036
 
                         inv._bytes_to_utf8name_key(bytes))
1037
 
 
1038
 
    def test_file2_entry_to_bytes(self):
1039
 
        inv = CHKInventory(None)
1040
 
        # \u30a9 == 'omega'
1041
 
        ie = inventory.InventoryFile('file-id', u'\u03a9name', 'parent-id')
1042
 
        ie.executable = False
1043
 
        ie.revision = 'file-rev-id'
1044
 
        ie.text_sha1 = '123456'
1045
 
        ie.text_size = 25
1046
 
        bytes = inv._entry_to_bytes(ie)
1047
 
        self.assertEqual('file: file-id\nparent-id\n\xce\xa9name\n'
1048
 
                         'file-rev-id\n123456\n25\nN', bytes)
1049
 
        ie2 = inv._bytes_to_entry(bytes)
1050
 
        self.assertEqual(ie, ie2)
1051
 
        self.assertIsInstance(ie2.name, unicode)
1052
 
        self.assertEqual(('\xce\xa9name', 'file-id', 'file-rev-id'),
1053
 
                         inv._bytes_to_utf8name_key(bytes))
1054
 
 
1055
 
    def test_dir_entry_to_bytes(self):
1056
 
        inv = CHKInventory(None)
1057
 
        ie = inventory.InventoryDirectory('dir-id', 'dirname', 'parent-id')
1058
 
        ie.revision = 'dir-rev-id'
1059
 
        bytes = inv._entry_to_bytes(ie)
1060
 
        self.assertEqual('dir: dir-id\nparent-id\ndirname\ndir-rev-id', bytes)
1061
 
        ie2 = inv._bytes_to_entry(bytes)
1062
 
        self.assertEqual(ie, ie2)
1063
 
        self.assertIsInstance(ie2.name, unicode)
1064
 
        self.assertEqual(('dirname', 'dir-id', 'dir-rev-id'),
1065
 
                         inv._bytes_to_utf8name_key(bytes))
1066
 
 
1067
 
    def test_dir2_entry_to_bytes(self):
1068
 
        inv = CHKInventory(None)
1069
 
        ie = inventory.InventoryDirectory('dir-id', u'dir\u03a9name',
1070
 
                                          None)
1071
 
        ie.revision = 'dir-rev-id'
1072
 
        bytes = inv._entry_to_bytes(ie)
1073
 
        self.assertEqual('dir: dir-id\n\ndir\xce\xa9name\n'
1074
 
                         'dir-rev-id', bytes)
1075
 
        ie2 = inv._bytes_to_entry(bytes)
1076
 
        self.assertEqual(ie, ie2)
1077
 
        self.assertIsInstance(ie2.name, unicode)
1078
 
        self.assertIs(ie2.parent_id, None)
1079
 
        self.assertEqual(('dir\xce\xa9name', 'dir-id', 'dir-rev-id'),
1080
 
                         inv._bytes_to_utf8name_key(bytes))
1081
 
 
1082
 
    def test_symlink_entry_to_bytes(self):
1083
 
        inv = CHKInventory(None)
1084
 
        ie = inventory.InventoryLink('link-id', 'linkname', 'parent-id')
1085
 
        ie.revision = 'link-rev-id'
1086
 
        ie.symlink_target = u'target/path'
1087
 
        bytes = inv._entry_to_bytes(ie)
1088
 
        self.assertEqual('symlink: link-id\nparent-id\nlinkname\n'
1089
 
                         'link-rev-id\ntarget/path', bytes)
1090
 
        ie2 = inv._bytes_to_entry(bytes)
1091
 
        self.assertEqual(ie, ie2)
1092
 
        self.assertIsInstance(ie2.name, unicode)
1093
 
        self.assertIsInstance(ie2.symlink_target, unicode)
1094
 
        self.assertEqual(('linkname', 'link-id', 'link-rev-id'),
1095
 
                         inv._bytes_to_utf8name_key(bytes))
1096
 
 
1097
 
    def test_symlink2_entry_to_bytes(self):
1098
 
        inv = CHKInventory(None)
1099
 
        ie = inventory.InventoryLink('link-id', u'link\u03a9name', 'parent-id')
1100
 
        ie.revision = 'link-rev-id'
1101
 
        ie.symlink_target = u'target/\u03a9path'
1102
 
        bytes = inv._entry_to_bytes(ie)
1103
 
        self.assertEqual('symlink: link-id\nparent-id\nlink\xce\xa9name\n'
1104
 
                         'link-rev-id\ntarget/\xce\xa9path', bytes)
1105
 
        ie2 = inv._bytes_to_entry(bytes)
1106
 
        self.assertEqual(ie, ie2)
1107
 
        self.assertIsInstance(ie2.name, unicode)
1108
 
        self.assertIsInstance(ie2.symlink_target, unicode)
1109
 
        self.assertEqual(('link\xce\xa9name', 'link-id', 'link-rev-id'),
1110
 
                         inv._bytes_to_utf8name_key(bytes))
1111
 
 
1112
 
    def test_tree_reference_entry_to_bytes(self):
1113
 
        inv = CHKInventory(None)
1114
 
        ie = inventory.TreeReference('tree-root-id', u'tree\u03a9name',
1115
 
                                     'parent-id')
1116
 
        ie.revision = 'tree-rev-id'
1117
 
        ie.reference_revision = 'ref-rev-id'
1118
 
        bytes = inv._entry_to_bytes(ie)
1119
 
        self.assertEqual('tree: tree-root-id\nparent-id\ntree\xce\xa9name\n'
1120
 
                         'tree-rev-id\nref-rev-id', bytes)
1121
 
        ie2 = inv._bytes_to_entry(bytes)
1122
 
        self.assertEqual(ie, ie2)
1123
 
        self.assertIsInstance(ie2.name, unicode)
1124
 
        self.assertEqual(('tree\xce\xa9name', 'tree-root-id', 'tree-rev-id'),
1125
 
                         inv._bytes_to_utf8name_key(bytes))
 
519
class TestExecutable(TestCaseWithTransport):
 
520
 
 
521
    def test_stays_executable(self):
 
522
        a_id = "a-20051208024829-849e76f7968d7a86"
 
523
        b_id = "b-20051208024829-849e76f7968d7a86"
 
524
        wt = self.make_branch_and_tree('b1')
 
525
        b = wt.branch
 
526
        tt = TreeTransform(wt)
 
527
        tt.new_file('a', tt.root, 'a test\n', a_id, True)
 
528
        tt.new_file('b', tt.root, 'b test\n', b_id, False)
 
529
        tt.apply()
 
530
 
 
531
        self.failUnless(wt.is_executable(a_id), "'a' lost the execute bit")
 
532
 
 
533
        # reopen the tree and ensure it stuck.
 
534
        wt = wt.bzrdir.open_workingtree()
 
535
        self.assertEqual(['a', 'b'], [cn for cn,ie in wt.inventory.iter_entries()])
 
536
 
 
537
        self.failUnless(wt.is_executable(a_id), "'a' lost the execute bit")
 
538
        self.failIf(wt.is_executable(b_id), "'b' gained an execute bit")
 
539
 
 
540
        wt.commit('adding a,b', rev_id='r1')
 
541
 
 
542
        rev_tree = b.repository.revision_tree('r1')
 
543
        self.failUnless(rev_tree.is_executable(a_id), "'a' lost the execute bit")
 
544
        self.failIf(rev_tree.is_executable(b_id), "'b' gained an execute bit")
 
545
 
 
546
        self.failUnless(rev_tree.inventory[a_id].executable)
 
547
        self.failIf(rev_tree.inventory[b_id].executable)
 
548
 
 
549
        # Make sure the entries are gone
 
550
        os.remove('b1/a')
 
551
        os.remove('b1/b')
 
552
        self.failIf(wt.has_id(a_id))
 
553
        self.failIf(wt.has_filename('a'))
 
554
        self.failIf(wt.has_id(b_id))
 
555
        self.failIf(wt.has_filename('b'))
 
556
 
 
557
        # Make sure that revert is able to bring them back,
 
558
        # and sets 'a' back to being executable
 
559
 
 
560
        wt.revert(['a', 'b'], rev_tree, backups=False)
 
561
        self.assertEqual(['a', 'b'], [cn for cn,ie in wt.inventory.iter_entries()])
 
562
 
 
563
        self.failUnless(wt.is_executable(a_id), "'a' lost the execute bit")
 
564
        self.failIf(wt.is_executable(b_id), "'b' gained an execute bit")
 
565
 
 
566
        # Now remove them again, and make sure that after a
 
567
        # commit, they are still marked correctly
 
568
        os.remove('b1/a')
 
569
        os.remove('b1/b')
 
570
        wt.commit('removed', rev_id='r2')
 
571
 
 
572
        self.assertEqual([], [cn for cn,ie in wt.inventory.iter_entries()])
 
573
        self.failIf(wt.has_id(a_id))
 
574
        self.failIf(wt.has_filename('a'))
 
575
        self.failIf(wt.has_id(b_id))
 
576
        self.failIf(wt.has_filename('b'))
 
577
 
 
578
        # Now revert back to the previous commit
 
579
        wt.revert([], rev_tree, backups=False)
 
580
        self.assertEqual(['a', 'b'], [cn for cn,ie in wt.inventory.iter_entries()])
 
581
 
 
582
        self.failUnless(wt.is_executable(a_id), "'a' lost the execute bit")
 
583
        self.failIf(wt.is_executable(b_id), "'b' gained an execute bit")
 
584
 
 
585
        # Now make sure that 'bzr branch' also preserves the
 
586
        # executable bit
 
587
        # TODO: Maybe this should be a blackbox test
 
588
        d2 = b.bzrdir.clone('b2', revision_id='r1')
 
589
        t2 = d2.open_workingtree()
 
590
        b2 = t2.branch
 
591
        self.assertEquals('r1', b2.last_revision())
 
592
 
 
593
        self.assertEqual(['a', 'b'], [cn for cn,ie in t2.inventory.iter_entries()])
 
594
        self.failUnless(t2.is_executable(a_id), "'a' lost the execute bit")
 
595
        self.failIf(t2.is_executable(b_id), "'b' gained an execute bit")
 
596
 
 
597
        # Make sure pull will delete the files
 
598
        t2.pull(b)
 
599
        self.assertEquals('r2', b2.last_revision())
 
600
        self.assertEqual([], [cn for cn,ie in t2.inventory.iter_entries()])
 
601
 
 
602
        # Now commit the changes on the first branch
 
603
        # so that the second branch can pull the changes
 
604
        # and make sure that the executable bit has been copied
 
605
        wt.commit('resurrected', rev_id='r3')
 
606
 
 
607
        t2.pull(b)
 
608
        self.assertEquals('r3', b2.last_revision())
 
609
        self.assertEqual(['a', 'b'], [cn for cn,ie in t2.inventory.iter_entries()])
 
610
 
 
611
        self.failUnless(t2.is_executable(a_id), "'a' lost the execute bit")
 
612
        self.failIf(t2.is_executable(b_id), "'b' gained an execute bit")
 
613
 
 
614
 
 
615
class TestRevert(TestCaseWithTransport):
 
616
 
 
617
    def test_dangling_id(self):
 
618
        wt = self.make_branch_and_tree('b1')
 
619
        self.assertEqual(len(wt.inventory), 1)
 
620
        open('b1/a', 'wb').write('a test\n')
 
621
        wt.add('a')
 
622
        self.assertEqual(len(wt.inventory), 2)
 
623
        os.unlink('b1/a')
 
624
        wt.revert([])
 
625
        self.assertEqual(len(wt.inventory), 1)