~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

  • Committer: Martin Pool
  • Date: 2005-06-30 07:58:00 UTC
  • mto: This revision was merged to the branch mainline in revision 852.
  • Revision ID: mbp@sourcefrog.net-20050630075800-9af4341e177e121a
Remove VerInfo class; just store sets directly in the list of  
versions.

Add tests for serialize/deserialize.

Show diffs side-by-side

added added

removed removed

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