/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

  • Committer: John Arbash Meinel
  • Date: 2011-04-22 14:12:22 UTC
  • mfrom: (5809 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5836.
  • Revision ID: john@arbash-meinel.com-20110422141222-nx2j0hbkihcb8j16
Merge newer bzr.dev and resolve conflicts.
Try to write some documentation about how the _dirblock_state works.
Fix up the tests so that they pass again.

Show diffs side-by-side

added added

removed removed

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