~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-10-26 08:08:23 UTC
  • mfrom: (5514.1.1 665100-content-type)
  • mto: This revision was merged to the branch mainline in revision 5516.
  • Revision ID: v.ladeuil+lp@free.fr-20101026080823-3wggo03b7cpn9908
Correctly set the Content-Type header when POSTing http requests

Show diffs side-by-side

added added

removed removed

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