/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-03-19 07:26:06 UTC
  • mfrom: (1551.13.3 Aaron's mergeable stuff)
  • Revision ID: pqm@pqm.ubuntu.com-20070319072606-0b45228c7ad6e0ae
Remove the merge-revert alias

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
 
18
 
 
19
import bisect
 
20
import os
 
21
import time
 
22
 
 
23
from bzrlib import (
 
24
    dirstate,
 
25
    errors,
 
26
    osutils,
 
27
    )
 
28
from bzrlib.memorytree import MemoryTree
 
29
from bzrlib.osutils import has_symlinks
 
30
from bzrlib.tests import (
 
31
        TestCase,
 
32
        TestCaseWithTransport,
 
33
        TestSkipped,
 
34
        )
 
35
 
 
36
 
 
37
# TODO:
 
38
# TESTS to write:
 
39
# general checks for NOT_IN_MEMORY error conditions.
 
40
# set_path_id on a NOT_IN_MEMORY dirstate
 
41
# set_path_id  unicode support
 
42
# set_path_id  setting id of a path not root
 
43
# set_path_id  setting id when there are parents without the id in the parents
 
44
# set_path_id  setting id when there are parents with the id in the parents
 
45
# set_path_id  setting id when state is not in memory
 
46
# set_path_id  setting id when state is in memory unmodified
 
47
# set_path_id  setting id when state is in memory modified
 
48
 
 
49
 
 
50
class TestCaseWithDirState(TestCaseWithTransport):
 
51
    """Helper functions for creating DirState objects with various content."""
 
52
 
 
53
    def create_empty_dirstate(self):
 
54
        """Return a locked but empty dirstate"""
 
55
        state = dirstate.DirState.initialize('dirstate')
 
56
        return state
 
57
 
 
58
    def create_dirstate_with_root(self):
 
59
        """Return a write-locked state with a single root entry."""
 
60
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
61
        root_entry_direntry = ('', '', 'a-root-value'), [
 
62
            ('d', '', 0, False, packed_stat),
 
63
            ]
 
64
        dirblocks = []
 
65
        dirblocks.append(('', [root_entry_direntry]))
 
66
        dirblocks.append(('', []))
 
67
        state = self.create_empty_dirstate()
 
68
        try:
 
69
            state._set_data([], dirblocks)
 
70
            state._validate()
 
71
        except:
 
72
            state.unlock()
 
73
            raise
 
74
        return state
 
75
 
 
76
    def create_dirstate_with_root_and_subdir(self):
 
77
        """Return a locked DirState with a root and a subdir"""
 
78
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
79
        subdir_entry = ('', 'subdir', 'subdir-id'), [
 
80
            ('d', '', 0, False, packed_stat),
 
81
            ]
 
82
        state = self.create_dirstate_with_root()
 
83
        try:
 
84
            dirblocks = list(state._dirblocks)
 
85
            dirblocks[1][1].append(subdir_entry)
 
86
            state._set_data([], dirblocks)
 
87
        except:
 
88
            state.unlock()
 
89
            raise
 
90
        return state
 
91
 
 
92
    def create_complex_dirstate(self):
 
93
        """This dirstate contains multiple files and directories.
 
94
 
 
95
         /        a-root-value
 
96
         a/       a-dir
 
97
         b/       b-dir
 
98
         c        c-file
 
99
         d        d-file
 
100
         a/e/     e-dir
 
101
         a/f      f-file
 
102
         b/g      g-file
 
103
         b/h\xc3\xa5  h-\xc3\xa5-file  #This is u'\xe5' encoded into utf-8
 
104
 
 
105
        # Notice that a/e is an empty directory.
 
106
        """
 
107
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
108
        null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
 
109
        root_entry = ('', '', 'a-root-value'), [
 
110
            ('d', '', 0, False, packed_stat),
 
111
            ]
 
112
        a_entry = ('', 'a', 'a-dir'), [
 
113
            ('d', '', 0, False, packed_stat),
 
114
            ]
 
115
        b_entry = ('', 'b', 'b-dir'), [
 
116
            ('d', '', 0, False, packed_stat),
 
117
            ]
 
118
        c_entry = ('', 'c', 'c-file'), [
 
119
            ('f', null_sha, 10, False, packed_stat),
 
120
            ]
 
121
        d_entry = ('', 'd', 'd-file'), [
 
122
            ('f', null_sha, 20, False, packed_stat),
 
123
            ]
 
124
        e_entry = ('a', 'e', 'e-dir'), [
 
125
            ('d', '', 0, False, packed_stat),
 
126
            ]
 
127
        f_entry = ('a', 'f', 'f-file'), [
 
128
            ('f', null_sha, 30, False, packed_stat),
 
129
            ]
 
130
        g_entry = ('b', 'g', 'g-file'), [
 
131
            ('f', null_sha, 30, False, packed_stat),
 
132
            ]
 
133
        h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
 
134
            ('f', null_sha, 40, False, packed_stat),
 
135
            ]
 
136
        dirblocks = []
 
137
        dirblocks.append(('', [root_entry]))
 
138
        dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
 
139
        dirblocks.append(('a', [e_entry, f_entry]))
 
140
        dirblocks.append(('b', [g_entry, h_entry]))
 
141
        state = dirstate.DirState.initialize('dirstate')
 
142
        state._validate()
 
143
        try:
 
144
            state._set_data([], dirblocks)
 
145
        except:
 
146
            state.unlock()
 
147
            raise
 
148
        return state
 
149
 
 
150
    def check_state_with_reopen(self, expected_result, state):
 
151
        """Check that state has current state expected_result.
 
152
 
 
153
        This will check the current state, open the file anew and check it
 
154
        again.
 
155
        This function expects the current state to be locked for writing, and
 
156
        will unlock it before re-opening.
 
157
        This is required because we can't open a lock_read() while something
 
158
        else has a lock_write().
 
159
            write => mutually exclusive lock
 
160
            read => shared lock
 
161
        """
 
162
        # The state should already be write locked, since we just had to do
 
163
        # some operation to get here.
 
164
        assert state._lock_token is not None
 
165
        try:
 
166
            self.assertEqual(expected_result[0],  state.get_parent_ids())
 
167
            # there should be no ghosts in this tree.
 
168
            self.assertEqual([], state.get_ghosts())
 
169
            # there should be one fileid in this tree - the root of the tree.
 
170
            self.assertEqual(expected_result[1], list(state._iter_entries()))
 
171
            state.save()
 
172
        finally:
 
173
            state.unlock()
 
174
        del state # Callers should unlock
 
175
        state = dirstate.DirState.on_file('dirstate')
 
176
        state.lock_read()
 
177
        try:
 
178
            self.assertEqual(expected_result[1], list(state._iter_entries()))
 
179
        finally:
 
180
            state.unlock()
 
181
 
 
182
 
 
183
class TestTreeToDirState(TestCaseWithDirState):
 
184
 
 
185
    def test_empty_to_dirstate(self):
 
186
        """We should be able to create a dirstate for an empty tree."""
 
187
        # There are no files on disk and no parents
 
188
        tree = self.make_branch_and_tree('tree')
 
189
        expected_result = ([], [
 
190
            (('', '', tree.path2id('')), # common details
 
191
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
192
             ])])
 
193
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
194
        state._validate()
 
195
        self.check_state_with_reopen(expected_result, state)
 
196
 
 
197
    def test_1_parents_empty_to_dirstate(self):
 
198
        # create a parent by doing a commit
 
199
        tree = self.make_branch_and_tree('tree')
 
200
        rev_id = tree.commit('first post').encode('utf8')
 
201
        root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
 
202
        expected_result = ([rev_id], [
 
203
            (('', '', tree.path2id('')), # common details
 
204
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
205
              ('d', '', 0, False, rev_id), # first parent details
 
206
             ])])
 
207
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
208
        self.check_state_with_reopen(expected_result, state)
 
209
        state._validate()
 
210
 
 
211
    def test_2_parents_empty_to_dirstate(self):
 
212
        # create a parent by doing a commit
 
213
        tree = self.make_branch_and_tree('tree')
 
214
        rev_id = tree.commit('first post')
 
215
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
216
        rev_id2 = tree2.commit('second post', allow_pointless=True)
 
217
        tree.merge_from_branch(tree2.branch)
 
218
        expected_result = ([rev_id, rev_id2], [
 
219
            (('', '', tree.path2id('')), # common details
 
220
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
221
              ('d', '', 0, False, rev_id), # first parent details
 
222
              ('d', '', 0, False, rev_id2), # second parent details
 
223
             ])])
 
224
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
225
        self.check_state_with_reopen(expected_result, state)
 
226
        state._validate()
 
227
 
 
228
    def test_empty_unknowns_are_ignored_to_dirstate(self):
 
229
        """We should be able to create a dirstate for an empty tree."""
 
230
        # There are no files on disk and no parents
 
231
        tree = self.make_branch_and_tree('tree')
 
232
        self.build_tree(['tree/unknown'])
 
233
        expected_result = ([], [
 
234
            (('', '', tree.path2id('')), # common details
 
235
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
236
             ])])
 
237
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
238
        self.check_state_with_reopen(expected_result, state)
 
239
 
 
240
    def get_tree_with_a_file(self):
 
241
        tree = self.make_branch_and_tree('tree')
 
242
        self.build_tree(['tree/a file'])
 
243
        tree.add('a file', 'a file id')
 
244
        return tree
 
245
 
 
246
    def test_non_empty_no_parents_to_dirstate(self):
 
247
        """We should be able to create a dirstate for an empty tree."""
 
248
        # There are files on disk and no parents
 
249
        tree = self.get_tree_with_a_file()
 
250
        expected_result = ([], [
 
251
            (('', '', tree.path2id('')), # common details
 
252
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
253
             ]),
 
254
            (('', 'a file', 'a file id'), # common
 
255
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
 
256
             ]),
 
257
            ])
 
258
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
259
        self.check_state_with_reopen(expected_result, state)
 
260
 
 
261
    def test_1_parents_not_empty_to_dirstate(self):
 
262
        # create a parent by doing a commit
 
263
        tree = self.get_tree_with_a_file()
 
264
        rev_id = tree.commit('first post').encode('utf8')
 
265
        # change the current content to be different this will alter stat, sha
 
266
        # and length:
 
267
        self.build_tree_contents([('tree/a file', 'new content\n')])
 
268
        expected_result = ([rev_id], [
 
269
            (('', '', tree.path2id('')), # common details
 
270
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
271
              ('d', '', 0, False, rev_id), # first parent details
 
272
             ]),
 
273
            (('', 'a file', 'a file id'), # common
 
274
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
 
275
              ('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
 
276
               rev_id), # first parent
 
277
             ]),
 
278
            ])
 
279
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
280
        self.check_state_with_reopen(expected_result, state)
 
281
 
 
282
    def test_2_parents_not_empty_to_dirstate(self):
 
283
        # create a parent by doing a commit
 
284
        tree = self.get_tree_with_a_file()
 
285
        rev_id = tree.commit('first post').encode('utf8')
 
286
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
287
        # change the current content to be different this will alter stat, sha
 
288
        # and length:
 
289
        self.build_tree_contents([('tree2/a file', 'merge content\n')])
 
290
        rev_id2 = tree2.commit('second post').encode('utf8')
 
291
        tree.merge_from_branch(tree2.branch)
 
292
        # change the current content to be different this will alter stat, sha
 
293
        # and length again, giving us three distinct values:
 
294
        self.build_tree_contents([('tree/a file', 'new content\n')])
 
295
        expected_result = ([rev_id, rev_id2], [
 
296
            (('', '', tree.path2id('')), # common details
 
297
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
298
              ('d', '', 0, False, rev_id), # first parent details
 
299
              ('d', '', 0, False, rev_id2), # second parent details
 
300
             ]),
 
301
            (('', 'a file', 'a file id'), # common
 
302
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
 
303
              ('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
 
304
               rev_id), # first parent
 
305
              ('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
 
306
               rev_id2), # second parent
 
307
             ]),
 
308
            ])
 
309
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
310
        self.check_state_with_reopen(expected_result, state)
 
311
 
 
312
    def test_colliding_fileids(self):
 
313
        # test insertion of parents creating several entries at the same path.
 
314
        # we used to have a bug where they could cause the dirstate to break
 
315
        # its ordering invariants.
 
316
        # create some trees to test from
 
317
        parents = []
 
318
        for i in range(7):
 
319
            tree = self.make_branch_and_tree('tree%d' % i)
 
320
            self.build_tree(['tree%d/name' % i,])
 
321
            tree.add(['name'], ['file-id%d' % i])
 
322
            revision_id = 'revid-%d' % i
 
323
            tree.commit('message', rev_id=revision_id)
 
324
            parents.append((revision_id,
 
325
                tree.branch.repository.revision_tree(revision_id)))
 
326
        # now fold these trees into a dirstate
 
327
        state = dirstate.DirState.initialize('dirstate')
 
328
        try:
 
329
            state.set_parent_trees(parents, [])
 
330
            state._validate()
 
331
        finally:
 
332
            state.unlock()
 
333
 
 
334
 
 
335
class TestDirStateOnFile(TestCaseWithDirState):
 
336
 
 
337
    def test_construct_with_path(self):
 
338
        tree = self.make_branch_and_tree('tree')
 
339
        state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
 
340
        # we want to be able to get the lines of the dirstate that we will
 
341
        # write to disk.
 
342
        lines = state.get_lines()
 
343
        state.unlock()
 
344
        self.build_tree_contents([('dirstate', ''.join(lines))])
 
345
        # get a state object
 
346
        # no parents, default tree content
 
347
        expected_result = ([], [
 
348
            (('', '', tree.path2id('')), # common details
 
349
             # current tree details, but new from_tree skips statting, it
 
350
             # uses set_state_from_inventory, and thus depends on the
 
351
             # inventory state.
 
352
             [('d', '', 0, False, dirstate.DirState.NULLSTAT),
 
353
             ])
 
354
            ])
 
355
        state = dirstate.DirState.on_file('dirstate')
 
356
        state.lock_write() # check_state_with_reopen will save() and unlock it
 
357
        self.check_state_with_reopen(expected_result, state)
 
358
 
 
359
    def test_can_save_clean_on_file(self):
 
360
        tree = self.make_branch_and_tree('tree')
 
361
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
362
        try:
 
363
            # doing a save should work here as there have been no changes.
 
364
            state.save()
 
365
            # TODO: stat it and check it hasn't changed; may require waiting
 
366
            # for the state accuracy window.
 
367
        finally:
 
368
            state.unlock()
 
369
 
 
370
 
 
371
class TestDirStateInitialize(TestCaseWithDirState):
 
372
 
 
373
    def test_initialize(self):
 
374
        expected_result = ([], [
 
375
            (('', '', 'TREE_ROOT'), # common details
 
376
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
377
             ])
 
378
            ])
 
379
        state = dirstate.DirState.initialize('dirstate')
 
380
        try:
 
381
            self.assertIsInstance(state, dirstate.DirState)
 
382
            lines = state.get_lines()
 
383
            self.assertFileEqual(''.join(state.get_lines()),
 
384
                'dirstate')
 
385
            self.check_state_with_reopen(expected_result, state)
 
386
        except:
 
387
            state.unlock()
 
388
            raise
 
389
 
 
390
 
 
391
class TestDirStateManipulations(TestCaseWithDirState):
 
392
 
 
393
    def test_set_state_from_inventory_no_content_no_parents(self):
 
394
        # setting the current inventory is a slow but important api to support.
 
395
        tree1 = self.make_branch_and_memory_tree('tree1')
 
396
        tree1.lock_write()
 
397
        try:
 
398
            tree1.add('')
 
399
            revid1 = tree1.commit('foo').encode('utf8')
 
400
            root_id = tree1.inventory.root.file_id
 
401
            inv = tree1.inventory
 
402
        finally:
 
403
            tree1.unlock()
 
404
        expected_result = [], [
 
405
            (('', '', root_id), [
 
406
             ('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
 
407
        state = dirstate.DirState.initialize('dirstate')
 
408
        try:
 
409
            state.set_state_from_inventory(inv)
 
410
            self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
411
                             state._header_state)
 
412
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
413
                             state._dirblock_state)
 
414
        except:
 
415
            state.unlock()
 
416
            raise
 
417
        else:
 
418
            # This will unlock it
 
419
            self.check_state_with_reopen(expected_result, state)
 
420
 
 
421
    def test_set_path_id_no_parents(self):
 
422
        """The id of a path can be changed trivally with no parents."""
 
423
        state = dirstate.DirState.initialize('dirstate')
 
424
        try:
 
425
            # check precondition to be sure the state does change appropriately.
 
426
            self.assertEqual(
 
427
                [(('', '', 'TREE_ROOT'), [('d', '', 0, False,
 
428
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
 
429
                list(state._iter_entries()))
 
430
            state.set_path_id('', 'foobarbaz')
 
431
            expected_rows = [
 
432
                (('', '', 'foobarbaz'), [('d', '', 0, False,
 
433
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
 
434
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
435
            # should work across save too
 
436
            state.save()
 
437
        finally:
 
438
            state.unlock()
 
439
        state = dirstate.DirState.on_file('dirstate')
 
440
        state._validate()
 
441
        state.lock_read()
 
442
        try:
 
443
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
444
        finally:
 
445
            state.unlock()
 
446
 
 
447
    def test_set_path_id_with_parents(self):
 
448
        """Set the root file id in a dirstate with parents"""
 
449
        mt = self.make_branch_and_tree('mt')
 
450
        # in case the default tree format uses a different root id
 
451
        mt.set_root_id('TREE_ROOT')
 
452
        mt.commit('foo', rev_id='parent-revid')
 
453
        rt = mt.branch.repository.revision_tree('parent-revid')
 
454
        state = dirstate.DirState.initialize('dirstate')
 
455
        state._validate()
 
456
        try:
 
457
            state.set_parent_trees([('parent-revid', rt)], ghosts=[])
 
458
            state.set_path_id('', 'foobarbaz')
 
459
            state._validate()
 
460
            # now see that it is what we expected
 
461
            expected_rows = [
 
462
                (('', '', 'TREE_ROOT'),
 
463
                    [('a', '', 0, False, ''),
 
464
                     ('d', '', 0, False, 'parent-revid'),
 
465
                     ]),
 
466
                (('', '', 'foobarbaz'),
 
467
                    [('d', '', 0, False, ''),
 
468
                     ('a', '', 0, False, ''),
 
469
                     ]),
 
470
                ]
 
471
            state._validate()
 
472
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
473
            # should work across save too
 
474
            state.save()
 
475
        finally:
 
476
            state.unlock()
 
477
        # now flush & check we get the same
 
478
        state = dirstate.DirState.on_file('dirstate')
 
479
        state.lock_read()
 
480
        try:
 
481
            state._validate()
 
482
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
483
        finally:
 
484
            state.unlock()
 
485
        # now change within an existing file-backed state
 
486
        state.lock_write()
 
487
        try:
 
488
            state._validate()
 
489
            state.set_path_id('', 'tree-root-2')
 
490
            state._validate()
 
491
        finally:
 
492
            state.unlock()
 
493
 
 
494
 
 
495
    def test_set_parent_trees_no_content(self):
 
496
        # set_parent_trees is a slow but important api to support.
 
497
        tree1 = self.make_branch_and_memory_tree('tree1')
 
498
        tree1.lock_write()
 
499
        try:
 
500
            tree1.add('')
 
501
            revid1 = tree1.commit('foo')
 
502
        finally:
 
503
            tree1.unlock()
 
504
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
 
505
        tree2 = MemoryTree.create_on_branch(branch2)
 
506
        tree2.lock_write()
 
507
        try:
 
508
            revid2 = tree2.commit('foo')
 
509
            root_id = tree2.inventory.root.file_id
 
510
        finally:
 
511
            tree2.unlock()
 
512
        state = dirstate.DirState.initialize('dirstate')
 
513
        try:
 
514
            state.set_path_id('', root_id)
 
515
            state.set_parent_trees(
 
516
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
517
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
 
518
                 ('ghost-rev', None)),
 
519
                ['ghost-rev'])
 
520
            # check we can reopen and use the dirstate after setting parent
 
521
            # trees.
 
522
            state._validate()
 
523
            state.save()
 
524
            state._validate()
 
525
        finally:
 
526
            state.unlock()
 
527
        state = dirstate.DirState.on_file('dirstate')
 
528
        state.lock_write()
 
529
        try:
 
530
            self.assertEqual([revid1, revid2, 'ghost-rev'],
 
531
                             state.get_parent_ids())
 
532
            # iterating the entire state ensures that the state is parsable.
 
533
            list(state._iter_entries())
 
534
            # be sure that it sets not appends - change it
 
535
            state.set_parent_trees(
 
536
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
537
                 ('ghost-rev', None)),
 
538
                ['ghost-rev'])
 
539
            # and now put it back.
 
540
            state.set_parent_trees(
 
541
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
542
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
 
543
                 ('ghost-rev', tree2.branch.repository.revision_tree(None))),
 
544
                ['ghost-rev'])
 
545
            self.assertEqual([revid1, revid2, 'ghost-rev'],
 
546
                             state.get_parent_ids())
 
547
            # the ghost should be recorded as such by set_parent_trees.
 
548
            self.assertEqual(['ghost-rev'], state.get_ghosts())
 
549
            self.assertEqual(
 
550
                [(('', '', root_id), [
 
551
                  ('d', '', 0, False, dirstate.DirState.NULLSTAT),
 
552
                  ('d', '', 0, False, revid1),
 
553
                  ('d', '', 0, False, revid2)
 
554
                  ])],
 
555
                list(state._iter_entries()))
 
556
        finally:
 
557
            state.unlock()
 
558
 
 
559
    def test_set_parent_trees_file_missing_from_tree(self):
 
560
        # Adding a parent tree may reference files not in the current state.
 
561
        # they should get listed just once by id, even if they are in two
 
562
        # separate trees.
 
563
        # set_parent_trees is a slow but important api to support.
 
564
        tree1 = self.make_branch_and_memory_tree('tree1')
 
565
        tree1.lock_write()
 
566
        try:
 
567
            tree1.add('')
 
568
            tree1.add(['a file'], ['file-id'], ['file'])
 
569
            tree1.put_file_bytes_non_atomic('file-id', 'file-content')
 
570
            revid1 = tree1.commit('foo')
 
571
        finally:
 
572
            tree1.unlock()
 
573
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
 
574
        tree2 = MemoryTree.create_on_branch(branch2)
 
575
        tree2.lock_write()
 
576
        try:
 
577
            tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
 
578
            revid2 = tree2.commit('foo')
 
579
            root_id = tree2.inventory.root.file_id
 
580
        finally:
 
581
            tree2.unlock()
 
582
        # check the layout in memory
 
583
        expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
 
584
            (('', '', root_id), [
 
585
             ('d', '', 0, False, dirstate.DirState.NULLSTAT),
 
586
             ('d', '', 0, False, revid1.encode('utf8')),
 
587
             ('d', '', 0, False, revid2.encode('utf8'))
 
588
             ]),
 
589
            (('', 'a file', 'file-id'), [
 
590
             ('a', '', 0, False, ''),
 
591
             ('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
 
592
              revid1.encode('utf8')),
 
593
             ('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
 
594
              revid2.encode('utf8'))
 
595
             ])
 
596
            ]
 
597
        state = dirstate.DirState.initialize('dirstate')
 
598
        try:
 
599
            state.set_path_id('', root_id)
 
600
            state.set_parent_trees(
 
601
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
602
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
 
603
                 ), [])
 
604
        except:
 
605
            state.unlock()
 
606
            raise
 
607
        else:
 
608
            # check_state_with_reopen will unlock
 
609
            self.check_state_with_reopen(expected_result, state)
 
610
 
 
611
    ### add a path via _set_data - so we dont need delta work, just
 
612
    # raw data in, and ensure that it comes out via get_lines happily.
 
613
 
 
614
    def test_add_path_to_root_no_parents_all_data(self):
 
615
        # The most trivial addition of a path is when there are no parents and
 
616
        # its in the root and all data about the file is supplied
 
617
        self.build_tree(['a file'])
 
618
        stat = os.lstat('a file')
 
619
        # the 1*20 is the sha1 pretend value.
 
620
        state = dirstate.DirState.initialize('dirstate')
 
621
        expected_entries = [
 
622
            (('', '', 'TREE_ROOT'), [
 
623
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
624
             ]),
 
625
            (('', 'a file', 'a file id'), [
 
626
             ('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
 
627
             ]),
 
628
            ]
 
629
        try:
 
630
            state.add('a file', 'a file id', 'file', stat, '1'*20)
 
631
            # having added it, it should be in the output of iter_entries.
 
632
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
633
            # saving and reloading should not affect this.
 
634
            state.save()
 
635
        finally:
 
636
            state.unlock()
 
637
        state = dirstate.DirState.on_file('dirstate')
 
638
        state.lock_read()
 
639
        try:
 
640
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
641
        finally:
 
642
            state.unlock()
 
643
 
 
644
    def test_add_path_to_unversioned_directory(self):
 
645
        """Adding a path to an unversioned directory should error.
 
646
 
 
647
        This is a duplicate of TestWorkingTree.test_add_in_unversioned,
 
648
        once dirstate is stable and if it is merged with WorkingTree3, consider
 
649
        removing this copy of the test.
 
650
        """
 
651
        self.build_tree(['unversioned/', 'unversioned/a file'])
 
652
        state = dirstate.DirState.initialize('dirstate')
 
653
        try:
 
654
            self.assertRaises(errors.NotVersionedError, state.add,
 
655
                'unversioned/a file', 'a file id', 'file', None, None)
 
656
        finally:
 
657
            state.unlock()
 
658
 
 
659
    def test_add_directory_to_root_no_parents_all_data(self):
 
660
        # The most trivial addition of a dir is when there are no parents and
 
661
        # its in the root and all data about the file is supplied
 
662
        self.build_tree(['a dir/'])
 
663
        stat = os.lstat('a dir')
 
664
        expected_entries = [
 
665
            (('', '', 'TREE_ROOT'), [
 
666
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
667
             ]),
 
668
            (('', 'a dir', 'a dir id'), [
 
669
             ('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
 
670
             ]),
 
671
            ]
 
672
        state = dirstate.DirState.initialize('dirstate')
 
673
        try:
 
674
            state.add('a dir', 'a dir id', 'directory', stat, None)
 
675
            # having added it, it should be in the output of iter_entries.
 
676
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
677
            # saving and reloading should not affect this.
 
678
            state.save()
 
679
        finally:
 
680
            state.unlock()
 
681
        state = dirstate.DirState.on_file('dirstate')
 
682
        state.lock_read()
 
683
        state._validate()
 
684
        try:
 
685
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
686
        finally:
 
687
            state.unlock()
 
688
 
 
689
    def test_add_symlink_to_root_no_parents_all_data(self):
 
690
        # The most trivial addition of a symlink when there are no parents and
 
691
        # its in the root and all data about the file is supplied
 
692
        # bzr doesn't support fake symlinks on windows, yet.
 
693
        if not has_symlinks():
 
694
            raise TestSkipped("No symlink support")
 
695
        os.symlink('target', 'a link')
 
696
        stat = os.lstat('a link')
 
697
        expected_entries = [
 
698
            (('', '', 'TREE_ROOT'), [
 
699
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
700
             ]),
 
701
            (('', 'a link', 'a link id'), [
 
702
             ('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
 
703
             ]),
 
704
            ]
 
705
        state = dirstate.DirState.initialize('dirstate')
 
706
        try:
 
707
            state.add('a link', 'a link id', 'symlink', stat, 'target')
 
708
            # having added it, it should be in the output of iter_entries.
 
709
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
710
            # saving and reloading should not affect this.
 
711
            state.save()
 
712
        finally:
 
713
            state.unlock()
 
714
        state = dirstate.DirState.on_file('dirstate')
 
715
        state.lock_read()
 
716
        try:
 
717
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
718
        finally:
 
719
            state.unlock()
 
720
 
 
721
    def test_add_directory_and_child_no_parents_all_data(self):
 
722
        # after adding a directory, we should be able to add children to it.
 
723
        self.build_tree(['a dir/', 'a dir/a file'])
 
724
        dirstat = os.lstat('a dir')
 
725
        filestat = os.lstat('a dir/a file')
 
726
        expected_entries = [
 
727
            (('', '', 'TREE_ROOT'), [
 
728
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
729
             ]),
 
730
            (('', 'a dir', 'a dir id'), [
 
731
             ('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
 
732
             ]),
 
733
            (('a dir', 'a file', 'a file id'), [
 
734
             ('f', '1'*20, 25, False,
 
735
              dirstate.pack_stat(filestat)), # current tree details
 
736
             ]),
 
737
            ]
 
738
        state = dirstate.DirState.initialize('dirstate')
 
739
        try:
 
740
            state.add('a dir', 'a dir id', 'directory', dirstat, None)
 
741
            state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
 
742
            # added it, it should be in the output of iter_entries.
 
743
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
744
            # saving and reloading should not affect this.
 
745
            state.save()
 
746
        finally:
 
747
            state.unlock()
 
748
        state = dirstate.DirState.on_file('dirstate')
 
749
        state.lock_read()
 
750
        try:
 
751
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
752
        finally:
 
753
            state.unlock()
 
754
 
 
755
    def test_add_tree_reference(self):
 
756
        # make a dirstate and add a tree reference
 
757
        state = dirstate.DirState.initialize('dirstate')
 
758
        expected_entry = (
 
759
            ('', 'subdir', 'subdir-id'),
 
760
            [('t', 'subtree-123123', 0, False,
 
761
              'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
 
762
            )
 
763
        try:
 
764
            state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
 
765
            entry = state._get_entry(0, 'subdir-id', 'subdir')
 
766
            self.assertEqual(entry, expected_entry)
 
767
            state._validate()
 
768
            state.save()
 
769
        finally:
 
770
            state.unlock()
 
771
        # now check we can read it back
 
772
        state.lock_read()
 
773
        state._validate()
 
774
        try:
 
775
            entry2 = state._get_entry(0, 'subdir-id', 'subdir')
 
776
            self.assertEqual(entry, entry2)
 
777
            self.assertEqual(entry, expected_entry)
 
778
            # and lookup by id should work too
 
779
            entry2 = state._get_entry(0, fileid_utf8='subdir-id')
 
780
            self.assertEqual(entry, expected_entry)
 
781
        finally:
 
782
            state.unlock()
 
783
 
 
784
    def test_add_forbidden_names(self):
 
785
        state = dirstate.DirState.initialize('dirstate')
 
786
        self.addCleanup(state.unlock)
 
787
        self.assertRaises(errors.BzrError,
 
788
            state.add, '.', 'ass-id', 'directory', None, None)
 
789
        self.assertRaises(errors.BzrError,
 
790
            state.add, '..', 'ass-id', 'directory', None, None)
 
791
 
 
792
 
 
793
class TestGetLines(TestCaseWithDirState):
 
794
 
 
795
    def test_get_line_with_2_rows(self):
 
796
        state = self.create_dirstate_with_root_and_subdir()
 
797
        try:
 
798
            self.assertEqual(['#bazaar dirstate flat format 3\n',
 
799
                'crc32: 41262208\n',
 
800
                'num_entries: 2\n',
 
801
                '0\x00\n\x00'
 
802
                '0\x00\n\x00'
 
803
                '\x00\x00a-root-value\x00'
 
804
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
 
805
                '\x00subdir\x00subdir-id\x00'
 
806
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
 
807
                ], state.get_lines())
 
808
        finally:
 
809
            state.unlock()
 
810
 
 
811
    def test_entry_to_line(self):
 
812
        state = self.create_dirstate_with_root()
 
813
        try:
 
814
            self.assertEqual(
 
815
                '\x00\x00a-root-value\x00d\x00\x000\x00n'
 
816
                '\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
 
817
                state._entry_to_line(state._dirblocks[0][1][0]))
 
818
        finally:
 
819
            state.unlock()
 
820
 
 
821
    def test_entry_to_line_with_parent(self):
 
822
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
823
        root_entry = ('', '', 'a-root-value'), [
 
824
            ('d', '', 0, False, packed_stat), # current tree details
 
825
             # first: a pointer to the current location
 
826
            ('a', 'dirname/basename', 0, False, ''),
 
827
            ]
 
828
        state = dirstate.DirState.initialize('dirstate')
 
829
        try:
 
830
            self.assertEqual(
 
831
                '\x00\x00a-root-value\x00'
 
832
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
 
833
                'a\x00dirname/basename\x000\x00n\x00',
 
834
                state._entry_to_line(root_entry))
 
835
        finally:
 
836
            state.unlock()
 
837
 
 
838
    def test_entry_to_line_with_two_parents_at_different_paths(self):
 
839
        # / in the tree, at / in one parent and /dirname/basename in the other.
 
840
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
841
        root_entry = ('', '', 'a-root-value'), [
 
842
            ('d', '', 0, False, packed_stat), # current tree details
 
843
            ('d', '', 0, False, 'rev_id'), # first parent details
 
844
             # second: a pointer to the current location
 
845
            ('a', 'dirname/basename', 0, False, ''),
 
846
            ]
 
847
        state = dirstate.DirState.initialize('dirstate')
 
848
        try:
 
849
            self.assertEqual(
 
850
                '\x00\x00a-root-value\x00'
 
851
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
 
852
                'd\x00\x000\x00n\x00rev_id\x00'
 
853
                'a\x00dirname/basename\x000\x00n\x00',
 
854
                state._entry_to_line(root_entry))
 
855
        finally:
 
856
            state.unlock()
 
857
 
 
858
    def test_iter_entries(self):
 
859
        # we should be able to iterate the dirstate entries from end to end
 
860
        # this is for get_lines to be easy to read.
 
861
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
862
        dirblocks = []
 
863
        root_entries = [(('', '', 'a-root-value'), [
 
864
            ('d', '', 0, False, packed_stat), # current tree details
 
865
            ])]
 
866
        dirblocks.append(('', root_entries))
 
867
        # add two files in the root
 
868
        subdir_entry = ('', 'subdir', 'subdir-id'), [
 
869
            ('d', '', 0, False, packed_stat), # current tree details
 
870
            ]
 
871
        afile_entry = ('', 'afile', 'afile-id'), [
 
872
            ('f', 'sha1value', 34, False, packed_stat), # current tree details
 
873
            ]
 
874
        dirblocks.append(('', [subdir_entry, afile_entry]))
 
875
        # and one in subdir
 
876
        file_entry2 = ('subdir', '2file', '2file-id'), [
 
877
            ('f', 'sha1value', 23, False, packed_stat), # current tree details
 
878
            ]
 
879
        dirblocks.append(('subdir', [file_entry2]))
 
880
        state = dirstate.DirState.initialize('dirstate')
 
881
        try:
 
882
            state._set_data([], dirblocks)
 
883
            expected_entries = [root_entries[0], subdir_entry, afile_entry,
 
884
                                file_entry2]
 
885
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
886
        finally:
 
887
            state.unlock()
 
888
 
 
889
 
 
890
class TestGetBlockRowIndex(TestCaseWithDirState):
 
891
 
 
892
    def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
 
893
        file_present, state, dirname, basename, tree_index):
 
894
        self.assertEqual((block_index, row_index, dir_present, file_present),
 
895
            state._get_block_entry_index(dirname, basename, tree_index))
 
896
        if dir_present:
 
897
            block = state._dirblocks[block_index]
 
898
            self.assertEqual(dirname, block[0])
 
899
        if dir_present and file_present:
 
900
            row = state._dirblocks[block_index][1][row_index]
 
901
            self.assertEqual(dirname, row[0][0])
 
902
            self.assertEqual(basename, row[0][1])
 
903
 
 
904
    def test_simple_structure(self):
 
905
        state = self.create_dirstate_with_root_and_subdir()
 
906
        self.addCleanup(state.unlock)
 
907
        self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
 
908
        self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
 
909
        self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
 
910
        self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
 
911
        self.assertBlockRowIndexEqual(2, 0, False, False, state,
 
912
                                      'subdir', 'foo', 0)
 
913
 
 
914
    def test_complex_structure_exists(self):
 
915
        state = self.create_complex_dirstate()
 
916
        self.addCleanup(state.unlock)
 
917
        # Make sure we can find everything that exists
 
918
        self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
 
919
        self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
 
920
        self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
 
921
        self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
 
922
        self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
 
923
        self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
 
924
        self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
 
925
        self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
 
926
        self.assertBlockRowIndexEqual(3, 1, True, True, state,
 
927
                                      'b', 'h\xc3\xa5', 0)
 
928
 
 
929
    def test_complex_structure_missing(self):
 
930
        state = self.create_complex_dirstate()
 
931
        self.addCleanup(state.unlock)
 
932
        # Make sure things would be inserted in the right locations
 
933
        # '_' comes before 'a'
 
934
        self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
 
935
        self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
 
936
        self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
 
937
        self.assertBlockRowIndexEqual(1, 4, True, False, state,
 
938
                                      '', 'h\xc3\xa5', 0)
 
939
        self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
 
940
        self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
 
941
        self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
 
942
        # This would be inserted between a/ and b/
 
943
        self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
 
944
        # Put at the end
 
945
        self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
 
946
 
 
947
 
 
948
class TestGetEntry(TestCaseWithDirState):
 
949
 
 
950
    def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
 
951
        """Check that the right entry is returned for a request to getEntry."""
 
952
        entry = state._get_entry(index, path_utf8=path)
 
953
        if file_id is None:
 
954
            self.assertEqual((None, None), entry)
 
955
        else:
 
956
            cur = entry[0]
 
957
            self.assertEqual((dirname, basename, file_id), cur[:3])
 
958
 
 
959
    def test_simple_structure(self):
 
960
        state = self.create_dirstate_with_root_and_subdir()
 
961
        self.addCleanup(state.unlock)
 
962
        self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
 
963
        self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
 
964
        self.assertEntryEqual(None, None, None, state, 'missing', 0)
 
965
        self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
 
966
        self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
 
967
 
 
968
    def test_complex_structure_exists(self):
 
969
        state = self.create_complex_dirstate()
 
970
        self.addCleanup(state.unlock)
 
971
        self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
 
972
        self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
 
973
        self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
 
974
        self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
 
975
        self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
 
976
        self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
 
977
        self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
 
978
        self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
 
979
        self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
 
980
                              'b/h\xc3\xa5', 0)
 
981
 
 
982
    def test_complex_structure_missing(self):
 
983
        state = self.create_complex_dirstate()
 
984
        self.addCleanup(state.unlock)
 
985
        self.assertEntryEqual(None, None, None, state, '_', 0)
 
986
        self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
 
987
        self.assertEntryEqual(None, None, None, state, 'a/b', 0)
 
988
        self.assertEntryEqual(None, None, None, state, 'c/d', 0)
 
989
 
 
990
    def test_get_entry_uninitialized(self):
 
991
        """Calling get_entry will load data if it needs to"""
 
992
        state = self.create_dirstate_with_root()
 
993
        try:
 
994
            state.save()
 
995
        finally:
 
996
            state.unlock()
 
997
        del state
 
998
        state = dirstate.DirState.on_file('dirstate')
 
999
        state.lock_read()
 
1000
        try:
 
1001
            self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
 
1002
                             state._header_state)
 
1003
            self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
 
1004
                             state._dirblock_state)
 
1005
            self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
 
1006
        finally:
 
1007
            state.unlock()
 
1008
 
 
1009
 
 
1010
class TestDirstateSortOrder(TestCaseWithTransport):
 
1011
    """Test that DirState adds entries in the right order."""
 
1012
 
 
1013
    def test_add_sorting(self):
 
1014
        """Add entries in lexicographical order, we get path sorted order.
 
1015
 
 
1016
        This tests it to a depth of 4, to make sure we don't just get it right
 
1017
        at a single depth. 'a/a' should come before 'a-a', even though it
 
1018
        doesn't lexicographically.
 
1019
        """
 
1020
        dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
 
1021
                'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
 
1022
               ]
 
1023
        null_sha = ''
 
1024
        state = dirstate.DirState.initialize('dirstate')
 
1025
        self.addCleanup(state.unlock)
 
1026
 
 
1027
        fake_stat = os.stat('dirstate')
 
1028
        for d in dirs:
 
1029
            d_id = d.replace('/', '_')+'-id'
 
1030
            file_path = d + '/f'
 
1031
            file_id = file_path.replace('/', '_')+'-id'
 
1032
            state.add(d, d_id, 'directory', fake_stat, null_sha)
 
1033
            state.add(file_path, file_id, 'file', fake_stat, null_sha)
 
1034
 
 
1035
        expected = ['', '', 'a',
 
1036
                'a/a', 'a/a/a', 'a/a/a/a',
 
1037
                'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
 
1038
               ]
 
1039
        split = lambda p:p.split('/')
 
1040
        self.assertEqual(sorted(expected, key=split), expected)
 
1041
        dirblock_names = [d[0] for d in state._dirblocks]
 
1042
        self.assertEqual(expected, dirblock_names)
 
1043
 
 
1044
    def test_set_parent_trees_correct_order(self):
 
1045
        """After calling set_parent_trees() we should maintain the order."""
 
1046
        dirs = ['a', 'a-a', 'a/a']
 
1047
        null_sha = ''
 
1048
        state = dirstate.DirState.initialize('dirstate')
 
1049
        self.addCleanup(state.unlock)
 
1050
 
 
1051
        fake_stat = os.stat('dirstate')
 
1052
        for d in dirs:
 
1053
            d_id = d.replace('/', '_')+'-id'
 
1054
            file_path = d + '/f'
 
1055
            file_id = file_path.replace('/', '_')+'-id'
 
1056
            state.add(d, d_id, 'directory', fake_stat, null_sha)
 
1057
            state.add(file_path, file_id, 'file', fake_stat, null_sha)
 
1058
 
 
1059
        expected = ['', '', 'a', 'a/a', 'a-a']
 
1060
        dirblock_names = [d[0] for d in state._dirblocks]
 
1061
        self.assertEqual(expected, dirblock_names)
 
1062
 
 
1063
        # *really* cheesy way to just get an empty tree
 
1064
        repo = self.make_repository('repo')
 
1065
        empty_tree = repo.revision_tree(None)
 
1066
        state.set_parent_trees([('null:', empty_tree)], [])
 
1067
 
 
1068
        dirblock_names = [d[0] for d in state._dirblocks]
 
1069
        self.assertEqual(expected, dirblock_names)
 
1070
 
 
1071
 
 
1072
class InstrumentedDirState(dirstate.DirState):
 
1073
    """An DirState with instrumented sha1 functionality."""
 
1074
 
 
1075
    def __init__(self, path):
 
1076
        super(InstrumentedDirState, self).__init__(path)
 
1077
        self._time_offset = 0
 
1078
        self._log = []
 
1079
 
 
1080
    def _sha_cutoff_time(self):
 
1081
        timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
 
1082
        self._cutoff_time = timestamp + self._time_offset
 
1083
 
 
1084
    def _sha1_file(self, abspath, entry):
 
1085
        self._log.append(('sha1', abspath))
 
1086
        return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
 
1087
 
 
1088
    def _read_link(self, abspath, old_link):
 
1089
        self._log.append(('read_link', abspath, old_link))
 
1090
        return super(InstrumentedDirState, self)._read_link(abspath, old_link)
 
1091
 
 
1092
    def _lstat(self, abspath, entry):
 
1093
        self._log.append(('lstat', abspath))
 
1094
        return super(InstrumentedDirState, self)._lstat(abspath, entry)
 
1095
 
 
1096
    def _is_executable(self, mode, old_executable):
 
1097
        self._log.append(('is_exec', mode, old_executable))
 
1098
        return super(InstrumentedDirState, self)._is_executable(mode,
 
1099
                                                                old_executable)
 
1100
 
 
1101
    def adjust_time(self, secs):
 
1102
        """Move the clock forward or back.
 
1103
 
 
1104
        :param secs: The amount to adjust the clock by. Positive values make it
 
1105
        seem as if we are in the future, negative values make it seem like we
 
1106
        are in the past.
 
1107
        """
 
1108
        self._time_offset += secs
 
1109
        self._cutoff_time = None
 
1110
 
 
1111
 
 
1112
class _FakeStat(object):
 
1113
    """A class with the same attributes as a real stat result."""
 
1114
 
 
1115
    def __init__(self, size, mtime, ctime, dev, ino, mode):
 
1116
        self.st_size = size
 
1117
        self.st_mtime = mtime
 
1118
        self.st_ctime = ctime
 
1119
        self.st_dev = dev
 
1120
        self.st_ino = ino
 
1121
        self.st_mode = mode
 
1122
 
 
1123
 
 
1124
class TestUpdateEntry(TestCaseWithDirState):
 
1125
    """Test the DirState.update_entry functions"""
 
1126
 
 
1127
    def get_state_with_a(self):
 
1128
        """Create a DirState tracking a single object named 'a'"""
 
1129
        state = InstrumentedDirState.initialize('dirstate')
 
1130
        self.addCleanup(state.unlock)
 
1131
        state.add('a', 'a-id', 'file', None, '')
 
1132
        entry = state._get_entry(0, path_utf8='a')
 
1133
        return state, entry
 
1134
 
 
1135
    def test_update_entry(self):
 
1136
        state, entry = self.get_state_with_a()
 
1137
        self.build_tree(['a'])
 
1138
        # Add one where we don't provide the stat or sha already
 
1139
        self.assertEqual(('', 'a', 'a-id'), entry[0])
 
1140
        self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
 
1141
                         entry[1])
 
1142
        # Flush the buffers to disk
 
1143
        state.save()
 
1144
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
1145
                         state._dirblock_state)
 
1146
 
 
1147
        stat_value = os.lstat('a')
 
1148
        packed_stat = dirstate.pack_stat(stat_value)
 
1149
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1150
                                          stat_value=stat_value)
 
1151
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1152
                         link_or_sha1)
 
1153
 
 
1154
        # The dirblock entry should be updated with the new info
 
1155
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
 
1156
                         entry[1])
 
1157
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
1158
                         state._dirblock_state)
 
1159
        mode = stat_value.st_mode
 
1160
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
 
1161
 
 
1162
        state.save()
 
1163
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
1164
                         state._dirblock_state)
 
1165
 
 
1166
        # If we do it again right away, we don't know if the file has changed
 
1167
        # so we will re-read the file. Roll the clock back so the file is
 
1168
        # guaranteed to look too new.
 
1169
        state.adjust_time(-10)
 
1170
 
 
1171
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1172
                                          stat_value=stat_value)
 
1173
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
 
1174
                          ('sha1', 'a'), ('is_exec', mode, False),
 
1175
                         ], state._log)
 
1176
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1177
                         link_or_sha1)
 
1178
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
1179
                         state._dirblock_state)
 
1180
        state.save()
 
1181
 
 
1182
        # However, if we move the clock forward so the file is considered
 
1183
        # "stable", it should just returned the cached value.
 
1184
        state.adjust_time(20)
 
1185
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1186
                                          stat_value=stat_value)
 
1187
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1188
                         link_or_sha1)
 
1189
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
 
1190
                          ('sha1', 'a'), ('is_exec', mode, False),
 
1191
                         ], state._log)
 
1192
 
 
1193
    def test_update_entry_no_stat_value(self):
 
1194
        """Passing the stat_value is optional."""
 
1195
        state, entry = self.get_state_with_a()
 
1196
        state.adjust_time(-10) # Make sure the file looks new
 
1197
        self.build_tree(['a'])
 
1198
        # Add one where we don't provide the stat or sha already
 
1199
        link_or_sha1 = state.update_entry(entry, abspath='a')
 
1200
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1201
                         link_or_sha1)
 
1202
        stat_value = os.lstat('a')
 
1203
        self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
 
1204
                          ('is_exec', stat_value.st_mode, False),
 
1205
                         ], state._log)
 
1206
 
 
1207
    def test_update_entry_symlink(self):
 
1208
        """Update entry should read symlinks."""
 
1209
        if not osutils.has_symlinks():
 
1210
            # PlatformDeficiency / TestSkipped
 
1211
            raise TestSkipped("No symlink support")
 
1212
        state, entry = self.get_state_with_a()
 
1213
        state.save()
 
1214
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
1215
                         state._dirblock_state)
 
1216
        os.symlink('target', 'a')
 
1217
 
 
1218
        state.adjust_time(-10) # Make the symlink look new
 
1219
        stat_value = os.lstat('a')
 
1220
        packed_stat = dirstate.pack_stat(stat_value)
 
1221
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1222
                                          stat_value=stat_value)
 
1223
        self.assertEqual('target', link_or_sha1)
 
1224
        self.assertEqual([('read_link', 'a', '')], state._log)
 
1225
        # Dirblock is updated
 
1226
        self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
 
1227
                         entry[1])
 
1228
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
1229
                         state._dirblock_state)
 
1230
 
 
1231
        # Because the stat_value looks new, we should re-read the target
 
1232
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1233
                                          stat_value=stat_value)
 
1234
        self.assertEqual('target', link_or_sha1)
 
1235
        self.assertEqual([('read_link', 'a', ''),
 
1236
                          ('read_link', 'a', 'target'),
 
1237
                         ], state._log)
 
1238
        state.adjust_time(+20) # Skip into the future, all files look old
 
1239
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1240
                                          stat_value=stat_value)
 
1241
        self.assertEqual('target', link_or_sha1)
 
1242
        # There should not be a new read_link call.
 
1243
        # (this is a weak assertion, because read_link is fairly inexpensive,
 
1244
        # versus the number of symlinks that we would have)
 
1245
        self.assertEqual([('read_link', 'a', ''),
 
1246
                          ('read_link', 'a', 'target'),
 
1247
                         ], state._log)
 
1248
 
 
1249
    def test_update_entry_dir(self):
 
1250
        state, entry = self.get_state_with_a()
 
1251
        self.build_tree(['a/'])
 
1252
        self.assertIs(None, state.update_entry(entry, 'a'))
 
1253
 
 
1254
    def create_and_test_file(self, state, entry):
 
1255
        """Create a file at 'a' and verify the state finds it.
 
1256
 
 
1257
        The state should already be versioning *something* at 'a'. This makes
 
1258
        sure that state.update_entry recognizes it as a file.
 
1259
        """
 
1260
        self.build_tree(['a'])
 
1261
        stat_value = os.lstat('a')
 
1262
        packed_stat = dirstate.pack_stat(stat_value)
 
1263
 
 
1264
        link_or_sha1 = state.update_entry(entry, abspath='a')
 
1265
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1266
                         link_or_sha1)
 
1267
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
 
1268
                         entry[1])
 
1269
        return packed_stat
 
1270
 
 
1271
    def create_and_test_dir(self, state, entry):
 
1272
        """Create a directory at 'a' and verify the state finds it.
 
1273
 
 
1274
        The state should already be versioning *something* at 'a'. This makes
 
1275
        sure that state.update_entry recognizes it as a directory.
 
1276
        """
 
1277
        self.build_tree(['a/'])
 
1278
        stat_value = os.lstat('a')
 
1279
        packed_stat = dirstate.pack_stat(stat_value)
 
1280
 
 
1281
        link_or_sha1 = state.update_entry(entry, abspath='a')
 
1282
        self.assertIs(None, link_or_sha1)
 
1283
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
 
1284
 
 
1285
        return packed_stat
 
1286
 
 
1287
    def create_and_test_symlink(self, state, entry):
 
1288
        """Create a symlink at 'a' and verify the state finds it.
 
1289
 
 
1290
        The state should already be versioning *something* at 'a'. This makes
 
1291
        sure that state.update_entry recognizes it as a symlink.
 
1292
 
 
1293
        This should not be called if this platform does not have symlink
 
1294
        support.
 
1295
        """
 
1296
        # caller should care about skipping test on platforms without symlinks
 
1297
        os.symlink('path/to/foo', 'a')
 
1298
 
 
1299
        stat_value = os.lstat('a')
 
1300
        packed_stat = dirstate.pack_stat(stat_value)
 
1301
 
 
1302
        link_or_sha1 = state.update_entry(entry, abspath='a')
 
1303
        self.assertEqual('path/to/foo', link_or_sha1)
 
1304
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
 
1305
                         entry[1])
 
1306
        return packed_stat
 
1307
 
 
1308
    def test_update_missing_file(self):
 
1309
        state, entry = self.get_state_with_a()
 
1310
        packed_stat = self.create_and_test_file(state, entry)
 
1311
        # Now if we delete the file, update_entry should recover and
 
1312
        # return None.
 
1313
        os.remove('a')
 
1314
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1315
        # And the record shouldn't be changed.
 
1316
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
 
1317
        self.assertEqual([('f', digest, 14, False, packed_stat)],
 
1318
                         entry[1])
 
1319
 
 
1320
    def test_update_missing_dir(self):
 
1321
        state, entry = self.get_state_with_a()
 
1322
        packed_stat = self.create_and_test_dir(state, entry)
 
1323
        # Now if we delete the directory, update_entry should recover and
 
1324
        # return None.
 
1325
        os.rmdir('a')
 
1326
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1327
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
 
1328
 
 
1329
    def test_update_missing_symlink(self):
 
1330
        if not osutils.has_symlinks():
 
1331
            # PlatformDeficiency / TestSkipped
 
1332
            raise TestSkipped("No symlink support")
 
1333
        state, entry = self.get_state_with_a()
 
1334
        packed_stat = self.create_and_test_symlink(state, entry)
 
1335
        os.remove('a')
 
1336
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1337
        # And the record shouldn't be changed.
 
1338
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
 
1339
                         entry[1])
 
1340
 
 
1341
    def test_update_file_to_dir(self):
 
1342
        """If a file changes to a directory we return None for the sha.
 
1343
        We also update the inventory record.
 
1344
        """
 
1345
        state, entry = self.get_state_with_a()
 
1346
        self.create_and_test_file(state, entry)
 
1347
        os.remove('a')
 
1348
        self.create_and_test_dir(state, entry)
 
1349
 
 
1350
    def test_update_file_to_symlink(self):
 
1351
        """File becomes a symlink"""
 
1352
        if not osutils.has_symlinks():
 
1353
            # PlatformDeficiency / TestSkipped
 
1354
            raise TestSkipped("No symlink support")
 
1355
        state, entry = self.get_state_with_a()
 
1356
        self.create_and_test_file(state, entry)
 
1357
        os.remove('a')
 
1358
        self.create_and_test_symlink(state, entry)
 
1359
 
 
1360
    def test_update_dir_to_file(self):
 
1361
        """Directory becoming a file updates the entry."""
 
1362
        state, entry = self.get_state_with_a()
 
1363
        self.create_and_test_dir(state, entry)
 
1364
        os.rmdir('a')
 
1365
        self.create_and_test_file(state, entry)
 
1366
 
 
1367
    def test_update_dir_to_symlink(self):
 
1368
        """Directory becomes a symlink"""
 
1369
        if not osutils.has_symlinks():
 
1370
            # PlatformDeficiency / TestSkipped
 
1371
            raise TestSkipped("No symlink support")
 
1372
        state, entry = self.get_state_with_a()
 
1373
        self.create_and_test_dir(state, entry)
 
1374
        os.rmdir('a')
 
1375
        self.create_and_test_symlink(state, entry)
 
1376
 
 
1377
    def test_update_symlink_to_file(self):
 
1378
        """Symlink becomes a file"""
 
1379
        if not has_symlinks():
 
1380
            raise TestSkipped("No symlink support")
 
1381
        state, entry = self.get_state_with_a()
 
1382
        self.create_and_test_symlink(state, entry)
 
1383
        os.remove('a')
 
1384
        self.create_and_test_file(state, entry)
 
1385
 
 
1386
    def test_update_symlink_to_dir(self):
 
1387
        """Symlink becomes a directory"""
 
1388
        if not has_symlinks():
 
1389
            raise TestSkipped("No symlink support")
 
1390
        state, entry = self.get_state_with_a()
 
1391
        self.create_and_test_symlink(state, entry)
 
1392
        os.remove('a')
 
1393
        self.create_and_test_dir(state, entry)
 
1394
 
 
1395
    def test__is_executable_win32(self):
 
1396
        state, entry = self.get_state_with_a()
 
1397
        self.build_tree(['a'])
 
1398
 
 
1399
        # Make sure we are using the win32 implementation of _is_executable
 
1400
        state._is_executable = state._is_executable_win32
 
1401
 
 
1402
        # The file on disk is not executable, but we are marking it as though
 
1403
        # it is. With _is_executable_win32 we ignore what is on disk.
 
1404
        entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
 
1405
 
 
1406
        stat_value = os.lstat('a')
 
1407
        packed_stat = dirstate.pack_stat(stat_value)
 
1408
 
 
1409
        state.adjust_time(-10) # Make sure everything is new
 
1410
        # Make sure it wants to kkkkkkkk
 
1411
        state.update_entry(entry, abspath='a', stat_value=stat_value)
 
1412
 
 
1413
        # The row is updated, but the executable bit stays set.
 
1414
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
 
1415
        self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
 
1416
 
 
1417
 
 
1418
class TestPackStat(TestCaseWithTransport):
 
1419
 
 
1420
    def assertPackStat(self, expected, stat_value):
 
1421
        """Check the packed and serialized form of a stat value."""
 
1422
        self.assertEqual(expected, dirstate.pack_stat(stat_value))
 
1423
 
 
1424
    def test_pack_stat_int(self):
 
1425
        st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
 
1426
        # Make sure that all parameters have an impact on the packed stat.
 
1427
        self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
 
1428
        st.st_size = 7000L
 
1429
        #                ay0 => bWE
 
1430
        self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
 
1431
        st.st_mtime = 1172758620
 
1432
        #                     4FZ => 4Fx
 
1433
        self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
 
1434
        st.st_ctime = 1172758630
 
1435
        #                          uBZ => uBm
 
1436
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1437
        st.st_dev = 888L
 
1438
        #                                DCQ => DeA
 
1439
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
 
1440
        st.st_ino = 6499540L
 
1441
        #                                     LNI => LNQ
 
1442
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
 
1443
        st.st_mode = 0100744
 
1444
        #                                          IGk => IHk
 
1445
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
 
1446
 
 
1447
    def test_pack_stat_float(self):
 
1448
        """On some platforms mtime and ctime are floats.
 
1449
 
 
1450
        Make sure we don't get warnings or errors, and that we ignore changes <
 
1451
        1s
 
1452
        """
 
1453
        st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
 
1454
                       777L, 6499538L, 0100644)
 
1455
        # These should all be the same as the integer counterparts
 
1456
        self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
 
1457
        st.st_mtime = 1172758620.0
 
1458
        #                     FZF5 => FxF5
 
1459
        self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
 
1460
        st.st_ctime = 1172758630.0
 
1461
        #                          uBZ => uBm
 
1462
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1463
        # fractional seconds are discarded, so no change from above
 
1464
        st.st_mtime = 1172758620.453
 
1465
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1466
        st.st_ctime = 1172758630.228
 
1467
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1468
 
 
1469
 
 
1470
class TestBisect(TestCaseWithTransport):
 
1471
    """Test the ability to bisect into the disk format."""
 
1472
 
 
1473
    def create_basic_dirstate(self):
 
1474
        """Create a dirstate with a few files and directories.
 
1475
 
 
1476
            a
 
1477
            b/
 
1478
              c
 
1479
              d/
 
1480
                e
 
1481
            f
 
1482
        """
 
1483
        tree = self.make_branch_and_tree('tree')
 
1484
        paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
 
1485
        file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
 
1486
        self.build_tree(['tree/' + p for p in paths])
 
1487
        tree.set_root_id('TREE_ROOT')
 
1488
        tree.add([p.rstrip('/') for p in paths], file_ids)
 
1489
        tree.commit('initial', rev_id='rev-1')
 
1490
        revision_id = 'rev-1'
 
1491
        # a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
 
1492
        t = self.get_transport().clone('tree')
 
1493
        a_text = t.get_bytes('a')
 
1494
        a_sha = osutils.sha_string(a_text)
 
1495
        a_len = len(a_text)
 
1496
        # b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
 
1497
        # c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
 
1498
        c_text = t.get_bytes('b/c')
 
1499
        c_sha = osutils.sha_string(c_text)
 
1500
        c_len = len(c_text)
 
1501
        # d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
 
1502
        # e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
 
1503
        e_text = t.get_bytes('b/d/e')
 
1504
        e_sha = osutils.sha_string(e_text)
 
1505
        e_len = len(e_text)
 
1506
        # f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
 
1507
        f_text = t.get_bytes('f')
 
1508
        f_sha = osutils.sha_string(f_text)
 
1509
        f_len = len(f_text)
 
1510
        null_stat = dirstate.DirState.NULLSTAT
 
1511
        expected = {
 
1512
            '':(('', '', 'TREE_ROOT'), [
 
1513
                  ('d', '', 0, False, null_stat),
 
1514
                  ('d', '', 0, False, revision_id),
 
1515
                ]),
 
1516
            'a':(('', 'a', 'a-id'), [
 
1517
                   ('f', '', 0, False, null_stat),
 
1518
                   ('f', a_sha, a_len, False, revision_id),
 
1519
                 ]),
 
1520
            'b':(('', 'b', 'b-id'), [
 
1521
                  ('d', '', 0, False, null_stat),
 
1522
                  ('d', '', 0, False, revision_id),
 
1523
                 ]),
 
1524
            'b/c':(('b', 'c', 'c-id'), [
 
1525
                    ('f', '', 0, False, null_stat),
 
1526
                    ('f', c_sha, c_len, False, revision_id),
 
1527
                   ]),
 
1528
            'b/d':(('b', 'd', 'd-id'), [
 
1529
                    ('d', '', 0, False, null_stat),
 
1530
                    ('d', '', 0, False, revision_id),
 
1531
                   ]),
 
1532
            'b/d/e':(('b/d', 'e', 'e-id'), [
 
1533
                      ('f', '', 0, False, null_stat),
 
1534
                      ('f', e_sha, e_len, False, revision_id),
 
1535
                     ]),
 
1536
            'f':(('', 'f', 'f-id'), [
 
1537
                  ('f', '', 0, False, null_stat),
 
1538
                  ('f', f_sha, f_len, False, revision_id),
 
1539
                 ]),
 
1540
        }
 
1541
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
1542
        try:
 
1543
            state.save()
 
1544
        finally:
 
1545
            state.unlock()
 
1546
        # Use a different object, to make sure nothing is pre-cached in memory.
 
1547
        state = dirstate.DirState.on_file('dirstate')
 
1548
        state.lock_read()
 
1549
        self.addCleanup(state.unlock)
 
1550
        self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
 
1551
                         state._dirblock_state)
 
1552
        # This is code is only really tested if we actually have to make more
 
1553
        # than one read, so set the page size to something smaller.
 
1554
        # We want it to contain about 2.2 records, so that we have a couple
 
1555
        # records that we can read per attempt
 
1556
        state._bisect_page_size = 200
 
1557
        return tree, state, expected
 
1558
 
 
1559
    def create_duplicated_dirstate(self):
 
1560
        """Create a dirstate with a deleted and added entries.
 
1561
 
 
1562
        This grabs a basic_dirstate, and then removes and re adds every entry
 
1563
        with a new file id.
 
1564
        """
 
1565
        tree, state, expected = self.create_basic_dirstate()
 
1566
        # Now we will just remove and add every file so we get an extra entry
 
1567
        # per entry. Unversion in reverse order so we handle subdirs
 
1568
        tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
 
1569
        tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
 
1570
                 ['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
 
1571
 
 
1572
        # Update the expected dictionary.
 
1573
        for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
 
1574
            orig = expected[path]
 
1575
            path2 = path + '2'
 
1576
            # This record was deleted in the current tree
 
1577
            expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
 
1578
                                        orig[1][1]])
 
1579
            new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
 
1580
            # And didn't exist in the basis tree
 
1581
            expected[path2] = (new_key, [orig[1][0],
 
1582
                                         dirstate.DirState.NULL_PARENT_DETAILS])
 
1583
 
 
1584
        # We will replace the 'dirstate' file underneath 'state', but that is
 
1585
        # okay as lock as we unlock 'state' first.
 
1586
        state.unlock()
 
1587
        try:
 
1588
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
 
1589
            try:
 
1590
                new_state.save()
 
1591
            finally:
 
1592
                new_state.unlock()
 
1593
        finally:
 
1594
            # But we need to leave state in a read-lock because we already have
 
1595
            # a cleanup scheduled
 
1596
            state.lock_read()
 
1597
        return tree, state, expected
 
1598
 
 
1599
    def create_renamed_dirstate(self):
 
1600
        """Create a dirstate with a few internal renames.
 
1601
 
 
1602
        This takes the basic dirstate, and moves the paths around.
 
1603
        """
 
1604
        tree, state, expected = self.create_basic_dirstate()
 
1605
        # Rename a file
 
1606
        tree.rename_one('a', 'b/g')
 
1607
        # And a directory
 
1608
        tree.rename_one('b/d', 'h')
 
1609
 
 
1610
        old_a = expected['a']
 
1611
        expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
 
1612
        expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
 
1613
                                                ('r', 'a', 0, False, '')])
 
1614
        old_d = expected['b/d']
 
1615
        expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
 
1616
        expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
 
1617
                                             ('r', 'b/d', 0, False, '')])
 
1618
 
 
1619
        old_e = expected['b/d/e']
 
1620
        expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
 
1621
                             old_e[1][1]])
 
1622
        expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
 
1623
                                                ('r', 'b/d/e', 0, False, '')])
 
1624
 
 
1625
        state.unlock()
 
1626
        try:
 
1627
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
 
1628
            try:
 
1629
                new_state.save()
 
1630
            finally:
 
1631
                new_state.unlock()
 
1632
        finally:
 
1633
            state.lock_read()
 
1634
        return tree, state, expected
 
1635
 
 
1636
    def assertBisect(self, expected_map, map_keys, state, paths):
 
1637
        """Assert that bisecting for paths returns the right result.
 
1638
 
 
1639
        :param expected_map: A map from key => entry value
 
1640
        :param map_keys: The keys to expect for each path
 
1641
        :param state: The DirState object.
 
1642
        :param paths: A list of paths, these will automatically be split into
 
1643
                      (dir, name) tuples, and sorted according to how _bisect
 
1644
                      requires.
 
1645
        """
 
1646
        dir_names = sorted(osutils.split(p) for p in paths)
 
1647
        result = state._bisect(dir_names)
 
1648
        # For now, results are just returned in whatever order we read them.
 
1649
        # We could sort by (dir, name, file_id) or something like that, but in
 
1650
        # the end it would still be fairly arbitrary, and we don't want the
 
1651
        # extra overhead if we can avoid it. So sort everything to make sure
 
1652
        # equality is true
 
1653
        assert len(map_keys) == len(dir_names)
 
1654
        expected = {}
 
1655
        for dir_name, keys in zip(dir_names, map_keys):
 
1656
            if keys is None:
 
1657
                # This should not be present in the output
 
1658
                continue
 
1659
            expected[dir_name] = sorted(expected_map[k] for k in keys)
 
1660
 
 
1661
        for dir_name in result:
 
1662
            result[dir_name].sort()
 
1663
 
 
1664
        self.assertEqual(expected, result)
 
1665
 
 
1666
    def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
 
1667
        """Assert that bisecting for dirbblocks returns the right result.
 
1668
 
 
1669
        :param expected_map: A map from key => expected values
 
1670
        :param map_keys: A nested list of paths we expect to be returned.
 
1671
            Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
 
1672
        :param state: The DirState object.
 
1673
        :param paths: A list of directories
 
1674
        """
 
1675
        result = state._bisect_dirblocks(paths)
 
1676
        assert len(map_keys) == len(paths)
 
1677
 
 
1678
        expected = {}
 
1679
        for path, keys in zip(paths, map_keys):
 
1680
            if keys is None:
 
1681
                # This should not be present in the output
 
1682
                continue
 
1683
            expected[path] = sorted(expected_map[k] for k in keys)
 
1684
        for path in result:
 
1685
            result[path].sort()
 
1686
 
 
1687
        self.assertEqual(expected, result)
 
1688
 
 
1689
    def assertBisectRecursive(self, expected_map, map_keys, state, paths):
 
1690
        """Assert the return value of a recursive bisection.
 
1691
 
 
1692
        :param expected_map: A map from key => entry value
 
1693
        :param map_keys: A list of paths we expect to be returned.
 
1694
            Something like ['a', 'b', 'f', 'b/d', 'b/d2']
 
1695
        :param state: The DirState object.
 
1696
        :param paths: A list of files and directories. It will be broken up
 
1697
            into (dir, name) pairs and sorted before calling _bisect_recursive.
 
1698
        """
 
1699
        expected = {}
 
1700
        for key in map_keys:
 
1701
            entry = expected_map[key]
 
1702
            dir_name_id, trees_info = entry
 
1703
            expected[dir_name_id] = trees_info
 
1704
 
 
1705
        dir_names = sorted(osutils.split(p) for p in paths)
 
1706
        result = state._bisect_recursive(dir_names)
 
1707
 
 
1708
        self.assertEqual(expected, result)
 
1709
 
 
1710
    def test_bisect_each(self):
 
1711
        """Find a single record using bisect."""
 
1712
        tree, state, expected = self.create_basic_dirstate()
 
1713
 
 
1714
        # Bisect should return the rows for the specified files.
 
1715
        self.assertBisect(expected, [['']], state, [''])
 
1716
        self.assertBisect(expected, [['a']], state, ['a'])
 
1717
        self.assertBisect(expected, [['b']], state, ['b'])
 
1718
        self.assertBisect(expected, [['b/c']], state, ['b/c'])
 
1719
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
 
1720
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
 
1721
        self.assertBisect(expected, [['f']], state, ['f'])
 
1722
 
 
1723
    def test_bisect_multi(self):
 
1724
        """Bisect can be used to find multiple records at the same time."""
 
1725
        tree, state, expected = self.create_basic_dirstate()
 
1726
        # Bisect should be capable of finding multiple entries at the same time
 
1727
        self.assertBisect(expected, [['a'], ['b'], ['f']],
 
1728
                          state, ['a', 'b', 'f'])
 
1729
        # ('', 'f') sorts before the others
 
1730
        self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
 
1731
                          state, ['b/d', 'b/d/e', 'f'])
 
1732
 
 
1733
    def test_bisect_one_page(self):
 
1734
        """Test bisect when there is only 1 page to read"""
 
1735
        tree, state, expected = self.create_basic_dirstate()
 
1736
        state._bisect_page_size = 5000
 
1737
        self.assertBisect(expected,[['']], state, [''])
 
1738
        self.assertBisect(expected,[['a']], state, ['a'])
 
1739
        self.assertBisect(expected,[['b']], state, ['b'])
 
1740
        self.assertBisect(expected,[['b/c']], state, ['b/c'])
 
1741
        self.assertBisect(expected,[['b/d']], state, ['b/d'])
 
1742
        self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
 
1743
        self.assertBisect(expected,[['f']], state, ['f'])
 
1744
        self.assertBisect(expected,[['a'], ['b'], ['f']],
 
1745
                          state, ['a', 'b', 'f'])
 
1746
        # ('', 'f') sorts before the others
 
1747
        self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
 
1748
                          state, ['b/d', 'b/d/e', 'f'])
 
1749
 
 
1750
    def test_bisect_duplicate_paths(self):
 
1751
        """When bisecting for a path, handle multiple entries."""
 
1752
        tree, state, expected = self.create_duplicated_dirstate()
 
1753
 
 
1754
        # Now make sure that both records are properly returned.
 
1755
        self.assertBisect(expected, [['']], state, [''])
 
1756
        self.assertBisect(expected, [['a', 'a2']], state, ['a'])
 
1757
        self.assertBisect(expected, [['b', 'b2']], state, ['b'])
 
1758
        self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
 
1759
        self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
 
1760
        self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
 
1761
                          state, ['b/d/e'])
 
1762
        self.assertBisect(expected, [['f', 'f2']], state, ['f'])
 
1763
 
 
1764
    def test_bisect_page_size_too_small(self):
 
1765
        """If the page size is too small, we will auto increase it."""
 
1766
        tree, state, expected = self.create_basic_dirstate()
 
1767
        state._bisect_page_size = 50
 
1768
        self.assertBisect(expected, [None], state, ['b/e'])
 
1769
        self.assertBisect(expected, [['a']], state, ['a'])
 
1770
        self.assertBisect(expected, [['b']], state, ['b'])
 
1771
        self.assertBisect(expected, [['b/c']], state, ['b/c'])
 
1772
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
 
1773
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
 
1774
        self.assertBisect(expected, [['f']], state, ['f'])
 
1775
 
 
1776
    def test_bisect_missing(self):
 
1777
        """Test that bisect return None if it cannot find a path."""
 
1778
        tree, state, expected = self.create_basic_dirstate()
 
1779
        self.assertBisect(expected, [None], state, ['foo'])
 
1780
        self.assertBisect(expected, [None], state, ['b/foo'])
 
1781
        self.assertBisect(expected, [None], state, ['bar/foo'])
 
1782
 
 
1783
        self.assertBisect(expected, [['a'], None, ['b/d']],
 
1784
                          state, ['a', 'foo', 'b/d'])
 
1785
 
 
1786
    def test_bisect_rename(self):
 
1787
        """Check that we find a renamed row."""
 
1788
        tree, state, expected = self.create_renamed_dirstate()
 
1789
 
 
1790
        # Search for the pre and post renamed entries
 
1791
        self.assertBisect(expected, [['a']], state, ['a'])
 
1792
        self.assertBisect(expected, [['b/g']], state, ['b/g'])
 
1793
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
 
1794
        self.assertBisect(expected, [['h']], state, ['h'])
 
1795
 
 
1796
        # What about b/d/e? shouldn't that also get 2 directory entries?
 
1797
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
 
1798
        self.assertBisect(expected, [['h/e']], state, ['h/e'])
 
1799
 
 
1800
    def test_bisect_dirblocks(self):
 
1801
        tree, state, expected = self.create_duplicated_dirstate()
 
1802
        self.assertBisectDirBlocks(expected,
 
1803
            [['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
 
1804
        self.assertBisectDirBlocks(expected,
 
1805
            [['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
 
1806
        self.assertBisectDirBlocks(expected,
 
1807
            [['b/d/e', 'b/d/e2']], state, ['b/d'])
 
1808
        self.assertBisectDirBlocks(expected,
 
1809
            [['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
 
1810
             ['b/c', 'b/c2', 'b/d', 'b/d2'],
 
1811
             ['b/d/e', 'b/d/e2'],
 
1812
            ], state, ['', 'b', 'b/d'])
 
1813
 
 
1814
    def test_bisect_dirblocks_missing(self):
 
1815
        tree, state, expected = self.create_basic_dirstate()
 
1816
        self.assertBisectDirBlocks(expected, [['b/d/e'], None],
 
1817
            state, ['b/d', 'b/e'])
 
1818
        # Files don't show up in this search
 
1819
        self.assertBisectDirBlocks(expected, [None], state, ['a'])
 
1820
        self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
 
1821
        self.assertBisectDirBlocks(expected, [None], state, ['c'])
 
1822
        self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
 
1823
        self.assertBisectDirBlocks(expected, [None], state, ['f'])
 
1824
 
 
1825
    def test_bisect_recursive_each(self):
 
1826
        tree, state, expected = self.create_basic_dirstate()
 
1827
        self.assertBisectRecursive(expected, ['a'], state, ['a'])
 
1828
        self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
 
1829
        self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
 
1830
        self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
 
1831
                                   state, ['b/d'])
 
1832
        self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
 
1833
                                   state, ['b'])
 
1834
        self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
 
1835
                                              'b/d', 'b/d/e'],
 
1836
                                   state, [''])
 
1837
 
 
1838
    def test_bisect_recursive_multiple(self):
 
1839
        tree, state, expected = self.create_basic_dirstate()
 
1840
        self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
 
1841
        self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
 
1842
                                   state, ['b/d', 'b/d/e'])
 
1843
 
 
1844
    def test_bisect_recursive_missing(self):
 
1845
        tree, state, expected = self.create_basic_dirstate()
 
1846
        self.assertBisectRecursive(expected, [], state, ['d'])
 
1847
        self.assertBisectRecursive(expected, [], state, ['b/e'])
 
1848
        self.assertBisectRecursive(expected, [], state, ['g'])
 
1849
        self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
 
1850
 
 
1851
    def test_bisect_recursive_renamed(self):
 
1852
        tree, state, expected = self.create_renamed_dirstate()
 
1853
 
 
1854
        # Looking for either renamed item should find the other
 
1855
        self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
 
1856
        self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
 
1857
        # Looking in the containing directory should find the rename target,
 
1858
        # and anything in a subdir of the renamed target.
 
1859
        self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
 
1860
                                              'b/d/e', 'b/g', 'h', 'h/e'],
 
1861
                                   state, ['b'])
 
1862
 
 
1863
 
 
1864
class TestBisectDirblock(TestCase):
 
1865
    """Test that bisect_dirblock() returns the expected values.
 
1866
 
 
1867
    bisect_dirblock is intended to work like bisect.bisect_left() except it
 
1868
    knows it is working on dirblocks and that dirblocks are sorted by ('path',
 
1869
    'to', 'foo') chunks rather than by raw 'path/to/foo'.
 
1870
    """
 
1871
 
 
1872
    def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
 
1873
        """Assert that bisect_split works like bisect_left on the split paths.
 
1874
 
 
1875
        :param dirblocks: A list of (path, [info]) pairs.
 
1876
        :param split_dirblocks: A list of ((split, path), [info]) pairs.
 
1877
        :param path: The path we are indexing.
 
1878
 
 
1879
        All other arguments will be passed along.
 
1880
        """
 
1881
        bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
 
1882
                                                 *args, **kwargs)
 
1883
        split_dirblock = (path.split('/'), [])
 
1884
        bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
 
1885
                                             *args)
 
1886
        self.assertEqual(bisect_left_idx, bisect_split_idx,
 
1887
                         'bisect_split disagreed. %s != %s'
 
1888
                         ' for key %s'
 
1889
                         % (bisect_left_idx, bisect_split_idx, path)
 
1890
                         )
 
1891
 
 
1892
    def paths_to_dirblocks(self, paths):
 
1893
        """Convert a list of paths into dirblock form.
 
1894
 
 
1895
        Also, ensure that the paths are in proper sorted order.
 
1896
        """
 
1897
        dirblocks = [(path, []) for path in paths]
 
1898
        split_dirblocks = [(path.split('/'), []) for path in paths]
 
1899
        self.assertEqual(sorted(split_dirblocks), split_dirblocks)
 
1900
        return dirblocks, split_dirblocks
 
1901
 
 
1902
    def test_simple(self):
 
1903
        """In the simple case it works just like bisect_left"""
 
1904
        paths = ['', 'a', 'b', 'c', 'd']
 
1905
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
1906
        for path in paths:
 
1907
            self.assertBisect(dirblocks, split_dirblocks, path)
 
1908
        self.assertBisect(dirblocks, split_dirblocks, '_')
 
1909
        self.assertBisect(dirblocks, split_dirblocks, 'aa')
 
1910
        self.assertBisect(dirblocks, split_dirblocks, 'bb')
 
1911
        self.assertBisect(dirblocks, split_dirblocks, 'cc')
 
1912
        self.assertBisect(dirblocks, split_dirblocks, 'dd')
 
1913
        self.assertBisect(dirblocks, split_dirblocks, 'a/a')
 
1914
        self.assertBisect(dirblocks, split_dirblocks, 'b/b')
 
1915
        self.assertBisect(dirblocks, split_dirblocks, 'c/c')
 
1916
        self.assertBisect(dirblocks, split_dirblocks, 'd/d')
 
1917
 
 
1918
    def test_involved(self):
 
1919
        """This is where bisect_left diverges slightly."""
 
1920
        paths = ['', 'a',
 
1921
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
 
1922
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
 
1923
                 'a-a', 'a-z',
 
1924
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
 
1925
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
 
1926
                 'z-a', 'z-z',
 
1927
                ]
 
1928
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
1929
        for path in paths:
 
1930
            self.assertBisect(dirblocks, split_dirblocks, path)
 
1931
 
 
1932
    def test_involved_cached(self):
 
1933
        """This is where bisect_left diverges slightly."""
 
1934
        paths = ['', 'a',
 
1935
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
 
1936
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
 
1937
                 'a-a', 'a-z',
 
1938
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
 
1939
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
 
1940
                 'z-a', 'z-z',
 
1941
                ]
 
1942
        cache = {}
 
1943
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
1944
        for path in paths:
 
1945
            self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
 
1946