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).
25
from cStringIO import StringIO
28
from bzrlib.lazy_import import lazy_import
29
lazy_import(globals(), """
30
from bisect import bisect_left
32
from copy import deepcopy
43
conflicts as _mod_conflicts,
59
from bzrlib.transport import get_transport
63
from bzrlib import symbol_versioning
64
from bzrlib.decorators import needs_read_lock, needs_write_lock
65
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID, entry_factory
66
from bzrlib.lockable_files import LockableFiles, TransportLock
67
from bzrlib.lockdir import LockDir
68
import bzrlib.mutabletree
69
from bzrlib.mutabletree import needs_tree_write_lock
70
from bzrlib.osutils import (
82
from bzrlib.trace import mutter, note
83
from bzrlib.transport.local import LocalTransport
84
from bzrlib.progress import DummyProgress, ProgressPhase
85
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
86
from bzrlib.rio import RioReader, rio_file, Stanza
87
from bzrlib.symbol_versioning import (deprecated_passed,
95
from bzrlib.tree import Tree
96
from bzrlib.workingtree import WorkingTree, WorkingTree3, WorkingTreeFormat3
99
class WorkingTree4(WorkingTree3):
100
"""This is the Format 4 working tree.
102
This differs from WorkingTree3 by:
103
- having a consolidated internal dirstate.
104
- not having a regular inventory attribute.
106
This is new in bzr TODO FIXME SETMEBEFORE MERGE.
109
def __init__(self, basedir,
114
"""Construct a WorkingTree for basedir.
116
If the branch is not supplied, it is opened automatically.
117
If the branch is supplied, it must be the branch for this basedir.
118
(branch.base is not cross checked, because for remote branches that
119
would be meaningless).
121
self._format = _format
122
self.bzrdir = _bzrdir
123
from bzrlib.hashcache import HashCache
124
from bzrlib.trace import note, mutter
125
assert isinstance(basedir, basestring), \
126
"base directory %r is not a string" % basedir
127
basedir = safe_unicode(basedir)
128
mutter("opening working tree %r", basedir)
129
self._branch = branch
130
assert isinstance(self.branch, bzrlib.branch.Branch), \
131
"branch %r is not a Branch" % self.branch
132
self.basedir = realpath(basedir)
133
# if branch is at our basedir and is a format 6 or less
134
# assume all other formats have their own control files.
135
assert isinstance(_control_files, LockableFiles), \
136
"_control_files must be a LockableFiles, not %r" % _control_files
137
self._control_files = _control_files
138
# update the whole cache up front and write to disk if anything changed;
139
# in the future we might want to do this more selectively
140
# two possible ways offer themselves : in self._unlock, write the cache
141
# if needed, or, when the cache sees a change, append it to the hash
142
# cache file, and have the parser take the most recent entry for a
144
cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
145
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
147
# is this scan needed ? it makes things kinda slow.
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
162
@needs_tree_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):
171
file_id = generate_ids.gen_file_id(f)
172
# deliberately add the file with no cached stat or sha1
173
# - on the first access it will be gathered, and we can
174
# always change this once tests are all passing.
175
state.add(f, file_id, kind, None, '')
178
def current_dirstate(self):
179
"""Return the current dirstate object.
181
This is not part of the tree interface and only exposed for ease of
184
:raises errors.NotWriteLocked: when not in a lock.
185
XXX: This should probably be errors.NotLocked.
187
if not self._control_files._lock_count:
188
raise errors.ObjectNotLocked(self)
189
if self._dirstate is not None:
190
return self._dirstate
191
local_path = self.bzrdir.get_workingtree_transport(None
192
).local_abspath('dirstate')
193
self._dirstate = dirstate.DirState.on_file(local_path)
194
return self._dirstate
196
def filter_unversioned_files(self, paths):
197
"""Filter out paths that are not versioned.
199
:return: set of paths.
201
# TODO: make a generic multi-bisect routine roughly that should list
202
# the paths, then process one half at a time recursively, and feed the
203
# results of each bisect in further still
204
paths = sorted(paths)
206
state = self.current_dirstate()
207
# TODO we want a paths_to_dirblocks helper I think
209
dirname, basename = os.path.split(path.encode('utf8'))
210
_, _, _, path_is_versioned = state._get_block_entry_index(
211
dirname, basename, 0)
212
if path_is_versioned:
217
"""Write all cached data to disk."""
218
if self._control_files._lock_mode != 'w':
219
raise errors.NotWriteLocked(self)
220
self.current_dirstate().save()
221
self._inventory = None
224
def _generate_inventory(self):
225
"""Create and set self.inventory from the dirstate object.
227
This is relatively expensive: we have to walk the entire dirstate.
228
Ideally we would not, and can deprecate this function.
230
#: uncomment to trap on inventory requests.
231
# import pdb;pdb.set_trace()
232
state = self.current_dirstate()
233
state._read_dirblocks_if_needed()
234
root_key, current_entry = self._get_entry(path='')
235
current_id = root_key[2]
236
assert current_entry[0][0] == 'directory'
237
inv = Inventory(root_id=current_id)
238
# we could do this straight out of the dirstate; it might be fast
239
# and should be profiled - RBC 20070216
240
parent_ids = {'' : inv.root.file_id}
241
for block in state._dirblocks[1:]: # skip the root
244
parent_id = parent_ids[block[0]]
246
# all the paths in this block are not versioned in this tree
248
for key, entry in block[1]:
249
if entry[0][0] in ('absent', 'relocated'):
250
# a parent tree only entry
253
name_unicode = name.decode('utf8')
255
kind, link_or_sha1, size, executable, stat = entry[0]
256
inv_entry = entry_factory[kind](file_id, name_unicode, parent_id)
258
# not strictly needed: working tree
259
#entry.executable = executable
260
#entry.text_size = size
261
#entry.text_sha1 = sha1
263
elif kind == 'directory':
264
# add this entry to the parent map.
265
parent_ids[(dirname + '/' + name).strip('/')] = file_id
267
self._inventory = inv
269
def _get_entry(self, file_id=None, path=None):
270
"""Get the dirstate row for file_id or path.
272
If either file_id or path is supplied, it is used as the key to lookup.
273
If both are supplied, the fastest lookup is used, and an error is
274
raised if they do not both point at the same row.
276
:param file_id: An optional unicode file_id to be looked up.
277
:param path: An optional unicode path to be looked up.
278
:return: The dirstate row tuple for path/file_id, or (None, None)
280
if file_id is None and path is None:
281
raise errors.BzrError('must supply file_id or path')
282
state = self.current_dirstate()
284
path = path.encode('utf8')
285
return state._get_entry(0, fileid_utf8=file_id, path_utf8=path)
287
def get_file_sha1(self, file_id, path=None, stat_value=None):
288
# check file id is valid unconditionally.
289
key, details = self._get_entry(file_id=file_id, path=path)
290
assert key is not None, 'what error should this raise'
292
# if row stat is valid, use cached sha1, else, get a new sha1.
294
path = os.path.join(*key[0:2]).decode('utf8')
295
return self._hashcache.get_sha1(path, stat_value)
297
def _get_inventory(self):
298
"""Get the inventory for the tree. This is only valid within a lock."""
299
if self._inventory is not None:
300
return self._inventory
301
self._generate_inventory()
302
return self._inventory
304
inventory = property(_get_inventory,
305
doc="Inventory of this Tree")
308
def get_parent_ids(self):
309
"""See Tree.get_parent_ids.
311
This implementation requests the ids list from the dirstate file.
313
return self.current_dirstate().get_parent_ids()
316
def get_root_id(self):
317
"""Return the id of this trees root"""
318
return self._get_entry(path='')[0][2]
320
def has_id(self, file_id):
321
state = self.current_dirstate()
322
file_id = osutils.safe_file_id(file_id)
323
row, parents = self._get_entry(file_id=file_id)
326
return osutils.lexists(pathjoin(
327
self.basedir, row[0].decode('utf8'), row[1].decode('utf8')))
330
def id2path(self, fileid):
331
state = self.current_dirstate()
332
fileid = osutils.safe_file_id(fileid)
333
key, tree_details = state._get_entry(0, fileid_utf8=fileid)
334
return os.path.join(*key[0:2]).decode('utf8')
338
"""Iterate through file_ids for this tree.
340
file_ids are in a WorkingTree if they are in the working inventory
341
and the working file exists.
344
for key, tree_details in self.current_dirstate()._iter_entries():
345
if tree_details[0][0] in ('absent', 'relocated'):
346
# not relevant to the working tree
348
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
349
if osutils.lexists(path):
350
result.append(key[2])
354
def _last_revision(self):
355
"""See Mutable.last_revision."""
356
parent_ids = self.current_dirstate().get_parent_ids()
362
@needs_tree_write_lock
363
def move(self, from_paths, to_dir=None, after=False, **kwargs):
364
"""See WorkingTree.move()."""
368
state = self.current_dirstate()
370
# check for deprecated use of signature
372
to_dir = kwargs.get('to_name', None)
374
raise TypeError('You must supply a target directory')
376
symbol_versioning.warn('The parameter to_name was deprecated'
377
' in version 0.13. Use to_dir instead',
380
assert not isinstance(from_paths, basestring)
381
to_dir_utf8 = to_dir.encode('utf8')
382
to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
383
# check destination directory
384
# get the details for it
385
to_entry_block_index, to_entry_entry_index, dir_present, entry_present = \
386
state._get_block_entry_index(to_entry_dirname, to_basename, 0)
387
if not entry_present:
388
raise errors.BzrMoveFailedError('', to_dir,
389
errors.NotInWorkingDirectory(to_dir))
390
to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
391
# get a handle on the block itself.
392
to_block_index = state._ensure_block(
393
to_entry_block_index, to_entry_entry_index, to_dir_utf8)
394
to_block = state._dirblocks[to_block_index]
395
to_abs = self.abspath(to_dir)
396
if not isdir(to_abs):
397
raise errors.BzrMoveFailedError('',to_dir,
398
errors.NotADirectory(to_abs))
400
if to_entry[1][0][0] != 'directory':
401
raise errors.BzrMoveFailedError('',to_dir,
402
errors.NotADirectory(to_abs))
404
if self._inventory is not None:
405
update_inventory = True
407
to_dir_ie = inv[to_dir_id]
408
to_dir_id = to_entry[0][2]
410
update_inventory = False
412
# create rename entries and tuples
413
for from_rel in from_paths:
414
# from_rel is 'pathinroot/foo/bar'
415
from_dirname, from_tail = os.path.split(from_rel)
416
from_dirname = from_dirname.encode('utf8')
417
from_entry = self._get_entry(path=from_rel)
418
if from_entry == (None, None):
419
raise errors.BzrMoveFailedError(from_rel,to_dir,
420
errors.NotVersionedError(path=str(from_rel)))
422
from_id = from_entry[0][2]
423
to_rel = pathjoin(to_dir, from_tail)
424
item_to_entry = self._get_entry(path=to_rel)
425
if item_to_entry != (None, None):
426
raise errors.BzrMoveFailedError(from_rel, to_rel,
427
"Target is already versioned.")
429
if from_rel == to_rel:
430
raise errors.BzrMoveFailedError(from_rel, to_rel,
431
"Source and target are identical.")
433
from_missing = not self.has_filename(from_rel)
434
to_missing = not self.has_filename(to_rel)
441
raise errors.BzrMoveFailedError(from_rel, to_rel,
442
errors.NoSuchFile(path=to_rel,
443
extra="New file has not been created yet"))
445
# neither path exists
446
raise errors.BzrRenameFailedError(from_rel, to_rel,
447
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
449
if from_missing: # implicitly just update our path mapping
452
raise errors.RenameFailedFilesExist(from_rel, to_rel,
453
extra="(Use --after to update the Bazaar id)")
456
def rollback_rename():
457
"""A single rename has failed, roll it back."""
459
for rollback in reversed(rollbacks):
463
import pdb;pdb.set_trace()
468
# perform the disk move first - its the most likely failure point.
469
from_rel_abs = self.abspath(from_rel)
470
to_rel_abs = self.abspath(to_rel)
472
osutils.rename(from_rel_abs, to_rel_abs)
474
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
475
rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
477
# perform the rename in the inventory next if needed: its easy
481
from_entry = inv[from_id]
482
current_parent = from_entry.parent_id
483
inv.rename(from_id, to_dir_id, from_tail)
485
lambda: inv.rename(from_id, current_parent, from_tail))
486
# finally do the rename in the dirstate, which is a little
487
# tricky to rollback, but least likely to need it.
488
basename = from_tail.encode('utf8')
489
old_block_index, old_entry_index, dir_present, file_present = \
490
state._get_block_entry_index(from_dirname, basename, 0)
491
old_block = state._dirblocks[old_block_index][1]
492
old_entry_details = old_block[old_entry_index][1]
494
from_key = old_block[old_entry_index][0]
495
to_key = ((to_block[0],) + from_key[1:3])
496
state._make_absent(old_block[old_entry_index])
498
lambda:state.update_minimal(from_key,
499
old_entry_details[0][0],
500
num_present_parents=len(old_entry_details) - 1,
501
executable=old_entry_details[0][3],
502
fingerprint=old_entry_details[0][1],
503
packed_stat=old_entry_details[0][4],
504
size=old_entry_details[0][2],
505
id_index=state._get_id_index(),
506
path_utf8=from_rel.encode('utf8')))
507
# create new row in current block
508
state.update_minimal(to_key,
509
old_entry_details[0][0],
510
num_present_parents=len(old_entry_details) - 1,
511
executable=old_entry_details[0][3],
512
fingerprint=old_entry_details[0][1],
513
packed_stat=old_entry_details[0][4],
514
size=old_entry_details[0][2],
515
id_index=state._get_id_index(),
516
path_utf8=to_rel.encode('utf8'))
517
added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
518
new_entry = to_block[added_entry_index]
519
rollbacks.append(lambda:state._make_absent(new_entry))
520
if new_entry[1][0][0] == 'directory':
521
import pdb;pdb.set_trace()
522
# if a directory, rename all the contents of child blocks
523
# adding rollbacks as each is inserted to remove them and
524
# restore the original
525
# TODO: large scale slice assignment.
527
# save old list region
528
# move up or down the old region
529
# add rollback to move the region back
530
# assign new list to new region
535
state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
538
return #rename_tuples
541
"""Initialize the state in this tree to be a new tree."""
545
def path2id(self, path):
546
"""Return the id for path in this tree."""
547
entry = self._get_entry(path=path)
548
if entry == (None, None):
552
def paths2ids(self, paths, trees=[], require_versioned=True):
553
"""See Tree.paths2ids().
555
This specialisation fast-paths the case where all the trees are in the
560
parents = self.get_parent_ids()
562
if not (isinstance(tree, DirStateRevisionTree) and tree._revision_id in
564
return super(WorkingTree4, self).paths2ids(paths, trees, require_versioned)
565
search_indexes = [0] + [1 + parents.index(tree._revision_id) for tree in trees]
566
# -- make all paths utf8 --
569
paths_utf8.add(path.encode('utf8'))
571
# -- paths is now a utf8 path set --
572
# -- get the state object and prepare it.
573
state = self.current_dirstate()
574
state._read_dirblocks_if_needed()
575
def _entries_for_path(path):
576
"""Return a list with all the entries that match path for all ids.
578
dirname, basename = os.path.split(path)
579
key = (dirname, basename, '')
580
block_index, present = state._find_block_index_from_key(key)
582
# the block which should contain path is absent.
585
block = state._dirblocks[block_index][1]
586
entry_index, _ = state._find_entry_index(key, block)
587
# we may need to look at multiple entries at this path: walk while the paths match.
588
while (entry_index < len(block) and
589
block[entry_index][0][0:2] == key[0:2]):
590
result.append(block[entry_index])
593
if require_versioned:
594
# -- check all supplied paths are versioned in all search trees. --
597
path_entries = _entries_for_path(path)
599
# this specified path is not present at all: error
600
all_versioned = False
602
found_versioned = False
603
# for each id at this path
604
for entry in path_entries:
606
for index in search_indexes:
607
if entry[1][index][0] != 'absent':
608
found_versioned = True
609
# all good: found a versioned cell
611
if not found_versioned:
612
# non of the indexes was not 'absent' at all ids for this
614
all_versioned = False
616
if not all_versioned:
617
raise errors.PathsNotVersionedError(paths)
618
# -- remove redundancy in supplied paths to prevent over-scanning --
621
other_paths = paths.difference(set([path]))
622
if not osutils.is_inside_any(other_paths, path):
623
# this is a top level path, we must check it.
624
search_paths.add(path)
626
# for all search_indexs in each path at or under each element of
627
# search_paths, if the detail is relocated: add the id, and add the
628
# relocated path as one to search if its not searched already. If the
629
# detail is not relocated, add the id.
630
searched_paths = set()
632
def _process_entry(entry):
633
"""Look at search_indexes within entry.
635
If a specific tree's details are relocated, add the relocation
636
target to search_paths if not searched already. If it is absent, do
637
nothing. Otherwise add the id to found_ids.
639
for index in search_indexes:
640
if entry[1][index][0] == 'relocated':
641
if not osutils.is_inside_any(searched_paths, entry[1][index][1]):
642
search_paths.add(entry[1][index][1])
643
elif entry[1][index][0] != 'absent':
644
found_ids.add(entry[0][2])
646
current_root = search_paths.pop()
647
searched_paths.add(current_root)
648
# process the entries for this containing directory: the rest will be
649
# found by their parents recursively.
650
root_entries = _entries_for_path(current_root)
652
# this specified path is not present at all, skip it.
654
for entry in root_entries:
655
_process_entry(entry)
656
initial_key = (current_root, '', '')
657
block_index, _ = state._find_block_index_from_key(initial_key)
658
while (block_index < len(state._dirblocks) and
659
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
660
for entry in state._dirblocks[block_index][1]:
661
_process_entry(entry)
665
def read_working_inventory(self):
666
"""Read the working inventory.
668
This is a meaningless operation for dirstate, but we obey it anyhow.
670
return self.inventory
673
def revision_tree(self, revision_id):
674
"""See Tree.revision_tree.
676
WorkingTree4 supplies revision_trees for any basis tree.
678
revision_id = osutils.safe_revision_id(revision_id)
679
dirstate = self.current_dirstate()
680
parent_ids = dirstate.get_parent_ids()
681
if revision_id not in parent_ids:
682
raise errors.NoSuchRevisionInTree(self, revision_id)
683
if revision_id in dirstate.get_ghosts():
684
raise errors.NoSuchRevisionInTree(self, revision_id)
685
return DirStateRevisionTree(dirstate, revision_id,
686
self.branch.repository)
688
@needs_tree_write_lock
689
def set_last_revision(self, new_revision):
690
"""Change the last revision in the working tree."""
691
new_revision = osutils.safe_revision_id(new_revision)
692
parents = self.get_parent_ids()
693
if new_revision in (NULL_REVISION, None):
694
assert len(parents) < 2, (
695
"setting the last parent to none with a pending merge is "
697
self.set_parent_ids([])
699
self.set_parent_ids([new_revision] + parents[1:],
700
allow_leftmost_as_ghost=True)
702
@needs_tree_write_lock
703
def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
704
"""Set the parent ids to revision_ids.
706
See also set_parent_trees. This api will try to retrieve the tree data
707
for each element of revision_ids from the trees repository. If you have
708
tree data already available, it is more efficient to use
709
set_parent_trees rather than set_parent_ids. set_parent_ids is however
710
an easier API to use.
712
:param revision_ids: The revision_ids to set as the parent ids of this
713
working tree. Any of these may be ghosts.
715
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
717
for revision_id in revision_ids:
719
revtree = self.branch.repository.revision_tree(revision_id)
720
# TODO: jam 20070213 KnitVersionedFile raises
721
# RevisionNotPresent rather than NoSuchRevision if a
722
# given revision_id is not present. Should Repository be
723
# catching it and re-raising NoSuchRevision?
724
except (errors.NoSuchRevision, errors.RevisionNotPresent):
726
trees.append((revision_id, revtree))
727
self.set_parent_trees(trees,
728
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
730
@needs_tree_write_lock
731
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
732
"""Set the parents of the working tree.
734
:param parents_list: A list of (revision_id, tree) tuples.
735
If tree is None, then that element is treated as an unreachable
736
parent tree - i.e. a ghost.
738
dirstate = self.current_dirstate()
739
if len(parents_list) > 0:
740
if not allow_leftmost_as_ghost and parents_list[0][1] is None:
741
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
744
# convert absent trees to the null tree, which we convert back to
746
for rev_id, tree in parents_list:
747
rev_id = osutils.safe_revision_id(rev_id)
749
real_trees.append((rev_id, tree))
751
real_trees.append((rev_id,
752
self.branch.repository.revision_tree(None)))
753
ghosts.append(rev_id)
754
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
757
def _set_root_id(self, file_id):
758
"""See WorkingTree.set_root_id."""
759
state = self.current_dirstate()
760
state.set_path_id('', file_id)
761
self._dirty = state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED
764
"""Unlock in format 4 trees needs to write the entire dirstate."""
765
if self._control_files._lock_count == 1:
766
self._write_hashcache_if_dirty()
767
# eventually we should do signature checking during read locks for
769
if self._control_files._lock_mode == 'w':
772
self._dirstate = None
773
self._inventory = None
774
# reverse order of locking.
776
return self._control_files.unlock()
780
@needs_tree_write_lock
781
def unversion(self, file_ids):
782
"""Remove the file ids in file_ids from the current versioned set.
784
When a file_id is unversioned, all of its children are automatically
787
:param file_ids: The file ids to stop versioning.
788
:raises: NoSuchId if any fileid is not currently versioned.
792
state = self.current_dirstate()
793
state._read_dirblocks_if_needed()
794
ids_to_unversion = set()
795
for file_id in file_ids:
796
ids_to_unversion.add(osutils.safe_file_id(file_id))
797
paths_to_unversion = set()
799
# check if the root is to be unversioned, if so, assert for now.
800
# walk the state marking unversioned things as absent.
801
# if there are any un-unversioned ids at the end, raise
802
for key, details in state._dirblocks[0][1]:
803
if (details[0][0] not in ('absent', 'relocated') and
804
key[2] in ids_to_unversion):
805
# I haven't written the code to unversion / yet - it should be
807
raise errors.BzrError('Unversioning the / is not currently supported')
808
details_length = len(state._dirblocks[0][1][0][1])
810
while block_index < len(state._dirblocks):
811
# process one directory at a time.
812
block = state._dirblocks[block_index]
813
# first check: is the path one to remove - it or its children
815
for path in paths_to_unversion:
816
if (block[0].startswith(path) and
817
(len(block[0]) == len(path) or
818
block[0][len(path)] == '/')):
819
# this entire block should be deleted - its the block for a
820
# path to unversion; or the child of one
823
# TODO: trim paths_to_unversion as we pass by paths
825
# this block is to be deleted: process it.
826
# TODO: we can special case the no-parents case and
827
# just forget the whole block.
829
while entry_index < len(block[1]):
830
if not state._make_absent(block[1][entry_index]):
832
# go to the next block. (At the moment we dont delete empty
837
while entry_index < len(block[1]):
838
entry = block[1][entry_index]
839
if (entry[1][0][0] in ('absent', 'relocated') or
841
entry[0][2] not in ids_to_unversion):
842
# ^ not an id to unversion
845
if entry[1][0][0] == 'directory':
846
paths_to_unversion.add(os.path.join(*entry[0][0:2]))
847
if not state._make_absent(entry):
849
# we have unversioned this id
850
ids_to_unversion.remove(entry[0][2])
853
raise errors.NoSuchId(self, iter(ids_to_unversion).next())
855
# have to change the legacy inventory too.
856
if self._inventory is not None:
857
for file_id in file_ids:
858
self._inventory.remove_recursive_id(file_id)
860
@needs_tree_write_lock
861
def _write_inventory(self, inv):
862
"""Write inventory as the current inventory."""
863
assert not self._dirty, "attempting to write an inventory when the dirstate is dirty will cause data loss"
864
self.current_dirstate().set_state_from_inventory(inv)
869
class WorkingTreeFormat4(WorkingTreeFormat3):
870
"""The first consolidated dirstate working tree format.
873
- exists within a metadir controlling .bzr
874
- includes an explicit version marker for the workingtree control
875
files, separate from the BzrDir format
876
- modifies the hash cache format
877
- is new in bzr TODO FIXME SETBEFOREMERGE
878
- uses a LockDir to guard access to it.
881
def get_format_string(self):
882
"""See WorkingTreeFormat.get_format_string()."""
883
return "Bazaar Working Tree format 4\n"
885
def get_format_description(self):
886
"""See WorkingTreeFormat.get_format_description()."""
887
return "Working tree format 4"
889
def initialize(self, a_bzrdir, revision_id=None):
890
"""See WorkingTreeFormat.initialize().
892
revision_id allows creating a working tree at a different
893
revision than the branch is at.
895
revision_id = osutils.safe_revision_id(revision_id)
896
if not isinstance(a_bzrdir.transport, LocalTransport):
897
raise errors.NotLocalUrl(a_bzrdir.transport.base)
898
transport = a_bzrdir.get_workingtree_transport(self)
899
control_files = self._open_control_files(a_bzrdir)
900
control_files.create_lock()
901
control_files.lock_write()
902
control_files.put_utf8('format', self.get_format_string())
903
branch = a_bzrdir.open_branch()
904
if revision_id is None:
905
revision_id = branch.last_revision()
906
local_path = transport.local_abspath('dirstate')
907
dirstate.DirState.initialize(local_path)
908
wt = WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
912
_control_files=control_files)
916
#wt.current_dirstate().set_path_id('', NEWROOT)
917
wt.set_last_revision(revision_id)
919
basis = wt.basis_tree()
921
transform.build_tree(basis, wt)
924
control_files.unlock()
929
def _open(self, a_bzrdir, control_files):
930
"""Open the tree itself.
932
:param a_bzrdir: the dir for the tree.
933
:param control_files: the control files for the tree.
935
return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
936
branch=a_bzrdir.open_branch(),
939
_control_files=control_files)
942
class DirStateRevisionTree(Tree):
943
"""A revision tree pulling the inventory from a dirstate."""
945
def __init__(self, dirstate, revision_id, repository):
946
self._dirstate = dirstate
947
self._revision_id = osutils.safe_revision_id(revision_id)
948
self._repository = repository
949
self._inventory = None
952
def annotate_iter(self, file_id):
953
"""See Tree.annotate_iter"""
954
w = self._repository.weave_store.get_weave(file_id,
955
self._repository.get_transaction())
956
return w.annotate_iter(self.inventory[file_id].revision)
958
def _comparison_data(self, entry, path):
959
"""See Tree._comparison_data."""
961
return None, False, None
962
# trust the entry as RevisionTree does, but this may not be
963
# sensible: the entry might not have come from us?
964
return entry.kind, entry.executable, None
966
def _file_size(self, entry, stat_value):
967
return entry.text_size
969
def filter_unversioned_files(self, paths):
970
"""Filter out paths that are not versioned.
972
:return: set of paths.
974
pred = self.has_filename
975
return set((p for p in paths if not pred(p)))
977
def _get_entry(self, file_id=None, path=None):
978
"""Get the dirstate row for file_id or path.
980
If either file_id or path is supplied, it is used as the key to lookup.
981
If both are supplied, the fastest lookup is used, and an error is
982
raised if they do not both point at the same row.
984
:param file_id: An optional unicode file_id to be looked up.
985
:param path: An optional unicode path to be looked up.
986
:return: The dirstate row tuple for path/file_id, or (None, None)
988
if file_id is None and path is None:
989
raise errors.BzrError('must supply file_id or path')
990
file_id = osutils.safe_file_id(file_id)
992
path = path.encode('utf8')
993
parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
994
return self._dirstate._get_entry(parent_index, fileid_utf8=file_id, path_utf8=path)
996
def _generate_inventory(self):
997
"""Create and set self.inventory from the dirstate object.
999
This is relatively expensive: we have to walk the entire dirstate.
1000
Ideally we would not, and instead would """
1001
assert self._locked, 'cannot generate inventory of an unlocked '\
1002
'dirstate revision tree'
1003
# separate call for profiling - makes it clear where the costs are.
1004
self._dirstate._read_dirblocks_if_needed()
1005
assert self._revision_id in self._dirstate.get_parent_ids(), \
1006
'parent %s has disappeared from %s' % (
1007
self._revision_id, self._dirstate.get_parent_ids())
1008
parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
1009
# This is identical now to the WorkingTree _generate_inventory except
1010
# for the tree index use.
1011
root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1012
current_id = root_key[2]
1013
assert current_entry[parent_index][0] == 'directory'
1014
inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1015
inv.root.revision = current_entry[parent_index][4]
1016
# we could do this straight out of the dirstate; it might be fast
1017
# and should be profiled - RBC 20070216
1018
parent_ids = {'' : inv.root.file_id}
1019
for block in self._dirstate._dirblocks[1:]: #skip root
1022
parent_id = parent_ids[dirname]
1024
# all the paths in this block are not versioned in this tree
1026
for key, entry in block[1]:
1027
if entry[parent_index][0] in ('absent', 'relocated'):
1031
name_unicode = name.decode('utf8')
1033
kind, link_or_sha1, size, executable, revid = entry[parent_index]
1034
inv_entry = entry_factory[kind](file_id, name_unicode, parent_id)
1035
inv_entry.revision = revid
1037
inv_entry.executable = executable
1038
inv_entry.text_size = size
1039
inv_entry.text_sha1 = link_or_sha1
1040
elif kind == 'directory':
1041
parent_ids[(dirname + '/' + name).strip('/')] = file_id
1042
elif kind == 'symlink':
1043
inv_entry.executable = False
1044
inv_entry.text_size = size
1045
inv_entry.symlink_target = link_or_sha1.decode('utf8')
1047
raise Exception, kind
1049
self._inventory = inv
1051
def get_file_sha1(self, file_id, path=None, stat_value=None):
1052
# TODO: if path is present, fast-path on that, as inventory
1053
# might not be present
1054
ie = self.inventory[file_id]
1055
if ie.kind == "file":
1059
def get_file(self, file_id):
1060
return StringIO(self.get_file_text(file_id))
1062
def get_file_lines(self, file_id):
1063
ie = self.inventory[file_id]
1064
return self._repository.weave_store.get_weave(file_id,
1065
self._repository.get_transaction()).get_lines(ie.revision)
1067
def get_file_size(self, file_id):
1068
return self.inventory[file_id].text_size
1070
def get_file_text(self, file_id):
1071
return ''.join(self.get_file_lines(file_id))
1073
def get_revision_id(self):
1074
"""Return the revision id for this tree."""
1075
return self._revision_id
1077
def _get_inventory(self):
1078
if self._inventory is not None:
1079
return self._inventory
1080
self._generate_inventory()
1081
return self._inventory
1083
inventory = property(_get_inventory,
1084
doc="Inventory of this Tree")
1086
def get_parent_ids(self):
1087
"""The parents of a tree in the dirstate are not cached."""
1088
return self._repository.get_revision(self._revision_id).parent_ids
1090
def has_filename(self, filename):
1091
return bool(self.path2id(filename))
1093
def kind(self, file_id):
1094
return self.inventory[file_id].kind
1096
def is_executable(self, file_id, path=None):
1097
ie = self.inventory[file_id]
1098
if ie.kind != "file":
1100
return ie.executable
1102
def list_files(self, include_root=False):
1103
# We use a standard implementation, because DirStateRevisionTree is
1104
# dealing with one of the parents of the current state
1105
inv = self._get_inventory()
1106
entries = inv.iter_entries()
1107
if self.inventory.root is not None and not include_root:
1109
for path, entry in entries:
1110
yield path, 'V', entry.kind, entry.file_id, entry
1112
def lock_read(self):
1113
"""Lock the tree for a set of operations."""
1114
if not self._locked:
1115
self._repository.lock_read()
1119
def path2id(self, path):
1120
"""Return the id for path in this tree."""
1121
# lookup by path: faster than splitting and walking the ivnentory.
1122
entry = self._get_entry(path=path)
1123
if entry == (None, None):
1128
"""Unlock, freeing any cache memory used during the lock."""
1129
# outside of a lock, the inventory is suspect: release it.
1131
if not self._locked:
1132
self._inventory = None
1133
self._locked = False
1134
self._repository.unlock()
1136
def walkdirs(self, prefix=""):
1137
# TODO: jam 20070215 This is the cheap way by cheating and using the
1138
# RevisionTree implementation.
1139
# This should be cleaned up to use the much faster Dirstate code
1140
# This is a little tricky, though, because the dirstate is
1141
# indexed by current path, not by parent path.
1142
# So for now, we just build up the parent inventory, and extract
1143
# it the same way RevisionTree does.
1144
_directory = 'directory'
1145
inv = self._get_inventory()
1146
top_id = inv.path2id(prefix)
1150
pending = [(prefix, top_id)]
1153
relpath, file_id = pending.pop()
1154
# 0 - relpath, 1- file-id
1156
relroot = relpath + '/'
1159
# FIXME: stash the node in pending
1160
entry = inv[file_id]
1161
for name, child in entry.sorted_children():
1162
toppath = relroot + name
1163
dirblock.append((toppath, name, child.kind, None,
1164
child.file_id, child.kind
1166
yield (relpath, entry.file_id), dirblock
1167
# push the user specified dirs from dirblock
1168
for dir in reversed(dirblock):
1169
if dir[2] == _directory:
1170
pending.append((dir[0], dir[4]))