~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

Merge bzr.dev.

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
 
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
        finally:
 
652
            state.unlock()
 
653
        # On win32 you can't read from a locked file, even within the same
 
654
        # process. So we have to unlock and release before we check the file
 
655
        # contents.
 
656
        self.assertFileEqual(''.join(lines), 'dirstate')
 
657
        state.lock_read() # check_state_with_reopen will unlock
 
658
        self.check_state_with_reopen(expected_result, state)
 
659
 
 
660
 
 
661
class TestDirStateManipulations(TestCaseWithDirState):
 
662
 
 
663
    def test_set_state_from_inventory_no_content_no_parents(self):
 
664
        # setting the current inventory is a slow but important api to support.
 
665
        tree1 = self.make_branch_and_memory_tree('tree1')
 
666
        tree1.lock_write()
 
667
        try:
 
668
            tree1.add('')
 
669
            revid1 = tree1.commit('foo').encode('utf8')
 
670
            root_id = tree1.inventory.root.file_id
 
671
            inv = tree1.inventory
 
672
        finally:
 
673
            tree1.unlock()
 
674
        expected_result = [], [
 
675
            (('', '', root_id), [
 
676
             ('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
 
677
        state = dirstate.DirState.initialize('dirstate')
 
678
        try:
 
679
            state.set_state_from_inventory(inv)
 
680
            self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
681
                             state._header_state)
 
682
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
683
                             state._dirblock_state)
 
684
        except:
 
685
            state.unlock()
 
686
            raise
 
687
        else:
 
688
            # This will unlock it
 
689
            self.check_state_with_reopen(expected_result, state)
 
690
 
 
691
    def test_set_path_id_no_parents(self):
 
692
        """The id of a path can be changed trivally with no parents."""
 
693
        state = dirstate.DirState.initialize('dirstate')
 
694
        try:
 
695
            # check precondition to be sure the state does change appropriately.
 
696
            self.assertEqual(
 
697
                [(('', '', 'TREE_ROOT'), [('d', '', 0, False,
 
698
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
 
699
                list(state._iter_entries()))
 
700
            state.set_path_id('', 'foobarbaz')
 
701
            expected_rows = [
 
702
                (('', '', 'foobarbaz'), [('d', '', 0, False,
 
703
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
 
704
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
705
            # should work across save too
 
706
            state.save()
 
707
        finally:
 
708
            state.unlock()
 
709
        state = dirstate.DirState.on_file('dirstate')
 
710
        state.lock_read()
 
711
        try:
 
712
            state._validate()
 
713
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
714
        finally:
 
715
            state.unlock()
 
716
 
 
717
    def test_set_path_id_with_parents(self):
 
718
        """Set the root file id in a dirstate with parents"""
 
719
        mt = self.make_branch_and_tree('mt')
 
720
        # in case the default tree format uses a different root id
 
721
        mt.set_root_id('TREE_ROOT')
 
722
        mt.commit('foo', rev_id='parent-revid')
 
723
        rt = mt.branch.repository.revision_tree('parent-revid')
 
724
        state = dirstate.DirState.initialize('dirstate')
 
725
        state._validate()
 
726
        try:
 
727
            state.set_parent_trees([('parent-revid', rt)], ghosts=[])
 
728
            state.set_path_id('', 'foobarbaz')
 
729
            state._validate()
 
730
            # now see that it is what we expected
 
731
            expected_rows = [
 
732
                (('', '', 'TREE_ROOT'),
 
733
                    [('a', '', 0, False, ''),
 
734
                     ('d', '', 0, False, 'parent-revid'),
 
735
                     ]),
 
736
                (('', '', 'foobarbaz'),
 
737
                    [('d', '', 0, False, ''),
 
738
                     ('a', '', 0, False, ''),
 
739
                     ]),
 
740
                ]
 
741
            state._validate()
 
742
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
743
            # should work across save too
 
744
            state.save()
 
745
        finally:
 
746
            state.unlock()
 
747
        # now flush & check we get the same
 
748
        state = dirstate.DirState.on_file('dirstate')
 
749
        state.lock_read()
 
750
        try:
 
751
            state._validate()
 
752
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
753
        finally:
 
754
            state.unlock()
 
755
        # now change within an existing file-backed state
 
756
        state.lock_write()
 
757
        try:
 
758
            state._validate()
 
759
            state.set_path_id('', 'tree-root-2')
 
760
            state._validate()
 
761
        finally:
 
762
            state.unlock()
 
763
 
 
764
 
 
765
    def test_set_parent_trees_no_content(self):
 
766
        # set_parent_trees is a slow but important api to support.
 
767
        tree1 = self.make_branch_and_memory_tree('tree1')
 
768
        tree1.lock_write()
 
769
        try:
 
770
            tree1.add('')
 
771
            revid1 = tree1.commit('foo')
 
772
        finally:
 
773
            tree1.unlock()
 
774
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
 
775
        tree2 = MemoryTree.create_on_branch(branch2)
 
776
        tree2.lock_write()
 
777
        try:
 
778
            revid2 = tree2.commit('foo')
 
779
            root_id = tree2.inventory.root.file_id
 
780
        finally:
 
781
            tree2.unlock()
 
782
        state = dirstate.DirState.initialize('dirstate')
 
783
        try:
 
784
            state.set_path_id('', root_id)
 
785
            state.set_parent_trees(
 
786
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
787
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
 
788
                 ('ghost-rev', None)),
 
789
                ['ghost-rev'])
 
790
            # check we can reopen and use the dirstate after setting parent
 
791
            # trees.
 
792
            state._validate()
 
793
            state.save()
 
794
            state._validate()
 
795
        finally:
 
796
            state.unlock()
 
797
        state = dirstate.DirState.on_file('dirstate')
 
798
        state.lock_write()
 
799
        try:
 
800
            self.assertEqual([revid1, revid2, 'ghost-rev'],
 
801
                             state.get_parent_ids())
 
802
            # iterating the entire state ensures that the state is parsable.
 
803
            list(state._iter_entries())
 
804
            # be sure that it sets not appends - change it
 
805
            state.set_parent_trees(
 
806
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
807
                 ('ghost-rev', None)),
 
808
                ['ghost-rev'])
 
809
            # and now put it back.
 
810
            state.set_parent_trees(
 
811
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
812
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
 
813
                 ('ghost-rev', tree2.branch.repository.revision_tree(None))),
 
814
                ['ghost-rev'])
 
815
            self.assertEqual([revid1, revid2, 'ghost-rev'],
 
816
                             state.get_parent_ids())
 
817
            # the ghost should be recorded as such by set_parent_trees.
 
818
            self.assertEqual(['ghost-rev'], state.get_ghosts())
 
819
            self.assertEqual(
 
820
                [(('', '', root_id), [
 
821
                  ('d', '', 0, False, dirstate.DirState.NULLSTAT),
 
822
                  ('d', '', 0, False, revid1),
 
823
                  ('d', '', 0, False, revid2)
 
824
                  ])],
 
825
                list(state._iter_entries()))
 
826
        finally:
 
827
            state.unlock()
 
828
 
 
829
    def test_set_parent_trees_file_missing_from_tree(self):
 
830
        # Adding a parent tree may reference files not in the current state.
 
831
        # they should get listed just once by id, even if they are in two
 
832
        # separate trees.
 
833
        # set_parent_trees is a slow but important api to support.
 
834
        tree1 = self.make_branch_and_memory_tree('tree1')
 
835
        tree1.lock_write()
 
836
        try:
 
837
            tree1.add('')
 
838
            tree1.add(['a file'], ['file-id'], ['file'])
 
839
            tree1.put_file_bytes_non_atomic('file-id', 'file-content')
 
840
            revid1 = tree1.commit('foo')
 
841
        finally:
 
842
            tree1.unlock()
 
843
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
 
844
        tree2 = MemoryTree.create_on_branch(branch2)
 
845
        tree2.lock_write()
 
846
        try:
 
847
            tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
 
848
            revid2 = tree2.commit('foo')
 
849
            root_id = tree2.inventory.root.file_id
 
850
        finally:
 
851
            tree2.unlock()
 
852
        # check the layout in memory
 
853
        expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
 
854
            (('', '', root_id), [
 
855
             ('d', '', 0, False, dirstate.DirState.NULLSTAT),
 
856
             ('d', '', 0, False, revid1.encode('utf8')),
 
857
             ('d', '', 0, False, revid2.encode('utf8'))
 
858
             ]),
 
859
            (('', 'a file', 'file-id'), [
 
860
             ('a', '', 0, False, ''),
 
861
             ('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
 
862
              revid1.encode('utf8')),
 
863
             ('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
 
864
              revid2.encode('utf8'))
 
865
             ])
 
866
            ]
 
867
        state = dirstate.DirState.initialize('dirstate')
 
868
        try:
 
869
            state.set_path_id('', root_id)
 
870
            state.set_parent_trees(
 
871
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
872
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
 
873
                 ), [])
 
874
        except:
 
875
            state.unlock()
 
876
            raise
 
877
        else:
 
878
            # check_state_with_reopen will unlock
 
879
            self.check_state_with_reopen(expected_result, state)
 
880
 
 
881
    ### add a path via _set_data - so we dont need delta work, just
 
882
    # raw data in, and ensure that it comes out via get_lines happily.
 
883
 
 
884
    def test_add_path_to_root_no_parents_all_data(self):
 
885
        # The most trivial addition of a path is when there are no parents and
 
886
        # its in the root and all data about the file is supplied
 
887
        self.build_tree(['a file'])
 
888
        stat = os.lstat('a file')
 
889
        # the 1*20 is the sha1 pretend value.
 
890
        state = dirstate.DirState.initialize('dirstate')
 
891
        expected_entries = [
 
892
            (('', '', 'TREE_ROOT'), [
 
893
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
894
             ]),
 
895
            (('', 'a file', 'a file id'), [
 
896
             ('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
 
897
             ]),
 
898
            ]
 
899
        try:
 
900
            state.add('a file', 'a file id', 'file', stat, '1'*20)
 
901
            # having added it, it should be in the output of iter_entries.
 
902
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
903
            # saving and reloading should not affect this.
 
904
            state.save()
 
905
        finally:
 
906
            state.unlock()
 
907
        state = dirstate.DirState.on_file('dirstate')
 
908
        state.lock_read()
 
909
        try:
 
910
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
911
        finally:
 
912
            state.unlock()
 
913
 
 
914
    def test_add_path_to_unversioned_directory(self):
 
915
        """Adding a path to an unversioned directory should error.
 
916
 
 
917
        This is a duplicate of TestWorkingTree.test_add_in_unversioned,
 
918
        once dirstate is stable and if it is merged with WorkingTree3, consider
 
919
        removing this copy of the test.
 
920
        """
 
921
        self.build_tree(['unversioned/', 'unversioned/a file'])
 
922
        state = dirstate.DirState.initialize('dirstate')
 
923
        try:
 
924
            self.assertRaises(errors.NotVersionedError, state.add,
 
925
                'unversioned/a file', 'a file id', 'file', None, None)
 
926
        finally:
 
927
            state.unlock()
 
928
 
 
929
    def test_add_directory_to_root_no_parents_all_data(self):
 
930
        # The most trivial addition of a dir is when there are no parents and
 
931
        # its in the root and all data about the file is supplied
 
932
        self.build_tree(['a dir/'])
 
933
        stat = os.lstat('a dir')
 
934
        expected_entries = [
 
935
            (('', '', 'TREE_ROOT'), [
 
936
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
937
             ]),
 
938
            (('', 'a dir', 'a dir id'), [
 
939
             ('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
 
940
             ]),
 
941
            ]
 
942
        state = dirstate.DirState.initialize('dirstate')
 
943
        try:
 
944
            state.add('a dir', 'a dir id', 'directory', stat, None)
 
945
            # having added it, it should be in the output of iter_entries.
 
946
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
947
            # saving and reloading should not affect this.
 
948
            state.save()
 
949
        finally:
 
950
            state.unlock()
 
951
        state = dirstate.DirState.on_file('dirstate')
 
952
        state.lock_read()
 
953
        state._validate()
 
954
        try:
 
955
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
956
        finally:
 
957
            state.unlock()
 
958
 
 
959
    def test_add_symlink_to_root_no_parents_all_data(self):
 
960
        # The most trivial addition of a symlink when there are no parents and
 
961
        # its in the root and all data about the file is supplied
 
962
        # bzr doesn't support fake symlinks on windows, yet.
 
963
        if not has_symlinks():
 
964
            raise TestSkipped("No symlink support")
 
965
        os.symlink('target', 'a link')
 
966
        stat = os.lstat('a link')
 
967
        expected_entries = [
 
968
            (('', '', 'TREE_ROOT'), [
 
969
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
970
             ]),
 
971
            (('', 'a link', 'a link id'), [
 
972
             ('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
 
973
             ]),
 
974
            ]
 
975
        state = dirstate.DirState.initialize('dirstate')
 
976
        try:
 
977
            state.add('a link', 'a link id', 'symlink', stat, 'target')
 
978
            # having added it, it should be in the output of iter_entries.
 
979
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
980
            # saving and reloading should not affect this.
 
981
            state.save()
 
982
        finally:
 
983
            state.unlock()
 
984
        state = dirstate.DirState.on_file('dirstate')
 
985
        state.lock_read()
 
986
        try:
 
987
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
988
        finally:
 
989
            state.unlock()
 
990
 
 
991
    def test_add_directory_and_child_no_parents_all_data(self):
 
992
        # after adding a directory, we should be able to add children to it.
 
993
        self.build_tree(['a dir/', 'a dir/a file'])
 
994
        dirstat = os.lstat('a dir')
 
995
        filestat = os.lstat('a dir/a file')
 
996
        expected_entries = [
 
997
            (('', '', 'TREE_ROOT'), [
 
998
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
999
             ]),
 
1000
            (('', 'a dir', 'a dir id'), [
 
1001
             ('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
 
1002
             ]),
 
1003
            (('a dir', 'a file', 'a file id'), [
 
1004
             ('f', '1'*20, 25, False,
 
1005
              dirstate.pack_stat(filestat)), # current tree details
 
1006
             ]),
 
1007
            ]
 
1008
        state = dirstate.DirState.initialize('dirstate')
 
1009
        try:
 
1010
            state.add('a dir', 'a dir id', 'directory', dirstat, None)
 
1011
            state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
 
1012
            # added it, it should be in the output of iter_entries.
 
1013
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
1014
            # saving and reloading should not affect this.
 
1015
            state.save()
 
1016
        finally:
 
1017
            state.unlock()
 
1018
        state = dirstate.DirState.on_file('dirstate')
 
1019
        state.lock_read()
 
1020
        try:
 
1021
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
1022
        finally:
 
1023
            state.unlock()
 
1024
 
 
1025
    def test_add_tree_reference(self):
 
1026
        # make a dirstate and add a tree reference
 
1027
        state = dirstate.DirState.initialize('dirstate')
 
1028
        expected_entry = (
 
1029
            ('', 'subdir', 'subdir-id'),
 
1030
            [('t', 'subtree-123123', 0, False,
 
1031
              'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
 
1032
            )
 
1033
        try:
 
1034
            state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
 
1035
            entry = state._get_entry(0, 'subdir-id', 'subdir')
 
1036
            self.assertEqual(entry, expected_entry)
 
1037
            state._validate()
 
1038
            state.save()
 
1039
        finally:
 
1040
            state.unlock()
 
1041
        # now check we can read it back
 
1042
        state.lock_read()
 
1043
        state._validate()
 
1044
        try:
 
1045
            entry2 = state._get_entry(0, 'subdir-id', 'subdir')
 
1046
            self.assertEqual(entry, entry2)
 
1047
            self.assertEqual(entry, expected_entry)
 
1048
            # and lookup by id should work too
 
1049
            entry2 = state._get_entry(0, fileid_utf8='subdir-id')
 
1050
            self.assertEqual(entry, expected_entry)
 
1051
        finally:
 
1052
            state.unlock()
 
1053
 
 
1054
    def test_add_forbidden_names(self):
 
1055
        state = dirstate.DirState.initialize('dirstate')
 
1056
        self.addCleanup(state.unlock)
 
1057
        self.assertRaises(errors.BzrError,
 
1058
            state.add, '.', 'ass-id', 'directory', None, None)
 
1059
        self.assertRaises(errors.BzrError,
 
1060
            state.add, '..', 'ass-id', 'directory', None, None)
 
1061
 
 
1062
 
 
1063
class TestGetLines(TestCaseWithDirState):
 
1064
 
 
1065
    def test_get_line_with_2_rows(self):
 
1066
        state = self.create_dirstate_with_root_and_subdir()
 
1067
        try:
 
1068
            self.assertEqual(['#bazaar dirstate flat format 3\n',
 
1069
                'crc32: 41262208\n',
 
1070
                'num_entries: 2\n',
 
1071
                '0\x00\n\x00'
 
1072
                '0\x00\n\x00'
 
1073
                '\x00\x00a-root-value\x00'
 
1074
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
 
1075
                '\x00subdir\x00subdir-id\x00'
 
1076
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
 
1077
                ], state.get_lines())
 
1078
        finally:
 
1079
            state.unlock()
 
1080
 
 
1081
    def test_entry_to_line(self):
 
1082
        state = self.create_dirstate_with_root()
 
1083
        try:
 
1084
            self.assertEqual(
 
1085
                '\x00\x00a-root-value\x00d\x00\x000\x00n'
 
1086
                '\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
 
1087
                state._entry_to_line(state._dirblocks[0][1][0]))
 
1088
        finally:
 
1089
            state.unlock()
 
1090
 
 
1091
    def test_entry_to_line_with_parent(self):
 
1092
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
1093
        root_entry = ('', '', 'a-root-value'), [
 
1094
            ('d', '', 0, False, packed_stat), # current tree details
 
1095
             # first: a pointer to the current location
 
1096
            ('a', 'dirname/basename', 0, False, ''),
 
1097
            ]
 
1098
        state = dirstate.DirState.initialize('dirstate')
 
1099
        try:
 
1100
            self.assertEqual(
 
1101
                '\x00\x00a-root-value\x00'
 
1102
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
 
1103
                'a\x00dirname/basename\x000\x00n\x00',
 
1104
                state._entry_to_line(root_entry))
 
1105
        finally:
 
1106
            state.unlock()
 
1107
 
 
1108
    def test_entry_to_line_with_two_parents_at_different_paths(self):
 
1109
        # / in the tree, at / in one parent and /dirname/basename in the other.
 
1110
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
1111
        root_entry = ('', '', 'a-root-value'), [
 
1112
            ('d', '', 0, False, packed_stat), # current tree details
 
1113
            ('d', '', 0, False, 'rev_id'), # first parent details
 
1114
             # second: a pointer to the current location
 
1115
            ('a', 'dirname/basename', 0, False, ''),
 
1116
            ]
 
1117
        state = dirstate.DirState.initialize('dirstate')
 
1118
        try:
 
1119
            self.assertEqual(
 
1120
                '\x00\x00a-root-value\x00'
 
1121
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
 
1122
                'd\x00\x000\x00n\x00rev_id\x00'
 
1123
                'a\x00dirname/basename\x000\x00n\x00',
 
1124
                state._entry_to_line(root_entry))
 
1125
        finally:
 
1126
            state.unlock()
 
1127
 
 
1128
    def test_iter_entries(self):
 
1129
        # we should be able to iterate the dirstate entries from end to end
 
1130
        # this is for get_lines to be easy to read.
 
1131
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
1132
        dirblocks = []
 
1133
        root_entries = [(('', '', 'a-root-value'), [
 
1134
            ('d', '', 0, False, packed_stat), # current tree details
 
1135
            ])]
 
1136
        dirblocks.append(('', root_entries))
 
1137
        # add two files in the root
 
1138
        subdir_entry = ('', 'subdir', 'subdir-id'), [
 
1139
            ('d', '', 0, False, packed_stat), # current tree details
 
1140
            ]
 
1141
        afile_entry = ('', 'afile', 'afile-id'), [
 
1142
            ('f', 'sha1value', 34, False, packed_stat), # current tree details
 
1143
            ]
 
1144
        dirblocks.append(('', [subdir_entry, afile_entry]))
 
1145
        # and one in subdir
 
1146
        file_entry2 = ('subdir', '2file', '2file-id'), [
 
1147
            ('f', 'sha1value', 23, False, packed_stat), # current tree details
 
1148
            ]
 
1149
        dirblocks.append(('subdir', [file_entry2]))
 
1150
        state = dirstate.DirState.initialize('dirstate')
 
1151
        try:
 
1152
            state._set_data([], dirblocks)
 
1153
            expected_entries = [root_entries[0], subdir_entry, afile_entry,
 
1154
                                file_entry2]
 
1155
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
1156
        finally:
 
1157
            state.unlock()
 
1158
 
 
1159
 
 
1160
class TestGetBlockRowIndex(TestCaseWithDirState):
 
1161
 
 
1162
    def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
 
1163
        file_present, state, dirname, basename, tree_index):
 
1164
        self.assertEqual((block_index, row_index, dir_present, file_present),
 
1165
            state._get_block_entry_index(dirname, basename, tree_index))
 
1166
        if dir_present:
 
1167
            block = state._dirblocks[block_index]
 
1168
            self.assertEqual(dirname, block[0])
 
1169
        if dir_present and file_present:
 
1170
            row = state._dirblocks[block_index][1][row_index]
 
1171
            self.assertEqual(dirname, row[0][0])
 
1172
            self.assertEqual(basename, row[0][1])
 
1173
 
 
1174
    def test_simple_structure(self):
 
1175
        state = self.create_dirstate_with_root_and_subdir()
 
1176
        self.addCleanup(state.unlock)
 
1177
        self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
 
1178
        self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
 
1179
        self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
 
1180
        self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
 
1181
        self.assertBlockRowIndexEqual(2, 0, False, False, state,
 
1182
                                      'subdir', 'foo', 0)
 
1183
 
 
1184
    def test_complex_structure_exists(self):
 
1185
        state = self.create_complex_dirstate()
 
1186
        self.addCleanup(state.unlock)
 
1187
        # Make sure we can find everything that exists
 
1188
        self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
 
1189
        self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
 
1190
        self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
 
1191
        self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
 
1192
        self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
 
1193
        self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
 
1194
        self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
 
1195
        self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
 
1196
        self.assertBlockRowIndexEqual(3, 1, True, True, state,
 
1197
                                      'b', 'h\xc3\xa5', 0)
 
1198
 
 
1199
    def test_complex_structure_missing(self):
 
1200
        state = self.create_complex_dirstate()
 
1201
        self.addCleanup(state.unlock)
 
1202
        # Make sure things would be inserted in the right locations
 
1203
        # '_' comes before 'a'
 
1204
        self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
 
1205
        self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
 
1206
        self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
 
1207
        self.assertBlockRowIndexEqual(1, 4, True, False, state,
 
1208
                                      '', 'h\xc3\xa5', 0)
 
1209
        self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
 
1210
        self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
 
1211
        self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
 
1212
        # This would be inserted between a/ and b/
 
1213
        self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
 
1214
        # Put at the end
 
1215
        self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
 
1216
 
 
1217
 
 
1218
class TestGetEntry(TestCaseWithDirState):
 
1219
 
 
1220
    def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
 
1221
        """Check that the right entry is returned for a request to getEntry."""
 
1222
        entry = state._get_entry(index, path_utf8=path)
 
1223
        if file_id is None:
 
1224
            self.assertEqual((None, None), entry)
 
1225
        else:
 
1226
            cur = entry[0]
 
1227
            self.assertEqual((dirname, basename, file_id), cur[:3])
 
1228
 
 
1229
    def test_simple_structure(self):
 
1230
        state = self.create_dirstate_with_root_and_subdir()
 
1231
        self.addCleanup(state.unlock)
 
1232
        self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
 
1233
        self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
 
1234
        self.assertEntryEqual(None, None, None, state, 'missing', 0)
 
1235
        self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
 
1236
        self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
 
1237
 
 
1238
    def test_complex_structure_exists(self):
 
1239
        state = self.create_complex_dirstate()
 
1240
        self.addCleanup(state.unlock)
 
1241
        self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
 
1242
        self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
 
1243
        self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
 
1244
        self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
 
1245
        self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
 
1246
        self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
 
1247
        self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
 
1248
        self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
 
1249
        self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
 
1250
                              'b/h\xc3\xa5', 0)
 
1251
 
 
1252
    def test_complex_structure_missing(self):
 
1253
        state = self.create_complex_dirstate()
 
1254
        self.addCleanup(state.unlock)
 
1255
        self.assertEntryEqual(None, None, None, state, '_', 0)
 
1256
        self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
 
1257
        self.assertEntryEqual(None, None, None, state, 'a/b', 0)
 
1258
        self.assertEntryEqual(None, None, None, state, 'c/d', 0)
 
1259
 
 
1260
    def test_get_entry_uninitialized(self):
 
1261
        """Calling get_entry will load data if it needs to"""
 
1262
        state = self.create_dirstate_with_root()
 
1263
        try:
 
1264
            state.save()
 
1265
        finally:
 
1266
            state.unlock()
 
1267
        del state
 
1268
        state = dirstate.DirState.on_file('dirstate')
 
1269
        state.lock_read()
 
1270
        try:
 
1271
            self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
 
1272
                             state._header_state)
 
1273
            self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
 
1274
                             state._dirblock_state)
 
1275
            self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
 
1276
        finally:
 
1277
            state.unlock()
 
1278
 
 
1279
 
 
1280
class TestDirstateSortOrder(TestCaseWithTransport):
 
1281
    """Test that DirState adds entries in the right order."""
 
1282
 
 
1283
    def test_add_sorting(self):
 
1284
        """Add entries in lexicographical order, we get path sorted order.
 
1285
 
 
1286
        This tests it to a depth of 4, to make sure we don't just get it right
 
1287
        at a single depth. 'a/a' should come before 'a-a', even though it
 
1288
        doesn't lexicographically.
 
1289
        """
 
1290
        dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
 
1291
                'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
 
1292
               ]
 
1293
        null_sha = ''
 
1294
        state = dirstate.DirState.initialize('dirstate')
 
1295
        self.addCleanup(state.unlock)
 
1296
 
 
1297
        fake_stat = os.stat('dirstate')
 
1298
        for d in dirs:
 
1299
            d_id = d.replace('/', '_')+'-id'
 
1300
            file_path = d + '/f'
 
1301
            file_id = file_path.replace('/', '_')+'-id'
 
1302
            state.add(d, d_id, 'directory', fake_stat, null_sha)
 
1303
            state.add(file_path, file_id, 'file', fake_stat, null_sha)
 
1304
 
 
1305
        expected = ['', '', 'a',
 
1306
                'a/a', 'a/a/a', 'a/a/a/a',
 
1307
                'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
 
1308
               ]
 
1309
        split = lambda p:p.split('/')
 
1310
        self.assertEqual(sorted(expected, key=split), expected)
 
1311
        dirblock_names = [d[0] for d in state._dirblocks]
 
1312
        self.assertEqual(expected, dirblock_names)
 
1313
 
 
1314
    def test_set_parent_trees_correct_order(self):
 
1315
        """After calling set_parent_trees() we should maintain the order."""
 
1316
        dirs = ['a', 'a-a', 'a/a']
 
1317
        null_sha = ''
 
1318
        state = dirstate.DirState.initialize('dirstate')
 
1319
        self.addCleanup(state.unlock)
 
1320
 
 
1321
        fake_stat = os.stat('dirstate')
 
1322
        for d in dirs:
 
1323
            d_id = d.replace('/', '_')+'-id'
 
1324
            file_path = d + '/f'
 
1325
            file_id = file_path.replace('/', '_')+'-id'
 
1326
            state.add(d, d_id, 'directory', fake_stat, null_sha)
 
1327
            state.add(file_path, file_id, 'file', fake_stat, null_sha)
 
1328
 
 
1329
        expected = ['', '', 'a', 'a/a', 'a-a']
 
1330
        dirblock_names = [d[0] for d in state._dirblocks]
 
1331
        self.assertEqual(expected, dirblock_names)
 
1332
 
 
1333
        # *really* cheesy way to just get an empty tree
 
1334
        repo = self.make_repository('repo')
 
1335
        empty_tree = repo.revision_tree(None)
 
1336
        state.set_parent_trees([('null:', empty_tree)], [])
 
1337
 
 
1338
        dirblock_names = [d[0] for d in state._dirblocks]
 
1339
        self.assertEqual(expected, dirblock_names)
 
1340
 
 
1341
 
 
1342
class InstrumentedDirState(dirstate.DirState):
 
1343
    """An DirState with instrumented sha1 functionality."""
 
1344
 
 
1345
    def __init__(self, path):
 
1346
        super(InstrumentedDirState, self).__init__(path)
 
1347
        self._time_offset = 0
 
1348
        self._log = []
 
1349
 
 
1350
    def _sha_cutoff_time(self):
 
1351
        timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
 
1352
        self._cutoff_time = timestamp + self._time_offset
 
1353
 
 
1354
    def _sha1_file(self, abspath, entry):
 
1355
        self._log.append(('sha1', abspath))
 
1356
        return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
 
1357
 
 
1358
    def _read_link(self, abspath, old_link):
 
1359
        self._log.append(('read_link', abspath, old_link))
 
1360
        return super(InstrumentedDirState, self)._read_link(abspath, old_link)
 
1361
 
 
1362
    def _lstat(self, abspath, entry):
 
1363
        self._log.append(('lstat', abspath))
 
1364
        return super(InstrumentedDirState, self)._lstat(abspath, entry)
 
1365
 
 
1366
    def _is_executable(self, mode, old_executable):
 
1367
        self._log.append(('is_exec', mode, old_executable))
 
1368
        return super(InstrumentedDirState, self)._is_executable(mode,
 
1369
                                                                old_executable)
 
1370
 
 
1371
    def adjust_time(self, secs):
 
1372
        """Move the clock forward or back.
 
1373
 
 
1374
        :param secs: The amount to adjust the clock by. Positive values make it
 
1375
        seem as if we are in the future, negative values make it seem like we
 
1376
        are in the past.
 
1377
        """
 
1378
        self._time_offset += secs
 
1379
        self._cutoff_time = None
 
1380
 
 
1381
 
 
1382
class _FakeStat(object):
 
1383
    """A class with the same attributes as a real stat result."""
 
1384
 
 
1385
    def __init__(self, size, mtime, ctime, dev, ino, mode):
 
1386
        self.st_size = size
 
1387
        self.st_mtime = mtime
 
1388
        self.st_ctime = ctime
 
1389
        self.st_dev = dev
 
1390
        self.st_ino = ino
 
1391
        self.st_mode = mode
 
1392
 
 
1393
 
 
1394
class TestUpdateEntry(TestCaseWithDirState):
 
1395
    """Test the DirState.update_entry functions"""
 
1396
 
 
1397
    def get_state_with_a(self):
 
1398
        """Create a DirState tracking a single object named 'a'"""
 
1399
        state = InstrumentedDirState.initialize('dirstate')
 
1400
        self.addCleanup(state.unlock)
 
1401
        state.add('a', 'a-id', 'file', None, '')
 
1402
        entry = state._get_entry(0, path_utf8='a')
 
1403
        return state, entry
 
1404
 
 
1405
    def test_update_entry(self):
 
1406
        state, entry = self.get_state_with_a()
 
1407
        self.build_tree(['a'])
 
1408
        # Add one where we don't provide the stat or sha already
 
1409
        self.assertEqual(('', 'a', 'a-id'), entry[0])
 
1410
        self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
 
1411
                         entry[1])
 
1412
        # Flush the buffers to disk
 
1413
        state.save()
 
1414
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
1415
                         state._dirblock_state)
 
1416
 
 
1417
        stat_value = os.lstat('a')
 
1418
        packed_stat = dirstate.pack_stat(stat_value)
 
1419
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1420
                                          stat_value=stat_value)
 
1421
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1422
                         link_or_sha1)
 
1423
 
 
1424
        # The dirblock entry should be updated with the new info
 
1425
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
 
1426
                         entry[1])
 
1427
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
1428
                         state._dirblock_state)
 
1429
        mode = stat_value.st_mode
 
1430
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
 
1431
 
 
1432
        state.save()
 
1433
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
1434
                         state._dirblock_state)
 
1435
 
 
1436
        # If we do it again right away, we don't know if the file has changed
 
1437
        # so we will re-read the file. Roll the clock back so the file is
 
1438
        # guaranteed to look too new.
 
1439
        state.adjust_time(-10)
 
1440
 
 
1441
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1442
                                          stat_value=stat_value)
 
1443
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
 
1444
                          ('sha1', 'a'), ('is_exec', mode, False),
 
1445
                         ], state._log)
 
1446
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1447
                         link_or_sha1)
 
1448
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
1449
                         state._dirblock_state)
 
1450
        state.save()
 
1451
 
 
1452
        # However, if we move the clock forward so the file is considered
 
1453
        # "stable", it should just returned the cached value.
 
1454
        state.adjust_time(20)
 
1455
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1456
                                          stat_value=stat_value)
 
1457
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1458
                         link_or_sha1)
 
1459
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
 
1460
                          ('sha1', 'a'), ('is_exec', mode, False),
 
1461
                         ], state._log)
 
1462
 
 
1463
    def test_update_entry_no_stat_value(self):
 
1464
        """Passing the stat_value is optional."""
 
1465
        state, entry = self.get_state_with_a()
 
1466
        state.adjust_time(-10) # Make sure the file looks new
 
1467
        self.build_tree(['a'])
 
1468
        # Add one where we don't provide the stat or sha already
 
1469
        link_or_sha1 = state.update_entry(entry, abspath='a')
 
1470
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1471
                         link_or_sha1)
 
1472
        stat_value = os.lstat('a')
 
1473
        self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
 
1474
                          ('is_exec', stat_value.st_mode, False),
 
1475
                         ], state._log)
 
1476
 
 
1477
    def test_update_entry_symlink(self):
 
1478
        """Update entry should read symlinks."""
 
1479
        if not osutils.has_symlinks():
 
1480
            # PlatformDeficiency / TestSkipped
 
1481
            raise TestSkipped("No symlink support")
 
1482
        state, entry = self.get_state_with_a()
 
1483
        state.save()
 
1484
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
1485
                         state._dirblock_state)
 
1486
        os.symlink('target', 'a')
 
1487
 
 
1488
        state.adjust_time(-10) # Make the symlink look new
 
1489
        stat_value = os.lstat('a')
 
1490
        packed_stat = dirstate.pack_stat(stat_value)
 
1491
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1492
                                          stat_value=stat_value)
 
1493
        self.assertEqual('target', link_or_sha1)
 
1494
        self.assertEqual([('read_link', 'a', '')], state._log)
 
1495
        # Dirblock is updated
 
1496
        self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
 
1497
                         entry[1])
 
1498
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
1499
                         state._dirblock_state)
 
1500
 
 
1501
        # Because the stat_value looks new, we should re-read the target
 
1502
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1503
                                          stat_value=stat_value)
 
1504
        self.assertEqual('target', link_or_sha1)
 
1505
        self.assertEqual([('read_link', 'a', ''),
 
1506
                          ('read_link', 'a', 'target'),
 
1507
                         ], state._log)
 
1508
        state.adjust_time(+20) # Skip into the future, all files look old
 
1509
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1510
                                          stat_value=stat_value)
 
1511
        self.assertEqual('target', link_or_sha1)
 
1512
        # There should not be a new read_link call.
 
1513
        # (this is a weak assertion, because read_link is fairly inexpensive,
 
1514
        # versus the number of symlinks that we would have)
 
1515
        self.assertEqual([('read_link', 'a', ''),
 
1516
                          ('read_link', 'a', 'target'),
 
1517
                         ], state._log)
 
1518
 
 
1519
    def test_update_entry_dir(self):
 
1520
        state, entry = self.get_state_with_a()
 
1521
        self.build_tree(['a/'])
 
1522
        self.assertIs(None, state.update_entry(entry, 'a'))
 
1523
 
 
1524
    def create_and_test_file(self, state, entry):
 
1525
        """Create a file at 'a' and verify the state finds it.
 
1526
 
 
1527
        The state should already be versioning *something* at 'a'. This makes
 
1528
        sure that state.update_entry recognizes it as a file.
 
1529
        """
 
1530
        self.build_tree(['a'])
 
1531
        stat_value = os.lstat('a')
 
1532
        packed_stat = dirstate.pack_stat(stat_value)
 
1533
 
 
1534
        link_or_sha1 = state.update_entry(entry, abspath='a')
 
1535
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1536
                         link_or_sha1)
 
1537
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
 
1538
                         entry[1])
 
1539
        return packed_stat
 
1540
 
 
1541
    def create_and_test_dir(self, state, entry):
 
1542
        """Create a directory at 'a' and verify the state finds it.
 
1543
 
 
1544
        The state should already be versioning *something* at 'a'. This makes
 
1545
        sure that state.update_entry recognizes it as a directory.
 
1546
        """
 
1547
        self.build_tree(['a/'])
 
1548
        stat_value = os.lstat('a')
 
1549
        packed_stat = dirstate.pack_stat(stat_value)
 
1550
 
 
1551
        link_or_sha1 = state.update_entry(entry, abspath='a')
 
1552
        self.assertIs(None, link_or_sha1)
 
1553
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
 
1554
 
 
1555
        return packed_stat
 
1556
 
 
1557
    def create_and_test_symlink(self, state, entry):
 
1558
        """Create a symlink at 'a' and verify the state finds it.
 
1559
 
 
1560
        The state should already be versioning *something* at 'a'. This makes
 
1561
        sure that state.update_entry recognizes it as a symlink.
 
1562
 
 
1563
        This should not be called if this platform does not have symlink
 
1564
        support.
 
1565
        """
 
1566
        # caller should care about skipping test on platforms without symlinks
 
1567
        os.symlink('path/to/foo', 'a')
 
1568
 
 
1569
        stat_value = os.lstat('a')
 
1570
        packed_stat = dirstate.pack_stat(stat_value)
 
1571
 
 
1572
        link_or_sha1 = state.update_entry(entry, abspath='a')
 
1573
        self.assertEqual('path/to/foo', link_or_sha1)
 
1574
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
 
1575
                         entry[1])
 
1576
        return packed_stat
 
1577
 
 
1578
    def test_update_missing_file(self):
 
1579
        state, entry = self.get_state_with_a()
 
1580
        packed_stat = self.create_and_test_file(state, entry)
 
1581
        # Now if we delete the file, update_entry should recover and
 
1582
        # return None.
 
1583
        os.remove('a')
 
1584
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1585
        # And the record shouldn't be changed.
 
1586
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
 
1587
        self.assertEqual([('f', digest, 14, False, packed_stat)],
 
1588
                         entry[1])
 
1589
 
 
1590
    def test_update_missing_dir(self):
 
1591
        state, entry = self.get_state_with_a()
 
1592
        packed_stat = self.create_and_test_dir(state, entry)
 
1593
        # Now if we delete the directory, update_entry should recover and
 
1594
        # return None.
 
1595
        os.rmdir('a')
 
1596
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1597
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
 
1598
 
 
1599
    def test_update_missing_symlink(self):
 
1600
        if not osutils.has_symlinks():
 
1601
            # PlatformDeficiency / TestSkipped
 
1602
            raise TestSkipped("No symlink support")
 
1603
        state, entry = self.get_state_with_a()
 
1604
        packed_stat = self.create_and_test_symlink(state, entry)
 
1605
        os.remove('a')
 
1606
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1607
        # And the record shouldn't be changed.
 
1608
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
 
1609
                         entry[1])
 
1610
 
 
1611
    def test_update_file_to_dir(self):
 
1612
        """If a file changes to a directory we return None for the sha.
 
1613
        We also update the inventory record.
 
1614
        """
 
1615
        state, entry = self.get_state_with_a()
 
1616
        self.create_and_test_file(state, entry)
 
1617
        os.remove('a')
 
1618
        self.create_and_test_dir(state, entry)
 
1619
 
 
1620
    def test_update_file_to_symlink(self):
 
1621
        """File becomes a symlink"""
 
1622
        if not osutils.has_symlinks():
 
1623
            # PlatformDeficiency / TestSkipped
 
1624
            raise TestSkipped("No symlink support")
 
1625
        state, entry = self.get_state_with_a()
 
1626
        self.create_and_test_file(state, entry)
 
1627
        os.remove('a')
 
1628
        self.create_and_test_symlink(state, entry)
 
1629
 
 
1630
    def test_update_dir_to_file(self):
 
1631
        """Directory becoming a file updates the entry."""
 
1632
        state, entry = self.get_state_with_a()
 
1633
        self.create_and_test_dir(state, entry)
 
1634
        os.rmdir('a')
 
1635
        self.create_and_test_file(state, entry)
 
1636
 
 
1637
    def test_update_dir_to_symlink(self):
 
1638
        """Directory becomes a symlink"""
 
1639
        if not osutils.has_symlinks():
 
1640
            # PlatformDeficiency / TestSkipped
 
1641
            raise TestSkipped("No symlink support")
 
1642
        state, entry = self.get_state_with_a()
 
1643
        self.create_and_test_dir(state, entry)
 
1644
        os.rmdir('a')
 
1645
        self.create_and_test_symlink(state, entry)
 
1646
 
 
1647
    def test_update_symlink_to_file(self):
 
1648
        """Symlink becomes a file"""
 
1649
        if not has_symlinks():
 
1650
            raise TestSkipped("No symlink support")
 
1651
        state, entry = self.get_state_with_a()
 
1652
        self.create_and_test_symlink(state, entry)
 
1653
        os.remove('a')
 
1654
        self.create_and_test_file(state, entry)
 
1655
 
 
1656
    def test_update_symlink_to_dir(self):
 
1657
        """Symlink becomes a directory"""
 
1658
        if not has_symlinks():
 
1659
            raise TestSkipped("No symlink support")
 
1660
        state, entry = self.get_state_with_a()
 
1661
        self.create_and_test_symlink(state, entry)
 
1662
        os.remove('a')
 
1663
        self.create_and_test_dir(state, entry)
 
1664
 
 
1665
    def test__is_executable_win32(self):
 
1666
        state, entry = self.get_state_with_a()
 
1667
        self.build_tree(['a'])
 
1668
 
 
1669
        # Make sure we are using the win32 implementation of _is_executable
 
1670
        state._is_executable = state._is_executable_win32
 
1671
 
 
1672
        # The file on disk is not executable, but we are marking it as though
 
1673
        # it is. With _is_executable_win32 we ignore what is on disk.
 
1674
        entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
 
1675
 
 
1676
        stat_value = os.lstat('a')
 
1677
        packed_stat = dirstate.pack_stat(stat_value)
 
1678
 
 
1679
        state.adjust_time(-10) # Make sure everything is new
 
1680
        # Make sure it wants to kkkkkkkk
 
1681
        state.update_entry(entry, abspath='a', stat_value=stat_value)
 
1682
 
 
1683
        # The row is updated, but the executable bit stays set.
 
1684
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
 
1685
        self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
 
1686
 
 
1687
 
 
1688
class TestPackStat(TestCaseWithTransport):
 
1689
 
 
1690
    def assertPackStat(self, expected, stat_value):
 
1691
        """Check the packed and serialized form of a stat value."""
 
1692
        self.assertEqual(expected, dirstate.pack_stat(stat_value))
 
1693
 
 
1694
    def test_pack_stat_int(self):
 
1695
        st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
 
1696
        # Make sure that all parameters have an impact on the packed stat.
 
1697
        self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
 
1698
        st.st_size = 7000L
 
1699
        #                ay0 => bWE
 
1700
        self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
 
1701
        st.st_mtime = 1172758620
 
1702
        #                     4FZ => 4Fx
 
1703
        self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
 
1704
        st.st_ctime = 1172758630
 
1705
        #                          uBZ => uBm
 
1706
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1707
        st.st_dev = 888L
 
1708
        #                                DCQ => DeA
 
1709
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
 
1710
        st.st_ino = 6499540L
 
1711
        #                                     LNI => LNQ
 
1712
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
 
1713
        st.st_mode = 0100744
 
1714
        #                                          IGk => IHk
 
1715
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
 
1716
 
 
1717
    def test_pack_stat_float(self):
 
1718
        """On some platforms mtime and ctime are floats.
 
1719
 
 
1720
        Make sure we don't get warnings or errors, and that we ignore changes <
 
1721
        1s
 
1722
        """
 
1723
        st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
 
1724
                       777L, 6499538L, 0100644)
 
1725
        # These should all be the same as the integer counterparts
 
1726
        self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
 
1727
        st.st_mtime = 1172758620.0
 
1728
        #                     FZF5 => FxF5
 
1729
        self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
 
1730
        st.st_ctime = 1172758630.0
 
1731
        #                          uBZ => uBm
 
1732
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1733
        # fractional seconds are discarded, so no change from above
 
1734
        st.st_mtime = 1172758620.453
 
1735
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1736
        st.st_ctime = 1172758630.228
 
1737
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1738
 
 
1739
 
 
1740
class TestBisect(TestCaseWithDirState):
 
1741
    """Test the ability to bisect into the disk format."""
 
1742
 
 
1743
 
 
1744
    def assertBisect(self, expected_map, map_keys, state, paths):
 
1745
        """Assert that bisecting for paths returns the right result.
 
1746
 
 
1747
        :param expected_map: A map from key => entry value
 
1748
        :param map_keys: The keys to expect for each path
 
1749
        :param state: The DirState object.
 
1750
        :param paths: A list of paths, these will automatically be split into
 
1751
                      (dir, name) tuples, and sorted according to how _bisect
 
1752
                      requires.
 
1753
        """
 
1754
        dir_names = sorted(osutils.split(p) for p in paths)
 
1755
        result = state._bisect(dir_names)
 
1756
        # For now, results are just returned in whatever order we read them.
 
1757
        # We could sort by (dir, name, file_id) or something like that, but in
 
1758
        # the end it would still be fairly arbitrary, and we don't want the
 
1759
        # extra overhead if we can avoid it. So sort everything to make sure
 
1760
        # equality is true
 
1761
        assert len(map_keys) == len(dir_names)
 
1762
        expected = {}
 
1763
        for dir_name, keys in zip(dir_names, map_keys):
 
1764
            if keys is None:
 
1765
                # This should not be present in the output
 
1766
                continue
 
1767
            expected[dir_name] = sorted(expected_map[k] for k in keys)
 
1768
 
 
1769
        for dir_name in result:
 
1770
            result[dir_name].sort()
 
1771
 
 
1772
        self.assertEqual(expected, result)
 
1773
 
 
1774
    def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
 
1775
        """Assert that bisecting for dirbblocks returns the right result.
 
1776
 
 
1777
        :param expected_map: A map from key => expected values
 
1778
        :param map_keys: A nested list of paths we expect to be returned.
 
1779
            Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
 
1780
        :param state: The DirState object.
 
1781
        :param paths: A list of directories
 
1782
        """
 
1783
        result = state._bisect_dirblocks(paths)
 
1784
        assert len(map_keys) == len(paths)
 
1785
 
 
1786
        expected = {}
 
1787
        for path, keys in zip(paths, map_keys):
 
1788
            if keys is None:
 
1789
                # This should not be present in the output
 
1790
                continue
 
1791
            expected[path] = sorted(expected_map[k] for k in keys)
 
1792
        for path in result:
 
1793
            result[path].sort()
 
1794
 
 
1795
        self.assertEqual(expected, result)
 
1796
 
 
1797
    def assertBisectRecursive(self, expected_map, map_keys, state, paths):
 
1798
        """Assert the return value of a recursive bisection.
 
1799
 
 
1800
        :param expected_map: A map from key => entry value
 
1801
        :param map_keys: A list of paths we expect to be returned.
 
1802
            Something like ['a', 'b', 'f', 'b/d', 'b/d2']
 
1803
        :param state: The DirState object.
 
1804
        :param paths: A list of files and directories. It will be broken up
 
1805
            into (dir, name) pairs and sorted before calling _bisect_recursive.
 
1806
        """
 
1807
        expected = {}
 
1808
        for key in map_keys:
 
1809
            entry = expected_map[key]
 
1810
            dir_name_id, trees_info = entry
 
1811
            expected[dir_name_id] = trees_info
 
1812
 
 
1813
        dir_names = sorted(osutils.split(p) for p in paths)
 
1814
        result = state._bisect_recursive(dir_names)
 
1815
 
 
1816
        self.assertEqual(expected, result)
 
1817
 
 
1818
    def test_bisect_each(self):
 
1819
        """Find a single record using bisect."""
 
1820
        tree, state, expected = self.create_basic_dirstate()
 
1821
 
 
1822
        # Bisect should return the rows for the specified files.
 
1823
        self.assertBisect(expected, [['']], state, [''])
 
1824
        self.assertBisect(expected, [['a']], state, ['a'])
 
1825
        self.assertBisect(expected, [['b']], state, ['b'])
 
1826
        self.assertBisect(expected, [['b/c']], state, ['b/c'])
 
1827
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
 
1828
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
 
1829
        self.assertBisect(expected, [['f']], state, ['f'])
 
1830
 
 
1831
    def test_bisect_multi(self):
 
1832
        """Bisect can be used to find multiple records at the same time."""
 
1833
        tree, state, expected = self.create_basic_dirstate()
 
1834
        # Bisect should be capable of finding multiple entries at the same time
 
1835
        self.assertBisect(expected, [['a'], ['b'], ['f']],
 
1836
                          state, ['a', 'b', 'f'])
 
1837
        # ('', 'f') sorts before the others
 
1838
        self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
 
1839
                          state, ['b/d', 'b/d/e', 'f'])
 
1840
 
 
1841
    def test_bisect_one_page(self):
 
1842
        """Test bisect when there is only 1 page to read"""
 
1843
        tree, state, expected = self.create_basic_dirstate()
 
1844
        state._bisect_page_size = 5000
 
1845
        self.assertBisect(expected,[['']], state, [''])
 
1846
        self.assertBisect(expected,[['a']], state, ['a'])
 
1847
        self.assertBisect(expected,[['b']], state, ['b'])
 
1848
        self.assertBisect(expected,[['b/c']], state, ['b/c'])
 
1849
        self.assertBisect(expected,[['b/d']], state, ['b/d'])
 
1850
        self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
 
1851
        self.assertBisect(expected,[['f']], state, ['f'])
 
1852
        self.assertBisect(expected,[['a'], ['b'], ['f']],
 
1853
                          state, ['a', 'b', 'f'])
 
1854
        # ('', 'f') sorts before the others
 
1855
        self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
 
1856
                          state, ['b/d', 'b/d/e', 'f'])
 
1857
 
 
1858
    def test_bisect_duplicate_paths(self):
 
1859
        """When bisecting for a path, handle multiple entries."""
 
1860
        tree, state, expected = self.create_duplicated_dirstate()
 
1861
 
 
1862
        # Now make sure that both records are properly returned.
 
1863
        self.assertBisect(expected, [['']], state, [''])
 
1864
        self.assertBisect(expected, [['a', 'a2']], state, ['a'])
 
1865
        self.assertBisect(expected, [['b', 'b2']], state, ['b'])
 
1866
        self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
 
1867
        self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
 
1868
        self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
 
1869
                          state, ['b/d/e'])
 
1870
        self.assertBisect(expected, [['f', 'f2']], state, ['f'])
 
1871
 
 
1872
    def test_bisect_page_size_too_small(self):
 
1873
        """If the page size is too small, we will auto increase it."""
 
1874
        tree, state, expected = self.create_basic_dirstate()
 
1875
        state._bisect_page_size = 50
 
1876
        self.assertBisect(expected, [None], state, ['b/e'])
 
1877
        self.assertBisect(expected, [['a']], state, ['a'])
 
1878
        self.assertBisect(expected, [['b']], state, ['b'])
 
1879
        self.assertBisect(expected, [['b/c']], state, ['b/c'])
 
1880
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
 
1881
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
 
1882
        self.assertBisect(expected, [['f']], state, ['f'])
 
1883
 
 
1884
    def test_bisect_missing(self):
 
1885
        """Test that bisect return None if it cannot find a path."""
 
1886
        tree, state, expected = self.create_basic_dirstate()
 
1887
        self.assertBisect(expected, [None], state, ['foo'])
 
1888
        self.assertBisect(expected, [None], state, ['b/foo'])
 
1889
        self.assertBisect(expected, [None], state, ['bar/foo'])
 
1890
 
 
1891
        self.assertBisect(expected, [['a'], None, ['b/d']],
 
1892
                          state, ['a', 'foo', 'b/d'])
 
1893
 
 
1894
    def test_bisect_rename(self):
 
1895
        """Check that we find a renamed row."""
 
1896
        tree, state, expected = self.create_renamed_dirstate()
 
1897
 
 
1898
        # Search for the pre and post renamed entries
 
1899
        self.assertBisect(expected, [['a']], state, ['a'])
 
1900
        self.assertBisect(expected, [['b/g']], state, ['b/g'])
 
1901
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
 
1902
        self.assertBisect(expected, [['h']], state, ['h'])
 
1903
 
 
1904
        # What about b/d/e? shouldn't that also get 2 directory entries?
 
1905
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
 
1906
        self.assertBisect(expected, [['h/e']], state, ['h/e'])
 
1907
 
 
1908
    def test_bisect_dirblocks(self):
 
1909
        tree, state, expected = self.create_duplicated_dirstate()
 
1910
        self.assertBisectDirBlocks(expected,
 
1911
            [['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
 
1912
        self.assertBisectDirBlocks(expected,
 
1913
            [['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
 
1914
        self.assertBisectDirBlocks(expected,
 
1915
            [['b/d/e', 'b/d/e2']], state, ['b/d'])
 
1916
        self.assertBisectDirBlocks(expected,
 
1917
            [['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
 
1918
             ['b/c', 'b/c2', 'b/d', 'b/d2'],
 
1919
             ['b/d/e', 'b/d/e2'],
 
1920
            ], state, ['', 'b', 'b/d'])
 
1921
 
 
1922
    def test_bisect_dirblocks_missing(self):
 
1923
        tree, state, expected = self.create_basic_dirstate()
 
1924
        self.assertBisectDirBlocks(expected, [['b/d/e'], None],
 
1925
            state, ['b/d', 'b/e'])
 
1926
        # Files don't show up in this search
 
1927
        self.assertBisectDirBlocks(expected, [None], state, ['a'])
 
1928
        self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
 
1929
        self.assertBisectDirBlocks(expected, [None], state, ['c'])
 
1930
        self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
 
1931
        self.assertBisectDirBlocks(expected, [None], state, ['f'])
 
1932
 
 
1933
    def test_bisect_recursive_each(self):
 
1934
        tree, state, expected = self.create_basic_dirstate()
 
1935
        self.assertBisectRecursive(expected, ['a'], state, ['a'])
 
1936
        self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
 
1937
        self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
 
1938
        self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
 
1939
                                   state, ['b/d'])
 
1940
        self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
 
1941
                                   state, ['b'])
 
1942
        self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
 
1943
                                              'b/d', 'b/d/e'],
 
1944
                                   state, [''])
 
1945
 
 
1946
    def test_bisect_recursive_multiple(self):
 
1947
        tree, state, expected = self.create_basic_dirstate()
 
1948
        self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
 
1949
        self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
 
1950
                                   state, ['b/d', 'b/d/e'])
 
1951
 
 
1952
    def test_bisect_recursive_missing(self):
 
1953
        tree, state, expected = self.create_basic_dirstate()
 
1954
        self.assertBisectRecursive(expected, [], state, ['d'])
 
1955
        self.assertBisectRecursive(expected, [], state, ['b/e'])
 
1956
        self.assertBisectRecursive(expected, [], state, ['g'])
 
1957
        self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
 
1958
 
 
1959
    def test_bisect_recursive_renamed(self):
 
1960
        tree, state, expected = self.create_renamed_dirstate()
 
1961
 
 
1962
        # Looking for either renamed item should find the other
 
1963
        self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
 
1964
        self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
 
1965
        # Looking in the containing directory should find the rename target,
 
1966
        # and anything in a subdir of the renamed target.
 
1967
        self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
 
1968
                                              'b/d/e', 'b/g', 'h', 'h/e'],
 
1969
                                   state, ['b'])
 
1970
 
 
1971
 
 
1972
class TestBisectDirblock(TestCase):
 
1973
    """Test that bisect_dirblock() returns the expected values.
 
1974
 
 
1975
    bisect_dirblock is intended to work like bisect.bisect_left() except it
 
1976
    knows it is working on dirblocks and that dirblocks are sorted by ('path',
 
1977
    'to', 'foo') chunks rather than by raw 'path/to/foo'.
 
1978
    """
 
1979
 
 
1980
    def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
 
1981
        """Assert that bisect_split works like bisect_left on the split paths.
 
1982
 
 
1983
        :param dirblocks: A list of (path, [info]) pairs.
 
1984
        :param split_dirblocks: A list of ((split, path), [info]) pairs.
 
1985
        :param path: The path we are indexing.
 
1986
 
 
1987
        All other arguments will be passed along.
 
1988
        """
 
1989
        bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
 
1990
                                                 *args, **kwargs)
 
1991
        split_dirblock = (path.split('/'), [])
 
1992
        bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
 
1993
                                             *args)
 
1994
        self.assertEqual(bisect_left_idx, bisect_split_idx,
 
1995
                         'bisect_split disagreed. %s != %s'
 
1996
                         ' for key %s'
 
1997
                         % (bisect_left_idx, bisect_split_idx, path)
 
1998
                         )
 
1999
 
 
2000
    def paths_to_dirblocks(self, paths):
 
2001
        """Convert a list of paths into dirblock form.
 
2002
 
 
2003
        Also, ensure that the paths are in proper sorted order.
 
2004
        """
 
2005
        dirblocks = [(path, []) for path in paths]
 
2006
        split_dirblocks = [(path.split('/'), []) for path in paths]
 
2007
        self.assertEqual(sorted(split_dirblocks), split_dirblocks)
 
2008
        return dirblocks, split_dirblocks
 
2009
 
 
2010
    def test_simple(self):
 
2011
        """In the simple case it works just like bisect_left"""
 
2012
        paths = ['', 'a', 'b', 'c', 'd']
 
2013
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
2014
        for path in paths:
 
2015
            self.assertBisect(dirblocks, split_dirblocks, path)
 
2016
        self.assertBisect(dirblocks, split_dirblocks, '_')
 
2017
        self.assertBisect(dirblocks, split_dirblocks, 'aa')
 
2018
        self.assertBisect(dirblocks, split_dirblocks, 'bb')
 
2019
        self.assertBisect(dirblocks, split_dirblocks, 'cc')
 
2020
        self.assertBisect(dirblocks, split_dirblocks, 'dd')
 
2021
        self.assertBisect(dirblocks, split_dirblocks, 'a/a')
 
2022
        self.assertBisect(dirblocks, split_dirblocks, 'b/b')
 
2023
        self.assertBisect(dirblocks, split_dirblocks, 'c/c')
 
2024
        self.assertBisect(dirblocks, split_dirblocks, 'd/d')
 
2025
 
 
2026
    def test_involved(self):
 
2027
        """This is where bisect_left diverges slightly."""
 
2028
        paths = ['', 'a',
 
2029
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
 
2030
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
 
2031
                 'a-a', 'a-z',
 
2032
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
 
2033
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
 
2034
                 'z-a', 'z-z',
 
2035
                ]
 
2036
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
2037
        for path in paths:
 
2038
            self.assertBisect(dirblocks, split_dirblocks, path)
 
2039
 
 
2040
    def test_involved_cached(self):
 
2041
        """This is where bisect_left diverges slightly."""
 
2042
        paths = ['', 'a',
 
2043
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
 
2044
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
 
2045
                 'a-a', 'a-z',
 
2046
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
 
2047
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
 
2048
                 'z-a', 'z-z',
 
2049
                ]
 
2050
        cache = {}
 
2051
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
2052
        for path in paths:
 
2053
            self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
 
2054
 
 
2055
 
 
2056
class TestDirstateValidation(TestCaseWithDirState):
 
2057
 
 
2058
    def test_validate_correct_dirstate(self):
 
2059
        state = self.create_complex_dirstate()
 
2060
        state._validate()
 
2061
        state.unlock()
 
2062
        # and make sure we can also validate with a read lock
 
2063
        state.lock_read()
 
2064
        try:
 
2065
            state._validate()
 
2066
        finally:
 
2067
            state.unlock()
 
2068
 
 
2069
    def test_dirblock_not_sorted(self):
 
2070
        tree, state, expected = self.create_renamed_dirstate()
 
2071
        state._read_dirblocks_if_needed()
 
2072
        last_dirblock = state._dirblocks[-1]
 
2073
        # we're appending to the dirblock, but this name comes before some of
 
2074
        # the existing names; that's wrong
 
2075
        last_dirblock[1].append(
 
2076
            (('h', 'aaaa', 'a-id'),
 
2077
             [('a', '', 0, False, ''),
 
2078
              ('a', '', 0, False, '')]))
 
2079
        e = self.assertRaises(AssertionError,
 
2080
            state._validate)
 
2081
        self.assertContainsRe(str(e), 'not sorted')
 
2082
 
 
2083
    def test_dirblock_name_mismatch(self):
 
2084
        tree, state, expected = self.create_renamed_dirstate()
 
2085
        state._read_dirblocks_if_needed()
 
2086
        last_dirblock = state._dirblocks[-1]
 
2087
        # add an entry with the wrong directory name
 
2088
        last_dirblock[1].append(
 
2089
            (('', 'z', 'a-id'),
 
2090
             [('a', '', 0, False, ''),
 
2091
              ('a', '', 0, False, '')]))
 
2092
        e = self.assertRaises(AssertionError,
 
2093
            state._validate)
 
2094
        self.assertContainsRe(str(e),
 
2095
            "doesn't match directory name")
 
2096
 
 
2097
    def test_dirblock_missing_rename(self):
 
2098
        tree, state, expected = self.create_renamed_dirstate()
 
2099
        state._read_dirblocks_if_needed()
 
2100
        last_dirblock = state._dirblocks[-1]
 
2101
        # make another entry for a-id, without a correct 'r' pointer to
 
2102
        # the real occurrence in the working tree
 
2103
        last_dirblock[1].append(
 
2104
            (('h', 'z', 'a-id'),
 
2105
             [('a', '', 0, False, ''),
 
2106
              ('a', '', 0, False, '')]))
 
2107
        e = self.assertRaises(AssertionError,
 
2108
            state._validate)
 
2109
        self.assertContainsRe(str(e),
 
2110
            'file a-id is absent in row')