~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

  • Committer: Martin Pool
  • Date: 2005-09-30 05:56:05 UTC
  • mto: (1185.14.2)
  • mto: This revision was merged to the branch mainline in revision 1396.
  • Revision ID: mbp@sourcefrog.net-20050930055605-a2c534529b392a7d
- fix upgrade for transport changes

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
 
            ('d', '', 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
 
            ('d', '', 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
 
            ('d', '', 0, False, packed_stat),
104
 
            ]
105
 
        a_entry = ('', 'a', 'a-dir'), [
106
 
            ('d', '', 0, False, packed_stat),
107
 
            ]
108
 
        b_entry = ('', 'b', 'b-dir'), [
109
 
            ('d', '', 0, False, packed_stat),
110
 
            ]
111
 
        c_entry = ('', 'c', 'c-file'), [
112
 
            ('f', null_sha, 10, False, packed_stat),
113
 
            ]
114
 
        d_entry = ('', 'd', 'd-file'), [
115
 
            ('f', null_sha, 20, False, packed_stat),
116
 
            ]
117
 
        e_entry = ('a', 'e', 'e-dir'), [
118
 
            ('d', '', 0, False, packed_stat),
119
 
            ]
120
 
        f_entry = ('a', 'f', 'f-file'), [
121
 
            ('f', null_sha, 30, False, packed_stat),
122
 
            ]
123
 
        g_entry = ('b', 'g', 'g-file'), [
124
 
            ('f', null_sha, 30, False, packed_stat),
125
 
            ]
126
 
        h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
127
 
            ('f', 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
 
             [('d', '', 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
 
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
175
 
              ('d', '', 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
 
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
190
 
              ('d', '', 0, False, rev_id), # first parent details
191
 
              ('d', '', 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
 
             [('d', '', 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
 
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
221
 
             ]),
222
 
            (('', 'a file', 'a file id'), # common
223
 
             [('f', '', 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
 
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
239
 
              ('d', '', 0, False, rev_id), # first parent details
240
 
             ]),
241
 
            (('', 'a file', 'a file id'), # common
242
 
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
243
 
              ('f', '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
 
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
265
 
              ('d', '', 0, False, rev_id), # first parent details
266
 
              ('d', '', 0, False, rev_id2), # second parent details
267
 
             ]),
268
 
            (('', 'a file', 'a file id'), # common
269
 
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
270
 
              ('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False, rev_id), # first parent
271
 
              ('f', '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
 
             [('d', '', 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
 
             [('d', '', 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
 
             ('d', '', 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'), [('d', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
349
 
            list(state._iter_entries()))
350
 
        state.set_path_id('', 'foobarbaz')
351
 
        expected_rows = [
352
 
            (('', '', 'foobarbaz'), [('d', '', 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
 
              ('d', '', 0, False, DirState.NULLSTAT),
402
 
              ('d', '', 0, False, revid1),
403
 
              ('d', '', 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
 
             ('d', '', 0, False, DirState.NULLSTAT),
436
 
             ('d', '', 0, False, revid1.encode('utf8')),
437
 
             ('d', '', 0, False, revid2.encode('utf8'))]),
438
 
            (('', 'a file', 'file-id'), [
439
 
             ('a', '', 0, False, ''),
440
 
             ('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False, revid1.encode('utf8')),
441
 
             ('f', '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
 
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
460
 
             ]),
461
 
            (('', 'a file', 'a file id'), [
462
 
             ('f', '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
 
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
494
 
             ]),
495
 
            (('', 'a dir', 'a dir id'), [
496
 
             ('d', '', 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
 
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
518
 
             ]),
519
 
            (('', 'a link', 'a link id'), [
520
 
             ('l', '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
 
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
541
 
             ]),
542
 
            (('', 'a dir', 'a dir id'), [
543
 
             ('d', '', 0, False, dirstate.pack_stat(stat)), # current tree details
544
 
             ]),
545
 
            (('a dir', 'a file', 'a file id'), [
546
 
             ('f', '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
 
            ('d', '', 0, False, packed_stat), # current tree details
582
 
            ('a', '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
 
            ('d', '', 0, False, packed_stat), # current tree details
596
 
            ('d', '', 0, False, 'rev_id'), # first parent details
597
 
            ('a', '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
 
            ('d', '', 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
 
            ('d', '', 0, False, packed_stat), # current tree details
619
 
            ]
620
 
        afile_entry = ('', 'afile', 'afile-id'), [
621
 
            ('f', '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
 
            ('f', '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)