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
41
# Explicitly import breezy.bzrdir so that the BzrProber
42
# is guaranteed to be registered.
45
from . import lazy_import
46
lazy_import.lazy_import(globals(), """
49
conflicts as _mod_conflicts,
55
revision as _mod_revision,
64
from .decorators import needs_write_lock, needs_read_lock
65
from .lock import _RelockDebugMixin, LogicalLockResult
66
from .mutabletree import needs_tree_write_lock
70
from .trace import mutter
71
from .workingtree import (
81
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
82
# TODO: Modifying the conflict objects or their type is currently nearly
83
# impossible as there is no clear relationship between the working tree format
84
# and the conflict list file format.
85
CONFLICT_HEADER_1 = "BZR conflict list format 1"
88
class InventoryWorkingTree(WorkingTree,
89
mutabletree.MutableInventoryTree):
90
"""Base class for working trees that are inventory-oriented.
92
The inventory is held in the `Branch` working-inventory, and the
93
files are in a directory on disk.
95
It is possible for a `WorkingTree` to have a filename which is
96
not listed in the Inventory and vice versa.
99
def __init__(self, basedir='.',
106
"""Construct a InventoryWorkingTree instance. This is not a public API.
108
:param branch: A branch to override probing for the branch.
110
super(InventoryWorkingTree, self).__init__(basedir=basedir,
111
branch=branch, _transport=_control_files._transport,
112
_internal=_internal, _format=_format, _bzrdir=_bzrdir)
114
self._control_files = _control_files
115
self._detect_case_handling()
117
if _inventory is None:
118
# This will be acquired on lock_read() or lock_write()
119
self._inventory_is_modified = False
120
self._inventory = None
122
# the caller of __init__ has provided an inventory,
123
# we assume they know what they are doing - as its only
124
# the Format factory and creation methods that are
125
# permitted to do this.
126
self._set_inventory(_inventory, dirty=False)
128
def _set_inventory(self, inv, dirty):
129
"""Set the internal cached inventory.
131
:param inv: The inventory to set.
132
:param dirty: A boolean indicating whether the inventory is the same
133
logical inventory as whats on disk. If True the inventory is not
134
the same and should be written to disk or data will be lost, if
135
False then the inventory is the same as that on disk and any
136
serialisation would be unneeded overhead.
138
self._inventory = inv
139
self._inventory_is_modified = dirty
141
def _detect_case_handling(self):
142
wt_trans = self.controldir.get_workingtree_transport(None)
144
wt_trans.stat(self._format.case_sensitive_filename)
145
except errors.NoSuchFile:
146
self.case_sensitive = True
148
self.case_sensitive = False
150
self._setup_directory_is_tree_reference()
152
def _serialize(self, inventory, out_file):
153
xml5.serializer_v5.write_inventory(self._inventory, out_file,
156
def _deserialize(selt, in_file):
157
return xml5.serializer_v5.read_inventory(in_file)
159
def break_lock(self):
160
"""Break a lock if one is present from another instance.
162
Uses the ui factory to ask for confirmation if the lock may be from
165
This will probe the repository for its lock as well.
167
self._control_files.break_lock()
168
self.branch.break_lock()
171
return self._control_files.is_locked()
173
def _must_be_locked(self):
174
if not self.is_locked():
175
raise errors.ObjectNotLocked(self)
178
"""Lock the tree for reading.
180
This also locks the branch, and can be unlocked via self.unlock().
182
:return: A breezy.lock.LogicalLockResult.
184
if not self.is_locked():
186
self.branch.lock_read()
188
self._control_files.lock_read()
189
return LogicalLockResult(self.unlock)
194
def lock_tree_write(self):
195
"""See MutableTree.lock_tree_write, and WorkingTree.unlock.
197
:return: A breezy.lock.LogicalLockResult.
199
if not self.is_locked():
201
self.branch.lock_read()
203
self._control_files.lock_write()
204
return LogicalLockResult(self.unlock)
209
def lock_write(self):
210
"""See MutableTree.lock_write, and WorkingTree.unlock.
212
:return: A breezy.lock.LogicalLockResult.
214
if not self.is_locked():
216
self.branch.lock_write()
218
self._control_files.lock_write()
219
return LogicalLockResult(self.unlock)
224
def get_physical_lock_status(self):
225
return self._control_files.get_physical_lock_status()
227
@needs_tree_write_lock
228
def _write_inventory(self, inv):
229
"""Write inventory as the current inventory."""
230
self._set_inventory(inv, dirty=True)
233
# XXX: This method should be deprecated in favour of taking in a proper
234
# new Inventory object.
235
@needs_tree_write_lock
236
def set_inventory(self, new_inventory_list):
237
from .inventory import (
242
inv = Inventory(self.get_root_id())
243
for path, file_id, parent, kind in new_inventory_list:
244
name = os.path.basename(path)
247
# fixme, there should be a factory function inv,add_??
248
if kind == 'directory':
249
inv.add(InventoryDirectory(file_id, name, parent))
251
inv.add(InventoryFile(file_id, name, parent))
252
elif kind == 'symlink':
253
inv.add(InventoryLink(file_id, name, parent))
255
raise errors.BzrError("unknown kind %r" % kind)
256
self._write_inventory(inv)
258
def _write_basis_inventory(self, xml):
259
"""Write the basis inventory XML to the basis-inventory file"""
260
path = self._basis_inventory_name()
262
self._transport.put_file(path, sio,
263
mode=self.controldir._get_file_mode())
265
def _reset_data(self):
266
"""Reset transient data that cannot be revalidated."""
267
self._inventory_is_modified = False
268
f = self._transport.get('inventory')
270
result = self._deserialize(f)
273
self._set_inventory(result, dirty=False)
275
def _set_root_id(self, file_id):
276
"""Set the root id for this tree, in a format specific manner.
278
:param file_id: The file id to assign to the root. It must not be
279
present in the current inventory or an error will occur. It must
280
not be None, but rather a valid file id.
282
inv = self._inventory
283
orig_root_id = inv.root.file_id
284
# TODO: it might be nice to exit early if there was nothing
285
# to do, saving us from trigger a sync on unlock.
286
self._inventory_is_modified = True
287
# we preserve the root inventory entry object, but
288
# unlinkit from the byid index
289
del inv._byid[inv.root.file_id]
290
inv.root.file_id = file_id
291
# and link it into the index with the new changed id.
292
inv._byid[inv.root.file_id] = inv.root
293
# and finally update all children to reference the new id.
294
# XXX: this should be safe to just look at the root.children
295
# list, not the WHOLE INVENTORY.
298
if entry.parent_id == orig_root_id:
299
entry.parent_id = inv.root.file_id
301
@needs_tree_write_lock
302
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
303
"""See MutableTree.set_parent_trees."""
304
parent_ids = [rev for (rev, tree) in parents_list]
305
for revision_id in parent_ids:
306
_mod_revision.check_not_reserved_id(revision_id)
308
self._check_parents_for_ghosts(parent_ids,
309
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
311
parent_ids = self._filter_parent_ids_by_ancestry(parent_ids)
313
if len(parent_ids) == 0:
314
leftmost_parent_id = _mod_revision.NULL_REVISION
315
leftmost_parent_tree = None
317
leftmost_parent_id, leftmost_parent_tree = parents_list[0]
319
if self._change_last_revision(leftmost_parent_id):
320
if leftmost_parent_tree is None:
321
# If we don't have a tree, fall back to reading the
322
# parent tree from the repository.
323
self._cache_basis_inventory(leftmost_parent_id)
325
inv = leftmost_parent_tree.root_inventory
326
xml = self._create_basis_xml_from_inventory(
327
leftmost_parent_id, inv)
328
self._write_basis_inventory(xml)
329
self._set_merges_from_parent_ids(parent_ids)
331
def _cache_basis_inventory(self, new_revision):
332
"""Cache new_revision as the basis inventory."""
333
# TODO: this should allow the ready-to-use inventory to be passed in,
334
# as commit already has that ready-to-use [while the format is the
337
# this double handles the inventory - unpack and repack -
338
# but is easier to understand. We can/should put a conditional
339
# in here based on whether the inventory is in the latest format
340
# - perhaps we should repack all inventories on a repository
342
# the fast path is to copy the raw xml from the repository. If the
343
# xml contains 'revision_id="', then we assume the right
344
# revision_id is set. We must check for this full string, because a
345
# root node id can legitimately look like 'revision_id' but cannot
347
xml = self.branch.repository._get_inventory_xml(new_revision)
348
firstline = xml.split('\n', 1)[0]
349
if (not 'revision_id="' in firstline or
350
'format="7"' not in firstline):
351
inv = self.branch.repository._serializer.read_inventory_from_string(
353
xml = self._create_basis_xml_from_inventory(new_revision, inv)
354
self._write_basis_inventory(xml)
355
except (errors.NoSuchRevision, errors.RevisionNotPresent):
358
def _basis_inventory_name(self):
359
return 'basis-inventory-cache'
361
def _create_basis_xml_from_inventory(self, revision_id, inventory):
362
"""Create the text that will be saved in basis-inventory"""
363
inventory.revision_id = revision_id
364
return xml7.serializer_v7.write_inventory_to_string(inventory)
366
@needs_tree_write_lock
367
def set_conflicts(self, conflicts):
368
self._put_rio('conflicts', conflicts.to_stanzas(),
371
@needs_tree_write_lock
372
def add_conflicts(self, new_conflicts):
373
conflict_set = set(self.conflicts())
374
conflict_set.update(set(list(new_conflicts)))
375
self.set_conflicts(_mod_conflicts.ConflictList(sorted(conflict_set,
376
key=_mod_conflicts.Conflict.sort_key)))
381
confile = self._transport.get('conflicts')
382
except errors.NoSuchFile:
383
return _mod_conflicts.ConflictList()
386
if next(confile) != CONFLICT_HEADER_1 + '\n':
387
raise errors.ConflictFormatError()
388
except StopIteration:
389
raise errors.ConflictFormatError()
390
reader = _mod_rio.RioReader(confile)
391
return _mod_conflicts.ConflictList.from_stanzas(reader)
395
def read_basis_inventory(self):
396
"""Read the cached basis inventory."""
397
path = self._basis_inventory_name()
398
return self._transport.get_bytes(path)
401
def read_working_inventory(self):
402
"""Read the working inventory.
404
:raises errors.InventoryModified: read_working_inventory will fail
405
when the current in memory inventory has been modified.
407
# conceptually this should be an implementation detail of the tree.
408
# XXX: Deprecate this.
409
# ElementTree does its own conversion from UTF-8, so open in
411
if self._inventory_is_modified:
412
raise errors.InventoryModified(self)
413
f = self._transport.get('inventory')
415
result = self._deserialize(f)
418
self._set_inventory(result, dirty=False)
422
def get_root_id(self):
423
"""Return the id of this trees root"""
424
return self._inventory.root.file_id
426
def has_id(self, file_id):
427
# files that have been deleted are excluded
428
inv, inv_file_id = self._unpack_file_id(file_id)
429
if not inv.has_id(inv_file_id):
431
path = inv.id2path(inv_file_id)
432
return osutils.lexists(self.abspath(path))
434
def has_or_had_id(self, file_id):
435
if file_id == self.get_root_id():
437
inv, inv_file_id = self._unpack_file_id(file_id)
438
return inv.has_id(inv_file_id)
440
def all_file_ids(self):
441
"""Iterate through file_ids for this tree.
443
file_ids are in a WorkingTree if they are in the working inventory
444
and the working file exists.
447
for path, ie in self.iter_entries_by_dir():
451
@needs_tree_write_lock
452
def set_last_revision(self, new_revision):
453
"""Change the last revision in the working tree."""
454
if self._change_last_revision(new_revision):
455
self._cache_basis_inventory(new_revision)
457
def _get_check_refs(self):
458
"""Return the references needed to perform a check of this tree.
460
The default implementation returns no refs, and is only suitable for
461
trees that have no local caching and can commit on ghosts at any time.
463
:seealso: breezy.check for details about check_refs.
468
def _check(self, references):
469
"""Check the tree for consistency.
471
:param references: A dict with keys matching the items returned by
472
self._get_check_refs(), and values from looking those keys up in
475
tree_basis = self.basis_tree()
476
tree_basis.lock_read()
478
repo_basis = references[('trees', self.last_revision())]
479
if len(list(repo_basis.iter_changes(tree_basis))) > 0:
480
raise errors.BzrCheckError(
481
"Mismatched basis inventory content.")
487
def check_state(self):
488
"""Check that the working state is/isn't valid."""
489
check_refs = self._get_check_refs()
491
for ref in check_refs:
494
refs[ref] = self.branch.repository.revision_tree(value)
497
@needs_tree_write_lock
498
def reset_state(self, revision_ids=None):
499
"""Reset the state of the working tree.
501
This does a hard-reset to a last-known-good state. This is a way to
502
fix if something got corrupted (like the .bzr/checkout/dirstate file)
504
if revision_ids is None:
505
revision_ids = self.get_parent_ids()
507
rt = self.branch.repository.revision_tree(
508
_mod_revision.NULL_REVISION)
510
rt = self.branch.repository.revision_tree(revision_ids[0])
511
self._write_inventory(rt.root_inventory)
512
self.set_parent_ids(revision_ids)
515
"""Write the in memory inventory to disk."""
516
# TODO: Maybe this should only write on dirty ?
517
if self._control_files._lock_mode != 'w':
518
raise errors.NotWriteLocked(self)
520
self._serialize(self._inventory, sio)
522
self._transport.put_file('inventory', sio,
523
mode=self.controldir._get_file_mode())
524
self._inventory_is_modified = False
526
def get_file_mtime(self, file_id, path=None):
527
"""See Tree.get_file_mtime."""
529
path = self.id2path(file_id)
531
return os.lstat(self.abspath(path)).st_mtime
533
if e.errno == errno.ENOENT:
534
raise errors.FileTimestampUnavailable(path)
537
def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
538
inv, file_id = self._path2inv_file_id(path)
540
# For unversioned files on win32, we just assume they are not
543
return inv[file_id].executable
545
def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
546
mode = stat_result.st_mode
547
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
549
def is_executable(self, file_id, path=None):
550
if not self._supports_executable():
551
inv, inv_file_id = self._unpack_file_id(file_id)
552
return inv[inv_file_id].executable
555
path = self.id2path(file_id)
556
mode = os.lstat(self.abspath(path)).st_mode
557
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
559
def _is_executable_from_path_and_stat(self, path, stat_result):
560
if not self._supports_executable():
561
return self._is_executable_from_path_and_stat_from_basis(path, stat_result)
563
return self._is_executable_from_path_and_stat_from_stat(path, stat_result)
565
@needs_tree_write_lock
566
def _add(self, files, ids, kinds):
567
"""See MutableTree._add."""
568
# TODO: Re-adding a file that is removed in the working copy
569
# should probably put it back with the previous ID.
570
# the read and write working inventory should not occur in this
571
# function - they should be part of lock_write and unlock.
572
# FIXME: nested trees
573
inv = self.root_inventory
574
for f, file_id, kind in zip(files, ids, kinds):
576
inv.add_path(f, kind=kind)
578
inv.add_path(f, kind=kind, file_id=file_id)
579
self._inventory_is_modified = True
581
def revision_tree(self, revision_id):
582
"""See WorkingTree.revision_id."""
583
if revision_id == self.last_revision():
585
xml = self.read_basis_inventory()
586
except errors.NoSuchFile:
590
inv = xml7.serializer_v7.read_inventory_from_string(xml)
591
# dont use the repository revision_tree api because we want
592
# to supply the inventory.
593
if inv.revision_id == revision_id:
594
return revisiontree.InventoryRevisionTree(
595
self.branch.repository, inv, revision_id)
596
except errors.BadInventoryFormat:
598
# raise if there was no inventory, or if we read the wrong inventory.
599
raise errors.NoSuchRevisionInTree(self, revision_id)
602
def annotate_iter(self, file_id,
603
default_revision=_mod_revision.CURRENT_REVISION):
604
"""See Tree.annotate_iter
606
This implementation will use the basis tree implementation if possible.
607
Lines not in the basis are attributed to CURRENT_REVISION
609
If there are pending merges, lines added by those merges will be
610
incorrectly attributed to CURRENT_REVISION (but after committing, the
611
attribution will be correct).
613
maybe_file_parent_keys = []
614
for parent_id in self.get_parent_ids():
616
parent_tree = self.revision_tree(parent_id)
617
except errors.NoSuchRevisionInTree:
618
parent_tree = self.branch.repository.revision_tree(parent_id)
619
parent_tree.lock_read()
622
kind = parent_tree.kind(file_id)
623
except errors.NoSuchId:
626
# Note: this is slightly unnecessary, because symlinks and
627
# directories have a "text" which is the empty text, and we
628
# know that won't mess up annotations. But it seems cleaner
631
file_id, parent_tree.get_file_revision(file_id))
632
if parent_text_key not in maybe_file_parent_keys:
633
maybe_file_parent_keys.append(parent_text_key)
636
graph = _mod_graph.Graph(self.branch.repository.texts)
637
heads = graph.heads(maybe_file_parent_keys)
638
file_parent_keys = []
639
for key in maybe_file_parent_keys:
641
file_parent_keys.append(key)
643
# Now we have the parents of this content
644
annotator = self.branch.repository.texts.get_annotator()
645
text = self.get_file_text(file_id)
646
this_key =(file_id, default_revision)
647
annotator.add_special_text(this_key, file_parent_keys, text)
648
annotations = [(key[-1], line)
649
for key, line in annotator.annotate_flat(this_key)]
652
def _put_rio(self, filename, stanzas, header):
653
self._must_be_locked()
654
my_file = _mod_rio.rio_file(stanzas, header)
655
self._transport.put_file(filename, my_file,
656
mode=self.controldir._get_file_mode())
658
@needs_tree_write_lock
659
def set_merge_modified(self, modified_hashes):
661
for file_id in modified_hashes:
662
yield _mod_rio.Stanza(file_id=file_id.decode('utf8'),
663
hash=modified_hashes[file_id])
664
self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
667
def merge_modified(self):
668
"""Return a dictionary of files modified by a merge.
670
The list is initialized by WorkingTree.set_merge_modified, which is
671
typically called after we make some automatic updates to the tree
674
This returns a map of file_id->sha1, containing only files which are
675
still in the working inventory and have that text hash.
678
hashfile = self._transport.get('merge-hashes')
679
except errors.NoSuchFile:
684
if next(hashfile) != MERGE_MODIFIED_HEADER_1 + '\n':
685
raise errors.MergeModifiedFormatError()
686
except StopIteration:
687
raise errors.MergeModifiedFormatError()
688
for s in _mod_rio.RioReader(hashfile):
689
# RioReader reads in Unicode, so convert file_ids back to utf8
690
file_id = cache_utf8.encode(s.get("file_id"))
691
if not self.has_id(file_id):
693
text_hash = s.get("hash")
694
if text_hash == self.get_file_sha1(file_id):
695
merge_hashes[file_id] = text_hash
701
def subsume(self, other_tree):
702
def add_children(inventory, entry):
703
for child_entry in entry.children.values():
704
inventory._byid[child_entry.file_id] = child_entry
705
if child_entry.kind == 'directory':
706
add_children(inventory, child_entry)
707
if other_tree.get_root_id() == self.get_root_id():
708
raise errors.BadSubsumeSource(self, other_tree,
709
'Trees have the same root')
711
other_tree_path = self.relpath(other_tree.basedir)
712
except errors.PathNotChild:
713
raise errors.BadSubsumeSource(self, other_tree,
714
'Tree is not contained by the other')
715
new_root_parent = self.path2id(osutils.dirname(other_tree_path))
716
if new_root_parent is None:
717
raise errors.BadSubsumeSource(self, other_tree,
718
'Parent directory is not versioned.')
719
# We need to ensure that the result of a fetch will have a
720
# versionedfile for the other_tree root, and only fetching into
721
# RepositoryKnit2 guarantees that.
722
if not self.branch.repository.supports_rich_root():
723
raise errors.SubsumeTargetNeedsUpgrade(other_tree)
724
other_tree.lock_tree_write()
726
new_parents = other_tree.get_parent_ids()
727
other_root = other_tree.root_inventory.root
728
other_root.parent_id = new_root_parent
729
other_root.name = osutils.basename(other_tree_path)
730
self.root_inventory.add(other_root)
731
add_children(self.root_inventory, other_root)
732
self._write_inventory(self.root_inventory)
733
# normally we don't want to fetch whole repositories, but i think
734
# here we really do want to consolidate the whole thing.
735
for parent_id in other_tree.get_parent_ids():
736
self.branch.fetch(other_tree.branch, parent_id)
737
self.add_parent_tree_id(parent_id)
740
other_tree.controldir.retire_bzrdir()
742
@needs_tree_write_lock
743
def extract(self, file_id, format=None):
744
"""Extract a subtree from this tree.
746
A new branch will be created, relative to the path for this tree.
750
segments = osutils.splitpath(path)
751
transport = self.branch.controldir.root_transport
752
for name in segments:
753
transport = transport.clone(name)
754
transport.ensure_base()
757
sub_path = self.id2path(file_id)
758
branch_transport = mkdirs(sub_path)
760
format = self.controldir.cloning_metadir()
761
branch_transport.ensure_base()
762
branch_bzrdir = format.initialize_on_transport(branch_transport)
764
repo = branch_bzrdir.find_repository()
765
except errors.NoRepositoryPresent:
766
repo = branch_bzrdir.create_repository()
767
if not repo.supports_rich_root():
768
raise errors.RootNotRich()
769
new_branch = branch_bzrdir.create_branch()
770
new_branch.pull(self.branch)
771
for parent_id in self.get_parent_ids():
772
new_branch.fetch(self.branch, parent_id)
773
tree_transport = self.controldir.root_transport.clone(sub_path)
774
if tree_transport.base != branch_transport.base:
775
tree_bzrdir = format.initialize_on_transport(tree_transport)
776
tree_bzrdir.set_branch_reference(new_branch)
778
tree_bzrdir = branch_bzrdir
779
wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION)
780
wt.set_parent_ids(self.get_parent_ids())
781
# FIXME: Support nested trees
782
my_inv = self.root_inventory
783
child_inv = inventory.Inventory(root_id=None)
784
new_root = my_inv[file_id]
785
my_inv.remove_recursive_id(file_id)
786
new_root.parent_id = None
787
child_inv.add(new_root)
788
self._write_inventory(my_inv)
789
wt._write_inventory(child_inv)
792
def list_files(self, include_root=False, from_dir=None, recursive=True):
793
"""List all files as (path, class, kind, id, entry).
795
Lists, but does not descend into unversioned directories.
796
This does not include files that have been deleted in this
797
tree. Skips the control directory.
799
:param include_root: if True, return an entry for the root
800
:param from_dir: start from this directory or None for the root
801
:param recursive: whether to recurse into subdirectories or not
803
# list_files is an iterator, so @needs_read_lock doesn't work properly
804
# with it. So callers should be careful to always read_lock the tree.
805
if not self.is_locked():
806
raise errors.ObjectNotLocked(self)
808
if from_dir is None and include_root is True:
809
yield ('', 'V', 'directory', self.get_root_id(), self.root_inventory.root)
810
# Convert these into local objects to save lookup times
811
pathjoin = osutils.pathjoin
812
file_kind = self._kind
814
# transport.base ends in a slash, we want the piece
815
# between the last two slashes
816
transport_base_dir = self.controldir.transport.base.rsplit('/', 2)[1]
818
fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
820
# directory file_id, relative path, absolute path, reverse sorted children
821
if from_dir is not None:
822
inv, from_dir_id = self._path2inv_file_id(from_dir)
823
if from_dir_id is None:
824
# Directory not versioned
826
from_dir_abspath = pathjoin(self.basedir, from_dir)
828
inv = self.root_inventory
829
from_dir_id = inv.root.file_id
830
from_dir_abspath = self.basedir
831
children = sorted(os.listdir(from_dir_abspath))
832
# jam 20060527 The kernel sized tree seems equivalent whether we
833
# use a deque and popleft to keep them sorted, or if we use a plain
834
# list and just reverse() them.
835
children = collections.deque(children)
836
stack = [(from_dir_id, u'', from_dir_abspath, children)]
838
from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
841
f = children.popleft()
842
## TODO: If we find a subdirectory with its own .bzr
843
## directory, then that is a separate tree and we
844
## should exclude it.
846
# the bzrdir for this tree
847
if transport_base_dir == f:
850
# we know that from_dir_relpath and from_dir_abspath never end in a slash
851
# and 'f' doesn't begin with one, we can do a string op, rather
852
# than the checks of pathjoin(), all relative paths will have an extra slash
854
fp = from_dir_relpath + '/' + f
857
fap = from_dir_abspath + '/' + f
859
dir_ie = inv[from_dir_id]
860
if dir_ie.kind == 'directory':
861
f_ie = dir_ie.children.get(f)
866
elif self.is_ignored(fp[1:]):
869
# we may not have found this file, because of a unicode
870
# issue, or because the directory was actually a symlink.
871
f_norm, can_access = osutils.normalized_filename(f)
872
if f == f_norm or not can_access:
873
# No change, so treat this file normally
876
# this file can be accessed by a normalized path
877
# check again if it is versioned
878
# these lines are repeated here for performance
880
fp = from_dir_relpath + '/' + f
881
fap = from_dir_abspath + '/' + f
882
f_ie = inv.get_child(from_dir_id, f)
885
elif self.is_ignored(fp[1:]):
890
fk = osutils.file_kind(fap)
892
# make a last minute entry
894
yield fp[1:], c, fk, f_ie.file_id, f_ie
897
yield fp[1:], c, fk, None, fk_entries[fk]()
899
yield fp[1:], c, fk, None, TreeEntry()
902
if fk != 'directory':
905
# But do this child first if recursing down
907
new_children = sorted(os.listdir(fap))
908
new_children = collections.deque(new_children)
909
stack.append((f_ie.file_id, fp, fap, new_children))
910
# Break out of inner loop,
911
# so that we start outer loop with child
914
# if we finished all children, pop it off the stack
917
@needs_tree_write_lock
918
def move(self, from_paths, to_dir=None, after=False):
921
to_dir must exist in the inventory.
923
If to_dir exists and is a directory, the files are moved into
924
it, keeping their old names.
926
Note that to_dir is only the last component of the new name;
927
this doesn't change the directory.
929
For each entry in from_paths the move mode will be determined
932
The first mode moves the file in the filesystem and updates the
933
inventory. The second mode only updates the inventory without
934
touching the file on the filesystem.
936
move uses the second mode if 'after == True' and the target is
937
either not versioned or newly added, and present in the working tree.
939
move uses the second mode if 'after == False' and the source is
940
versioned but no longer in the working tree, and the target is not
941
versioned but present in the working tree.
943
move uses the first mode if 'after == False' and the source is
944
versioned and present in the working tree, and the target is not
945
versioned and not present in the working tree.
947
Everything else results in an error.
949
This returns a list of (from_path, to_path) pairs for each
955
invs_to_write = set()
957
# check for deprecated use of signature
959
raise TypeError('You must supply a target directory')
960
# check destination directory
961
if isinstance(from_paths, basestring):
963
to_abs = self.abspath(to_dir)
964
if not osutils.isdir(to_abs):
965
raise errors.BzrMoveFailedError('',to_dir,
966
errors.NotADirectory(to_abs))
967
if not self.has_filename(to_dir):
968
raise errors.BzrMoveFailedError('',to_dir,
969
errors.NotInWorkingDirectory(to_dir))
970
to_inv, to_dir_id = self._path2inv_file_id(to_dir)
971
if to_dir_id is None:
972
raise errors.BzrMoveFailedError('',to_dir,
973
errors.NotVersionedError(path=to_dir))
975
to_dir_ie = to_inv[to_dir_id]
976
if to_dir_ie.kind != 'directory':
977
raise errors.BzrMoveFailedError('',to_dir,
978
errors.NotADirectory(to_abs))
980
# create rename entries and tuples
981
for from_rel in from_paths:
982
from_tail = osutils.splitpath(from_rel)[-1]
983
from_inv, from_id = self._path2inv_file_id(from_rel)
985
raise errors.BzrMoveFailedError(from_rel,to_dir,
986
errors.NotVersionedError(path=from_rel))
988
from_entry = from_inv[from_id]
989
from_parent_id = from_entry.parent_id
990
to_rel = osutils.pathjoin(to_dir, from_tail)
991
rename_entry = InventoryWorkingTree._RenameEntry(
995
from_parent_id=from_parent_id,
996
to_rel=to_rel, to_tail=from_tail,
997
to_parent_id=to_dir_id)
998
rename_entries.append(rename_entry)
999
rename_tuples.append((from_rel, to_rel))
1001
# determine which move mode to use. checks also for movability
1002
rename_entries = self._determine_mv_mode(rename_entries, after)
1004
original_modified = self._inventory_is_modified
1007
self._inventory_is_modified = True
1008
self._move(rename_entries)
1010
# restore the inventory on error
1011
self._inventory_is_modified = original_modified
1013
#FIXME: Should potentially also write the from_invs
1014
self._write_inventory(to_inv)
1015
return rename_tuples
1017
@needs_tree_write_lock
1018
def rename_one(self, from_rel, to_rel, after=False):
1021
This can change the directory or the filename or both.
1023
rename_one has several 'modes' to work. First, it can rename a physical
1024
file and change the file_id. That is the normal mode. Second, it can
1025
only change the file_id without touching any physical file.
1027
rename_one uses the second mode if 'after == True' and 'to_rel' is not
1028
versioned but present in the working tree.
1030
rename_one uses the second mode if 'after == False' and 'from_rel' is
1031
versioned but no longer in the working tree, and 'to_rel' is not
1032
versioned but present in the working tree.
1034
rename_one uses the first mode if 'after == False' and 'from_rel' is
1035
versioned and present in the working tree, and 'to_rel' is not
1036
versioned and not present in the working tree.
1038
Everything else results in an error.
1042
# create rename entries and tuples
1043
from_tail = osutils.splitpath(from_rel)[-1]
1044
from_inv, from_id = self._path2inv_file_id(from_rel)
1046
# if file is missing in the inventory maybe it's in the basis_tree
1047
basis_tree = self.branch.basis_tree()
1048
from_id = basis_tree.path2id(from_rel)
1050
raise errors.BzrRenameFailedError(from_rel,to_rel,
1051
errors.NotVersionedError(path=from_rel))
1052
# put entry back in the inventory so we can rename it
1053
from_entry = basis_tree.root_inventory[from_id].copy()
1054
from_inv.add(from_entry)
1056
from_inv, from_inv_id = self._unpack_file_id(from_id)
1057
from_entry = from_inv[from_inv_id]
1058
from_parent_id = from_entry.parent_id
1059
to_dir, to_tail = os.path.split(to_rel)
1060
to_inv, to_dir_id = self._path2inv_file_id(to_dir)
1061
rename_entry = InventoryWorkingTree._RenameEntry(from_rel=from_rel,
1063
from_tail=from_tail,
1064
from_parent_id=from_parent_id,
1065
to_rel=to_rel, to_tail=to_tail,
1066
to_parent_id=to_dir_id)
1067
rename_entries.append(rename_entry)
1069
# determine which move mode to use. checks also for movability
1070
rename_entries = self._determine_mv_mode(rename_entries, after)
1072
# check if the target changed directory and if the target directory is
1074
if to_dir_id is None:
1075
raise errors.BzrMoveFailedError(from_rel,to_rel,
1076
errors.NotVersionedError(path=to_dir))
1078
# all checks done. now we can continue with our actual work
1079
mutter('rename_one:\n'
1084
' to_dir_id {%s}\n',
1085
from_id, from_rel, to_rel, to_dir, to_dir_id)
1087
self._move(rename_entries)
1088
self._write_inventory(to_inv)
1090
class _RenameEntry(object):
1091
def __init__(self, from_rel, from_id, from_tail, from_parent_id,
1092
to_rel, to_tail, to_parent_id, only_change_inv=False,
1094
self.from_rel = from_rel
1095
self.from_id = from_id
1096
self.from_tail = from_tail
1097
self.from_parent_id = from_parent_id
1098
self.to_rel = to_rel
1099
self.to_tail = to_tail
1100
self.to_parent_id = to_parent_id
1101
self.change_id = change_id
1102
self.only_change_inv = only_change_inv
1104
def _determine_mv_mode(self, rename_entries, after=False):
1105
"""Determines for each from-to pair if both inventory and working tree
1106
or only the inventory has to be changed.
1108
Also does basic plausability tests.
1110
# FIXME: Handling of nested trees
1111
inv = self.root_inventory
1113
for rename_entry in rename_entries:
1114
# store to local variables for easier reference
1115
from_rel = rename_entry.from_rel
1116
from_id = rename_entry.from_id
1117
to_rel = rename_entry.to_rel
1118
to_id = inv.path2id(to_rel)
1119
only_change_inv = False
1122
# check the inventory for source and destination
1124
raise errors.BzrMoveFailedError(from_rel,to_rel,
1125
errors.NotVersionedError(path=from_rel))
1126
if to_id is not None:
1128
# allow it with --after but only if dest is newly added
1130
basis = self.basis_tree()
1133
if not basis.has_id(to_id):
1134
rename_entry.change_id = True
1139
raise errors.BzrMoveFailedError(from_rel,to_rel,
1140
errors.AlreadyVersionedError(path=to_rel))
1142
# try to determine the mode for rename (only change inv or change
1143
# inv and file system)
1145
if not self.has_filename(to_rel):
1146
raise errors.BzrMoveFailedError(from_id,to_rel,
1147
errors.NoSuchFile(path=to_rel,
1148
extra="New file has not been created yet"))
1149
only_change_inv = True
1150
elif not self.has_filename(from_rel) and self.has_filename(to_rel):
1151
only_change_inv = True
1152
elif self.has_filename(from_rel) and not self.has_filename(to_rel):
1153
only_change_inv = False
1154
elif (not self.case_sensitive
1155
and from_rel.lower() == to_rel.lower()
1156
and self.has_filename(from_rel)):
1157
only_change_inv = False
1159
# something is wrong, so lets determine what exactly
1160
if not self.has_filename(from_rel) and \
1161
not self.has_filename(to_rel):
1162
raise errors.BzrRenameFailedError(from_rel, to_rel,
1163
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
1165
raise errors.RenameFailedFilesExist(from_rel, to_rel)
1166
rename_entry.only_change_inv = only_change_inv
1167
return rename_entries
1169
def _move(self, rename_entries):
1170
"""Moves a list of files.
1172
Depending on the value of the flag 'only_change_inv', the
1173
file will be moved on the file system or not.
1177
for entry in rename_entries:
1179
self._move_entry(entry)
1181
self._rollback_move(moved)
1185
def _rollback_move(self, moved):
1186
"""Try to rollback a previous move in case of an filesystem error."""
1189
self._move_entry(WorkingTree._RenameEntry(
1190
entry.to_rel, entry.from_id,
1191
entry.to_tail, entry.to_parent_id, entry.from_rel,
1192
entry.from_tail, entry.from_parent_id,
1193
entry.only_change_inv))
1194
except errors.BzrMoveFailedError as e:
1195
raise errors.BzrMoveFailedError( '', '', "Rollback failed."
1196
" The working tree is in an inconsistent state."
1197
" Please consider doing a 'bzr revert'."
1198
" Error message is: %s" % e)
1200
def _move_entry(self, entry):
1201
inv = self.root_inventory
1202
from_rel_abs = self.abspath(entry.from_rel)
1203
to_rel_abs = self.abspath(entry.to_rel)
1204
if from_rel_abs == to_rel_abs:
1205
raise errors.BzrMoveFailedError(entry.from_rel, entry.to_rel,
1206
"Source and target are identical.")
1208
if not entry.only_change_inv:
1210
osutils.rename(from_rel_abs, to_rel_abs)
1211
except OSError as e:
1212
raise errors.BzrMoveFailedError(entry.from_rel,
1215
to_id = inv.path2id(entry.to_rel)
1216
inv.remove_recursive_id(to_id)
1217
inv.rename(entry.from_id, entry.to_parent_id, entry.to_tail)
1219
@needs_tree_write_lock
1220
def unversion(self, file_ids):
1221
"""Remove the file ids in file_ids from the current versioned set.
1223
When a file_id is unversioned, all of its children are automatically
1226
:param file_ids: The file ids to stop versioning.
1227
:raises: NoSuchId if any fileid is not currently versioned.
1229
for file_id in file_ids:
1230
if not self._inventory.has_id(file_id):
1231
raise errors.NoSuchId(self, file_id)
1232
for file_id in file_ids:
1233
if self._inventory.has_id(file_id):
1234
self._inventory.remove_recursive_id(file_id)
1236
# in the future this should just set a dirty bit to wait for the
1237
# final unlock. However, until all methods of workingtree start
1238
# with the current in -memory inventory rather than triggering
1239
# a read, it is more complex - we need to teach read_inventory
1240
# to know when to read, and when to not read first... and possibly
1241
# to save first when the in memory one may be corrupted.
1242
# so for now, we just only write it if it is indeed dirty.
1244
self._write_inventory(self._inventory)
1246
def stored_kind(self, file_id):
1247
"""See Tree.stored_kind"""
1248
inv, inv_file_id = self._unpack_file_id(file_id)
1249
return inv[inv_file_id].kind
1252
"""Yield all unversioned files in this WorkingTree.
1254
If there are any unversioned directories then only the directory is
1255
returned, not all its children. But if there are unversioned files
1256
under a versioned subdirectory, they are returned.
1258
Currently returned depth-first, sorted by name within directories.
1259
This is the same order used by 'osutils.walkdirs'.
1261
## TODO: Work from given directory downwards
1262
for path, dir_entry in self.iter_entries_by_dir():
1263
if dir_entry.kind != 'directory':
1265
# mutter("search for unknowns in %r", path)
1266
dirabs = self.abspath(path)
1267
if not osutils.isdir(dirabs):
1268
# e.g. directory deleted
1272
for subf in os.listdir(dirabs):
1273
if self.controldir.is_control_filename(subf):
1275
if subf not in dir_entry.children:
1278
can_access) = osutils.normalized_filename(subf)
1279
except UnicodeDecodeError:
1280
path_os_enc = path.encode(osutils._fs_enc)
1281
relpath = path_os_enc + '/' + subf
1282
raise errors.BadFilenameEncoding(relpath,
1284
if subf_norm != subf and can_access:
1285
if subf_norm not in dir_entry.children:
1286
fl.append(subf_norm)
1292
subp = osutils.pathjoin(path, subf)
1295
def _walkdirs(self, prefix=""):
1296
"""Walk the directories of this tree.
1298
:param prefix: is used as the directrory to start with.
1299
:returns: a generator which yields items in the form::
1301
((curren_directory_path, fileid),
1302
[(file1_path, file1_name, file1_kind, None, file1_id,
1305
_directory = 'directory'
1306
# get the root in the inventory
1307
inv, top_id = self._path2inv_file_id(prefix)
1311
pending = [(prefix, '', _directory, None, top_id, None)]
1314
currentdir = pending.pop()
1315
# 0 - relpath, 1- basename, 2- kind, 3- stat, 4-id, 5-kind
1316
top_id = currentdir[4]
1318
relroot = currentdir[0] + '/'
1321
# FIXME: stash the node in pending
1323
if entry.kind == 'directory':
1324
for name, child in entry.sorted_children():
1325
dirblock.append((relroot + name, name, child.kind, None,
1326
child.file_id, child.kind
1328
yield (currentdir[0], entry.file_id), dirblock
1329
# push the user specified dirs from dirblock
1330
for dir in reversed(dirblock):
1331
if dir[2] == _directory:
1335
def update_feature_flags(self, updated_flags):
1336
"""Update the feature flags for this branch.
1338
:param updated_flags: Dictionary mapping feature names to necessities
1339
A necessity can be None to indicate the feature should be removed
1341
self._format._update_feature_flags(updated_flags)
1342
self.control_transport.put_bytes('format', self._format.as_string())
1344
def _check_for_tree_references(self, iterator):
1345
"""See if directories have become tree-references."""
1346
blocked_parent_ids = set()
1347
for path, ie in iterator:
1348
if ie.parent_id in blocked_parent_ids:
1349
# This entry was pruned because one of its parents became a
1350
# TreeReference. If this is a directory, mark it as blocked.
1351
if ie.kind == 'directory':
1352
blocked_parent_ids.add(ie.file_id)
1354
if ie.kind == 'directory' and self._directory_is_tree_reference(path):
1355
# This InventoryDirectory needs to be a TreeReference
1356
ie = inventory.TreeReference(ie.file_id, ie.name, ie.parent_id)
1357
blocked_parent_ids.add(ie.file_id)
1360
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1361
"""See Tree.iter_entries_by_dir()"""
1362
# The only trick here is that if we supports_tree_reference then we
1363
# need to detect if a directory becomes a tree-reference.
1364
iterator = super(WorkingTree, self).iter_entries_by_dir(
1365
specific_file_ids=specific_file_ids,
1366
yield_parents=yield_parents)
1367
if not self.supports_tree_reference():
1370
return self._check_for_tree_references(iterator)
1373
class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
1374
"""Base class for working trees that live in bzr meta directories."""
1377
WorkingTreeFormat.__init__(self)
1378
bzrdir.BzrFormat.__init__(self)
1381
def find_format_string(klass, controldir):
1382
"""Return format name for the working tree object in controldir."""
1384
transport = controldir.get_workingtree_transport(None)
1385
return transport.get_bytes("format")
1386
except errors.NoSuchFile:
1387
raise errors.NoWorkingTree(base=transport.base)
1390
def find_format(klass, controldir):
1391
"""Return the format for the working tree object in controldir."""
1392
format_string = klass.find_format_string(controldir)
1393
return klass._find_format(format_registry, 'working tree',
1396
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
1398
WorkingTreeFormat.check_support_status(self,
1399
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
1401
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
1402
recommend_upgrade=recommend_upgrade, basedir=basedir)
1404
def get_controldir_for_branch(self):
1405
"""Get the control directory format for creating branches.
1407
This is to support testing of working tree formats that can not exist
1408
in the same control directory as a branch.
1410
return self._matchingbzrdir
1413
class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
1414
"""Base class for working trees that live in bzr meta directories."""
1417
WorkingTreeFormat.__init__(self)
1418
bzrdir.BzrFormat.__init__(self)
1421
def find_format_string(klass, controldir):
1422
"""Return format name for the working tree object in controldir."""
1424
transport = controldir.get_workingtree_transport(None)
1425
return transport.get_bytes("format")
1426
except errors.NoSuchFile:
1427
raise errors.NoWorkingTree(base=transport.base)
1430
def find_format(klass, controldir):
1431
"""Return the format for the working tree object in controldir."""
1432
format_string = klass.find_format_string(controldir)
1433
return klass._find_format(format_registry, 'working tree',
1436
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
1438
WorkingTreeFormat.check_support_status(self,
1439
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
1441
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
1442
recommend_upgrade=recommend_upgrade, basedir=basedir)