1
# Copyright (C) 2005-2011 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""InventoryWorkingTree object and friends.
19
A WorkingTree represents the editable working copy of a branch.
20
Operations which represent the WorkingTree are also done here,
21
such as renaming or adding files. The WorkingTree has an inventory
22
which is updated by these operations. A commit produces a
23
new revision based on the workingtree and its inventory.
25
At the moment every WorkingTree has its own branch. Remote
26
WorkingTrees aren't supported.
28
To get a WorkingTree, call bzrdir.open_workingtree() or
29
WorkingTree.open(dir).
34
from __future__ import absolute_import
40
# Explicitly import breezy.bzrdir so that the BzrProber
41
# is guaranteed to be registered.
44
from . import lazy_import
45
lazy_import.lazy_import(globals(), """
48
conflicts as _mod_conflicts,
54
revision as _mod_revision,
63
from .decorators import needs_write_lock, needs_read_lock
64
from .lock import _RelockDebugMixin, LogicalLockResult
65
from .mutabletree import needs_tree_write_lock
69
from .trace import mutter
70
from .workingtree import (
80
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
81
# TODO: Modifying the conflict objects or their type is currently nearly
82
# impossible as there is no clear relationship between the working tree format
83
# and the conflict list file format.
84
CONFLICT_HEADER_1 = "BZR conflict list format 1"
87
class InventoryWorkingTree(WorkingTree,
88
mutabletree.MutableInventoryTree):
89
"""Base class for working trees that are inventory-oriented.
91
The inventory is held in the `Branch` working-inventory, and the
92
files are in a directory on disk.
94
It is possible for a `WorkingTree` to have a filename which is
95
not listed in the Inventory and vice versa.
98
def __init__(self, basedir='.',
105
"""Construct a InventoryWorkingTree instance. This is not a public API.
107
:param branch: A branch to override probing for the branch.
109
super(InventoryWorkingTree, self).__init__(basedir=basedir,
110
branch=branch, _transport=_control_files._transport,
111
_internal=_internal, _format=_format, _bzrdir=_bzrdir)
113
self._control_files = _control_files
114
self._detect_case_handling()
116
if _inventory is None:
117
# This will be acquired on lock_read() or lock_write()
118
self._inventory_is_modified = False
119
self._inventory = None
121
# the caller of __init__ has provided an inventory,
122
# we assume they know what they are doing - as its only
123
# the Format factory and creation methods that are
124
# permitted to do this.
125
self._set_inventory(_inventory, dirty=False)
127
def _set_inventory(self, inv, dirty):
128
"""Set the internal cached inventory.
130
:param inv: The inventory to set.
131
:param dirty: A boolean indicating whether the inventory is the same
132
logical inventory as whats on disk. If True the inventory is not
133
the same and should be written to disk or data will be lost, if
134
False then the inventory is the same as that on disk and any
135
serialisation would be unneeded overhead.
137
self._inventory = inv
138
self._inventory_is_modified = dirty
140
def _detect_case_handling(self):
141
wt_trans = self.bzrdir.get_workingtree_transport(None)
143
wt_trans.stat(self._format.case_sensitive_filename)
144
except errors.NoSuchFile:
145
self.case_sensitive = True
147
self.case_sensitive = False
149
self._setup_directory_is_tree_reference()
151
def _serialize(self, inventory, out_file):
152
xml5.serializer_v5.write_inventory(self._inventory, out_file,
155
def _deserialize(selt, in_file):
156
return xml5.serializer_v5.read_inventory(in_file)
158
def break_lock(self):
159
"""Break a lock if one is present from another instance.
161
Uses the ui factory to ask for confirmation if the lock may be from
164
This will probe the repository for its lock as well.
166
self._control_files.break_lock()
167
self.branch.break_lock()
170
return self._control_files.is_locked()
172
def _must_be_locked(self):
173
if not self.is_locked():
174
raise errors.ObjectNotLocked(self)
177
"""Lock the tree for reading.
179
This also locks the branch, and can be unlocked via self.unlock().
181
:return: A breezy.lock.LogicalLockResult.
183
if not self.is_locked():
185
self.branch.lock_read()
187
self._control_files.lock_read()
188
return LogicalLockResult(self.unlock)
193
def lock_tree_write(self):
194
"""See MutableTree.lock_tree_write, and WorkingTree.unlock.
196
:return: A breezy.lock.LogicalLockResult.
198
if not self.is_locked():
200
self.branch.lock_read()
202
self._control_files.lock_write()
203
return LogicalLockResult(self.unlock)
208
def lock_write(self):
209
"""See MutableTree.lock_write, and WorkingTree.unlock.
211
:return: A breezy.lock.LogicalLockResult.
213
if not self.is_locked():
215
self.branch.lock_write()
217
self._control_files.lock_write()
218
return LogicalLockResult(self.unlock)
223
def get_physical_lock_status(self):
224
return self._control_files.get_physical_lock_status()
226
@needs_tree_write_lock
227
def _write_inventory(self, inv):
228
"""Write inventory as the current inventory."""
229
self._set_inventory(inv, dirty=True)
232
# XXX: This method should be deprecated in favour of taking in a proper
233
# new Inventory object.
234
@needs_tree_write_lock
235
def set_inventory(self, new_inventory_list):
236
from .inventory import (
241
inv = Inventory(self.get_root_id())
242
for path, file_id, parent, kind in new_inventory_list:
243
name = os.path.basename(path)
246
# fixme, there should be a factory function inv,add_??
247
if kind == 'directory':
248
inv.add(InventoryDirectory(file_id, name, parent))
250
inv.add(InventoryFile(file_id, name, parent))
251
elif kind == 'symlink':
252
inv.add(InventoryLink(file_id, name, parent))
254
raise errors.BzrError("unknown kind %r" % kind)
255
self._write_inventory(inv)
257
def _write_basis_inventory(self, xml):
258
"""Write the basis inventory XML to the basis-inventory file"""
259
path = self._basis_inventory_name()
261
self._transport.put_file(path, sio,
262
mode=self.bzrdir._get_file_mode())
264
def _reset_data(self):
265
"""Reset transient data that cannot be revalidated."""
266
self._inventory_is_modified = False
267
f = self._transport.get('inventory')
269
result = self._deserialize(f)
272
self._set_inventory(result, dirty=False)
274
def _set_root_id(self, file_id):
275
"""Set the root id for this tree, in a format specific manner.
277
:param file_id: The file id to assign to the root. It must not be
278
present in the current inventory or an error will occur. It must
279
not be None, but rather a valid file id.
281
inv = self._inventory
282
orig_root_id = inv.root.file_id
283
# TODO: it might be nice to exit early if there was nothing
284
# to do, saving us from trigger a sync on unlock.
285
self._inventory_is_modified = True
286
# we preserve the root inventory entry object, but
287
# unlinkit from the byid index
288
del inv._byid[inv.root.file_id]
289
inv.root.file_id = file_id
290
# and link it into the index with the new changed id.
291
inv._byid[inv.root.file_id] = inv.root
292
# and finally update all children to reference the new id.
293
# XXX: this should be safe to just look at the root.children
294
# list, not the WHOLE INVENTORY.
297
if entry.parent_id == orig_root_id:
298
entry.parent_id = inv.root.file_id
300
@needs_tree_write_lock
301
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
302
"""See MutableTree.set_parent_trees."""
303
parent_ids = [rev for (rev, tree) in parents_list]
304
for revision_id in parent_ids:
305
_mod_revision.check_not_reserved_id(revision_id)
307
self._check_parents_for_ghosts(parent_ids,
308
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
310
parent_ids = self._filter_parent_ids_by_ancestry(parent_ids)
312
if len(parent_ids) == 0:
313
leftmost_parent_id = _mod_revision.NULL_REVISION
314
leftmost_parent_tree = None
316
leftmost_parent_id, leftmost_parent_tree = parents_list[0]
318
if self._change_last_revision(leftmost_parent_id):
319
if leftmost_parent_tree is None:
320
# If we don't have a tree, fall back to reading the
321
# parent tree from the repository.
322
self._cache_basis_inventory(leftmost_parent_id)
324
inv = leftmost_parent_tree.root_inventory
325
xml = self._create_basis_xml_from_inventory(
326
leftmost_parent_id, inv)
327
self._write_basis_inventory(xml)
328
self._set_merges_from_parent_ids(parent_ids)
330
def _cache_basis_inventory(self, new_revision):
331
"""Cache new_revision as the basis inventory."""
332
# TODO: this should allow the ready-to-use inventory to be passed in,
333
# as commit already has that ready-to-use [while the format is the
336
# this double handles the inventory - unpack and repack -
337
# but is easier to understand. We can/should put a conditional
338
# in here based on whether the inventory is in the latest format
339
# - perhaps we should repack all inventories on a repository
341
# the fast path is to copy the raw xml from the repository. If the
342
# xml contains 'revision_id="', then we assume the right
343
# revision_id is set. We must check for this full string, because a
344
# root node id can legitimately look like 'revision_id' but cannot
346
xml = self.branch.repository._get_inventory_xml(new_revision)
347
firstline = xml.split('\n', 1)[0]
348
if (not 'revision_id="' in firstline or
349
'format="7"' not in firstline):
350
inv = self.branch.repository._serializer.read_inventory_from_string(
352
xml = self._create_basis_xml_from_inventory(new_revision, inv)
353
self._write_basis_inventory(xml)
354
except (errors.NoSuchRevision, errors.RevisionNotPresent):
357
def _basis_inventory_name(self):
358
return 'basis-inventory-cache'
360
def _create_basis_xml_from_inventory(self, revision_id, inventory):
361
"""Create the text that will be saved in basis-inventory"""
362
inventory.revision_id = revision_id
363
return xml7.serializer_v7.write_inventory_to_string(inventory)
365
@needs_tree_write_lock
366
def set_conflicts(self, conflicts):
367
self._put_rio('conflicts', conflicts.to_stanzas(),
370
@needs_tree_write_lock
371
def add_conflicts(self, new_conflicts):
372
conflict_set = set(self.conflicts())
373
conflict_set.update(set(list(new_conflicts)))
374
self.set_conflicts(_mod_conflicts.ConflictList(sorted(conflict_set,
375
key=_mod_conflicts.Conflict.sort_key)))
380
confile = self._transport.get('conflicts')
381
except errors.NoSuchFile:
382
return _mod_conflicts.ConflictList()
385
if next(confile) != CONFLICT_HEADER_1 + '\n':
386
raise errors.ConflictFormatError()
387
except StopIteration:
388
raise errors.ConflictFormatError()
389
reader = _mod_rio.RioReader(confile)
390
return _mod_conflicts.ConflictList.from_stanzas(reader)
394
def read_basis_inventory(self):
395
"""Read the cached basis inventory."""
396
path = self._basis_inventory_name()
397
return self._transport.get_bytes(path)
400
def read_working_inventory(self):
401
"""Read the working inventory.
403
:raises errors.InventoryModified: read_working_inventory will fail
404
when the current in memory inventory has been modified.
406
# conceptually this should be an implementation detail of the tree.
407
# XXX: Deprecate this.
408
# ElementTree does its own conversion from UTF-8, so open in
410
if self._inventory_is_modified:
411
raise errors.InventoryModified(self)
412
f = self._transport.get('inventory')
414
result = self._deserialize(f)
417
self._set_inventory(result, dirty=False)
421
def get_root_id(self):
422
"""Return the id of this trees root"""
423
return self._inventory.root.file_id
425
def has_id(self, file_id):
426
# files that have been deleted are excluded
427
inv, inv_file_id = self._unpack_file_id(file_id)
428
if not inv.has_id(inv_file_id):
430
path = inv.id2path(inv_file_id)
431
return osutils.lexists(self.abspath(path))
433
def has_or_had_id(self, file_id):
434
if file_id == self.get_root_id():
436
inv, inv_file_id = self._unpack_file_id(file_id)
437
return inv.has_id(inv_file_id)
439
def all_file_ids(self):
440
"""Iterate through file_ids for this tree.
442
file_ids are in a WorkingTree if they are in the working inventory
443
and the working file exists.
446
for path, ie in self.iter_entries_by_dir():
450
@needs_tree_write_lock
451
def set_last_revision(self, new_revision):
452
"""Change the last revision in the working tree."""
453
if self._change_last_revision(new_revision):
454
self._cache_basis_inventory(new_revision)
456
def _get_check_refs(self):
457
"""Return the references needed to perform a check of this tree.
459
The default implementation returns no refs, and is only suitable for
460
trees that have no local caching and can commit on ghosts at any time.
462
:seealso: breezy.check for details about check_refs.
467
def _check(self, references):
468
"""Check the tree for consistency.
470
:param references: A dict with keys matching the items returned by
471
self._get_check_refs(), and values from looking those keys up in
474
tree_basis = self.basis_tree()
475
tree_basis.lock_read()
477
repo_basis = references[('trees', self.last_revision())]
478
if len(list(repo_basis.iter_changes(tree_basis))) > 0:
479
raise errors.BzrCheckError(
480
"Mismatched basis inventory content.")
486
def check_state(self):
487
"""Check that the working state is/isn't valid."""
488
check_refs = self._get_check_refs()
490
for ref in check_refs:
493
refs[ref] = self.branch.repository.revision_tree(value)
496
@needs_tree_write_lock
497
def reset_state(self, revision_ids=None):
498
"""Reset the state of the working tree.
500
This does a hard-reset to a last-known-good state. This is a way to
501
fix if something got corrupted (like the .bzr/checkout/dirstate file)
503
if revision_ids is None:
504
revision_ids = self.get_parent_ids()
506
rt = self.branch.repository.revision_tree(
507
_mod_revision.NULL_REVISION)
509
rt = self.branch.repository.revision_tree(revision_ids[0])
510
self._write_inventory(rt.root_inventory)
511
self.set_parent_ids(revision_ids)
514
"""Write the in memory inventory to disk."""
515
# TODO: Maybe this should only write on dirty ?
516
if self._control_files._lock_mode != 'w':
517
raise errors.NotWriteLocked(self)
519
self._serialize(self._inventory, sio)
521
self._transport.put_file('inventory', sio,
522
mode=self.bzrdir._get_file_mode())
523
self._inventory_is_modified = False
525
def get_file_mtime(self, file_id, path=None):
526
"""See Tree.get_file_mtime."""
528
path = self.id2path(file_id)
530
return os.lstat(self.abspath(path)).st_mtime
532
if e.errno == errno.ENOENT:
533
raise errors.FileTimestampUnavailable(path)
536
def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
537
inv, file_id = self._path2inv_file_id(path)
539
# For unversioned files on win32, we just assume they are not
542
return inv[file_id].executable
544
def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
545
mode = stat_result.st_mode
546
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
548
def is_executable(self, file_id, path=None):
549
if not self._supports_executable():
550
inv, inv_file_id = self._unpack_file_id(file_id)
551
return inv[inv_file_id].executable
554
path = self.id2path(file_id)
555
mode = os.lstat(self.abspath(path)).st_mode
556
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
558
def _is_executable_from_path_and_stat(self, path, stat_result):
559
if not self._supports_executable():
560
return self._is_executable_from_path_and_stat_from_basis(path, stat_result)
562
return self._is_executable_from_path_and_stat_from_stat(path, stat_result)
564
@needs_tree_write_lock
565
def _add(self, files, ids, kinds):
566
"""See MutableTree._add."""
567
# TODO: Re-adding a file that is removed in the working copy
568
# should probably put it back with the previous ID.
569
# the read and write working inventory should not occur in this
570
# function - they should be part of lock_write and unlock.
571
# FIXME: nested trees
572
inv = self.root_inventory
573
for f, file_id, kind in zip(files, ids, kinds):
575
inv.add_path(f, kind=kind)
577
inv.add_path(f, kind=kind, file_id=file_id)
578
self._inventory_is_modified = True
580
def revision_tree(self, revision_id):
581
"""See WorkingTree.revision_id."""
582
if revision_id == self.last_revision():
584
xml = self.read_basis_inventory()
585
except errors.NoSuchFile:
589
inv = xml7.serializer_v7.read_inventory_from_string(xml)
590
# dont use the repository revision_tree api because we want
591
# to supply the inventory.
592
if inv.revision_id == revision_id:
593
return revisiontree.InventoryRevisionTree(
594
self.branch.repository, inv, revision_id)
595
except errors.BadInventoryFormat:
597
# raise if there was no inventory, or if we read the wrong inventory.
598
raise errors.NoSuchRevisionInTree(self, revision_id)
601
def annotate_iter(self, file_id,
602
default_revision=_mod_revision.CURRENT_REVISION):
603
"""See Tree.annotate_iter
605
This implementation will use the basis tree implementation if possible.
606
Lines not in the basis are attributed to CURRENT_REVISION
608
If there are pending merges, lines added by those merges will be
609
incorrectly attributed to CURRENT_REVISION (but after committing, the
610
attribution will be correct).
612
maybe_file_parent_keys = []
613
for parent_id in self.get_parent_ids():
615
parent_tree = self.revision_tree(parent_id)
616
except errors.NoSuchRevisionInTree:
617
parent_tree = self.branch.repository.revision_tree(parent_id)
618
parent_tree.lock_read()
621
kind = parent_tree.kind(file_id)
622
except errors.NoSuchId:
625
# Note: this is slightly unnecessary, because symlinks and
626
# directories have a "text" which is the empty text, and we
627
# know that won't mess up annotations. But it seems cleaner
630
file_id, parent_tree.get_file_revision(file_id))
631
if parent_text_key not in maybe_file_parent_keys:
632
maybe_file_parent_keys.append(parent_text_key)
635
graph = _mod_graph.Graph(self.branch.repository.texts)
636
heads = graph.heads(maybe_file_parent_keys)
637
file_parent_keys = []
638
for key in maybe_file_parent_keys:
640
file_parent_keys.append(key)
642
# Now we have the parents of this content
643
annotator = self.branch.repository.texts.get_annotator()
644
text = self.get_file_text(file_id)
645
this_key =(file_id, default_revision)
646
annotator.add_special_text(this_key, file_parent_keys, text)
647
annotations = [(key[-1], line)
648
for key, line in annotator.annotate_flat(this_key)]
651
def _put_rio(self, filename, stanzas, header):
652
self._must_be_locked()
653
my_file = _mod_rio.rio_file(stanzas, header)
654
self._transport.put_file(filename, my_file,
655
mode=self.bzrdir._get_file_mode())
657
@needs_tree_write_lock
658
def set_merge_modified(self, modified_hashes):
660
for file_id in modified_hashes:
661
yield _mod_rio.Stanza(file_id=file_id.decode('utf8'),
662
hash=modified_hashes[file_id])
663
self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
666
def merge_modified(self):
667
"""Return a dictionary of files modified by a merge.
669
The list is initialized by WorkingTree.set_merge_modified, which is
670
typically called after we make some automatic updates to the tree
673
This returns a map of file_id->sha1, containing only files which are
674
still in the working inventory and have that text hash.
677
hashfile = self._transport.get('merge-hashes')
678
except errors.NoSuchFile:
683
if next(hashfile) != MERGE_MODIFIED_HEADER_1 + '\n':
684
raise errors.MergeModifiedFormatError()
685
except StopIteration:
686
raise errors.MergeModifiedFormatError()
687
for s in _mod_rio.RioReader(hashfile):
688
# RioReader reads in Unicode, so convert file_ids back to utf8
689
file_id = cache_utf8.encode(s.get("file_id"))
690
if not self.has_id(file_id):
692
text_hash = s.get("hash")
693
if text_hash == self.get_file_sha1(file_id):
694
merge_hashes[file_id] = text_hash
700
def subsume(self, other_tree):
701
def add_children(inventory, entry):
702
for child_entry in entry.children.values():
703
inventory._byid[child_entry.file_id] = child_entry
704
if child_entry.kind == 'directory':
705
add_children(inventory, child_entry)
706
if other_tree.get_root_id() == self.get_root_id():
707
raise errors.BadSubsumeSource(self, other_tree,
708
'Trees have the same root')
710
other_tree_path = self.relpath(other_tree.basedir)
711
except errors.PathNotChild:
712
raise errors.BadSubsumeSource(self, other_tree,
713
'Tree is not contained by the other')
714
new_root_parent = self.path2id(osutils.dirname(other_tree_path))
715
if new_root_parent is None:
716
raise errors.BadSubsumeSource(self, other_tree,
717
'Parent directory is not versioned.')
718
# We need to ensure that the result of a fetch will have a
719
# versionedfile for the other_tree root, and only fetching into
720
# RepositoryKnit2 guarantees that.
721
if not self.branch.repository.supports_rich_root():
722
raise errors.SubsumeTargetNeedsUpgrade(other_tree)
723
other_tree.lock_tree_write()
725
new_parents = other_tree.get_parent_ids()
726
other_root = other_tree.root_inventory.root
727
other_root.parent_id = new_root_parent
728
other_root.name = osutils.basename(other_tree_path)
729
self.root_inventory.add(other_root)
730
add_children(self.root_inventory, other_root)
731
self._write_inventory(self.root_inventory)
732
# normally we don't want to fetch whole repositories, but i think
733
# here we really do want to consolidate the whole thing.
734
for parent_id in other_tree.get_parent_ids():
735
self.branch.fetch(other_tree.branch, parent_id)
736
self.add_parent_tree_id(parent_id)
739
other_tree.bzrdir.retire_bzrdir()
741
@needs_tree_write_lock
742
def extract(self, file_id, format=None):
743
"""Extract a subtree from this tree.
745
A new branch will be created, relative to the path for this tree.
749
segments = osutils.splitpath(path)
750
transport = self.branch.bzrdir.root_transport
751
for name in segments:
752
transport = transport.clone(name)
753
transport.ensure_base()
756
sub_path = self.id2path(file_id)
757
branch_transport = mkdirs(sub_path)
759
format = self.bzrdir.cloning_metadir()
760
branch_transport.ensure_base()
761
branch_bzrdir = format.initialize_on_transport(branch_transport)
763
repo = branch_bzrdir.find_repository()
764
except errors.NoRepositoryPresent:
765
repo = branch_bzrdir.create_repository()
766
if not repo.supports_rich_root():
767
raise errors.RootNotRich()
768
new_branch = branch_bzrdir.create_branch()
769
new_branch.pull(self.branch)
770
for parent_id in self.get_parent_ids():
771
new_branch.fetch(self.branch, parent_id)
772
tree_transport = self.bzrdir.root_transport.clone(sub_path)
773
if tree_transport.base != branch_transport.base:
774
tree_bzrdir = format.initialize_on_transport(tree_transport)
775
tree_bzrdir.set_branch_reference(new_branch)
777
tree_bzrdir = branch_bzrdir
778
wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION)
779
wt.set_parent_ids(self.get_parent_ids())
780
# FIXME: Support nested trees
781
my_inv = self.root_inventory
782
child_inv = inventory.Inventory(root_id=None)
783
new_root = my_inv[file_id]
784
my_inv.remove_recursive_id(file_id)
785
new_root.parent_id = None
786
child_inv.add(new_root)
787
self._write_inventory(my_inv)
788
wt._write_inventory(child_inv)
791
def list_files(self, include_root=False, from_dir=None, recursive=True):
792
"""List all files as (path, class, kind, id, entry).
794
Lists, but does not descend into unversioned directories.
795
This does not include files that have been deleted in this
796
tree. Skips the control directory.
798
:param include_root: if True, return an entry for the root
799
:param from_dir: start from this directory or None for the root
800
:param recursive: whether to recurse into subdirectories or not
802
# list_files is an iterator, so @needs_read_lock doesn't work properly
803
# with it. So callers should be careful to always read_lock the tree.
804
if not self.is_locked():
805
raise errors.ObjectNotLocked(self)
807
if from_dir is None and include_root is True:
808
yield ('', 'V', 'directory', self.get_root_id(), self.root_inventory.root)
809
# Convert these into local objects to save lookup times
810
pathjoin = osutils.pathjoin
811
file_kind = self._kind
813
# transport.base ends in a slash, we want the piece
814
# between the last two slashes
815
transport_base_dir = self.bzrdir.transport.base.rsplit('/', 2)[1]
817
fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
819
# directory file_id, relative path, absolute path, reverse sorted children
820
if from_dir is not None:
821
inv, from_dir_id = self._path2inv_file_id(from_dir)
822
if from_dir_id is None:
823
# Directory not versioned
825
from_dir_abspath = pathjoin(self.basedir, from_dir)
827
inv = self.root_inventory
828
from_dir_id = inv.root.file_id
829
from_dir_abspath = self.basedir
830
children = sorted(os.listdir(from_dir_abspath))
831
# jam 20060527 The kernel sized tree seems equivalent whether we
832
# use a deque and popleft to keep them sorted, or if we use a plain
833
# list and just reverse() them.
834
children = collections.deque(children)
835
stack = [(from_dir_id, u'', from_dir_abspath, children)]
837
from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
840
f = children.popleft()
841
## TODO: If we find a subdirectory with its own .bzr
842
## directory, then that is a separate tree and we
843
## should exclude it.
845
# the bzrdir for this tree
846
if transport_base_dir == f:
849
# we know that from_dir_relpath and from_dir_abspath never end in a slash
850
# and 'f' doesn't begin with one, we can do a string op, rather
851
# than the checks of pathjoin(), all relative paths will have an extra slash
853
fp = from_dir_relpath + '/' + f
856
fap = from_dir_abspath + '/' + f
858
dir_ie = inv[from_dir_id]
859
if dir_ie.kind == 'directory':
860
f_ie = dir_ie.children.get(f)
865
elif self.is_ignored(fp[1:]):
868
# we may not have found this file, because of a unicode
869
# issue, or because the directory was actually a symlink.
870
f_norm, can_access = osutils.normalized_filename(f)
871
if f == f_norm or not can_access:
872
# No change, so treat this file normally
875
# this file can be accessed by a normalized path
876
# check again if it is versioned
877
# these lines are repeated here for performance
879
fp = from_dir_relpath + '/' + f
880
fap = from_dir_abspath + '/' + f
881
f_ie = inv.get_child(from_dir_id, f)
884
elif self.is_ignored(fp[1:]):
889
fk = osutils.file_kind(fap)
891
# make a last minute entry
893
yield fp[1:], c, fk, f_ie.file_id, f_ie
896
yield fp[1:], c, fk, None, fk_entries[fk]()
898
yield fp[1:], c, fk, None, TreeEntry()
901
if fk != 'directory':
904
# But do this child first if recursing down
906
new_children = sorted(os.listdir(fap))
907
new_children = collections.deque(new_children)
908
stack.append((f_ie.file_id, fp, fap, new_children))
909
# Break out of inner loop,
910
# so that we start outer loop with child
913
# if we finished all children, pop it off the stack
916
@needs_tree_write_lock
917
def move(self, from_paths, to_dir=None, after=False):
920
to_dir must exist in the inventory.
922
If to_dir exists and is a directory, the files are moved into
923
it, keeping their old names.
925
Note that to_dir is only the last component of the new name;
926
this doesn't change the directory.
928
For each entry in from_paths the move mode will be determined
931
The first mode moves the file in the filesystem and updates the
932
inventory. The second mode only updates the inventory without
933
touching the file on the filesystem.
935
move uses the second mode if 'after == True' and the target is
936
either not versioned or newly added, and present in the working tree.
938
move uses the second mode if 'after == False' and the source is
939
versioned but no longer in the working tree, and the target is not
940
versioned but present in the working tree.
942
move uses the first mode if 'after == False' and the source is
943
versioned and present in the working tree, and the target is not
944
versioned and not present in the working tree.
946
Everything else results in an error.
948
This returns a list of (from_path, to_path) pairs for each
954
invs_to_write = set()
956
# check for deprecated use of signature
958
raise TypeError('You must supply a target directory')
959
# check destination directory
960
if isinstance(from_paths, basestring):
962
to_abs = self.abspath(to_dir)
963
if not osutils.isdir(to_abs):
964
raise errors.BzrMoveFailedError('',to_dir,
965
errors.NotADirectory(to_abs))
966
if not self.has_filename(to_dir):
967
raise errors.BzrMoveFailedError('',to_dir,
968
errors.NotInWorkingDirectory(to_dir))
969
to_inv, to_dir_id = self._path2inv_file_id(to_dir)
970
if to_dir_id is None:
971
raise errors.BzrMoveFailedError('',to_dir,
972
errors.NotVersionedError(path=to_dir))
974
to_dir_ie = to_inv[to_dir_id]
975
if to_dir_ie.kind != 'directory':
976
raise errors.BzrMoveFailedError('',to_dir,
977
errors.NotADirectory(to_abs))
979
# create rename entries and tuples
980
for from_rel in from_paths:
981
from_tail = osutils.splitpath(from_rel)[-1]
982
from_inv, from_id = self._path2inv_file_id(from_rel)
984
raise errors.BzrMoveFailedError(from_rel,to_dir,
985
errors.NotVersionedError(path=from_rel))
987
from_entry = from_inv[from_id]
988
from_parent_id = from_entry.parent_id
989
to_rel = osutils.pathjoin(to_dir, from_tail)
990
rename_entry = InventoryWorkingTree._RenameEntry(
994
from_parent_id=from_parent_id,
995
to_rel=to_rel, to_tail=from_tail,
996
to_parent_id=to_dir_id)
997
rename_entries.append(rename_entry)
998
rename_tuples.append((from_rel, to_rel))
1000
# determine which move mode to use. checks also for movability
1001
rename_entries = self._determine_mv_mode(rename_entries, after)
1003
original_modified = self._inventory_is_modified
1006
self._inventory_is_modified = True
1007
self._move(rename_entries)
1009
# restore the inventory on error
1010
self._inventory_is_modified = original_modified
1012
#FIXME: Should potentially also write the from_invs
1013
self._write_inventory(to_inv)
1014
return rename_tuples
1016
@needs_tree_write_lock
1017
def rename_one(self, from_rel, to_rel, after=False):
1020
This can change the directory or the filename or both.
1022
rename_one has several 'modes' to work. First, it can rename a physical
1023
file and change the file_id. That is the normal mode. Second, it can
1024
only change the file_id without touching any physical file.
1026
rename_one uses the second mode if 'after == True' and 'to_rel' is not
1027
versioned but present in the working tree.
1029
rename_one uses the second mode if 'after == False' and 'from_rel' is
1030
versioned but no longer in the working tree, and 'to_rel' is not
1031
versioned but present in the working tree.
1033
rename_one uses the first mode if 'after == False' and 'from_rel' is
1034
versioned and present in the working tree, and 'to_rel' is not
1035
versioned and not present in the working tree.
1037
Everything else results in an error.
1041
# create rename entries and tuples
1042
from_tail = osutils.splitpath(from_rel)[-1]
1043
from_inv, from_id = self._path2inv_file_id(from_rel)
1045
# if file is missing in the inventory maybe it's in the basis_tree
1046
basis_tree = self.branch.basis_tree()
1047
from_id = basis_tree.path2id(from_rel)
1049
raise errors.BzrRenameFailedError(from_rel,to_rel,
1050
errors.NotVersionedError(path=from_rel))
1051
# put entry back in the inventory so we can rename it
1052
from_entry = basis_tree.root_inventory[from_id].copy()
1053
from_inv.add(from_entry)
1055
from_inv, from_inv_id = self._unpack_file_id(from_id)
1056
from_entry = from_inv[from_inv_id]
1057
from_parent_id = from_entry.parent_id
1058
to_dir, to_tail = os.path.split(to_rel)
1059
to_inv, to_dir_id = self._path2inv_file_id(to_dir)
1060
rename_entry = InventoryWorkingTree._RenameEntry(from_rel=from_rel,
1062
from_tail=from_tail,
1063
from_parent_id=from_parent_id,
1064
to_rel=to_rel, to_tail=to_tail,
1065
to_parent_id=to_dir_id)
1066
rename_entries.append(rename_entry)
1068
# determine which move mode to use. checks also for movability
1069
rename_entries = self._determine_mv_mode(rename_entries, after)
1071
# check if the target changed directory and if the target directory is
1073
if to_dir_id is None:
1074
raise errors.BzrMoveFailedError(from_rel,to_rel,
1075
errors.NotVersionedError(path=to_dir))
1077
# all checks done. now we can continue with our actual work
1078
mutter('rename_one:\n'
1083
' to_dir_id {%s}\n',
1084
from_id, from_rel, to_rel, to_dir, to_dir_id)
1086
self._move(rename_entries)
1087
self._write_inventory(to_inv)
1089
class _RenameEntry(object):
1090
def __init__(self, from_rel, from_id, from_tail, from_parent_id,
1091
to_rel, to_tail, to_parent_id, only_change_inv=False,
1093
self.from_rel = from_rel
1094
self.from_id = from_id
1095
self.from_tail = from_tail
1096
self.from_parent_id = from_parent_id
1097
self.to_rel = to_rel
1098
self.to_tail = to_tail
1099
self.to_parent_id = to_parent_id
1100
self.change_id = change_id
1101
self.only_change_inv = only_change_inv
1103
def _determine_mv_mode(self, rename_entries, after=False):
1104
"""Determines for each from-to pair if both inventory and working tree
1105
or only the inventory has to be changed.
1107
Also does basic plausability tests.
1109
# FIXME: Handling of nested trees
1110
inv = self.root_inventory
1112
for rename_entry in rename_entries:
1113
# store to local variables for easier reference
1114
from_rel = rename_entry.from_rel
1115
from_id = rename_entry.from_id
1116
to_rel = rename_entry.to_rel
1117
to_id = inv.path2id(to_rel)
1118
only_change_inv = False
1121
# check the inventory for source and destination
1123
raise errors.BzrMoveFailedError(from_rel,to_rel,
1124
errors.NotVersionedError(path=from_rel))
1125
if to_id is not None:
1127
# allow it with --after but only if dest is newly added
1129
basis = self.basis_tree()
1132
if not basis.has_id(to_id):
1133
rename_entry.change_id = True
1138
raise errors.BzrMoveFailedError(from_rel,to_rel,
1139
errors.AlreadyVersionedError(path=to_rel))
1141
# try to determine the mode for rename (only change inv or change
1142
# inv and file system)
1144
if not self.has_filename(to_rel):
1145
raise errors.BzrMoveFailedError(from_id,to_rel,
1146
errors.NoSuchFile(path=to_rel,
1147
extra="New file has not been created yet"))
1148
only_change_inv = True
1149
elif not self.has_filename(from_rel) and self.has_filename(to_rel):
1150
only_change_inv = True
1151
elif self.has_filename(from_rel) and not self.has_filename(to_rel):
1152
only_change_inv = False
1153
elif (not self.case_sensitive
1154
and from_rel.lower() == to_rel.lower()
1155
and self.has_filename(from_rel)):
1156
only_change_inv = False
1158
# something is wrong, so lets determine what exactly
1159
if not self.has_filename(from_rel) and \
1160
not self.has_filename(to_rel):
1161
raise errors.BzrRenameFailedError(from_rel, to_rel,
1162
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
1164
raise errors.RenameFailedFilesExist(from_rel, to_rel)
1165
rename_entry.only_change_inv = only_change_inv
1166
return rename_entries
1168
def _move(self, rename_entries):
1169
"""Moves a list of files.
1171
Depending on the value of the flag 'only_change_inv', the
1172
file will be moved on the file system or not.
1176
for entry in rename_entries:
1178
self._move_entry(entry)
1180
self._rollback_move(moved)
1184
def _rollback_move(self, moved):
1185
"""Try to rollback a previous move in case of an filesystem error."""
1188
self._move_entry(WorkingTree._RenameEntry(
1189
entry.to_rel, entry.from_id,
1190
entry.to_tail, entry.to_parent_id, entry.from_rel,
1191
entry.from_tail, entry.from_parent_id,
1192
entry.only_change_inv))
1193
except errors.BzrMoveFailedError as e:
1194
raise errors.BzrMoveFailedError( '', '', "Rollback failed."
1195
" The working tree is in an inconsistent state."
1196
" Please consider doing a 'bzr revert'."
1197
" Error message is: %s" % e)
1199
def _move_entry(self, entry):
1200
inv = self.root_inventory
1201
from_rel_abs = self.abspath(entry.from_rel)
1202
to_rel_abs = self.abspath(entry.to_rel)
1203
if from_rel_abs == to_rel_abs:
1204
raise errors.BzrMoveFailedError(entry.from_rel, entry.to_rel,
1205
"Source and target are identical.")
1207
if not entry.only_change_inv:
1209
osutils.rename(from_rel_abs, to_rel_abs)
1210
except OSError as e:
1211
raise errors.BzrMoveFailedError(entry.from_rel,
1214
to_id = inv.path2id(entry.to_rel)
1215
inv.remove_recursive_id(to_id)
1216
inv.rename(entry.from_id, entry.to_parent_id, entry.to_tail)
1218
@needs_tree_write_lock
1219
def unversion(self, file_ids):
1220
"""Remove the file ids in file_ids from the current versioned set.
1222
When a file_id is unversioned, all of its children are automatically
1225
:param file_ids: The file ids to stop versioning.
1226
:raises: NoSuchId if any fileid is not currently versioned.
1228
for file_id in file_ids:
1229
if not self._inventory.has_id(file_id):
1230
raise errors.NoSuchId(self, file_id)
1231
for file_id in file_ids:
1232
if self._inventory.has_id(file_id):
1233
self._inventory.remove_recursive_id(file_id)
1235
# in the future this should just set a dirty bit to wait for the
1236
# final unlock. However, until all methods of workingtree start
1237
# with the current in -memory inventory rather than triggering
1238
# a read, it is more complex - we need to teach read_inventory
1239
# to know when to read, and when to not read first... and possibly
1240
# to save first when the in memory one may be corrupted.
1241
# so for now, we just only write it if it is indeed dirty.
1243
self._write_inventory(self._inventory)
1245
def stored_kind(self, file_id):
1246
"""See Tree.stored_kind"""
1247
inv, inv_file_id = self._unpack_file_id(file_id)
1248
return inv[inv_file_id].kind
1251
"""Yield all unversioned files in this WorkingTree.
1253
If there are any unversioned directories then only the directory is
1254
returned, not all its children. But if there are unversioned files
1255
under a versioned subdirectory, they are returned.
1257
Currently returned depth-first, sorted by name within directories.
1258
This is the same order used by 'osutils.walkdirs'.
1260
## TODO: Work from given directory downwards
1261
for path, dir_entry in self.iter_entries_by_dir():
1262
if dir_entry.kind != 'directory':
1264
# mutter("search for unknowns in %r", path)
1265
dirabs = self.abspath(path)
1266
if not osutils.isdir(dirabs):
1267
# e.g. directory deleted
1271
for subf in os.listdir(dirabs):
1272
if self.bzrdir.is_control_filename(subf):
1274
if subf not in dir_entry.children:
1277
can_access) = osutils.normalized_filename(subf)
1278
except UnicodeDecodeError:
1279
path_os_enc = path.encode(osutils._fs_enc)
1280
relpath = path_os_enc + '/' + subf
1281
raise errors.BadFilenameEncoding(relpath,
1283
if subf_norm != subf and can_access:
1284
if subf_norm not in dir_entry.children:
1285
fl.append(subf_norm)
1291
subp = osutils.pathjoin(path, subf)
1294
def _walkdirs(self, prefix=""):
1295
"""Walk the directories of this tree.
1297
:param prefix: is used as the directrory to start with.
1298
:returns: a generator which yields items in the form::
1300
((curren_directory_path, fileid),
1301
[(file1_path, file1_name, file1_kind, None, file1_id,
1304
_directory = 'directory'
1305
# get the root in the inventory
1306
inv, top_id = self._path2inv_file_id(prefix)
1310
pending = [(prefix, '', _directory, None, top_id, None)]
1313
currentdir = pending.pop()
1314
# 0 - relpath, 1- basename, 2- kind, 3- stat, 4-id, 5-kind
1315
top_id = currentdir[4]
1317
relroot = currentdir[0] + '/'
1320
# FIXME: stash the node in pending
1322
if entry.kind == 'directory':
1323
for name, child in entry.sorted_children():
1324
dirblock.append((relroot + name, name, child.kind, None,
1325
child.file_id, child.kind
1327
yield (currentdir[0], entry.file_id), dirblock
1328
# push the user specified dirs from dirblock
1329
for dir in reversed(dirblock):
1330
if dir[2] == _directory:
1334
def update_feature_flags(self, updated_flags):
1335
"""Update the feature flags for this branch.
1337
:param updated_flags: Dictionary mapping feature names to necessities
1338
A necessity can be None to indicate the feature should be removed
1340
self._format._update_feature_flags(updated_flags)
1341
self.control_transport.put_bytes('format', self._format.as_string())
1343
def _check_for_tree_references(self, iterator):
1344
"""See if directories have become tree-references."""
1345
blocked_parent_ids = set()
1346
for path, ie in iterator:
1347
if ie.parent_id in blocked_parent_ids:
1348
# This entry was pruned because one of its parents became a
1349
# TreeReference. If this is a directory, mark it as blocked.
1350
if ie.kind == 'directory':
1351
blocked_parent_ids.add(ie.file_id)
1353
if ie.kind == 'directory' and self._directory_is_tree_reference(path):
1354
# This InventoryDirectory needs to be a TreeReference
1355
ie = inventory.TreeReference(ie.file_id, ie.name, ie.parent_id)
1356
blocked_parent_ids.add(ie.file_id)
1359
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1360
"""See Tree.iter_entries_by_dir()"""
1361
# The only trick here is that if we supports_tree_reference then we
1362
# need to detect if a directory becomes a tree-reference.
1363
iterator = super(WorkingTree, self).iter_entries_by_dir(
1364
specific_file_ids=specific_file_ids,
1365
yield_parents=yield_parents)
1366
if not self.supports_tree_reference():
1369
return self._check_for_tree_references(iterator)
1372
class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
1373
"""Base class for working trees that live in bzr meta directories."""
1376
WorkingTreeFormat.__init__(self)
1377
bzrdir.BzrFormat.__init__(self)
1380
def find_format_string(klass, controldir):
1381
"""Return format name for the working tree object in controldir."""
1383
transport = controldir.get_workingtree_transport(None)
1384
return transport.get_bytes("format")
1385
except errors.NoSuchFile:
1386
raise errors.NoWorkingTree(base=transport.base)
1389
def find_format(klass, controldir):
1390
"""Return the format for the working tree object in controldir."""
1391
format_string = klass.find_format_string(controldir)
1392
return klass._find_format(format_registry, 'working tree',
1395
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
1397
WorkingTreeFormat.check_support_status(self,
1398
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
1400
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
1401
recommend_upgrade=recommend_upgrade, basedir=basedir)
1403
def get_controldir_for_branch(self):
1404
"""Get the control directory format for creating branches.
1406
This is to support testing of working tree formats that can not exist
1407
in the same control directory as a branch.
1409
return self._matchingbzrdir
1412
class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
1413
"""Base class for working trees that live in bzr meta directories."""
1416
WorkingTreeFormat.__init__(self)
1417
bzrdir.BzrFormat.__init__(self)
1420
def find_format_string(klass, controldir):
1421
"""Return format name for the working tree object in controldir."""
1423
transport = controldir.get_workingtree_transport(None)
1424
return transport.get_bytes("format")
1425
except errors.NoSuchFile:
1426
raise errors.NoWorkingTree(base=transport.base)
1429
def find_format(klass, controldir):
1430
"""Return the format for the working tree object in controldir."""
1431
format_string = klass.find_format_string(controldir)
1432
return klass._find_format(format_registry, 'working tree',
1435
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
1437
WorkingTreeFormat.check_support_status(self,
1438
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
1440
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
1441
recommend_upgrade=recommend_upgrade, basedir=basedir)