/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

(working), fix dirstate to use utf8 file ids.
Also fix a bug in _generate_inventory for non-ascii paths. It was
combining the decoded path with the utf8 prefix and assuming the
whole thing was utf8.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
 
18
 
 
19
import os
 
20
 
 
21
from bzrlib import dirstate, errors
 
22
from bzrlib.dirstate import DirState
 
23
from bzrlib.memorytree import MemoryTree
 
24
from bzrlib.tests import TestCaseWithTransport
 
25
 
 
26
 
 
27
# TODO:
 
28
# test DirStateRevisionTree : test filtering out of deleted files does not
 
29
#         filter out files called RECYCLED.BIN ;)
 
30
# test 0 parents, 1 parent, 4 parents.
 
31
# test unicode parents, non unicode parents
 
32
# test all change permutations in one and two parents.
 
33
# i.e. file in parent 1, dir in parent 2, symlink in tree.
 
34
# test that renames in the tree result in correct parent paths 
 
35
# Test get state from a file, then asking for lines.
 
36
# write a smaller state, and check the file has been truncated.
 
37
# add a entry when its in state deleted
 
38
# revision attribute for root entries.
 
39
# test that utf8 strings are preserved in _row_to_line
 
40
# test parent manipulation 
 
41
# test parents that are null in save : i.e. no record in the parent tree for this.
 
42
# todo: _set_data records ghost parents.
 
43
# TESTS to write:
 
44
# general checks for NOT_IN_MEMORY error conditions.
 
45
# set_path_id on a NOT_IN_MEMORY dirstate
 
46
# set_path_id  unicode support
 
47
# set_path_id  setting id of a path not root
 
48
# set_path_id  setting id when there are parents without the id in the parents
 
49
# set_path_id  setting id when there are parents with the id in the parents
 
50
# set_path_id  setting id when state is not in memory
 
51
# set_path_id  setting id when state is in memory unmodified
 
52
# set_path_id  setting id when state is in memory modified
 
53
 
 
54
class TestCaseWithDirState(TestCaseWithTransport):
 
55
    """Helper functions for creating DirState objects with various content."""
 
56
 
 
57
    def create_empty_dirstate(self):
 
58
        state = dirstate.DirState.initialize('dirstate')
 
59
        return state
 
60
 
 
61
    def create_dirstate_with_root(self):
 
62
        state = self.create_empty_dirstate()
 
63
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
64
        root_entry_direntry = ('', '', 'a-root-value'), [
 
65
            ('directory', '', 0, False, packed_stat),
 
66
            ]
 
67
        dirblocks = []
 
68
        dirblocks.append(('', [root_entry_direntry]))
 
69
        dirblocks.append(('', []))
 
70
        state._set_data([], dirblocks)
 
71
        return state
 
72
 
 
73
    def create_dirstate_with_root_and_subdir(self):
 
74
        state = self.create_dirstate_with_root()
 
75
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
76
        dirblocks = list(state._dirblocks)
 
77
        subdir_entry = ('', 'subdir', 'subdir-id'), [
 
78
            ('directory', '', 0, False, packed_stat),
 
79
            ]
 
80
        dirblocks[1][1].append(subdir_entry)
 
81
        state._set_data([], dirblocks)
 
82
        return state
 
83
 
 
84
    def create_complex_dirstate(self):
 
85
        """This dirstate contains multiple files and directories.
 
86
 
 
87
         /        a-root-value
 
88
         a/       a-dir
 
89
         b/       b-dir
 
90
         c        c-file
 
91
         d        d-file
 
92
         a/e/     e-dir
 
93
         a/f      f-file
 
94
         b/g      g-file
 
95
         b/h\xc3\xa5  h-\xc3\xa5-file  #This is u'\xe5' encoded into utf-8
 
96
 
 
97
        # Notice that a/e is an empty directory.
 
98
        """
 
99
        state = dirstate.DirState.initialize('dirstate')
 
100
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
101
        null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
 
102
        root_entry = ('', '', 'a-root-value'), [
 
103
            ('directory', '', 0, False, packed_stat),
 
104
            ]
 
105
        a_entry = ('', 'a', 'a-dir'), [
 
106
            ('directory', '', 0, False, packed_stat),
 
107
            ]
 
108
        b_entry = ('', 'b', 'b-dir'), [
 
109
            ('directory', '', 0, False, packed_stat),
 
110
            ]
 
111
        c_entry = ('', 'c', 'c-file'), [
 
112
            ('file', null_sha, 10, False, packed_stat),
 
113
            ]
 
114
        d_entry = ('', 'd', 'd-file'), [
 
115
            ('file', null_sha, 20, False, packed_stat),
 
116
            ]
 
117
        e_entry = ('a', 'e', 'e-dir'), [
 
118
            ('directory', '', 0, False, packed_stat),
 
119
            ]
 
120
        f_entry = ('a', 'f', 'f-file'), [
 
121
            ('file', null_sha, 30, False, packed_stat),
 
122
            ]
 
123
        g_entry = ('b', 'g', 'g-file'), [
 
124
            ('file', null_sha, 30, False, packed_stat),
 
125
            ]
 
126
        h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
 
127
            ('file', null_sha, 40, False, packed_stat),
 
128
            ]
 
129
        dirblocks = []
 
130
        dirblocks.append(('', [root_entry]))
 
131
        dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
 
132
        dirblocks.append(('a', [e_entry, f_entry]))
 
133
        dirblocks.append(('b', [g_entry, h_entry]))
 
134
        state._set_data([], dirblocks)
 
135
        return state
 
136
 
 
137
    def check_state_with_reopen(self, expected_result, state):
 
138
        """Check that state has current state expected_result.
 
139
        
 
140
        This will check the current state, open the file anew and check it
 
141
        again.
 
142
        """
 
143
        self.assertEqual(expected_result[0],  state.get_parent_ids())
 
144
        # there should be no ghosts in this tree.
 
145
        self.assertEqual([], state.get_ghosts())
 
146
        # there should be one fileid in this tree - the root of the tree.
 
147
        self.assertEqual(expected_result[1], list(state._iter_entries()))
 
148
        state.save()
 
149
        state = dirstate.DirState.on_file('dirstate')
 
150
        self.assertEqual(expected_result[1], list(state._iter_entries()))
 
151
 
 
152
 
 
153
class TestTreeToDirState(TestCaseWithDirState):
 
154
 
 
155
    def test_empty_to_dirstate(self):
 
156
        """We should be able to create a dirstate for an empty tree."""
 
157
        # There are no files on disk and no parents
 
158
        tree = self.make_branch_and_tree('tree')
 
159
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
160
        expected_result = ([], [
 
161
            (('', '', tree.path2id('')), # common details
 
162
             [('directory', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
 
163
             ])])
 
164
        self.check_state_with_reopen(expected_result, state)
 
165
 
 
166
    def test_1_parents_empty_to_dirstate(self):
 
167
        # create a parent by doing a commit
 
168
        tree = self.make_branch_and_tree('tree')
 
169
        rev_id = tree.commit('first post').encode('utf8')
 
170
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
171
        root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
 
172
        expected_result = ([rev_id], [
 
173
            (('', '', tree.path2id('')), # common details
 
174
             [('directory', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
 
175
              ('directory', '', 0, False, rev_id), # first parent details
 
176
             ])])
 
177
        self.check_state_with_reopen(expected_result, state)
 
178
 
 
179
    def test_2_parents_empty_to_dirstate(self):
 
180
        # create a parent by doing a commit
 
181
        tree = self.make_branch_and_tree('tree')
 
182
        rev_id = tree.commit('first post')
 
183
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
184
        rev_id2 = tree2.commit('second post', allow_pointless=True)
 
185
        tree.merge_from_branch(tree2.branch)
 
186
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
187
        expected_result = ([rev_id, rev_id2], [
 
188
            (('', '', tree.path2id('')), # common details
 
189
             [('directory', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
 
190
              ('directory', '', 0, False, rev_id), # first parent details
 
191
              ('directory', '', 0, False, rev_id2), # second parent details
 
192
             ])])
 
193
        self.check_state_with_reopen(expected_result, state)
 
194
        
 
195
    def test_empty_unknowns_are_ignored_to_dirstate(self):
 
196
        """We should be able to create a dirstate for an empty tree."""
 
197
        # There are no files on disk and no parents
 
198
        tree = self.make_branch_and_tree('tree')
 
199
        self.build_tree(['tree/unknown'])
 
200
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
201
        expected_result = ([], [
 
202
            (('', '', tree.path2id('')), # common details
 
203
             [('directory', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
 
204
             ])])
 
205
        self.check_state_with_reopen(expected_result, state)
 
206
        
 
207
    def get_tree_with_a_file(self):
 
208
        tree = self.make_branch_and_tree('tree')
 
209
        self.build_tree(['tree/a file'])
 
210
        tree.add('a file', 'a file id')
 
211
        return tree
 
212
 
 
213
    def test_non_empty_no_parents_to_dirstate(self):
 
214
        """We should be able to create a dirstate for an empty tree."""
 
215
        # There are files on disk and no parents
 
216
        tree = self.get_tree_with_a_file()
 
217
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
218
        expected_result = ([], [
 
219
            (('', '', tree.path2id('')), # common details
 
220
             [('directory', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
 
221
             ]),
 
222
            (('', 'a file', 'a file id'), # common
 
223
             [('file', '', 0, False, dirstate.DirState.NULLSTAT), # current
 
224
             ]),
 
225
            ])
 
226
        self.check_state_with_reopen(expected_result, state)
 
227
 
 
228
    def test_1_parents_not_empty_to_dirstate(self):
 
229
        # create a parent by doing a commit
 
230
        tree = self.get_tree_with_a_file()
 
231
        rev_id = tree.commit('first post').encode('utf8')
 
232
        # change the current content to be different this will alter stat, sha
 
233
        # and length:
 
234
        self.build_tree_contents([('tree/a file', 'new content\n')])
 
235
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
236
        expected_result = ([rev_id], [
 
237
            (('', '', tree.path2id('')), # common details
 
238
             [('directory', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
 
239
              ('directory', '', 0, False, rev_id), # first parent details
 
240
             ]),
 
241
            (('', 'a file', 'a file id'), # common
 
242
             [('file', '', 0, False, dirstate.DirState.NULLSTAT), # current
 
243
              ('file', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False, rev_id), # first parent
 
244
             ]),
 
245
            ])
 
246
        self.check_state_with_reopen(expected_result, state)
 
247
 
 
248
    def test_2_parents_not_empty_to_dirstate(self):
 
249
        # create a parent by doing a commit
 
250
        tree = self.get_tree_with_a_file()
 
251
        rev_id = tree.commit('first post').encode('utf8')
 
252
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
253
        # change the current content to be different this will alter stat, sha
 
254
        # and length:
 
255
        self.build_tree_contents([('tree2/a file', 'merge content\n')])
 
256
        rev_id2 = tree2.commit('second post').encode('utf8')
 
257
        tree.merge_from_branch(tree2.branch)
 
258
        # change the current content to be different this will alter stat, sha
 
259
        # and length again, giving us three distinct values:
 
260
        self.build_tree_contents([('tree/a file', 'new content\n')])
 
261
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
262
        expected_result = ([rev_id, rev_id2], [
 
263
            (('', '', tree.path2id('')), # common details
 
264
             [('directory', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
 
265
              ('directory', '', 0, False, rev_id), # first parent details
 
266
              ('directory', '', 0, False, rev_id2), # second parent details
 
267
             ]),
 
268
            (('', 'a file', 'a file id'), # common
 
269
             [('file', '', 0, False, dirstate.DirState.NULLSTAT), # current
 
270
              ('file', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False, rev_id), # first parent
 
271
              ('file', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False, rev_id2), # second parent
 
272
             ]),
 
273
            ])
 
274
        self.check_state_with_reopen(expected_result, state)
 
275
 
 
276
 
 
277
class TestDirStateOnFile(TestCaseWithDirState):
 
278
 
 
279
    def test_construct_with_path(self):
 
280
        tree = self.make_branch_and_tree('tree')
 
281
        state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
 
282
        # we want to be able to get the lines of the dirstate that we will
 
283
        # write to disk.
 
284
        lines = state.get_lines()
 
285
        self.build_tree_contents([('dirstate', ''.join(lines))])
 
286
        # get a state object
 
287
        state = dirstate.DirState.on_file('dirstate')
 
288
        # no parents, default tree content
 
289
        expected_result = ([], [
 
290
            (('', '', tree.path2id('')), # common details
 
291
             # current tree details, but new from_tree skips statting, it
 
292
             # uses set_state_from_inventory, and thus depends on the
 
293
             # inventory state.
 
294
             [('directory', '', 0, False, dirstate.DirState.NULLSTAT),
 
295
             ])
 
296
            ])
 
297
        self.check_state_with_reopen(expected_result, state)
 
298
 
 
299
    def test_can_save_clean_on_file(self):
 
300
        tree = self.make_branch_and_tree('tree')
 
301
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
302
        # doing a save should work here as there have been no changes.
 
303
        state.save()
 
304
        # TODO: stat it and check it hasn't changed; may require waiting for
 
305
        # the state accuracy window.
 
306
 
 
307
 
 
308
class TestDirStateInitialize(TestCaseWithDirState):
 
309
 
 
310
    def test_initialize(self):
 
311
        state = dirstate.DirState.initialize('dirstate')
 
312
        self.assertIsInstance(state, dirstate.DirState)
 
313
        lines = state.get_lines()
 
314
        self.assertFileEqual(''.join(state.get_lines()),
 
315
            'dirstate')
 
316
        expected_result = ([], [
 
317
            (('', '', 'TREE_ROOT'), # common details
 
318
             [('directory', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
319
             ])
 
320
            ])
 
321
        self.check_state_with_reopen(expected_result, state)
 
322
 
 
323
 
 
324
class TestDirStateManipulations(TestCaseWithDirState):
 
325
 
 
326
    def test_set_state_from_inventory_no_content_no_parents(self):
 
327
        # setting the current inventory is a slow but important api to support.
 
328
        state = dirstate.DirState.initialize('dirstate')
 
329
        tree1 = self.make_branch_and_memory_tree('tree1')
 
330
        tree1.lock_write()
 
331
        tree1.add('')
 
332
        revid1 = tree1.commit('foo').encode('utf8')
 
333
        root_id = tree1.inventory.root.file_id
 
334
        state.set_state_from_inventory(tree1.inventory)
 
335
        tree1.unlock()
 
336
        self.assertEqual(DirState.IN_MEMORY_UNMODIFIED, state._header_state)
 
337
        self.assertEqual(DirState.IN_MEMORY_MODIFIED, state._dirblock_state)
 
338
        expected_result = [], [
 
339
            (('', '', root_id), [
 
340
             ('directory', '', 0, False, DirState.NULLSTAT)])]
 
341
        self.check_state_with_reopen(expected_result, state)
 
342
 
 
343
    def test_set_path_id_no_parents(self):
 
344
        """The id of a path can be changed trivally with no parents."""
 
345
        state = dirstate.DirState.initialize('dirstate')
 
346
        # check precondition to be sure the state does change appropriately.
 
347
        self.assertEqual(
 
348
            [(('', '', 'TREE_ROOT'), [('directory', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
 
349
            list(state._iter_entries()))
 
350
        state.set_path_id('', 'foobarbaz')
 
351
        expected_rows = [
 
352
            (('', '', 'foobarbaz'), [('directory', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
 
353
        self.assertEqual(expected_rows, list(state._iter_entries()))
 
354
        # should work across save too
 
355
        state.save()
 
356
        state = dirstate.DirState.on_file('dirstate')
 
357
        self.assertEqual(expected_rows, list(state._iter_entries()))
 
358
 
 
359
    def test_set_parent_trees_no_content(self):
 
360
        # set_parent_trees is a slow but important api to support.
 
361
        state = dirstate.DirState.initialize('dirstate')
 
362
        tree1 = self.make_branch_and_memory_tree('tree1')
 
363
        tree1.lock_write()
 
364
        tree1.add('')
 
365
        revid1 = tree1.commit('foo')
 
366
        tree1.unlock()
 
367
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
 
368
        tree2 = MemoryTree.create_on_branch(branch2)
 
369
        tree2.lock_write()
 
370
        revid2 = tree2.commit('foo')
 
371
        root_id = tree2.inventory.root.file_id
 
372
        state.set_path_id('', root_id)
 
373
        tree2.unlock()
 
374
        state.set_parent_trees(
 
375
            ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
376
             (revid2, tree2.branch.repository.revision_tree(revid2)),
 
377
             ('ghost-rev', None)),
 
378
            ['ghost-rev'])
 
379
        # check we can reopen and use the dirstate after setting parent trees.
 
380
        state.save()
 
381
        state = dirstate.DirState.on_file('dirstate')
 
382
        self.assertEqual([revid1, revid2, 'ghost-rev'],  state.get_parent_ids())
 
383
        # iterating the entire state ensures that the state is parsable.
 
384
        list(state._iter_entries())
 
385
        # be sure that it sets not appends - change it
 
386
        state.set_parent_trees(
 
387
            ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
388
             ('ghost-rev', None)),
 
389
            ['ghost-rev'])
 
390
        # and now put it back.
 
391
        state.set_parent_trees(
 
392
            ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
393
             (revid2, tree2.branch.repository.revision_tree(revid2)),
 
394
             ('ghost-rev', tree2.branch.repository.revision_tree(None))),
 
395
            ['ghost-rev'])
 
396
        self.assertEqual([revid1, revid2, 'ghost-rev'],  state.get_parent_ids())
 
397
        # the ghost should be recorded as such by set_parent_trees.
 
398
        self.assertEqual(['ghost-rev'], state.get_ghosts())
 
399
        self.assertEqual(
 
400
            [(('', '', root_id), [
 
401
              ('directory', '', 0, False, DirState.NULLSTAT),
 
402
              ('directory', '', 0, False, revid1),
 
403
              ('directory', '', 0, False, revid2)
 
404
              ])],
 
405
            list(state._iter_entries()))
 
406
 
 
407
    def test_set_parent_trees_file_missing_from_tree(self):
 
408
        # Adding a parent tree may reference files not in the current state.
 
409
        # they should get listed just once by id, even if they are in two  
 
410
        # separate trees.
 
411
        # set_parent_trees is a slow but important api to support.
 
412
        state = dirstate.DirState.initialize('dirstate')
 
413
        tree1 = self.make_branch_and_memory_tree('tree1')
 
414
        tree1.lock_write()
 
415
        tree1.add('')
 
416
        tree1.add(['a file'], ['file-id'], ['file'])
 
417
        tree1.put_file_bytes_non_atomic('file-id', 'file-content')
 
418
        revid1 = tree1.commit('foo')
 
419
        tree1.unlock()
 
420
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
 
421
        tree2 = MemoryTree.create_on_branch(branch2)
 
422
        tree2.lock_write()
 
423
        tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
 
424
        revid2 = tree2.commit('foo')
 
425
        root_id = tree2.inventory.root.file_id
 
426
        state.set_path_id('', root_id)
 
427
        tree2.unlock()
 
428
        state.set_parent_trees(
 
429
            ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
430
             (revid2, tree2.branch.repository.revision_tree(revid2)),
 
431
             ), [])
 
432
        # check the layout in memory
 
433
        expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
 
434
            (('', '', root_id), [
 
435
             ('directory', '', 0, False, DirState.NULLSTAT),
 
436
             ('directory', '', 0, False, revid1.encode('utf8')),
 
437
             ('directory', '', 0, False, revid2.encode('utf8'))]),
 
438
            (('', 'a file', 'file-id'), [
 
439
             ('absent', '', 0, False, ''),
 
440
             ('file', '2439573625385400f2a669657a7db6ae7515d371', 12, False, revid1.encode('utf8')),
 
441
             ('file', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False, revid2.encode('utf8'))])
 
442
            ]
 
443
        self.check_state_with_reopen(expected_result, state)
 
444
 
 
445
    ### add a path via _set_data - so we dont need delta work, just
 
446
    # raw data in, and ensure that it comes out via get_lines happily.
 
447
 
 
448
    def test_add_path_to_root_no_parents_all_data(self):
 
449
        # The most trivial addition of a path is when there are no parents and
 
450
        # its in the root and all data about the file is supplied
 
451
        state = dirstate.DirState.initialize('dirstate')
 
452
        self.build_tree(['a file'])
 
453
        stat = os.lstat('a file')
 
454
        # the 1*20 is the sha1 pretend value.
 
455
        state.add('a file', 'a file id', 'file', stat, '1'*20)
 
456
        # having added it, it should be in the output of iter_entries.
 
457
        expected_entries = [
 
458
            (('', '', 'TREE_ROOT'), [
 
459
             ('directory', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
 
460
             ]),
 
461
            (('', 'a file', 'a file id'), [
 
462
             ('file', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree details
 
463
             ]),
 
464
            ]
 
465
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
466
        # saving and reloading should not affect this.
 
467
        state.save()
 
468
        state = dirstate.DirState.on_file('dirstate')
 
469
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
470
 
 
471
    def test_add_path_to_unversioned_directory(self):
 
472
        """Adding a path to an unversioned directory should error.
 
473
        
 
474
        This is a duplicate of TestWorkingTree.test_add_in_unversioned, 
 
475
        once dirstate is stable and if it is merged with WorkingTree3, consider
 
476
        removing this copy of the test.
 
477
        """
 
478
        state = dirstate.DirState.initialize('dirstate')
 
479
        self.build_tree(['unversioned/', 'unversioned/a file'])
 
480
        self.assertRaises(errors.NotVersionedError, state.add,
 
481
            'unversioned/a file', 'a file id', 'file', None, None)
 
482
        
 
483
    def test_add_directory_to_root_no_parents_all_data(self):
 
484
        # The most trivial addition of a dir is when there are no parents and
 
485
        # its in the root and all data about the file is supplied
 
486
        state = dirstate.DirState.initialize('dirstate')
 
487
        self.build_tree(['a dir/'])
 
488
        stat = os.lstat('a dir')
 
489
        state.add('a dir', 'a dir id', 'directory', stat, None)
 
490
        # having added it, it should be in the output of iter_entries.
 
491
        expected_entries = [
 
492
            (('', '', 'TREE_ROOT'), [
 
493
             ('directory', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
 
494
             ]),
 
495
            (('', 'a dir', 'a dir id'), [
 
496
             ('directory', '', 0, False, dirstate.pack_stat(stat)), # current tree details
 
497
             ]),
 
498
            ]
 
499
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
500
        # saving and reloading should not affect this.
 
501
        state.save()
 
502
        state = dirstate.DirState.on_file('dirstate')
 
503
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
504
 
 
505
    def test_add_symlink_to_root_no_parents_all_data(self):
 
506
        # The most trivial addition of a symlink when there are no parents and
 
507
        # its in the root and all data about the file is supplied
 
508
        state = dirstate.DirState.initialize('dirstate')
 
509
        ## TODO: windows: dont fail this test. Also, how are symlinks meant to
 
510
        # be represented on windows.
 
511
        os.symlink('target', 'a link')
 
512
        stat = os.lstat('a link')
 
513
        state.add('a link', 'a link id', 'symlink', stat, 'target')
 
514
        # having added it, it should be in the output of iter_entries.
 
515
        expected_entries = [
 
516
            (('', '', 'TREE_ROOT'), [
 
517
             ('directory', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
 
518
             ]),
 
519
            (('', 'a link', 'a link id'), [
 
520
             ('symlink', 'target', 6, False, dirstate.pack_stat(stat)), # current tree details
 
521
             ]),
 
522
            ]
 
523
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
524
        # saving and reloading should not affect this.
 
525
        state.save()
 
526
        state = dirstate.DirState.on_file('dirstate')
 
527
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
528
 
 
529
    def test_add_directory_and_child_no_parents_all_data(self):
 
530
        # after adding a directory, we should be able to add children to it.
 
531
        state = dirstate.DirState.initialize('dirstate')
 
532
        self.build_tree(['a dir/', 'a dir/a file'])
 
533
        stat = os.lstat('a dir')
 
534
        state.add('a dir', 'a dir id', 'directory', stat, None)
 
535
        filestat = os.lstat('a dir/a file')
 
536
        state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
 
537
        # having added it, it should be in the output of iter_entries.
 
538
        expected_entries = [
 
539
            (('', '', 'TREE_ROOT'), [
 
540
             ('directory', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
 
541
             ]),
 
542
            (('', 'a dir', 'a dir id'), [
 
543
             ('directory', '', 0, False, dirstate.pack_stat(stat)), # current tree details
 
544
             ]),
 
545
            (('a dir', 'a file', 'a file id'), [
 
546
             ('file', '1'*20, 25, False, dirstate.pack_stat(filestat)), # current tree details
 
547
             ]),
 
548
            ]
 
549
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
550
        # saving and reloading should not affect this.
 
551
        state.save()
 
552
        state = dirstate.DirState.on_file('dirstate')
 
553
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
554
 
 
555
 
 
556
class TestGetLines(TestCaseWithDirState):
 
557
 
 
558
    def test_get_line_with_2_rows(self):
 
559
        state = self.create_dirstate_with_root_and_subdir()
 
560
        self.assertEqual(['#bazaar dirstate flat format 2\n',
 
561
            'adler32: -1327947603\n',
 
562
            'num_entries: 2\n',
 
563
            '0\x00\n\x00'
 
564
            '0\x00\n\x00'
 
565
            '\x00\x00a-root-value\x00'
 
566
            'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
 
567
            '\x00subdir\x00subdir-id\x00'
 
568
            'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'],
 
569
            state.get_lines())
 
570
 
 
571
    def test_entry_to_line(self):
 
572
        state = self.create_dirstate_with_root()
 
573
        self.assertEqual(
 
574
            '\x00\x00a-root-value\x00d\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
 
575
            state._entry_to_line(state._dirblocks[0][1][0]))
 
576
 
 
577
    def test_entry_to_line_with_parent(self):
 
578
        state = dirstate.DirState.initialize('dirstate')
 
579
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
580
        root_entry = ('', '', 'a-root-value'), [
 
581
            ('directory', '', 0, False, packed_stat), # current tree details
 
582
            ('absent', 'dirname/basename', 0, False, ''), # first: a pointer to the current location
 
583
            ]
 
584
        self.assertEqual(
 
585
            '\x00\x00a-root-value\x00'
 
586
            'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
 
587
            'a\x00dirname/basename\x000\x00n\x00',
 
588
            state._entry_to_line(root_entry))
 
589
 
 
590
    def test_entry_to_line_with_two_parents_at_different_paths(self):
 
591
        # / in the tree, at / in one parent and /dirname/basename in the other.
 
592
        state = dirstate.DirState.initialize('dirstate')
 
593
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
594
        root_entry = ('', '', 'a-root-value'), [
 
595
            ('directory', '', 0, False, packed_stat), # current tree details
 
596
            ('directory', '', 0, False, 'rev_id'), # first parent details
 
597
            ('absent', 'dirname/basename', 0, False, ''), # second: a pointer to the current location
 
598
            ]
 
599
        self.assertEqual(
 
600
            '\x00\x00a-root-value\x00'
 
601
            'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
 
602
            'd\x00\x000\x00n\x00rev_id\x00'
 
603
            'a\x00dirname/basename\x000\x00n\x00',
 
604
            state._entry_to_line(root_entry))
 
605
 
 
606
    def test_iter_entries(self):
 
607
        # we should be able to iterate the dirstate entries from end to end
 
608
        # this is for get_lines to be easy to read.
 
609
        state = dirstate.DirState.initialize('dirstate')
 
610
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
611
        dirblocks = []
 
612
        root_entries = [(('', '', 'a-root-value'), [
 
613
            ('directory', '', 0, False, packed_stat), # current tree details
 
614
            ])]
 
615
        dirblocks.append(('', root_entries))
 
616
        # add two files in the root
 
617
        subdir_entry = ('', 'subdir', 'subdir-id'), [
 
618
            ('directory', '', 0, False, packed_stat), # current tree details
 
619
            ]
 
620
        afile_entry = ('', 'afile', 'afile-id'), [
 
621
            ('file', 'sha1value', 34, False, packed_stat), # current tree details
 
622
            ]
 
623
        dirblocks.append(('', [subdir_entry, afile_entry]))
 
624
        # and one in subdir
 
625
        file_entry2 = ('subdir', '2file', '2file-id'), [
 
626
            ('file', 'sha1value', 23, False, packed_stat), # current tree details
 
627
            ]
 
628
        dirblocks.append(('subdir', [file_entry2]))
 
629
        state._set_data([], dirblocks)
 
630
        expected_entries = [root_entries[0], subdir_entry, afile_entry, file_entry2]
 
631
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
632
 
 
633
 
 
634
class TestGetBlockRowIndex(TestCaseWithDirState):
 
635
 
 
636
    def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
 
637
        file_present, state, dirname, basename, tree_index):
 
638
        self.assertEqual((block_index, row_index, dir_present, file_present),
 
639
            state._get_block_entry_index(dirname, basename, tree_index))
 
640
        if dir_present:
 
641
            block = state._dirblocks[block_index]
 
642
            self.assertEqual(dirname, block[0])
 
643
        if dir_present and file_present:
 
644
            row = state._dirblocks[block_index][1][row_index]
 
645
            self.assertEqual(dirname, row[0][0])
 
646
            self.assertEqual(basename, row[0][1])
 
647
 
 
648
    def test_simple_structure(self):
 
649
        state = self.create_dirstate_with_root_and_subdir()
 
650
        self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
 
651
        self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
 
652
        self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
 
653
        self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
 
654
        self.assertBlockRowIndexEqual(2, 0, False, False, state, 'subdir', 'foo', 0)
 
655
 
 
656
    def test_complex_structure_exists(self):
 
657
        state = self.create_complex_dirstate()
 
658
        # Make sure we can find everything that exists
 
659
        self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
 
660
        self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
 
661
        self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
 
662
        self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
 
663
        self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
 
664
        self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
 
665
        self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
 
666
        self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
 
667
        self.assertBlockRowIndexEqual(3, 1, True, True, state, 'b', 'h\xc3\xa5', 0)
 
668
 
 
669
    def test_complex_structure_missing(self):
 
670
        state = self.create_complex_dirstate()
 
671
        # Make sure things would be inserted in the right locations
 
672
        # '_' comes before 'a'
 
673
        self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
 
674
        self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
 
675
        self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
 
676
        self.assertBlockRowIndexEqual(1, 4, True, False, state, '', 'h\xc3\xa5', 0)
 
677
        self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
 
678
        self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
 
679
        self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
 
680
        # This would be inserted between a/ and b/
 
681
        self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
 
682
        # Put at the end
 
683
        self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
 
684
 
 
685
 
 
686
class TestGetEntry(TestCaseWithDirState):
 
687
 
 
688
    def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
 
689
        """Check that the right entry is returned for a request to getEntry."""
 
690
        entry = state._get_entry(index, path_utf8=path)
 
691
        if file_id is None:
 
692
            self.assertEqual((None, None), entry)
 
693
        else:
 
694
            cur = entry[0]
 
695
            self.assertEqual((dirname, basename, file_id), cur[:3])
 
696
 
 
697
    def test_simple_structure(self):
 
698
        state = self.create_dirstate_with_root_and_subdir()
 
699
        self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
 
700
        self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
 
701
        self.assertEntryEqual(None, None, None, state, 'missing', 0)
 
702
        self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
 
703
        self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
 
704
 
 
705
    def test_complex_structure_exists(self):
 
706
        state = self.create_complex_dirstate()
 
707
        self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
 
708
        self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
 
709
        self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
 
710
        self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
 
711
        self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
 
712
        self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
 
713
        self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
 
714
        self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
 
715
        self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state, 'b/h\xc3\xa5', 0)
 
716
 
 
717
    def test_complex_structure_missing(self):
 
718
        state = self.create_complex_dirstate()
 
719
        self.assertEntryEqual(None, None, None, state, '_', 0)
 
720
        self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
 
721
        self.assertEntryEqual(None, None, None, state, 'a/b', 0)
 
722
        self.assertEntryEqual(None, None, None, state, 'c/d', 0)
 
723
 
 
724
    def test_get_entry_uninitialized(self):
 
725
        """Calling get_entry will load data if it needs to"""
 
726
        state = self.create_dirstate_with_root()
 
727
        state.save()
 
728
        del state
 
729
        state = dirstate.DirState.on_file('dirstate')
 
730
        self.assertEqual(dirstate.DirState.NOT_IN_MEMORY, state._header_state)
 
731
        self.assertEqual(dirstate.DirState.NOT_IN_MEMORY, state._dirblock_state)
 
732
        self.assertEntryEqual('', '', 'a-root-value', state, '', 0)