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
35
from bisect import bisect_left
38
from collections.abc import deque
39
except ImportError: # python < 3.7
40
from collections import deque
48
# Explicitly import breezy.bzrdir so that the BzrProber
49
# is guaranteed to be registered.
52
from .. import lazy_import
53
lazy_import.lazy_import(globals(), """
57
conflicts as _mod_conflicts,
60
revision as _mod_revision,
63
from breezy.bzr import (
74
from ..lock import LogicalLockResult
75
from .inventorytree import InventoryRevisionTree, MutableInventoryTree
76
from ..sixish import (
80
from ..trace import mutter, note
89
from ..workingtree import (
96
MERGE_MODIFIED_HEADER_1 = b"BZR merge-modified list format 1"
97
# TODO: Modifying the conflict objects or their type is currently nearly
98
# impossible as there is no clear relationship between the working tree format
99
# and the conflict list file format.
100
CONFLICT_HEADER_1 = b"BZR conflict list format 1"
101
ERROR_PATH_NOT_FOUND = 3 # WindowsError errno code, equivalent to ENOENT
104
class InventoryWorkingTree(WorkingTree, MutableInventoryTree):
105
"""Base class for working trees that are inventory-oriented.
107
The inventory is held in the `Branch` working-inventory, and the
108
files are in a directory on disk.
110
It is possible for a `WorkingTree` to have a filename which is
111
not listed in the Inventory and vice versa.
114
def __init__(self, basedir='.',
121
"""Construct a InventoryWorkingTree instance. This is not a public API.
123
:param branch: A branch to override probing for the branch.
125
super(InventoryWorkingTree, self).__init__(
126
basedir=basedir, branch=branch,
127
_transport=_control_files._transport, _internal=_internal,
128
_format=_format, _controldir=_controldir)
130
self._control_files = _control_files
131
self._detect_case_handling()
132
self._setup_directory_is_tree_reference()
134
if _inventory is None:
135
# This will be acquired on lock_read() or lock_write()
136
self._inventory_is_modified = False
137
self._inventory = None
139
# the caller of __init__ has provided an inventory,
140
# we assume they know what they are doing - as its only
141
# the Format factory and creation methods that are
142
# permitted to do this.
143
self._set_inventory(_inventory, dirty=False)
145
def _set_inventory(self, inv, dirty):
146
"""Set the internal cached inventory.
148
:param inv: The inventory to set.
149
:param dirty: A boolean indicating whether the inventory is the same
150
logical inventory as whats on disk. If True the inventory is not
151
the same and should be written to disk or data will be lost, if
152
False then the inventory is the same as that on disk and any
153
serialisation would be unneeded overhead.
155
self._inventory = inv
156
self._inventory_is_modified = dirty
158
def _detect_case_handling(self):
159
wt_trans = self.controldir.get_workingtree_transport(None)
161
wt_trans.stat(self._format.case_sensitive_filename)
162
except errors.NoSuchFile:
163
self.case_sensitive = True
165
self.case_sensitive = False
167
def get_transform(self, pb=None):
168
from ..transform import TreeTransform
169
return TreeTransform(self, pb=pb)
171
def _setup_directory_is_tree_reference(self):
172
if self._branch.repository._format.supports_tree_reference:
173
self._directory_is_tree_reference = \
174
self._directory_may_be_tree_reference
176
self._directory_is_tree_reference = \
177
self._directory_is_never_tree_reference
179
def _directory_is_never_tree_reference(self, relpath):
182
def _directory_may_be_tree_reference(self, relpath):
183
# as a special case, if a directory contains control files then
184
# it's a tree reference, except that the root of the tree is not
185
return relpath and osutils.isdir(self.abspath(relpath) + u"/.bzr")
186
# TODO: We could ask all the control formats whether they
187
# recognize this directory, but at the moment there's no cheap api
188
# to do that. Since we probably can only nest bzr checkouts and
189
# they always use this name it's ok for now. -- mbp 20060306
191
# FIXME: There is an unhandled case here of a subdirectory
192
# containing .bzr but not a branch; that will probably blow up
193
# when you try to commit it. It might happen if there is a
194
# checkout in a subdirectory. This can be avoided by not adding
197
def _serialize(self, inventory, out_file):
198
xml5.serializer_v5.write_inventory(
199
self._inventory, out_file, working=True)
201
def _deserialize(selt, in_file):
202
return xml5.serializer_v5.read_inventory(in_file)
204
def break_lock(self):
205
"""Break a lock if one is present from another instance.
207
Uses the ui factory to ask for confirmation if the lock may be from
210
This will probe the repository for its lock as well.
212
self._control_files.break_lock()
213
self.branch.break_lock()
216
return self._control_files.is_locked()
218
def _must_be_locked(self):
219
if not self.is_locked():
220
raise errors.ObjectNotLocked(self)
223
"""Lock the tree for reading.
225
This also locks the branch, and can be unlocked via self.unlock().
227
:return: A breezy.lock.LogicalLockResult.
229
if not self.is_locked():
231
self.branch.lock_read()
233
self._control_files.lock_read()
234
return LogicalLockResult(self.unlock)
235
except BaseException:
239
def lock_tree_write(self):
240
"""See MutableTree.lock_tree_write, and WorkingTree.unlock.
242
:return: A breezy.lock.LogicalLockResult.
244
if not self.is_locked():
246
self.branch.lock_read()
248
self._control_files.lock_write()
249
return LogicalLockResult(self.unlock)
250
except BaseException:
254
def lock_write(self):
255
"""See MutableTree.lock_write, and WorkingTree.unlock.
257
:return: A breezy.lock.LogicalLockResult.
259
if not self.is_locked():
261
self.branch.lock_write()
263
self._control_files.lock_write()
264
return LogicalLockResult(self.unlock)
265
except BaseException:
269
def get_physical_lock_status(self):
270
return self._control_files.get_physical_lock_status()
272
def _write_inventory(self, inv):
273
"""Write inventory as the current inventory."""
274
with self.lock_tree_write():
275
self._set_inventory(inv, dirty=True)
278
# XXX: This method should be deprecated in favour of taking in a proper
279
# new Inventory object.
280
def set_inventory(self, new_inventory_list):
281
from .inventory import (
286
with self.lock_tree_write():
287
inv = Inventory(self.path2id(''))
288
for path, file_id, parent, kind in new_inventory_list:
289
name = os.path.basename(path)
292
# fixme, there should be a factory function inv,add_??
293
if kind == 'directory':
294
inv.add(InventoryDirectory(file_id, name, parent))
296
inv.add(InventoryFile(file_id, name, parent))
297
elif kind == 'symlink':
298
inv.add(InventoryLink(file_id, name, parent))
300
raise errors.BzrError("unknown kind %r" % kind)
301
self._write_inventory(inv)
303
def _write_basis_inventory(self, xml):
304
"""Write the basis inventory XML to the basis-inventory file"""
305
path = self._basis_inventory_name()
306
sio = BytesIO(b''.join(xml))
307
self._transport.put_file(path, sio,
308
mode=self.controldir._get_file_mode())
310
def _reset_data(self):
311
"""Reset transient data that cannot be revalidated."""
312
self._inventory_is_modified = False
313
with self._transport.get('inventory') as f:
314
result = self._deserialize(f)
315
self._set_inventory(result, dirty=False)
317
def store_uncommitted(self):
318
"""Store uncommitted changes from the tree in the branch."""
319
with self.lock_write():
320
target_tree = self.basis_tree()
321
from ..shelf import ShelfCreator
322
shelf_creator = ShelfCreator(self, target_tree)
324
if not shelf_creator.shelve_all():
326
self.branch.store_uncommitted(shelf_creator)
327
shelf_creator.transform()
329
shelf_creator.finalize()
330
note('Uncommitted changes stored in branch "%s".',
333
def restore_uncommitted(self):
334
"""Restore uncommitted changes from the branch into the tree."""
335
with self.lock_write():
336
unshelver = self.branch.get_unshelver(self)
337
if unshelver is None:
340
merger = unshelver.make_merger()
341
merger.ignore_zero = True
343
self.branch.store_uncommitted(None)
347
def get_shelf_manager(self):
348
"""Return the ShelfManager for this WorkingTree."""
349
from ..shelf import ShelfManager
350
return ShelfManager(self, self._transport)
352
def _set_root_id(self, file_id):
353
"""Set the root id for this tree, in a format specific manner.
355
:param file_id: The file id to assign to the root. It must not be
356
present in the current inventory or an error will occur. It must
357
not be None, but rather a valid file id.
359
inv = self._inventory
360
orig_root_id = inv.root.file_id
361
# TODO: it might be nice to exit early if there was nothing
362
# to do, saving us from trigger a sync on unlock.
363
self._inventory_is_modified = True
364
# we preserve the root inventory entry object, but
365
# unlinkit from the byid index
366
inv.delete(inv.root.file_id)
367
inv.root.file_id = file_id
368
# and link it into the index with the new changed id.
369
inv._byid[inv.root.file_id] = inv.root
370
# and finally update all children to reference the new id.
371
# XXX: this should be safe to just look at the root.children
372
# list, not the WHOLE INVENTORY.
373
for fid in inv.iter_all_ids():
374
entry = inv.get_entry(fid)
375
if entry.parent_id == orig_root_id:
376
entry.parent_id = inv.root.file_id
378
def remove(self, files, verbose=False, to_file=None, keep_files=True,
380
"""Remove nominated files from the working tree metadata.
382
:files: File paths relative to the basedir.
383
:keep_files: If true, the files will also be kept.
384
:force: Delete files and directories, even if they are changed and
385
even if the directories are not empty.
387
if isinstance(files, (str, text_type)):
392
all_files = set() # specified and nested files
398
def recurse_directory_to_add_files(directory):
399
# Recurse directory and add all files
400
# so we can check if they have changed.
401
for parent_info, file_infos in self.walkdirs(directory):
402
for relpath, basename, kind, lstat, fileid, kind in file_infos:
403
# Is it versioned or ignored?
404
if self.is_versioned(relpath):
405
# Add nested content for deletion.
406
all_files.add(relpath)
408
# Files which are not versioned
409
# should be treated as unknown.
410
files_to_backup.append(relpath)
412
with self.lock_tree_write():
414
for filename in files:
415
# Get file name into canonical form.
416
abspath = self.abspath(filename)
417
filename = self.relpath(abspath)
418
if len(filename) > 0:
419
all_files.add(filename)
420
recurse_directory_to_add_files(filename)
422
files = list(all_files)
425
return # nothing to do
427
# Sort needed to first handle directory content before the
429
files.sort(reverse=True)
431
# Bail out if we are going to delete files we shouldn't
432
if not keep_files and not force:
433
for change in self.iter_changes(
434
self.basis_tree(), include_unchanged=True,
435
require_versioned=False, want_unversioned=True,
436
specific_files=files):
437
if change.versioned[0] is False:
438
# The record is unknown or newly added
439
files_to_backup.append(change.path[1])
440
elif (change.changed_content and (change.kind[1] is not None)
441
and osutils.is_inside_any(files, change.path[1])):
442
# Versioned and changed, but not deleted, and still
443
# in one of the dirs to be deleted.
444
files_to_backup.append(change.path[1])
446
def backup(file_to_backup):
447
backup_name = self.controldir._available_backup_name(
449
osutils.rename(abs_path, self.abspath(backup_name))
450
return "removed %s (but kept a copy: %s)" % (file_to_backup,
453
# Build inv_delta and delete files where applicable,
454
# do this before any modifications to meta data.
456
fid = self.path2id(f)
459
message = "%s is not versioned." % (f,)
462
# having removed it, it must be either ignored or
464
if self.is_ignored(f):
468
# XXX: Really should be a more abstract reporter
470
kind_ch = osutils.kind_marker(self.kind(f))
472
new_status + ' ' + f + kind_ch + '\n')
474
inv_delta.append((f, None, fid, None))
475
message = "removed %s" % (f,)
478
abs_path = self.abspath(f)
479
if osutils.lexists(abs_path):
480
if (osutils.isdir(abs_path)
481
and len(os.listdir(abs_path)) > 0):
483
osutils.rmtree(abs_path)
484
message = "deleted %s" % (f,)
488
if f in files_to_backup:
491
osutils.delete_any(abs_path)
492
message = "deleted %s" % (f,)
493
elif message is not None:
494
# Only care if we haven't done anything yet.
495
message = "%s does not exist." % (f,)
497
# Print only one message (if any) per file.
498
if message is not None:
500
self.apply_inventory_delta(inv_delta)
502
def get_nested_tree(self, path):
503
return WorkingTree.open(self.abspath(path))
505
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
506
"""See MutableTree.set_parent_trees."""
507
parent_ids = [rev for (rev, tree) in parents_list]
508
for revision_id in parent_ids:
509
_mod_revision.check_not_reserved_id(revision_id)
511
with self.lock_tree_write():
512
self._check_parents_for_ghosts(parent_ids,
513
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
515
parent_ids = self._filter_parent_ids_by_ancestry(parent_ids)
517
if len(parent_ids) == 0:
518
leftmost_parent_id = _mod_revision.NULL_REVISION
519
leftmost_parent_tree = None
521
leftmost_parent_id, leftmost_parent_tree = parents_list[0]
523
if self._change_last_revision(leftmost_parent_id):
524
if leftmost_parent_tree is None:
525
# If we don't have a tree, fall back to reading the
526
# parent tree from the repository.
527
self._cache_basis_inventory(leftmost_parent_id)
529
inv = leftmost_parent_tree.root_inventory
530
xml = self._create_basis_xml_from_inventory(
531
leftmost_parent_id, inv)
532
self._write_basis_inventory(xml)
533
self._set_merges_from_parent_ids(parent_ids)
535
def _cache_basis_inventory(self, new_revision):
536
"""Cache new_revision as the basis inventory."""
537
# TODO: this should allow the ready-to-use inventory to be passed in,
538
# as commit already has that ready-to-use [while the format is the
541
# this double handles the inventory - unpack and repack -
542
# but is easier to understand. We can/should put a conditional
543
# in here based on whether the inventory is in the latest format
544
# - perhaps we should repack all inventories on a repository
546
# the fast path is to copy the raw xml from the repository. If the
547
# xml contains 'revision_id="', then we assume the right
548
# revision_id is set. We must check for this full string, because a
549
# root node id can legitimately look like 'revision_id' but cannot
551
lines = self.branch.repository._get_inventory_xml(new_revision)
553
if (b'revision_id="' not in firstline
554
or b'format="7"' not in firstline):
555
inv = self.branch.repository._serializer.read_inventory_from_lines(
557
lines = self._create_basis_xml_from_inventory(new_revision, inv)
558
self._write_basis_inventory(lines)
559
except (errors.NoSuchRevision, errors.RevisionNotPresent):
562
def _basis_inventory_name(self):
563
return 'basis-inventory-cache'
565
def _create_basis_xml_from_inventory(self, revision_id, inventory):
566
"""Create the text that will be saved in basis-inventory"""
567
inventory.revision_id = revision_id
568
return xml7.serializer_v7.write_inventory_to_lines(inventory)
570
def set_conflicts(self, conflicts):
571
with self.lock_tree_write():
572
self._put_rio('conflicts', conflicts.to_stanzas(),
575
def add_conflicts(self, new_conflicts):
576
with self.lock_tree_write():
577
conflict_set = set(self.conflicts())
578
conflict_set.update(set(list(new_conflicts)))
579
self.set_conflicts(_mod_conflicts.ConflictList(
580
sorted(conflict_set, key=_mod_conflicts.Conflict.sort_key)))
583
with self.lock_read():
585
confile = self._transport.get('conflicts')
586
except errors.NoSuchFile:
587
return _mod_conflicts.ConflictList()
590
if next(confile) != CONFLICT_HEADER_1 + b'\n':
591
raise errors.ConflictFormatError()
592
except StopIteration:
593
raise errors.ConflictFormatError()
594
reader = _mod_rio.RioReader(confile)
595
return _mod_conflicts.ConflictList.from_stanzas(reader)
599
def get_ignore_list(self):
600
"""Return list of ignore patterns.
602
Cached in the Tree object after the first call.
604
ignoreset = getattr(self, '_ignoreset', None)
605
if ignoreset is not None:
609
ignore_globs.update(ignores.get_runtime_ignores())
610
ignore_globs.update(ignores.get_user_ignores())
611
if self.has_filename(self._format.ignore_filename):
612
with self.get_file(self._format.ignore_filename) as f:
613
ignore_globs.update(ignores.parse_ignore_file(f))
614
self._ignoreset = ignore_globs
618
self._flush_ignore_list_cache()
620
def _flush_ignore_list_cache(self):
621
"""Resets the cached ignore list to force a cache rebuild."""
622
self._ignoreset = None
623
self._ignoreglobster = None
625
def is_ignored(self, filename):
626
r"""Check whether the filename matches an ignore pattern.
628
Patterns containing '/' or '\' need to match the whole path;
629
others match against only the last component. Patterns starting
630
with '!' are ignore exceptions. Exceptions take precedence
631
over regular patterns and cause the filename to not be ignored.
633
If the file is ignored, returns the pattern which caused it to
634
be ignored, otherwise None. So this can simply be used as a
635
boolean if desired."""
636
if getattr(self, '_ignoreglobster', None) is None:
637
self._ignoreglobster = globbing.ExceptionGlobster(
638
self.get_ignore_list())
639
return self._ignoreglobster.match(filename)
641
def read_basis_inventory(self):
642
"""Read the cached basis inventory."""
643
path = self._basis_inventory_name()
644
return osutils.split_lines(self._transport.get_bytes(path))
646
def read_working_inventory(self):
647
"""Read the working inventory.
649
:raises errors.InventoryModified: read_working_inventory will fail
650
when the current in memory inventory has been modified.
652
# conceptually this should be an implementation detail of the tree.
653
# XXX: Deprecate this.
654
# ElementTree does its own conversion from UTF-8, so open in
656
with self.lock_read():
657
if self._inventory_is_modified:
658
raise errors.InventoryModified(self)
659
with self._transport.get('inventory') as f:
660
result = self._deserialize(f)
661
self._set_inventory(result, dirty=False)
664
def all_file_ids(self):
665
"""Iterate through file_ids for this tree.
667
file_ids are in a WorkingTree if they are in the working inventory
668
and the working file exists.
670
return {ie.file_id for path, ie in self.iter_entries_by_dir()}
672
def all_versioned_paths(self):
673
return {path for path, ie in self.iter_entries_by_dir()}
675
def set_last_revision(self, new_revision):
676
"""Change the last revision in the working tree."""
677
with self.lock_tree_write():
678
if self._change_last_revision(new_revision):
679
self._cache_basis_inventory(new_revision)
681
def _get_check_refs(self):
682
"""Return the references needed to perform a check of this tree.
684
The default implementation returns no refs, and is only suitable for
685
trees that have no local caching and can commit on ghosts at any time.
687
:seealso: breezy.check for details about check_refs.
691
def _check(self, references):
692
"""Check the tree for consistency.
694
:param references: A dict with keys matching the items returned by
695
self._get_check_refs(), and values from looking those keys up in
698
with self.lock_read():
699
tree_basis = self.basis_tree()
700
with tree_basis.lock_read():
701
repo_basis = references[('trees', self.last_revision())]
702
if len(list(repo_basis.iter_changes(tree_basis))) > 0:
703
raise errors.BzrCheckError(
704
"Mismatched basis inventory content.")
707
def check_state(self):
708
"""Check that the working state is/isn't valid."""
709
with self.lock_read():
710
check_refs = self._get_check_refs()
712
for ref in check_refs:
715
refs[ref] = self.branch.repository.revision_tree(value)
718
def reset_state(self, revision_ids=None):
719
"""Reset the state of the working tree.
721
This does a hard-reset to a last-known-good state. This is a way to
722
fix if something got corrupted (like the .bzr/checkout/dirstate file)
724
with self.lock_tree_write():
725
if revision_ids is None:
726
revision_ids = self.get_parent_ids()
728
rt = self.branch.repository.revision_tree(
729
_mod_revision.NULL_REVISION)
731
rt = self.branch.repository.revision_tree(revision_ids[0])
732
self._write_inventory(rt.root_inventory)
733
self.set_parent_ids(revision_ids)
736
"""Write the in memory inventory to disk."""
737
# TODO: Maybe this should only write on dirty ?
738
if self._control_files._lock_mode != 'w':
739
raise errors.NotWriteLocked(self)
741
self._serialize(self._inventory, sio)
743
self._transport.put_file('inventory', sio,
744
mode=self.controldir._get_file_mode())
745
self._inventory_is_modified = False
747
def get_file_mtime(self, path):
748
"""See Tree.get_file_mtime."""
750
return os.lstat(self.abspath(path)).st_mtime
752
if e.errno == errno.ENOENT:
753
raise errors.NoSuchFile(path)
756
def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
758
return self._path2ie(path).executable
759
except errors.NoSuchFile:
760
# For unversioned files on win32, we just assume they are not
764
def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
765
mode = stat_result.st_mode
766
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
768
def is_executable(self, path):
769
if not self._supports_executable():
770
ie = self._path2ie(path)
773
mode = os.lstat(self.abspath(path)).st_mode
774
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
776
def _is_executable_from_path_and_stat(self, path, stat_result):
777
if not self._supports_executable():
778
return self._is_executable_from_path_and_stat_from_basis(
781
return self._is_executable_from_path_and_stat_from_stat(
784
def _add(self, files, ids, kinds):
785
"""See MutableTree._add."""
786
with self.lock_tree_write():
787
# TODO: Re-adding a file that is removed in the working copy
788
# should probably put it back with the previous ID.
789
# the read and write working inventory should not occur in this
790
# function - they should be part of lock_write and unlock.
791
# FIXME: nested trees
792
inv = self.root_inventory
793
for f, file_id, kind in zip(files, ids, kinds):
795
inv.add_path(f, kind=kind)
797
inv.add_path(f, kind=kind, file_id=file_id)
798
self._inventory_is_modified = True
800
def revision_tree(self, revision_id):
801
"""See WorkingTree.revision_id."""
802
if revision_id == self.last_revision():
804
xml_lines = self.read_basis_inventory()
805
except errors.NoSuchFile:
809
inv = xml7.serializer_v7.read_inventory_from_lines(xml_lines)
810
# dont use the repository revision_tree api because we want
811
# to supply the inventory.
812
if inv.revision_id == revision_id:
813
return InventoryRevisionTree(
814
self.branch.repository, inv, revision_id)
815
except errors.BadInventoryFormat:
817
# raise if there was no inventory, or if we read the wrong inventory.
818
raise errors.NoSuchRevisionInTree(self, revision_id)
820
def annotate_iter(self, path,
821
default_revision=_mod_revision.CURRENT_REVISION):
822
"""See Tree.annotate_iter
824
This implementation will use the basis tree implementation if possible.
825
Lines not in the basis are attributed to CURRENT_REVISION
827
If there are pending merges, lines added by those merges will be
828
incorrectly attributed to CURRENT_REVISION (but after committing, the
829
attribution will be correct).
831
with self.lock_read():
832
file_id = self.path2id(path)
834
raise errors.NoSuchFile(path)
835
maybe_file_parent_keys = []
836
for parent_id in self.get_parent_ids():
838
parent_tree = self.revision_tree(parent_id)
839
except errors.NoSuchRevisionInTree:
840
parent_tree = self.branch.repository.revision_tree(
842
with parent_tree.lock_read():
845
kind = parent_tree.kind(path)
846
except errors.NoSuchFile:
849
# Note: this is slightly unnecessary, because symlinks
850
# and directories have a "text" which is the empty
851
# text, and we know that won't mess up annotations. But
854
parent_path = parent_tree.id2path(file_id)
857
parent_tree.get_file_revision(parent_path))
858
if parent_text_key not in maybe_file_parent_keys:
859
maybe_file_parent_keys.append(parent_text_key)
860
graph = self.branch.repository.get_file_graph()
861
heads = graph.heads(maybe_file_parent_keys)
862
file_parent_keys = []
863
for key in maybe_file_parent_keys:
865
file_parent_keys.append(key)
867
# Now we have the parents of this content
868
annotator = self.branch.repository.texts.get_annotator()
869
text = self.get_file_text(path)
870
this_key = (file_id, default_revision)
871
annotator.add_special_text(this_key, file_parent_keys, text)
872
annotations = [(key[-1], line)
873
for key, line in annotator.annotate_flat(this_key)]
876
def _put_rio(self, filename, stanzas, header):
877
self._must_be_locked()
878
my_file = _mod_rio.rio_file(stanzas, header)
879
self._transport.put_file(filename, my_file,
880
mode=self.controldir._get_file_mode())
882
def set_merge_modified(self, modified_hashes):
884
for path, sha1 in modified_hashes.items():
885
file_id = self.path2id(path)
888
yield _mod_rio.Stanza(file_id=file_id.decode('utf8'),
890
with self.lock_tree_write():
891
self._put_rio('merge-hashes', iter_stanzas(),
892
MERGE_MODIFIED_HEADER_1)
894
def merge_modified(self):
895
"""Return a dictionary of files modified by a merge.
897
The list is initialized by WorkingTree.set_merge_modified, which is
898
typically called after we make some automatic updates to the tree
901
This returns a map of file_id->sha1, containing only files which are
902
still in the working inventory and have that text hash.
904
with self.lock_read():
906
hashfile = self._transport.get('merge-hashes')
907
except errors.NoSuchFile:
912
if next(hashfile) != MERGE_MODIFIED_HEADER_1 + b'\n':
913
raise errors.MergeModifiedFormatError()
914
except StopIteration:
915
raise errors.MergeModifiedFormatError()
916
for s in _mod_rio.RioReader(hashfile):
917
# RioReader reads in Unicode, so convert file_ids back to
919
file_id = cache_utf8.encode(s.get("file_id"))
921
path = self.id2path(file_id)
922
except errors.NoSuchId:
924
text_hash = s.get("hash").encode('ascii')
925
if text_hash == self.get_file_sha1(path):
926
merge_hashes[path] = text_hash
931
def subsume(self, other_tree):
932
def add_children(inventory, entry):
933
for child_entry in entry.children.values():
934
inventory._byid[child_entry.file_id] = child_entry
935
if child_entry.kind == 'directory':
936
add_children(inventory, child_entry)
937
with self.lock_write():
938
if other_tree.path2id('') == self.path2id(''):
939
raise errors.BadSubsumeSource(self, other_tree,
940
'Trees have the same root')
942
other_tree_path = self.relpath(other_tree.basedir)
943
except errors.PathNotChild:
944
raise errors.BadSubsumeSource(
945
self, other_tree, 'Tree is not contained by the other')
946
new_root_parent = self.path2id(osutils.dirname(other_tree_path))
947
if new_root_parent is None:
948
raise errors.BadSubsumeSource(
949
self, other_tree, 'Parent directory is not versioned.')
950
# We need to ensure that the result of a fetch will have a
951
# versionedfile for the other_tree root, and only fetching into
952
# RepositoryKnit2 guarantees that.
953
if not self.branch.repository.supports_rich_root():
954
raise errors.SubsumeTargetNeedsUpgrade(other_tree)
955
with other_tree.lock_tree_write():
956
other_root = other_tree.root_inventory.root
957
other_root.parent_id = new_root_parent
958
other_root.name = osutils.basename(other_tree_path)
959
self.root_inventory.add(other_root)
960
add_children(self.root_inventory, other_root)
961
self._write_inventory(self.root_inventory)
962
# normally we don't want to fetch whole repositories, but i
963
# think here we really do want to consolidate the whole thing.
964
for parent_id in other_tree.get_parent_ids():
965
self.branch.fetch(other_tree.branch, parent_id)
966
self.add_parent_tree_id(parent_id)
967
other_tree.controldir.retire_bzrdir()
969
def extract(self, sub_path, format=None):
970
"""Extract a subtree from this tree.
972
A new branch will be created, relative to the path for this tree.
975
segments = osutils.splitpath(path)
976
transport = self.branch.controldir.root_transport
977
for name in segments:
978
transport = transport.clone(name)
979
transport.ensure_base()
982
with self.lock_tree_write():
984
branch_transport = mkdirs(sub_path)
986
format = self.controldir.cloning_metadir()
987
branch_transport.ensure_base()
988
branch_bzrdir = format.initialize_on_transport(branch_transport)
990
repo = branch_bzrdir.find_repository()
991
except errors.NoRepositoryPresent:
992
repo = branch_bzrdir.create_repository()
993
if not repo.supports_rich_root():
994
raise errors.RootNotRich()
995
new_branch = branch_bzrdir.create_branch()
996
new_branch.pull(self.branch)
997
for parent_id in self.get_parent_ids():
998
new_branch.fetch(self.branch, parent_id)
999
tree_transport = self.controldir.root_transport.clone(sub_path)
1000
if tree_transport.base != branch_transport.base:
1001
tree_bzrdir = format.initialize_on_transport(tree_transport)
1002
tree_bzrdir.set_branch_reference(new_branch)
1004
tree_bzrdir = branch_bzrdir
1005
wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION)
1006
wt.set_parent_ids(self.get_parent_ids())
1007
# FIXME: Support nested trees
1008
my_inv = self.root_inventory
1009
child_inv = inventory.Inventory(root_id=None)
1010
file_id = self.path2id(sub_path)
1011
new_root = my_inv.get_entry(file_id)
1012
my_inv.remove_recursive_id(file_id)
1013
new_root.parent_id = None
1014
child_inv.add(new_root)
1015
self._write_inventory(my_inv)
1016
wt._write_inventory(child_inv)
1019
def list_files(self, include_root=False, from_dir=None, recursive=True,
1020
recurse_nested=False):
1021
"""List all files as (path, class, kind, id, entry).
1023
Lists, but does not descend into unversioned directories.
1024
This does not include files that have been deleted in this
1025
tree. Skips the control directory.
1027
:param include_root: if True, return an entry for the root
1028
:param from_dir: start from this directory or None for the root
1029
:param recursive: whether to recurse into subdirectories or not
1031
with cleanup.ExitStack() as exit_stack:
1032
exit_stack.enter_context(self.lock_read())
1033
if from_dir is None and include_root is True:
1034
yield ('', 'V', 'directory', self.root_inventory.root)
1035
# Convert these into local objects to save lookup times
1036
pathjoin = osutils.pathjoin
1038
# transport.base ends in a slash, we want the piece
1039
# between the last two slashes
1040
transport_base_dir = self.controldir.transport.base.rsplit(
1044
'directory': TreeDirectory,
1046
'symlink': TreeLink,
1047
'tree-reference': TreeReference,
1050
# directory file_id, relative path, absolute path, reverse sorted
1052
if from_dir is not None:
1053
from_inv, from_dir_id = self._path2inv_file_id(from_dir)
1054
if from_dir_id is None:
1055
# Directory not versioned
1057
from_dir_abspath = pathjoin(self.basedir, from_dir)
1059
from_inv = self.root_inventory
1060
from_dir_id = from_inv.root.file_id
1061
from_dir_abspath = self.basedir
1062
children = sorted(os.listdir(from_dir_abspath))
1063
# jam 20060527 The kernel sized tree seems equivalent whether we
1064
# use a deque and popleft to keep them sorted, or if we use a plain
1065
# list and just reverse() them.
1066
children = deque(children)
1067
stack = [(from_inv, from_dir_id, u'', from_dir_abspath, children)]
1069
(inv, from_dir_id, from_dir_relpath, from_dir_abspath,
1070
children) = stack[-1]
1073
f = children.popleft()
1074
# TODO: If we find a subdirectory with its own .bzr
1075
# directory, then that is a separate tree and we
1076
# should exclude it.
1078
# the bzrdir for this tree
1079
if transport_base_dir == f:
1082
# we know that from_dir_relpath and from_dir_abspath never
1083
# end in a slash and 'f' doesn't begin with one, we can do
1084
# a string op, rather than the checks of pathjoin(), all
1085
# relative paths will have an extra slash at the beginning
1086
fp = from_dir_relpath + '/' + f
1089
fap = from_dir_abspath + '/' + f
1091
dir_ie = inv.get_entry(from_dir_id)
1092
if dir_ie.kind == 'directory':
1093
f_ie = dir_ie.children.get(f)
1098
elif self.is_ignored(fp[1:]):
1101
# we may not have found this file, because of a unicode
1102
# issue, or because the directory was actually a
1104
f_norm, can_access = osutils.normalized_filename(f)
1105
if f == f_norm or not can_access:
1106
# No change, so treat this file normally
1109
# this file can be accessed by a normalized path
1110
# check again if it is versioned
1111
# these lines are repeated here for performance
1113
fp = from_dir_relpath + '/' + f
1114
fap = from_dir_abspath + '/' + f
1115
f_ie = inv.get_child(from_dir_id, f)
1118
elif self.is_ignored(fp[1:]):
1123
fk = osutils.file_kind(fap)
1124
if fk == 'directory' and self._directory_is_tree_reference(f):
1125
if not recurse_nested:
1126
fk = 'tree-reference'
1128
subtree = self.get_nested_tree(f)
1129
exit_stack.enter_context(subtree.lock_read())
1130
inv = subtree.root_inventory
1131
f_ie = inv.get_entry(f_ie.file_id)
1134
# make a last minute entry
1136
yield fp[1:], c, fk, f_ie
1139
yield fp[1:], c, fk, fk_entries[fk]()
1141
yield fp[1:], c, fk, TreeEntry()
1144
if fk != 'directory':
1147
# But do this child first if recursing down
1149
new_children = sorted(os.listdir(fap))
1150
new_children = deque(new_children)
1151
stack.append((inv, f_ie.file_id, fp, fap, new_children))
1152
# Break out of inner loop,
1153
# so that we start outer loop with child
1156
# if we finished all children, pop it off the stack
1159
def move(self, from_paths, to_dir=None, after=False):
1162
to_dir must exist in the inventory.
1164
If to_dir exists and is a directory, the files are moved into
1165
it, keeping their old names.
1167
Note that to_dir is only the last component of the new name;
1168
this doesn't change the directory.
1170
For each entry in from_paths the move mode will be determined
1173
The first mode moves the file in the filesystem and updates the
1174
inventory. The second mode only updates the inventory without
1175
touching the file on the filesystem.
1177
move uses the second mode if 'after == True' and the target is
1178
either not versioned or newly added, and present in the working tree.
1180
move uses the second mode if 'after == False' and the source is
1181
versioned but no longer in the working tree, and the target is not
1182
versioned but present in the working tree.
1184
move uses the first mode if 'after == False' and the source is
1185
versioned and present in the working tree, and the target is not
1186
versioned and not present in the working tree.
1188
Everything else results in an error.
1190
This returns a list of (from_path, to_path) pairs for each
1191
entry that is moved.
1196
# check for deprecated use of signature
1198
raise TypeError('You must supply a target directory')
1199
# check destination directory
1200
if isinstance(from_paths, (str, text_type)):
1202
with self.lock_tree_write():
1203
to_abs = self.abspath(to_dir)
1204
if not osutils.isdir(to_abs):
1205
raise errors.BzrMoveFailedError(
1206
'', to_dir, errors.NotADirectory(to_abs))
1207
if not self.has_filename(to_dir):
1208
raise errors.BzrMoveFailedError(
1209
'', to_dir, errors.NotInWorkingDirectory(to_dir))
1210
to_inv, to_dir_id = self._path2inv_file_id(to_dir)
1211
if to_dir_id is None:
1212
raise errors.BzrMoveFailedError(
1213
'', to_dir, errors.NotVersionedError(path=to_dir))
1215
to_dir_ie = to_inv.get_entry(to_dir_id)
1216
if to_dir_ie.kind != 'directory':
1217
raise errors.BzrMoveFailedError(
1218
'', to_dir, errors.NotADirectory(to_abs))
1220
# create rename entries and tuples
1221
for from_rel in from_paths:
1222
from_tail = osutils.splitpath(from_rel)[-1]
1223
from_inv, from_id = self._path2inv_file_id(from_rel)
1225
raise errors.BzrMoveFailedError(from_rel, to_dir,
1226
errors.NotVersionedError(path=from_rel))
1228
from_entry = from_inv.get_entry(from_id)
1229
from_parent_id = from_entry.parent_id
1230
to_rel = osutils.pathjoin(to_dir, from_tail)
1231
rename_entry = InventoryWorkingTree._RenameEntry(
1234
from_tail=from_tail,
1235
from_parent_id=from_parent_id,
1236
to_rel=to_rel, to_tail=from_tail,
1237
to_parent_id=to_dir_id)
1238
rename_entries.append(rename_entry)
1239
rename_tuples.append((from_rel, to_rel))
1241
# determine which move mode to use. checks also for movability
1242
rename_entries = self._determine_mv_mode(rename_entries, after)
1244
original_modified = self._inventory_is_modified
1247
self._inventory_is_modified = True
1248
self._move(rename_entries)
1249
except BaseException:
1250
# restore the inventory on error
1251
self._inventory_is_modified = original_modified
1253
# TODO(jelmer): what about the from_invs?
1254
self._write_inventory(to_inv)
1255
return rename_tuples
1257
def rename_one(self, from_rel, to_rel, after=False):
1260
This can change the directory or the filename or both.
1262
rename_one has several 'modes' to work. First, it can rename a physical
1263
file and change the file_id. That is the normal mode. Second, it can
1264
only change the file_id without touching any physical file.
1266
rename_one uses the second mode if 'after == True' and 'to_rel' is not
1267
versioned but present in the working tree.
1269
rename_one uses the second mode if 'after == False' and 'from_rel' is
1270
versioned but no longer in the working tree, and 'to_rel' is not
1271
versioned but present in the working tree.
1273
rename_one uses the first mode if 'after == False' and 'from_rel' is
1274
versioned and present in the working tree, and 'to_rel' is not
1275
versioned and not present in the working tree.
1277
Everything else results in an error.
1279
with self.lock_tree_write():
1282
# create rename entries and tuples
1283
from_tail = osutils.splitpath(from_rel)[-1]
1284
from_inv, from_id = self._path2inv_file_id(from_rel)
1286
# if file is missing in the inventory maybe it's in the
1288
# TODO(jelmer): This is going to break with nested trees.
1289
from_inv = self.root_inventory
1290
basis_tree = self.branch.basis_tree()
1291
basis_from_inv, from_id = basis_tree._path2inv_file_id(from_rel)
1293
raise errors.BzrRenameFailedError(
1295
errors.NotVersionedError(path=from_rel))
1297
from_entry = from_inv.get_entry(from_id)
1298
except errors.NoSuchId:
1299
# put entry back in the inventory so we can rename it
1300
from_entry = basis_from_inv.get_entry(from_id).copy()
1301
from_inv.add(from_entry)
1303
from_inv, from_inv_id = self._unpack_file_id(from_id)
1304
from_entry = from_inv.get_entry(from_inv_id)
1305
from_parent_id = from_entry.parent_id
1306
to_dir, to_tail = os.path.split(to_rel)
1307
to_inv, to_dir_id = self._path2inv_file_id(to_dir)
1308
rename_entry = InventoryWorkingTree._RenameEntry(
1311
from_tail=from_tail,
1312
from_parent_id=from_parent_id,
1313
to_rel=to_rel, to_tail=to_tail,
1314
to_parent_id=to_dir_id)
1315
rename_entries.append(rename_entry)
1317
# determine which move mode to use. checks also for movability
1318
rename_entries = self._determine_mv_mode(rename_entries, after)
1320
# check if the target changed directory and if the target directory
1322
if to_dir_id is None:
1323
raise errors.BzrMoveFailedError(
1324
from_rel, to_rel, errors.NotVersionedError(path=to_dir))
1326
# all checks done. now we can continue with our actual work
1327
mutter('rename_one:\n'
1332
' to_dir_id {%s}\n',
1333
from_id, from_rel, to_rel, to_dir, to_dir_id)
1335
self._move(rename_entries)
1336
self._write_inventory(to_inv)
1338
class _RenameEntry(object):
1339
def __init__(self, from_rel, from_id, from_tail, from_parent_id,
1340
to_rel, to_tail, to_parent_id, only_change_inv=False,
1342
self.from_rel = from_rel
1343
self.from_id = from_id
1344
self.from_tail = from_tail
1345
self.from_parent_id = from_parent_id
1346
self.to_rel = to_rel
1347
self.to_tail = to_tail
1348
self.to_parent_id = to_parent_id
1349
self.change_id = change_id
1350
self.only_change_inv = only_change_inv
1352
def _determine_mv_mode(self, rename_entries, after=False):
1353
"""Determines for each from-to pair if both inventory and working tree
1354
or only the inventory has to be changed.
1356
Also does basic plausability tests.
1358
# FIXME: Handling of nested trees
1359
inv = self.root_inventory
1361
for rename_entry in rename_entries:
1362
# store to local variables for easier reference
1363
from_rel = rename_entry.from_rel
1364
from_id = rename_entry.from_id
1365
to_rel = rename_entry.to_rel
1366
to_id = inv.path2id(to_rel)
1367
only_change_inv = False
1369
# check the inventory for source and destination
1371
raise errors.BzrMoveFailedError(
1372
from_rel, to_rel, errors.NotVersionedError(path=from_rel))
1373
if to_id is not None:
1375
# allow it with --after but only if dest is newly added
1377
basis = self.basis_tree()
1378
with basis.lock_read():
1380
basis.id2path(to_id)
1381
except errors.NoSuchId:
1382
rename_entry.change_id = True
1385
raise errors.BzrMoveFailedError(
1387
errors.AlreadyVersionedError(path=to_rel))
1389
# try to determine the mode for rename (only change inv or change
1390
# inv and file system)
1392
if not self.has_filename(to_rel):
1393
raise errors.BzrMoveFailedError(
1397
extra="New file has not been created yet"))
1398
only_change_inv = True
1399
elif not self.has_filename(from_rel) and self.has_filename(to_rel):
1400
only_change_inv = True
1401
elif self.has_filename(from_rel) and not self.has_filename(to_rel):
1402
only_change_inv = False
1403
elif (not self.case_sensitive and
1404
from_rel.lower() == to_rel.lower() and
1405
self.has_filename(from_rel)):
1406
only_change_inv = False
1408
# something is wrong, so lets determine what exactly
1409
if not self.has_filename(from_rel) and \
1410
not self.has_filename(to_rel):
1411
raise errors.BzrRenameFailedError(
1413
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
1415
raise errors.RenameFailedFilesExist(from_rel, to_rel)
1416
rename_entry.only_change_inv = only_change_inv
1417
return rename_entries
1419
def _move(self, rename_entries):
1420
"""Moves a list of files.
1422
Depending on the value of the flag 'only_change_inv', the
1423
file will be moved on the file system or not.
1427
for entry in rename_entries:
1429
self._move_entry(entry)
1430
except BaseException:
1431
self._rollback_move(moved)
1435
def _rollback_move(self, moved):
1436
"""Try to rollback a previous move in case of an filesystem error."""
1439
self._move_entry(WorkingTree._RenameEntry(
1440
entry.to_rel, entry.from_id,
1441
entry.to_tail, entry.to_parent_id, entry.from_rel,
1442
entry.from_tail, entry.from_parent_id,
1443
entry.only_change_inv))
1444
except errors.BzrMoveFailedError as e:
1445
raise errors.BzrMoveFailedError(
1446
'', '', "Rollback failed."
1447
" The working tree is in an inconsistent state."
1448
" Please consider doing a 'bzr revert'."
1449
" Error message is: %s" % e)
1451
def _move_entry(self, entry):
1452
inv = self.root_inventory
1453
from_rel_abs = self.abspath(entry.from_rel)
1454
to_rel_abs = self.abspath(entry.to_rel)
1455
if from_rel_abs == to_rel_abs:
1456
raise errors.BzrMoveFailedError(entry.from_rel, entry.to_rel,
1457
"Source and target are identical.")
1459
if not entry.only_change_inv:
1461
osutils.rename(from_rel_abs, to_rel_abs)
1462
except OSError as e:
1463
raise errors.BzrMoveFailedError(
1464
entry.from_rel, entry.to_rel, e[1])
1466
to_id = inv.path2id(entry.to_rel)
1467
inv.remove_recursive_id(to_id)
1468
inv.rename(entry.from_id, entry.to_parent_id, entry.to_tail)
1470
def unversion(self, paths):
1471
"""Remove the paths in paths from the current versioned set.
1473
When a path is unversioned, all of its children are automatically
1476
:param paths: The paths to stop versioning.
1477
:raises NoSuchFile: if any path is not currently versioned.
1479
with self.lock_tree_write():
1482
file_id = self._inventory.path2id(path)
1484
raise errors.NoSuchFile(path, self)
1485
file_ids.add(file_id)
1486
for file_id in file_ids:
1487
if self._inventory.has_id(file_id):
1488
self._inventory.remove_recursive_id(file_id)
1490
# in the future this should just set a dirty bit to wait for
1491
# the final unlock. However, until all methods of workingtree
1492
# start with the current in -memory inventory rather than
1493
# triggering a read, it is more complex - we need to teach
1494
# read_inventory to know when to read, and when to not read
1495
# first... and possibly to save first when the in memory one
1496
# may be corrupted. so for now, we just only write it if it is
1497
# indeed dirty. - RBC 20060907
1498
self._write_inventory(self._inventory)
1500
def stored_kind(self, path):
1501
"""See Tree.stored_kind"""
1502
return self._path2ie(path).kind
1505
"""Yield all unversioned files in this WorkingTree.
1507
If there are any unversioned directories then only the directory is
1508
returned, not all its children. But if there are unversioned files
1509
under a versioned subdirectory, they are returned.
1511
Currently returned depth-first, sorted by name within directories.
1512
This is the same order used by 'osutils.walkdirs'.
1514
# TODO: Work from given directory downwards
1515
for path, dir_entry in self.iter_entries_by_dir():
1516
if dir_entry.kind != 'directory':
1518
# mutter("search for unknowns in %r", path)
1519
dirabs = self.abspath(path)
1520
if not osutils.isdir(dirabs):
1521
# e.g. directory deleted
1525
for subf in os.listdir(dirabs.encode(osutils._fs_enc)):
1527
subf = subf.decode(osutils._fs_enc)
1528
except UnicodeDecodeError:
1529
path_os_enc = path.encode(osutils._fs_enc)
1530
relpath = path_os_enc + b'/' + subf
1531
raise errors.BadFilenameEncoding(relpath,
1534
if self.controldir.is_control_filename(subf):
1536
if subf not in dir_entry.children:
1539
can_access) = osutils.normalized_filename(subf)
1540
except UnicodeDecodeError:
1541
path_os_enc = path.encode(osutils._fs_enc)
1542
relpath = path_os_enc + '/' + subf
1543
raise errors.BadFilenameEncoding(relpath,
1545
if subf_norm != subf and can_access:
1546
if subf_norm not in dir_entry.children:
1547
fl.append(subf_norm)
1553
subp = osutils.pathjoin(path, subf)
1556
def walkdirs(self, prefix=""):
1557
"""Walk the directories of this tree.
1559
returns a generator which yields items in the form:
1560
((curren_directory_path, fileid),
1561
[(file1_path, file1_name, file1_kind, (lstat), file1_id,
1564
This API returns a generator, which is only valid during the current
1565
tree transaction - within a single lock_read or lock_write duration.
1567
If the tree is not locked, it may cause an error to be raised,
1568
depending on the tree implementation.
1570
disk_top = self.abspath(prefix)
1571
if disk_top.endswith('/'):
1572
disk_top = disk_top[:-1]
1573
top_strip_len = len(disk_top) + 1
1574
inventory_iterator = self._walkdirs(prefix)
1575
disk_iterator = osutils.walkdirs(disk_top, prefix)
1577
current_disk = next(disk_iterator)
1578
disk_finished = False
1579
except OSError as e:
1580
if not (e.errno == errno.ENOENT
1581
or (sys.platform == 'win32' and e.errno == ERROR_PATH_NOT_FOUND)):
1584
disk_finished = True
1586
current_inv = next(inventory_iterator)
1587
inv_finished = False
1588
except StopIteration:
1591
while not inv_finished or not disk_finished:
1593
((cur_disk_dir_relpath, cur_disk_dir_path_from_top),
1594
cur_disk_dir_content) = current_disk
1596
((cur_disk_dir_relpath, cur_disk_dir_path_from_top),
1597
cur_disk_dir_content) = ((None, None), None)
1598
if not disk_finished:
1599
# strip out .bzr dirs
1600
if (cur_disk_dir_path_from_top[top_strip_len:] == ''
1601
and len(cur_disk_dir_content) > 0):
1602
# osutils.walkdirs can be made nicer -
1603
# yield the path-from-prefix rather than the pathjoined
1605
bzrdir_loc = bisect_left(cur_disk_dir_content,
1607
if (bzrdir_loc < len(cur_disk_dir_content) and
1608
self.controldir.is_control_filename(
1609
cur_disk_dir_content[bzrdir_loc][0])):
1610
# we dont yield the contents of, or, .bzr itself.
1611
del cur_disk_dir_content[bzrdir_loc]
1613
# everything is unknown
1616
# everything is missing
1619
direction = ((current_inv[0][0] > cur_disk_dir_relpath)
1620
- (current_inv[0][0] < cur_disk_dir_relpath))
1623
# disk is before inventory - unknown
1624
dirblock = [(relpath, basename, kind, stat, None, None) for
1625
relpath, basename, kind, stat, top_path in
1626
cur_disk_dir_content]
1627
yield (cur_disk_dir_relpath, None), dirblock
1629
current_disk = next(disk_iterator)
1630
except StopIteration:
1631
disk_finished = True
1633
# inventory is before disk - missing.
1634
dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
1635
for relpath, basename, dkind, stat, fileid, kind in
1637
yield (current_inv[0][0], current_inv[0][1]), dirblock
1639
current_inv = next(inventory_iterator)
1640
except StopIteration:
1643
# versioned present directory
1644
# merge the inventory and disk data together
1646
for relpath, subiterator in itertools.groupby(sorted(
1647
current_inv[1] + cur_disk_dir_content,
1648
key=operator.itemgetter(0)), operator.itemgetter(1)):
1649
path_elements = list(subiterator)
1650
if len(path_elements) == 2:
1651
inv_row, disk_row = path_elements
1652
# versioned, present file
1653
dirblock.append((inv_row[0],
1654
inv_row[1], disk_row[2],
1655
disk_row[3], inv_row[4],
1657
elif len(path_elements[0]) == 5:
1660
(path_elements[0][0], path_elements[0][1],
1661
path_elements[0][2], path_elements[0][3], None,
1663
elif len(path_elements[0]) == 6:
1664
# versioned, absent file.
1666
(path_elements[0][0], path_elements[0][1],
1667
'unknown', None, path_elements[0][4],
1668
path_elements[0][5]))
1670
raise NotImplementedError('unreachable code')
1671
yield current_inv[0], dirblock
1673
current_inv = next(inventory_iterator)
1674
except StopIteration:
1677
current_disk = next(disk_iterator)
1678
except StopIteration:
1679
disk_finished = True
1681
def _walkdirs(self, prefix=""):
1682
"""Walk the directories of this tree.
1684
:param prefix: is used as the directrory to start with.
1685
:returns: a generator which yields items in the form::
1687
((curren_directory_path, fileid),
1688
[(file1_path, file1_name, file1_kind, None, file1_id,
1691
_directory = 'directory'
1692
# get the root in the inventory
1693
inv, top_id = self._path2inv_file_id(prefix)
1697
pending = [(prefix, '', _directory, None, top_id, None)]
1700
currentdir = pending.pop()
1701
# 0 - relpath, 1- basename, 2- kind, 3- stat, 4-id, 5-kind
1702
top_id = currentdir[4]
1704
relroot = currentdir[0] + '/'
1707
# FIXME: stash the node in pending
1708
entry = inv.get_entry(top_id)
1709
if entry.kind == 'directory':
1710
for name, child in entry.sorted_children():
1711
dirblock.append((relroot + name, name, child.kind, None,
1712
child.file_id, child.kind
1714
yield (currentdir[0], entry.file_id), dirblock
1715
# push the user specified dirs from dirblock
1716
for dir in reversed(dirblock):
1717
if dir[2] == _directory:
1720
def update_feature_flags(self, updated_flags):
1721
"""Update the feature flags for this branch.
1723
:param updated_flags: Dictionary mapping feature names to necessities
1724
A necessity can be None to indicate the feature should be removed
1726
with self.lock_write():
1727
self._format._update_feature_flags(updated_flags)
1728
self.control_transport.put_bytes(
1729
'format', self._format.as_string())
1731
def _check_for_tree_references(self, iterator, recurse_nested, specific_files=None):
1732
"""See if directories have become tree-references."""
1733
blocked_parent_ids = set()
1734
for path, ie in iterator:
1735
if ie.parent_id in blocked_parent_ids:
1736
# This entry was pruned because one of its parents became a
1737
# TreeReference. If this is a directory, mark it as blocked.
1738
if ie.kind == 'directory':
1739
blocked_parent_ids.add(ie.file_id)
1741
if (ie.kind == 'directory' and
1742
self._directory_is_tree_reference(path)):
1744
# This InventoryDirectory needs to be a TreeReference
1745
ie = inventory.TreeReference(ie.file_id, ie.name, ie.parent_id)
1746
blocked_parent_ids.add(ie.file_id)
1748
if ie.kind == 'tree-reference' and recurse_nested:
1749
subtree = self.get_nested_tree(path)
1750
for subpath, ie in subtree.iter_entries_by_dir(
1751
recurse_nested=recurse_nested,
1752
specific_files=specific_files):
1754
full_subpath = osutils.pathjoin(path, subpath)
1757
yield full_subpath, ie
1761
def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
1762
"""See Tree.iter_entries_by_dir()"""
1763
# The only trick here is that if we supports_tree_reference then we
1764
# need to detect if a directory becomes a tree-reference.
1765
iterator = super(WorkingTree, self).iter_entries_by_dir(
1766
specific_files=specific_files)
1767
if not self.supports_tree_reference():
1770
return self._check_for_tree_references(
1771
iterator, recurse_nested=recurse_nested,
1772
specific_files=specific_files)
1774
def get_canonical_paths(self, paths):
1775
"""Look up canonical paths for multiple items.
1777
:param paths: A sequence of paths relative to the root of the tree.
1778
:return: A iterator over paths, with each item the corresponding input
1779
path adjusted to account for existing elements that match case
1782
with self.lock_read():
1783
if not self.case_sensitive:
1786
elif sys.platform == 'darwin':
1790
return unicodedata.normalize('NFC', x)
1794
if normalize is None or self.is_versioned(path):
1797
yield get_canonical_path(self, path, normalize)
1799
def get_reference_info(self, path, branch=None):
1800
file_id = self.path2id(path)
1803
return self.branch.get_reference_info(file_id)[0]
1805
def set_reference_info(self, tree_path, branch_location):
1806
file_id = self.path2id(tree_path)
1808
raise errors.NoSuchFile(tree_path)
1809
self.branch.set_reference_info(file_id, branch_location, tree_path)
1811
def reference_parent(self, path, branch=None, possible_transports=None):
1812
return self.branch.reference_parent(
1814
path, possible_transports=possible_transports)
1817
class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
1818
"""Base class for working trees that live in bzr meta directories."""
1820
ignore_filename = '.bzrignore'
1823
WorkingTreeFormat.__init__(self)
1824
bzrdir.BzrFormat.__init__(self)
1827
def find_format_string(klass, controldir):
1828
"""Return format name for the working tree object in controldir."""
1830
transport = controldir.get_workingtree_transport(None)
1831
return transport.get_bytes("format")
1832
except errors.NoSuchFile:
1833
raise errors.NoWorkingTree(base=transport.base)
1836
def find_format(klass, controldir):
1837
"""Return the format for the working tree object in controldir."""
1838
format_string = klass.find_format_string(controldir)
1839
return klass._find_format(format_registry, 'working tree',
1842
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
1844
WorkingTreeFormat.check_support_status(
1845
self, allow_unsupported=allow_unsupported,
1846
recommend_upgrade=recommend_upgrade, basedir=basedir)
1847
bzrdir.BzrFormat.check_support_status(
1848
self, allow_unsupported=allow_unsupported,
1849
recommend_upgrade=recommend_upgrade, basedir=basedir)