2689
1851
return ShelfManager(self, self._transport)
2692
class WorkingTree3(WorkingTree):
1854
class InventoryWorkingTree(WorkingTree,
1855
bzrlib.mutabletree.MutableInventoryTree):
1856
"""Base class for working trees that are inventory-oriented.
1858
The inventory is held in the `Branch` working-inventory, and the
1859
files are in a directory on disk.
1861
It is possible for a `WorkingTree` to have a filename which is
1862
not listed in the Inventory and vice versa.
1865
def __init__(self, basedir='.',
1866
branch=DEPRECATED_PARAMETER,
1868
_control_files=None,
1872
"""Construct a InventoryWorkingTree instance. This is not a public API.
1874
:param branch: A branch to override probing for the branch.
1876
super(InventoryWorkingTree, self).__init__(basedir=basedir,
1877
branch=branch, _control_files=_control_files, _internal=_internal,
1878
_format=_format, _bzrdir=_bzrdir)
1880
if _inventory is None:
1881
# This will be acquired on lock_read() or lock_write()
1882
self._inventory_is_modified = False
1883
self._inventory = None
1885
# the caller of __init__ has provided an inventory,
1886
# we assume they know what they are doing - as its only
1887
# the Format factory and creation methods that are
1888
# permitted to do this.
1889
self._set_inventory(_inventory, dirty=False)
1891
def _set_inventory(self, inv, dirty):
1892
"""Set the internal cached inventory.
1894
:param inv: The inventory to set.
1895
:param dirty: A boolean indicating whether the inventory is the same
1896
logical inventory as whats on disk. If True the inventory is not
1897
the same and should be written to disk or data will be lost, if
1898
False then the inventory is the same as that on disk and any
1899
serialisation would be unneeded overhead.
1901
self._inventory = inv
1902
self._inventory_is_modified = dirty
1904
def _serialize(self, inventory, out_file):
1905
xml5.serializer_v5.write_inventory(self._inventory, out_file,
1908
def _deserialize(selt, in_file):
1909
return xml5.serializer_v5.read_inventory(in_file)
1911
@needs_tree_write_lock
1912
def _write_inventory(self, inv):
1913
"""Write inventory as the current inventory."""
1914
self._set_inventory(inv, dirty=True)
1917
# XXX: This method should be deprecated in favour of taking in a proper
1918
# new Inventory object.
1919
@needs_tree_write_lock
1920
def set_inventory(self, new_inventory_list):
1921
from bzrlib.inventory import (Inventory,
1925
inv = Inventory(self.get_root_id())
1926
for path, file_id, parent, kind in new_inventory_list:
1927
name = os.path.basename(path)
1930
# fixme, there should be a factory function inv,add_??
1931
if kind == 'directory':
1932
inv.add(InventoryDirectory(file_id, name, parent))
1933
elif kind == 'file':
1934
inv.add(InventoryFile(file_id, name, parent))
1935
elif kind == 'symlink':
1936
inv.add(InventoryLink(file_id, name, parent))
1938
raise errors.BzrError("unknown kind %r" % kind)
1939
self._write_inventory(inv)
1941
def _write_basis_inventory(self, xml):
1942
"""Write the basis inventory XML to the basis-inventory file"""
1943
path = self._basis_inventory_name()
1945
self._transport.put_file(path, sio,
1946
mode=self.bzrdir._get_file_mode())
1948
def _reset_data(self):
1949
"""Reset transient data that cannot be revalidated."""
1950
self._inventory_is_modified = False
1951
f = self._transport.get('inventory')
1953
result = self._deserialize(f)
1956
self._set_inventory(result, dirty=False)
1958
def _set_root_id(self, file_id):
1959
"""Set the root id for this tree, in a format specific manner.
1961
:param file_id: The file id to assign to the root. It must not be
1962
present in the current inventory or an error will occur. It must
1963
not be None, but rather a valid file id.
1965
inv = self._inventory
1966
orig_root_id = inv.root.file_id
1967
# TODO: it might be nice to exit early if there was nothing
1968
# to do, saving us from trigger a sync on unlock.
1969
self._inventory_is_modified = True
1970
# we preserve the root inventory entry object, but
1971
# unlinkit from the byid index
1972
del inv._byid[inv.root.file_id]
1973
inv.root.file_id = file_id
1974
# and link it into the index with the new changed id.
1975
inv._byid[inv.root.file_id] = inv.root
1976
# and finally update all children to reference the new id.
1977
# XXX: this should be safe to just look at the root.children
1978
# list, not the WHOLE INVENTORY.
1981
if entry.parent_id == orig_root_id:
1982
entry.parent_id = inv.root.file_id
1984
def all_file_ids(self):
1985
"""See Tree.iter_all_file_ids"""
1986
return set(self.inventory)
1988
def _cache_basis_inventory(self, new_revision):
1989
"""Cache new_revision as the basis inventory."""
1990
# TODO: this should allow the ready-to-use inventory to be passed in,
1991
# as commit already has that ready-to-use [while the format is the
1994
# this double handles the inventory - unpack and repack -
1995
# but is easier to understand. We can/should put a conditional
1996
# in here based on whether the inventory is in the latest format
1997
# - perhaps we should repack all inventories on a repository
1999
# the fast path is to copy the raw xml from the repository. If the
2000
# xml contains 'revision_id="', then we assume the right
2001
# revision_id is set. We must check for this full string, because a
2002
# root node id can legitimately look like 'revision_id' but cannot
2004
xml = self.branch.repository._get_inventory_xml(new_revision)
2005
firstline = xml.split('\n', 1)[0]
2006
if (not 'revision_id="' in firstline or
2007
'format="7"' not in firstline):
2008
inv = self.branch.repository._serializer.read_inventory_from_string(
2010
xml = self._create_basis_xml_from_inventory(new_revision, inv)
2011
self._write_basis_inventory(xml)
2012
except (errors.NoSuchRevision, errors.RevisionNotPresent):
2015
def _basis_inventory_name(self):
2016
return 'basis-inventory-cache'
2018
def _create_basis_xml_from_inventory(self, revision_id, inventory):
2019
"""Create the text that will be saved in basis-inventory"""
2020
inventory.revision_id = revision_id
2021
return xml7.serializer_v7.write_inventory_to_string(inventory)
2023
def read_basis_inventory(self):
2024
"""Read the cached basis inventory."""
2025
path = self._basis_inventory_name()
2026
return self._transport.get_bytes(path)
2029
def read_working_inventory(self):
2030
"""Read the working inventory.
2032
:raises errors.InventoryModified: read_working_inventory will fail
2033
when the current in memory inventory has been modified.
2035
# conceptually this should be an implementation detail of the tree.
2036
# XXX: Deprecate this.
2037
# ElementTree does its own conversion from UTF-8, so open in
2039
if self._inventory_is_modified:
2040
raise errors.InventoryModified(self)
2041
f = self._transport.get('inventory')
2043
result = self._deserialize(f)
2046
self._set_inventory(result, dirty=False)
2050
def get_root_id(self):
2051
"""Return the id of this trees root"""
2052
return self._inventory.root.file_id
2054
def has_id(self, file_id):
2055
# files that have been deleted are excluded
2056
inv = self.inventory
2057
if not inv.has_id(file_id):
2059
path = inv.id2path(file_id)
2060
return osutils.lexists(self.abspath(path))
2062
def has_or_had_id(self, file_id):
2063
if file_id == self.inventory.root.file_id:
2065
return self.inventory.has_id(file_id)
2067
__contains__ = has_id
2069
# should be deprecated - this is slow and in any case treating them as a
2070
# container is (we now know) bad style -- mbp 20070302
2071
## @deprecated_method(zero_fifteen)
2073
"""Iterate through file_ids for this tree.
2075
file_ids are in a WorkingTree if they are in the working inventory
2076
and the working file exists.
2078
inv = self._inventory
2079
for path, ie in inv.iter_entries():
2080
if osutils.lexists(self.abspath(path)):
2083
@needs_tree_write_lock
2084
def set_last_revision(self, new_revision):
2085
"""Change the last revision in the working tree."""
2086
if self._change_last_revision(new_revision):
2087
self._cache_basis_inventory(new_revision)
2089
@needs_tree_write_lock
2090
def reset_state(self, revision_ids=None):
2091
"""Reset the state of the working tree.
2093
This does a hard-reset to a last-known-good state. This is a way to
2094
fix if something got corrupted (like the .bzr/checkout/dirstate file)
2096
if revision_ids is None:
2097
revision_ids = self.get_parent_ids()
2098
if not revision_ids:
2099
rt = self.branch.repository.revision_tree(
2100
_mod_revision.NULL_REVISION)
2102
rt = self.branch.repository.revision_tree(revision_ids[0])
2103
self._write_inventory(rt.inventory)
2104
self.set_parent_ids(revision_ids)
2107
"""Write the in memory inventory to disk."""
2108
# TODO: Maybe this should only write on dirty ?
2109
if self._control_files._lock_mode != 'w':
2110
raise errors.NotWriteLocked(self)
2112
self._serialize(self._inventory, sio)
2114
self._transport.put_file('inventory', sio,
2115
mode=self.bzrdir._get_file_mode())
2116
self._inventory_is_modified = False
2119
def get_file_sha1(self, file_id, path=None, stat_value=None):
2121
path = self._inventory.id2path(file_id)
2122
return self._hashcache.get_sha1(path, stat_value)
2124
def get_file_mtime(self, file_id, path=None):
2125
"""See Tree.get_file_mtime."""
2127
path = self.inventory.id2path(file_id)
2128
return os.lstat(self.abspath(path)).st_mtime
2130
def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
2131
file_id = self.path2id(path)
2133
# For unversioned files on win32, we just assume they are not
2136
return self._inventory[file_id].executable
2138
def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
2139
mode = stat_result.st_mode
2140
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
2142
if not supports_executable():
2143
def is_executable(self, file_id, path=None):
2144
return self._inventory[file_id].executable
2146
_is_executable_from_path_and_stat = \
2147
_is_executable_from_path_and_stat_from_basis
2149
def is_executable(self, file_id, path=None):
2151
path = self.id2path(file_id)
2152
mode = os.lstat(self.abspath(path)).st_mode
2153
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
2155
_is_executable_from_path_and_stat = \
2156
_is_executable_from_path_and_stat_from_stat
2158
@needs_tree_write_lock
2159
def _add(self, files, ids, kinds):
2160
"""See MutableTree._add."""
2161
# TODO: Re-adding a file that is removed in the working copy
2162
# should probably put it back with the previous ID.
2163
# the read and write working inventory should not occur in this
2164
# function - they should be part of lock_write and unlock.
2165
inv = self.inventory
2166
for f, file_id, kind in zip(files, ids, kinds):
2168
inv.add_path(f, kind=kind)
2170
inv.add_path(f, kind=kind, file_id=file_id)
2171
self._inventory_is_modified = True
2173
def revision_tree(self, revision_id):
2174
"""See WorkingTree.revision_id."""
2175
if revision_id == self.last_revision():
2177
xml = self.read_basis_inventory()
2178
except errors.NoSuchFile:
2182
inv = xml7.serializer_v7.read_inventory_from_string(xml)
2183
# dont use the repository revision_tree api because we want
2184
# to supply the inventory.
2185
if inv.revision_id == revision_id:
2186
return revisiontree.RevisionTree(self.branch.repository,
2188
except errors.BadInventoryFormat:
2190
# raise if there was no inventory, or if we read the wrong inventory.
2191
raise errors.NoSuchRevisionInTree(self, revision_id)
2194
def annotate_iter(self, file_id, default_revision=CURRENT_REVISION):
2195
"""See Tree.annotate_iter
2197
This implementation will use the basis tree implementation if possible.
2198
Lines not in the basis are attributed to CURRENT_REVISION
2200
If there are pending merges, lines added by those merges will be
2201
incorrectly attributed to CURRENT_REVISION (but after committing, the
2202
attribution will be correct).
2204
maybe_file_parent_keys = []
2205
for parent_id in self.get_parent_ids():
2207
parent_tree = self.revision_tree(parent_id)
2208
except errors.NoSuchRevisionInTree:
2209
parent_tree = self.branch.repository.revision_tree(parent_id)
2210
parent_tree.lock_read()
2212
if file_id not in parent_tree:
2214
ie = parent_tree.inventory[file_id]
2215
if ie.kind != 'file':
2216
# Note: this is slightly unnecessary, because symlinks and
2217
# directories have a "text" which is the empty text, and we
2218
# know that won't mess up annotations. But it seems cleaner
2220
parent_text_key = (file_id, ie.revision)
2221
if parent_text_key not in maybe_file_parent_keys:
2222
maybe_file_parent_keys.append(parent_text_key)
2224
parent_tree.unlock()
2225
graph = _mod_graph.Graph(self.branch.repository.texts)
2226
heads = graph.heads(maybe_file_parent_keys)
2227
file_parent_keys = []
2228
for key in maybe_file_parent_keys:
2230
file_parent_keys.append(key)
2232
# Now we have the parents of this content
2233
annotator = self.branch.repository.texts.get_annotator()
2234
text = self.get_file_text(file_id)
2235
this_key =(file_id, default_revision)
2236
annotator.add_special_text(this_key, file_parent_keys, text)
2237
annotations = [(key[-1], line)
2238
for key, line in annotator.annotate_flat(this_key)]
2242
def merge_modified(self):
2243
"""Return a dictionary of files modified by a merge.
2245
The list is initialized by WorkingTree.set_merge_modified, which is
2246
typically called after we make some automatic updates to the tree
2249
This returns a map of file_id->sha1, containing only files which are
2250
still in the working inventory and have that text hash.
2253
hashfile = self._transport.get('merge-hashes')
2254
except errors.NoSuchFile:
2259
if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
2260
raise errors.MergeModifiedFormatError()
2261
except StopIteration:
2262
raise errors.MergeModifiedFormatError()
2263
for s in _mod_rio.RioReader(hashfile):
2264
# RioReader reads in Unicode, so convert file_ids back to utf8
2265
file_id = osutils.safe_file_id(s.get("file_id"), warn=False)
2266
if file_id not in self.inventory:
2268
text_hash = s.get("hash")
2269
if text_hash == self.get_file_sha1(file_id):
2270
merge_hashes[file_id] = text_hash
2276
def subsume(self, other_tree):
2277
def add_children(inventory, entry):
2278
for child_entry in entry.children.values():
2279
inventory._byid[child_entry.file_id] = child_entry
2280
if child_entry.kind == 'directory':
2281
add_children(inventory, child_entry)
2282
if other_tree.get_root_id() == self.get_root_id():
2283
raise errors.BadSubsumeSource(self, other_tree,
2284
'Trees have the same root')
2286
other_tree_path = self.relpath(other_tree.basedir)
2287
except errors.PathNotChild:
2288
raise errors.BadSubsumeSource(self, other_tree,
2289
'Tree is not contained by the other')
2290
new_root_parent = self.path2id(osutils.dirname(other_tree_path))
2291
if new_root_parent is None:
2292
raise errors.BadSubsumeSource(self, other_tree,
2293
'Parent directory is not versioned.')
2294
# We need to ensure that the result of a fetch will have a
2295
# versionedfile for the other_tree root, and only fetching into
2296
# RepositoryKnit2 guarantees that.
2297
if not self.branch.repository.supports_rich_root():
2298
raise errors.SubsumeTargetNeedsUpgrade(other_tree)
2299
other_tree.lock_tree_write()
2301
new_parents = other_tree.get_parent_ids()
2302
other_root = other_tree.inventory.root
2303
other_root.parent_id = new_root_parent
2304
other_root.name = osutils.basename(other_tree_path)
2305
self.inventory.add(other_root)
2306
add_children(self.inventory, other_root)
2307
self._write_inventory(self.inventory)
2308
# normally we don't want to fetch whole repositories, but i think
2309
# here we really do want to consolidate the whole thing.
2310
for parent_id in other_tree.get_parent_ids():
2311
self.branch.fetch(other_tree.branch, parent_id)
2312
self.add_parent_tree_id(parent_id)
2315
other_tree.bzrdir.retire_bzrdir()
2317
@needs_tree_write_lock
2318
def extract(self, file_id, format=None):
2319
"""Extract a subtree from this tree.
2321
A new branch will be created, relative to the path for this tree.
2325
segments = osutils.splitpath(path)
2326
transport = self.branch.bzrdir.root_transport
2327
for name in segments:
2328
transport = transport.clone(name)
2329
transport.ensure_base()
2332
sub_path = self.id2path(file_id)
2333
branch_transport = mkdirs(sub_path)
2335
format = self.bzrdir.cloning_metadir()
2336
branch_transport.ensure_base()
2337
branch_bzrdir = format.initialize_on_transport(branch_transport)
2339
repo = branch_bzrdir.find_repository()
2340
except errors.NoRepositoryPresent:
2341
repo = branch_bzrdir.create_repository()
2342
if not repo.supports_rich_root():
2343
raise errors.RootNotRich()
2344
new_branch = branch_bzrdir.create_branch()
2345
new_branch.pull(self.branch)
2346
for parent_id in self.get_parent_ids():
2347
new_branch.fetch(self.branch, parent_id)
2348
tree_transport = self.bzrdir.root_transport.clone(sub_path)
2349
if tree_transport.base != branch_transport.base:
2350
tree_bzrdir = format.initialize_on_transport(tree_transport)
2351
branch.BranchReferenceFormat().initialize(tree_bzrdir,
2352
target_branch=new_branch)
2354
tree_bzrdir = branch_bzrdir
2355
wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION)
2356
wt.set_parent_ids(self.get_parent_ids())
2357
my_inv = self.inventory
2358
child_inv = inventory.Inventory(root_id=None)
2359
new_root = my_inv[file_id]
2360
my_inv.remove_recursive_id(file_id)
2361
new_root.parent_id = None
2362
child_inv.add(new_root)
2363
self._write_inventory(my_inv)
2364
wt._write_inventory(child_inv)
2367
def list_files(self, include_root=False, from_dir=None, recursive=True):
2368
"""List all files as (path, class, kind, id, entry).
2370
Lists, but does not descend into unversioned directories.
2371
This does not include files that have been deleted in this
2372
tree. Skips the control directory.
2374
:param include_root: if True, return an entry for the root
2375
:param from_dir: start from this directory or None for the root
2376
:param recursive: whether to recurse into subdirectories or not
2378
# list_files is an iterator, so @needs_read_lock doesn't work properly
2379
# with it. So callers should be careful to always read_lock the tree.
2380
if not self.is_locked():
2381
raise errors.ObjectNotLocked(self)
2383
inv = self.inventory
2384
if from_dir is None and include_root is True:
2385
yield ('', 'V', 'directory', inv.root.file_id, inv.root)
2386
# Convert these into local objects to save lookup times
2387
pathjoin = osutils.pathjoin
2388
file_kind = self._kind
2390
# transport.base ends in a slash, we want the piece
2391
# between the last two slashes
2392
transport_base_dir = self.bzrdir.transport.base.rsplit('/', 2)[1]
2394
fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
2396
# directory file_id, relative path, absolute path, reverse sorted children
2397
if from_dir is not None:
2398
from_dir_id = inv.path2id(from_dir)
2399
if from_dir_id is None:
2400
# Directory not versioned
2402
from_dir_abspath = pathjoin(self.basedir, from_dir)
2404
from_dir_id = inv.root.file_id
2405
from_dir_abspath = self.basedir
2406
children = os.listdir(from_dir_abspath)
2408
# jam 20060527 The kernel sized tree seems equivalent whether we
2409
# use a deque and popleft to keep them sorted, or if we use a plain
2410
# list and just reverse() them.
2411
children = collections.deque(children)
2412
stack = [(from_dir_id, u'', from_dir_abspath, children)]
2414
from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
2417
f = children.popleft()
2418
## TODO: If we find a subdirectory with its own .bzr
2419
## directory, then that is a separate tree and we
2420
## should exclude it.
2422
# the bzrdir for this tree
2423
if transport_base_dir == f:
2426
# we know that from_dir_relpath and from_dir_abspath never end in a slash
2427
# and 'f' doesn't begin with one, we can do a string op, rather
2428
# than the checks of pathjoin(), all relative paths will have an extra slash
2430
fp = from_dir_relpath + '/' + f
2433
fap = from_dir_abspath + '/' + f
2435
dir_ie = inv[from_dir_id]
2436
if dir_ie.kind == 'directory':
2437
f_ie = dir_ie.children.get(f)
2442
elif self.is_ignored(fp[1:]):
2445
# we may not have found this file, because of a unicode
2446
# issue, or because the directory was actually a symlink.
2447
f_norm, can_access = osutils.normalized_filename(f)
2448
if f == f_norm or not can_access:
2449
# No change, so treat this file normally
2452
# this file can be accessed by a normalized path
2453
# check again if it is versioned
2454
# these lines are repeated here for performance
2456
fp = from_dir_relpath + '/' + f
2457
fap = from_dir_abspath + '/' + f
2458
f_ie = inv.get_child(from_dir_id, f)
2461
elif self.is_ignored(fp[1:]):
2468
# make a last minute entry
2470
yield fp[1:], c, fk, f_ie.file_id, f_ie
2473
yield fp[1:], c, fk, None, fk_entries[fk]()
2475
yield fp[1:], c, fk, None, TreeEntry()
2478
if fk != 'directory':
2481
# But do this child first if recursing down
2483
new_children = os.listdir(fap)
2485
new_children = collections.deque(new_children)
2486
stack.append((f_ie.file_id, fp, fap, new_children))
2487
# Break out of inner loop,
2488
# so that we start outer loop with child
2491
# if we finished all children, pop it off the stack
2494
@needs_tree_write_lock
2495
def move(self, from_paths, to_dir=None, after=False):
2498
to_dir must exist in the inventory.
2500
If to_dir exists and is a directory, the files are moved into
2501
it, keeping their old names.
2503
Note that to_dir is only the last component of the new name;
2504
this doesn't change the directory.
2506
For each entry in from_paths the move mode will be determined
2509
The first mode moves the file in the filesystem and updates the
2510
inventory. The second mode only updates the inventory without
2511
touching the file on the filesystem.
2513
move uses the second mode if 'after == True' and the target is not
2514
versioned but present in the working tree.
2516
move uses the second mode if 'after == False' and the source is
2517
versioned but no longer in the working tree, and the target is not
2518
versioned but present in the working tree.
2520
move uses the first mode if 'after == False' and the source is
2521
versioned and present in the working tree, and the target is not
2522
versioned and not present in the working tree.
2524
Everything else results in an error.
2526
This returns a list of (from_path, to_path) pairs for each
2527
entry that is moved.
2532
# check for deprecated use of signature
2534
raise TypeError('You must supply a target directory')
2535
# check destination directory
2536
if isinstance(from_paths, basestring):
2538
inv = self.inventory
2539
to_abs = self.abspath(to_dir)
2540
if not isdir(to_abs):
2541
raise errors.BzrMoveFailedError('',to_dir,
2542
errors.NotADirectory(to_abs))
2543
if not self.has_filename(to_dir):
2544
raise errors.BzrMoveFailedError('',to_dir,
2545
errors.NotInWorkingDirectory(to_dir))
2546
to_dir_id = inv.path2id(to_dir)
2547
if to_dir_id is None:
2548
raise errors.BzrMoveFailedError('',to_dir,
2549
errors.NotVersionedError(path=to_dir))
2551
to_dir_ie = inv[to_dir_id]
2552
if to_dir_ie.kind != 'directory':
2553
raise errors.BzrMoveFailedError('',to_dir,
2554
errors.NotADirectory(to_abs))
2556
# create rename entries and tuples
2557
for from_rel in from_paths:
2558
from_tail = splitpath(from_rel)[-1]
2559
from_id = inv.path2id(from_rel)
2561
raise errors.BzrMoveFailedError(from_rel,to_dir,
2562
errors.NotVersionedError(path=from_rel))
2564
from_entry = inv[from_id]
2565
from_parent_id = from_entry.parent_id
2566
to_rel = pathjoin(to_dir, from_tail)
2567
rename_entry = InventoryWorkingTree._RenameEntry(
2570
from_tail=from_tail,
2571
from_parent_id=from_parent_id,
2572
to_rel=to_rel, to_tail=from_tail,
2573
to_parent_id=to_dir_id)
2574
rename_entries.append(rename_entry)
2575
rename_tuples.append((from_rel, to_rel))
2577
# determine which move mode to use. checks also for movability
2578
rename_entries = self._determine_mv_mode(rename_entries, after)
2580
original_modified = self._inventory_is_modified
2583
self._inventory_is_modified = True
2584
self._move(rename_entries)
2586
# restore the inventory on error
2587
self._inventory_is_modified = original_modified
2589
self._write_inventory(inv)
2590
return rename_tuples
2592
@needs_tree_write_lock
2593
def rename_one(self, from_rel, to_rel, after=False):
2596
This can change the directory or the filename or both.
2598
rename_one has several 'modes' to work. First, it can rename a physical
2599
file and change the file_id. That is the normal mode. Second, it can
2600
only change the file_id without touching any physical file.
2602
rename_one uses the second mode if 'after == True' and 'to_rel' is not
2603
versioned but present in the working tree.
2605
rename_one uses the second mode if 'after == False' and 'from_rel' is
2606
versioned but no longer in the working tree, and 'to_rel' is not
2607
versioned but present in the working tree.
2609
rename_one uses the first mode if 'after == False' and 'from_rel' is
2610
versioned and present in the working tree, and 'to_rel' is not
2611
versioned and not present in the working tree.
2613
Everything else results in an error.
2615
inv = self.inventory
2618
# create rename entries and tuples
2619
from_tail = splitpath(from_rel)[-1]
2620
from_id = inv.path2id(from_rel)
2622
# if file is missing in the inventory maybe it's in the basis_tree
2623
basis_tree = self.branch.basis_tree()
2624
from_id = basis_tree.path2id(from_rel)
2626
raise errors.BzrRenameFailedError(from_rel,to_rel,
2627
errors.NotVersionedError(path=from_rel))
2628
# put entry back in the inventory so we can rename it
2629
from_entry = basis_tree.inventory[from_id].copy()
2632
from_entry = inv[from_id]
2633
from_parent_id = from_entry.parent_id
2634
to_dir, to_tail = os.path.split(to_rel)
2635
to_dir_id = inv.path2id(to_dir)
2636
rename_entry = InventoryWorkingTree._RenameEntry(from_rel=from_rel,
2638
from_tail=from_tail,
2639
from_parent_id=from_parent_id,
2640
to_rel=to_rel, to_tail=to_tail,
2641
to_parent_id=to_dir_id)
2642
rename_entries.append(rename_entry)
2644
# determine which move mode to use. checks also for movability
2645
rename_entries = self._determine_mv_mode(rename_entries, after)
2647
# check if the target changed directory and if the target directory is
2649
if to_dir_id is None:
2650
raise errors.BzrMoveFailedError(from_rel,to_rel,
2651
errors.NotVersionedError(path=to_dir))
2653
# all checks done. now we can continue with our actual work
2654
mutter('rename_one:\n'
2659
' to_dir_id {%s}\n',
2660
from_id, from_rel, to_rel, to_dir, to_dir_id)
2662
self._move(rename_entries)
2663
self._write_inventory(inv)
2665
class _RenameEntry(object):
2666
def __init__(self, from_rel, from_id, from_tail, from_parent_id,
2667
to_rel, to_tail, to_parent_id, only_change_inv=False):
2668
self.from_rel = from_rel
2669
self.from_id = from_id
2670
self.from_tail = from_tail
2671
self.from_parent_id = from_parent_id
2672
self.to_rel = to_rel
2673
self.to_tail = to_tail
2674
self.to_parent_id = to_parent_id
2675
self.only_change_inv = only_change_inv
2677
def _determine_mv_mode(self, rename_entries, after=False):
2678
"""Determines for each from-to pair if both inventory and working tree
2679
or only the inventory has to be changed.
2681
Also does basic plausability tests.
2683
inv = self.inventory
2685
for rename_entry in rename_entries:
2686
# store to local variables for easier reference
2687
from_rel = rename_entry.from_rel
2688
from_id = rename_entry.from_id
2689
to_rel = rename_entry.to_rel
2690
to_id = inv.path2id(to_rel)
2691
only_change_inv = False
2693
# check the inventory for source and destination
2695
raise errors.BzrMoveFailedError(from_rel,to_rel,
2696
errors.NotVersionedError(path=from_rel))
2697
if to_id is not None:
2698
raise errors.BzrMoveFailedError(from_rel,to_rel,
2699
errors.AlreadyVersionedError(path=to_rel))
2701
# try to determine the mode for rename (only change inv or change
2702
# inv and file system)
2704
if not self.has_filename(to_rel):
2705
raise errors.BzrMoveFailedError(from_id,to_rel,
2706
errors.NoSuchFile(path=to_rel,
2707
extra="New file has not been created yet"))
2708
only_change_inv = True
2709
elif not self.has_filename(from_rel) and self.has_filename(to_rel):
2710
only_change_inv = True
2711
elif self.has_filename(from_rel) and not self.has_filename(to_rel):
2712
only_change_inv = False
2713
elif (not self.case_sensitive
2714
and from_rel.lower() == to_rel.lower()
2715
and self.has_filename(from_rel)):
2716
only_change_inv = False
2718
# something is wrong, so lets determine what exactly
2719
if not self.has_filename(from_rel) and \
2720
not self.has_filename(to_rel):
2721
raise errors.BzrRenameFailedError(from_rel,to_rel,
2722
errors.PathsDoNotExist(paths=(str(from_rel),
2725
raise errors.RenameFailedFilesExist(from_rel, to_rel)
2726
rename_entry.only_change_inv = only_change_inv
2727
return rename_entries
2729
def _move(self, rename_entries):
2730
"""Moves a list of files.
2732
Depending on the value of the flag 'only_change_inv', the
2733
file will be moved on the file system or not.
2735
inv = self.inventory
2738
for entry in rename_entries:
2740
self._move_entry(entry)
2742
self._rollback_move(moved)
2746
def _rollback_move(self, moved):
2747
"""Try to rollback a previous move in case of an filesystem error."""
2748
inv = self.inventory
2751
self._move_entry(WorkingTree._RenameEntry(
2752
entry.to_rel, entry.from_id,
2753
entry.to_tail, entry.to_parent_id, entry.from_rel,
2754
entry.from_tail, entry.from_parent_id,
2755
entry.only_change_inv))
2756
except errors.BzrMoveFailedError, e:
2757
raise errors.BzrMoveFailedError( '', '', "Rollback failed."
2758
" The working tree is in an inconsistent state."
2759
" Please consider doing a 'bzr revert'."
2760
" Error message is: %s" % e)
2762
def _move_entry(self, entry):
2763
inv = self.inventory
2764
from_rel_abs = self.abspath(entry.from_rel)
2765
to_rel_abs = self.abspath(entry.to_rel)
2766
if from_rel_abs == to_rel_abs:
2767
raise errors.BzrMoveFailedError(entry.from_rel, entry.to_rel,
2768
"Source and target are identical.")
2770
if not entry.only_change_inv:
2772
osutils.rename(from_rel_abs, to_rel_abs)
2774
raise errors.BzrMoveFailedError(entry.from_rel,
2776
inv.rename(entry.from_id, entry.to_parent_id, entry.to_tail)
2778
@needs_tree_write_lock
2779
def unversion(self, file_ids):
2780
"""Remove the file ids in file_ids from the current versioned set.
2782
When a file_id is unversioned, all of its children are automatically
2785
:param file_ids: The file ids to stop versioning.
2786
:raises: NoSuchId if any fileid is not currently versioned.
2788
for file_id in file_ids:
2789
if file_id not in self._inventory:
2790
raise errors.NoSuchId(self, file_id)
2791
for file_id in file_ids:
2792
if self._inventory.has_id(file_id):
2793
self._inventory.remove_recursive_id(file_id)
2795
# in the future this should just set a dirty bit to wait for the
2796
# final unlock. However, until all methods of workingtree start
2797
# with the current in -memory inventory rather than triggering
2798
# a read, it is more complex - we need to teach read_inventory
2799
# to know when to read, and when to not read first... and possibly
2800
# to save first when the in memory one may be corrupted.
2801
# so for now, we just only write it if it is indeed dirty.
2803
self._write_inventory(self._inventory)
2805
def stored_kind(self, file_id):
2806
"""See Tree.stored_kind"""
2807
return self.inventory[file_id].kind
2810
"""Yield all unversioned files in this WorkingTree.
2812
If there are any unversioned directories then only the directory is
2813
returned, not all its children. But if there are unversioned files
2814
under a versioned subdirectory, they are returned.
2816
Currently returned depth-first, sorted by name within directories.
2817
This is the same order used by 'osutils.walkdirs'.
2819
## TODO: Work from given directory downwards
2820
for path, dir_entry in self.inventory.directories():
2821
# mutter("search for unknowns in %r", path)
2822
dirabs = self.abspath(path)
2823
if not isdir(dirabs):
2824
# e.g. directory deleted
2828
for subf in os.listdir(dirabs):
2829
if self.bzrdir.is_control_filename(subf):
2831
if subf not in dir_entry.children:
2834
can_access) = osutils.normalized_filename(subf)
2835
except UnicodeDecodeError:
2836
path_os_enc = path.encode(osutils._fs_enc)
2837
relpath = path_os_enc + '/' + subf
2838
raise errors.BadFilenameEncoding(relpath,
2840
if subf_norm != subf and can_access:
2841
if subf_norm not in dir_entry.children:
2842
fl.append(subf_norm)
2848
subp = pathjoin(path, subf)
2851
def _walkdirs(self, prefix=""):
2852
"""Walk the directories of this tree.
2854
:prefix: is used as the directrory to start with.
2855
returns a generator which yields items in the form:
2856
((curren_directory_path, fileid),
2857
[(file1_path, file1_name, file1_kind, None, file1_id,
2860
_directory = 'directory'
2861
# get the root in the inventory
2862
inv = self.inventory
2863
top_id = inv.path2id(prefix)
2867
pending = [(prefix, '', _directory, None, top_id, None)]
2870
currentdir = pending.pop()
2871
# 0 - relpath, 1- basename, 2- kind, 3- stat, 4-id, 5-kind
2872
top_id = currentdir[4]
2874
relroot = currentdir[0] + '/'
2877
# FIXME: stash the node in pending
2879
if entry.kind == 'directory':
2880
for name, child in entry.sorted_children():
2881
dirblock.append((relroot + name, name, child.kind, None,
2882
child.file_id, child.kind
2884
yield (currentdir[0], entry.file_id), dirblock
2885
# push the user specified dirs from dirblock
2886
for dir in reversed(dirblock):
2887
if dir[2] == _directory:
2891
class WorkingTree3(InventoryWorkingTree):
2693
2892
"""This is the Format 3 working tree.
2695
2894
This differs from the base WorkingTree by: