/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
1
# Copyright (C) 2005, 2006 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
"""WorkingTree4 format and implementation.
18
19
WorkingTree4 provides the dirstate based working tree logic.
20
21
To get a WorkingTree, call bzrdir.open_workingtree() or
22
WorkingTree.open(dir).
23
"""
24
25
import os
26
27
from bzrlib.lazy_import import lazy_import
28
lazy_import(globals(), """
29
from bisect import bisect_left
30
import collections
31
from copy import deepcopy
32
import errno
33
import itertools
34
import operator
35
import stat
36
from time import time
37
import warnings
38
39
import bzrlib
40
from bzrlib import (
41
    bzrdir,
42
    conflicts as _mod_conflicts,
43
    dirstate,
44
    errors,
45
    generate_ids,
46
    globbing,
47
    hashcache,
48
    ignores,
49
    merge,
50
    osutils,
51
    textui,
52
    transform,
53
    urlutils,
54
    xml5,
55
    xml6,
56
    )
57
import bzrlib.branch
58
from bzrlib.transport import get_transport
59
import bzrlib.ui
60
""")
61
62
from bzrlib import symbol_versioning
63
from bzrlib.decorators import needs_read_lock, needs_write_lock
2255.2.10 by Robert Collins
Now all tests matching dirstate pass - added generation of inventories for parent trees.
64
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID, make_entry
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
65
from bzrlib.lockable_files import LockableFiles, TransportLock
66
from bzrlib.lockdir import LockDir
67
import bzrlib.mutabletree
68
from bzrlib.mutabletree import needs_tree_write_lock
69
from bzrlib.osutils import (
70
    compact_date,
71
    file_kind,
72
    isdir,
73
    normpath,
74
    pathjoin,
75
    rand_chars,
76
    realpath,
77
    safe_unicode,
78
    splitpath,
79
    supports_executable,
80
    )
81
from bzrlib.trace import mutter, note
82
from bzrlib.transport.local import LocalTransport
83
from bzrlib.progress import DummyProgress, ProgressPhase
84
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
85
from bzrlib.rio import RioReader, rio_file, Stanza
86
from bzrlib.symbol_versioning import (deprecated_passed,
87
        deprecated_method,
88
        deprecated_function,
89
        DEPRECATED_PARAMETER,
90
        zero_eight,
91
        zero_eleven,
92
        zero_thirteen,
93
        )
94
from bzrlib.tree import Tree
95
from bzrlib.workingtree import WorkingTree3, WorkingTreeFormat3
96
97
98
class WorkingTree4(WorkingTree3):
99
    """This is the Format 4 working tree.
100
101
    This differs from WorkingTree3 by:
102
     - having a consolidated internal dirstate.
2255.2.15 by Robert Collins
Dirstate - truncate state file fixing bug in saving a smaller file, get more tree_implementation tests passing.
103
     - not having a regular inventory attribute.
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
104
105
    This is new in bzr TODO FIXME SETMEBEFORE MERGE.
106
    """
107
108
    def __init__(self, basedir,
109
                 branch,
110
                 _control_files=None,
111
                 _format=None,
112
                 _bzrdir=None):
113
        """Construct a WorkingTree for basedir.
114
115
        If the branch is not supplied, it is opened automatically.
116
        If the branch is supplied, it must be the branch for this basedir.
117
        (branch.base is not cross checked, because for remote branches that
118
        would be meaningless).
119
        """
120
        self._format = _format
121
        self.bzrdir = _bzrdir
122
        from bzrlib.hashcache import HashCache
123
        from bzrlib.trace import note, mutter
124
        assert isinstance(basedir, basestring), \
125
            "base directory %r is not a string" % basedir
126
        basedir = safe_unicode(basedir)
127
        mutter("opening working tree %r", basedir)
128
        self._branch = branch
129
        assert isinstance(self.branch, bzrlib.branch.Branch), \
130
            "branch %r is not a Branch" % self.branch
131
        self.basedir = realpath(basedir)
132
        # if branch is at our basedir and is a format 6 or less
133
        # assume all other formats have their own control files.
134
        assert isinstance(_control_files, LockableFiles), \
135
            "_control_files must be a LockableFiles, not %r" % _control_files
136
        self._control_files = _control_files
137
        # update the whole cache up front and write to disk if anything changed;
138
        # in the future we might want to do this more selectively
139
        # two possible ways offer themselves : in self._unlock, write the cache
140
        # if needed, or, when the cache sees a change, append it to the hash
141
        # cache file, and have the parser take the most recent entry for a
142
        # given path only.
143
        cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
144
        hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
145
        hc.read()
146
        # is this scan needed ? it makes things kinda slow.
147
        #hc.scan()
148
149
        if hc.needs_write:
150
            mutter("write hc")
151
            hc.write()
152
153
        self._dirty = None
154
        self._parent_revisions = None
2255.2.15 by Robert Collins
Dirstate - truncate state file fixing bug in saving a smaller file, get more tree_implementation tests passing.
155
        #-------------
156
        # during a read or write lock these objects are set, and are
157
        # None the rest of the time.
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
158
        self._dirstate = None
2255.2.15 by Robert Collins
Dirstate - truncate state file fixing bug in saving a smaller file, get more tree_implementation tests passing.
159
        self._inventory = None
160
        #-------------
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
161
2255.2.12 by Robert Collins
Partial implementation of WorkingTree4._add.
162
    @needs_write_lock
163
    def _add(self, files, ids, kinds):
164
        """See MutableTree._add."""
165
        state = self.current_dirstate()
166
        for f, file_id, kind in zip(files, ids, kinds):
2255.2.14 by Robert Collins
Dirstate: fix adding of directories to setup the next directories block, and test representation of symlinks. Also fix iter_rows to not reset the dirty bit.
167
            f = f.strip('/')
2255.2.12 by Robert Collins
Partial implementation of WorkingTree4._add.
168
            assert '//' not in f
169
            assert '..' not in f
170
            if file_id is None:
171
                file_id = generate_ids.gen_file_id(name)
172
            stat = os.lstat(self.abspath(f))
173
            sha1 = '1' * 20 # FIXME: DIRSTATE MERGE BLOCKER
174
            state.add(f, file_id, kind, stat, sha1)
2255.2.16 by Robert Collins
Implement WorkingTreeFormat4._write_inventory for better compatability with existing code, letting more test_test_trees pass, now up to test_tree_with_subdirs_and_all_content_types.
175
        self._dirty = True
2255.2.12 by Robert Collins
Partial implementation of WorkingTree4._add.
176
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
177
    def current_dirstate(self):
178
        """Return the current dirstate object. 
179
180
        This is not part of the tree interface and only exposed for ease of
181
        testing.
182
183
        :raises errors.NotWriteLocked: when not in a lock. 
184
            XXX: This should probably be errors.NotLocked.
185
        """
186
        if not self._control_files._lock_count:
2255.2.15 by Robert Collins
Dirstate - truncate state file fixing bug in saving a smaller file, get more tree_implementation tests passing.
187
            raise errors.ObjectNotLocked(self)
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
188
        if self._dirstate is not None:
189
            return self._dirstate
190
        local_path = self.bzrdir.get_workingtree_transport(None
191
            ).local_abspath('dirstate')
192
        self._dirstate = dirstate.DirState.on_file(local_path)
193
        return self._dirstate
194
2255.2.15 by Robert Collins
Dirstate - truncate state file fixing bug in saving a smaller file, get more tree_implementation tests passing.
195
    def _generate_inventory(self):
196
        """Create and set self.inventory from the dirstate object.
197
        
198
        This is relatively expensive: we have to walk the entire dirstate.
199
        Ideally we would not, and can deprecate this function.
200
        """
201
        dirstate = self.current_dirstate()
202
        rows = self._dirstate._iter_rows()
203
        root_row = rows.next()
204
        inv = Inventory(root_id=root_row[0][3].decode('utf8'))
205
        for line in rows:
206
            dirname, name, kind, fileid_utf8, size, stat, link_or_sha1 = line[0]
207
            if dirname == '/':
208
                # not in this revision tree.
209
                continue
210
            parent_id = inv[inv.path2id(dirname.decode('utf8'))].file_id
211
            file_id = fileid_utf8.decode('utf8')
212
            entry = make_entry(kind, name.decode('utf8'), parent_id, file_id)
213
            if kind == 'file':
214
                #entry.executable = executable
215
                #entry.text_size = size
216
                #entry.text_sha1 = sha1
217
                pass
218
            inv.add(entry)
219
        self._inventory = inv
220
2255.2.17 by Robert Collins
tweaks - finishes off all the test_test_trees tests for dirstate.
221
    def _get_inventory(self):
222
        """Get the inventory for the tree. This is only valid within a lock."""
223
        if self._inventory is not None:
224
            return self._inventory
225
        self._generate_inventory()
226
        return self._inventory
227
228
    inventory = property(_get_inventory,
229
                         doc="Inventory of this Tree")
230
231
    @needs_read_lock
232
    def get_root_id(self):
233
        """Return the id of this trees root"""
234
        return self.current_dirstate()._iter_rows().next()[0][3].decode('utf8')
235
2255.2.15 by Robert Collins
Dirstate - truncate state file fixing bug in saving a smaller file, get more tree_implementation tests passing.
236
    @needs_read_lock
237
    def id2path(self, fileid):
238
        state = self.current_dirstate()
239
        fileid_utf8 = fileid.encode('utf8')
240
        for row, parents in state._iter_rows():
241
            if row[3] == fileid_utf8:
242
                return (row[0] + '/' + row[1]).decode('utf8').strip('/')
243
244
    @needs_read_lock
245
    def __iter__(self):
246
        """Iterate through file_ids for this tree.
247
248
        file_ids are in a WorkingTree if they are in the working inventory
249
        and the working file exists.
250
        """
251
        result = []
252
        for row, parents in self.current_dirstate()._iter_rows():
253
            if row[0] == '/':
254
                continue
255
            path = pathjoin(self.basedir, row[0].decode('utf8'), row[1].decode('utf8'))
256
            if osutils.lexists(path):
257
                result.append(row[3].decode('utf8'))
258
        return iter(result)
259
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
260
    def _new_tree(self):
261
        """Initialize the state in this tree to be a new tree."""
262
        self._parent_revisions = [NULL_REVISION]
263
        self._dirty = True
264
265
    @needs_read_lock
2255.2.17 by Robert Collins
tweaks - finishes off all the test_test_trees tests for dirstate.
266
    def path2id(self, path):
267
        """Return the id for path in this tree."""
268
        state = self.current_dirstate()
269
        path_utf8 = os.path.split(path.encode('utf8'))
270
        for row, parents in state._iter_rows():
271
            if row[0:2] == path_utf8:
272
                return row[3].decode('utf8')
273
        return None
274
275
    @needs_read_lock
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
276
    def revision_tree(self, revision_id):
277
        """See Tree.revision_tree.
278
279
        WorkingTree4 supplies revision_trees for any basis tree.
280
        """
281
        dirstate = self.current_dirstate()
282
        parent_ids = dirstate.get_parent_ids()
283
        if revision_id not in parent_ids:
284
            raise errors.NoSuchRevisionInTree(self, revision_id)
285
        return DirStateRevisionTree(dirstate, revision_id,
286
            self.branch.repository)
287
288
    @needs_write_lock
289
    def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
290
        """Set the parent ids to revision_ids.
291
        
292
        See also set_parent_trees. This api will try to retrieve the tree data
293
        for each element of revision_ids from the trees repository. If you have
294
        tree data already available, it is more efficient to use
295
        set_parent_trees rather than set_parent_ids. set_parent_ids is however
296
        an easier API to use.
297
298
        :param revision_ids: The revision_ids to set as the parent ids of this
299
            working tree. Any of these may be ghosts.
300
        """
301
        trees = []
302
        for revision_id in revision_ids:
303
            try:
304
                revtree = self.branch.repository.revision_tree(revision_id)
305
            except errors.NoSuchRevision:
306
                revtree = None
307
            trees.append((revision_id, revtree))
308
        self.set_parent_trees(trees,
309
            allow_leftmost_as_ghost=allow_leftmost_as_ghost)
310
311
    @needs_write_lock
312
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
313
        """Set the parents of the working tree.
314
315
        :param parents_list: A list of (revision_id, tree) tuples. 
316
            If tree is None, then that element is treated as an unreachable
317
            parent tree - i.e. a ghost.
318
        """
319
        dirstate = self.current_dirstate()
320
        if len(parents_list) > 0:
321
            if not allow_leftmost_as_ghost and parents_list[0][1] is None:
322
                raise errors.GhostRevisionUnusableHere(leftmost_id)
323
        real_trees = []
324
        ghosts = []
325
        # convert absent trees to the null tree, which we convert back to 
326
        # missing on access.
327
        for rev_id, tree in parents_list:
328
            if tree is not None:
329
                real_trees.append((rev_id, tree))
330
            else:
331
                real_trees.append((rev_id,
332
                    self.branch.repository.revision_tree(None)))
333
                ghosts.append(rev_id)
334
        dirstate.set_parent_trees(real_trees, ghosts=ghosts)
335
        self._dirty = True
336
2255.2.15 by Robert Collins
Dirstate - truncate state file fixing bug in saving a smaller file, get more tree_implementation tests passing.
337
    def _set_root_id(self, file_id):
338
        """See WorkingTree.set_root_id."""
339
        self.current_dirstate().set_path_id('', file_id)
340
        self._dirty = True
341
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
342
    def unlock(self):
343
        """Unlock in format 4 trees needs to write the entire dirstate."""
344
        if self._control_files._lock_count == 1:
345
            if self._hashcache.needs_write:
346
                self._hashcache.write()
347
            # eventually we should do signature checking during read locks for
348
            # dirstate updates.
349
            if self._control_files._lock_mode == 'w':
350
                if self._dirty:
351
                    self.flush()
352
            self._dirstate = None
2255.2.15 by Robert Collins
Dirstate - truncate state file fixing bug in saving a smaller file, get more tree_implementation tests passing.
353
            self._inventory = None
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
354
        # reverse order of locking.
355
        try:
356
            return self._control_files.unlock()
357
        finally:
358
            self.branch.unlock()
359
360
    def flush(self):
2255.2.16 by Robert Collins
Implement WorkingTreeFormat4._write_inventory for better compatability with existing code, letting more test_test_trees pass, now up to test_tree_with_subdirs_and_all_content_types.
361
        """Write all cached data to disk."""
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
362
        self.current_dirstate().save()
2255.2.16 by Robert Collins
Implement WorkingTreeFormat4._write_inventory for better compatability with existing code, letting more test_test_trees pass, now up to test_tree_with_subdirs_and_all_content_types.
363
        self._inventory = None
364
        self._dirty = False
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
365
        
2255.2.16 by Robert Collins
Implement WorkingTreeFormat4._write_inventory for better compatability with existing code, letting more test_test_trees pass, now up to test_tree_with_subdirs_and_all_content_types.
366
    @needs_tree_write_lock
367
    def _write_inventory(self, inv):
368
        """Write inventory as the current inventory."""
369
        assert not self._dirty, "attempting to write an inventory when the dirstate is dirty will cause data loss"
370
        self.current_dirstate().set_state_from_inventory(inv)
371
        self._dirty = True
372
        self.flush()
373
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
374
375
class WorkingTreeFormat4(WorkingTreeFormat3):
376
    """The first consolidated dirstate working tree format.
377
378
    This format:
379
        - exists within a metadir controlling .bzr
380
        - includes an explicit version marker for the workingtree control
381
          files, separate from the BzrDir format
382
        - modifies the hash cache format
383
        - is new in bzr TODO FIXME SETBEFOREMERGE
384
        - uses a LockDir to guard access to it.
385
    """
386
387
    def get_format_string(self):
388
        """See WorkingTreeFormat.get_format_string()."""
389
        return "Bazaar Working Tree format 4\n"
390
391
    def get_format_description(self):
392
        """See WorkingTreeFormat.get_format_description()."""
393
        return "Working tree format 4"
394
395
    def initialize(self, a_bzrdir, revision_id=None):
396
        """See WorkingTreeFormat.initialize().
397
        
398
        revision_id allows creating a working tree at a different
399
        revision than the branch is at.
400
        """
401
        if not isinstance(a_bzrdir.transport, LocalTransport):
402
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
403
        transport = a_bzrdir.get_workingtree_transport(self)
404
        control_files = self._open_control_files(a_bzrdir)
405
        control_files.create_lock()
406
        control_files.lock_write()
407
        control_files.put_utf8('format', self.get_format_string())
408
        branch = a_bzrdir.open_branch()
409
        if revision_id is None:
410
            revision_id = branch.last_revision()
411
        local_path = transport.local_abspath('dirstate')
412
        dirstate.DirState.initialize(local_path)
413
        wt = WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
414
                         branch,
415
                         _format=self,
416
                         _bzrdir=a_bzrdir,
417
                         _control_files=control_files)
418
        wt._new_tree()
419
        wt.lock_write()
420
        try:
2255.2.15 by Robert Collins
Dirstate - truncate state file fixing bug in saving a smaller file, get more tree_implementation tests passing.
421
            #wt.current_dirstate().set_path_id('', NEWROOT)
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
422
            wt.set_last_revision(revision_id)
2255.2.16 by Robert Collins
Implement WorkingTreeFormat4._write_inventory for better compatability with existing code, letting more test_test_trees pass, now up to test_tree_with_subdirs_and_all_content_types.
423
            wt.flush()
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
424
            transform.build_tree(wt.basis_tree(), wt)
425
        finally:
426
            control_files.unlock()
427
            wt.unlock()
428
        return wt
429
430
431
    def _open(self, a_bzrdir, control_files):
432
        """Open the tree itself.
433
        
434
        :param a_bzrdir: the dir for the tree.
435
        :param control_files: the control files for the tree.
436
        """
437
        return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
438
                           branch=a_bzrdir.open_branch(),
439
                           _format=self,
440
                           _bzrdir=a_bzrdir,
441
                           _control_files=control_files)
442
443
444
class DirStateRevisionTree(Tree):
445
    """A revision tree pulling the inventory from a dirstate."""
446
447
    def __init__(self, dirstate, revision_id, repository):
448
        self._dirstate = dirstate
449
        self._revision_id = revision_id
450
        self._repository = repository
451
        self._inventory = None
452
        self._locked = False
453
454
    def _comparison_data(self, entry, path):
455
        """See Tree._comparison_data."""
456
        if entry is None:
457
            return None, False, None
458
        # trust the entry as RevisionTree does, but this may not be
459
        # sensible: the entry might not have come from us?
460
        return entry.kind, entry.executable, None
461
2255.2.10 by Robert Collins
Now all tests matching dirstate pass - added generation of inventories for parent trees.
462
    def _file_size(self, entry, stat_value):
463
        return entry.text_size
464
465
    def get_file_sha1(self, file_id, path=None, stat_value=None):
466
        # TODO: if path is present, fast-path on that, as inventory
467
        # might not be present
468
        ie = self.inventory[file_id]
469
        if ie.kind == "file":
470
            return ie.text_sha1
471
        return None
472
473
    def get_file_size(self, file_id):
474
        return self.inventory[file_id].text_size
475
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
476
    def _get_inventory(self):
477
        if self._inventory is not None:
478
            return self._inventory
479
        self._generate_inventory()
480
        return self._inventory
481
482
    inventory = property(_get_inventory,
483
                         doc="Inventory of this Tree")
484
485
    def _generate_inventory(self):
486
        """Create and set self.inventory from the dirstate object.
487
        
488
        This is relatively expensive: we have to walk the entire dirstate.
489
        Ideally we would not, and instead would """
490
        assert self._locked, 'cannot generate inventory of an unlocked '\
491
            'dirstate revision tree'
492
        assert self._revision_id in self._dirstate.get_parent_ids(), \
493
            'parent %s has disappeared from %s' % (
494
            self._revision_id, self._dirstate.get_parent_ids())
2255.2.10 by Robert Collins
Now all tests matching dirstate pass - added generation of inventories for parent trees.
495
        parent_index = self._dirstate.get_parent_ids().index(self._revision_id)
496
        rows = self._dirstate._iter_rows()
497
        root_row = rows.next()
498
        inv = Inventory(root_id=root_row[0][3].decode('utf8'),
499
            revision_id=self._revision_id)
500
        for line in rows:
501
            revid, kind, dirname, name, size, executable, sha1 = line[1][parent_index]
502
            if not revid:
503
                # not in this revision tree.
504
                continue
505
            parent_id = inv[inv.path2id(dirname.decode('utf8'))].file_id
506
            file_id = line[0][3].decode('utf8')
507
            entry = make_entry(kind, name.decode('utf8'), parent_id, file_id)
508
            entry.revision = revid.decode('utf8')
509
            if kind == 'file':
510
                entry.executable = executable
511
                entry.text_size = size
512
                entry.text_sha1 = sha1
513
            inv.add(entry)
2255.2.3 by Robert Collins
Split out working tree format 4 to its own file, create stub dirstate revision object, start working on dirstate.set_parent_trees - a key failure point.
514
        self._inventory = inv
515
516
    def get_parent_ids(self):
517
        """The parents of a tree in the dirstate are not cached."""
518
        return self._repository.get_revision(self._revision_id).parent_ids
519
520
    def lock_read(self):
521
        """Lock the tree for a set of operations."""
522
        self._locked = True
523
524
    def unlock(self):
525
        """Unlock, freeing any cache memory used during the lock."""
526
        # outside of a lock, the inventory is suspect: release it.
527
        self._inventory = None
528
        self._locked = False