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
44
conflicts as _mod_conflicts,
61
from bzrlib.transport import get_transport
65
from bzrlib import symbol_versioning
66
from bzrlib.decorators import needs_read_lock, needs_write_lock
67
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID, entry_factory
68
from bzrlib.lockable_files import LockableFiles, TransportLock
69
from bzrlib.lockdir import LockDir
70
import bzrlib.mutabletree
71
from bzrlib.mutabletree import needs_tree_write_lock
72
from bzrlib.osutils import (
84
from bzrlib.trace import mutter, note
85
from bzrlib.transport.local import LocalTransport
86
from bzrlib.tree import InterTree
87
from bzrlib.progress import DummyProgress, ProgressPhase
88
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
89
from bzrlib.rio import RioReader, rio_file, Stanza
90
from bzrlib.symbol_versioning import (deprecated_passed,
98
from bzrlib.tree import Tree
99
from bzrlib.workingtree import WorkingTree, WorkingTree3, WorkingTreeFormat3
102
class WorkingTree4(WorkingTree3):
103
"""This is the Format 4 working tree.
105
This differs from WorkingTree3 by:
106
- having a consolidated internal dirstate.
107
- not having a regular inventory attribute.
109
This is new in bzr TODO FIXME SETMEBEFORE MERGE.
112
def __init__(self, basedir,
117
"""Construct a WorkingTree for basedir.
119
If the branch is not supplied, it is opened automatically.
120
If the branch is supplied, it must be the branch for this basedir.
121
(branch.base is not cross checked, because for remote branches that
122
would be meaningless).
124
self._format = _format
125
self.bzrdir = _bzrdir
126
from bzrlib.hashcache import HashCache
127
from bzrlib.trace import note, mutter
128
assert isinstance(basedir, basestring), \
129
"base directory %r is not a string" % basedir
130
basedir = safe_unicode(basedir)
131
mutter("opening working tree %r", basedir)
132
self._branch = branch
133
assert isinstance(self.branch, bzrlib.branch.Branch), \
134
"branch %r is not a Branch" % self.branch
135
self.basedir = realpath(basedir)
136
# if branch is at our basedir and is a format 6 or less
137
# assume all other formats have their own control files.
138
assert isinstance(_control_files, LockableFiles), \
139
"_control_files must be a LockableFiles, not %r" % _control_files
140
self._control_files = _control_files
141
# update the whole cache up front and write to disk if anything changed;
142
# in the future we might want to do this more selectively
143
# two possible ways offer themselves : in self._unlock, write the cache
144
# if needed, or, when the cache sees a change, append it to the hash
145
# cache file, and have the parser take the most recent entry for a
147
cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
148
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
150
# is this scan needed ? it makes things kinda slow.
159
# during a read or write lock these objects are set, and are
160
# None the rest of the time.
161
self._dirstate = None
162
self._inventory = None
165
@needs_tree_write_lock
166
def _add(self, files, ids, kinds):
167
"""See MutableTree._add."""
168
state = self.current_dirstate()
169
for f, file_id, kind in zip(files, ids, kinds):
174
file_id = generate_ids.gen_file_id(f)
175
# deliberately add the file with no cached stat or sha1
176
# - on the first access it will be gathered, and we can
177
# always change this once tests are all passing.
178
state.add(f, file_id, kind, None, '')
181
def current_dirstate(self):
182
"""Return the current dirstate object.
184
This is not part of the tree interface and only exposed for ease of
187
:raises errors.NotWriteLocked: when not in a lock.
188
XXX: This should probably be errors.NotLocked.
190
if not self._control_files._lock_count:
191
raise errors.ObjectNotLocked(self)
192
if self._dirstate is not None:
193
return self._dirstate
194
local_path = self.bzrdir.get_workingtree_transport(None
195
).local_abspath('dirstate')
196
self._dirstate = dirstate.DirState.on_file(local_path)
197
return self._dirstate
199
def filter_unversioned_files(self, paths):
200
"""Filter out paths that are not versioned.
202
:return: set of paths.
204
# TODO: make a generic multi-bisect routine roughly that should list
205
# the paths, then process one half at a time recursively, and feed the
206
# results of each bisect in further still
207
paths = sorted(paths)
209
state = self.current_dirstate()
210
# TODO we want a paths_to_dirblocks helper I think
212
dirname, basename = os.path.split(path.encode('utf8'))
213
_, _, _, path_is_versioned = state._get_block_entry_index(
214
dirname, basename, 0)
215
if path_is_versioned:
220
"""Write all cached data to disk."""
221
if self._control_files._lock_mode != 'w':
222
raise errors.NotWriteLocked(self)
223
self.current_dirstate().save()
224
self._inventory = None
227
def _generate_inventory(self):
228
"""Create and set self.inventory from the dirstate object.
230
This is relatively expensive: we have to walk the entire dirstate.
231
Ideally we would not, and can deprecate this function.
233
#: uncomment to trap on inventory requests.
234
# import pdb;pdb.set_trace()
235
state = self.current_dirstate()
236
state._read_dirblocks_if_needed()
237
root_key, current_entry = self._get_entry(path='')
238
current_id = root_key[2]
239
assert current_entry[0][0] == 'd' # directory
240
inv = Inventory(root_id=current_id)
241
# Turn some things into local variables
242
minikind_to_kind = dirstate.DirState._minikind_to_kind
243
factory = entry_factory
244
utf8_decode = cache_utf8._utf8_decode
246
# we could do this straight out of the dirstate; it might be fast
247
# and should be profiled - RBC 20070216
248
parent_ies = {'' : inv.root}
249
for block in state._dirblocks[1:]: # skip the root
252
parent_ie = parent_ies[block[0]]
254
# all the paths in this block are not versioned in this tree
256
for key, entry in block[1]:
257
minikind, link_or_sha1, size, executable, stat = entry[0]
258
if minikind in ('a', 'r'): # absent, relocated
259
# a parent tree only entry
262
name_unicode = utf8_decode(name)[0]
264
kind = minikind_to_kind[minikind]
265
inv_entry = factory[kind](file_id, name_unicode,
268
# not strictly needed: working tree
269
#entry.executable = executable
270
#entry.text_size = size
271
#entry.text_sha1 = sha1
273
elif kind == 'directory':
274
# add this entry to the parent map.
275
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
276
# These checks cost us around 40ms on a 55k entry tree
277
assert file_id not in inv_byid
278
assert name_unicode not in parent_ie.children
279
inv_byid[file_id] = inv_entry
280
parent_ie.children[name_unicode] = inv_entry
281
self._inventory = inv
283
def _get_entry(self, file_id=None, path=None):
284
"""Get the dirstate row for file_id or path.
286
If either file_id or path is supplied, it is used as the key to lookup.
287
If both are supplied, the fastest lookup is used, and an error is
288
raised if they do not both point at the same row.
290
:param file_id: An optional unicode file_id to be looked up.
291
:param path: An optional unicode path to be looked up.
292
:return: The dirstate row tuple for path/file_id, or (None, None)
294
if file_id is None and path is None:
295
raise errors.BzrError('must supply file_id or path')
296
state = self.current_dirstate()
298
path = path.encode('utf8')
299
return state._get_entry(0, fileid_utf8=file_id, path_utf8=path)
301
def get_file_sha1(self, file_id, path=None, stat_value=None):
302
# check file id is valid unconditionally.
303
key, details = self._get_entry(file_id=file_id, path=path)
304
assert key is not None, 'what error should this raise'
306
# if row stat is valid, use cached sha1, else, get a new sha1.
308
path = os.path.join(*key[0:2]).decode('utf8')
309
return self._hashcache.get_sha1(path, stat_value)
311
def _get_inventory(self):
312
"""Get the inventory for the tree. This is only valid within a lock."""
313
if self._inventory is not None:
314
return self._inventory
315
self._generate_inventory()
316
return self._inventory
318
inventory = property(_get_inventory,
319
doc="Inventory of this Tree")
322
def get_parent_ids(self):
323
"""See Tree.get_parent_ids.
325
This implementation requests the ids list from the dirstate file.
327
return self.current_dirstate().get_parent_ids()
330
def get_root_id(self):
331
"""Return the id of this trees root"""
332
return self._get_entry(path='')[0][2]
334
def has_id(self, file_id):
335
state = self.current_dirstate()
336
file_id = osutils.safe_file_id(file_id)
337
row, parents = self._get_entry(file_id=file_id)
340
return osutils.lexists(pathjoin(
341
self.basedir, row[0].decode('utf8'), row[1].decode('utf8')))
344
def id2path(self, fileid):
345
fileid = osutils.safe_file_id(fileid)
346
inv = self._get_inventory()
347
return inv.id2path(fileid)
348
# TODO: jam 20070222 At present dirstate is very slow at id => path,
349
# while inventory is very fast at it. So for now, just generate
350
# the inventory and do the id => path check.
351
# In the future, we want to make dirstate better at id=>path
352
# checks so that we don't have to create the inventory.
353
# state = self.current_dirstate()
354
# key, tree_details = state._get_entry(0, fileid_utf8=fileid)
355
# return os.path.join(*key[0:2]).decode('utf8')
359
"""Iterate through file_ids for this tree.
361
file_ids are in a WorkingTree if they are in the working inventory
362
and the working file exists.
365
for key, tree_details in self.current_dirstate()._iter_entries():
366
if tree_details[0][0] in ('a', 'r'): # absent, relocated
367
# not relevant to the working tree
369
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
370
if osutils.lexists(path):
371
result.append(key[2])
375
def _last_revision(self):
376
"""See Mutable.last_revision."""
377
parent_ids = self.current_dirstate().get_parent_ids()
383
@needs_tree_write_lock
384
def move(self, from_paths, to_dir=None, after=False, **kwargs):
385
"""See WorkingTree.move()."""
389
state = self.current_dirstate()
391
# check for deprecated use of signature
393
to_dir = kwargs.get('to_name', None)
395
raise TypeError('You must supply a target directory')
397
symbol_versioning.warn('The parameter to_name was deprecated'
398
' in version 0.13. Use to_dir instead',
401
assert not isinstance(from_paths, basestring)
402
to_dir_utf8 = to_dir.encode('utf8')
403
to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
404
# check destination directory
405
# get the details for it
406
to_entry_block_index, to_entry_entry_index, dir_present, entry_present = \
407
state._get_block_entry_index(to_entry_dirname, to_basename, 0)
408
if not entry_present:
409
raise errors.BzrMoveFailedError('', to_dir,
410
errors.NotInWorkingDirectory(to_dir))
411
to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
412
# get a handle on the block itself.
413
to_block_index = state._ensure_block(
414
to_entry_block_index, to_entry_entry_index, to_dir_utf8)
415
to_block = state._dirblocks[to_block_index]
416
to_abs = self.abspath(to_dir)
417
if not isdir(to_abs):
418
raise errors.BzrMoveFailedError('',to_dir,
419
errors.NotADirectory(to_abs))
421
if to_entry[1][0][0] != 'd':
422
raise errors.BzrMoveFailedError('',to_dir,
423
errors.NotADirectory(to_abs))
425
if self._inventory is not None:
426
update_inventory = True
428
to_dir_ie = inv[to_dir_id]
429
to_dir_id = to_entry[0][2]
431
update_inventory = False
433
# create rename entries and tuples
434
for from_rel in from_paths:
435
# from_rel is 'pathinroot/foo/bar'
436
from_dirname, from_tail = os.path.split(from_rel)
437
from_dirname = from_dirname.encode('utf8')
438
from_entry = self._get_entry(path=from_rel)
439
if from_entry == (None, None):
440
raise errors.BzrMoveFailedError(from_rel,to_dir,
441
errors.NotVersionedError(path=str(from_rel)))
443
from_id = from_entry[0][2]
444
to_rel = pathjoin(to_dir, from_tail)
445
item_to_entry = self._get_entry(path=to_rel)
446
if item_to_entry != (None, None):
447
raise errors.BzrMoveFailedError(from_rel, to_rel,
448
"Target is already versioned.")
450
if from_rel == to_rel:
451
raise errors.BzrMoveFailedError(from_rel, to_rel,
452
"Source and target are identical.")
454
from_missing = not self.has_filename(from_rel)
455
to_missing = not self.has_filename(to_rel)
462
raise errors.BzrMoveFailedError(from_rel, to_rel,
463
errors.NoSuchFile(path=to_rel,
464
extra="New file has not been created yet"))
466
# neither path exists
467
raise errors.BzrRenameFailedError(from_rel, to_rel,
468
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
470
if from_missing: # implicitly just update our path mapping
473
raise errors.RenameFailedFilesExist(from_rel, to_rel,
474
extra="(Use --after to update the Bazaar id)")
477
def rollback_rename():
478
"""A single rename has failed, roll it back."""
480
for rollback in reversed(rollbacks):
484
import pdb;pdb.set_trace()
489
# perform the disk move first - its the most likely failure point.
490
from_rel_abs = self.abspath(from_rel)
491
to_rel_abs = self.abspath(to_rel)
493
osutils.rename(from_rel_abs, to_rel_abs)
495
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
496
rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
498
# perform the rename in the inventory next if needed: its easy
502
from_entry = inv[from_id]
503
current_parent = from_entry.parent_id
504
inv.rename(from_id, to_dir_id, from_tail)
506
lambda: inv.rename(from_id, current_parent, from_tail))
507
# finally do the rename in the dirstate, which is a little
508
# tricky to rollback, but least likely to need it.
509
basename = from_tail.encode('utf8')
510
old_block_index, old_entry_index, dir_present, file_present = \
511
state._get_block_entry_index(from_dirname, basename, 0)
512
old_block = state._dirblocks[old_block_index][1]
513
old_entry_details = old_block[old_entry_index][1]
515
from_key = old_block[old_entry_index][0]
516
to_key = ((to_block[0],) + from_key[1:3])
517
state._make_absent(old_block[old_entry_index])
518
minikind = old_entry_details[0][0]
519
kind = dirstate.DirState._minikind_to_kind[minikind]
521
lambda:state.update_minimal(from_key,
523
num_present_parents=len(old_entry_details) - 1,
524
executable=old_entry_details[0][3],
525
fingerprint=old_entry_details[0][1],
526
packed_stat=old_entry_details[0][4],
527
size=old_entry_details[0][2],
528
id_index=state._get_id_index(),
529
path_utf8=from_rel.encode('utf8')))
530
# create new row in current block
531
state.update_minimal(to_key,
533
num_present_parents=len(old_entry_details) - 1,
534
executable=old_entry_details[0][3],
535
fingerprint=old_entry_details[0][1],
536
packed_stat=old_entry_details[0][4],
537
size=old_entry_details[0][2],
538
id_index=state._get_id_index(),
539
path_utf8=to_rel.encode('utf8'))
540
added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
541
new_entry = to_block[added_entry_index]
542
rollbacks.append(lambda:state._make_absent(new_entry))
543
if new_entry[1][0][0] == 'd':
544
import pdb;pdb.set_trace()
545
# if a directory, rename all the contents of child blocks
546
# adding rollbacks as each is inserted to remove them and
547
# restore the original
548
# TODO: large scale slice assignment.
550
# save old list region
551
# move up or down the old region
552
# add rollback to move the region back
553
# assign new list to new region
558
state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
561
return #rename_tuples
564
"""Initialize the state in this tree to be a new tree."""
568
def path2id(self, path):
569
"""Return the id for path in this tree."""
570
entry = self._get_entry(path=path)
571
if entry == (None, None):
575
def paths2ids(self, paths, trees=[], require_versioned=True):
576
"""See Tree.paths2ids().
578
This specialisation fast-paths the case where all the trees are in the
583
parents = self.get_parent_ids()
585
if not (isinstance(tree, DirStateRevisionTree) and tree._revision_id in
587
return super(WorkingTree4, self).paths2ids(paths, trees, require_versioned)
588
search_indexes = [0] + [1 + parents.index(tree._revision_id) for tree in trees]
589
# -- make all paths utf8 --
592
paths_utf8.add(path.encode('utf8'))
594
# -- paths is now a utf8 path set --
595
# -- get the state object and prepare it.
596
state = self.current_dirstate()
597
state._read_dirblocks_if_needed()
598
def _entries_for_path(path):
599
"""Return a list with all the entries that match path for all ids.
601
dirname, basename = os.path.split(path)
602
key = (dirname, basename, '')
603
block_index, present = state._find_block_index_from_key(key)
605
# the block which should contain path is absent.
608
block = state._dirblocks[block_index][1]
609
entry_index, _ = state._find_entry_index(key, block)
610
# we may need to look at multiple entries at this path: walk while the paths match.
611
while (entry_index < len(block) and
612
block[entry_index][0][0:2] == key[0:2]):
613
result.append(block[entry_index])
616
if require_versioned:
617
# -- check all supplied paths are versioned in all search trees. --
620
path_entries = _entries_for_path(path)
622
# this specified path is not present at all: error
623
all_versioned = False
625
found_versioned = False
626
# for each id at this path
627
for entry in path_entries:
629
for index in search_indexes:
630
if entry[1][index][0] != 'a': # absent
631
found_versioned = True
632
# all good: found a versioned cell
634
if not found_versioned:
635
# none of the indexes was not 'absent' at all ids for this
637
all_versioned = False
639
if not all_versioned:
640
raise errors.PathsNotVersionedError(paths)
641
# -- remove redundancy in supplied paths to prevent over-scanning --
644
other_paths = paths.difference(set([path]))
645
if not osutils.is_inside_any(other_paths, path):
646
# this is a top level path, we must check it.
647
search_paths.add(path)
649
# for all search_indexs in each path at or under each element of
650
# search_paths, if the detail is relocated: add the id, and add the
651
# relocated path as one to search if its not searched already. If the
652
# detail is not relocated, add the id.
653
searched_paths = set()
655
def _process_entry(entry):
656
"""Look at search_indexes within entry.
658
If a specific tree's details are relocated, add the relocation
659
target to search_paths if not searched already. If it is absent, do
660
nothing. Otherwise add the id to found_ids.
662
for index in search_indexes:
663
if entry[1][index][0] == 'r': # relocated
664
if not osutils.is_inside_any(searched_paths, entry[1][index][1]):
665
search_paths.add(entry[1][index][1])
666
elif entry[1][index][0] != 'a': # absent
667
found_ids.add(entry[0][2])
669
current_root = search_paths.pop()
670
searched_paths.add(current_root)
671
# process the entries for this containing directory: the rest will be
672
# found by their parents recursively.
673
root_entries = _entries_for_path(current_root)
675
# this specified path is not present at all, skip it.
677
for entry in root_entries:
678
_process_entry(entry)
679
initial_key = (current_root, '', '')
680
block_index, _ = state._find_block_index_from_key(initial_key)
681
while (block_index < len(state._dirblocks) and
682
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
683
for entry in state._dirblocks[block_index][1]:
684
_process_entry(entry)
688
def read_working_inventory(self):
689
"""Read the working inventory.
691
This is a meaningless operation for dirstate, but we obey it anyhow.
693
return self.inventory
696
def revision_tree(self, revision_id):
697
"""See Tree.revision_tree.
699
WorkingTree4 supplies revision_trees for any basis tree.
701
revision_id = osutils.safe_revision_id(revision_id)
702
dirstate = self.current_dirstate()
703
parent_ids = dirstate.get_parent_ids()
704
if revision_id not in parent_ids:
705
raise errors.NoSuchRevisionInTree(self, revision_id)
706
if revision_id in dirstate.get_ghosts():
707
raise errors.NoSuchRevisionInTree(self, revision_id)
708
return DirStateRevisionTree(dirstate, revision_id,
709
self.branch.repository)
711
@needs_tree_write_lock
712
def set_last_revision(self, new_revision):
713
"""Change the last revision in the working tree."""
714
new_revision = osutils.safe_revision_id(new_revision)
715
parents = self.get_parent_ids()
716
if new_revision in (NULL_REVISION, None):
717
assert len(parents) < 2, (
718
"setting the last parent to none with a pending merge is "
720
self.set_parent_ids([])
722
self.set_parent_ids([new_revision] + parents[1:],
723
allow_leftmost_as_ghost=True)
725
@needs_tree_write_lock
726
def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
727
"""Set the parent ids to revision_ids.
729
See also set_parent_trees. This api will try to retrieve the tree data
730
for each element of revision_ids from the trees repository. If you have
731
tree data already available, it is more efficient to use
732
set_parent_trees rather than set_parent_ids. set_parent_ids is however
733
an easier API to use.
735
:param revision_ids: The revision_ids to set as the parent ids of this
736
working tree. Any of these may be ghosts.
738
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
740
for revision_id in revision_ids:
742
revtree = self.branch.repository.revision_tree(revision_id)
743
# TODO: jam 20070213 KnitVersionedFile raises
744
# RevisionNotPresent rather than NoSuchRevision if a
745
# given revision_id is not present. Should Repository be
746
# catching it and re-raising NoSuchRevision?
747
except (errors.NoSuchRevision, errors.RevisionNotPresent):
749
trees.append((revision_id, revtree))
750
self.set_parent_trees(trees,
751
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
753
@needs_tree_write_lock
754
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
755
"""Set the parents of the working tree.
757
:param parents_list: A list of (revision_id, tree) tuples.
758
If tree is None, then that element is treated as an unreachable
759
parent tree - i.e. a ghost.
761
dirstate = self.current_dirstate()
762
if len(parents_list) > 0:
763
if not allow_leftmost_as_ghost and parents_list[0][1] is None:
764
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
767
# convert absent trees to the null tree, which we convert back to
769
for rev_id, tree in parents_list:
770
rev_id = osutils.safe_revision_id(rev_id)
772
real_trees.append((rev_id, tree))
774
real_trees.append((rev_id,
775
self.branch.repository.revision_tree(None)))
776
ghosts.append(rev_id)
777
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
780
def _set_root_id(self, file_id):
781
"""See WorkingTree.set_root_id."""
782
state = self.current_dirstate()
783
state.set_path_id('', file_id)
784
self._dirty = state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED
787
"""Unlock in format 4 trees needs to write the entire dirstate."""
788
if self._control_files._lock_count == 1:
789
self._write_hashcache_if_dirty()
790
# eventually we should do signature checking during read locks for
792
if self._control_files._lock_mode == 'w':
795
self._dirstate = None
796
self._inventory = None
797
# reverse order of locking.
799
return self._control_files.unlock()
803
@needs_tree_write_lock
804
def unversion(self, file_ids):
805
"""Remove the file ids in file_ids from the current versioned set.
807
When a file_id is unversioned, all of its children are automatically
810
:param file_ids: The file ids to stop versioning.
811
:raises: NoSuchId if any fileid is not currently versioned.
815
state = self.current_dirstate()
816
state._read_dirblocks_if_needed()
817
ids_to_unversion = set()
818
for file_id in file_ids:
819
ids_to_unversion.add(osutils.safe_file_id(file_id))
820
paths_to_unversion = set()
822
# check if the root is to be unversioned, if so, assert for now.
823
# walk the state marking unversioned things as absent.
824
# if there are any un-unversioned ids at the end, raise
825
for key, details in state._dirblocks[0][1]:
826
if (details[0][0] not in ('a', 'r') and # absent or relocated
827
key[2] in ids_to_unversion):
828
# I haven't written the code to unversion / yet - it should be
830
raise errors.BzrError('Unversioning the / is not currently supported')
831
details_length = len(state._dirblocks[0][1][0][1])
833
while block_index < len(state._dirblocks):
834
# process one directory at a time.
835
block = state._dirblocks[block_index]
836
# first check: is the path one to remove - it or its children
838
for path in paths_to_unversion:
839
if (block[0].startswith(path) and
840
(len(block[0]) == len(path) or
841
block[0][len(path)] == '/')):
842
# this entire block should be deleted - its the block for a
843
# path to unversion; or the child of one
846
# TODO: trim paths_to_unversion as we pass by paths
848
# this block is to be deleted: process it.
849
# TODO: we can special case the no-parents case and
850
# just forget the whole block.
852
while entry_index < len(block[1]):
853
if not state._make_absent(block[1][entry_index]):
855
# go to the next block. (At the moment we dont delete empty
860
while entry_index < len(block[1]):
861
entry = block[1][entry_index]
862
if (entry[1][0][0] in ('a', 'r') or # absent, relocated
864
entry[0][2] not in ids_to_unversion):
865
# ^ not an id to unversion
868
if entry[1][0][0] == 'd':
869
paths_to_unversion.add(os.path.join(*entry[0][0:2]))
870
if not state._make_absent(entry):
872
# we have unversioned this id
873
ids_to_unversion.remove(entry[0][2])
876
raise errors.NoSuchId(self, iter(ids_to_unversion).next())
878
# have to change the legacy inventory too.
879
if self._inventory is not None:
880
for file_id in file_ids:
881
self._inventory.remove_recursive_id(file_id)
883
@needs_tree_write_lock
884
def _write_inventory(self, inv):
885
"""Write inventory as the current inventory."""
886
assert not self._dirty, "attempting to write an inventory when the dirstate is dirty will cause data loss"
887
self.current_dirstate().set_state_from_inventory(inv)
892
class WorkingTreeFormat4(WorkingTreeFormat3):
893
"""The first consolidated dirstate working tree format.
896
- exists within a metadir controlling .bzr
897
- includes an explicit version marker for the workingtree control
898
files, separate from the BzrDir format
899
- modifies the hash cache format
900
- is new in bzr TODO FIXME SETBEFOREMERGE
901
- uses a LockDir to guard access to it.
904
def get_format_string(self):
905
"""See WorkingTreeFormat.get_format_string()."""
906
return "Bazaar Working Tree format 4\n"
908
def get_format_description(self):
909
"""See WorkingTreeFormat.get_format_description()."""
910
return "Working tree format 4"
912
def initialize(self, a_bzrdir, revision_id=None):
913
"""See WorkingTreeFormat.initialize().
915
revision_id allows creating a working tree at a different
916
revision than the branch is at.
918
revision_id = osutils.safe_revision_id(revision_id)
919
if not isinstance(a_bzrdir.transport, LocalTransport):
920
raise errors.NotLocalUrl(a_bzrdir.transport.base)
921
transport = a_bzrdir.get_workingtree_transport(self)
922
control_files = self._open_control_files(a_bzrdir)
923
control_files.create_lock()
924
control_files.lock_write()
925
control_files.put_utf8('format', self.get_format_string())
926
branch = a_bzrdir.open_branch()
927
if revision_id is None:
928
revision_id = branch.last_revision()
929
local_path = transport.local_abspath('dirstate')
930
dirstate.DirState.initialize(local_path)
931
wt = WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
935
_control_files=control_files)
939
#wt.current_dirstate().set_path_id('', NEWROOT)
940
wt.set_last_revision(revision_id)
942
basis = wt.basis_tree()
944
transform.build_tree(basis, wt)
947
control_files.unlock()
952
def _open(self, a_bzrdir, control_files):
953
"""Open the tree itself.
955
:param a_bzrdir: the dir for the tree.
956
:param control_files: the control files for the tree.
958
return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
959
branch=a_bzrdir.open_branch(),
962
_control_files=control_files)
965
class DirStateRevisionTree(Tree):
966
"""A revision tree pulling the inventory from a dirstate."""
968
def __init__(self, dirstate, revision_id, repository):
969
self._dirstate = dirstate
970
self._revision_id = osutils.safe_revision_id(revision_id)
971
self._repository = repository
972
self._inventory = None
975
def annotate_iter(self, file_id):
976
"""See Tree.annotate_iter"""
977
w = self._repository.weave_store.get_weave(file_id,
978
self._repository.get_transaction())
979
return w.annotate_iter(self.inventory[file_id].revision)
981
def _comparison_data(self, entry, path):
982
"""See Tree._comparison_data."""
984
return None, False, None
985
# trust the entry as RevisionTree does, but this may not be
986
# sensible: the entry might not have come from us?
987
return entry.kind, entry.executable, None
989
def _file_size(self, entry, stat_value):
990
return entry.text_size
992
def filter_unversioned_files(self, paths):
993
"""Filter out paths that are not versioned.
995
:return: set of paths.
997
pred = self.has_filename
998
return set((p for p in paths if not pred(p)))
1000
def _get_entry(self, file_id=None, path=None):
1001
"""Get the dirstate row for file_id or path.
1003
If either file_id or path is supplied, it is used as the key to lookup.
1004
If both are supplied, the fastest lookup is used, and an error is
1005
raised if they do not both point at the same row.
1007
:param file_id: An optional unicode file_id to be looked up.
1008
:param path: An optional unicode path to be looked up.
1009
:return: The dirstate row tuple for path/file_id, or (None, None)
1011
if file_id is None and path is None:
1012
raise errors.BzrError('must supply file_id or path')
1013
file_id = osutils.safe_file_id(file_id)
1014
if path is not None:
1015
path = path.encode('utf8')
1016
parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
1017
return self._dirstate._get_entry(parent_index, fileid_utf8=file_id, path_utf8=path)
1019
def _generate_inventory(self):
1020
"""Create and set self.inventory from the dirstate object.
1022
This is relatively expensive: we have to walk the entire dirstate.
1023
Ideally we would not, and instead would """
1024
assert self._locked, 'cannot generate inventory of an unlocked '\
1025
'dirstate revision tree'
1026
# separate call for profiling - makes it clear where the costs are.
1027
self._dirstate._read_dirblocks_if_needed()
1028
assert self._revision_id in self._dirstate.get_parent_ids(), \
1029
'parent %s has disappeared from %s' % (
1030
self._revision_id, self._dirstate.get_parent_ids())
1031
parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
1032
# This is identical now to the WorkingTree _generate_inventory except
1033
# for the tree index use.
1034
root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1035
current_id = root_key[2]
1036
assert current_entry[parent_index][0] == 'd'
1037
inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1038
inv.root.revision = current_entry[parent_index][4]
1039
# Turn some things into local variables
1040
minikind_to_kind = dirstate.DirState._minikind_to_kind
1041
factory = entry_factory
1042
utf8_decode = cache_utf8._utf8_decode
1043
inv_byid = inv._byid
1044
# we could do this straight out of the dirstate; it might be fast
1045
# and should be profiled - RBC 20070216
1046
parent_ies = {'' : inv.root}
1047
for block in self._dirstate._dirblocks[1:]: #skip root
1050
parent_ie = parent_ies[dirname]
1052
# all the paths in this block are not versioned in this tree
1054
for key, entry in block[1]:
1055
minikind, link_or_sha1, size, executable, revid = entry[parent_index]
1056
if minikind in ('a', 'r'): # absent, relocated
1060
name_unicode = utf8_decode(name)[0]
1062
kind = minikind_to_kind[minikind]
1063
inv_entry = factory[kind](file_id, name_unicode,
1065
inv_entry.revision = revid
1067
inv_entry.executable = executable
1068
inv_entry.text_size = size
1069
inv_entry.text_sha1 = link_or_sha1
1070
elif kind == 'directory':
1071
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1072
elif kind == 'symlink':
1073
inv_entry.executable = False
1074
inv_entry.text_size = size
1075
inv_entry.symlink_target = utf8_decode(link_or_sha1)[0]
1077
raise Exception, kind
1078
# These checks cost us around 40ms on a 55k entry tree
1079
assert file_id not in inv_byid
1080
assert name_unicode not in parent_ie.children
1081
inv_byid[file_id] = inv_entry
1082
parent_ie.children[name_unicode] = inv_entry
1083
self._inventory = inv
1085
def get_file_sha1(self, file_id, path=None, stat_value=None):
1086
# TODO: if path is present, fast-path on that, as inventory
1087
# might not be present
1088
ie = self.inventory[file_id]
1089
if ie.kind == "file":
1093
def get_file(self, file_id):
1094
return StringIO(self.get_file_text(file_id))
1096
def get_file_lines(self, file_id):
1097
ie = self.inventory[file_id]
1098
return self._repository.weave_store.get_weave(file_id,
1099
self._repository.get_transaction()).get_lines(ie.revision)
1101
def get_file_size(self, file_id):
1102
return self.inventory[file_id].text_size
1104
def get_file_text(self, file_id):
1105
return ''.join(self.get_file_lines(file_id))
1107
def get_revision_id(self):
1108
"""Return the revision id for this tree."""
1109
return self._revision_id
1111
def _get_inventory(self):
1112
if self._inventory is not None:
1113
return self._inventory
1114
self._generate_inventory()
1115
return self._inventory
1117
inventory = property(_get_inventory,
1118
doc="Inventory of this Tree")
1120
def get_parent_ids(self):
1121
"""The parents of a tree in the dirstate are not cached."""
1122
return self._repository.get_revision(self._revision_id).parent_ids
1124
def has_filename(self, filename):
1125
return bool(self.path2id(filename))
1127
def kind(self, file_id):
1128
return self.inventory[file_id].kind
1130
def is_executable(self, file_id, path=None):
1131
ie = self.inventory[file_id]
1132
if ie.kind != "file":
1134
return ie.executable
1136
def list_files(self, include_root=False):
1137
# We use a standard implementation, because DirStateRevisionTree is
1138
# dealing with one of the parents of the current state
1139
inv = self._get_inventory()
1140
entries = inv.iter_entries()
1141
if self.inventory.root is not None and not include_root:
1143
for path, entry in entries:
1144
yield path, 'V', entry.kind, entry.file_id, entry
1146
def lock_read(self):
1147
"""Lock the tree for a set of operations."""
1148
if not self._locked:
1149
self._repository.lock_read()
1153
def path2id(self, path):
1154
"""Return the id for path in this tree."""
1155
# lookup by path: faster than splitting and walking the ivnentory.
1156
entry = self._get_entry(path=path)
1157
if entry == (None, None):
1162
"""Unlock, freeing any cache memory used during the lock."""
1163
# outside of a lock, the inventory is suspect: release it.
1165
if not self._locked:
1166
self._inventory = None
1167
self._locked = False
1168
self._repository.unlock()
1170
def walkdirs(self, prefix=""):
1171
# TODO: jam 20070215 This is the cheap way by cheating and using the
1172
# RevisionTree implementation.
1173
# This should be cleaned up to use the much faster Dirstate code
1174
# This is a little tricky, though, because the dirstate is
1175
# indexed by current path, not by parent path.
1176
# So for now, we just build up the parent inventory, and extract
1177
# it the same way RevisionTree does.
1178
_directory = 'directory'
1179
inv = self._get_inventory()
1180
top_id = inv.path2id(prefix)
1184
pending = [(prefix, top_id)]
1187
relpath, file_id = pending.pop()
1188
# 0 - relpath, 1- file-id
1190
relroot = relpath + '/'
1193
# FIXME: stash the node in pending
1194
entry = inv[file_id]
1195
for name, child in entry.sorted_children():
1196
toppath = relroot + name
1197
dirblock.append((toppath, name, child.kind, None,
1198
child.file_id, child.kind
1200
yield (relpath, entry.file_id), dirblock
1201
# push the user specified dirs from dirblock
1202
for dir in reversed(dirblock):
1203
if dir[2] == _directory:
1204
pending.append((dir[0], dir[4]))
1207
class InterDirStateTree(InterTree):
1208
"""Fast path optimiser for changes_from with dirstate trees."""
1211
def revision_tree_from_workingtree(tree):
1212
"""Create a revision tree from a working tree."""
1213
revid = tree.commit('save tree', allow_pointless=True)
1214
return tree.branch.repository.revision_tree(revid)
1215
_from_tree_converter = revision_tree_from_workingtree
1216
_matching_from_tree_format = WorkingTreeFormat4()
1217
_matching_to_tree_format = WorkingTreeFormat4()
1218
_to_tree_converter = staticmethod(lambda x: x)
1221
def is_compatible(source, target):
1222
# the target must be a dirstate working tree
1223
if not isinstance(target, WorkingTree4):
1225
# the source must be a revtreee or dirstate rev tree.
1226
if not isinstance(source,
1227
(revisiontree.RevisionTree, DirStateRevisionTree)):
1229
# the source revid must be in the target dirstate
1230
if not (source._revision_id == NULL_REVISION or
1231
source._revision_id in target.get_parent_ids()):
1232
# TODO: what about ghosts? it may well need to
1233
# check for them explicitly.
1237
InterTree.register_optimiser(InterDirStateTree)