/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: Alexander Belchenko
  • Date: 2007-04-16 20:42:59 UTC
  • mto: This revision was merged to the branch mainline in revision 2422.
  • Revision ID: bialix@ukr.net-20070416204259-tyi3ptqe80gpkwcl
forget to return tree

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