~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

  • Committer: Vincent Ladeuil
  • Date: 2010-02-11 09:27:55 UTC
  • mfrom: (5017.3.46 test-servers)
  • mto: This revision was merged to the branch mainline in revision 5030.
  • Revision ID: v.ladeuil+lp@free.fr-20100211092755-3vvu4vbwiwjjte3s
Move tests servers from bzrlib.transport to bzrlib.tests.test_server

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_set_state_from_inventory_no_content_no_parents(self):
 
734
        # setting the current inventory is a slow but important api to support.
 
735
        tree1 = self.make_branch_and_memory_tree('tree1')
 
736
        tree1.lock_write()
 
737
        try:
 
738
            tree1.add('')
 
739
            revid1 = tree1.commit('foo').encode('utf8')
 
740
            root_id = tree1.get_root_id()
 
741
            inv = tree1.inventory
 
742
        finally:
 
743
            tree1.unlock()
 
744
        expected_result = [], [
 
745
            (('', '', root_id), [
 
746
             ('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
 
747
        state = dirstate.DirState.initialize('dirstate')
 
748
        try:
 
749
            state.set_state_from_inventory(inv)
 
750
            self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
751
                             state._header_state)
 
752
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
753
                             state._dirblock_state)
 
754
        except:
 
755
            state.unlock()
 
756
            raise
 
757
        else:
 
758
            # This will unlock it
 
759
            self.check_state_with_reopen(expected_result, state)
 
760
 
 
761
    def test_set_state_from_inventory_preserves_hashcache(self):
 
762
        # https://bugs.launchpad.net/bzr/+bug/146176
 
763
        # set_state_from_inventory should preserve the stat and hash value for
 
764
        # workingtree files that are not changed by the inventory.
 
765
 
 
766
        tree = self.make_branch_and_tree('.')
 
767
        # depends on the default format using dirstate...
 
768
        tree.lock_write()
 
769
        try:
 
770
            # make a dirstate with some valid hashcache data
 
771
            # file on disk, but that's not needed for this test
 
772
            foo_contents = 'contents of foo'
 
773
            self.build_tree_contents([('foo', foo_contents)])
 
774
            tree.add('foo', 'foo-id')
 
775
 
 
776
            foo_stat = os.stat('foo')
 
777
            foo_packed = dirstate.pack_stat(foo_stat)
 
778
            foo_sha = osutils.sha_string(foo_contents)
 
779
            foo_size = len(foo_contents)
 
780
 
 
781
            # should not be cached yet, because the file's too fresh
 
782
            self.assertEqual(
 
783
                (('', 'foo', 'foo-id',),
 
784
                 [('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
 
785
                tree._dirstate._get_entry(0, 'foo-id'))
 
786
            # poke in some hashcache information - it wouldn't normally be
 
787
            # stored because it's too fresh
 
788
            tree._dirstate.update_minimal(
 
789
                ('', 'foo', 'foo-id'),
 
790
                'f', False, foo_sha, foo_packed, foo_size, 'foo')
 
791
            # now should be cached
 
792
            self.assertEqual(
 
793
                (('', 'foo', 'foo-id',),
 
794
                 [('f', foo_sha, foo_size, False, foo_packed)]),
 
795
                tree._dirstate._get_entry(0, 'foo-id'))
 
796
 
 
797
            # extract the inventory, and add something to it
 
798
            inv = tree._get_inventory()
 
799
            # should see the file we poked in...
 
800
            self.assertTrue(inv.has_id('foo-id'))
 
801
            self.assertTrue(inv.has_filename('foo'))
 
802
            inv.add_path('bar', 'file', 'bar-id')
 
803
            tree._dirstate._validate()
 
804
            # this used to cause it to lose its hashcache
 
805
            tree._dirstate.set_state_from_inventory(inv)
 
806
            tree._dirstate._validate()
 
807
        finally:
 
808
            tree.unlock()
 
809
 
 
810
        tree.lock_read()
 
811
        try:
 
812
            # now check that the state still has the original hashcache value
 
813
            state = tree._dirstate
 
814
            state._validate()
 
815
            foo_tuple = state._get_entry(0, path_utf8='foo')
 
816
            self.assertEqual(
 
817
                (('', 'foo', 'foo-id',),
 
818
                 [('f', foo_sha, len(foo_contents), False,
 
819
                   dirstate.pack_stat(foo_stat))]),
 
820
                foo_tuple)
 
821
        finally:
 
822
            tree.unlock()
 
823
 
 
824
    def test_set_state_from_inventory_mixed_paths(self):
 
825
        tree1 = self.make_branch_and_tree('tree1')
 
826
        self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
 
827
                         'tree1/a/b/foo', 'tree1/a-b/bar'])
 
828
        tree1.lock_write()
 
829
        try:
 
830
            tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
 
831
                      ['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
 
832
            tree1.commit('rev1', rev_id='rev1')
 
833
            root_id = tree1.get_root_id()
 
834
            inv = tree1.inventory
 
835
        finally:
 
836
            tree1.unlock()
 
837
        expected_result1 = [('', '', root_id, 'd'),
 
838
                            ('', 'a', 'a-id', 'd'),
 
839
                            ('', 'a-b', 'a-b-id', 'd'),
 
840
                            ('a', 'b', 'b-id', 'd'),
 
841
                            ('a/b', 'foo', 'foo-id', 'f'),
 
842
                            ('a-b', 'bar', 'bar-id', 'f'),
 
843
                           ]
 
844
        expected_result2 = [('', '', root_id, 'd'),
 
845
                            ('', 'a', 'a-id', 'd'),
 
846
                            ('', 'a-b', 'a-b-id', 'd'),
 
847
                            ('a-b', 'bar', 'bar-id', 'f'),
 
848
                           ]
 
849
        state = dirstate.DirState.initialize('dirstate')
 
850
        try:
 
851
            state.set_state_from_inventory(inv)
 
852
            values = []
 
853
            for entry in state._iter_entries():
 
854
                values.append(entry[0] + entry[1][0][:1])
 
855
            self.assertEqual(expected_result1, values)
 
856
            del inv['b-id']
 
857
            state.set_state_from_inventory(inv)
 
858
            values = []
 
859
            for entry in state._iter_entries():
 
860
                values.append(entry[0] + entry[1][0][:1])
 
861
            self.assertEqual(expected_result2, values)
 
862
        finally:
 
863
            state.unlock()
 
864
 
 
865
    def test_set_path_id_no_parents(self):
 
866
        """The id of a path can be changed trivally with no parents."""
 
867
        state = dirstate.DirState.initialize('dirstate')
 
868
        try:
 
869
            # check precondition to be sure the state does change appropriately.
 
870
            root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
 
871
            self.assertEqual([root_entry], list(state._iter_entries()))
 
872
            self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
 
873
            self.assertEqual(root_entry,
 
874
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
 
875
            self.assertEqual((None, None),
 
876
                             state._get_entry(0, fileid_utf8='second-root-id'))
 
877
            state.set_path_id('', 'second-root-id')
 
878
            new_root_entry = (('', '', 'second-root-id'),
 
879
                              [('d', '', 0, False, 'x'*32)])
 
880
            expected_rows = [new_root_entry]
 
881
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
882
            self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
 
883
            self.assertEqual(new_root_entry, 
 
884
                             state._get_entry(0, fileid_utf8='second-root-id'))
 
885
            self.assertEqual((None, None),
 
886
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
 
887
            # should work across save too
 
888
            state.save()
 
889
        finally:
 
890
            state.unlock()
 
891
        state = dirstate.DirState.on_file('dirstate')
 
892
        state.lock_read()
 
893
        try:
 
894
            state._validate()
 
895
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
896
        finally:
 
897
            state.unlock()
 
898
 
 
899
    def test_set_path_id_with_parents(self):
 
900
        """Set the root file id in a dirstate with parents"""
 
901
        mt = self.make_branch_and_tree('mt')
 
902
        # in case the default tree format uses a different root id
 
903
        mt.set_root_id('TREE_ROOT')
 
904
        mt.commit('foo', rev_id='parent-revid')
 
905
        rt = mt.branch.repository.revision_tree('parent-revid')
 
906
        state = dirstate.DirState.initialize('dirstate')
 
907
        state._validate()
 
908
        try:
 
909
            state.set_parent_trees([('parent-revid', rt)], ghosts=[])
 
910
            root_entry = (('', '', 'TREE_ROOT'),
 
911
                          [('d', '', 0, False, 'x'*32),
 
912
                           ('d', '', 0, False, 'parent-revid')])
 
913
            self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
 
914
            self.assertEqual(root_entry,
 
915
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
 
916
            self.assertEqual((None, None),
 
917
                             state._get_entry(0, fileid_utf8='Asecond-root-id'))
 
918
            state.set_path_id('', 'Asecond-root-id')
 
919
            state._validate()
 
920
            # now see that it is what we expected
 
921
            old_root_entry = (('', '', 'TREE_ROOT'),
 
922
                              [('a', '', 0, False, ''),
 
923
                               ('d', '', 0, False, 'parent-revid')])
 
924
            new_root_entry = (('', '', 'Asecond-root-id'),
 
925
                              [('d', '', 0, False, ''),
 
926
                               ('a', '', 0, False, '')])
 
927
            expected_rows = [new_root_entry, old_root_entry]
 
928
            state._validate()
 
929
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
930
            self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
 
931
            self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
 
932
            self.assertEqual((None, None),
 
933
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
 
934
            self.assertEqual(old_root_entry,
 
935
                             state._get_entry(1, fileid_utf8='TREE_ROOT'))
 
936
            self.assertEqual(new_root_entry,
 
937
                             state._get_entry(0, fileid_utf8='Asecond-root-id'))
 
938
            self.assertEqual((None, None),
 
939
                             state._get_entry(1, fileid_utf8='Asecond-root-id'))
 
940
            # should work across save too
 
941
            state.save()
 
942
        finally:
 
943
            state.unlock()
 
944
        # now flush & check we get the same
 
945
        state = dirstate.DirState.on_file('dirstate')
 
946
        state.lock_read()
 
947
        try:
 
948
            state._validate()
 
949
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
950
        finally:
 
951
            state.unlock()
 
952
        # now change within an existing file-backed state
 
953
        state.lock_write()
 
954
        try:
 
955
            state._validate()
 
956
            state.set_path_id('', 'tree-root-2')
 
957
            state._validate()
 
958
        finally:
 
959
            state.unlock()
 
960
 
 
961
    def test_set_parent_trees_no_content(self):
 
962
        # set_parent_trees is a slow but important api to support.
 
963
        tree1 = self.make_branch_and_memory_tree('tree1')
 
964
        tree1.lock_write()
 
965
        try:
 
966
            tree1.add('')
 
967
            revid1 = tree1.commit('foo')
 
968
        finally:
 
969
            tree1.unlock()
 
970
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
 
971
        tree2 = memorytree.MemoryTree.create_on_branch(branch2)
 
972
        tree2.lock_write()
 
973
        try:
 
974
            revid2 = tree2.commit('foo')
 
975
            root_id = tree2.get_root_id()
 
976
        finally:
 
977
            tree2.unlock()
 
978
        state = dirstate.DirState.initialize('dirstate')
 
979
        try:
 
980
            state.set_path_id('', root_id)
 
981
            state.set_parent_trees(
 
982
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
983
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
 
984
                 ('ghost-rev', None)),
 
985
                ['ghost-rev'])
 
986
            # check we can reopen and use the dirstate after setting parent
 
987
            # trees.
 
988
            state._validate()
 
989
            state.save()
 
990
            state._validate()
 
991
        finally:
 
992
            state.unlock()
 
993
        state = dirstate.DirState.on_file('dirstate')
 
994
        state.lock_write()
 
995
        try:
 
996
            self.assertEqual([revid1, revid2, 'ghost-rev'],
 
997
                             state.get_parent_ids())
 
998
            # iterating the entire state ensures that the state is parsable.
 
999
            list(state._iter_entries())
 
1000
            # be sure that it sets not appends - change it
 
1001
            state.set_parent_trees(
 
1002
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
1003
                 ('ghost-rev', None)),
 
1004
                ['ghost-rev'])
 
1005
            # and now put it back.
 
1006
            state.set_parent_trees(
 
1007
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
1008
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
 
1009
                 ('ghost-rev', tree2.branch.repository.revision_tree(
 
1010
                                   _mod_revision.NULL_REVISION))),
 
1011
                ['ghost-rev'])
 
1012
            self.assertEqual([revid1, revid2, 'ghost-rev'],
 
1013
                             state.get_parent_ids())
 
1014
            # the ghost should be recorded as such by set_parent_trees.
 
1015
            self.assertEqual(['ghost-rev'], state.get_ghosts())
 
1016
            self.assertEqual(
 
1017
                [(('', '', root_id), [
 
1018
                  ('d', '', 0, False, dirstate.DirState.NULLSTAT),
 
1019
                  ('d', '', 0, False, revid1),
 
1020
                  ('d', '', 0, False, revid1)
 
1021
                  ])],
 
1022
                list(state._iter_entries()))
 
1023
        finally:
 
1024
            state.unlock()
 
1025
 
 
1026
    def test_set_parent_trees_file_missing_from_tree(self):
 
1027
        # Adding a parent tree may reference files not in the current state.
 
1028
        # they should get listed just once by id, even if they are in two
 
1029
        # separate trees.
 
1030
        # set_parent_trees is a slow but important api to support.
 
1031
        tree1 = self.make_branch_and_memory_tree('tree1')
 
1032
        tree1.lock_write()
 
1033
        try:
 
1034
            tree1.add('')
 
1035
            tree1.add(['a file'], ['file-id'], ['file'])
 
1036
            tree1.put_file_bytes_non_atomic('file-id', 'file-content')
 
1037
            revid1 = tree1.commit('foo')
 
1038
        finally:
 
1039
            tree1.unlock()
 
1040
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
 
1041
        tree2 = memorytree.MemoryTree.create_on_branch(branch2)
 
1042
        tree2.lock_write()
 
1043
        try:
 
1044
            tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
 
1045
            revid2 = tree2.commit('foo')
 
1046
            root_id = tree2.get_root_id()
 
1047
        finally:
 
1048
            tree2.unlock()
 
1049
        # check the layout in memory
 
1050
        expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
 
1051
            (('', '', root_id), [
 
1052
             ('d', '', 0, False, dirstate.DirState.NULLSTAT),
 
1053
             ('d', '', 0, False, revid1.encode('utf8')),
 
1054
             ('d', '', 0, False, revid1.encode('utf8'))
 
1055
             ]),
 
1056
            (('', 'a file', 'file-id'), [
 
1057
             ('a', '', 0, False, ''),
 
1058
             ('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
 
1059
              revid1.encode('utf8')),
 
1060
             ('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
 
1061
              revid2.encode('utf8'))
 
1062
             ])
 
1063
            ]
 
1064
        state = dirstate.DirState.initialize('dirstate')
 
1065
        try:
 
1066
            state.set_path_id('', root_id)
 
1067
            state.set_parent_trees(
 
1068
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
1069
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
 
1070
                 ), [])
 
1071
        except:
 
1072
            state.unlock()
 
1073
            raise
 
1074
        else:
 
1075
            # check_state_with_reopen will unlock
 
1076
            self.check_state_with_reopen(expected_result, state)
 
1077
 
 
1078
    ### add a path via _set_data - so we dont need delta work, just
 
1079
    # raw data in, and ensure that it comes out via get_lines happily.
 
1080
 
 
1081
    def test_add_path_to_root_no_parents_all_data(self):
 
1082
        # The most trivial addition of a path is when there are no parents and
 
1083
        # its in the root and all data about the file is supplied
 
1084
        self.build_tree(['a file'])
 
1085
        stat = os.lstat('a file')
 
1086
        # the 1*20 is the sha1 pretend value.
 
1087
        state = dirstate.DirState.initialize('dirstate')
 
1088
        expected_entries = [
 
1089
            (('', '', 'TREE_ROOT'), [
 
1090
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
1091
             ]),
 
1092
            (('', 'a file', 'a-file-id'), [
 
1093
             ('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
 
1094
             ]),
 
1095
            ]
 
1096
        try:
 
1097
            state.add('a file', 'a-file-id', 'file', stat, '1'*20)
 
1098
            # having added it, it should be in the output of iter_entries.
 
1099
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
1100
            # saving and reloading should not affect this.
 
1101
            state.save()
 
1102
        finally:
 
1103
            state.unlock()
 
1104
        state = dirstate.DirState.on_file('dirstate')
 
1105
        state.lock_read()
 
1106
        self.addCleanup(state.unlock)
 
1107
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
1108
 
 
1109
    def test_add_path_to_unversioned_directory(self):
 
1110
        """Adding a path to an unversioned directory should error.
 
1111
 
 
1112
        This is a duplicate of TestWorkingTree.test_add_in_unversioned,
 
1113
        once dirstate is stable and if it is merged with WorkingTree3, consider
 
1114
        removing this copy of the test.
 
1115
        """
 
1116
        self.build_tree(['unversioned/', 'unversioned/a file'])
 
1117
        state = dirstate.DirState.initialize('dirstate')
 
1118
        self.addCleanup(state.unlock)
 
1119
        self.assertRaises(errors.NotVersionedError, state.add,
 
1120
                          'unversioned/a file', 'a-file-id', 'file', None, None)
 
1121
 
 
1122
    def test_add_directory_to_root_no_parents_all_data(self):
 
1123
        # The most trivial addition of a dir is when there are no parents and
 
1124
        # its in the root and all data about the file is supplied
 
1125
        self.build_tree(['a dir/'])
 
1126
        stat = os.lstat('a dir')
 
1127
        expected_entries = [
 
1128
            (('', '', 'TREE_ROOT'), [
 
1129
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
1130
             ]),
 
1131
            (('', 'a dir', 'a dir id'), [
 
1132
             ('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
 
1133
             ]),
 
1134
            ]
 
1135
        state = dirstate.DirState.initialize('dirstate')
 
1136
        try:
 
1137
            state.add('a dir', 'a dir id', 'directory', stat, None)
 
1138
            # having added it, it should be in the output of iter_entries.
 
1139
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
1140
            # saving and reloading should not affect this.
 
1141
            state.save()
 
1142
        finally:
 
1143
            state.unlock()
 
1144
        state = dirstate.DirState.on_file('dirstate')
 
1145
        state.lock_read()
 
1146
        self.addCleanup(state.unlock)
 
1147
        state._validate()
 
1148
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
1149
 
 
1150
    def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
 
1151
        # The most trivial addition of a symlink when there are no parents and
 
1152
        # its in the root and all data about the file is supplied
 
1153
        # bzr doesn't support fake symlinks on windows, yet.
 
1154
        self.requireFeature(tests.SymlinkFeature)
 
1155
        os.symlink(target, link_name)
 
1156
        stat = os.lstat(link_name)
 
1157
        expected_entries = [
 
1158
            (('', '', 'TREE_ROOT'), [
 
1159
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
1160
             ]),
 
1161
            (('', link_name.encode('UTF-8'), 'a link id'), [
 
1162
             ('l', target.encode('UTF-8'), stat[6],
 
1163
              False, dirstate.pack_stat(stat)), # current tree
 
1164
             ]),
 
1165
            ]
 
1166
        state = dirstate.DirState.initialize('dirstate')
 
1167
        try:
 
1168
            state.add(link_name, 'a link id', 'symlink', stat,
 
1169
                      target.encode('UTF-8'))
 
1170
            # having added it, it should be in the output of iter_entries.
 
1171
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
1172
            # saving and reloading should not affect this.
 
1173
            state.save()
 
1174
        finally:
 
1175
            state.unlock()
 
1176
        state = dirstate.DirState.on_file('dirstate')
 
1177
        state.lock_read()
 
1178
        self.addCleanup(state.unlock)
 
1179
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
1180
 
 
1181
    def test_add_symlink_to_root_no_parents_all_data(self):
 
1182
        self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
 
1183
 
 
1184
    def test_add_symlink_unicode_to_root_no_parents_all_data(self):
 
1185
        self.requireFeature(tests.UnicodeFilenameFeature)
 
1186
        self._test_add_symlink_to_root_no_parents_all_data(
 
1187
            u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
 
1188
 
 
1189
    def test_add_directory_and_child_no_parents_all_data(self):
 
1190
        # after adding a directory, we should be able to add children to it.
 
1191
        self.build_tree(['a dir/', 'a dir/a file'])
 
1192
        dirstat = os.lstat('a dir')
 
1193
        filestat = os.lstat('a dir/a file')
 
1194
        expected_entries = [
 
1195
            (('', '', 'TREE_ROOT'), [
 
1196
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
1197
             ]),
 
1198
            (('', 'a dir', 'a dir id'), [
 
1199
             ('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
 
1200
             ]),
 
1201
            (('a dir', 'a file', 'a-file-id'), [
 
1202
             ('f', '1'*20, 25, False,
 
1203
              dirstate.pack_stat(filestat)), # current tree details
 
1204
             ]),
 
1205
            ]
 
1206
        state = dirstate.DirState.initialize('dirstate')
 
1207
        try:
 
1208
            state.add('a dir', 'a dir id', 'directory', dirstat, None)
 
1209
            state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
 
1210
            # added it, it should be in the output of iter_entries.
 
1211
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
1212
            # saving and reloading should not affect this.
 
1213
            state.save()
 
1214
        finally:
 
1215
            state.unlock()
 
1216
        state = dirstate.DirState.on_file('dirstate')
 
1217
        state.lock_read()
 
1218
        self.addCleanup(state.unlock)
 
1219
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
1220
 
 
1221
    def test_add_tree_reference(self):
 
1222
        # make a dirstate and add a tree reference
 
1223
        state = dirstate.DirState.initialize('dirstate')
 
1224
        expected_entry = (
 
1225
            ('', 'subdir', 'subdir-id'),
 
1226
            [('t', 'subtree-123123', 0, False,
 
1227
              'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
 
1228
            )
 
1229
        try:
 
1230
            state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
 
1231
            entry = state._get_entry(0, 'subdir-id', 'subdir')
 
1232
            self.assertEqual(entry, expected_entry)
 
1233
            state._validate()
 
1234
            state.save()
 
1235
        finally:
 
1236
            state.unlock()
 
1237
        # now check we can read it back
 
1238
        state.lock_read()
 
1239
        self.addCleanup(state.unlock)
 
1240
        state._validate()
 
1241
        entry2 = state._get_entry(0, 'subdir-id', 'subdir')
 
1242
        self.assertEqual(entry, entry2)
 
1243
        self.assertEqual(entry, expected_entry)
 
1244
        # and lookup by id should work too
 
1245
        entry2 = state._get_entry(0, fileid_utf8='subdir-id')
 
1246
        self.assertEqual(entry, expected_entry)
 
1247
 
 
1248
    def test_add_forbidden_names(self):
 
1249
        state = dirstate.DirState.initialize('dirstate')
 
1250
        self.addCleanup(state.unlock)
 
1251
        self.assertRaises(errors.BzrError,
 
1252
            state.add, '.', 'ass-id', 'directory', None, None)
 
1253
        self.assertRaises(errors.BzrError,
 
1254
            state.add, '..', 'ass-id', 'directory', None, None)
 
1255
 
 
1256
    def test_set_state_with_rename_b_a_bug_395556(self):
 
1257
        # bug 395556 uncovered a bug where the dirstate ends up with a false
 
1258
        # relocation record - in a tree with no parents there should be no
 
1259
        # absent or relocated records. This then leads to further corruption
 
1260
        # when a commit occurs, as the incorrect relocation gathers an
 
1261
        # incorrect absent in tree 1, and future changes go to pot.
 
1262
        tree1 = self.make_branch_and_tree('tree1')
 
1263
        self.build_tree(['tree1/b'])
 
1264
        tree1.lock_write()
 
1265
        try:
 
1266
            tree1.add(['b'], ['b-id'])
 
1267
            root_id = tree1.get_root_id()
 
1268
            inv = tree1.inventory
 
1269
            state = dirstate.DirState.initialize('dirstate')
 
1270
            try:
 
1271
                # Set the initial state with 'b'
 
1272
                state.set_state_from_inventory(inv)
 
1273
                inv.rename('b-id', root_id, 'a')
 
1274
                # Set the new state with 'a', which currently corrupts.
 
1275
                state.set_state_from_inventory(inv)
 
1276
                expected_result1 = [('', '', root_id, 'd'),
 
1277
                                    ('', 'a', 'b-id', 'f'),
 
1278
                                   ]
 
1279
                values = []
 
1280
                for entry in state._iter_entries():
 
1281
                    values.append(entry[0] + entry[1][0][:1])
 
1282
                self.assertEqual(expected_result1, values)
 
1283
            finally:
 
1284
                state.unlock()
 
1285
        finally:
 
1286
            tree1.unlock()
 
1287
 
 
1288
 
 
1289
class TestGetLines(TestCaseWithDirState):
 
1290
 
 
1291
    def test_get_line_with_2_rows(self):
 
1292
        state = self.create_dirstate_with_root_and_subdir()
 
1293
        try:
 
1294
            self.assertEqual(['#bazaar dirstate flat format 3\n',
 
1295
                'crc32: 41262208\n',
 
1296
                'num_entries: 2\n',
 
1297
                '0\x00\n\x00'
 
1298
                '0\x00\n\x00'
 
1299
                '\x00\x00a-root-value\x00'
 
1300
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
 
1301
                '\x00subdir\x00subdir-id\x00'
 
1302
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
 
1303
                ], state.get_lines())
 
1304
        finally:
 
1305
            state.unlock()
 
1306
 
 
1307
    def test_entry_to_line(self):
 
1308
        state = self.create_dirstate_with_root()
 
1309
        try:
 
1310
            self.assertEqual(
 
1311
                '\x00\x00a-root-value\x00d\x00\x000\x00n'
 
1312
                '\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
 
1313
                state._entry_to_line(state._dirblocks[0][1][0]))
 
1314
        finally:
 
1315
            state.unlock()
 
1316
 
 
1317
    def test_entry_to_line_with_parent(self):
 
1318
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
1319
        root_entry = ('', '', 'a-root-value'), [
 
1320
            ('d', '', 0, False, packed_stat), # current tree details
 
1321
             # first: a pointer to the current location
 
1322
            ('a', 'dirname/basename', 0, False, ''),
 
1323
            ]
 
1324
        state = dirstate.DirState.initialize('dirstate')
 
1325
        try:
 
1326
            self.assertEqual(
 
1327
                '\x00\x00a-root-value\x00'
 
1328
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
 
1329
                'a\x00dirname/basename\x000\x00n\x00',
 
1330
                state._entry_to_line(root_entry))
 
1331
        finally:
 
1332
            state.unlock()
 
1333
 
 
1334
    def test_entry_to_line_with_two_parents_at_different_paths(self):
 
1335
        # / in the tree, at / in one parent and /dirname/basename in the other.
 
1336
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
1337
        root_entry = ('', '', 'a-root-value'), [
 
1338
            ('d', '', 0, False, packed_stat), # current tree details
 
1339
            ('d', '', 0, False, 'rev_id'), # first parent details
 
1340
             # second: a pointer to the current location
 
1341
            ('a', 'dirname/basename', 0, False, ''),
 
1342
            ]
 
1343
        state = dirstate.DirState.initialize('dirstate')
 
1344
        try:
 
1345
            self.assertEqual(
 
1346
                '\x00\x00a-root-value\x00'
 
1347
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
 
1348
                'd\x00\x000\x00n\x00rev_id\x00'
 
1349
                'a\x00dirname/basename\x000\x00n\x00',
 
1350
                state._entry_to_line(root_entry))
 
1351
        finally:
 
1352
            state.unlock()
 
1353
 
 
1354
    def test_iter_entries(self):
 
1355
        # we should be able to iterate the dirstate entries from end to end
 
1356
        # this is for get_lines to be easy to read.
 
1357
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
1358
        dirblocks = []
 
1359
        root_entries = [(('', '', 'a-root-value'), [
 
1360
            ('d', '', 0, False, packed_stat), # current tree details
 
1361
            ])]
 
1362
        dirblocks.append(('', root_entries))
 
1363
        # add two files in the root
 
1364
        subdir_entry = ('', 'subdir', 'subdir-id'), [
 
1365
            ('d', '', 0, False, packed_stat), # current tree details
 
1366
            ]
 
1367
        afile_entry = ('', 'afile', 'afile-id'), [
 
1368
            ('f', 'sha1value', 34, False, packed_stat), # current tree details
 
1369
            ]
 
1370
        dirblocks.append(('', [subdir_entry, afile_entry]))
 
1371
        # and one in subdir
 
1372
        file_entry2 = ('subdir', '2file', '2file-id'), [
 
1373
            ('f', 'sha1value', 23, False, packed_stat), # current tree details
 
1374
            ]
 
1375
        dirblocks.append(('subdir', [file_entry2]))
 
1376
        state = dirstate.DirState.initialize('dirstate')
 
1377
        try:
 
1378
            state._set_data([], dirblocks)
 
1379
            expected_entries = [root_entries[0], subdir_entry, afile_entry,
 
1380
                                file_entry2]
 
1381
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
1382
        finally:
 
1383
            state.unlock()
 
1384
 
 
1385
 
 
1386
class TestGetBlockRowIndex(TestCaseWithDirState):
 
1387
 
 
1388
    def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
 
1389
        file_present, state, dirname, basename, tree_index):
 
1390
        self.assertEqual((block_index, row_index, dir_present, file_present),
 
1391
            state._get_block_entry_index(dirname, basename, tree_index))
 
1392
        if dir_present:
 
1393
            block = state._dirblocks[block_index]
 
1394
            self.assertEqual(dirname, block[0])
 
1395
        if dir_present and file_present:
 
1396
            row = state._dirblocks[block_index][1][row_index]
 
1397
            self.assertEqual(dirname, row[0][0])
 
1398
            self.assertEqual(basename, row[0][1])
 
1399
 
 
1400
    def test_simple_structure(self):
 
1401
        state = self.create_dirstate_with_root_and_subdir()
 
1402
        self.addCleanup(state.unlock)
 
1403
        self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
 
1404
        self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
 
1405
        self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
 
1406
        self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
 
1407
        self.assertBlockRowIndexEqual(2, 0, False, False, state,
 
1408
                                      'subdir', 'foo', 0)
 
1409
 
 
1410
    def test_complex_structure_exists(self):
 
1411
        state = self.create_complex_dirstate()
 
1412
        self.addCleanup(state.unlock)
 
1413
        # Make sure we can find everything that exists
 
1414
        self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
 
1415
        self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
 
1416
        self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
 
1417
        self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
 
1418
        self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
 
1419
        self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
 
1420
        self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
 
1421
        self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
 
1422
        self.assertBlockRowIndexEqual(3, 1, True, True, state,
 
1423
                                      'b', 'h\xc3\xa5', 0)
 
1424
 
 
1425
    def test_complex_structure_missing(self):
 
1426
        state = self.create_complex_dirstate()
 
1427
        self.addCleanup(state.unlock)
 
1428
        # Make sure things would be inserted in the right locations
 
1429
        # '_' comes before 'a'
 
1430
        self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
 
1431
        self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
 
1432
        self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
 
1433
        self.assertBlockRowIndexEqual(1, 4, True, False, state,
 
1434
                                      '', 'h\xc3\xa5', 0)
 
1435
        self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
 
1436
        self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
 
1437
        self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
 
1438
        # This would be inserted between a/ and b/
 
1439
        self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
 
1440
        # Put at the end
 
1441
        self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
 
1442
 
 
1443
 
 
1444
class TestGetEntry(TestCaseWithDirState):
 
1445
 
 
1446
    def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
 
1447
        """Check that the right entry is returned for a request to getEntry."""
 
1448
        entry = state._get_entry(index, path_utf8=path)
 
1449
        if file_id is None:
 
1450
            self.assertEqual((None, None), entry)
 
1451
        else:
 
1452
            cur = entry[0]
 
1453
            self.assertEqual((dirname, basename, file_id), cur[:3])
 
1454
 
 
1455
    def test_simple_structure(self):
 
1456
        state = self.create_dirstate_with_root_and_subdir()
 
1457
        self.addCleanup(state.unlock)
 
1458
        self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
 
1459
        self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
 
1460
        self.assertEntryEqual(None, None, None, state, 'missing', 0)
 
1461
        self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
 
1462
        self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
 
1463
 
 
1464
    def test_complex_structure_exists(self):
 
1465
        state = self.create_complex_dirstate()
 
1466
        self.addCleanup(state.unlock)
 
1467
        self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
 
1468
        self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
 
1469
        self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
 
1470
        self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
 
1471
        self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
 
1472
        self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
 
1473
        self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
 
1474
        self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
 
1475
        self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
 
1476
                              'b/h\xc3\xa5', 0)
 
1477
 
 
1478
    def test_complex_structure_missing(self):
 
1479
        state = self.create_complex_dirstate()
 
1480
        self.addCleanup(state.unlock)
 
1481
        self.assertEntryEqual(None, None, None, state, '_', 0)
 
1482
        self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
 
1483
        self.assertEntryEqual(None, None, None, state, 'a/b', 0)
 
1484
        self.assertEntryEqual(None, None, None, state, 'c/d', 0)
 
1485
 
 
1486
    def test_get_entry_uninitialized(self):
 
1487
        """Calling get_entry will load data if it needs to"""
 
1488
        state = self.create_dirstate_with_root()
 
1489
        try:
 
1490
            state.save()
 
1491
        finally:
 
1492
            state.unlock()
 
1493
        del state
 
1494
        state = dirstate.DirState.on_file('dirstate')
 
1495
        state.lock_read()
 
1496
        try:
 
1497
            self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
 
1498
                             state._header_state)
 
1499
            self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
 
1500
                             state._dirblock_state)
 
1501
            self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
 
1502
        finally:
 
1503
            state.unlock()
 
1504
 
 
1505
 
 
1506
class TestIterChildEntries(TestCaseWithDirState):
 
1507
 
 
1508
    def create_dirstate_with_two_trees(self):
 
1509
        """This dirstate contains multiple files and directories.
 
1510
 
 
1511
         /        a-root-value
 
1512
         a/       a-dir
 
1513
         b/       b-dir
 
1514
         c        c-file
 
1515
         d        d-file
 
1516
         a/e/     e-dir
 
1517
         a/f      f-file
 
1518
         b/g      g-file
 
1519
         b/h\xc3\xa5  h-\xc3\xa5-file  #This is u'\xe5' encoded into utf-8
 
1520
 
 
1521
        Notice that a/e is an empty directory.
 
1522
 
 
1523
        There is one parent tree, which has the same shape with the following variations:
 
1524
        b/g in the parent is gone.
 
1525
        b/h in the parent has a different id
 
1526
        b/i is new in the parent
 
1527
        c is renamed to b/j in the parent
 
1528
 
 
1529
        :return: The dirstate, still write-locked.
 
1530
        """
 
1531
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
1532
        null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
 
1533
        NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
 
1534
        root_entry = ('', '', 'a-root-value'), [
 
1535
            ('d', '', 0, False, packed_stat),
 
1536
            ('d', '', 0, False, 'parent-revid'),
 
1537
            ]
 
1538
        a_entry = ('', 'a', 'a-dir'), [
 
1539
            ('d', '', 0, False, packed_stat),
 
1540
            ('d', '', 0, False, 'parent-revid'),
 
1541
            ]
 
1542
        b_entry = ('', 'b', 'b-dir'), [
 
1543
            ('d', '', 0, False, packed_stat),
 
1544
            ('d', '', 0, False, 'parent-revid'),
 
1545
            ]
 
1546
        c_entry = ('', 'c', 'c-file'), [
 
1547
            ('f', null_sha, 10, False, packed_stat),
 
1548
            ('r', 'b/j', 0, False, ''),
 
1549
            ]
 
1550
        d_entry = ('', 'd', 'd-file'), [
 
1551
            ('f', null_sha, 20, False, packed_stat),
 
1552
            ('f', 'd', 20, False, 'parent-revid'),
 
1553
            ]
 
1554
        e_entry = ('a', 'e', 'e-dir'), [
 
1555
            ('d', '', 0, False, packed_stat),
 
1556
            ('d', '', 0, False, 'parent-revid'),
 
1557
            ]
 
1558
        f_entry = ('a', 'f', 'f-file'), [
 
1559
            ('f', null_sha, 30, False, packed_stat),
 
1560
            ('f', 'f', 20, False, 'parent-revid'),
 
1561
            ]
 
1562
        g_entry = ('b', 'g', 'g-file'), [
 
1563
            ('f', null_sha, 30, False, packed_stat),
 
1564
            NULL_PARENT_DETAILS,
 
1565
            ]
 
1566
        h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
 
1567
            ('f', null_sha, 40, False, packed_stat),
 
1568
            NULL_PARENT_DETAILS,
 
1569
            ]
 
1570
        h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
 
1571
            NULL_PARENT_DETAILS,
 
1572
            ('f', 'h', 20, False, 'parent-revid'),
 
1573
            ]
 
1574
        i_entry = ('b', 'i', 'i-file'), [
 
1575
            NULL_PARENT_DETAILS,
 
1576
            ('f', 'h', 20, False, 'parent-revid'),
 
1577
            ]
 
1578
        j_entry = ('b', 'j', 'c-file'), [
 
1579
            ('r', 'c', 0, False, ''),
 
1580
            ('f', 'j', 20, False, 'parent-revid'),
 
1581
            ]
 
1582
        dirblocks = []
 
1583
        dirblocks.append(('', [root_entry]))
 
1584
        dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
 
1585
        dirblocks.append(('a', [e_entry, f_entry]))
 
1586
        dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
 
1587
        state = dirstate.DirState.initialize('dirstate')
 
1588
        state._validate()
 
1589
        try:
 
1590
            state._set_data(['parent'], dirblocks)
 
1591
        except:
 
1592
            state.unlock()
 
1593
            raise
 
1594
        return state, dirblocks
 
1595
 
 
1596
    def test_iter_children_b(self):
 
1597
        state, dirblocks = self.create_dirstate_with_two_trees()
 
1598
        self.addCleanup(state.unlock)
 
1599
        expected_result = []
 
1600
        expected_result.append(dirblocks[3][1][2]) # h2
 
1601
        expected_result.append(dirblocks[3][1][3]) # i
 
1602
        expected_result.append(dirblocks[3][1][4]) # j
 
1603
        self.assertEqual(expected_result,
 
1604
            list(state._iter_child_entries(1, 'b')))
 
1605
 
 
1606
    def test_iter_child_root(self):
 
1607
        state, dirblocks = self.create_dirstate_with_two_trees()
 
1608
        self.addCleanup(state.unlock)
 
1609
        expected_result = []
 
1610
        expected_result.append(dirblocks[1][1][0]) # a
 
1611
        expected_result.append(dirblocks[1][1][1]) # b
 
1612
        expected_result.append(dirblocks[1][1][3]) # d
 
1613
        expected_result.append(dirblocks[2][1][0]) # e
 
1614
        expected_result.append(dirblocks[2][1][1]) # f
 
1615
        expected_result.append(dirblocks[3][1][2]) # h2
 
1616
        expected_result.append(dirblocks[3][1][3]) # i
 
1617
        expected_result.append(dirblocks[3][1][4]) # j
 
1618
        self.assertEqual(expected_result,
 
1619
            list(state._iter_child_entries(1, '')))
 
1620
 
 
1621
 
 
1622
class TestDirstateSortOrder(tests.TestCaseWithTransport):
 
1623
    """Test that DirState adds entries in the right order."""
 
1624
 
 
1625
    def test_add_sorting(self):
 
1626
        """Add entries in lexicographical order, we get path sorted order.
 
1627
 
 
1628
        This tests it to a depth of 4, to make sure we don't just get it right
 
1629
        at a single depth. 'a/a' should come before 'a-a', even though it
 
1630
        doesn't lexicographically.
 
1631
        """
 
1632
        dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
 
1633
                'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
 
1634
               ]
 
1635
        null_sha = ''
 
1636
        state = dirstate.DirState.initialize('dirstate')
 
1637
        self.addCleanup(state.unlock)
 
1638
 
 
1639
        fake_stat = os.stat('dirstate')
 
1640
        for d in dirs:
 
1641
            d_id = d.replace('/', '_')+'-id'
 
1642
            file_path = d + '/f'
 
1643
            file_id = file_path.replace('/', '_')+'-id'
 
1644
            state.add(d, d_id, 'directory', fake_stat, null_sha)
 
1645
            state.add(file_path, file_id, 'file', fake_stat, null_sha)
 
1646
 
 
1647
        expected = ['', '', 'a',
 
1648
                '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
        split = lambda p:p.split('/')
 
1652
        self.assertEqual(sorted(expected, key=split), expected)
 
1653
        dirblock_names = [d[0] for d in state._dirblocks]
 
1654
        self.assertEqual(expected, dirblock_names)
 
1655
 
 
1656
    def test_set_parent_trees_correct_order(self):
 
1657
        """After calling set_parent_trees() we should maintain the order."""
 
1658
        dirs = ['a', 'a-a', 'a/a']
 
1659
        null_sha = ''
 
1660
        state = dirstate.DirState.initialize('dirstate')
 
1661
        self.addCleanup(state.unlock)
 
1662
 
 
1663
        fake_stat = os.stat('dirstate')
 
1664
        for d in dirs:
 
1665
            d_id = d.replace('/', '_')+'-id'
 
1666
            file_path = d + '/f'
 
1667
            file_id = file_path.replace('/', '_')+'-id'
 
1668
            state.add(d, d_id, 'directory', fake_stat, null_sha)
 
1669
            state.add(file_path, file_id, 'file', fake_stat, null_sha)
 
1670
 
 
1671
        expected = ['', '', 'a', 'a/a', 'a-a']
 
1672
        dirblock_names = [d[0] for d in state._dirblocks]
 
1673
        self.assertEqual(expected, dirblock_names)
 
1674
 
 
1675
        # *really* cheesy way to just get an empty tree
 
1676
        repo = self.make_repository('repo')
 
1677
        empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
 
1678
        state.set_parent_trees([('null:', empty_tree)], [])
 
1679
 
 
1680
        dirblock_names = [d[0] for d in state._dirblocks]
 
1681
        self.assertEqual(expected, dirblock_names)
 
1682
 
 
1683
 
 
1684
class InstrumentedDirState(dirstate.DirState):
 
1685
    """An DirState with instrumented sha1 functionality."""
 
1686
 
 
1687
    def __init__(self, path, sha1_provider):
 
1688
        super(InstrumentedDirState, self).__init__(path, sha1_provider)
 
1689
        self._time_offset = 0
 
1690
        self._log = []
 
1691
        # member is dynamically set in DirState.__init__ to turn on trace
 
1692
        self._sha1_provider = sha1_provider
 
1693
        self._sha1_file = self._sha1_file_and_log
 
1694
 
 
1695
    def _sha_cutoff_time(self):
 
1696
        timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
 
1697
        self._cutoff_time = timestamp + self._time_offset
 
1698
 
 
1699
    def _sha1_file_and_log(self, abspath):
 
1700
        self._log.append(('sha1', abspath))
 
1701
        return self._sha1_provider.sha1(abspath)
 
1702
 
 
1703
    def _read_link(self, abspath, old_link):
 
1704
        self._log.append(('read_link', abspath, old_link))
 
1705
        return super(InstrumentedDirState, self)._read_link(abspath, old_link)
 
1706
 
 
1707
    def _lstat(self, abspath, entry):
 
1708
        self._log.append(('lstat', abspath))
 
1709
        return super(InstrumentedDirState, self)._lstat(abspath, entry)
 
1710
 
 
1711
    def _is_executable(self, mode, old_executable):
 
1712
        self._log.append(('is_exec', mode, old_executable))
 
1713
        return super(InstrumentedDirState, self)._is_executable(mode,
 
1714
                                                                old_executable)
 
1715
 
 
1716
    def adjust_time(self, secs):
 
1717
        """Move the clock forward or back.
 
1718
 
 
1719
        :param secs: The amount to adjust the clock by. Positive values make it
 
1720
        seem as if we are in the future, negative values make it seem like we
 
1721
        are in the past.
 
1722
        """
 
1723
        self._time_offset += secs
 
1724
        self._cutoff_time = None
 
1725
 
 
1726
 
 
1727
class _FakeStat(object):
 
1728
    """A class with the same attributes as a real stat result."""
 
1729
 
 
1730
    def __init__(self, size, mtime, ctime, dev, ino, mode):
 
1731
        self.st_size = size
 
1732
        self.st_mtime = mtime
 
1733
        self.st_ctime = ctime
 
1734
        self.st_dev = dev
 
1735
        self.st_ino = ino
 
1736
        self.st_mode = mode
 
1737
 
 
1738
    @staticmethod
 
1739
    def from_stat(st):
 
1740
        return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
 
1741
            st.st_ino, st.st_mode)
 
1742
 
 
1743
 
 
1744
class TestPackStat(tests.TestCaseWithTransport):
 
1745
 
 
1746
    def assertPackStat(self, expected, stat_value):
 
1747
        """Check the packed and serialized form of a stat value."""
 
1748
        self.assertEqual(expected, dirstate.pack_stat(stat_value))
 
1749
 
 
1750
    def test_pack_stat_int(self):
 
1751
        st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
 
1752
        # Make sure that all parameters have an impact on the packed stat.
 
1753
        self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
 
1754
        st.st_size = 7000L
 
1755
        #                ay0 => bWE
 
1756
        self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
 
1757
        st.st_mtime = 1172758620
 
1758
        #                     4FZ => 4Fx
 
1759
        self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
 
1760
        st.st_ctime = 1172758630
 
1761
        #                          uBZ => uBm
 
1762
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1763
        st.st_dev = 888L
 
1764
        #                                DCQ => DeA
 
1765
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
 
1766
        st.st_ino = 6499540L
 
1767
        #                                     LNI => LNQ
 
1768
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
 
1769
        st.st_mode = 0100744
 
1770
        #                                          IGk => IHk
 
1771
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
 
1772
 
 
1773
    def test_pack_stat_float(self):
 
1774
        """On some platforms mtime and ctime are floats.
 
1775
 
 
1776
        Make sure we don't get warnings or errors, and that we ignore changes <
 
1777
        1s
 
1778
        """
 
1779
        st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
 
1780
                       777L, 6499538L, 0100644)
 
1781
        # These should all be the same as the integer counterparts
 
1782
        self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
 
1783
        st.st_mtime = 1172758620.0
 
1784
        #                     FZF5 => FxF5
 
1785
        self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
 
1786
        st.st_ctime = 1172758630.0
 
1787
        #                          uBZ => uBm
 
1788
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1789
        # fractional seconds are discarded, so no change from above
 
1790
        st.st_mtime = 1172758620.453
 
1791
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1792
        st.st_ctime = 1172758630.228
 
1793
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1794
 
 
1795
 
 
1796
class TestBisect(TestCaseWithDirState):
 
1797
    """Test the ability to bisect into the disk format."""
 
1798
 
 
1799
    def assertBisect(self, expected_map, map_keys, state, paths):
 
1800
        """Assert that bisecting for paths returns the right result.
 
1801
 
 
1802
        :param expected_map: A map from key => entry value
 
1803
        :param map_keys: The keys to expect for each path
 
1804
        :param state: The DirState object.
 
1805
        :param paths: A list of paths, these will automatically be split into
 
1806
                      (dir, name) tuples, and sorted according to how _bisect
 
1807
                      requires.
 
1808
        """
 
1809
        result = state._bisect(paths)
 
1810
        # For now, results are just returned in whatever order we read them.
 
1811
        # We could sort by (dir, name, file_id) or something like that, but in
 
1812
        # the end it would still be fairly arbitrary, and we don't want the
 
1813
        # extra overhead if we can avoid it. So sort everything to make sure
 
1814
        # equality is true
 
1815
        self.assertEqual(len(map_keys), len(paths))
 
1816
        expected = {}
 
1817
        for path, keys in zip(paths, map_keys):
 
1818
            if keys is None:
 
1819
                # This should not be present in the output
 
1820
                continue
 
1821
            expected[path] = sorted(expected_map[k] for k in keys)
 
1822
 
 
1823
        # The returned values are just arranged randomly based on when they
 
1824
        # were read, for testing, make sure it is properly sorted.
 
1825
        for path in result:
 
1826
            result[path].sort()
 
1827
 
 
1828
        self.assertEqual(expected, result)
 
1829
 
 
1830
    def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
 
1831
        """Assert that bisecting for dirbblocks returns the right result.
 
1832
 
 
1833
        :param expected_map: A map from key => expected values
 
1834
        :param map_keys: A nested list of paths we expect to be returned.
 
1835
            Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
 
1836
        :param state: The DirState object.
 
1837
        :param paths: A list of directories
 
1838
        """
 
1839
        result = state._bisect_dirblocks(paths)
 
1840
        self.assertEqual(len(map_keys), len(paths))
 
1841
        expected = {}
 
1842
        for path, keys in zip(paths, map_keys):
 
1843
            if keys is None:
 
1844
                # This should not be present in the output
 
1845
                continue
 
1846
            expected[path] = sorted(expected_map[k] for k in keys)
 
1847
        for path in result:
 
1848
            result[path].sort()
 
1849
 
 
1850
        self.assertEqual(expected, result)
 
1851
 
 
1852
    def assertBisectRecursive(self, expected_map, map_keys, state, paths):
 
1853
        """Assert the return value of a recursive bisection.
 
1854
 
 
1855
        :param expected_map: A map from key => entry value
 
1856
        :param map_keys: A list of paths we expect to be returned.
 
1857
            Something like ['a', 'b', 'f', 'b/d', 'b/d2']
 
1858
        :param state: The DirState object.
 
1859
        :param paths: A list of files and directories. It will be broken up
 
1860
            into (dir, name) pairs and sorted before calling _bisect_recursive.
 
1861
        """
 
1862
        expected = {}
 
1863
        for key in map_keys:
 
1864
            entry = expected_map[key]
 
1865
            dir_name_id, trees_info = entry
 
1866
            expected[dir_name_id] = trees_info
 
1867
 
 
1868
        result = state._bisect_recursive(paths)
 
1869
 
 
1870
        self.assertEqual(expected, result)
 
1871
 
 
1872
    def test_bisect_each(self):
 
1873
        """Find a single record using bisect."""
 
1874
        tree, state, expected = self.create_basic_dirstate()
 
1875
 
 
1876
        # Bisect should return the rows for the specified files.
 
1877
        self.assertBisect(expected, [['']], state, [''])
 
1878
        self.assertBisect(expected, [['a']], state, ['a'])
 
1879
        self.assertBisect(expected, [['b']], state, ['b'])
 
1880
        self.assertBisect(expected, [['b/c']], state, ['b/c'])
 
1881
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
 
1882
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
 
1883
        self.assertBisect(expected, [['b-c']], state, ['b-c'])
 
1884
        self.assertBisect(expected, [['f']], state, ['f'])
 
1885
 
 
1886
    def test_bisect_multi(self):
 
1887
        """Bisect can be used to find multiple records at the same time."""
 
1888
        tree, state, expected = self.create_basic_dirstate()
 
1889
        # Bisect should be capable of finding multiple entries at the same time
 
1890
        self.assertBisect(expected, [['a'], ['b'], ['f']],
 
1891
                          state, ['a', 'b', 'f'])
 
1892
        self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
 
1893
                          state, ['f', 'b/d', 'b/d/e'])
 
1894
        self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
 
1895
                          state, ['b', 'b-c', 'b/c'])
 
1896
 
 
1897
    def test_bisect_one_page(self):
 
1898
        """Test bisect when there is only 1 page to read"""
 
1899
        tree, state, expected = self.create_basic_dirstate()
 
1900
        state._bisect_page_size = 5000
 
1901
        self.assertBisect(expected,[['']], state, [''])
 
1902
        self.assertBisect(expected,[['a']], state, ['a'])
 
1903
        self.assertBisect(expected,[['b']], state, ['b'])
 
1904
        self.assertBisect(expected,[['b/c']], state, ['b/c'])
 
1905
        self.assertBisect(expected,[['b/d']], state, ['b/d'])
 
1906
        self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
 
1907
        self.assertBisect(expected,[['b-c']], state, ['b-c'])
 
1908
        self.assertBisect(expected,[['f']], state, ['f'])
 
1909
        self.assertBisect(expected,[['a'], ['b'], ['f']],
 
1910
                          state, ['a', 'b', 'f'])
 
1911
        self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
 
1912
                          state, ['b/d', 'b/d/e', 'f'])
 
1913
        self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
 
1914
                          state, ['b', 'b/c', 'b-c'])
 
1915
 
 
1916
    def test_bisect_duplicate_paths(self):
 
1917
        """When bisecting for a path, handle multiple entries."""
 
1918
        tree, state, expected = self.create_duplicated_dirstate()
 
1919
 
 
1920
        # Now make sure that both records are properly returned.
 
1921
        self.assertBisect(expected, [['']], state, [''])
 
1922
        self.assertBisect(expected, [['a', 'a2']], state, ['a'])
 
1923
        self.assertBisect(expected, [['b', 'b2']], state, ['b'])
 
1924
        self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
 
1925
        self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
 
1926
        self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
 
1927
                          state, ['b/d/e'])
 
1928
        self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
 
1929
        self.assertBisect(expected, [['f', 'f2']], state, ['f'])
 
1930
 
 
1931
    def test_bisect_page_size_too_small(self):
 
1932
        """If the page size is too small, we will auto increase it."""
 
1933
        tree, state, expected = self.create_basic_dirstate()
 
1934
        state._bisect_page_size = 50
 
1935
        self.assertBisect(expected, [None], state, ['b/e'])
 
1936
        self.assertBisect(expected, [['a']], state, ['a'])
 
1937
        self.assertBisect(expected, [['b']], state, ['b'])
 
1938
        self.assertBisect(expected, [['b/c']], state, ['b/c'])
 
1939
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
 
1940
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
 
1941
        self.assertBisect(expected, [['b-c']], state, ['b-c'])
 
1942
        self.assertBisect(expected, [['f']], state, ['f'])
 
1943
 
 
1944
    def test_bisect_missing(self):
 
1945
        """Test that bisect return None if it cannot find a path."""
 
1946
        tree, state, expected = self.create_basic_dirstate()
 
1947
        self.assertBisect(expected, [None], state, ['foo'])
 
1948
        self.assertBisect(expected, [None], state, ['b/foo'])
 
1949
        self.assertBisect(expected, [None], state, ['bar/foo'])
 
1950
        self.assertBisect(expected, [None], state, ['b-c/foo'])
 
1951
 
 
1952
        self.assertBisect(expected, [['a'], None, ['b/d']],
 
1953
                          state, ['a', 'foo', 'b/d'])
 
1954
 
 
1955
    def test_bisect_rename(self):
 
1956
        """Check that we find a renamed row."""
 
1957
        tree, state, expected = self.create_renamed_dirstate()
 
1958
 
 
1959
        # Search for the pre and post renamed entries
 
1960
        self.assertBisect(expected, [['a']], state, ['a'])
 
1961
        self.assertBisect(expected, [['b/g']], state, ['b/g'])
 
1962
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
 
1963
        self.assertBisect(expected, [['h']], state, ['h'])
 
1964
 
 
1965
        # What about b/d/e? shouldn't that also get 2 directory entries?
 
1966
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
 
1967
        self.assertBisect(expected, [['h/e']], state, ['h/e'])
 
1968
 
 
1969
    def test_bisect_dirblocks(self):
 
1970
        tree, state, expected = self.create_duplicated_dirstate()
 
1971
        self.assertBisectDirBlocks(expected,
 
1972
            [['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
 
1973
            state, [''])
 
1974
        self.assertBisectDirBlocks(expected,
 
1975
            [['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
 
1976
        self.assertBisectDirBlocks(expected,
 
1977
            [['b/d/e', 'b/d/e2']], state, ['b/d'])
 
1978
        self.assertBisectDirBlocks(expected,
 
1979
            [['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
 
1980
             ['b/c', 'b/c2', 'b/d', 'b/d2'],
 
1981
             ['b/d/e', 'b/d/e2'],
 
1982
            ], state, ['', 'b', 'b/d'])
 
1983
 
 
1984
    def test_bisect_dirblocks_missing(self):
 
1985
        tree, state, expected = self.create_basic_dirstate()
 
1986
        self.assertBisectDirBlocks(expected, [['b/d/e'], None],
 
1987
            state, ['b/d', 'b/e'])
 
1988
        # Files don't show up in this search
 
1989
        self.assertBisectDirBlocks(expected, [None], state, ['a'])
 
1990
        self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
 
1991
        self.assertBisectDirBlocks(expected, [None], state, ['c'])
 
1992
        self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
 
1993
        self.assertBisectDirBlocks(expected, [None], state, ['f'])
 
1994
 
 
1995
    def test_bisect_recursive_each(self):
 
1996
        tree, state, expected = self.create_basic_dirstate()
 
1997
        self.assertBisectRecursive(expected, ['a'], state, ['a'])
 
1998
        self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
 
1999
        self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
 
2000
        self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
 
2001
        self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
 
2002
                                   state, ['b/d'])
 
2003
        self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
 
2004
                                   state, ['b'])
 
2005
        self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
 
2006
                                              'b/d', 'b/d/e'],
 
2007
                                   state, [''])
 
2008
 
 
2009
    def test_bisect_recursive_multiple(self):
 
2010
        tree, state, expected = self.create_basic_dirstate()
 
2011
        self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
 
2012
        self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
 
2013
                                   state, ['b/d', 'b/d/e'])
 
2014
 
 
2015
    def test_bisect_recursive_missing(self):
 
2016
        tree, state, expected = self.create_basic_dirstate()
 
2017
        self.assertBisectRecursive(expected, [], state, ['d'])
 
2018
        self.assertBisectRecursive(expected, [], state, ['b/e'])
 
2019
        self.assertBisectRecursive(expected, [], state, ['g'])
 
2020
        self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
 
2021
 
 
2022
    def test_bisect_recursive_renamed(self):
 
2023
        tree, state, expected = self.create_renamed_dirstate()
 
2024
 
 
2025
        # Looking for either renamed item should find the other
 
2026
        self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
 
2027
        self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
 
2028
        # Looking in the containing directory should find the rename target,
 
2029
        # and anything in a subdir of the renamed target.
 
2030
        self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
 
2031
                                              'b/d/e', 'b/g', 'h', 'h/e'],
 
2032
                                   state, ['b'])
 
2033
 
 
2034
 
 
2035
class TestDirstateValidation(TestCaseWithDirState):
 
2036
 
 
2037
    def test_validate_correct_dirstate(self):
 
2038
        state = self.create_complex_dirstate()
 
2039
        state._validate()
 
2040
        state.unlock()
 
2041
        # and make sure we can also validate with a read lock
 
2042
        state.lock_read()
 
2043
        try:
 
2044
            state._validate()
 
2045
        finally:
 
2046
            state.unlock()
 
2047
 
 
2048
    def test_dirblock_not_sorted(self):
 
2049
        tree, state, expected = self.create_renamed_dirstate()
 
2050
        state._read_dirblocks_if_needed()
 
2051
        last_dirblock = state._dirblocks[-1]
 
2052
        # we're appending to the dirblock, but this name comes before some of
 
2053
        # the existing names; that's wrong
 
2054
        last_dirblock[1].append(
 
2055
            (('h', 'aaaa', 'a-id'),
 
2056
             [('a', '', 0, False, ''),
 
2057
              ('a', '', 0, False, '')]))
 
2058
        e = self.assertRaises(AssertionError,
 
2059
            state._validate)
 
2060
        self.assertContainsRe(str(e), 'not sorted')
 
2061
 
 
2062
    def test_dirblock_name_mismatch(self):
 
2063
        tree, state, expected = self.create_renamed_dirstate()
 
2064
        state._read_dirblocks_if_needed()
 
2065
        last_dirblock = state._dirblocks[-1]
 
2066
        # add an entry with the wrong directory name
 
2067
        last_dirblock[1].append(
 
2068
            (('', 'z', 'a-id'),
 
2069
             [('a', '', 0, False, ''),
 
2070
              ('a', '', 0, False, '')]))
 
2071
        e = self.assertRaises(AssertionError,
 
2072
            state._validate)
 
2073
        self.assertContainsRe(str(e),
 
2074
            "doesn't match directory name")
 
2075
 
 
2076
    def test_dirblock_missing_rename(self):
 
2077
        tree, state, expected = self.create_renamed_dirstate()
 
2078
        state._read_dirblocks_if_needed()
 
2079
        last_dirblock = state._dirblocks[-1]
 
2080
        # make another entry for a-id, without a correct 'r' pointer to
 
2081
        # the real occurrence in the working tree
 
2082
        last_dirblock[1].append(
 
2083
            (('h', 'z', 'a-id'),
 
2084
             [('a', '', 0, False, ''),
 
2085
              ('a', '', 0, False, '')]))
 
2086
        e = self.assertRaises(AssertionError,
 
2087
            state._validate)
 
2088
        self.assertContainsRe(str(e),
 
2089
            'file a-id is absent in row')
 
2090
 
 
2091
 
 
2092
class TestDirstateTreeReference(TestCaseWithDirState):
 
2093
 
 
2094
    def test_reference_revision_is_none(self):
 
2095
        tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
 
2096
        subtree = self.make_branch_and_tree('tree/subtree',
 
2097
                            format='dirstate-with-subtree')
 
2098
        subtree.set_root_id('subtree')
 
2099
        tree.add_reference(subtree)
 
2100
        tree.add('subtree')
 
2101
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
2102
        key = ('', 'subtree', 'subtree')
 
2103
        expected = ('', [(key,
 
2104
            [('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
 
2105
 
 
2106
        try:
 
2107
            self.assertEqual(expected, state._find_block(key))
 
2108
        finally:
 
2109
            state.unlock()
 
2110
 
 
2111
 
 
2112
class TestDiscardMergeParents(TestCaseWithDirState):
 
2113
 
 
2114
    def test_discard_no_parents(self):
 
2115
        # This should be a no-op
 
2116
        state = self.create_empty_dirstate()
 
2117
        self.addCleanup(state.unlock)
 
2118
        state._discard_merge_parents()
 
2119
        state._validate()
 
2120
 
 
2121
    def test_discard_one_parent(self):
 
2122
        # No-op
 
2123
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
2124
        root_entry_direntry = ('', '', 'a-root-value'), [
 
2125
            ('d', '', 0, False, packed_stat),
 
2126
            ('d', '', 0, False, packed_stat),
 
2127
            ]
 
2128
        dirblocks = []
 
2129
        dirblocks.append(('', [root_entry_direntry]))
 
2130
        dirblocks.append(('', []))
 
2131
 
 
2132
        state = self.create_empty_dirstate()
 
2133
        self.addCleanup(state.unlock)
 
2134
        state._set_data(['parent-id'], dirblocks[:])
 
2135
        state._validate()
 
2136
 
 
2137
        state._discard_merge_parents()
 
2138
        state._validate()
 
2139
        self.assertEqual(dirblocks, state._dirblocks)
 
2140
 
 
2141
    def test_discard_simple(self):
 
2142
        # No-op
 
2143
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
2144
        root_entry_direntry = ('', '', 'a-root-value'), [
 
2145
            ('d', '', 0, False, packed_stat),
 
2146
            ('d', '', 0, False, packed_stat),
 
2147
            ('d', '', 0, False, packed_stat),
 
2148
            ]
 
2149
        expected_root_entry_direntry = ('', '', 'a-root-value'), [
 
2150
            ('d', '', 0, False, packed_stat),
 
2151
            ('d', '', 0, False, packed_stat),
 
2152
            ]
 
2153
        dirblocks = []
 
2154
        dirblocks.append(('', [root_entry_direntry]))
 
2155
        dirblocks.append(('', []))
 
2156
 
 
2157
        state = self.create_empty_dirstate()
 
2158
        self.addCleanup(state.unlock)
 
2159
        state._set_data(['parent-id', 'merged-id'], dirblocks[:])
 
2160
        state._validate()
 
2161
 
 
2162
        # This should strip of the extra column
 
2163
        state._discard_merge_parents()
 
2164
        state._validate()
 
2165
        expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
 
2166
        self.assertEqual(expected_dirblocks, state._dirblocks)
 
2167
 
 
2168
    def test_discard_absent(self):
 
2169
        """If entries are only in a merge, discard should remove the entries"""
 
2170
        null_stat = dirstate.DirState.NULLSTAT
 
2171
        present_dir = ('d', '', 0, False, null_stat)
 
2172
        present_file = ('f', '', 0, False, null_stat)
 
2173
        absent = dirstate.DirState.NULL_PARENT_DETAILS
 
2174
        root_key = ('', '', 'a-root-value')
 
2175
        file_in_root_key = ('', 'file-in-root', 'a-file-id')
 
2176
        file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
 
2177
        dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
 
2178
                     ('', [(file_in_merged_key,
 
2179
                            [absent, absent, present_file]),
 
2180
                           (file_in_root_key,
 
2181
                            [present_file, present_file, present_file]),
 
2182
                          ]),
 
2183
                    ]
 
2184
 
 
2185
        state = self.create_empty_dirstate()
 
2186
        self.addCleanup(state.unlock)
 
2187
        state._set_data(['parent-id', 'merged-id'], dirblocks[:])
 
2188
        state._validate()
 
2189
 
 
2190
        exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
 
2191
                         ('', [(file_in_root_key,
 
2192
                                [present_file, present_file]),
 
2193
                              ]),
 
2194
                        ]
 
2195
        state._discard_merge_parents()
 
2196
        state._validate()
 
2197
        self.assertEqual(exp_dirblocks, state._dirblocks)
 
2198
 
 
2199
    def test_discard_renamed(self):
 
2200
        null_stat = dirstate.DirState.NULLSTAT
 
2201
        present_dir = ('d', '', 0, False, null_stat)
 
2202
        present_file = ('f', '', 0, False, null_stat)
 
2203
        absent = dirstate.DirState.NULL_PARENT_DETAILS
 
2204
        root_key = ('', '', 'a-root-value')
 
2205
        file_in_root_key = ('', 'file-in-root', 'a-file-id')
 
2206
        # Renamed relative to parent
 
2207
        file_rename_s_key = ('', 'file-s', 'b-file-id')
 
2208
        file_rename_t_key = ('', 'file-t', 'b-file-id')
 
2209
        # And one that is renamed between the parents, but absent in this
 
2210
        key_in_1 = ('', 'file-in-1', 'c-file-id')
 
2211
        key_in_2 = ('', 'file-in-2', 'c-file-id')
 
2212
 
 
2213
        dirblocks = [
 
2214
            ('', [(root_key, [present_dir, present_dir, present_dir])]),
 
2215
            ('', [(key_in_1,
 
2216
                   [absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
 
2217
                  (key_in_2,
 
2218
                   [absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
 
2219
                  (file_in_root_key,
 
2220
                   [present_file, present_file, present_file]),
 
2221
                  (file_rename_s_key,
 
2222
                   [('r', 'file-t', 'b-file-id'), absent, present_file]),
 
2223
                  (file_rename_t_key,
 
2224
                   [present_file, absent, ('r', 'file-s', 'b-file-id')]),
 
2225
                 ]),
 
2226
        ]
 
2227
        exp_dirblocks = [
 
2228
            ('', [(root_key, [present_dir, present_dir])]),
 
2229
            ('', [(key_in_1, [absent, present_file]),
 
2230
                  (file_in_root_key, [present_file, present_file]),
 
2231
                  (file_rename_t_key, [present_file, absent]),
 
2232
                 ]),
 
2233
        ]
 
2234
        state = self.create_empty_dirstate()
 
2235
        self.addCleanup(state.unlock)
 
2236
        state._set_data(['parent-id', 'merged-id'], dirblocks[:])
 
2237
        state._validate()
 
2238
 
 
2239
        state._discard_merge_parents()
 
2240
        state._validate()
 
2241
        self.assertEqual(exp_dirblocks, state._dirblocks)
 
2242
 
 
2243
    def test_discard_all_subdir(self):
 
2244
        null_stat = dirstate.DirState.NULLSTAT
 
2245
        present_dir = ('d', '', 0, False, null_stat)
 
2246
        present_file = ('f', '', 0, False, null_stat)
 
2247
        absent = dirstate.DirState.NULL_PARENT_DETAILS
 
2248
        root_key = ('', '', 'a-root-value')
 
2249
        subdir_key = ('', 'sub', 'dir-id')
 
2250
        child1_key = ('sub', 'child1', 'child1-id')
 
2251
        child2_key = ('sub', 'child2', 'child2-id')
 
2252
        child3_key = ('sub', 'child3', 'child3-id')
 
2253
 
 
2254
        dirblocks = [
 
2255
            ('', [(root_key, [present_dir, present_dir, present_dir])]),
 
2256
            ('', [(subdir_key, [present_dir, present_dir, present_dir])]),
 
2257
            ('sub', [(child1_key, [absent, absent, present_file]),
 
2258
                     (child2_key, [absent, absent, present_file]),
 
2259
                     (child3_key, [absent, absent, present_file]),
 
2260
                    ]),
 
2261
        ]
 
2262
        exp_dirblocks = [
 
2263
            ('', [(root_key, [present_dir, present_dir])]),
 
2264
            ('', [(subdir_key, [present_dir, present_dir])]),
 
2265
            ('sub', []),
 
2266
        ]
 
2267
        state = self.create_empty_dirstate()
 
2268
        self.addCleanup(state.unlock)
 
2269
        state._set_data(['parent-id', 'merged-id'], dirblocks[:])
 
2270
        state._validate()
 
2271
 
 
2272
        state._discard_merge_parents()
 
2273
        state._validate()
 
2274
        self.assertEqual(exp_dirblocks, state._dirblocks)
 
2275
 
 
2276
 
 
2277
class Test_InvEntryToDetails(tests.TestCase):
 
2278
 
 
2279
    def assertDetails(self, expected, inv_entry):
 
2280
        details = dirstate.DirState._inv_entry_to_details(inv_entry)
 
2281
        self.assertEqual(expected, details)
 
2282
        # details should always allow join() and always be a plain str when
 
2283
        # finished
 
2284
        (minikind, fingerprint, size, executable, tree_data) = details
 
2285
        self.assertIsInstance(minikind, str)
 
2286
        self.assertIsInstance(fingerprint, str)
 
2287
        self.assertIsInstance(tree_data, str)
 
2288
 
 
2289
    def test_unicode_symlink(self):
 
2290
        inv_entry = inventory.InventoryLink('link-file-id',
 
2291
                                            u'nam\N{Euro Sign}e',
 
2292
                                            'link-parent-id')
 
2293
        inv_entry.revision = 'link-revision-id'
 
2294
        target = u'link-targ\N{Euro Sign}t'
 
2295
        inv_entry.symlink_target = target
 
2296
        self.assertDetails(('l', target.encode('UTF-8'), 0, False,
 
2297
                            'link-revision-id'), inv_entry)
 
2298
 
 
2299
 
 
2300
class TestSHA1Provider(tests.TestCaseInTempDir):
 
2301
 
 
2302
    def test_sha1provider_is_an_interface(self):
 
2303
        p = dirstate.SHA1Provider()
 
2304
        self.assertRaises(NotImplementedError, p.sha1, "foo")
 
2305
        self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
 
2306
 
 
2307
    def test_defaultsha1provider_sha1(self):
 
2308
        text = 'test\r\nwith\nall\rpossible line endings\r\n'
 
2309
        self.build_tree_contents([('foo', text)])
 
2310
        expected_sha = osutils.sha_string(text)
 
2311
        p = dirstate.DefaultSHA1Provider()
 
2312
        self.assertEqual(expected_sha, p.sha1('foo'))
 
2313
 
 
2314
    def test_defaultsha1provider_stat_and_sha1(self):
 
2315
        text = 'test\r\nwith\nall\rpossible line endings\r\n'
 
2316
        self.build_tree_contents([('foo', text)])
 
2317
        expected_sha = osutils.sha_string(text)
 
2318
        p = dirstate.DefaultSHA1Provider()
 
2319
        statvalue, sha1 = p.stat_and_sha1('foo')
 
2320
        self.assertTrue(len(statvalue) >= 10)
 
2321
        self.assertEqual(len(text), statvalue.st_size)
 
2322
        self.assertEqual(expected_sha, sha1)