/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
64
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID
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.
103
104
    This is new in bzr TODO FIXME SETMEBEFORE MERGE.
105
    """
106
107
    def __init__(self, basedir,
108
                 branch,
109
                 _inventory=None,
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
        if _inventory is None:
154
            self._inventory_is_modified = False
155
            self.read_working_inventory()
156
        else:
157
            # the caller of __init__ has provided an inventory,
158
            # we assume they know what they are doing - as its only
159
            # the Format factory and creation methods that are
160
            # permitted to do this.
161
            self._set_inventory(_inventory, dirty=False)
162
        self._dirty = None
163
        self._parent_revisions = None
164
        self._dirstate = None
165
166
    def current_dirstate(self):
167
        """Return the current dirstate object. 
168
169
        This is not part of the tree interface and only exposed for ease of
170
        testing.
171
172
        :raises errors.NotWriteLocked: when not in a lock. 
173
            XXX: This should probably be errors.NotLocked.
174
        """
175
        if not self._control_files._lock_count:
176
            raise errors.NotWriteLocked(self)
177
        if self._dirstate is not None:
178
            return self._dirstate
179
        local_path = self.bzrdir.get_workingtree_transport(None
180
            ).local_abspath('dirstate')
181
        self._dirstate = dirstate.DirState.on_file(local_path)
182
        return self._dirstate
183
184
    def _new_tree(self):
185
        """Initialize the state in this tree to be a new tree."""
186
        self._parent_revisions = [NULL_REVISION]
187
        self._inventory = Inventory()
188
        self._dirty = True
189
190
    @needs_read_lock
191
    def revision_tree(self, revision_id):
192
        """See Tree.revision_tree.
193
194
        WorkingTree4 supplies revision_trees for any basis tree.
195
        """
196
        dirstate = self.current_dirstate()
197
        parent_ids = dirstate.get_parent_ids()
198
        if revision_id not in parent_ids:
199
            raise errors.NoSuchRevisionInTree(self, revision_id)
200
        return DirStateRevisionTree(dirstate, revision_id,
201
            self.branch.repository)
202
203
    @needs_write_lock
204
    def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
205
        """Set the parent ids to revision_ids.
206
        
207
        See also set_parent_trees. This api will try to retrieve the tree data
208
        for each element of revision_ids from the trees repository. If you have
209
        tree data already available, it is more efficient to use
210
        set_parent_trees rather than set_parent_ids. set_parent_ids is however
211
        an easier API to use.
212
213
        :param revision_ids: The revision_ids to set as the parent ids of this
214
            working tree. Any of these may be ghosts.
215
        """
216
        trees = []
217
        for revision_id in revision_ids:
218
            try:
219
                revtree = self.branch.repository.revision_tree(revision_id)
220
            except errors.NoSuchRevision:
221
                revtree = None
222
            trees.append((revision_id, revtree))
223
        self.set_parent_trees(trees,
224
            allow_leftmost_as_ghost=allow_leftmost_as_ghost)
225
226
    @needs_write_lock
227
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
228
        """Set the parents of the working tree.
229
230
        :param parents_list: A list of (revision_id, tree) tuples. 
231
            If tree is None, then that element is treated as an unreachable
232
            parent tree - i.e. a ghost.
233
        """
234
        dirstate = self.current_dirstate()
235
        if len(parents_list) > 0:
236
            if not allow_leftmost_as_ghost and parents_list[0][1] is None:
237
                raise errors.GhostRevisionUnusableHere(leftmost_id)
238
        real_trees = []
239
        ghosts = []
240
        # convert absent trees to the null tree, which we convert back to 
241
        # missing on access.
242
        for rev_id, tree in parents_list:
243
            if tree is not None:
244
                real_trees.append((rev_id, tree))
245
            else:
246
                real_trees.append((rev_id,
247
                    self.branch.repository.revision_tree(None)))
248
                ghosts.append(rev_id)
249
        dirstate.set_parent_trees(real_trees, ghosts=ghosts)
250
        self._dirty = True
251
252
    def unlock(self):
253
        """Unlock in format 4 trees needs to write the entire dirstate."""
254
        if self._control_files._lock_count == 1:
255
            if self._hashcache.needs_write:
256
                self._hashcache.write()
257
            # eventually we should do signature checking during read locks for
258
            # dirstate updates.
259
            if self._control_files._lock_mode == 'w':
260
                if self._dirty:
261
                    self.flush()
262
            self._dirstate = None
263
        # reverse order of locking.
264
        try:
265
            return self._control_files.unlock()
266
        finally:
267
            self.branch.unlock()
268
269
    def flush(self):
270
        """Write the full dirstate to disk."""
271
        self.current_dirstate().save()
272
        
273
274
class WorkingTreeFormat4(WorkingTreeFormat3):
275
    """The first consolidated dirstate working tree format.
276
277
    This format:
278
        - exists within a metadir controlling .bzr
279
        - includes an explicit version marker for the workingtree control
280
          files, separate from the BzrDir format
281
        - modifies the hash cache format
282
        - is new in bzr TODO FIXME SETBEFOREMERGE
283
        - uses a LockDir to guard access to it.
284
    """
285
286
    def get_format_string(self):
287
        """See WorkingTreeFormat.get_format_string()."""
288
        return "Bazaar Working Tree format 4\n"
289
290
    def get_format_description(self):
291
        """See WorkingTreeFormat.get_format_description()."""
292
        return "Working tree format 4"
293
294
    def initialize(self, a_bzrdir, revision_id=None):
295
        """See WorkingTreeFormat.initialize().
296
        
297
        revision_id allows creating a working tree at a different
298
        revision than the branch is at.
299
        """
300
        if not isinstance(a_bzrdir.transport, LocalTransport):
301
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
302
        transport = a_bzrdir.get_workingtree_transport(self)
303
        control_files = self._open_control_files(a_bzrdir)
304
        control_files.create_lock()
305
        control_files.lock_write()
306
        control_files.put_utf8('format', self.get_format_string())
307
        branch = a_bzrdir.open_branch()
308
        if revision_id is None:
309
            revision_id = branch.last_revision()
310
        inv = Inventory()
311
        local_path = transport.local_abspath('dirstate')
312
        dirstate.DirState.initialize(local_path)
313
        wt = WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
314
                         branch,
315
                         inv,
316
                         _format=self,
317
                         _bzrdir=a_bzrdir,
318
                         _control_files=control_files)
319
        wt._new_tree()
320
        wt.lock_write()
321
        try:
322
            wt._write_inventory(inv)
323
            wt.set_root_id(inv.root.file_id)
324
            wt.set_last_revision(revision_id)
325
            transform.build_tree(wt.basis_tree(), wt)
326
        finally:
327
            control_files.unlock()
328
            wt.unlock()
329
        return wt
330
331
332
    def _open(self, a_bzrdir, control_files):
333
        """Open the tree itself.
334
        
335
        :param a_bzrdir: the dir for the tree.
336
        :param control_files: the control files for the tree.
337
        """
338
        return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
339
                           branch=a_bzrdir.open_branch(),
340
                           _format=self,
341
                           _bzrdir=a_bzrdir,
342
                           _control_files=control_files)
343
344
345
class DirStateRevisionTree(Tree):
346
    """A revision tree pulling the inventory from a dirstate."""
347
348
    def __init__(self, dirstate, revision_id, repository):
349
        self._dirstate = dirstate
350
        self._revision_id = revision_id
351
        self._repository = repository
352
        self._inventory = None
353
        self._locked = False
354
355
    def _comparison_data(self, entry, path):
356
        """See Tree._comparison_data."""
357
        if entry is None:
358
            return None, False, None
359
        # trust the entry as RevisionTree does, but this may not be
360
        # sensible: the entry might not have come from us?
361
        return entry.kind, entry.executable, None
362
363
    def _get_inventory(self):
364
        if self._inventory is not None:
365
            return self._inventory
366
        self._generate_inventory()
367
        return self._inventory
368
369
    inventory = property(_get_inventory,
370
                         doc="Inventory of this Tree")
371
372
    def _generate_inventory(self):
373
        """Create and set self.inventory from the dirstate object.
374
        
375
        This is relatively expensive: we have to walk the entire dirstate.
376
        Ideally we would not, and instead would """
377
        assert self._locked, 'cannot generate inventory of an unlocked '\
378
            'dirstate revision tree'
379
        assert self._revision_id in self._dirstate.get_parent_ids(), \
380
            'parent %s has disappeared from %s' % (
381
            self._revision_id, self._dirstate.get_parent_ids())
382
        inv = Inventory()
383
        for line in self._dirstate._iter_rows():
384
            print line
385
        self._inventory = inv
386
387
    def get_parent_ids(self):
388
        """The parents of a tree in the dirstate are not cached."""
389
        return self._repository.get_revision(self._revision_id).parent_ids
390
391
    def lock_read(self):
392
        """Lock the tree for a set of operations."""
393
        self._locked = True
394
395
    def unlock(self):
396
        """Unlock, freeing any cache memory used during the lock."""
397
        # outside of a lock, the inventory is suspect: release it.
398
        self._inventory = None
399
        self._locked = False