2608
1846
return ShelfManager(self, self._transport)
2611
class WorkingTree2(WorkingTree):
2612
"""This is the Format 2 working tree.
2614
This was the first weave based working tree.
2615
- uses os locks for locking.
2616
- uses the branch last-revision.
1849
class InventoryWorkingTree(WorkingTree,
1850
bzrlib.mutabletree.MutableInventoryTree):
1851
"""Base class for working trees that are inventory-oriented.
1853
The inventory is held in the `Branch` working-inventory, and the
1854
files are in a directory on disk.
1856
It is possible for a `WorkingTree` to have a filename which is
1857
not listed in the Inventory and vice versa.
2619
def __init__(self, *args, **kwargs):
2620
super(WorkingTree2, self).__init__(*args, **kwargs)
2621
# WorkingTree2 has more of a constraint that self._inventory must
2622
# exist. Because this is an older format, we don't mind the overhead
2623
# caused by the extra computation here.
2625
# Newer WorkingTree's should only have self._inventory set when they
2627
if self._inventory is None:
2628
self.read_working_inventory()
2630
def _get_check_refs(self):
2631
"""Return the references needed to perform a check of this tree."""
2632
return [('trees', self.last_revision())]
2634
def lock_tree_write(self):
2635
"""See WorkingTree.lock_tree_write().
2637
In Format2 WorkingTrees we have a single lock for the branch and tree
2638
so lock_tree_write() degrades to lock_write().
2640
self.branch.lock_write()
2642
return self._control_files.lock_write()
1860
def __init__(self, basedir='.',
1861
branch=DEPRECATED_PARAMETER,
1863
_control_files=None,
1867
"""Construct a InventoryWorkingTree instance. This is not a public API.
1869
:param branch: A branch to override probing for the branch.
1871
super(InventoryWorkingTree, self).__init__(basedir=basedir,
1872
branch=branch, _control_files=_control_files, _internal=_internal,
1873
_format=_format, _bzrdir=_bzrdir)
1875
if _inventory is None:
1876
# This will be acquired on lock_read() or lock_write()
1877
self._inventory_is_modified = False
1878
self._inventory = None
1880
# the caller of __init__ has provided an inventory,
1881
# we assume they know what they are doing - as its only
1882
# the Format factory and creation methods that are
1883
# permitted to do this.
1884
self._set_inventory(_inventory, dirty=False)
1886
def _set_inventory(self, inv, dirty):
1887
"""Set the internal cached inventory.
1889
:param inv: The inventory to set.
1890
:param dirty: A boolean indicating whether the inventory is the same
1891
logical inventory as whats on disk. If True the inventory is not
1892
the same and should be written to disk or data will be lost, if
1893
False then the inventory is the same as that on disk and any
1894
serialisation would be unneeded overhead.
1896
self._inventory = inv
1897
self._inventory_is_modified = dirty
1899
def _serialize(self, inventory, out_file):
1900
xml5.serializer_v5.write_inventory(self._inventory, out_file,
1903
def _deserialize(selt, in_file):
1904
return xml5.serializer_v5.read_inventory(in_file)
1906
@needs_tree_write_lock
1907
def _write_inventory(self, inv):
1908
"""Write inventory as the current inventory."""
1909
self._set_inventory(inv, dirty=True)
1912
# XXX: This method should be deprecated in favour of taking in a proper
1913
# new Inventory object.
1914
@needs_tree_write_lock
1915
def set_inventory(self, new_inventory_list):
1916
from bzrlib.inventory import (Inventory,
1920
inv = Inventory(self.get_root_id())
1921
for path, file_id, parent, kind in new_inventory_list:
1922
name = os.path.basename(path)
1925
# fixme, there should be a factory function inv,add_??
1926
if kind == 'directory':
1927
inv.add(InventoryDirectory(file_id, name, parent))
1928
elif kind == 'file':
1929
inv.add(InventoryFile(file_id, name, parent))
1930
elif kind == 'symlink':
1931
inv.add(InventoryLink(file_id, name, parent))
1933
raise errors.BzrError("unknown kind %r" % kind)
1934
self._write_inventory(inv)
1936
def _write_basis_inventory(self, xml):
1937
"""Write the basis inventory XML to the basis-inventory file"""
1938
path = self._basis_inventory_name()
1940
self._transport.put_file(path, sio,
1941
mode=self.bzrdir._get_file_mode())
1943
def _reset_data(self):
1944
"""Reset transient data that cannot be revalidated."""
1945
self._inventory_is_modified = False
1946
f = self._transport.get('inventory')
1948
result = self._deserialize(f)
1951
self._set_inventory(result, dirty=False)
1953
def _set_root_id(self, file_id):
1954
"""Set the root id for this tree, in a format specific manner.
1956
:param file_id: The file id to assign to the root. It must not be
1957
present in the current inventory or an error will occur. It must
1958
not be None, but rather a valid file id.
1960
inv = self._inventory
1961
orig_root_id = inv.root.file_id
1962
# TODO: it might be nice to exit early if there was nothing
1963
# to do, saving us from trigger a sync on unlock.
1964
self._inventory_is_modified = True
1965
# we preserve the root inventory entry object, but
1966
# unlinkit from the byid index
1967
del inv._byid[inv.root.file_id]
1968
inv.root.file_id = file_id
1969
# and link it into the index with the new changed id.
1970
inv._byid[inv.root.file_id] = inv.root
1971
# and finally update all children to reference the new id.
1972
# XXX: this should be safe to just look at the root.children
1973
# list, not the WHOLE INVENTORY.
1976
if entry.parent_id == orig_root_id:
1977
entry.parent_id = inv.root.file_id
1979
def all_file_ids(self):
1980
"""See Tree.iter_all_file_ids"""
1981
return set(self.inventory)
1983
def _cache_basis_inventory(self, new_revision):
1984
"""Cache new_revision as the basis inventory."""
1985
# TODO: this should allow the ready-to-use inventory to be passed in,
1986
# as commit already has that ready-to-use [while the format is the
1989
# this double handles the inventory - unpack and repack -
1990
# but is easier to understand. We can/should put a conditional
1991
# in here based on whether the inventory is in the latest format
1992
# - perhaps we should repack all inventories on a repository
1994
# the fast path is to copy the raw xml from the repository. If the
1995
# xml contains 'revision_id="', then we assume the right
1996
# revision_id is set. We must check for this full string, because a
1997
# root node id can legitimately look like 'revision_id' but cannot
1999
xml = self.branch.repository._get_inventory_xml(new_revision)
2000
firstline = xml.split('\n', 1)[0]
2001
if (not 'revision_id="' in firstline or
2002
'format="7"' not in firstline):
2003
inv = self.branch.repository._serializer.read_inventory_from_string(
2005
xml = self._create_basis_xml_from_inventory(new_revision, inv)
2006
self._write_basis_inventory(xml)
2007
except (errors.NoSuchRevision, errors.RevisionNotPresent):
2010
def _basis_inventory_name(self):
2011
return 'basis-inventory-cache'
2013
def _create_basis_xml_from_inventory(self, revision_id, inventory):
2014
"""Create the text that will be saved in basis-inventory"""
2015
inventory.revision_id = revision_id
2016
return xml7.serializer_v7.write_inventory_to_string(inventory)
2018
def read_basis_inventory(self):
2019
"""Read the cached basis inventory."""
2020
path = self._basis_inventory_name()
2021
return self._transport.get_bytes(path)
2024
def read_working_inventory(self):
2025
"""Read the working inventory.
2027
:raises errors.InventoryModified: read_working_inventory will fail
2028
when the current in memory inventory has been modified.
2030
# conceptually this should be an implementation detail of the tree.
2031
# XXX: Deprecate this.
2032
# ElementTree does its own conversion from UTF-8, so open in
2034
if self._inventory_is_modified:
2035
raise errors.InventoryModified(self)
2036
f = self._transport.get('inventory')
2038
result = self._deserialize(f)
2041
self._set_inventory(result, dirty=False)
2045
def get_root_id(self):
2046
"""Return the id of this trees root"""
2047
return self._inventory.root.file_id
2049
def has_id(self, file_id):
2050
# files that have been deleted are excluded
2051
inv = self.inventory
2052
if not inv.has_id(file_id):
2054
path = inv.id2path(file_id)
2055
return osutils.lexists(self.abspath(path))
2057
def has_or_had_id(self, file_id):
2058
if file_id == self.inventory.root.file_id:
2060
return self.inventory.has_id(file_id)
2062
__contains__ = has_id
2064
# should be deprecated - this is slow and in any case treating them as a
2065
# container is (we now know) bad style -- mbp 20070302
2066
## @deprecated_method(zero_fifteen)
2068
"""Iterate through file_ids for this tree.
2070
file_ids are in a WorkingTree if they are in the working inventory
2071
and the working file exists.
2073
inv = self._inventory
2074
for path, ie in inv.iter_entries():
2075
if osutils.lexists(self.abspath(path)):
2078
@needs_tree_write_lock
2079
def set_last_revision(self, new_revision):
2080
"""Change the last revision in the working tree."""
2081
if self._change_last_revision(new_revision):
2082
self._cache_basis_inventory(new_revision)
2084
@needs_tree_write_lock
2085
def reset_state(self, revision_ids=None):
2086
"""Reset the state of the working tree.
2088
This does a hard-reset to a last-known-good state. This is a way to
2089
fix if something got corrupted (like the .bzr/checkout/dirstate file)
2091
if revision_ids is None:
2092
revision_ids = self.get_parent_ids()
2093
if not revision_ids:
2094
rt = self.branch.repository.revision_tree(
2095
_mod_revision.NULL_REVISION)
2097
rt = self.branch.repository.revision_tree(revision_ids[0])
2098
self._write_inventory(rt.inventory)
2099
self.set_parent_ids(revision_ids)
2102
"""Write the in memory inventory to disk."""
2103
# TODO: Maybe this should only write on dirty ?
2104
if self._control_files._lock_mode != 'w':
2105
raise errors.NotWriteLocked(self)
2107
self._serialize(self._inventory, sio)
2109
self._transport.put_file('inventory', sio,
2110
mode=self.bzrdir._get_file_mode())
2111
self._inventory_is_modified = False
2114
def get_file_sha1(self, file_id, path=None, stat_value=None):
2116
path = self._inventory.id2path(file_id)
2117
return self._hashcache.get_sha1(path, stat_value)
2119
def get_file_mtime(self, file_id, path=None):
2120
"""See Tree.get_file_mtime."""
2122
path = self.inventory.id2path(file_id)
2123
return os.lstat(self.abspath(path)).st_mtime
2125
def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
2126
file_id = self.path2id(path)
2128
# For unversioned files on win32, we just assume they are not
2131
return self._inventory[file_id].executable
2133
def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
2134
mode = stat_result.st_mode
2135
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
2137
if not supports_executable():
2138
def is_executable(self, file_id, path=None):
2139
return self._inventory[file_id].executable
2141
_is_executable_from_path_and_stat = \
2142
_is_executable_from_path_and_stat_from_basis
2144
def is_executable(self, file_id, path=None):
2146
path = self.id2path(file_id)
2147
mode = os.lstat(self.abspath(path)).st_mode
2148
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
2150
_is_executable_from_path_and_stat = \
2151
_is_executable_from_path_and_stat_from_stat
2153
@needs_tree_write_lock
2154
def _add(self, files, ids, kinds):
2155
"""See MutableTree._add."""
2156
# TODO: Re-adding a file that is removed in the working copy
2157
# should probably put it back with the previous ID.
2158
# the read and write working inventory should not occur in this
2159
# function - they should be part of lock_write and unlock.
2160
inv = self.inventory
2161
for f, file_id, kind in zip(files, ids, kinds):
2163
inv.add_path(f, kind=kind)
2165
inv.add_path(f, kind=kind, file_id=file_id)
2166
self._inventory_is_modified = True
2168
def revision_tree(self, revision_id):
2169
"""See WorkingTree.revision_id."""
2170
if revision_id == self.last_revision():
2172
xml = self.read_basis_inventory()
2173
except errors.NoSuchFile:
2177
inv = xml7.serializer_v7.read_inventory_from_string(xml)
2178
# dont use the repository revision_tree api because we want
2179
# to supply the inventory.
2180
if inv.revision_id == revision_id:
2181
return revisiontree.InventoryRevisionTree(
2182
self.branch.repository, inv, revision_id)
2183
except errors.BadInventoryFormat:
2185
# raise if there was no inventory, or if we read the wrong inventory.
2186
raise errors.NoSuchRevisionInTree(self, revision_id)
2189
def annotate_iter(self, file_id, default_revision=CURRENT_REVISION):
2190
"""See Tree.annotate_iter
2192
This implementation will use the basis tree implementation if possible.
2193
Lines not in the basis are attributed to CURRENT_REVISION
2195
If there are pending merges, lines added by those merges will be
2196
incorrectly attributed to CURRENT_REVISION (but after committing, the
2197
attribution will be correct).
2199
maybe_file_parent_keys = []
2200
for parent_id in self.get_parent_ids():
2202
parent_tree = self.revision_tree(parent_id)
2203
except errors.NoSuchRevisionInTree:
2204
parent_tree = self.branch.repository.revision_tree(parent_id)
2205
parent_tree.lock_read()
2207
if file_id not in parent_tree:
2209
ie = parent_tree.inventory[file_id]
2210
if ie.kind != 'file':
2211
# Note: this is slightly unnecessary, because symlinks and
2212
# directories have a "text" which is the empty text, and we
2213
# know that won't mess up annotations. But it seems cleaner
2215
parent_text_key = (file_id, ie.revision)
2216
if parent_text_key not in maybe_file_parent_keys:
2217
maybe_file_parent_keys.append(parent_text_key)
2219
parent_tree.unlock()
2220
graph = _mod_graph.Graph(self.branch.repository.texts)
2221
heads = graph.heads(maybe_file_parent_keys)
2222
file_parent_keys = []
2223
for key in maybe_file_parent_keys:
2225
file_parent_keys.append(key)
2227
# Now we have the parents of this content
2228
annotator = self.branch.repository.texts.get_annotator()
2229
text = self.get_file_text(file_id)
2230
this_key =(file_id, default_revision)
2231
annotator.add_special_text(this_key, file_parent_keys, text)
2232
annotations = [(key[-1], line)
2233
for key, line in annotator.annotate_flat(this_key)]
2237
def merge_modified(self):
2238
"""Return a dictionary of files modified by a merge.
2240
The list is initialized by WorkingTree.set_merge_modified, which is
2241
typically called after we make some automatic updates to the tree
2244
This returns a map of file_id->sha1, containing only files which are
2245
still in the working inventory and have that text hash.
2248
hashfile = self._transport.get('merge-hashes')
2249
except errors.NoSuchFile:
2254
if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
2255
raise errors.MergeModifiedFormatError()
2256
except StopIteration:
2257
raise errors.MergeModifiedFormatError()
2258
for s in _mod_rio.RioReader(hashfile):
2259
# RioReader reads in Unicode, so convert file_ids back to utf8
2260
file_id = osutils.safe_file_id(s.get("file_id"), warn=False)
2261
if file_id not in self.inventory:
2263
text_hash = s.get("hash")
2264
if text_hash == self.get_file_sha1(file_id):
2265
merge_hashes[file_id] = text_hash
2271
def subsume(self, other_tree):
2272
def add_children(inventory, entry):
2273
for child_entry in entry.children.values():
2274
inventory._byid[child_entry.file_id] = child_entry
2275
if child_entry.kind == 'directory':
2276
add_children(inventory, child_entry)
2277
if other_tree.get_root_id() == self.get_root_id():
2278
raise errors.BadSubsumeSource(self, other_tree,
2279
'Trees have the same root')
2281
other_tree_path = self.relpath(other_tree.basedir)
2282
except errors.PathNotChild:
2283
raise errors.BadSubsumeSource(self, other_tree,
2284
'Tree is not contained by the other')
2285
new_root_parent = self.path2id(osutils.dirname(other_tree_path))
2286
if new_root_parent is None:
2287
raise errors.BadSubsumeSource(self, other_tree,
2288
'Parent directory is not versioned.')
2289
# We need to ensure that the result of a fetch will have a
2290
# versionedfile for the other_tree root, and only fetching into
2291
# RepositoryKnit2 guarantees that.
2292
if not self.branch.repository.supports_rich_root():
2293
raise errors.SubsumeTargetNeedsUpgrade(other_tree)
2294
other_tree.lock_tree_write()
2296
new_parents = other_tree.get_parent_ids()
2297
other_root = other_tree.inventory.root
2298
other_root.parent_id = new_root_parent
2299
other_root.name = osutils.basename(other_tree_path)
2300
self.inventory.add(other_root)
2301
add_children(self.inventory, other_root)
2302
self._write_inventory(self.inventory)
2303
# normally we don't want to fetch whole repositories, but i think
2304
# here we really do want to consolidate the whole thing.
2305
for parent_id in other_tree.get_parent_ids():
2306
self.branch.fetch(other_tree.branch, parent_id)
2307
self.add_parent_tree_id(parent_id)
2310
other_tree.bzrdir.retire_bzrdir()
2312
@needs_tree_write_lock
2313
def extract(self, file_id, format=None):
2314
"""Extract a subtree from this tree.
2316
A new branch will be created, relative to the path for this tree.
2320
segments = osutils.splitpath(path)
2321
transport = self.branch.bzrdir.root_transport
2322
for name in segments:
2323
transport = transport.clone(name)
2324
transport.ensure_base()
2327
sub_path = self.id2path(file_id)
2328
branch_transport = mkdirs(sub_path)
2330
format = self.bzrdir.cloning_metadir()
2331
branch_transport.ensure_base()
2332
branch_bzrdir = format.initialize_on_transport(branch_transport)
2334
repo = branch_bzrdir.find_repository()
2335
except errors.NoRepositoryPresent:
2336
repo = branch_bzrdir.create_repository()
2337
if not repo.supports_rich_root():
2338
raise errors.RootNotRich()
2339
new_branch = branch_bzrdir.create_branch()
2340
new_branch.pull(self.branch)
2341
for parent_id in self.get_parent_ids():
2342
new_branch.fetch(self.branch, parent_id)
2343
tree_transport = self.bzrdir.root_transport.clone(sub_path)
2344
if tree_transport.base != branch_transport.base:
2345
tree_bzrdir = format.initialize_on_transport(tree_transport)
2346
branch.BranchReferenceFormat().initialize(tree_bzrdir,
2347
target_branch=new_branch)
2349
tree_bzrdir = branch_bzrdir
2350
wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION)
2351
wt.set_parent_ids(self.get_parent_ids())
2352
my_inv = self.inventory
2353
child_inv = inventory.Inventory(root_id=None)
2354
new_root = my_inv[file_id]
2355
my_inv.remove_recursive_id(file_id)
2356
new_root.parent_id = None
2357
child_inv.add(new_root)
2358
self._write_inventory(my_inv)
2359
wt._write_inventory(child_inv)
2362
def list_files(self, include_root=False, from_dir=None, recursive=True):
2363
"""List all files as (path, class, kind, id, entry).
2365
Lists, but does not descend into unversioned directories.
2366
This does not include files that have been deleted in this
2367
tree. Skips the control directory.
2369
:param include_root: if True, return an entry for the root
2370
:param from_dir: start from this directory or None for the root
2371
:param recursive: whether to recurse into subdirectories or not
2373
# list_files is an iterator, so @needs_read_lock doesn't work properly
2374
# with it. So callers should be careful to always read_lock the tree.
2375
if not self.is_locked():
2376
raise errors.ObjectNotLocked(self)
2378
inv = self.inventory
2379
if from_dir is None and include_root is True:
2380
yield ('', 'V', 'directory', inv.root.file_id, inv.root)
2381
# Convert these into local objects to save lookup times
2382
pathjoin = osutils.pathjoin
2383
file_kind = self._kind
2385
# transport.base ends in a slash, we want the piece
2386
# between the last two slashes
2387
transport_base_dir = self.bzrdir.transport.base.rsplit('/', 2)[1]
2389
fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
2391
# directory file_id, relative path, absolute path, reverse sorted children
2392
if from_dir is not None:
2393
from_dir_id = inv.path2id(from_dir)
2394
if from_dir_id is None:
2395
# Directory not versioned
2397
from_dir_abspath = pathjoin(self.basedir, from_dir)
2399
from_dir_id = inv.root.file_id
2400
from_dir_abspath = self.basedir
2401
children = os.listdir(from_dir_abspath)
2403
# jam 20060527 The kernel sized tree seems equivalent whether we
2404
# use a deque and popleft to keep them sorted, or if we use a plain
2405
# list and just reverse() them.
2406
children = collections.deque(children)
2407
stack = [(from_dir_id, u'', from_dir_abspath, children)]
2409
from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
2412
f = children.popleft()
2413
## TODO: If we find a subdirectory with its own .bzr
2414
## directory, then that is a separate tree and we
2415
## should exclude it.
2417
# the bzrdir for this tree
2418
if transport_base_dir == f:
2421
# we know that from_dir_relpath and from_dir_abspath never end in a slash
2422
# and 'f' doesn't begin with one, we can do a string op, rather
2423
# than the checks of pathjoin(), all relative paths will have an extra slash
2425
fp = from_dir_relpath + '/' + f
2428
fap = from_dir_abspath + '/' + f
2430
dir_ie = inv[from_dir_id]
2431
if dir_ie.kind == 'directory':
2432
f_ie = dir_ie.children.get(f)
2437
elif self.is_ignored(fp[1:]):
2440
# we may not have found this file, because of a unicode
2441
# issue, or because the directory was actually a symlink.
2442
f_norm, can_access = osutils.normalized_filename(f)
2443
if f == f_norm or not can_access:
2444
# No change, so treat this file normally
2447
# this file can be accessed by a normalized path
2448
# check again if it is versioned
2449
# these lines are repeated here for performance
2451
fp = from_dir_relpath + '/' + f
2452
fap = from_dir_abspath + '/' + f
2453
f_ie = inv.get_child(from_dir_id, f)
2456
elif self.is_ignored(fp[1:]):
2463
# make a last minute entry
2465
yield fp[1:], c, fk, f_ie.file_id, f_ie
2468
yield fp[1:], c, fk, None, fk_entries[fk]()
2470
yield fp[1:], c, fk, None, TreeEntry()
2473
if fk != 'directory':
2476
# But do this child first if recursing down
2478
new_children = os.listdir(fap)
2480
new_children = collections.deque(new_children)
2481
stack.append((f_ie.file_id, fp, fap, new_children))
2482
# Break out of inner loop,
2483
# so that we start outer loop with child
2486
# if we finished all children, pop it off the stack
2489
@needs_tree_write_lock
2490
def move(self, from_paths, to_dir=None, after=False):
2493
to_dir must exist in the inventory.
2495
If to_dir exists and is a directory, the files are moved into
2496
it, keeping their old names.
2498
Note that to_dir is only the last component of the new name;
2499
this doesn't change the directory.
2501
For each entry in from_paths the move mode will be determined
2504
The first mode moves the file in the filesystem and updates the
2505
inventory. The second mode only updates the inventory without
2506
touching the file on the filesystem.
2508
move uses the second mode if 'after == True' and the target is not
2509
versioned but present in the working tree.
2511
move uses the second mode if 'after == False' and the source is
2512
versioned but no longer in the working tree, and the target is not
2513
versioned but present in the working tree.
2515
move uses the first mode if 'after == False' and the source is
2516
versioned and present in the working tree, and the target is not
2517
versioned and not present in the working tree.
2519
Everything else results in an error.
2521
This returns a list of (from_path, to_path) pairs for each
2522
entry that is moved.
2527
# check for deprecated use of signature
2529
raise TypeError('You must supply a target directory')
2530
# check destination directory
2531
if isinstance(from_paths, basestring):
2533
inv = self.inventory
2534
to_abs = self.abspath(to_dir)
2535
if not isdir(to_abs):
2536
raise errors.BzrMoveFailedError('',to_dir,
2537
errors.NotADirectory(to_abs))
2538
if not self.has_filename(to_dir):
2539
raise errors.BzrMoveFailedError('',to_dir,
2540
errors.NotInWorkingDirectory(to_dir))
2541
to_dir_id = inv.path2id(to_dir)
2542
if to_dir_id is None:
2543
raise errors.BzrMoveFailedError('',to_dir,
2544
errors.NotVersionedError(path=to_dir))
2546
to_dir_ie = inv[to_dir_id]
2547
if to_dir_ie.kind != 'directory':
2548
raise errors.BzrMoveFailedError('',to_dir,
2549
errors.NotADirectory(to_abs))
2551
# create rename entries and tuples
2552
for from_rel in from_paths:
2553
from_tail = splitpath(from_rel)[-1]
2554
from_id = inv.path2id(from_rel)
2556
raise errors.BzrMoveFailedError(from_rel,to_dir,
2557
errors.NotVersionedError(path=from_rel))
2559
from_entry = inv[from_id]
2560
from_parent_id = from_entry.parent_id
2561
to_rel = pathjoin(to_dir, from_tail)
2562
rename_entry = InventoryWorkingTree._RenameEntry(
2565
from_tail=from_tail,
2566
from_parent_id=from_parent_id,
2567
to_rel=to_rel, to_tail=from_tail,
2568
to_parent_id=to_dir_id)
2569
rename_entries.append(rename_entry)
2570
rename_tuples.append((from_rel, to_rel))
2572
# determine which move mode to use. checks also for movability
2573
rename_entries = self._determine_mv_mode(rename_entries, after)
2575
original_modified = self._inventory_is_modified
2578
self._inventory_is_modified = True
2579
self._move(rename_entries)
2644
self.branch.unlock()
2581
# restore the inventory on error
2582
self._inventory_is_modified = original_modified
2648
# do non-implementation specific cleanup
2651
# we share control files:
2652
if self._control_files._lock_count == 3:
2653
# _inventory_is_modified is always False during a read lock.
2654
if self._inventory_is_modified:
2656
self._write_hashcache_if_dirty()
2658
# reverse order of locking.
2660
return self._control_files.unlock()
2662
self.branch.unlock()
2665
class WorkingTree3(WorkingTree):
2584
self._write_inventory(inv)
2585
return rename_tuples
2587
@needs_tree_write_lock
2588
def rename_one(self, from_rel, to_rel, after=False):
2591
This can change the directory or the filename or both.
2593
rename_one has several 'modes' to work. First, it can rename a physical
2594
file and change the file_id. That is the normal mode. Second, it can
2595
only change the file_id without touching any physical file.
2597
rename_one uses the second mode if 'after == True' and 'to_rel' is not
2598
versioned but present in the working tree.
2600
rename_one uses the second mode if 'after == False' and 'from_rel' is
2601
versioned but no longer in the working tree, and 'to_rel' is not
2602
versioned but present in the working tree.
2604
rename_one uses the first mode if 'after == False' and 'from_rel' is
2605
versioned and present in the working tree, and 'to_rel' is not
2606
versioned and not present in the working tree.
2608
Everything else results in an error.
2610
inv = self.inventory
2613
# create rename entries and tuples
2614
from_tail = splitpath(from_rel)[-1]
2615
from_id = inv.path2id(from_rel)
2617
# if file is missing in the inventory maybe it's in the basis_tree
2618
basis_tree = self.branch.basis_tree()
2619
from_id = basis_tree.path2id(from_rel)
2621
raise errors.BzrRenameFailedError(from_rel,to_rel,
2622
errors.NotVersionedError(path=from_rel))
2623
# put entry back in the inventory so we can rename it
2624
from_entry = basis_tree.inventory[from_id].copy()
2627
from_entry = inv[from_id]
2628
from_parent_id = from_entry.parent_id
2629
to_dir, to_tail = os.path.split(to_rel)
2630
to_dir_id = inv.path2id(to_dir)
2631
rename_entry = InventoryWorkingTree._RenameEntry(from_rel=from_rel,
2633
from_tail=from_tail,
2634
from_parent_id=from_parent_id,
2635
to_rel=to_rel, to_tail=to_tail,
2636
to_parent_id=to_dir_id)
2637
rename_entries.append(rename_entry)
2639
# determine which move mode to use. checks also for movability
2640
rename_entries = self._determine_mv_mode(rename_entries, after)
2642
# check if the target changed directory and if the target directory is
2644
if to_dir_id is None:
2645
raise errors.BzrMoveFailedError(from_rel,to_rel,
2646
errors.NotVersionedError(path=to_dir))
2648
# all checks done. now we can continue with our actual work
2649
mutter('rename_one:\n'
2654
' to_dir_id {%s}\n',
2655
from_id, from_rel, to_rel, to_dir, to_dir_id)
2657
self._move(rename_entries)
2658
self._write_inventory(inv)
2660
class _RenameEntry(object):
2661
def __init__(self, from_rel, from_id, from_tail, from_parent_id,
2662
to_rel, to_tail, to_parent_id, only_change_inv=False):
2663
self.from_rel = from_rel
2664
self.from_id = from_id
2665
self.from_tail = from_tail
2666
self.from_parent_id = from_parent_id
2667
self.to_rel = to_rel
2668
self.to_tail = to_tail
2669
self.to_parent_id = to_parent_id
2670
self.only_change_inv = only_change_inv
2672
def _determine_mv_mode(self, rename_entries, after=False):
2673
"""Determines for each from-to pair if both inventory and working tree
2674
or only the inventory has to be changed.
2676
Also does basic plausability tests.
2678
inv = self.inventory
2680
for rename_entry in rename_entries:
2681
# store to local variables for easier reference
2682
from_rel = rename_entry.from_rel
2683
from_id = rename_entry.from_id
2684
to_rel = rename_entry.to_rel
2685
to_id = inv.path2id(to_rel)
2686
only_change_inv = False
2688
# check the inventory for source and destination
2690
raise errors.BzrMoveFailedError(from_rel,to_rel,
2691
errors.NotVersionedError(path=from_rel))
2692
if to_id is not None:
2693
raise errors.BzrMoveFailedError(from_rel,to_rel,
2694
errors.AlreadyVersionedError(path=to_rel))
2696
# try to determine the mode for rename (only change inv or change
2697
# inv and file system)
2699
if not self.has_filename(to_rel):
2700
raise errors.BzrMoveFailedError(from_id,to_rel,
2701
errors.NoSuchFile(path=to_rel,
2702
extra="New file has not been created yet"))
2703
only_change_inv = True
2704
elif not self.has_filename(from_rel) and self.has_filename(to_rel):
2705
only_change_inv = True
2706
elif self.has_filename(from_rel) and not self.has_filename(to_rel):
2707
only_change_inv = False
2708
elif (not self.case_sensitive
2709
and from_rel.lower() == to_rel.lower()
2710
and self.has_filename(from_rel)):
2711
only_change_inv = False
2713
# something is wrong, so lets determine what exactly
2714
if not self.has_filename(from_rel) and \
2715
not self.has_filename(to_rel):
2716
raise errors.BzrRenameFailedError(from_rel,to_rel,
2717
errors.PathsDoNotExist(paths=(str(from_rel),
2720
raise errors.RenameFailedFilesExist(from_rel, to_rel)
2721
rename_entry.only_change_inv = only_change_inv
2722
return rename_entries
2724
def _move(self, rename_entries):
2725
"""Moves a list of files.
2727
Depending on the value of the flag 'only_change_inv', the
2728
file will be moved on the file system or not.
2730
inv = self.inventory
2733
for entry in rename_entries:
2735
self._move_entry(entry)
2737
self._rollback_move(moved)
2741
def _rollback_move(self, moved):
2742
"""Try to rollback a previous move in case of an filesystem error."""
2743
inv = self.inventory
2746
self._move_entry(WorkingTree._RenameEntry(
2747
entry.to_rel, entry.from_id,
2748
entry.to_tail, entry.to_parent_id, entry.from_rel,
2749
entry.from_tail, entry.from_parent_id,
2750
entry.only_change_inv))
2751
except errors.BzrMoveFailedError, e:
2752
raise errors.BzrMoveFailedError( '', '', "Rollback failed."
2753
" The working tree is in an inconsistent state."
2754
" Please consider doing a 'bzr revert'."
2755
" Error message is: %s" % e)
2757
def _move_entry(self, entry):
2758
inv = self.inventory
2759
from_rel_abs = self.abspath(entry.from_rel)
2760
to_rel_abs = self.abspath(entry.to_rel)
2761
if from_rel_abs == to_rel_abs:
2762
raise errors.BzrMoveFailedError(entry.from_rel, entry.to_rel,
2763
"Source and target are identical.")
2765
if not entry.only_change_inv:
2767
osutils.rename(from_rel_abs, to_rel_abs)
2769
raise errors.BzrMoveFailedError(entry.from_rel,
2771
inv.rename(entry.from_id, entry.to_parent_id, entry.to_tail)
2773
@needs_tree_write_lock
2774
def unversion(self, file_ids):
2775
"""Remove the file ids in file_ids from the current versioned set.
2777
When a file_id is unversioned, all of its children are automatically
2780
:param file_ids: The file ids to stop versioning.
2781
:raises: NoSuchId if any fileid is not currently versioned.
2783
for file_id in file_ids:
2784
if file_id not in self._inventory:
2785
raise errors.NoSuchId(self, file_id)
2786
for file_id in file_ids:
2787
if self._inventory.has_id(file_id):
2788
self._inventory.remove_recursive_id(file_id)
2790
# in the future this should just set a dirty bit to wait for the
2791
# final unlock. However, until all methods of workingtree start
2792
# with the current in -memory inventory rather than triggering
2793
# a read, it is more complex - we need to teach read_inventory
2794
# to know when to read, and when to not read first... and possibly
2795
# to save first when the in memory one may be corrupted.
2796
# so for now, we just only write it if it is indeed dirty.
2798
self._write_inventory(self._inventory)
2800
def stored_kind(self, file_id):
2801
"""See Tree.stored_kind"""
2802
return self.inventory[file_id].kind
2805
"""Yield all unversioned files in this WorkingTree.
2807
If there are any unversioned directories then only the directory is
2808
returned, not all its children. But if there are unversioned files
2809
under a versioned subdirectory, they are returned.
2811
Currently returned depth-first, sorted by name within directories.
2812
This is the same order used by 'osutils.walkdirs'.
2814
## TODO: Work from given directory downwards
2815
for path, dir_entry in self.inventory.directories():
2816
# mutter("search for unknowns in %r", path)
2817
dirabs = self.abspath(path)
2818
if not isdir(dirabs):
2819
# e.g. directory deleted
2823
for subf in os.listdir(dirabs):
2824
if self.bzrdir.is_control_filename(subf):
2826
if subf not in dir_entry.children:
2829
can_access) = osutils.normalized_filename(subf)
2830
except UnicodeDecodeError:
2831
path_os_enc = path.encode(osutils._fs_enc)
2832
relpath = path_os_enc + '/' + subf
2833
raise errors.BadFilenameEncoding(relpath,
2835
if subf_norm != subf and can_access:
2836
if subf_norm not in dir_entry.children:
2837
fl.append(subf_norm)
2843
subp = pathjoin(path, subf)
2846
def _walkdirs(self, prefix=""):
2847
"""Walk the directories of this tree.
2849
:prefix: is used as the directrory to start with.
2850
returns a generator which yields items in the form:
2851
((curren_directory_path, fileid),
2852
[(file1_path, file1_name, file1_kind, None, file1_id,
2855
_directory = 'directory'
2856
# get the root in the inventory
2857
inv = self.inventory
2858
top_id = inv.path2id(prefix)
2862
pending = [(prefix, '', _directory, None, top_id, None)]
2865
currentdir = pending.pop()
2866
# 0 - relpath, 1- basename, 2- kind, 3- stat, 4-id, 5-kind
2867
top_id = currentdir[4]
2869
relroot = currentdir[0] + '/'
2872
# FIXME: stash the node in pending
2874
if entry.kind == 'directory':
2875
for name, child in entry.sorted_children():
2876
dirblock.append((relroot + name, name, child.kind, None,
2877
child.file_id, child.kind
2879
yield (currentdir[0], entry.file_id), dirblock
2880
# push the user specified dirs from dirblock
2881
for dir in reversed(dirblock):
2882
if dir[2] == _directory:
2886
class WorkingTree3(InventoryWorkingTree):
2666
2887
"""This is the Format 3 working tree.
2668
2889
This differs from the base WorkingTree by: