/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/workingtree_4.py

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.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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