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).
33
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,
60
from breezy.bzr import (
67
from ..decorators import needs_write_lock, needs_read_lock
68
from ..lock import _RelockDebugMixin, LogicalLockResult
69
from ..mutabletree import needs_tree_write_lock
70
from .inventorytree import InventoryRevisionTree, MutableInventoryTree
71
from ..sixish import (
75
from ..trace import mutter
76
from ..tree import FileTimestampUnavailable
77
from ..workingtree import (
87
MERGE_MODIFIED_HEADER_1 = b"BZR merge-modified list format 1"
88
# TODO: Modifying the conflict objects or their type is currently nearly
89
# impossible as there is no clear relationship between the working tree format
90
# and the conflict list file format.
91
CONFLICT_HEADER_1 = b"BZR conflict list format 1"
94
class InventoryWorkingTree(WorkingTree,MutableInventoryTree):
95
"""Base class for working trees that are inventory-oriented.
97
The inventory is held in the `Branch` working-inventory, and the
98
files are in a directory on disk.
100
It is possible for a `WorkingTree` to have a filename which is
101
not listed in the Inventory and vice versa.
104
def __init__(self, basedir='.',
111
"""Construct a InventoryWorkingTree instance. This is not a public API.
113
:param branch: A branch to override probing for the branch.
115
super(InventoryWorkingTree, self).__init__(basedir=basedir,
116
branch=branch, _transport=_control_files._transport,
117
_internal=_internal, _format=_format, _controldir=_controldir)
119
self._control_files = _control_files
120
self._detect_case_handling()
122
if _inventory is None:
123
# This will be acquired on lock_read() or lock_write()
124
self._inventory_is_modified = False
125
self._inventory = None
127
# the caller of __init__ has provided an inventory,
128
# we assume they know what they are doing - as its only
129
# the Format factory and creation methods that are
130
# permitted to do this.
131
self._set_inventory(_inventory, dirty=False)
133
def _set_inventory(self, inv, dirty):
134
"""Set the internal cached inventory.
136
:param inv: The inventory to set.
137
:param dirty: A boolean indicating whether the inventory is the same
138
logical inventory as whats on disk. If True the inventory is not
139
the same and should be written to disk or data will be lost, if
140
False then the inventory is the same as that on disk and any
141
serialisation would be unneeded overhead.
143
self._inventory = inv
144
self._inventory_is_modified = dirty
146
def _detect_case_handling(self):
147
wt_trans = self.controldir.get_workingtree_transport(None)
149
wt_trans.stat(self._format.case_sensitive_filename)
150
except errors.NoSuchFile:
151
self.case_sensitive = True
153
self.case_sensitive = False
155
self._setup_directory_is_tree_reference()
157
def _serialize(self, inventory, out_file):
158
xml5.serializer_v5.write_inventory(self._inventory, out_file,
161
def _deserialize(selt, in_file):
162
return xml5.serializer_v5.read_inventory(in_file)
164
def break_lock(self):
165
"""Break a lock if one is present from another instance.
167
Uses the ui factory to ask for confirmation if the lock may be from
170
This will probe the repository for its lock as well.
172
self._control_files.break_lock()
173
self.branch.break_lock()
176
return self._control_files.is_locked()
178
def _must_be_locked(self):
179
if not self.is_locked():
180
raise errors.ObjectNotLocked(self)
183
"""Lock the tree for reading.
185
This also locks the branch, and can be unlocked via self.unlock().
187
:return: A breezy.lock.LogicalLockResult.
189
if not self.is_locked():
191
self.branch.lock_read()
193
self._control_files.lock_read()
194
return LogicalLockResult(self.unlock)
199
def lock_tree_write(self):
200
"""See MutableTree.lock_tree_write, and WorkingTree.unlock.
202
:return: A breezy.lock.LogicalLockResult.
204
if not self.is_locked():
206
self.branch.lock_read()
208
self._control_files.lock_write()
209
return LogicalLockResult(self.unlock)
214
def lock_write(self):
215
"""See MutableTree.lock_write, and WorkingTree.unlock.
217
:return: A breezy.lock.LogicalLockResult.
219
if not self.is_locked():
221
self.branch.lock_write()
223
self._control_files.lock_write()
224
return LogicalLockResult(self.unlock)
229
def get_physical_lock_status(self):
230
return self._control_files.get_physical_lock_status()
232
@needs_tree_write_lock
233
def _write_inventory(self, inv):
234
"""Write inventory as the current inventory."""
235
self._set_inventory(inv, dirty=True)
238
# XXX: This method should be deprecated in favour of taking in a proper
239
# new Inventory object.
240
@needs_tree_write_lock
241
def set_inventory(self, new_inventory_list):
242
from .inventory import (
247
inv = Inventory(self.get_root_id())
248
for path, file_id, parent, kind in new_inventory_list:
249
name = os.path.basename(path)
252
# fixme, there should be a factory function inv,add_??
253
if kind == 'directory':
254
inv.add(InventoryDirectory(file_id, name, parent))
256
inv.add(InventoryFile(file_id, name, parent))
257
elif kind == 'symlink':
258
inv.add(InventoryLink(file_id, name, parent))
260
raise errors.BzrError("unknown kind %r" % kind)
261
self._write_inventory(inv)
263
def _write_basis_inventory(self, xml):
264
"""Write the basis inventory XML to the basis-inventory file"""
265
path = self._basis_inventory_name()
267
self._transport.put_file(path, sio,
268
mode=self.controldir._get_file_mode())
270
def _reset_data(self):
271
"""Reset transient data that cannot be revalidated."""
272
self._inventory_is_modified = False
273
f = self._transport.get('inventory')
275
result = self._deserialize(f)
278
self._set_inventory(result, dirty=False)
280
def _set_root_id(self, file_id):
281
"""Set the root id for this tree, in a format specific manner.
283
:param file_id: The file id to assign to the root. It must not be
284
present in the current inventory or an error will occur. It must
285
not be None, but rather a valid file id.
287
inv = self._inventory
288
orig_root_id = inv.root.file_id
289
# TODO: it might be nice to exit early if there was nothing
290
# to do, saving us from trigger a sync on unlock.
291
self._inventory_is_modified = True
292
# we preserve the root inventory entry object, but
293
# unlinkit from the byid index
294
del inv._byid[inv.root.file_id]
295
inv.root.file_id = file_id
296
# and link it into the index with the new changed id.
297
inv._byid[inv.root.file_id] = inv.root
298
# and finally update all children to reference the new id.
299
# XXX: this should be safe to just look at the root.children
300
# list, not the WHOLE INVENTORY.
303
if entry.parent_id == orig_root_id:
304
entry.parent_id = inv.root.file_id
306
@needs_tree_write_lock
307
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
308
"""See MutableTree.set_parent_trees."""
309
parent_ids = [rev for (rev, tree) in parents_list]
310
for revision_id in parent_ids:
311
_mod_revision.check_not_reserved_id(revision_id)
313
self._check_parents_for_ghosts(parent_ids,
314
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
316
parent_ids = self._filter_parent_ids_by_ancestry(parent_ids)
318
if len(parent_ids) == 0:
319
leftmost_parent_id = _mod_revision.NULL_REVISION
320
leftmost_parent_tree = None
322
leftmost_parent_id, leftmost_parent_tree = parents_list[0]
324
if self._change_last_revision(leftmost_parent_id):
325
if leftmost_parent_tree is None:
326
# If we don't have a tree, fall back to reading the
327
# parent tree from the repository.
328
self._cache_basis_inventory(leftmost_parent_id)
330
inv = leftmost_parent_tree.root_inventory
331
xml = self._create_basis_xml_from_inventory(
332
leftmost_parent_id, inv)
333
self._write_basis_inventory(xml)
334
self._set_merges_from_parent_ids(parent_ids)
336
def _cache_basis_inventory(self, new_revision):
337
"""Cache new_revision as the basis inventory."""
338
# TODO: this should allow the ready-to-use inventory to be passed in,
339
# as commit already has that ready-to-use [while the format is the
342
# this double handles the inventory - unpack and repack -
343
# but is easier to understand. We can/should put a conditional
344
# in here based on whether the inventory is in the latest format
345
# - perhaps we should repack all inventories on a repository
347
# the fast path is to copy the raw xml from the repository. If the
348
# xml contains 'revision_id="', then we assume the right
349
# revision_id is set. We must check for this full string, because a
350
# root node id can legitimately look like 'revision_id' but cannot
352
xml = self.branch.repository._get_inventory_xml(new_revision)
353
firstline = xml.split('\n', 1)[0]
354
if (not 'revision_id="' in firstline or
355
'format="7"' not in firstline):
356
inv = self.branch.repository._serializer.read_inventory_from_string(
358
xml = self._create_basis_xml_from_inventory(new_revision, inv)
359
self._write_basis_inventory(xml)
360
except (errors.NoSuchRevision, errors.RevisionNotPresent):
363
def _basis_inventory_name(self):
364
return 'basis-inventory-cache'
366
def _create_basis_xml_from_inventory(self, revision_id, inventory):
367
"""Create the text that will be saved in basis-inventory"""
368
inventory.revision_id = revision_id
369
return xml7.serializer_v7.write_inventory_to_string(inventory)
371
@needs_tree_write_lock
372
def set_conflicts(self, conflicts):
373
self._put_rio('conflicts', conflicts.to_stanzas(),
376
@needs_tree_write_lock
377
def add_conflicts(self, new_conflicts):
378
conflict_set = set(self.conflicts())
379
conflict_set.update(set(list(new_conflicts)))
380
self.set_conflicts(_mod_conflicts.ConflictList(sorted(conflict_set,
381
key=_mod_conflicts.Conflict.sort_key)))
386
confile = self._transport.get('conflicts')
387
except errors.NoSuchFile:
388
return _mod_conflicts.ConflictList()
391
if next(confile) != CONFLICT_HEADER_1 + b'\n':
392
raise errors.ConflictFormatError()
393
except StopIteration:
394
raise errors.ConflictFormatError()
395
reader = _mod_rio.RioReader(confile)
396
return _mod_conflicts.ConflictList.from_stanzas(reader)
400
def get_ignore_list(self):
401
"""Return list of ignore patterns.
403
Cached in the Tree object after the first call.
405
ignoreset = getattr(self, '_ignoreset', None)
406
if ignoreset is not None:
410
ignore_globs.update(ignores.get_runtime_ignores())
411
ignore_globs.update(ignores.get_user_ignores())
412
if self.has_filename(breezy.IGNORE_FILENAME):
413
f = self.get_file_byname(breezy.IGNORE_FILENAME)
415
ignore_globs.update(ignores.parse_ignore_file(f))
418
self._ignoreset = ignore_globs
422
self._flush_ignore_list_cache()
424
def _flush_ignore_list_cache(self):
425
"""Resets the cached ignore list to force a cache rebuild."""
426
self._ignoreset = None
427
self._ignoreglobster = None
429
def is_ignored(self, filename):
430
r"""Check whether the filename matches an ignore pattern.
432
Patterns containing '/' or '\' need to match the whole path;
433
others match against only the last component. Patterns starting
434
with '!' are ignore exceptions. Exceptions take precedence
435
over regular patterns and cause the filename to not be ignored.
437
If the file is ignored, returns the pattern which caused it to
438
be ignored, otherwise None. So this can simply be used as a
439
boolean if desired."""
440
if getattr(self, '_ignoreglobster', None) is None:
441
self._ignoreglobster = globbing.ExceptionGlobster(self.get_ignore_list())
442
return self._ignoreglobster.match(filename)
444
def read_basis_inventory(self):
445
"""Read the cached basis inventory."""
446
path = self._basis_inventory_name()
447
return self._transport.get_bytes(path)
450
def read_working_inventory(self):
451
"""Read the working inventory.
453
:raises errors.InventoryModified: read_working_inventory will fail
454
when the current in memory inventory has been modified.
456
# conceptually this should be an implementation detail of the tree.
457
# XXX: Deprecate this.
458
# ElementTree does its own conversion from UTF-8, so open in
460
if self._inventory_is_modified:
461
raise errors.InventoryModified(self)
462
f = self._transport.get('inventory')
464
result = self._deserialize(f)
467
self._set_inventory(result, dirty=False)
471
def get_root_id(self):
472
"""Return the id of this trees root"""
473
return self._inventory.root.file_id
475
def has_id(self, file_id):
476
# files that have been deleted are excluded
477
inv, inv_file_id = self._unpack_file_id(file_id)
478
if not inv.has_id(inv_file_id):
480
path = inv.id2path(inv_file_id)
481
return osutils.lexists(self.abspath(path))
483
def has_or_had_id(self, file_id):
484
if file_id == self.get_root_id():
486
inv, inv_file_id = self._unpack_file_id(file_id)
487
return inv.has_id(inv_file_id)
489
def all_file_ids(self):
490
"""Iterate through file_ids for this tree.
492
file_ids are in a WorkingTree if they are in the working inventory
493
and the working file exists.
496
for path, ie in self.iter_entries_by_dir():
500
@needs_tree_write_lock
501
def set_last_revision(self, new_revision):
502
"""Change the last revision in the working tree."""
503
if self._change_last_revision(new_revision):
504
self._cache_basis_inventory(new_revision)
506
def _get_check_refs(self):
507
"""Return the references needed to perform a check of this tree.
509
The default implementation returns no refs, and is only suitable for
510
trees that have no local caching and can commit on ghosts at any time.
512
:seealso: breezy.check for details about check_refs.
517
def _check(self, references):
518
"""Check the tree for consistency.
520
:param references: A dict with keys matching the items returned by
521
self._get_check_refs(), and values from looking those keys up in
524
tree_basis = self.basis_tree()
525
tree_basis.lock_read()
527
repo_basis = references[('trees', self.last_revision())]
528
if len(list(repo_basis.iter_changes(tree_basis))) > 0:
529
raise errors.BzrCheckError(
530
"Mismatched basis inventory content.")
536
def check_state(self):
537
"""Check that the working state is/isn't valid."""
538
check_refs = self._get_check_refs()
540
for ref in check_refs:
543
refs[ref] = self.branch.repository.revision_tree(value)
546
@needs_tree_write_lock
547
def reset_state(self, revision_ids=None):
548
"""Reset the state of the working tree.
550
This does a hard-reset to a last-known-good state. This is a way to
551
fix if something got corrupted (like the .bzr/checkout/dirstate file)
553
if revision_ids is None:
554
revision_ids = self.get_parent_ids()
556
rt = self.branch.repository.revision_tree(
557
_mod_revision.NULL_REVISION)
559
rt = self.branch.repository.revision_tree(revision_ids[0])
560
self._write_inventory(rt.root_inventory)
561
self.set_parent_ids(revision_ids)
564
"""Write the in memory inventory to disk."""
565
# TODO: Maybe this should only write on dirty ?
566
if self._control_files._lock_mode != 'w':
567
raise errors.NotWriteLocked(self)
569
self._serialize(self._inventory, sio)
571
self._transport.put_file('inventory', sio,
572
mode=self.controldir._get_file_mode())
573
self._inventory_is_modified = False
575
def get_file_mtime(self, file_id, path=None):
576
"""See Tree.get_file_mtime."""
578
path = self.id2path(file_id)
580
return os.lstat(self.abspath(path)).st_mtime
582
if e.errno == errno.ENOENT:
583
raise FileTimestampUnavailable(path)
586
def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
587
inv, file_id = self._path2inv_file_id(path)
589
# For unversioned files on win32, we just assume they are not
592
return inv[file_id].executable
594
def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
595
mode = stat_result.st_mode
596
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
598
def is_executable(self, file_id, path=None):
599
if not self._supports_executable():
600
inv, inv_file_id = self._unpack_file_id(file_id)
601
return inv[inv_file_id].executable
604
path = self.id2path(file_id)
605
mode = os.lstat(self.abspath(path)).st_mode
606
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
608
def _is_executable_from_path_and_stat(self, path, stat_result):
609
if not self._supports_executable():
610
return self._is_executable_from_path_and_stat_from_basis(path, stat_result)
612
return self._is_executable_from_path_and_stat_from_stat(path, stat_result)
614
@needs_tree_write_lock
615
def _add(self, files, ids, kinds):
616
"""See MutableTree._add."""
617
# TODO: Re-adding a file that is removed in the working copy
618
# should probably put it back with the previous ID.
619
# the read and write working inventory should not occur in this
620
# function - they should be part of lock_write and unlock.
621
# FIXME: nested trees
622
inv = self.root_inventory
623
for f, file_id, kind in zip(files, ids, kinds):
625
inv.add_path(f, kind=kind)
627
inv.add_path(f, kind=kind, file_id=file_id)
628
self._inventory_is_modified = True
630
def revision_tree(self, revision_id):
631
"""See WorkingTree.revision_id."""
632
if revision_id == self.last_revision():
634
xml = self.read_basis_inventory()
635
except errors.NoSuchFile:
639
inv = xml7.serializer_v7.read_inventory_from_string(xml)
640
# dont use the repository revision_tree api because we want
641
# to supply the inventory.
642
if inv.revision_id == revision_id:
643
return InventoryRevisionTree(
644
self.branch.repository, inv, revision_id)
645
except errors.BadInventoryFormat:
647
# raise if there was no inventory, or if we read the wrong inventory.
648
raise errors.NoSuchRevisionInTree(self, revision_id)
651
def annotate_iter(self, file_id,
652
default_revision=_mod_revision.CURRENT_REVISION):
653
"""See Tree.annotate_iter
655
This implementation will use the basis tree implementation if possible.
656
Lines not in the basis are attributed to CURRENT_REVISION
658
If there are pending merges, lines added by those merges will be
659
incorrectly attributed to CURRENT_REVISION (but after committing, the
660
attribution will be correct).
662
maybe_file_parent_keys = []
663
for parent_id in self.get_parent_ids():
665
parent_tree = self.revision_tree(parent_id)
666
except errors.NoSuchRevisionInTree:
667
parent_tree = self.branch.repository.revision_tree(parent_id)
668
parent_tree.lock_read()
671
kind = parent_tree.kind(file_id)
672
except errors.NoSuchId:
675
# Note: this is slightly unnecessary, because symlinks and
676
# directories have a "text" which is the empty text, and we
677
# know that won't mess up annotations. But it seems cleaner
680
file_id, parent_tree.get_file_revision(file_id))
681
if parent_text_key not in maybe_file_parent_keys:
682
maybe_file_parent_keys.append(parent_text_key)
685
graph = _mod_graph.Graph(self.branch.repository.texts)
686
heads = graph.heads(maybe_file_parent_keys)
687
file_parent_keys = []
688
for key in maybe_file_parent_keys:
690
file_parent_keys.append(key)
692
# Now we have the parents of this content
693
annotator = self.branch.repository.texts.get_annotator()
694
text = self.get_file_text(file_id)
695
this_key =(file_id, default_revision)
696
annotator.add_special_text(this_key, file_parent_keys, text)
697
annotations = [(key[-1], line)
698
for key, line in annotator.annotate_flat(this_key)]
701
def _put_rio(self, filename, stanzas, header):
702
self._must_be_locked()
703
my_file = _mod_rio.rio_file(stanzas, header)
704
self._transport.put_file(filename, my_file,
705
mode=self.controldir._get_file_mode())
707
@needs_tree_write_lock
708
def set_merge_modified(self, modified_hashes):
710
for file_id in modified_hashes:
711
yield _mod_rio.Stanza(file_id=file_id.decode('utf8'),
712
hash=modified_hashes[file_id])
713
self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
716
def merge_modified(self):
717
"""Return a dictionary of files modified by a merge.
719
The list is initialized by WorkingTree.set_merge_modified, which is
720
typically called after we make some automatic updates to the tree
723
This returns a map of file_id->sha1, containing only files which are
724
still in the working inventory and have that text hash.
727
hashfile = self._transport.get('merge-hashes')
728
except errors.NoSuchFile:
733
if next(hashfile) != MERGE_MODIFIED_HEADER_1 + b'\n':
734
raise errors.MergeModifiedFormatError()
735
except StopIteration:
736
raise errors.MergeModifiedFormatError()
737
for s in _mod_rio.RioReader(hashfile):
738
# RioReader reads in Unicode, so convert file_ids back to utf8
739
file_id = cache_utf8.encode(s.get("file_id"))
740
if not self.has_id(file_id):
742
text_hash = s.get("hash")
743
if text_hash == self.get_file_sha1(file_id):
744
merge_hashes[file_id] = text_hash
750
def subsume(self, other_tree):
751
def add_children(inventory, entry):
752
for child_entry in entry.children.values():
753
inventory._byid[child_entry.file_id] = child_entry
754
if child_entry.kind == 'directory':
755
add_children(inventory, child_entry)
756
if other_tree.get_root_id() == self.get_root_id():
757
raise errors.BadSubsumeSource(self, other_tree,
758
'Trees have the same root')
760
other_tree_path = self.relpath(other_tree.basedir)
761
except errors.PathNotChild:
762
raise errors.BadSubsumeSource(self, other_tree,
763
'Tree is not contained by the other')
764
new_root_parent = self.path2id(osutils.dirname(other_tree_path))
765
if new_root_parent is None:
766
raise errors.BadSubsumeSource(self, other_tree,
767
'Parent directory is not versioned.')
768
# We need to ensure that the result of a fetch will have a
769
# versionedfile for the other_tree root, and only fetching into
770
# RepositoryKnit2 guarantees that.
771
if not self.branch.repository.supports_rich_root():
772
raise errors.SubsumeTargetNeedsUpgrade(other_tree)
773
other_tree.lock_tree_write()
775
new_parents = other_tree.get_parent_ids()
776
other_root = other_tree.root_inventory.root
777
other_root.parent_id = new_root_parent
778
other_root.name = osutils.basename(other_tree_path)
779
self.root_inventory.add(other_root)
780
add_children(self.root_inventory, other_root)
781
self._write_inventory(self.root_inventory)
782
# normally we don't want to fetch whole repositories, but i think
783
# here we really do want to consolidate the whole thing.
784
for parent_id in other_tree.get_parent_ids():
785
self.branch.fetch(other_tree.branch, parent_id)
786
self.add_parent_tree_id(parent_id)
789
other_tree.controldir.retire_bzrdir()
791
@needs_tree_write_lock
792
def extract(self, file_id, format=None):
793
"""Extract a subtree from this tree.
795
A new branch will be created, relative to the path for this tree.
799
segments = osutils.splitpath(path)
800
transport = self.branch.controldir.root_transport
801
for name in segments:
802
transport = transport.clone(name)
803
transport.ensure_base()
806
sub_path = self.id2path(file_id)
807
branch_transport = mkdirs(sub_path)
809
format = self.controldir.cloning_metadir()
810
branch_transport.ensure_base()
811
branch_bzrdir = format.initialize_on_transport(branch_transport)
813
repo = branch_bzrdir.find_repository()
814
except errors.NoRepositoryPresent:
815
repo = branch_bzrdir.create_repository()
816
if not repo.supports_rich_root():
817
raise errors.RootNotRich()
818
new_branch = branch_bzrdir.create_branch()
819
new_branch.pull(self.branch)
820
for parent_id in self.get_parent_ids():
821
new_branch.fetch(self.branch, parent_id)
822
tree_transport = self.controldir.root_transport.clone(sub_path)
823
if tree_transport.base != branch_transport.base:
824
tree_bzrdir = format.initialize_on_transport(tree_transport)
825
tree_bzrdir.set_branch_reference(new_branch)
827
tree_bzrdir = branch_bzrdir
828
wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION)
829
wt.set_parent_ids(self.get_parent_ids())
830
# FIXME: Support nested trees
831
my_inv = self.root_inventory
832
child_inv = inventory.Inventory(root_id=None)
833
new_root = my_inv[file_id]
834
my_inv.remove_recursive_id(file_id)
835
new_root.parent_id = None
836
child_inv.add(new_root)
837
self._write_inventory(my_inv)
838
wt._write_inventory(child_inv)
841
def list_files(self, include_root=False, from_dir=None, recursive=True):
842
"""List all files as (path, class, kind, id, entry).
844
Lists, but does not descend into unversioned directories.
845
This does not include files that have been deleted in this
846
tree. Skips the control directory.
848
:param include_root: if True, return an entry for the root
849
:param from_dir: start from this directory or None for the root
850
:param recursive: whether to recurse into subdirectories or not
852
# list_files is an iterator, so @needs_read_lock doesn't work properly
853
# with it. So callers should be careful to always read_lock the tree.
854
if not self.is_locked():
855
raise errors.ObjectNotLocked(self)
857
if from_dir is None and include_root is True:
858
yield ('', 'V', 'directory', self.get_root_id(), self.root_inventory.root)
859
# Convert these into local objects to save lookup times
860
pathjoin = osutils.pathjoin
861
file_kind = self._kind
863
# transport.base ends in a slash, we want the piece
864
# between the last two slashes
865
transport_base_dir = self.controldir.transport.base.rsplit('/', 2)[1]
867
fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
869
# directory file_id, relative path, absolute path, reverse sorted children
870
if from_dir is not None:
871
inv, from_dir_id = self._path2inv_file_id(from_dir)
872
if from_dir_id is None:
873
# Directory not versioned
875
from_dir_abspath = pathjoin(self.basedir, from_dir)
877
inv = self.root_inventory
878
from_dir_id = inv.root.file_id
879
from_dir_abspath = self.basedir
880
children = sorted(os.listdir(from_dir_abspath))
881
# jam 20060527 The kernel sized tree seems equivalent whether we
882
# use a deque and popleft to keep them sorted, or if we use a plain
883
# list and just reverse() them.
884
children = collections.deque(children)
885
stack = [(from_dir_id, u'', from_dir_abspath, children)]
887
from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
890
f = children.popleft()
891
## TODO: If we find a subdirectory with its own .bzr
892
## directory, then that is a separate tree and we
893
## should exclude it.
895
# the bzrdir for this tree
896
if transport_base_dir == f:
899
# we know that from_dir_relpath and from_dir_abspath never end in a slash
900
# and 'f' doesn't begin with one, we can do a string op, rather
901
# than the checks of pathjoin(), all relative paths will have an extra slash
903
fp = from_dir_relpath + '/' + f
906
fap = from_dir_abspath + '/' + f
908
dir_ie = inv[from_dir_id]
909
if dir_ie.kind == 'directory':
910
f_ie = dir_ie.children.get(f)
915
elif self.is_ignored(fp[1:]):
918
# we may not have found this file, because of a unicode
919
# issue, or because the directory was actually a symlink.
920
f_norm, can_access = osutils.normalized_filename(f)
921
if f == f_norm or not can_access:
922
# No change, so treat this file normally
925
# this file can be accessed by a normalized path
926
# check again if it is versioned
927
# these lines are repeated here for performance
929
fp = from_dir_relpath + '/' + f
930
fap = from_dir_abspath + '/' + f
931
f_ie = inv.get_child(from_dir_id, f)
934
elif self.is_ignored(fp[1:]):
939
fk = osutils.file_kind(fap)
941
# make a last minute entry
943
yield fp[1:], c, fk, f_ie.file_id, f_ie
946
yield fp[1:], c, fk, None, fk_entries[fk]()
948
yield fp[1:], c, fk, None, TreeEntry()
951
if fk != 'directory':
954
# But do this child first if recursing down
956
new_children = sorted(os.listdir(fap))
957
new_children = collections.deque(new_children)
958
stack.append((f_ie.file_id, fp, fap, new_children))
959
# Break out of inner loop,
960
# so that we start outer loop with child
963
# if we finished all children, pop it off the stack
966
@needs_tree_write_lock
967
def move(self, from_paths, to_dir=None, after=False):
970
to_dir must exist in the inventory.
972
If to_dir exists and is a directory, the files are moved into
973
it, keeping their old names.
975
Note that to_dir is only the last component of the new name;
976
this doesn't change the directory.
978
For each entry in from_paths the move mode will be determined
981
The first mode moves the file in the filesystem and updates the
982
inventory. The second mode only updates the inventory without
983
touching the file on the filesystem.
985
move uses the second mode if 'after == True' and the target is
986
either not versioned or newly added, and present in the working tree.
988
move uses the second mode if 'after == False' and the source is
989
versioned but no longer in the working tree, and the target is not
990
versioned but present in the working tree.
992
move uses the first mode if 'after == False' and the source is
993
versioned and present in the working tree, and the target is not
994
versioned and not present in the working tree.
996
Everything else results in an error.
998
This returns a list of (from_path, to_path) pairs for each
1004
invs_to_write = set()
1006
# check for deprecated use of signature
1008
raise TypeError('You must supply a target directory')
1009
# check destination directory
1010
if isinstance(from_paths, (str, text_type)):
1012
to_abs = self.abspath(to_dir)
1013
if not osutils.isdir(to_abs):
1014
raise errors.BzrMoveFailedError('',to_dir,
1015
errors.NotADirectory(to_abs))
1016
if not self.has_filename(to_dir):
1017
raise errors.BzrMoveFailedError('',to_dir,
1018
errors.NotInWorkingDirectory(to_dir))
1019
to_inv, to_dir_id = self._path2inv_file_id(to_dir)
1020
if to_dir_id is None:
1021
raise errors.BzrMoveFailedError('',to_dir,
1022
errors.NotVersionedError(path=to_dir))
1024
to_dir_ie = to_inv[to_dir_id]
1025
if to_dir_ie.kind != 'directory':
1026
raise errors.BzrMoveFailedError('',to_dir,
1027
errors.NotADirectory(to_abs))
1029
# create rename entries and tuples
1030
for from_rel in from_paths:
1031
from_tail = osutils.splitpath(from_rel)[-1]
1032
from_inv, from_id = self._path2inv_file_id(from_rel)
1034
raise errors.BzrMoveFailedError(from_rel,to_dir,
1035
errors.NotVersionedError(path=from_rel))
1037
from_entry = from_inv[from_id]
1038
from_parent_id = from_entry.parent_id
1039
to_rel = osutils.pathjoin(to_dir, from_tail)
1040
rename_entry = InventoryWorkingTree._RenameEntry(
1043
from_tail=from_tail,
1044
from_parent_id=from_parent_id,
1045
to_rel=to_rel, to_tail=from_tail,
1046
to_parent_id=to_dir_id)
1047
rename_entries.append(rename_entry)
1048
rename_tuples.append((from_rel, to_rel))
1050
# determine which move mode to use. checks also for movability
1051
rename_entries = self._determine_mv_mode(rename_entries, after)
1053
original_modified = self._inventory_is_modified
1056
self._inventory_is_modified = True
1057
self._move(rename_entries)
1059
# restore the inventory on error
1060
self._inventory_is_modified = original_modified
1062
#FIXME: Should potentially also write the from_invs
1063
self._write_inventory(to_inv)
1064
return rename_tuples
1066
@needs_tree_write_lock
1067
def rename_one(self, from_rel, to_rel, after=False):
1070
This can change the directory or the filename or both.
1072
rename_one has several 'modes' to work. First, it can rename a physical
1073
file and change the file_id. That is the normal mode. Second, it can
1074
only change the file_id without touching any physical file.
1076
rename_one uses the second mode if 'after == True' and 'to_rel' is not
1077
versioned but present in the working tree.
1079
rename_one uses the second mode if 'after == False' and 'from_rel' is
1080
versioned but no longer in the working tree, and 'to_rel' is not
1081
versioned but present in the working tree.
1083
rename_one uses the first mode if 'after == False' and 'from_rel' is
1084
versioned and present in the working tree, and 'to_rel' is not
1085
versioned and not present in the working tree.
1087
Everything else results in an error.
1091
# create rename entries and tuples
1092
from_tail = osutils.splitpath(from_rel)[-1]
1093
from_inv, from_id = self._path2inv_file_id(from_rel)
1095
# if file is missing in the inventory maybe it's in the basis_tree
1096
basis_tree = self.branch.basis_tree()
1097
from_id = basis_tree.path2id(from_rel)
1099
raise errors.BzrRenameFailedError(from_rel,to_rel,
1100
errors.NotVersionedError(path=from_rel))
1101
# put entry back in the inventory so we can rename it
1102
from_entry = basis_tree.root_inventory[from_id].copy()
1103
from_inv.add(from_entry)
1105
from_inv, from_inv_id = self._unpack_file_id(from_id)
1106
from_entry = from_inv[from_inv_id]
1107
from_parent_id = from_entry.parent_id
1108
to_dir, to_tail = os.path.split(to_rel)
1109
to_inv, to_dir_id = self._path2inv_file_id(to_dir)
1110
rename_entry = InventoryWorkingTree._RenameEntry(from_rel=from_rel,
1112
from_tail=from_tail,
1113
from_parent_id=from_parent_id,
1114
to_rel=to_rel, to_tail=to_tail,
1115
to_parent_id=to_dir_id)
1116
rename_entries.append(rename_entry)
1118
# determine which move mode to use. checks also for movability
1119
rename_entries = self._determine_mv_mode(rename_entries, after)
1121
# check if the target changed directory and if the target directory is
1123
if to_dir_id is None:
1124
raise errors.BzrMoveFailedError(from_rel,to_rel,
1125
errors.NotVersionedError(path=to_dir))
1127
# all checks done. now we can continue with our actual work
1128
mutter('rename_one:\n'
1133
' to_dir_id {%s}\n',
1134
from_id, from_rel, to_rel, to_dir, to_dir_id)
1136
self._move(rename_entries)
1137
self._write_inventory(to_inv)
1139
class _RenameEntry(object):
1140
def __init__(self, from_rel, from_id, from_tail, from_parent_id,
1141
to_rel, to_tail, to_parent_id, only_change_inv=False,
1143
self.from_rel = from_rel
1144
self.from_id = from_id
1145
self.from_tail = from_tail
1146
self.from_parent_id = from_parent_id
1147
self.to_rel = to_rel
1148
self.to_tail = to_tail
1149
self.to_parent_id = to_parent_id
1150
self.change_id = change_id
1151
self.only_change_inv = only_change_inv
1153
def _determine_mv_mode(self, rename_entries, after=False):
1154
"""Determines for each from-to pair if both inventory and working tree
1155
or only the inventory has to be changed.
1157
Also does basic plausability tests.
1159
# FIXME: Handling of nested trees
1160
inv = self.root_inventory
1162
for rename_entry in rename_entries:
1163
# store to local variables for easier reference
1164
from_rel = rename_entry.from_rel
1165
from_id = rename_entry.from_id
1166
to_rel = rename_entry.to_rel
1167
to_id = inv.path2id(to_rel)
1168
only_change_inv = False
1171
# check the inventory for source and destination
1173
raise errors.BzrMoveFailedError(from_rel,to_rel,
1174
errors.NotVersionedError(path=from_rel))
1175
if to_id is not None:
1177
# allow it with --after but only if dest is newly added
1179
basis = self.basis_tree()
1182
if not basis.has_id(to_id):
1183
rename_entry.change_id = True
1188
raise errors.BzrMoveFailedError(from_rel,to_rel,
1189
errors.AlreadyVersionedError(path=to_rel))
1191
# try to determine the mode for rename (only change inv or change
1192
# inv and file system)
1194
if not self.has_filename(to_rel):
1195
raise errors.BzrMoveFailedError(from_id,to_rel,
1196
errors.NoSuchFile(path=to_rel,
1197
extra="New file has not been created yet"))
1198
only_change_inv = True
1199
elif not self.has_filename(from_rel) and self.has_filename(to_rel):
1200
only_change_inv = True
1201
elif self.has_filename(from_rel) and not self.has_filename(to_rel):
1202
only_change_inv = False
1203
elif (not self.case_sensitive
1204
and from_rel.lower() == to_rel.lower()
1205
and self.has_filename(from_rel)):
1206
only_change_inv = False
1208
# something is wrong, so lets determine what exactly
1209
if not self.has_filename(from_rel) and \
1210
not self.has_filename(to_rel):
1211
raise errors.BzrRenameFailedError(from_rel, to_rel,
1212
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
1214
raise errors.RenameFailedFilesExist(from_rel, to_rel)
1215
rename_entry.only_change_inv = only_change_inv
1216
return rename_entries
1218
def _move(self, rename_entries):
1219
"""Moves a list of files.
1221
Depending on the value of the flag 'only_change_inv', the
1222
file will be moved on the file system or not.
1226
for entry in rename_entries:
1228
self._move_entry(entry)
1230
self._rollback_move(moved)
1234
def _rollback_move(self, moved):
1235
"""Try to rollback a previous move in case of an filesystem error."""
1238
self._move_entry(WorkingTree._RenameEntry(
1239
entry.to_rel, entry.from_id,
1240
entry.to_tail, entry.to_parent_id, entry.from_rel,
1241
entry.from_tail, entry.from_parent_id,
1242
entry.only_change_inv))
1243
except errors.BzrMoveFailedError as e:
1244
raise errors.BzrMoveFailedError( '', '', "Rollback failed."
1245
" The working tree is in an inconsistent state."
1246
" Please consider doing a 'bzr revert'."
1247
" Error message is: %s" % e)
1249
def _move_entry(self, entry):
1250
inv = self.root_inventory
1251
from_rel_abs = self.abspath(entry.from_rel)
1252
to_rel_abs = self.abspath(entry.to_rel)
1253
if from_rel_abs == to_rel_abs:
1254
raise errors.BzrMoveFailedError(entry.from_rel, entry.to_rel,
1255
"Source and target are identical.")
1257
if not entry.only_change_inv:
1259
osutils.rename(from_rel_abs, to_rel_abs)
1260
except OSError as e:
1261
raise errors.BzrMoveFailedError(entry.from_rel,
1264
to_id = inv.path2id(entry.to_rel)
1265
inv.remove_recursive_id(to_id)
1266
inv.rename(entry.from_id, entry.to_parent_id, entry.to_tail)
1268
@needs_tree_write_lock
1269
def unversion(self, file_ids):
1270
"""Remove the file ids in file_ids from the current versioned set.
1272
When a file_id is unversioned, all of its children are automatically
1275
:param file_ids: The file ids to stop versioning.
1276
:raises: NoSuchId if any fileid is not currently versioned.
1278
for file_id in file_ids:
1279
if not self._inventory.has_id(file_id):
1280
raise errors.NoSuchId(self, file_id)
1281
for file_id in file_ids:
1282
if self._inventory.has_id(file_id):
1283
self._inventory.remove_recursive_id(file_id)
1285
# in the future this should just set a dirty bit to wait for the
1286
# final unlock. However, until all methods of workingtree start
1287
# with the current in -memory inventory rather than triggering
1288
# a read, it is more complex - we need to teach read_inventory
1289
# to know when to read, and when to not read first... and possibly
1290
# to save first when the in memory one may be corrupted.
1291
# so for now, we just only write it if it is indeed dirty.
1293
self._write_inventory(self._inventory)
1295
def stored_kind(self, file_id):
1296
"""See Tree.stored_kind"""
1297
inv, inv_file_id = self._unpack_file_id(file_id)
1298
return inv[inv_file_id].kind
1301
"""Yield all unversioned files in this WorkingTree.
1303
If there are any unversioned directories then only the directory is
1304
returned, not all its children. But if there are unversioned files
1305
under a versioned subdirectory, they are returned.
1307
Currently returned depth-first, sorted by name within directories.
1308
This is the same order used by 'osutils.walkdirs'.
1310
## TODO: Work from given directory downwards
1311
for path, dir_entry in self.iter_entries_by_dir():
1312
if dir_entry.kind != 'directory':
1314
# mutter("search for unknowns in %r", path)
1315
dirabs = self.abspath(path)
1316
if not osutils.isdir(dirabs):
1317
# e.g. directory deleted
1321
for subf in os.listdir(dirabs):
1322
if self.controldir.is_control_filename(subf):
1324
if subf not in dir_entry.children:
1327
can_access) = osutils.normalized_filename(subf)
1328
except UnicodeDecodeError:
1329
path_os_enc = path.encode(osutils._fs_enc)
1330
relpath = path_os_enc + '/' + subf
1331
raise errors.BadFilenameEncoding(relpath,
1333
if subf_norm != subf and can_access:
1334
if subf_norm not in dir_entry.children:
1335
fl.append(subf_norm)
1341
subp = osutils.pathjoin(path, subf)
1344
def _walkdirs(self, prefix=""):
1345
"""Walk the directories of this tree.
1347
:param prefix: is used as the directrory to start with.
1348
:returns: a generator which yields items in the form::
1350
((curren_directory_path, fileid),
1351
[(file1_path, file1_name, file1_kind, None, file1_id,
1354
_directory = 'directory'
1355
# get the root in the inventory
1356
inv, top_id = self._path2inv_file_id(prefix)
1360
pending = [(prefix, '', _directory, None, top_id, None)]
1363
currentdir = pending.pop()
1364
# 0 - relpath, 1- basename, 2- kind, 3- stat, 4-id, 5-kind
1365
top_id = currentdir[4]
1367
relroot = currentdir[0] + '/'
1370
# FIXME: stash the node in pending
1372
if entry.kind == 'directory':
1373
for name, child in entry.sorted_children():
1374
dirblock.append((relroot + name, name, child.kind, None,
1375
child.file_id, child.kind
1377
yield (currentdir[0], entry.file_id), dirblock
1378
# push the user specified dirs from dirblock
1379
for dir in reversed(dirblock):
1380
if dir[2] == _directory:
1384
def update_feature_flags(self, updated_flags):
1385
"""Update the feature flags for this branch.
1387
:param updated_flags: Dictionary mapping feature names to necessities
1388
A necessity can be None to indicate the feature should be removed
1390
self._format._update_feature_flags(updated_flags)
1391
self.control_transport.put_bytes('format', self._format.as_string())
1393
def _check_for_tree_references(self, iterator):
1394
"""See if directories have become tree-references."""
1395
blocked_parent_ids = set()
1396
for path, ie in iterator:
1397
if ie.parent_id in blocked_parent_ids:
1398
# This entry was pruned because one of its parents became a
1399
# TreeReference. If this is a directory, mark it as blocked.
1400
if ie.kind == 'directory':
1401
blocked_parent_ids.add(ie.file_id)
1403
if ie.kind == 'directory' and self._directory_is_tree_reference(path):
1404
# This InventoryDirectory needs to be a TreeReference
1405
ie = inventory.TreeReference(ie.file_id, ie.name, ie.parent_id)
1406
blocked_parent_ids.add(ie.file_id)
1409
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1410
"""See Tree.iter_entries_by_dir()"""
1411
# The only trick here is that if we supports_tree_reference then we
1412
# need to detect if a directory becomes a tree-reference.
1413
iterator = super(WorkingTree, self).iter_entries_by_dir(
1414
specific_file_ids=specific_file_ids,
1415
yield_parents=yield_parents)
1416
if not self.supports_tree_reference():
1419
return self._check_for_tree_references(iterator)
1422
class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
1423
"""Base class for working trees that live in bzr meta directories."""
1426
WorkingTreeFormat.__init__(self)
1427
bzrdir.BzrFormat.__init__(self)
1430
def find_format_string(klass, controldir):
1431
"""Return format name for the working tree object in controldir."""
1433
transport = controldir.get_workingtree_transport(None)
1434
return transport.get_bytes("format")
1435
except errors.NoSuchFile:
1436
raise errors.NoWorkingTree(base=transport.base)
1439
def find_format(klass, controldir):
1440
"""Return the format for the working tree object in controldir."""
1441
format_string = klass.find_format_string(controldir)
1442
return klass._find_format(format_registry, 'working tree',
1445
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
1447
WorkingTreeFormat.check_support_status(self,
1448
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
1450
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
1451
recommend_upgrade=recommend_upgrade, basedir=basedir)
1453
def get_controldir_for_branch(self):
1454
"""Get the control directory format for creating branches.
1456
This is to support testing of working tree formats that can not exist
1457
in the same control directory as a branch.
1459
return self._matchingcontroldir
1462
class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
1463
"""Base class for working trees that live in bzr meta directories."""
1466
WorkingTreeFormat.__init__(self)
1467
bzrdir.BzrFormat.__init__(self)
1470
def find_format_string(klass, controldir):
1471
"""Return format name for the working tree object in controldir."""
1473
transport = controldir.get_workingtree_transport(None)
1474
# GZ 2017-06-09: When do decode format strings?
1475
return transport.get_bytes("format").decode('ascii')
1476
except errors.NoSuchFile:
1477
raise errors.NoWorkingTree(base=transport.base)
1480
def find_format(klass, controldir):
1481
"""Return the format for the working tree object in controldir."""
1482
format_string = klass.find_format_string(controldir)
1483
return klass._find_format(format_registry, 'working tree',
1486
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
1488
WorkingTreeFormat.check_support_status(self,
1489
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
1491
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
1492
recommend_upgrade=recommend_upgrade, basedir=basedir)