1
# Copyright (C) 2005, 2006 Canonical Ltd
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.
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.
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
17
"""WorkingTree4 format and implementation.
19
WorkingTree4 provides the dirstate based working tree logic.
21
To get a WorkingTree, call bzrdir.open_workingtree() or
22
WorkingTree.open(dir).
27
from bzrlib.lazy_import import lazy_import
28
lazy_import(globals(), """
29
from bisect import bisect_left
31
from copy import deepcopy
42
conflicts as _mod_conflicts,
58
from bzrlib.transport import get_transport
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, make_entry
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 (
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,
94
from bzrlib.tree import Tree
95
from bzrlib.workingtree import WorkingTree3, WorkingTreeFormat3
98
class WorkingTree4(WorkingTree3):
99
"""This is the Format 4 working tree.
101
This differs from WorkingTree3 by:
102
- having a consolidated internal dirstate.
103
- not having a regular inventory attribute.
105
This is new in bzr TODO FIXME SETMEBEFORE MERGE.
108
def __init__(self, basedir,
113
"""Construct a WorkingTree for basedir.
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).
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
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)
146
# is this scan needed ? it makes things kinda slow.
154
self._parent_revisions = None
156
# during a read or write lock these objects are set, and are
157
# None the rest of the time.
158
self._dirstate = None
159
self._inventory = None
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):
171
file_id = generate_ids.gen_file_id(f)
172
stat = os.lstat(self.abspath(f))
173
sha1 = '1' * 20 # FIXME: DIRSTATE MERGE BLOCKER
174
state.add(f, file_id, kind, stat, sha1)
177
def current_dirstate(self):
178
"""Return the current dirstate object.
180
This is not part of the tree interface and only exposed for ease of
183
:raises errors.NotWriteLocked: when not in a lock.
184
XXX: This should probably be errors.NotLocked.
186
if not self._control_files._lock_count:
187
raise errors.ObjectNotLocked(self)
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
195
def _generate_inventory(self):
196
"""Create and set self.inventory from the dirstate object.
198
This is relatively expensive: we have to walk the entire dirstate.
199
Ideally we would not, and can deprecate this function.
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'))
206
dirname, name, kind, fileid_utf8, size, stat, link_or_sha1 = line[0]
208
# not in this revision tree.
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)
214
#entry.executable = executable
215
#entry.text_size = size
216
#entry.text_sha1 = sha1
219
self._inventory = inv
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
228
inventory = property(_get_inventory,
229
doc="Inventory of this Tree")
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')
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('/')
246
"""Iterate through file_ids for this tree.
248
file_ids are in a WorkingTree if they are in the working inventory
249
and the working file exists.
252
for row, parents in self.current_dirstate()._iter_rows():
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'))
261
"""Initialize the state in this tree to be a new tree."""
262
self._parent_revisions = [NULL_REVISION]
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')
276
def revision_tree(self, revision_id):
277
"""See Tree.revision_tree.
279
WorkingTree4 supplies revision_trees for any basis tree.
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)
289
def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
290
"""Set the parent ids to revision_ids.
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.
298
:param revision_ids: The revision_ids to set as the parent ids of this
299
working tree. Any of these may be ghosts.
302
for revision_id in revision_ids:
304
revtree = self.branch.repository.revision_tree(revision_id)
305
except errors.NoSuchRevision:
307
trees.append((revision_id, revtree))
308
self.set_parent_trees(trees,
309
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
312
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
313
"""Set the parents of the working tree.
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.
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)
325
# convert absent trees to the null tree, which we convert back to
327
for rev_id, tree in parents_list:
329
real_trees.append((rev_id, tree))
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)
337
def _set_root_id(self, file_id):
338
"""See WorkingTree.set_root_id."""
339
self.current_dirstate().set_path_id('', file_id)
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
349
if self._control_files._lock_mode == 'w':
352
self._dirstate = None
353
self._inventory = None
354
# reverse order of locking.
356
return self._control_files.unlock()
361
"""Write all cached data to disk."""
362
self.current_dirstate().save()
363
self._inventory = None
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)
375
class WorkingTreeFormat4(WorkingTreeFormat3):
376
"""The first consolidated dirstate working tree 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.
387
def get_format_string(self):
388
"""See WorkingTreeFormat.get_format_string()."""
389
return "Bazaar Working Tree format 4\n"
391
def get_format_description(self):
392
"""See WorkingTreeFormat.get_format_description()."""
393
return "Working tree format 4"
395
def initialize(self, a_bzrdir, revision_id=None):
396
"""See WorkingTreeFormat.initialize().
398
revision_id allows creating a working tree at a different
399
revision than the branch is at.
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('.'),
417
_control_files=control_files)
421
#wt.current_dirstate().set_path_id('', NEWROOT)
422
wt.set_last_revision(revision_id)
424
transform.build_tree(wt.basis_tree(), wt)
426
control_files.unlock()
431
def _open(self, a_bzrdir, control_files):
432
"""Open the tree itself.
434
:param a_bzrdir: the dir for the tree.
435
:param control_files: the control files for the tree.
437
return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
438
branch=a_bzrdir.open_branch(),
441
_control_files=control_files)
444
class DirStateRevisionTree(Tree):
445
"""A revision tree pulling the inventory from a dirstate."""
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
454
def _comparison_data(self, entry, path):
455
"""See Tree._comparison_data."""
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
462
def _file_size(self, entry, stat_value):
463
return entry.text_size
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":
473
def get_file_size(self, file_id):
474
return self.inventory[file_id].text_size
476
def _get_inventory(self):
477
if self._inventory is not None:
478
return self._inventory
479
self._generate_inventory()
480
return self._inventory
482
inventory = property(_get_inventory,
483
doc="Inventory of this Tree")
485
def _generate_inventory(self):
486
"""Create and set self.inventory from the dirstate object.
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())
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)
501
revid, kind, dirname, name, size, executable, sha1 = line[1][parent_index]
503
# not in this revision tree.
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')
510
entry.executable = executable
511
entry.text_size = size
512
entry.text_sha1 = sha1
514
self._inventory = inv
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
521
"""Lock the tree for a set of operations."""
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