/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.
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
2255.2.12 by Robert Collins
Partial implementation of WorkingTree4._add.
166
    @needs_write_lock
167
    def _add(self, files, ids, kinds):
168
        """See MutableTree._add."""
169
        state = self.current_dirstate()
170
        for f, file_id, kind in zip(files, ids, kinds):
171
            assert '//' not in f
172
            assert not f.startswith('/')
173
            assert '..' not in f
174
            if file_id is None:
175
                file_id = generate_ids.gen_file_id(name)
176
            stat = os.lstat(self.abspath(f))
177
            sha1 = '1' * 20 # FIXME: DIRSTATE MERGE BLOCKER
178
            state.add(f, file_id, kind, stat, sha1)
179
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.
180
    def current_dirstate(self):
181
        """Return the current dirstate object. 
182
183
        This is not part of the tree interface and only exposed for ease of
184
        testing.
185
186
        :raises errors.NotWriteLocked: when not in a lock. 
187
            XXX: This should probably be errors.NotLocked.
188
        """
189
        if not self._control_files._lock_count:
190
            raise errors.NotWriteLocked(self)
191
        if self._dirstate is not None:
192
            return self._dirstate
193
        local_path = self.bzrdir.get_workingtree_transport(None
194
            ).local_abspath('dirstate')
195
        self._dirstate = dirstate.DirState.on_file(local_path)
196
        return self._dirstate
197
198
    def _new_tree(self):
199
        """Initialize the state in this tree to be a new tree."""
200
        self._parent_revisions = [NULL_REVISION]
201
        self._inventory = Inventory()
202
        self._dirty = True
203
204
    @needs_read_lock
205
    def revision_tree(self, revision_id):
206
        """See Tree.revision_tree.
207
208
        WorkingTree4 supplies revision_trees for any basis tree.
209
        """
210
        dirstate = self.current_dirstate()
211
        parent_ids = dirstate.get_parent_ids()
212
        if revision_id not in parent_ids:
213
            raise errors.NoSuchRevisionInTree(self, revision_id)
214
        return DirStateRevisionTree(dirstate, revision_id,
215
            self.branch.repository)
216
217
    @needs_write_lock
218
    def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
219
        """Set the parent ids to revision_ids.
220
        
221
        See also set_parent_trees. This api will try to retrieve the tree data
222
        for each element of revision_ids from the trees repository. If you have
223
        tree data already available, it is more efficient to use
224
        set_parent_trees rather than set_parent_ids. set_parent_ids is however
225
        an easier API to use.
226
227
        :param revision_ids: The revision_ids to set as the parent ids of this
228
            working tree. Any of these may be ghosts.
229
        """
230
        trees = []
231
        for revision_id in revision_ids:
232
            try:
233
                revtree = self.branch.repository.revision_tree(revision_id)
234
            except errors.NoSuchRevision:
235
                revtree = None
236
            trees.append((revision_id, revtree))
237
        self.set_parent_trees(trees,
238
            allow_leftmost_as_ghost=allow_leftmost_as_ghost)
239
240
    @needs_write_lock
241
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
242
        """Set the parents of the working tree.
243
244
        :param parents_list: A list of (revision_id, tree) tuples. 
245
            If tree is None, then that element is treated as an unreachable
246
            parent tree - i.e. a ghost.
247
        """
248
        dirstate = self.current_dirstate()
249
        if len(parents_list) > 0:
250
            if not allow_leftmost_as_ghost and parents_list[0][1] is None:
251
                raise errors.GhostRevisionUnusableHere(leftmost_id)
252
        real_trees = []
253
        ghosts = []
254
        # convert absent trees to the null tree, which we convert back to 
255
        # missing on access.
256
        for rev_id, tree in parents_list:
257
            if tree is not None:
258
                real_trees.append((rev_id, tree))
259
            else:
260
                real_trees.append((rev_id,
261
                    self.branch.repository.revision_tree(None)))
262
                ghosts.append(rev_id)
263
        dirstate.set_parent_trees(real_trees, ghosts=ghosts)
264
        self._dirty = True
265
266
    def unlock(self):
267
        """Unlock in format 4 trees needs to write the entire dirstate."""
268
        if self._control_files._lock_count == 1:
269
            if self._hashcache.needs_write:
270
                self._hashcache.write()
271
            # eventually we should do signature checking during read locks for
272
            # dirstate updates.
273
            if self._control_files._lock_mode == 'w':
274
                if self._dirty:
275
                    self.flush()
276
            self._dirstate = None
277
        # reverse order of locking.
278
        try:
279
            return self._control_files.unlock()
280
        finally:
281
            self.branch.unlock()
282
283
    def flush(self):
284
        """Write the full dirstate to disk."""
285
        self.current_dirstate().save()
286
        
287
288
class WorkingTreeFormat4(WorkingTreeFormat3):
289
    """The first consolidated dirstate working tree format.
290
291
    This format:
292
        - exists within a metadir controlling .bzr
293
        - includes an explicit version marker for the workingtree control
294
          files, separate from the BzrDir format
295
        - modifies the hash cache format
296
        - is new in bzr TODO FIXME SETBEFOREMERGE
297
        - uses a LockDir to guard access to it.
298
    """
299
300
    def get_format_string(self):
301
        """See WorkingTreeFormat.get_format_string()."""
302
        return "Bazaar Working Tree format 4\n"
303
304
    def get_format_description(self):
305
        """See WorkingTreeFormat.get_format_description()."""
306
        return "Working tree format 4"
307
308
    def initialize(self, a_bzrdir, revision_id=None):
309
        """See WorkingTreeFormat.initialize().
310
        
311
        revision_id allows creating a working tree at a different
312
        revision than the branch is at.
313
        """
314
        if not isinstance(a_bzrdir.transport, LocalTransport):
315
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
316
        transport = a_bzrdir.get_workingtree_transport(self)
317
        control_files = self._open_control_files(a_bzrdir)
318
        control_files.create_lock()
319
        control_files.lock_write()
320
        control_files.put_utf8('format', self.get_format_string())
321
        branch = a_bzrdir.open_branch()
322
        if revision_id is None:
323
            revision_id = branch.last_revision()
324
        inv = Inventory()
325
        local_path = transport.local_abspath('dirstate')
326
        dirstate.DirState.initialize(local_path)
327
        wt = WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
328
                         branch,
329
                         inv,
330
                         _format=self,
331
                         _bzrdir=a_bzrdir,
332
                         _control_files=control_files)
333
        wt._new_tree()
334
        wt.lock_write()
335
        try:
336
            wt._write_inventory(inv)
337
            wt.set_root_id(inv.root.file_id)
338
            wt.set_last_revision(revision_id)
339
            transform.build_tree(wt.basis_tree(), wt)
340
        finally:
341
            control_files.unlock()
342
            wt.unlock()
343
        return wt
344
345
346
    def _open(self, a_bzrdir, control_files):
347
        """Open the tree itself.
348
        
349
        :param a_bzrdir: the dir for the tree.
350
        :param control_files: the control files for the tree.
351
        """
352
        return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
353
                           branch=a_bzrdir.open_branch(),
354
                           _format=self,
355
                           _bzrdir=a_bzrdir,
356
                           _control_files=control_files)
357
358
359
class DirStateRevisionTree(Tree):
360
    """A revision tree pulling the inventory from a dirstate."""
361
362
    def __init__(self, dirstate, revision_id, repository):
363
        self._dirstate = dirstate
364
        self._revision_id = revision_id
365
        self._repository = repository
366
        self._inventory = None
367
        self._locked = False
368
369
    def _comparison_data(self, entry, path):
370
        """See Tree._comparison_data."""
371
        if entry is None:
372
            return None, False, None
373
        # trust the entry as RevisionTree does, but this may not be
374
        # sensible: the entry might not have come from us?
375
        return entry.kind, entry.executable, None
376
2255.2.10 by Robert Collins
Now all tests matching dirstate pass - added generation of inventories for parent trees.
377
    def _file_size(self, entry, stat_value):
378
        return entry.text_size
379
380
    def get_file_sha1(self, file_id, path=None, stat_value=None):
381
        # TODO: if path is present, fast-path on that, as inventory
382
        # might not be present
383
        ie = self.inventory[file_id]
384
        if ie.kind == "file":
385
            return ie.text_sha1
386
        return None
387
388
    def get_file_size(self, file_id):
389
        return self.inventory[file_id].text_size
390
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.
391
    def _get_inventory(self):
392
        if self._inventory is not None:
393
            return self._inventory
394
        self._generate_inventory()
395
        return self._inventory
396
397
    inventory = property(_get_inventory,
398
                         doc="Inventory of this Tree")
399
400
    def _generate_inventory(self):
401
        """Create and set self.inventory from the dirstate object.
402
        
403
        This is relatively expensive: we have to walk the entire dirstate.
404
        Ideally we would not, and instead would """
405
        assert self._locked, 'cannot generate inventory of an unlocked '\
406
            'dirstate revision tree'
407
        assert self._revision_id in self._dirstate.get_parent_ids(), \
408
            'parent %s has disappeared from %s' % (
409
            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.
410
        parent_index = self._dirstate.get_parent_ids().index(self._revision_id)
411
        rows = self._dirstate._iter_rows()
412
        root_row = rows.next()
413
        inv = Inventory(root_id=root_row[0][3].decode('utf8'),
414
            revision_id=self._revision_id)
415
        for line in rows:
416
            revid, kind, dirname, name, size, executable, sha1 = line[1][parent_index]
417
            if not revid:
418
                # not in this revision tree.
419
                continue
420
            parent_id = inv[inv.path2id(dirname.decode('utf8'))].file_id
421
            file_id = line[0][3].decode('utf8')
422
            entry = make_entry(kind, name.decode('utf8'), parent_id, file_id)
423
            entry.revision = revid.decode('utf8')
424
            if kind == 'file':
425
                entry.executable = executable
426
                entry.text_size = size
427
                entry.text_sha1 = sha1
428
            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.
429
        self._inventory = inv
430
431
    def get_parent_ids(self):
432
        """The parents of a tree in the dirstate are not cached."""
433
        return self._repository.get_revision(self._revision_id).parent_ids
434
435
    def lock_read(self):
436
        """Lock the tree for a set of operations."""
437
        self._locked = True
438
439
    def unlock(self):
440
        """Unlock, freeing any cache memory used during the lock."""
441
        # outside of a lock, the inventory is suspect: release it.
442
        self._inventory = None
443
        self._locked = False