~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

  • Committer: Andrew Bennetts
  • Date: 2007-03-26 06:24:01 UTC
  • mto: This revision was merged to the branch mainline in revision 2376.
  • Revision ID: andrew.bennetts@canonical.com-20070326062401-k3nbefzje5332jaf
Deal with review comments from Robert:

  * Add my name to the NEWS file
  * Move the test case to a new module in branch_implementations
  * Remove revision_history cruft from identitymap and test_identitymap
  * Improve some docstrings

Also, this fixes a bug where revision_history was not returning a copy of the
cached data, allowing the cache to be corrupted.

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
from bzrlib import (
24
24
    dirstate,
25
25
    errors,
26
 
    inventory,
27
26
    osutils,
28
27
    )
29
28
from bzrlib.memorytree import MemoryTree
 
29
from bzrlib.osutils import has_symlinks
30
30
from bzrlib.tests import (
31
 
        SymlinkFeature,
32
31
        TestCase,
33
32
        TestCaseWithTransport,
 
33
        TestSkipped,
34
34
        )
35
35
 
36
36
 
102
102
         b/g      g-file
103
103
         b/h\xc3\xa5  h-\xc3\xa5-file  #This is u'\xe5' encoded into utf-8
104
104
 
105
 
        Notice that a/e is an empty directory.
106
 
 
107
 
        :return: The dirstate, still write-locked.
 
105
        # Notice that a/e is an empty directory.
108
106
        """
109
107
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
110
108
        null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
163
161
        """
164
162
        # The state should already be write locked, since we just had to do
165
163
        # some operation to get here.
166
 
        self.assertTrue(state._lock_token is not None)
 
164
        assert state._lock_token is not None
167
165
        try:
168
166
            self.assertEqual(expected_result[0],  state.get_parent_ids())
169
167
            # there should be no ghosts in this tree.
173
171
            state.save()
174
172
        finally:
175
173
            state.unlock()
176
 
        del state
 
174
        del state # Callers should unlock
177
175
        state = dirstate.DirState.on_file('dirstate')
178
176
        state.lock_read()
179
177
        try:
181
179
        finally:
182
180
            state.unlock()
183
181
 
184
 
    def create_basic_dirstate(self):
185
 
        """Create a dirstate with a few files and directories.
186
 
 
187
 
            a
188
 
            b/
189
 
              c
190
 
              d/
191
 
                e
192
 
            b-c
193
 
            f
194
 
        """
195
 
        tree = self.make_branch_and_tree('tree')
196
 
        paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
197
 
        file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
198
 
        self.build_tree(['tree/' + p for p in paths])
199
 
        tree.set_root_id('TREE_ROOT')
200
 
        tree.add([p.rstrip('/') for p in paths], file_ids)
201
 
        tree.commit('initial', rev_id='rev-1')
202
 
        revision_id = 'rev-1'
203
 
        # a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
204
 
        t = self.get_transport('tree')
205
 
        a_text = t.get_bytes('a')
206
 
        a_sha = osutils.sha_string(a_text)
207
 
        a_len = len(a_text)
208
 
        # b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
209
 
        # c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
210
 
        c_text = t.get_bytes('b/c')
211
 
        c_sha = osutils.sha_string(c_text)
212
 
        c_len = len(c_text)
213
 
        # d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
214
 
        # e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
215
 
        e_text = t.get_bytes('b/d/e')
216
 
        e_sha = osutils.sha_string(e_text)
217
 
        e_len = len(e_text)
218
 
        b_c_text = t.get_bytes('b-c')
219
 
        b_c_sha = osutils.sha_string(b_c_text)
220
 
        b_c_len = len(b_c_text)
221
 
        # f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
222
 
        f_text = t.get_bytes('f')
223
 
        f_sha = osutils.sha_string(f_text)
224
 
        f_len = len(f_text)
225
 
        null_stat = dirstate.DirState.NULLSTAT
226
 
        expected = {
227
 
            '':(('', '', 'TREE_ROOT'), [
228
 
                  ('d', '', 0, False, null_stat),
229
 
                  ('d', '', 0, False, revision_id),
230
 
                ]),
231
 
            'a':(('', 'a', 'a-id'), [
232
 
                   ('f', '', 0, False, null_stat),
233
 
                   ('f', a_sha, a_len, False, revision_id),
234
 
                 ]),
235
 
            'b':(('', 'b', 'b-id'), [
236
 
                  ('d', '', 0, False, null_stat),
237
 
                  ('d', '', 0, False, revision_id),
238
 
                 ]),
239
 
            'b/c':(('b', 'c', 'c-id'), [
240
 
                    ('f', '', 0, False, null_stat),
241
 
                    ('f', c_sha, c_len, False, revision_id),
242
 
                   ]),
243
 
            'b/d':(('b', 'd', 'd-id'), [
244
 
                    ('d', '', 0, False, null_stat),
245
 
                    ('d', '', 0, False, revision_id),
246
 
                   ]),
247
 
            'b/d/e':(('b/d', 'e', 'e-id'), [
248
 
                      ('f', '', 0, False, null_stat),
249
 
                      ('f', e_sha, e_len, False, revision_id),
250
 
                     ]),
251
 
            'b-c':(('', 'b-c', 'b-c-id'), [
252
 
                      ('f', '', 0, False, null_stat),
253
 
                      ('f', b_c_sha, b_c_len, False, revision_id),
254
 
                     ]),
255
 
            'f':(('', 'f', 'f-id'), [
256
 
                  ('f', '', 0, False, null_stat),
257
 
                  ('f', f_sha, f_len, False, revision_id),
258
 
                 ]),
259
 
        }
260
 
        state = dirstate.DirState.from_tree(tree, 'dirstate')
261
 
        try:
262
 
            state.save()
263
 
        finally:
264
 
            state.unlock()
265
 
        # Use a different object, to make sure nothing is pre-cached in memory.
266
 
        state = dirstate.DirState.on_file('dirstate')
267
 
        state.lock_read()
268
 
        self.addCleanup(state.unlock)
269
 
        self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
270
 
                         state._dirblock_state)
271
 
        # This is code is only really tested if we actually have to make more
272
 
        # than one read, so set the page size to something smaller.
273
 
        # We want it to contain about 2.2 records, so that we have a couple
274
 
        # records that we can read per attempt
275
 
        state._bisect_page_size = 200
276
 
        return tree, state, expected
277
 
 
278
 
    def create_duplicated_dirstate(self):
279
 
        """Create a dirstate with a deleted and added entries.
280
 
 
281
 
        This grabs a basic_dirstate, and then removes and re adds every entry
282
 
        with a new file id.
283
 
        """
284
 
        tree, state, expected = self.create_basic_dirstate()
285
 
        # Now we will just remove and add every file so we get an extra entry
286
 
        # per entry. Unversion in reverse order so we handle subdirs
287
 
        tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
288
 
        tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
289
 
                 ['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
290
 
 
291
 
        # Update the expected dictionary.
292
 
        for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
293
 
            orig = expected[path]
294
 
            path2 = path + '2'
295
 
            # This record was deleted in the current tree
296
 
            expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
297
 
                                        orig[1][1]])
298
 
            new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
299
 
            # And didn't exist in the basis tree
300
 
            expected[path2] = (new_key, [orig[1][0],
301
 
                                         dirstate.DirState.NULL_PARENT_DETAILS])
302
 
 
303
 
        # We will replace the 'dirstate' file underneath 'state', but that is
304
 
        # okay as lock as we unlock 'state' first.
305
 
        state.unlock()
306
 
        try:
307
 
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
308
 
            try:
309
 
                new_state.save()
310
 
            finally:
311
 
                new_state.unlock()
312
 
        finally:
313
 
            # But we need to leave state in a read-lock because we already have
314
 
            # a cleanup scheduled
315
 
            state.lock_read()
316
 
        return tree, state, expected
317
 
 
318
 
    def create_renamed_dirstate(self):
319
 
        """Create a dirstate with a few internal renames.
320
 
 
321
 
        This takes the basic dirstate, and moves the paths around.
322
 
        """
323
 
        tree, state, expected = self.create_basic_dirstate()
324
 
        # Rename a file
325
 
        tree.rename_one('a', 'b/g')
326
 
        # And a directory
327
 
        tree.rename_one('b/d', 'h')
328
 
 
329
 
        old_a = expected['a']
330
 
        expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
331
 
        expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
332
 
                                                ('r', 'a', 0, False, '')])
333
 
        old_d = expected['b/d']
334
 
        expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
335
 
        expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
336
 
                                             ('r', 'b/d', 0, False, '')])
337
 
 
338
 
        old_e = expected['b/d/e']
339
 
        expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
340
 
                             old_e[1][1]])
341
 
        expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
342
 
                                                ('r', 'b/d/e', 0, False, '')])
343
 
 
344
 
        state.unlock()
345
 
        try:
346
 
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
347
 
            try:
348
 
                new_state.save()
349
 
            finally:
350
 
                new_state.unlock()
351
 
        finally:
352
 
            state.lock_read()
353
 
        return tree, state, expected
354
 
 
355
182
 
356
183
class TestTreeToDirState(TestCaseWithDirState):
357
184
 
360
187
        # There are no files on disk and no parents
361
188
        tree = self.make_branch_and_tree('tree')
362
189
        expected_result = ([], [
363
 
            (('', '', tree.get_root_id()), # common details
 
190
            (('', '', tree.path2id('')), # common details
364
191
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
365
192
             ])])
366
193
        state = dirstate.DirState.from_tree(tree, 'dirstate')
373
200
        rev_id = tree.commit('first post').encode('utf8')
374
201
        root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
375
202
        expected_result = ([rev_id], [
376
 
            (('', '', tree.get_root_id()), # common details
 
203
            (('', '', tree.path2id('')), # common details
377
204
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
378
205
              ('d', '', 0, False, rev_id), # first parent details
379
206
             ])])
380
207
        state = dirstate.DirState.from_tree(tree, 'dirstate')
381
208
        self.check_state_with_reopen(expected_result, state)
382
 
        state.lock_read()
383
 
        try:
384
 
            state._validate()
385
 
        finally:
386
 
            state.unlock()
 
209
        state._validate()
387
210
 
388
211
    def test_2_parents_empty_to_dirstate(self):
389
212
        # create a parent by doing a commit
393
216
        rev_id2 = tree2.commit('second post', allow_pointless=True)
394
217
        tree.merge_from_branch(tree2.branch)
395
218
        expected_result = ([rev_id, rev_id2], [
396
 
            (('', '', tree.get_root_id()), # common details
 
219
            (('', '', tree.path2id('')), # common details
397
220
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
398
221
              ('d', '', 0, False, rev_id), # first parent details
399
222
              ('d', '', 0, False, rev_id2), # second parent details
400
223
             ])])
401
224
        state = dirstate.DirState.from_tree(tree, 'dirstate')
402
225
        self.check_state_with_reopen(expected_result, state)
403
 
        state.lock_read()
404
 
        try:
405
 
            state._validate()
406
 
        finally:
407
 
            state.unlock()
 
226
        state._validate()
408
227
 
409
228
    def test_empty_unknowns_are_ignored_to_dirstate(self):
410
229
        """We should be able to create a dirstate for an empty tree."""
412
231
        tree = self.make_branch_and_tree('tree')
413
232
        self.build_tree(['tree/unknown'])
414
233
        expected_result = ([], [
415
 
            (('', '', tree.get_root_id()), # common details
 
234
            (('', '', tree.path2id('')), # common details
416
235
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
417
236
             ])])
418
237
        state = dirstate.DirState.from_tree(tree, 'dirstate')
421
240
    def get_tree_with_a_file(self):
422
241
        tree = self.make_branch_and_tree('tree')
423
242
        self.build_tree(['tree/a file'])
424
 
        tree.add('a file', 'a-file-id')
 
243
        tree.add('a file', 'a file id')
425
244
        return tree
426
245
 
427
246
    def test_non_empty_no_parents_to_dirstate(self):
429
248
        # There are files on disk and no parents
430
249
        tree = self.get_tree_with_a_file()
431
250
        expected_result = ([], [
432
 
            (('', '', tree.get_root_id()), # common details
 
251
            (('', '', tree.path2id('')), # common details
433
252
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
434
253
             ]),
435
 
            (('', 'a file', 'a-file-id'), # common
 
254
            (('', 'a file', 'a file id'), # common
436
255
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
437
256
             ]),
438
257
            ])
447
266
        # and length:
448
267
        self.build_tree_contents([('tree/a file', 'new content\n')])
449
268
        expected_result = ([rev_id], [
450
 
            (('', '', tree.get_root_id()), # common details
 
269
            (('', '', tree.path2id('')), # common details
451
270
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
452
271
              ('d', '', 0, False, rev_id), # first parent details
453
272
             ]),
454
 
            (('', 'a file', 'a-file-id'), # common
 
273
            (('', 'a file', 'a file id'), # common
455
274
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
456
275
              ('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
457
276
               rev_id), # first parent
474
293
        # and length again, giving us three distinct values:
475
294
        self.build_tree_contents([('tree/a file', 'new content\n')])
476
295
        expected_result = ([rev_id, rev_id2], [
477
 
            (('', '', tree.get_root_id()), # common details
 
296
            (('', '', tree.path2id('')), # common details
478
297
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
479
298
              ('d', '', 0, False, rev_id), # first parent details
480
299
              ('d', '', 0, False, rev_id2), # second parent details
481
300
             ]),
482
 
            (('', 'a file', 'a-file-id'), # common
 
301
            (('', 'a file', 'a file id'), # common
483
302
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
484
303
              ('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
485
304
               rev_id), # first parent
526
345
        # get a state object
527
346
        # no parents, default tree content
528
347
        expected_result = ([], [
529
 
            (('', '', tree.get_root_id()), # common details
 
348
            (('', '', tree.path2id('')), # common details
530
349
             # current tree details, but new from_tree skips statting, it
531
350
             # uses set_state_from_inventory, and thus depends on the
532
351
             # inventory state.
567
386
            self.assertEqual('', entry[1][0][1])
568
387
            # We should have a real entry.
569
388
            self.assertNotEqual((None, None), entry)
570
 
            # Make sure everything is old enough
571
 
            state._sha_cutoff_time()
572
 
            state._cutoff_time += 10
573
389
            sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
574
390
            # We should have gotten a real sha1
575
391
            self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
647
463
        finally:
648
464
            state.unlock()
649
465
 
650
 
    def test_save_refuses_if_changes_aborted(self):
651
 
        self.build_tree(['a-file', 'a-dir/'])
652
 
        state = dirstate.DirState.initialize('dirstate')
653
 
        try:
654
 
            # No stat and no sha1 sum.
655
 
            state.add('a-file', 'a-file-id', 'file', None, '')
656
 
            state.save()
657
 
        finally:
658
 
            state.unlock()
659
 
 
660
 
        # The dirstate should include TREE_ROOT and 'a-file' and nothing else
661
 
        expected_blocks = [
662
 
            ('', [(('', '', 'TREE_ROOT'),
663
 
                   [('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
664
 
            ('', [(('', 'a-file', 'a-file-id'),
665
 
                   [('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
666
 
        ]
667
 
 
668
 
        state = dirstate.DirState.on_file('dirstate')
669
 
        state.lock_write()
670
 
        try:
671
 
            state._read_dirblocks_if_needed()
672
 
            self.assertEqual(expected_blocks, state._dirblocks)
673
 
 
674
 
            # Now modify the state, but mark it as inconsistent
675
 
            state.add('a-dir', 'a-dir-id', 'directory', None, '')
676
 
            state._changes_aborted = True
677
 
            state.save()
678
 
        finally:
679
 
            state.unlock()
680
 
 
681
 
        state = dirstate.DirState.on_file('dirstate')
682
 
        state.lock_read()
683
 
        try:
684
 
            state._read_dirblocks_if_needed()
685
 
            self.assertEqual(expected_blocks, state._dirblocks)
686
 
        finally:
687
 
            state.unlock()
688
 
 
689
466
 
690
467
class TestDirStateInitialize(TestCaseWithDirState):
691
468
 
699
476
        try:
700
477
            self.assertIsInstance(state, dirstate.DirState)
701
478
            lines = state.get_lines()
702
 
        finally:
 
479
            self.assertFileEqual(''.join(state.get_lines()),
 
480
                'dirstate')
 
481
            self.check_state_with_reopen(expected_result, state)
 
482
        except:
703
483
            state.unlock()
704
 
        # On win32 you can't read from a locked file, even within the same
705
 
        # process. So we have to unlock and release before we check the file
706
 
        # contents.
707
 
        self.assertFileEqual(''.join(lines), 'dirstate')
708
 
        state.lock_read() # check_state_with_reopen will unlock
709
 
        self.check_state_with_reopen(expected_result, state)
 
484
            raise
710
485
 
711
486
 
712
487
class TestDirStateManipulations(TestCaseWithDirState):
718
493
        try:
719
494
            tree1.add('')
720
495
            revid1 = tree1.commit('foo').encode('utf8')
721
 
            root_id = tree1.get_root_id()
 
496
            root_id = tree1.inventory.root.file_id
722
497
            inv = tree1.inventory
723
498
        finally:
724
499
            tree1.unlock()
739
514
            # This will unlock it
740
515
            self.check_state_with_reopen(expected_result, state)
741
516
 
742
 
    def test_set_state_from_inventory_preserves_hashcache(self):
743
 
        # https://bugs.launchpad.net/bzr/+bug/146176
744
 
        # set_state_from_inventory should preserve the stat and hash value for
745
 
        # workingtree files that are not changed by the inventory.
746
 
       
747
 
        tree = self.make_branch_and_tree('.')
748
 
        # depends on the default format using dirstate...
749
 
        tree.lock_write()
750
 
        try:
751
 
            # make a dirstate with some valid hashcache data 
752
 
            # file on disk, but that's not needed for this test
753
 
            foo_contents = 'contents of foo'
754
 
            self.build_tree_contents([('foo', foo_contents)])
755
 
            tree.add('foo', 'foo-id')
756
 
 
757
 
            foo_stat = os.stat('foo')
758
 
            foo_packed = dirstate.pack_stat(foo_stat)
759
 
            foo_sha = osutils.sha_string(foo_contents)
760
 
            foo_size = len(foo_contents)
761
 
 
762
 
            # should not be cached yet, because the file's too fresh
763
 
            self.assertEqual(
764
 
                (('', 'foo', 'foo-id',),
765
 
                 [('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
766
 
                tree._dirstate._get_entry(0, 'foo-id'))
767
 
            # poke in some hashcache information - it wouldn't normally be
768
 
            # stored because it's too fresh
769
 
            tree._dirstate.update_minimal(
770
 
                ('', 'foo', 'foo-id'),
771
 
                'f', False, foo_sha, foo_packed, foo_size, 'foo')
772
 
            # now should be cached
773
 
            self.assertEqual(
774
 
                (('', 'foo', 'foo-id',),
775
 
                 [('f', foo_sha, foo_size, False, foo_packed)]),
776
 
                tree._dirstate._get_entry(0, 'foo-id'))
777
 
           
778
 
            # extract the inventory, and add something to it
779
 
            inv = tree._get_inventory()
780
 
            # should see the file we poked in...
781
 
            self.assertTrue(inv.has_id('foo-id'))
782
 
            self.assertTrue(inv.has_filename('foo'))
783
 
            inv.add_path('bar', 'file', 'bar-id')
784
 
            tree._dirstate._validate()
785
 
            # this used to cause it to lose its hashcache
786
 
            tree._dirstate.set_state_from_inventory(inv)
787
 
            tree._dirstate._validate()
788
 
        finally:
789
 
            tree.unlock()
790
 
 
791
 
        tree.lock_read()
792
 
        try:
793
 
            # now check that the state still has the original hashcache value
794
 
            state = tree._dirstate
795
 
            state._validate()
796
 
            foo_tuple = state._get_entry(0, path_utf8='foo')
797
 
            self.assertEqual(
798
 
                (('', 'foo', 'foo-id',),
799
 
                 [('f', foo_sha, len(foo_contents), False,
800
 
                   dirstate.pack_stat(foo_stat))]),
801
 
                foo_tuple)
802
 
        finally:
803
 
            tree.unlock()
804
 
 
805
 
 
806
 
    def test_set_state_from_inventory_mixed_paths(self):
807
 
        tree1 = self.make_branch_and_tree('tree1')
808
 
        self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
809
 
                         'tree1/a/b/foo', 'tree1/a-b/bar'])
810
 
        tree1.lock_write()
811
 
        try:
812
 
            tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
813
 
                      ['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
814
 
            tree1.commit('rev1', rev_id='rev1')
815
 
            root_id = tree1.get_root_id()
816
 
            inv = tree1.inventory
817
 
        finally:
818
 
            tree1.unlock()
819
 
        expected_result1 = [('', '', root_id, 'd'),
820
 
                            ('', 'a', 'a-id', 'd'),
821
 
                            ('', 'a-b', 'a-b-id', 'd'),
822
 
                            ('a', 'b', 'b-id', 'd'),
823
 
                            ('a/b', 'foo', 'foo-id', 'f'),
824
 
                            ('a-b', 'bar', 'bar-id', 'f'),
825
 
                           ]
826
 
        expected_result2 = [('', '', root_id, 'd'),
827
 
                            ('', 'a', 'a-id', 'd'),
828
 
                            ('', 'a-b', 'a-b-id', 'd'),
829
 
                            ('a-b', 'bar', 'bar-id', 'f'),
830
 
                           ]
831
 
        state = dirstate.DirState.initialize('dirstate')
832
 
        try:
833
 
            state.set_state_from_inventory(inv)
834
 
            values = []
835
 
            for entry in state._iter_entries():
836
 
                values.append(entry[0] + entry[1][0][:1])
837
 
            self.assertEqual(expected_result1, values)
838
 
            del inv['b-id']
839
 
            state.set_state_from_inventory(inv)
840
 
            values = []
841
 
            for entry in state._iter_entries():
842
 
                values.append(entry[0] + entry[1][0][:1])
843
 
            self.assertEqual(expected_result2, values)
844
 
        finally:
845
 
            state.unlock()
846
 
 
847
517
    def test_set_path_id_no_parents(self):
848
518
        """The id of a path can be changed trivally with no parents."""
849
519
        state = dirstate.DirState.initialize('dirstate')
863
533
        finally:
864
534
            state.unlock()
865
535
        state = dirstate.DirState.on_file('dirstate')
 
536
        state._validate()
866
537
        state.lock_read()
867
538
        try:
868
 
            state._validate()
869
539
            self.assertEqual(expected_rows, list(state._iter_entries()))
870
540
        finally:
871
541
            state.unlock()
932
602
        tree2.lock_write()
933
603
        try:
934
604
            revid2 = tree2.commit('foo')
935
 
            root_id = tree2.get_root_id()
 
605
            root_id = tree2.inventory.root.file_id
936
606
        finally:
937
607
            tree2.unlock()
938
608
        state = dirstate.DirState.initialize('dirstate')
1002
672
        try:
1003
673
            tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1004
674
            revid2 = tree2.commit('foo')
1005
 
            root_id = tree2.get_root_id()
 
675
            root_id = tree2.inventory.root.file_id
1006
676
        finally:
1007
677
            tree2.unlock()
1008
678
        # check the layout in memory
1048
718
            (('', '', 'TREE_ROOT'), [
1049
719
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1050
720
             ]),
1051
 
            (('', 'a file', 'a-file-id'), [
 
721
            (('', 'a file', 'a file id'), [
1052
722
             ('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1053
723
             ]),
1054
724
            ]
1055
725
        try:
1056
 
            state.add('a file', 'a-file-id', 'file', stat, '1'*20)
 
726
            state.add('a file', 'a file id', 'file', stat, '1'*20)
1057
727
            # having added it, it should be in the output of iter_entries.
1058
728
            self.assertEqual(expected_entries, list(state._iter_entries()))
1059
729
            # saving and reloading should not affect this.
1078
748
        state = dirstate.DirState.initialize('dirstate')
1079
749
        try:
1080
750
            self.assertRaises(errors.NotVersionedError, state.add,
1081
 
                'unversioned/a file', 'a-file-id', 'file', None, None)
 
751
                'unversioned/a file', 'a file id', 'file', None, None)
1082
752
        finally:
1083
753
            state.unlock()
1084
754
 
1116
786
        # The most trivial addition of a symlink when there are no parents and
1117
787
        # its in the root and all data about the file is supplied
1118
788
        # bzr doesn't support fake symlinks on windows, yet.
1119
 
        self.requireFeature(SymlinkFeature)
 
789
        if not has_symlinks():
 
790
            raise TestSkipped("No symlink support")
1120
791
        os.symlink('target', 'a link')
1121
792
        stat = os.lstat('a link')
1122
793
        expected_entries = [
1155
826
            (('', 'a dir', 'a dir id'), [
1156
827
             ('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1157
828
             ]),
1158
 
            (('a dir', 'a file', 'a-file-id'), [
 
829
            (('a dir', 'a file', 'a file id'), [
1159
830
             ('f', '1'*20, 25, False,
1160
831
              dirstate.pack_stat(filestat)), # current tree details
1161
832
             ]),
1163
834
        state = dirstate.DirState.initialize('dirstate')
1164
835
        try:
1165
836
            state.add('a dir', 'a dir id', 'directory', dirstat, None)
1166
 
            state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
 
837
            state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
1167
838
            # added it, it should be in the output of iter_entries.
1168
839
            self.assertEqual(expected_entries, list(state._iter_entries()))
1169
840
            # saving and reloading should not affect this.
1432
1103
            state.unlock()
1433
1104
 
1434
1105
 
1435
 
class TestIterChildEntries(TestCaseWithDirState):
1436
 
 
1437
 
    def create_dirstate_with_two_trees(self):
1438
 
        """This dirstate contains multiple files and directories.
1439
 
 
1440
 
         /        a-root-value
1441
 
         a/       a-dir
1442
 
         b/       b-dir
1443
 
         c        c-file
1444
 
         d        d-file
1445
 
         a/e/     e-dir
1446
 
         a/f      f-file
1447
 
         b/g      g-file
1448
 
         b/h\xc3\xa5  h-\xc3\xa5-file  #This is u'\xe5' encoded into utf-8
1449
 
 
1450
 
        Notice that a/e is an empty directory.
1451
 
 
1452
 
        There is one parent tree, which has the same shape with the following variations:
1453
 
        b/g in the parent is gone.
1454
 
        b/h in the parent has a different id
1455
 
        b/i is new in the parent 
1456
 
        c is renamed to b/j in the parent
1457
 
 
1458
 
        :return: The dirstate, still write-locked.
1459
 
        """
1460
 
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1461
 
        null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1462
 
        NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1463
 
        root_entry = ('', '', 'a-root-value'), [
1464
 
            ('d', '', 0, False, packed_stat),
1465
 
            ('d', '', 0, False, 'parent-revid'),
1466
 
            ]
1467
 
        a_entry = ('', 'a', 'a-dir'), [
1468
 
            ('d', '', 0, False, packed_stat),
1469
 
            ('d', '', 0, False, 'parent-revid'),
1470
 
            ]
1471
 
        b_entry = ('', 'b', 'b-dir'), [
1472
 
            ('d', '', 0, False, packed_stat),
1473
 
            ('d', '', 0, False, 'parent-revid'),
1474
 
            ]
1475
 
        c_entry = ('', 'c', 'c-file'), [
1476
 
            ('f', null_sha, 10, False, packed_stat),
1477
 
            ('r', 'b/j', 0, False, ''),
1478
 
            ]
1479
 
        d_entry = ('', 'd', 'd-file'), [
1480
 
            ('f', null_sha, 20, False, packed_stat),
1481
 
            ('f', 'd', 20, False, 'parent-revid'),
1482
 
            ]
1483
 
        e_entry = ('a', 'e', 'e-dir'), [
1484
 
            ('d', '', 0, False, packed_stat),
1485
 
            ('d', '', 0, False, 'parent-revid'),
1486
 
            ]
1487
 
        f_entry = ('a', 'f', 'f-file'), [
1488
 
            ('f', null_sha, 30, False, packed_stat),
1489
 
            ('f', 'f', 20, False, 'parent-revid'),
1490
 
            ]
1491
 
        g_entry = ('b', 'g', 'g-file'), [
1492
 
            ('f', null_sha, 30, False, packed_stat),
1493
 
            NULL_PARENT_DETAILS,
1494
 
            ]
1495
 
        h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1496
 
            ('f', null_sha, 40, False, packed_stat),
1497
 
            NULL_PARENT_DETAILS,
1498
 
            ]
1499
 
        h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1500
 
            NULL_PARENT_DETAILS,
1501
 
            ('f', 'h', 20, False, 'parent-revid'),
1502
 
            ]
1503
 
        i_entry = ('b', 'i', 'i-file'), [
1504
 
            NULL_PARENT_DETAILS,
1505
 
            ('f', 'h', 20, False, 'parent-revid'),
1506
 
            ]
1507
 
        j_entry = ('b', 'j', 'c-file'), [
1508
 
            ('r', 'c', 0, False, ''),
1509
 
            ('f', 'j', 20, False, 'parent-revid'),
1510
 
            ]
1511
 
        dirblocks = []
1512
 
        dirblocks.append(('', [root_entry]))
1513
 
        dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1514
 
        dirblocks.append(('a', [e_entry, f_entry]))
1515
 
        dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1516
 
        state = dirstate.DirState.initialize('dirstate')
1517
 
        state._validate()
1518
 
        try:
1519
 
            state._set_data(['parent'], dirblocks)
1520
 
        except:
1521
 
            state.unlock()
1522
 
            raise
1523
 
        return state, dirblocks
1524
 
 
1525
 
    def test_iter_children_b(self):
1526
 
        state, dirblocks = self.create_dirstate_with_two_trees()
1527
 
        self.addCleanup(state.unlock)
1528
 
        expected_result = []
1529
 
        expected_result.append(dirblocks[3][1][2]) # h2
1530
 
        expected_result.append(dirblocks[3][1][3]) # i
1531
 
        expected_result.append(dirblocks[3][1][4]) # j
1532
 
        self.assertEqual(expected_result,
1533
 
            list(state._iter_child_entries(1, 'b')))
1534
 
 
1535
 
    def test_iter_child_root(self):
1536
 
        state, dirblocks = self.create_dirstate_with_two_trees()
1537
 
        self.addCleanup(state.unlock)
1538
 
        expected_result = []
1539
 
        expected_result.append(dirblocks[1][1][0]) # a
1540
 
        expected_result.append(dirblocks[1][1][1]) # b
1541
 
        expected_result.append(dirblocks[1][1][3]) # d
1542
 
        expected_result.append(dirblocks[2][1][0]) # e
1543
 
        expected_result.append(dirblocks[2][1][1]) # f
1544
 
        expected_result.append(dirblocks[3][1][2]) # h2
1545
 
        expected_result.append(dirblocks[3][1][3]) # i
1546
 
        expected_result.append(dirblocks[3][1][4]) # j
1547
 
        self.assertEqual(expected_result,
1548
 
            list(state._iter_child_entries(1, '')))
1549
 
 
1550
 
 
1551
1106
class TestDirstateSortOrder(TestCaseWithTransport):
1552
1107
    """Test that DirState adds entries in the right order."""
1553
1108
 
1617
1172
        super(InstrumentedDirState, self).__init__(path)
1618
1173
        self._time_offset = 0
1619
1174
        self._log = []
1620
 
        # member is dynamically set in DirState.__init__ to turn on trace
1621
 
        self._sha1_file = self._sha1_file_and_log
1622
1175
 
1623
1176
    def _sha_cutoff_time(self):
1624
1177
        timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1625
1178
        self._cutoff_time = timestamp + self._time_offset
1626
1179
 
1627
 
    def _sha1_file_and_log(self, abspath):
 
1180
    def _sha1_file(self, abspath, entry):
1628
1181
        self._log.append(('sha1', abspath))
1629
 
        return osutils.sha_file_by_name(abspath)
 
1182
        return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
1630
1183
 
1631
1184
    def _read_link(self, abspath, old_link):
1632
1185
        self._log.append(('read_link', abspath, old_link))
1694
1247
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1695
1248
                         link_or_sha1)
1696
1249
 
1697
 
        # The dirblock entry should not cache the file's sha1
1698
 
        self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
 
1250
        # The dirblock entry should be updated with the new info
 
1251
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1699
1252
                         entry[1])
1700
1253
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1701
1254
                         state._dirblock_state)
1720
1273
                         link_or_sha1)
1721
1274
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1722
1275
                         state._dirblock_state)
1723
 
        self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1724
 
                         entry[1])
1725
1276
        state.save()
1726
1277
 
1727
1278
        # However, if we move the clock forward so the file is considered
1728
 
        # "stable", it should just cache the value.
1729
 
        state.adjust_time(+20)
 
1279
        # "stable", it should just returned the cached value.
 
1280
        state.adjust_time(20)
1730
1281
        link_or_sha1 = state.update_entry(entry, abspath='a',
1731
1282
                                          stat_value=stat_value)
1732
1283
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1733
1284
                         link_or_sha1)
1734
1285
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1735
1286
                          ('sha1', 'a'), ('is_exec', mode, False),
1736
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1737
1287
                         ], state._log)
1738
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1739
 
                         entry[1])
1740
1288
 
1741
 
        # Subsequent calls will just return the cached value
1742
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1743
 
                                          stat_value=stat_value)
 
1289
    def test_update_entry_no_stat_value(self):
 
1290
        """Passing the stat_value is optional."""
 
1291
        state, entry = self.get_state_with_a()
 
1292
        state.adjust_time(-10) # Make sure the file looks new
 
1293
        self.build_tree(['a'])
 
1294
        # Add one where we don't provide the stat or sha already
 
1295
        link_or_sha1 = state.update_entry(entry, abspath='a')
1744
1296
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1745
1297
                         link_or_sha1)
1746
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1747
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1748
 
                          ('sha1', 'a'), ('is_exec', mode, False),
 
1298
        stat_value = os.lstat('a')
 
1299
        self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
 
1300
                          ('is_exec', stat_value.st_mode, False),
1749
1301
                         ], state._log)
1750
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1751
 
                         entry[1])
1752
1302
 
1753
1303
    def test_update_entry_symlink(self):
1754
1304
        """Update entry should read symlinks."""
1755
 
        self.requireFeature(SymlinkFeature)
 
1305
        if not osutils.has_symlinks():
 
1306
            # PlatformDeficiency / TestSkipped
 
1307
            raise TestSkipped("No symlink support")
1756
1308
        state, entry = self.get_state_with_a()
1757
1309
        state.save()
1758
1310
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1766
1318
                                          stat_value=stat_value)
1767
1319
        self.assertEqual('target', link_or_sha1)
1768
1320
        self.assertEqual([('read_link', 'a', '')], state._log)
1769
 
        # Dirblock is not updated (the link is too new)
1770
 
        self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
 
1321
        # Dirblock is updated
 
1322
        self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
1771
1323
                         entry[1])
1772
1324
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1773
1325
                         state._dirblock_state)
1777
1329
                                          stat_value=stat_value)
1778
1330
        self.assertEqual('target', link_or_sha1)
1779
1331
        self.assertEqual([('read_link', 'a', ''),
1780
 
                          ('read_link', 'a', ''),
 
1332
                          ('read_link', 'a', 'target'),
1781
1333
                         ], state._log)
1782
 
        self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1783
 
                         entry[1])
1784
1334
        state.adjust_time(+20) # Skip into the future, all files look old
1785
1335
        link_or_sha1 = state.update_entry(entry, abspath='a',
1786
1336
                                          stat_value=stat_value)
1787
1337
        self.assertEqual('target', link_or_sha1)
1788
 
        # We need to re-read the link because only now can we cache it
1789
 
        self.assertEqual([('read_link', 'a', ''),
1790
 
                          ('read_link', 'a', ''),
1791
 
                          ('read_link', 'a', ''),
1792
 
                         ], state._log)
1793
 
        self.assertEqual([('l', 'target', 6, False, packed_stat)],
1794
 
                         entry[1])
1795
 
 
1796
 
        # Another call won't re-read the link
1797
 
        self.assertEqual([('read_link', 'a', ''),
1798
 
                          ('read_link', 'a', ''),
1799
 
                          ('read_link', 'a', ''),
1800
 
                         ], state._log)
1801
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1802
 
                                          stat_value=stat_value)
1803
 
        self.assertEqual('target', link_or_sha1)
1804
 
        self.assertEqual([('l', 'target', 6, False, packed_stat)],
1805
 
                         entry[1])
1806
 
 
1807
 
    def do_update_entry(self, state, entry, abspath):
1808
 
        stat_value = os.lstat(abspath)
1809
 
        return state.update_entry(entry, abspath, stat_value)
 
1338
        # There should not be a new read_link call.
 
1339
        # (this is a weak assertion, because read_link is fairly inexpensive,
 
1340
        # versus the number of symlinks that we would have)
 
1341
        self.assertEqual([('read_link', 'a', ''),
 
1342
                          ('read_link', 'a', 'target'),
 
1343
                         ], state._log)
1810
1344
 
1811
1345
    def test_update_entry_dir(self):
1812
1346
        state, entry = self.get_state_with_a()
1813
1347
        self.build_tree(['a/'])
1814
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1815
 
 
1816
 
    def test_update_entry_dir_unchanged(self):
1817
 
        state, entry = self.get_state_with_a()
1818
 
        self.build_tree(['a/'])
1819
 
        state.adjust_time(+20)
1820
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1821
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1822
 
                         state._dirblock_state)
1823
 
        state.save()
1824
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1825
 
                         state._dirblock_state)
1826
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1827
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1828
 
                         state._dirblock_state)
1829
 
 
1830
 
    def test_update_entry_file_unchanged(self):
1831
 
        state, entry = self.get_state_with_a()
1832
 
        self.build_tree(['a'])
1833
 
        sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1834
 
        state.adjust_time(+20)
1835
 
        self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1836
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1837
 
                         state._dirblock_state)
1838
 
        state.save()
1839
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1840
 
                         state._dirblock_state)
1841
 
        self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1842
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1843
 
                         state._dirblock_state)
 
1348
        self.assertIs(None, state.update_entry(entry, 'a'))
1844
1349
 
1845
1350
    def create_and_test_file(self, state, entry):
1846
1351
        """Create a file at 'a' and verify the state finds it.
1852
1357
        stat_value = os.lstat('a')
1853
1358
        packed_stat = dirstate.pack_stat(stat_value)
1854
1359
 
1855
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
 
1360
        link_or_sha1 = state.update_entry(entry, abspath='a')
1856
1361
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1857
1362
                         link_or_sha1)
1858
1363
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1869
1374
        stat_value = os.lstat('a')
1870
1375
        packed_stat = dirstate.pack_stat(stat_value)
1871
1376
 
1872
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
 
1377
        link_or_sha1 = state.update_entry(entry, abspath='a')
1873
1378
        self.assertIs(None, link_or_sha1)
1874
1379
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1875
1380
 
1890
1395
        stat_value = os.lstat('a')
1891
1396
        packed_stat = dirstate.pack_stat(stat_value)
1892
1397
 
1893
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
 
1398
        link_or_sha1 = state.update_entry(entry, abspath='a')
1894
1399
        self.assertEqual('path/to/foo', link_or_sha1)
1895
1400
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1896
1401
                         entry[1])
1897
1402
        return packed_stat
1898
1403
 
 
1404
    def test_update_missing_file(self):
 
1405
        state, entry = self.get_state_with_a()
 
1406
        packed_stat = self.create_and_test_file(state, entry)
 
1407
        # Now if we delete the file, update_entry should recover and
 
1408
        # return None.
 
1409
        os.remove('a')
 
1410
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1411
        # And the record shouldn't be changed.
 
1412
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
 
1413
        self.assertEqual([('f', digest, 14, False, packed_stat)],
 
1414
                         entry[1])
 
1415
 
 
1416
    def test_update_missing_dir(self):
 
1417
        state, entry = self.get_state_with_a()
 
1418
        packed_stat = self.create_and_test_dir(state, entry)
 
1419
        # Now if we delete the directory, update_entry should recover and
 
1420
        # return None.
 
1421
        os.rmdir('a')
 
1422
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1423
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
 
1424
 
 
1425
    def test_update_missing_symlink(self):
 
1426
        if not osutils.has_symlinks():
 
1427
            # PlatformDeficiency / TestSkipped
 
1428
            raise TestSkipped("No symlink support")
 
1429
        state, entry = self.get_state_with_a()
 
1430
        packed_stat = self.create_and_test_symlink(state, entry)
 
1431
        os.remove('a')
 
1432
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1433
        # And the record shouldn't be changed.
 
1434
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
 
1435
                         entry[1])
 
1436
 
1899
1437
    def test_update_file_to_dir(self):
1900
1438
        """If a file changes to a directory we return None for the sha.
1901
1439
        We also update the inventory record.
1902
1440
        """
1903
1441
        state, entry = self.get_state_with_a()
1904
 
        # The file sha1 won't be cached unless the file is old
1905
 
        state.adjust_time(+10)
1906
1442
        self.create_and_test_file(state, entry)
1907
1443
        os.remove('a')
1908
1444
        self.create_and_test_dir(state, entry)
1909
1445
 
1910
1446
    def test_update_file_to_symlink(self):
1911
1447
        """File becomes a symlink"""
1912
 
        self.requireFeature(SymlinkFeature)
 
1448
        if not osutils.has_symlinks():
 
1449
            # PlatformDeficiency / TestSkipped
 
1450
            raise TestSkipped("No symlink support")
1913
1451
        state, entry = self.get_state_with_a()
1914
 
        # The file sha1 won't be cached unless the file is old
1915
 
        state.adjust_time(+10)
1916
1452
        self.create_and_test_file(state, entry)
1917
1453
        os.remove('a')
1918
1454
        self.create_and_test_symlink(state, entry)
1920
1456
    def test_update_dir_to_file(self):
1921
1457
        """Directory becoming a file updates the entry."""
1922
1458
        state, entry = self.get_state_with_a()
1923
 
        # The file sha1 won't be cached unless the file is old
1924
 
        state.adjust_time(+10)
1925
1459
        self.create_and_test_dir(state, entry)
1926
1460
        os.rmdir('a')
1927
1461
        self.create_and_test_file(state, entry)
1928
1462
 
1929
1463
    def test_update_dir_to_symlink(self):
1930
1464
        """Directory becomes a symlink"""
1931
 
        self.requireFeature(SymlinkFeature)
 
1465
        if not osutils.has_symlinks():
 
1466
            # PlatformDeficiency / TestSkipped
 
1467
            raise TestSkipped("No symlink support")
1932
1468
        state, entry = self.get_state_with_a()
1933
 
        # The symlink target won't be cached if it isn't old
1934
 
        state.adjust_time(+10)
1935
1469
        self.create_and_test_dir(state, entry)
1936
1470
        os.rmdir('a')
1937
1471
        self.create_and_test_symlink(state, entry)
1938
1472
 
1939
1473
    def test_update_symlink_to_file(self):
1940
1474
        """Symlink becomes a file"""
1941
 
        self.requireFeature(SymlinkFeature)
 
1475
        if not has_symlinks():
 
1476
            raise TestSkipped("No symlink support")
1942
1477
        state, entry = self.get_state_with_a()
1943
 
        # The symlink and file info won't be cached unless old
1944
 
        state.adjust_time(+10)
1945
1478
        self.create_and_test_symlink(state, entry)
1946
1479
        os.remove('a')
1947
1480
        self.create_and_test_file(state, entry)
1948
1481
 
1949
1482
    def test_update_symlink_to_dir(self):
1950
1483
        """Symlink becomes a directory"""
1951
 
        self.requireFeature(SymlinkFeature)
 
1484
        if not has_symlinks():
 
1485
            raise TestSkipped("No symlink support")
1952
1486
        state, entry = self.get_state_with_a()
1953
 
        # The symlink target won't be cached if it isn't old
1954
 
        state.adjust_time(+10)
1955
1487
        self.create_and_test_symlink(state, entry)
1956
1488
        os.remove('a')
1957
1489
        self.create_and_test_dir(state, entry)
1971
1503
        packed_stat = dirstate.pack_stat(stat_value)
1972
1504
 
1973
1505
        state.adjust_time(-10) # Make sure everything is new
 
1506
        # Make sure it wants to kkkkkkkk
1974
1507
        state.update_entry(entry, abspath='a', stat_value=stat_value)
1975
1508
 
1976
1509
        # The row is updated, but the executable bit stays set.
1977
 
        self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1978
 
                         entry[1])
1979
 
 
1980
 
        # Make the disk object look old enough to cache
1981
 
        state.adjust_time(+20)
1982
1510
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1983
 
        state.update_entry(entry, abspath='a', stat_value=stat_value)
1984
1511
        self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1985
1512
 
1986
1513
 
2036
1563
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2037
1564
 
2038
1565
 
2039
 
class TestBisect(TestCaseWithDirState):
 
1566
class TestBisect(TestCaseWithTransport):
2040
1567
    """Test the ability to bisect into the disk format."""
2041
1568
 
 
1569
    def create_basic_dirstate(self):
 
1570
        """Create a dirstate with a few files and directories.
 
1571
 
 
1572
            a
 
1573
            b/
 
1574
              c
 
1575
              d/
 
1576
                e
 
1577
            f
 
1578
        """
 
1579
        tree = self.make_branch_and_tree('tree')
 
1580
        paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
 
1581
        file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
 
1582
        self.build_tree(['tree/' + p for p in paths])
 
1583
        tree.set_root_id('TREE_ROOT')
 
1584
        tree.add([p.rstrip('/') for p in paths], file_ids)
 
1585
        tree.commit('initial', rev_id='rev-1')
 
1586
        revision_id = 'rev-1'
 
1587
        # a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
 
1588
        t = self.get_transport().clone('tree')
 
1589
        a_text = t.get_bytes('a')
 
1590
        a_sha = osutils.sha_string(a_text)
 
1591
        a_len = len(a_text)
 
1592
        # b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
 
1593
        # c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
 
1594
        c_text = t.get_bytes('b/c')
 
1595
        c_sha = osutils.sha_string(c_text)
 
1596
        c_len = len(c_text)
 
1597
        # d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
 
1598
        # e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
 
1599
        e_text = t.get_bytes('b/d/e')
 
1600
        e_sha = osutils.sha_string(e_text)
 
1601
        e_len = len(e_text)
 
1602
        # f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
 
1603
        f_text = t.get_bytes('f')
 
1604
        f_sha = osutils.sha_string(f_text)
 
1605
        f_len = len(f_text)
 
1606
        null_stat = dirstate.DirState.NULLSTAT
 
1607
        expected = {
 
1608
            '':(('', '', 'TREE_ROOT'), [
 
1609
                  ('d', '', 0, False, null_stat),
 
1610
                  ('d', '', 0, False, revision_id),
 
1611
                ]),
 
1612
            'a':(('', 'a', 'a-id'), [
 
1613
                   ('f', '', 0, False, null_stat),
 
1614
                   ('f', a_sha, a_len, False, revision_id),
 
1615
                 ]),
 
1616
            'b':(('', 'b', 'b-id'), [
 
1617
                  ('d', '', 0, False, null_stat),
 
1618
                  ('d', '', 0, False, revision_id),
 
1619
                 ]),
 
1620
            'b/c':(('b', 'c', 'c-id'), [
 
1621
                    ('f', '', 0, False, null_stat),
 
1622
                    ('f', c_sha, c_len, False, revision_id),
 
1623
                   ]),
 
1624
            'b/d':(('b', 'd', 'd-id'), [
 
1625
                    ('d', '', 0, False, null_stat),
 
1626
                    ('d', '', 0, False, revision_id),
 
1627
                   ]),
 
1628
            'b/d/e':(('b/d', 'e', 'e-id'), [
 
1629
                      ('f', '', 0, False, null_stat),
 
1630
                      ('f', e_sha, e_len, False, revision_id),
 
1631
                     ]),
 
1632
            'f':(('', 'f', 'f-id'), [
 
1633
                  ('f', '', 0, False, null_stat),
 
1634
                  ('f', f_sha, f_len, False, revision_id),
 
1635
                 ]),
 
1636
        }
 
1637
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
1638
        try:
 
1639
            state.save()
 
1640
        finally:
 
1641
            state.unlock()
 
1642
        # Use a different object, to make sure nothing is pre-cached in memory.
 
1643
        state = dirstate.DirState.on_file('dirstate')
 
1644
        state.lock_read()
 
1645
        self.addCleanup(state.unlock)
 
1646
        self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
 
1647
                         state._dirblock_state)
 
1648
        # This is code is only really tested if we actually have to make more
 
1649
        # than one read, so set the page size to something smaller.
 
1650
        # We want it to contain about 2.2 records, so that we have a couple
 
1651
        # records that we can read per attempt
 
1652
        state._bisect_page_size = 200
 
1653
        return tree, state, expected
 
1654
 
 
1655
    def create_duplicated_dirstate(self):
 
1656
        """Create a dirstate with a deleted and added entries.
 
1657
 
 
1658
        This grabs a basic_dirstate, and then removes and re adds every entry
 
1659
        with a new file id.
 
1660
        """
 
1661
        tree, state, expected = self.create_basic_dirstate()
 
1662
        # Now we will just remove and add every file so we get an extra entry
 
1663
        # per entry. Unversion in reverse order so we handle subdirs
 
1664
        tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
 
1665
        tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
 
1666
                 ['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
 
1667
 
 
1668
        # Update the expected dictionary.
 
1669
        for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
 
1670
            orig = expected[path]
 
1671
            path2 = path + '2'
 
1672
            # This record was deleted in the current tree
 
1673
            expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
 
1674
                                        orig[1][1]])
 
1675
            new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
 
1676
            # And didn't exist in the basis tree
 
1677
            expected[path2] = (new_key, [orig[1][0],
 
1678
                                         dirstate.DirState.NULL_PARENT_DETAILS])
 
1679
 
 
1680
        # We will replace the 'dirstate' file underneath 'state', but that is
 
1681
        # okay as lock as we unlock 'state' first.
 
1682
        state.unlock()
 
1683
        try:
 
1684
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
 
1685
            try:
 
1686
                new_state.save()
 
1687
            finally:
 
1688
                new_state.unlock()
 
1689
        finally:
 
1690
            # But we need to leave state in a read-lock because we already have
 
1691
            # a cleanup scheduled
 
1692
            state.lock_read()
 
1693
        return tree, state, expected
 
1694
 
 
1695
    def create_renamed_dirstate(self):
 
1696
        """Create a dirstate with a few internal renames.
 
1697
 
 
1698
        This takes the basic dirstate, and moves the paths around.
 
1699
        """
 
1700
        tree, state, expected = self.create_basic_dirstate()
 
1701
        # Rename a file
 
1702
        tree.rename_one('a', 'b/g')
 
1703
        # And a directory
 
1704
        tree.rename_one('b/d', 'h')
 
1705
 
 
1706
        old_a = expected['a']
 
1707
        expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
 
1708
        expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
 
1709
                                                ('r', 'a', 0, False, '')])
 
1710
        old_d = expected['b/d']
 
1711
        expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
 
1712
        expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
 
1713
                                             ('r', 'b/d', 0, False, '')])
 
1714
 
 
1715
        old_e = expected['b/d/e']
 
1716
        expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
 
1717
                             old_e[1][1]])
 
1718
        expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
 
1719
                                                ('r', 'b/d/e', 0, False, '')])
 
1720
 
 
1721
        state.unlock()
 
1722
        try:
 
1723
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
 
1724
            try:
 
1725
                new_state.save()
 
1726
            finally:
 
1727
                new_state.unlock()
 
1728
        finally:
 
1729
            state.lock_read()
 
1730
        return tree, state, expected
 
1731
 
2042
1732
    def assertBisect(self, expected_map, map_keys, state, paths):
2043
1733
        """Assert that bisecting for paths returns the right result.
2044
1734
 
2049
1739
                      (dir, name) tuples, and sorted according to how _bisect
2050
1740
                      requires.
2051
1741
        """
2052
 
        result = state._bisect(paths)
 
1742
        dir_names = sorted(osutils.split(p) for p in paths)
 
1743
        result = state._bisect(dir_names)
2053
1744
        # For now, results are just returned in whatever order we read them.
2054
1745
        # We could sort by (dir, name, file_id) or something like that, but in
2055
1746
        # the end it would still be fairly arbitrary, and we don't want the
2056
1747
        # extra overhead if we can avoid it. So sort everything to make sure
2057
1748
        # equality is true
2058
 
        self.assertEqual(len(map_keys), len(paths))
 
1749
        assert len(map_keys) == len(dir_names)
2059
1750
        expected = {}
2060
 
        for path, keys in zip(paths, map_keys):
 
1751
        for dir_name, keys in zip(dir_names, map_keys):
2061
1752
            if keys is None:
2062
1753
                # This should not be present in the output
2063
1754
                continue
2064
 
            expected[path] = sorted(expected_map[k] for k in keys)
 
1755
            expected[dir_name] = sorted(expected_map[k] for k in keys)
2065
1756
 
2066
 
        # The returned values are just arranged randomly based on when they
2067
 
        # were read, for testing, make sure it is properly sorted.
2068
 
        for path in result:
2069
 
            result[path].sort()
 
1757
        for dir_name in result:
 
1758
            result[dir_name].sort()
2070
1759
 
2071
1760
        self.assertEqual(expected, result)
2072
1761
 
2080
1769
        :param paths: A list of directories
2081
1770
        """
2082
1771
        result = state._bisect_dirblocks(paths)
2083
 
        self.assertEqual(len(map_keys), len(paths))
 
1772
        assert len(map_keys) == len(paths)
 
1773
 
2084
1774
        expected = {}
2085
1775
        for path, keys in zip(paths, map_keys):
2086
1776
            if keys is None:
2108
1798
            dir_name_id, trees_info = entry
2109
1799
            expected[dir_name_id] = trees_info
2110
1800
 
2111
 
        result = state._bisect_recursive(paths)
 
1801
        dir_names = sorted(osutils.split(p) for p in paths)
 
1802
        result = state._bisect_recursive(dir_names)
2112
1803
 
2113
1804
        self.assertEqual(expected, result)
2114
1805
 
2123
1814
        self.assertBisect(expected, [['b/c']], state, ['b/c'])
2124
1815
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
2125
1816
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2126
 
        self.assertBisect(expected, [['b-c']], state, ['b-c'])
2127
1817
        self.assertBisect(expected, [['f']], state, ['f'])
2128
1818
 
2129
1819
    def test_bisect_multi(self):
2132
1822
        # Bisect should be capable of finding multiple entries at the same time
2133
1823
        self.assertBisect(expected, [['a'], ['b'], ['f']],
2134
1824
                          state, ['a', 'b', 'f'])
 
1825
        # ('', 'f') sorts before the others
2135
1826
        self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
2136
 
                          state, ['f', 'b/d', 'b/d/e'])
2137
 
        self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
2138
 
                          state, ['b', 'b-c', 'b/c'])
 
1827
                          state, ['b/d', 'b/d/e', 'f'])
2139
1828
 
2140
1829
    def test_bisect_one_page(self):
2141
1830
        """Test bisect when there is only 1 page to read"""
2147
1836
        self.assertBisect(expected,[['b/c']], state, ['b/c'])
2148
1837
        self.assertBisect(expected,[['b/d']], state, ['b/d'])
2149
1838
        self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
2150
 
        self.assertBisect(expected,[['b-c']], state, ['b-c'])
2151
1839
        self.assertBisect(expected,[['f']], state, ['f'])
2152
1840
        self.assertBisect(expected,[['a'], ['b'], ['f']],
2153
1841
                          state, ['a', 'b', 'f'])
2154
 
        self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
 
1842
        # ('', 'f') sorts before the others
 
1843
        self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
2155
1844
                          state, ['b/d', 'b/d/e', 'f'])
2156
 
        self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
2157
 
                          state, ['b', 'b/c', 'b-c'])
2158
1845
 
2159
1846
    def test_bisect_duplicate_paths(self):
2160
1847
        """When bisecting for a path, handle multiple entries."""
2168
1855
        self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
2169
1856
        self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
2170
1857
                          state, ['b/d/e'])
2171
 
        self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
2172
1858
        self.assertBisect(expected, [['f', 'f2']], state, ['f'])
2173
1859
 
2174
1860
    def test_bisect_page_size_too_small(self):
2181
1867
        self.assertBisect(expected, [['b/c']], state, ['b/c'])
2182
1868
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
2183
1869
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2184
 
        self.assertBisect(expected, [['b-c']], state, ['b-c'])
2185
1870
        self.assertBisect(expected, [['f']], state, ['f'])
2186
1871
 
2187
1872
    def test_bisect_missing(self):
2190
1875
        self.assertBisect(expected, [None], state, ['foo'])
2191
1876
        self.assertBisect(expected, [None], state, ['b/foo'])
2192
1877
        self.assertBisect(expected, [None], state, ['bar/foo'])
2193
 
        self.assertBisect(expected, [None], state, ['b-c/foo'])
2194
1878
 
2195
1879
        self.assertBisect(expected, [['a'], None, ['b/d']],
2196
1880
                          state, ['a', 'foo', 'b/d'])
2212
1896
    def test_bisect_dirblocks(self):
2213
1897
        tree, state, expected = self.create_duplicated_dirstate()
2214
1898
        self.assertBisectDirBlocks(expected,
2215
 
            [['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2216
 
            state, [''])
 
1899
            [['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
2217
1900
        self.assertBisectDirBlocks(expected,
2218
1901
            [['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2219
1902
        self.assertBisectDirBlocks(expected,
2220
1903
            [['b/d/e', 'b/d/e2']], state, ['b/d'])
2221
1904
        self.assertBisectDirBlocks(expected,
2222
 
            [['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
 
1905
            [['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
2223
1906
             ['b/c', 'b/c2', 'b/d', 'b/d2'],
2224
1907
             ['b/d/e', 'b/d/e2'],
2225
1908
            ], state, ['', 'b', 'b/d'])
2240
1923
        self.assertBisectRecursive(expected, ['a'], state, ['a'])
2241
1924
        self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2242
1925
        self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2243
 
        self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2244
1926
        self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2245
1927
                                   state, ['b/d'])
2246
1928
        self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2247
1929
                                   state, ['b'])
2248
 
        self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
 
1930
        self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
2249
1931
                                              'b/d', 'b/d/e'],
2250
1932
                                   state, [''])
2251
1933
 
2275
1957
                                   state, ['b'])
2276
1958
 
2277
1959
 
2278
 
class TestDirstateValidation(TestCaseWithDirState):
2279
 
 
2280
 
    def test_validate_correct_dirstate(self):
2281
 
        state = self.create_complex_dirstate()
2282
 
        state._validate()
2283
 
        state.unlock()
2284
 
        # and make sure we can also validate with a read lock
2285
 
        state.lock_read()
2286
 
        try:
2287
 
            state._validate()
2288
 
        finally:
2289
 
            state.unlock()
2290
 
 
2291
 
    def test_dirblock_not_sorted(self):
2292
 
        tree, state, expected = self.create_renamed_dirstate()
2293
 
        state._read_dirblocks_if_needed()
2294
 
        last_dirblock = state._dirblocks[-1]
2295
 
        # we're appending to the dirblock, but this name comes before some of
2296
 
        # the existing names; that's wrong
2297
 
        last_dirblock[1].append(
2298
 
            (('h', 'aaaa', 'a-id'),
2299
 
             [('a', '', 0, False, ''),
2300
 
              ('a', '', 0, False, '')]))
2301
 
        e = self.assertRaises(AssertionError,
2302
 
            state._validate)
2303
 
        self.assertContainsRe(str(e), 'not sorted')
2304
 
 
2305
 
    def test_dirblock_name_mismatch(self):
2306
 
        tree, state, expected = self.create_renamed_dirstate()
2307
 
        state._read_dirblocks_if_needed()
2308
 
        last_dirblock = state._dirblocks[-1]
2309
 
        # add an entry with the wrong directory name
2310
 
        last_dirblock[1].append(
2311
 
            (('', 'z', 'a-id'),
2312
 
             [('a', '', 0, False, ''),
2313
 
              ('a', '', 0, False, '')]))
2314
 
        e = self.assertRaises(AssertionError,
2315
 
            state._validate)
2316
 
        self.assertContainsRe(str(e),
2317
 
            "doesn't match directory name")
2318
 
 
2319
 
    def test_dirblock_missing_rename(self):
2320
 
        tree, state, expected = self.create_renamed_dirstate()
2321
 
        state._read_dirblocks_if_needed()
2322
 
        last_dirblock = state._dirblocks[-1]
2323
 
        # make another entry for a-id, without a correct 'r' pointer to
2324
 
        # the real occurrence in the working tree
2325
 
        last_dirblock[1].append(
2326
 
            (('h', 'z', 'a-id'),
2327
 
             [('a', '', 0, False, ''),
2328
 
              ('a', '', 0, False, '')]))
2329
 
        e = self.assertRaises(AssertionError,
2330
 
            state._validate)
2331
 
        self.assertContainsRe(str(e),
2332
 
            'file a-id is absent in row')
2333
 
 
2334
 
 
2335
 
class TestDirstateTreeReference(TestCaseWithDirState):
2336
 
 
2337
 
    def test_reference_revision_is_none(self):
2338
 
        tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2339
 
        subtree = self.make_branch_and_tree('tree/subtree',
2340
 
                            format='dirstate-with-subtree')
2341
 
        subtree.set_root_id('subtree')
2342
 
        tree.add_reference(subtree)
2343
 
        tree.add('subtree')
2344
 
        state = dirstate.DirState.from_tree(tree, 'dirstate')
2345
 
        key = ('', 'subtree', 'subtree')
2346
 
        expected = ('', [(key,
2347
 
            [('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2348
 
 
2349
 
        try:
2350
 
            self.assertEqual(expected, state._find_block(key))
2351
 
        finally:
2352
 
            state.unlock()
2353
 
 
2354
 
 
2355
 
class TestDiscardMergeParents(TestCaseWithDirState):
2356
 
 
2357
 
    def test_discard_no_parents(self):
2358
 
        # This should be a no-op
2359
 
        state = self.create_empty_dirstate()
2360
 
        self.addCleanup(state.unlock)
2361
 
        state._discard_merge_parents()
2362
 
        state._validate()
2363
 
 
2364
 
    def test_discard_one_parent(self):
2365
 
        # No-op
2366
 
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2367
 
        root_entry_direntry = ('', '', 'a-root-value'), [
2368
 
            ('d', '', 0, False, packed_stat),
2369
 
            ('d', '', 0, False, packed_stat),
2370
 
            ]
2371
 
        dirblocks = []
2372
 
        dirblocks.append(('', [root_entry_direntry]))
2373
 
        dirblocks.append(('', []))
2374
 
 
2375
 
        state = self.create_empty_dirstate()
2376
 
        self.addCleanup(state.unlock)
2377
 
        state._set_data(['parent-id'], dirblocks[:])
2378
 
        state._validate()
2379
 
 
2380
 
        state._discard_merge_parents()
2381
 
        state._validate()
2382
 
        self.assertEqual(dirblocks, state._dirblocks)
2383
 
 
2384
 
    def test_discard_simple(self):
2385
 
        # No-op
2386
 
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2387
 
        root_entry_direntry = ('', '', 'a-root-value'), [
2388
 
            ('d', '', 0, False, packed_stat),
2389
 
            ('d', '', 0, False, packed_stat),
2390
 
            ('d', '', 0, False, packed_stat),
2391
 
            ]
2392
 
        expected_root_entry_direntry = ('', '', 'a-root-value'), [
2393
 
            ('d', '', 0, False, packed_stat),
2394
 
            ('d', '', 0, False, packed_stat),
2395
 
            ]
2396
 
        dirblocks = []
2397
 
        dirblocks.append(('', [root_entry_direntry]))
2398
 
        dirblocks.append(('', []))
2399
 
 
2400
 
        state = self.create_empty_dirstate()
2401
 
        self.addCleanup(state.unlock)
2402
 
        state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2403
 
        state._validate()
2404
 
 
2405
 
        # This should strip of the extra column
2406
 
        state._discard_merge_parents()
2407
 
        state._validate()
2408
 
        expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2409
 
        self.assertEqual(expected_dirblocks, state._dirblocks)
2410
 
 
2411
 
    def test_discard_absent(self):
2412
 
        """If entries are only in a merge, discard should remove the entries"""
2413
 
        null_stat = dirstate.DirState.NULLSTAT
2414
 
        present_dir = ('d', '', 0, False, null_stat)
2415
 
        present_file = ('f', '', 0, False, null_stat)
2416
 
        absent = dirstate.DirState.NULL_PARENT_DETAILS
2417
 
        root_key = ('', '', 'a-root-value')
2418
 
        file_in_root_key = ('', 'file-in-root', 'a-file-id')
2419
 
        file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2420
 
        dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2421
 
                     ('', [(file_in_merged_key,
2422
 
                            [absent, absent, present_file]),
2423
 
                           (file_in_root_key,
2424
 
                            [present_file, present_file, present_file]),
2425
 
                          ]),
2426
 
                    ]
2427
 
 
2428
 
        state = self.create_empty_dirstate()
2429
 
        self.addCleanup(state.unlock)
2430
 
        state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2431
 
        state._validate()
2432
 
 
2433
 
        exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2434
 
                         ('', [(file_in_root_key,
2435
 
                                [present_file, present_file]),
2436
 
                              ]),
2437
 
                        ]
2438
 
        state._discard_merge_parents()
2439
 
        state._validate()
2440
 
        self.assertEqual(exp_dirblocks, state._dirblocks)
2441
 
 
2442
 
    def test_discard_renamed(self):
2443
 
        null_stat = dirstate.DirState.NULLSTAT
2444
 
        present_dir = ('d', '', 0, False, null_stat)
2445
 
        present_file = ('f', '', 0, False, null_stat)
2446
 
        absent = dirstate.DirState.NULL_PARENT_DETAILS
2447
 
        root_key = ('', '', 'a-root-value')
2448
 
        file_in_root_key = ('', 'file-in-root', 'a-file-id')
2449
 
        # Renamed relative to parent
2450
 
        file_rename_s_key = ('', 'file-s', 'b-file-id')
2451
 
        file_rename_t_key = ('', 'file-t', 'b-file-id')
2452
 
        # And one that is renamed between the parents, but absent in this
2453
 
        key_in_1 = ('', 'file-in-1', 'c-file-id')
2454
 
        key_in_2 = ('', 'file-in-2', 'c-file-id')
2455
 
 
2456
 
        dirblocks = [
2457
 
            ('', [(root_key, [present_dir, present_dir, present_dir])]),
2458
 
            ('', [(key_in_1,
2459
 
                   [absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2460
 
                  (key_in_2,
2461
 
                   [absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2462
 
                  (file_in_root_key,
2463
 
                   [present_file, present_file, present_file]),
2464
 
                  (file_rename_s_key,
2465
 
                   [('r', 'file-t', 'b-file-id'), absent, present_file]),
2466
 
                  (file_rename_t_key,
2467
 
                   [present_file, absent, ('r', 'file-s', 'b-file-id')]),
2468
 
                 ]),
2469
 
        ]
2470
 
        exp_dirblocks = [
2471
 
            ('', [(root_key, [present_dir, present_dir])]),
2472
 
            ('', [(key_in_1, [absent, present_file]),
2473
 
                  (file_in_root_key, [present_file, present_file]),
2474
 
                  (file_rename_t_key, [present_file, absent]),
2475
 
                 ]),
2476
 
        ]
2477
 
        state = self.create_empty_dirstate()
2478
 
        self.addCleanup(state.unlock)
2479
 
        state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2480
 
        state._validate()
2481
 
 
2482
 
        state._discard_merge_parents()
2483
 
        state._validate()
2484
 
        self.assertEqual(exp_dirblocks, state._dirblocks)
2485
 
 
2486
 
    def test_discard_all_subdir(self):
2487
 
        null_stat = dirstate.DirState.NULLSTAT
2488
 
        present_dir = ('d', '', 0, False, null_stat)
2489
 
        present_file = ('f', '', 0, False, null_stat)
2490
 
        absent = dirstate.DirState.NULL_PARENT_DETAILS
2491
 
        root_key = ('', '', 'a-root-value')
2492
 
        subdir_key = ('', 'sub', 'dir-id')
2493
 
        child1_key = ('sub', 'child1', 'child1-id')
2494
 
        child2_key = ('sub', 'child2', 'child2-id')
2495
 
        child3_key = ('sub', 'child3', 'child3-id')
2496
 
 
2497
 
        dirblocks = [
2498
 
            ('', [(root_key, [present_dir, present_dir, present_dir])]),
2499
 
            ('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2500
 
            ('sub', [(child1_key, [absent, absent, present_file]),
2501
 
                     (child2_key, [absent, absent, present_file]),
2502
 
                     (child3_key, [absent, absent, present_file]),
2503
 
                    ]),
2504
 
        ]
2505
 
        exp_dirblocks = [
2506
 
            ('', [(root_key, [present_dir, present_dir])]),
2507
 
            ('', [(subdir_key, [present_dir, present_dir])]),
2508
 
            ('sub', []),
2509
 
        ]
2510
 
        state = self.create_empty_dirstate()
2511
 
        self.addCleanup(state.unlock)
2512
 
        state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2513
 
        state._validate()
2514
 
 
2515
 
        state._discard_merge_parents()
2516
 
        state._validate()
2517
 
        self.assertEqual(exp_dirblocks, state._dirblocks)
2518
 
 
2519
 
 
2520
 
class Test_InvEntryToDetails(TestCaseWithDirState):
2521
 
 
2522
 
    def assertDetails(self, expected, inv_entry):
2523
 
        details = dirstate.DirState._inv_entry_to_details(inv_entry)
2524
 
        self.assertEqual(expected, details)
2525
 
        # details should always allow join() and always be a plain str when
2526
 
        # finished
2527
 
        (minikind, fingerprint, size, executable, tree_data) = details
2528
 
        self.assertIsInstance(minikind, str)
2529
 
        self.assertIsInstance(fingerprint, str)
2530
 
        self.assertIsInstance(tree_data, str)
2531
 
 
2532
 
    def test_unicode_symlink(self):
2533
 
        # In general, the code base doesn't support a target that contains
2534
 
        # non-ascii characters. So we just assert tha 
2535
 
        inv_entry = inventory.InventoryLink('link-file-id', 'name',
2536
 
                                            'link-parent-id')
2537
 
        inv_entry.revision = 'link-revision-id'
2538
 
        inv_entry.symlink_target = u'link-target'
2539
 
        details = self.assertDetails(('l', 'link-target', 0, False,
2540
 
                                      'link-revision-id'), inv_entry)
 
1960
class TestBisectDirblock(TestCase):
 
1961
    """Test that bisect_dirblock() returns the expected values.
 
1962
 
 
1963
    bisect_dirblock is intended to work like bisect.bisect_left() except it
 
1964
    knows it is working on dirblocks and that dirblocks are sorted by ('path',
 
1965
    'to', 'foo') chunks rather than by raw 'path/to/foo'.
 
1966
    """
 
1967
 
 
1968
    def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
 
1969
        """Assert that bisect_split works like bisect_left on the split paths.
 
1970
 
 
1971
        :param dirblocks: A list of (path, [info]) pairs.
 
1972
        :param split_dirblocks: A list of ((split, path), [info]) pairs.
 
1973
        :param path: The path we are indexing.
 
1974
 
 
1975
        All other arguments will be passed along.
 
1976
        """
 
1977
        bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
 
1978
                                                 *args, **kwargs)
 
1979
        split_dirblock = (path.split('/'), [])
 
1980
        bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
 
1981
                                             *args)
 
1982
        self.assertEqual(bisect_left_idx, bisect_split_idx,
 
1983
                         'bisect_split disagreed. %s != %s'
 
1984
                         ' for key %s'
 
1985
                         % (bisect_left_idx, bisect_split_idx, path)
 
1986
                         )
 
1987
 
 
1988
    def paths_to_dirblocks(self, paths):
 
1989
        """Convert a list of paths into dirblock form.
 
1990
 
 
1991
        Also, ensure that the paths are in proper sorted order.
 
1992
        """
 
1993
        dirblocks = [(path, []) for path in paths]
 
1994
        split_dirblocks = [(path.split('/'), []) for path in paths]
 
1995
        self.assertEqual(sorted(split_dirblocks), split_dirblocks)
 
1996
        return dirblocks, split_dirblocks
 
1997
 
 
1998
    def test_simple(self):
 
1999
        """In the simple case it works just like bisect_left"""
 
2000
        paths = ['', 'a', 'b', 'c', 'd']
 
2001
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
2002
        for path in paths:
 
2003
            self.assertBisect(dirblocks, split_dirblocks, path)
 
2004
        self.assertBisect(dirblocks, split_dirblocks, '_')
 
2005
        self.assertBisect(dirblocks, split_dirblocks, 'aa')
 
2006
        self.assertBisect(dirblocks, split_dirblocks, 'bb')
 
2007
        self.assertBisect(dirblocks, split_dirblocks, 'cc')
 
2008
        self.assertBisect(dirblocks, split_dirblocks, 'dd')
 
2009
        self.assertBisect(dirblocks, split_dirblocks, 'a/a')
 
2010
        self.assertBisect(dirblocks, split_dirblocks, 'b/b')
 
2011
        self.assertBisect(dirblocks, split_dirblocks, 'c/c')
 
2012
        self.assertBisect(dirblocks, split_dirblocks, 'd/d')
 
2013
 
 
2014
    def test_involved(self):
 
2015
        """This is where bisect_left diverges slightly."""
 
2016
        paths = ['', 'a',
 
2017
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
 
2018
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
 
2019
                 'a-a', 'a-z',
 
2020
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
 
2021
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
 
2022
                 'z-a', 'z-z',
 
2023
                ]
 
2024
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
2025
        for path in paths:
 
2026
            self.assertBisect(dirblocks, split_dirblocks, path)
 
2027
 
 
2028
    def test_involved_cached(self):
 
2029
        """This is where bisect_left diverges slightly."""
 
2030
        paths = ['', 'a',
 
2031
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
 
2032
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
 
2033
                 'a-a', 'a-z',
 
2034
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
 
2035
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
 
2036
                 'z-a', 'z-z',
 
2037
                ]
 
2038
        cache = {}
 
2039
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
2040
        for path in paths:
 
2041
            self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
 
2042