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 bisect import bisect_left
36
from collections.abc import deque
37
except ImportError: # python < 3.7
38
from collections import deque
40
from io import BytesIO
47
# Explicitly import breezy.bzrdir so that the BzrProber
48
# is guaranteed to be registered.
51
from .. import lazy_import
52
lazy_import.lazy_import(globals(), """
56
conflicts as _mod_conflicts,
60
revision as _mod_revision,
63
from breezy.bzr import (
64
conflicts as _mod_bzr_conflicts,
76
from ..lock import LogicalLockResult
77
from .inventorytree import InventoryRevisionTree, MutableInventoryTree
78
from ..trace import mutter, note
87
from ..workingtree import (
94
MERGE_MODIFIED_HEADER_1 = b"BZR merge-modified list format 1"
95
# TODO: Modifying the conflict objects or their type is currently nearly
96
# impossible as there is no clear relationship between the working tree format
97
# and the conflict list file format.
98
CONFLICT_HEADER_1 = b"BZR conflict list format 1"
99
ERROR_PATH_NOT_FOUND = 3 # WindowsError errno code, equivalent to ENOENT
102
class InventoryModified(errors.InternalBzrError):
104
_fmt = ("The current inventory for the tree %(tree)r has been modified,"
105
" so a clean inventory cannot be read without data loss.")
107
def __init__(self, tree):
111
class InventoryWorkingTree(WorkingTree, MutableInventoryTree):
112
"""Base class for working trees that are inventory-oriented.
114
The inventory is held in the `Branch` working-inventory, and the
115
files are in a directory on disk.
117
It is possible for a `WorkingTree` to have a filename which is
118
not listed in the Inventory and vice versa.
121
def __init__(self, basedir='.',
128
"""Construct a InventoryWorkingTree instance. This is not a public API.
130
:param branch: A branch to override probing for the branch.
132
super(InventoryWorkingTree, self).__init__(
133
basedir=basedir, branch=branch,
134
_transport=_control_files._transport, _internal=_internal,
135
_format=_format, _controldir=_controldir)
137
self._control_files = _control_files
138
self._detect_case_handling()
139
self._setup_directory_is_tree_reference()
141
if _inventory is None:
142
# This will be acquired on lock_read() or lock_write()
143
self._inventory_is_modified = False
144
self._inventory = None
146
# the caller of __init__ has provided an inventory,
147
# we assume they know what they are doing - as its only
148
# the Format factory and creation methods that are
149
# permitted to do this.
150
self._set_inventory(_inventory, dirty=False)
152
def _set_inventory(self, inv, dirty):
153
"""Set the internal cached inventory.
155
:param inv: The inventory to set.
156
:param dirty: A boolean indicating whether the inventory is the same
157
logical inventory as whats on disk. If True the inventory is not
158
the same and should be written to disk or data will be lost, if
159
False then the inventory is the same as that on disk and any
160
serialisation would be unneeded overhead.
162
self._inventory = inv
163
self._inventory_is_modified = dirty
165
def _detect_case_handling(self):
166
wt_trans = self.controldir.get_workingtree_transport(None)
168
wt_trans.stat(self._format.case_sensitive_filename)
169
except errors.NoSuchFile:
170
self.case_sensitive = True
172
self.case_sensitive = False
174
def transform(self, pb=None):
175
from .transform import InventoryTreeTransform
176
return InventoryTreeTransform(self, pb=pb)
178
def _setup_directory_is_tree_reference(self):
179
if self._branch.repository._format.supports_tree_reference:
180
self._directory_is_tree_reference = \
181
self._directory_may_be_tree_reference
183
self._directory_is_tree_reference = \
184
self._directory_is_never_tree_reference
186
def _directory_is_never_tree_reference(self, relpath):
189
def _directory_may_be_tree_reference(self, relpath):
190
# as a special case, if a directory contains control files then
191
# it's a tree reference, except that the root of the tree is not
192
return relpath and osutils.isdir(self.abspath(relpath) + u"/.bzr")
193
# TODO: We could ask all the control formats whether they
194
# recognize this directory, but at the moment there's no cheap api
195
# to do that. Since we probably can only nest bzr checkouts and
196
# they always use this name it's ok for now. -- mbp 20060306
198
# FIXME: There is an unhandled case here of a subdirectory
199
# containing .bzr but not a branch; that will probably blow up
200
# when you try to commit it. It might happen if there is a
201
# checkout in a subdirectory. This can be avoided by not adding
204
def _serialize(self, inventory, out_file):
205
xml5.serializer_v5.write_inventory(
206
self._inventory, out_file, working=True)
208
def _deserialize(selt, in_file):
209
return xml5.serializer_v5.read_inventory(in_file)
211
def break_lock(self):
212
"""Break a lock if one is present from another instance.
214
Uses the ui factory to ask for confirmation if the lock may be from
217
This will probe the repository for its lock as well.
219
self._control_files.break_lock()
220
self.branch.break_lock()
223
return self._control_files.is_locked()
225
def _must_be_locked(self):
226
if not self.is_locked():
227
raise errors.ObjectNotLocked(self)
230
"""Lock the tree for reading.
232
This also locks the branch, and can be unlocked via self.unlock().
234
:return: A breezy.lock.LogicalLockResult.
236
if not self.is_locked():
238
self.branch.lock_read()
240
self._control_files.lock_read()
241
return LogicalLockResult(self.unlock)
242
except BaseException:
246
def lock_tree_write(self):
247
"""See MutableTree.lock_tree_write, and WorkingTree.unlock.
249
:return: A breezy.lock.LogicalLockResult.
251
if not self.is_locked():
253
self.branch.lock_read()
255
self._control_files.lock_write()
256
return LogicalLockResult(self.unlock)
257
except BaseException:
261
def lock_write(self):
262
"""See MutableTree.lock_write, and WorkingTree.unlock.
264
:return: A breezy.lock.LogicalLockResult.
266
if not self.is_locked():
268
self.branch.lock_write()
270
self._control_files.lock_write()
271
return LogicalLockResult(self.unlock)
272
except BaseException:
276
def get_physical_lock_status(self):
277
return self._control_files.get_physical_lock_status()
279
def _write_inventory(self, inv):
280
"""Write inventory as the current inventory."""
281
with self.lock_tree_write():
282
self._set_inventory(inv, dirty=True)
285
# XXX: This method should be deprecated in favour of taking in a proper
286
# new Inventory object.
287
def set_inventory(self, new_inventory_list):
288
from .inventory import (
293
with self.lock_tree_write():
294
inv = Inventory(self.path2id(''))
295
for path, file_id, parent, kind in new_inventory_list:
296
name = os.path.basename(path)
299
# fixme, there should be a factory function inv,add_??
300
if kind == 'directory':
301
inv.add(InventoryDirectory(file_id, name, parent))
303
inv.add(InventoryFile(file_id, name, parent))
304
elif kind == 'symlink':
305
inv.add(InventoryLink(file_id, name, parent))
307
raise errors.BzrError("unknown kind %r" % kind)
308
self._write_inventory(inv)
310
def _write_basis_inventory(self, xml):
311
"""Write the basis inventory XML to the basis-inventory file"""
312
path = self._basis_inventory_name()
313
sio = BytesIO(b''.join(xml))
314
self._transport.put_file(path, sio,
315
mode=self.controldir._get_file_mode())
317
def _reset_data(self):
318
"""Reset transient data that cannot be revalidated."""
319
self._inventory_is_modified = False
320
with self._transport.get('inventory') as f:
321
result = self._deserialize(f)
322
self._set_inventory(result, dirty=False)
324
def store_uncommitted(self):
325
"""Store uncommitted changes from the tree in the branch."""
326
with self.lock_write():
327
target_tree = self.basis_tree()
328
from ..shelf import ShelfCreator
329
shelf_creator = ShelfCreator(self, target_tree)
331
if not shelf_creator.shelve_all():
333
self.branch.store_uncommitted(shelf_creator)
334
shelf_creator.transform()
336
shelf_creator.finalize()
337
note('Uncommitted changes stored in branch "%s".',
340
def restore_uncommitted(self):
341
"""Restore uncommitted changes from the branch into the tree."""
342
with self.lock_write():
343
unshelver = self.branch.get_unshelver(self)
344
if unshelver is None:
347
merger = unshelver.make_merger()
348
merger.ignore_zero = True
350
self.branch.store_uncommitted(None)
354
def get_shelf_manager(self):
355
"""Return the ShelfManager for this WorkingTree."""
356
from ..shelf import ShelfManager
357
return ShelfManager(self, self._transport)
359
def _set_root_id(self, file_id):
360
"""Set the root id for this tree, in a format specific manner.
362
:param file_id: The file id to assign to the root. It must not be
363
present in the current inventory or an error will occur. It must
364
not be None, but rather a valid file id.
366
inv = self._inventory
367
orig_root_id = inv.root.file_id
368
# TODO: it might be nice to exit early if there was nothing
369
# to do, saving us from trigger a sync on unlock.
370
self._inventory_is_modified = True
371
# we preserve the root inventory entry object, but
372
# unlinkit from the byid index
373
inv.delete(inv.root.file_id)
374
inv.root.file_id = file_id
375
# and link it into the index with the new changed id.
376
inv._byid[inv.root.file_id] = inv.root
377
# and finally update all children to reference the new id.
378
# XXX: this should be safe to just look at the root.children
379
# list, not the WHOLE INVENTORY.
380
for fid in inv.iter_all_ids():
381
entry = inv.get_entry(fid)
382
if entry.parent_id == orig_root_id:
383
entry.parent_id = inv.root.file_id
385
def remove(self, files, verbose=False, to_file=None, keep_files=True,
387
"""Remove nominated files from the working tree metadata.
389
:files: File paths relative to the basedir.
390
:keep_files: If true, the files will also be kept.
391
:force: Delete files and directories, even if they are changed and
392
even if the directories are not empty.
394
if isinstance(files, str):
399
all_files = set() # specified and nested files
405
def recurse_directory_to_add_files(directory):
406
# Recurse directory and add all files
407
# so we can check if they have changed.
408
for parent_path, file_infos in self.walkdirs(directory):
409
for relpath, basename, kind, lstat, kind in file_infos:
410
# Is it versioned or ignored?
411
if self.is_versioned(relpath):
412
# Add nested content for deletion.
413
all_files.add(relpath)
415
# Files which are not versioned
416
# should be treated as unknown.
417
files_to_backup.append(relpath)
419
with self.lock_tree_write():
421
for filename in files:
422
# Get file name into canonical form.
423
abspath = self.abspath(filename)
424
filename = self.relpath(abspath)
425
if len(filename) > 0:
426
all_files.add(filename)
427
recurse_directory_to_add_files(filename)
429
files = list(all_files)
432
return # nothing to do
434
# Sort needed to first handle directory content before the
436
files.sort(reverse=True)
438
# Bail out if we are going to delete files we shouldn't
439
if not keep_files and not force:
440
for change in self.iter_changes(
441
self.basis_tree(), include_unchanged=True,
442
require_versioned=False, want_unversioned=True,
443
specific_files=files):
444
if change.versioned[0] is False:
445
# The record is unknown or newly added
446
files_to_backup.append(change.path[1])
447
elif (change.changed_content and (change.kind[1] is not None)
448
and osutils.is_inside_any(files, change.path[1])):
449
# Versioned and changed, but not deleted, and still
450
# in one of the dirs to be deleted.
451
files_to_backup.append(change.path[1])
453
def backup(file_to_backup):
454
backup_name = self.controldir._available_backup_name(
456
osutils.rename(abs_path, self.abspath(backup_name))
457
return "removed %s (but kept a copy: %s)" % (file_to_backup,
460
# Build inv_delta and delete files where applicable,
461
# do this before any modifications to meta data.
463
fid = self.path2id(f)
466
message = "%s is not versioned." % (f,)
469
# having removed it, it must be either ignored or
471
if self.is_ignored(f):
475
# XXX: Really should be a more abstract reporter
477
kind_ch = osutils.kind_marker(self.kind(f))
479
new_status + ' ' + f + kind_ch + '\n')
481
inv_delta.append((f, None, fid, None))
482
message = "removed %s" % (f,)
485
abs_path = self.abspath(f)
486
if osutils.lexists(abs_path):
487
if (osutils.isdir(abs_path)
488
and len(os.listdir(abs_path)) > 0):
490
osutils.rmtree(abs_path)
491
message = "deleted %s" % (f,)
495
if f in files_to_backup:
498
osutils.delete_any(abs_path)
499
message = "deleted %s" % (f,)
500
elif message is not None:
501
# Only care if we haven't done anything yet.
502
message = "%s does not exist." % (f,)
504
# Print only one message (if any) per file.
505
if message is not None:
507
self.apply_inventory_delta(inv_delta)
509
def get_nested_tree(self, path):
510
return WorkingTree.open(self.abspath(path))
512
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
513
"""See MutableTree.set_parent_trees."""
514
parent_ids = [rev for (rev, tree) in parents_list]
515
for revision_id in parent_ids:
516
_mod_revision.check_not_reserved_id(revision_id)
518
with self.lock_tree_write():
519
self._check_parents_for_ghosts(parent_ids,
520
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
522
parent_ids = self._filter_parent_ids_by_ancestry(parent_ids)
524
if len(parent_ids) == 0:
525
leftmost_parent_id = _mod_revision.NULL_REVISION
526
leftmost_parent_tree = None
528
leftmost_parent_id, leftmost_parent_tree = parents_list[0]
530
if self._change_last_revision(leftmost_parent_id):
531
if leftmost_parent_tree is None:
532
# If we don't have a tree, fall back to reading the
533
# parent tree from the repository.
534
self._cache_basis_inventory(leftmost_parent_id)
536
inv = leftmost_parent_tree.root_inventory
537
xml = self._create_basis_xml_from_inventory(
538
leftmost_parent_id, inv)
539
self._write_basis_inventory(xml)
540
self._set_merges_from_parent_ids(parent_ids)
542
def _cache_basis_inventory(self, new_revision):
543
"""Cache new_revision as the basis inventory."""
544
# TODO: this should allow the ready-to-use inventory to be passed in,
545
# as commit already has that ready-to-use [while the format is the
548
# this double handles the inventory - unpack and repack -
549
# but is easier to understand. We can/should put a conditional
550
# in here based on whether the inventory is in the latest format
551
# - perhaps we should repack all inventories on a repository
553
# the fast path is to copy the raw xml from the repository. If the
554
# xml contains 'revision_id="', then we assume the right
555
# revision_id is set. We must check for this full string, because a
556
# root node id can legitimately look like 'revision_id' but cannot
558
lines = self.branch.repository._get_inventory_xml(new_revision)
560
if (b'revision_id="' not in firstline
561
or b'format="7"' not in firstline):
562
inv = self.branch.repository._serializer.read_inventory_from_lines(
564
lines = self._create_basis_xml_from_inventory(new_revision, inv)
565
self._write_basis_inventory(lines)
566
except (errors.NoSuchRevision, errors.RevisionNotPresent):
569
def _basis_inventory_name(self):
570
return 'basis-inventory-cache'
572
def _create_basis_xml_from_inventory(self, revision_id, inventory):
573
"""Create the text that will be saved in basis-inventory"""
574
inventory.revision_id = revision_id
575
return xml7.serializer_v7.write_inventory_to_lines(inventory)
577
def set_conflicts(self, conflicts):
578
conflict_list = _mod_bzr_conflicts.ConflictList(conflicts)
579
with self.lock_tree_write():
580
self._put_rio('conflicts', conflict_list.to_stanzas(),
583
def add_conflicts(self, new_conflicts):
584
with self.lock_tree_write():
585
conflict_set = set(self.conflicts())
586
conflict_set.update(set(list(new_conflicts)))
588
sorted(conflict_set, key=_mod_bzr_conflicts.Conflict.sort_key))
591
with self.lock_read():
593
confile = self._transport.get('conflicts')
594
except errors.NoSuchFile:
595
return _mod_bzr_conflicts.ConflictList()
598
if next(confile) != CONFLICT_HEADER_1 + b'\n':
599
raise errors.ConflictFormatError()
600
except StopIteration:
601
raise errors.ConflictFormatError()
602
reader = _mod_rio.RioReader(confile)
603
return _mod_bzr_conflicts.ConflictList.from_stanzas(reader)
607
def get_ignore_list(self):
608
"""Return list of ignore patterns.
610
Cached in the Tree object after the first call.
612
ignoreset = getattr(self, '_ignoreset', None)
613
if ignoreset is not None:
617
ignore_globs.update(ignores.get_runtime_ignores())
618
ignore_globs.update(ignores.get_user_ignores())
619
if self.has_filename(self._format.ignore_filename):
620
with self.get_file(self._format.ignore_filename) as f:
621
ignore_globs.update(ignores.parse_ignore_file(f))
622
self._ignoreset = ignore_globs
626
self._flush_ignore_list_cache()
628
def _flush_ignore_list_cache(self):
629
"""Resets the cached ignore list to force a cache rebuild."""
630
self._ignoreset = None
631
self._ignoreglobster = None
633
def is_ignored(self, filename):
634
r"""Check whether the filename matches an ignore pattern.
636
Patterns containing '/' or '\' need to match the whole path;
637
others match against only the last component. Patterns starting
638
with '!' are ignore exceptions. Exceptions take precedence
639
over regular patterns and cause the filename to not be ignored.
641
If the file is ignored, returns the pattern which caused it to
642
be ignored, otherwise None. So this can simply be used as a
643
boolean if desired."""
644
if getattr(self, '_ignoreglobster', None) is None:
645
self._ignoreglobster = globbing.ExceptionGlobster(
646
self.get_ignore_list())
647
return self._ignoreglobster.match(filename)
649
def read_basis_inventory(self):
650
"""Read the cached basis inventory."""
651
path = self._basis_inventory_name()
652
return osutils.split_lines(self._transport.get_bytes(path))
654
def read_working_inventory(self):
655
"""Read the working inventory.
657
:raises errors.InventoryModified: read_working_inventory will fail
658
when the current in memory inventory has been modified.
660
# conceptually this should be an implementation detail of the tree.
661
# XXX: Deprecate this.
662
# ElementTree does its own conversion from UTF-8, so open in
664
with self.lock_read():
665
if self._inventory_is_modified:
666
raise InventoryModified(self)
667
with self._transport.get('inventory') as f:
668
result = self._deserialize(f)
669
self._set_inventory(result, dirty=False)
672
def all_file_ids(self):
673
"""Iterate through file_ids for this tree.
675
file_ids are in a WorkingTree if they are in the working inventory
676
and the working file exists.
678
return {ie.file_id for path, ie in self.iter_entries_by_dir()}
680
def all_versioned_paths(self):
681
return {path for path, ie in self.iter_entries_by_dir()}
683
def set_last_revision(self, new_revision):
684
"""Change the last revision in the working tree."""
685
with self.lock_tree_write():
686
if self._change_last_revision(new_revision):
687
self._cache_basis_inventory(new_revision)
689
def _get_check_refs(self):
690
"""Return the references needed to perform a check of this tree.
692
The default implementation returns no refs, and is only suitable for
693
trees that have no local caching and can commit on ghosts at any time.
695
:seealso: breezy.check for details about check_refs.
699
def _check(self, references):
700
"""Check the tree for consistency.
702
:param references: A dict with keys matching the items returned by
703
self._get_check_refs(), and values from looking those keys up in
706
with self.lock_read():
707
tree_basis = self.basis_tree()
708
with tree_basis.lock_read():
709
repo_basis = references[('trees', self.last_revision())]
710
if len(list(repo_basis.iter_changes(tree_basis))) > 0:
711
raise errors.BzrCheckError(
712
"Mismatched basis inventory content.")
715
def check_state(self):
716
"""Check that the working state is/isn't valid."""
717
with self.lock_read():
718
check_refs = self._get_check_refs()
720
for ref in check_refs:
723
refs[ref] = self.branch.repository.revision_tree(value)
726
def reset_state(self, revision_ids=None):
727
"""Reset the state of the working tree.
729
This does a hard-reset to a last-known-good state. This is a way to
730
fix if something got corrupted (like the .bzr/checkout/dirstate file)
732
with self.lock_tree_write():
733
if revision_ids is None:
734
revision_ids = self.get_parent_ids()
736
rt = self.branch.repository.revision_tree(
737
_mod_revision.NULL_REVISION)
739
rt = self.branch.repository.revision_tree(revision_ids[0])
740
self._write_inventory(rt.root_inventory)
741
self.set_parent_ids(revision_ids)
744
"""Write the in memory inventory to disk."""
745
# TODO: Maybe this should only write on dirty ?
746
if self._control_files._lock_mode != 'w':
747
raise errors.NotWriteLocked(self)
749
self._serialize(self._inventory, sio)
751
self._transport.put_file('inventory', sio,
752
mode=self.controldir._get_file_mode())
753
self._inventory_is_modified = False
755
def get_file_mtime(self, path):
756
"""See Tree.get_file_mtime."""
758
return os.lstat(self.abspath(path)).st_mtime
760
if e.errno == errno.ENOENT:
761
raise errors.NoSuchFile(path)
764
def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
766
return self._path2ie(path).executable
767
except errors.NoSuchFile:
768
# For unversioned files on win32, we just assume they are not
772
def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
773
mode = stat_result.st_mode
774
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
776
def is_executable(self, path):
777
if not self._supports_executable():
778
ie = self._path2ie(path)
781
mode = os.lstat(self.abspath(path)).st_mode
782
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
784
def _is_executable_from_path_and_stat(self, path, stat_result):
785
if not self._supports_executable():
786
return self._is_executable_from_path_and_stat_from_basis(
789
return self._is_executable_from_path_and_stat_from_stat(
792
def _add(self, files, ids, kinds):
793
"""See MutableTree._add."""
794
with self.lock_tree_write():
795
# TODO: Re-adding a file that is removed in the working copy
796
# should probably put it back with the previous ID.
797
# the read and write working inventory should not occur in this
798
# function - they should be part of lock_write and unlock.
799
# FIXME: nested trees
800
inv = self.root_inventory
801
for f, file_id, kind in zip(files, ids, kinds):
803
inv.add_path(f, kind=kind)
805
inv.add_path(f, kind=kind, file_id=file_id)
806
self._inventory_is_modified = True
808
def revision_tree(self, revision_id):
809
"""See WorkingTree.revision_id."""
810
if revision_id == self.last_revision():
812
xml_lines = self.read_basis_inventory()
813
except errors.NoSuchFile:
817
inv = xml7.serializer_v7.read_inventory_from_lines(xml_lines)
818
# dont use the repository revision_tree api because we want
819
# to supply the inventory.
820
if inv.revision_id == revision_id:
821
return InventoryRevisionTree(
822
self.branch.repository, inv, revision_id)
823
except serializer.BadInventoryFormat:
825
# raise if there was no inventory, or if we read the wrong inventory.
826
raise errors.NoSuchRevisionInTree(self, revision_id)
828
def annotate_iter(self, path,
829
default_revision=_mod_revision.CURRENT_REVISION):
830
"""See Tree.annotate_iter
832
This implementation will use the basis tree implementation if possible.
833
Lines not in the basis are attributed to CURRENT_REVISION
835
If there are pending merges, lines added by those merges will be
836
incorrectly attributed to CURRENT_REVISION (but after committing, the
837
attribution will be correct).
839
with self.lock_read():
840
file_id = self.path2id(path)
842
raise errors.NoSuchFile(path)
843
maybe_file_parent_keys = []
844
for parent_id in self.get_parent_ids():
846
parent_tree = self.revision_tree(parent_id)
847
except errors.NoSuchRevisionInTree:
848
parent_tree = self.branch.repository.revision_tree(
850
with parent_tree.lock_read():
853
kind = parent_tree.kind(path)
854
except errors.NoSuchFile:
857
# Note: this is slightly unnecessary, because symlinks
858
# and directories have a "text" which is the empty
859
# text, and we know that won't mess up annotations. But
862
parent_path = parent_tree.id2path(file_id)
865
parent_tree.get_file_revision(parent_path))
866
if parent_text_key not in maybe_file_parent_keys:
867
maybe_file_parent_keys.append(parent_text_key)
868
graph = self.branch.repository.get_file_graph()
869
heads = graph.heads(maybe_file_parent_keys)
870
file_parent_keys = []
871
for key in maybe_file_parent_keys:
873
file_parent_keys.append(key)
875
# Now we have the parents of this content
876
annotator = self.branch.repository.texts.get_annotator()
877
text = self.get_file_text(path)
878
this_key = (file_id, default_revision)
879
annotator.add_special_text(this_key, file_parent_keys, text)
880
annotations = [(key[-1], line)
881
for key, line in annotator.annotate_flat(this_key)]
884
def _put_rio(self, filename, stanzas, header):
885
self._must_be_locked()
886
my_file = _mod_rio.rio_file(stanzas, header)
887
self._transport.put_file(filename, my_file,
888
mode=self.controldir._get_file_mode())
890
def set_merge_modified(self, modified_hashes):
892
for path, sha1 in modified_hashes.items():
893
file_id = self.path2id(path)
896
yield _mod_rio.Stanza(file_id=file_id.decode('utf8'),
898
with self.lock_tree_write():
899
self._put_rio('merge-hashes', iter_stanzas(),
900
MERGE_MODIFIED_HEADER_1)
902
def merge_modified(self):
903
"""Return a dictionary of files modified by a merge.
905
The list is initialized by WorkingTree.set_merge_modified, which is
906
typically called after we make some automatic updates to the tree
909
This returns a map of file_id->sha1, containing only files which are
910
still in the working inventory and have that text hash.
912
with self.lock_read():
914
hashfile = self._transport.get('merge-hashes')
915
except errors.NoSuchFile:
920
if next(hashfile) != MERGE_MODIFIED_HEADER_1 + b'\n':
921
raise errors.MergeModifiedFormatError()
922
except StopIteration:
923
raise errors.MergeModifiedFormatError()
924
for s in _mod_rio.RioReader(hashfile):
925
# RioReader reads in Unicode, so convert file_ids back to
927
file_id = cache_utf8.encode(s.get("file_id"))
929
path = self.id2path(file_id)
930
except errors.NoSuchId:
932
text_hash = s.get("hash").encode('ascii')
933
if text_hash == self.get_file_sha1(path):
934
merge_hashes[path] = text_hash
939
def subsume(self, other_tree):
940
def add_children(inventory, entry):
941
for child_entry in entry.children.values():
942
inventory._byid[child_entry.file_id] = child_entry
943
if child_entry.kind == 'directory':
944
add_children(inventory, child_entry)
945
with self.lock_write():
946
if other_tree.path2id('') == self.path2id(''):
947
raise errors.BadSubsumeSource(self, other_tree,
948
'Trees have the same root')
950
other_tree_path = self.relpath(other_tree.basedir)
951
except errors.PathNotChild:
952
raise errors.BadSubsumeSource(
953
self, other_tree, 'Tree is not contained by the other')
954
new_root_parent = self.path2id(osutils.dirname(other_tree_path))
955
if new_root_parent is None:
956
raise errors.BadSubsumeSource(
957
self, other_tree, 'Parent directory is not versioned.')
958
# We need to ensure that the result of a fetch will have a
959
# versionedfile for the other_tree root, and only fetching into
960
# RepositoryKnit2 guarantees that.
961
if not self.branch.repository.supports_rich_root():
962
raise errors.SubsumeTargetNeedsUpgrade(other_tree)
963
with other_tree.lock_tree_write():
964
other_root = other_tree.root_inventory.root
965
other_root.parent_id = new_root_parent
966
other_root.name = osutils.basename(other_tree_path)
967
self.root_inventory.add(other_root)
968
add_children(self.root_inventory, other_root)
969
self._write_inventory(self.root_inventory)
970
# normally we don't want to fetch whole repositories, but i
971
# think here we really do want to consolidate the whole thing.
972
for parent_id in other_tree.get_parent_ids():
973
self.branch.fetch(other_tree.branch, parent_id)
974
self.add_parent_tree_id(parent_id)
975
other_tree.controldir.retire_bzrdir()
977
def extract(self, sub_path, format=None):
978
"""Extract a subtree from this tree.
980
A new branch will be created, relative to the path for this tree.
983
segments = osutils.splitpath(path)
984
transport = self.branch.controldir.root_transport
985
for name in segments:
986
transport = transport.clone(name)
987
transport.ensure_base()
990
with self.lock_tree_write():
992
branch_transport = mkdirs(sub_path)
994
format = self.controldir.cloning_metadir()
995
branch_transport.ensure_base()
996
branch_bzrdir = format.initialize_on_transport(branch_transport)
998
repo = branch_bzrdir.find_repository()
999
except errors.NoRepositoryPresent:
1000
repo = branch_bzrdir.create_repository()
1001
if not repo.supports_rich_root():
1002
raise errors.RootNotRich()
1003
new_branch = branch_bzrdir.create_branch()
1004
new_branch.pull(self.branch)
1005
for parent_id in self.get_parent_ids():
1006
new_branch.fetch(self.branch, parent_id)
1007
tree_transport = self.controldir.root_transport.clone(sub_path)
1008
if tree_transport.base != branch_transport.base:
1009
tree_bzrdir = format.initialize_on_transport(tree_transport)
1010
tree_bzrdir.set_branch_reference(new_branch)
1012
tree_bzrdir = branch_bzrdir
1013
wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION)
1014
wt.set_parent_ids(self.get_parent_ids())
1015
# FIXME: Support nested trees
1016
my_inv = self.root_inventory
1017
child_inv = inventory.Inventory(root_id=None)
1018
file_id = self.path2id(sub_path)
1019
new_root = my_inv.get_entry(file_id)
1020
my_inv.remove_recursive_id(file_id)
1021
new_root.parent_id = None
1022
child_inv.add(new_root)
1023
self._write_inventory(my_inv)
1024
wt._write_inventory(child_inv)
1027
def list_files(self, include_root=False, from_dir=None, recursive=True,
1028
recurse_nested=False):
1029
"""List all files as (path, class, kind, id, entry).
1031
Lists, but does not descend into unversioned directories.
1032
This does not include files that have been deleted in this
1033
tree. Skips the control directory.
1035
:param include_root: if True, return an entry for the root
1036
:param from_dir: start from this directory or None for the root
1037
:param recursive: whether to recurse into subdirectories or not
1039
with contextlib.ExitStack() as exit_stack:
1040
exit_stack.enter_context(self.lock_read())
1041
if from_dir is None and include_root is True:
1042
yield ('', 'V', 'directory', self.root_inventory.root)
1043
# Convert these into local objects to save lookup times
1044
pathjoin = osutils.pathjoin
1046
# transport.base ends in a slash, we want the piece
1047
# between the last two slashes
1048
transport_base_dir = self.controldir.transport.base.rsplit(
1052
'directory': TreeDirectory,
1054
'symlink': TreeLink,
1055
'tree-reference': TreeReference,
1058
# directory file_id, relative path, absolute path, reverse sorted
1060
if from_dir is not None:
1061
from_inv, from_dir_id = self._path2inv_file_id(from_dir)
1062
if from_dir_id is None:
1063
# Directory not versioned
1065
from_dir_abspath = pathjoin(self.basedir, from_dir)
1067
from_inv = self.root_inventory
1068
from_dir_id = from_inv.root.file_id
1069
from_dir_abspath = self.basedir
1070
children = sorted(os.listdir(from_dir_abspath))
1071
# jam 20060527 The kernel sized tree seems equivalent whether we
1072
# use a deque and popleft to keep them sorted, or if we use a plain
1073
# list and just reverse() them.
1074
children = deque(children)
1075
stack = [(from_inv, from_dir_id, u'', from_dir_abspath, children)]
1077
(inv, from_dir_id, from_dir_relpath, from_dir_abspath,
1078
children) = stack[-1]
1081
f = children.popleft()
1082
# TODO: If we find a subdirectory with its own .bzr
1083
# directory, then that is a separate tree and we
1084
# should exclude it.
1086
# the bzrdir for this tree
1087
if transport_base_dir == f:
1090
# we know that from_dir_relpath and from_dir_abspath never
1091
# end in a slash and 'f' doesn't begin with one, we can do
1092
# a string op, rather than the checks of pathjoin(), all
1093
# relative paths will have an extra slash at the beginning
1094
fp = from_dir_relpath + '/' + f
1097
fap = from_dir_abspath + '/' + f
1099
dir_ie = inv.get_entry(from_dir_id)
1100
if dir_ie.kind == 'directory':
1101
f_ie = dir_ie.children.get(f)
1106
elif self.is_ignored(fp[1:]):
1109
# we may not have found this file, because of a unicode
1110
# issue, or because the directory was actually a
1112
f_norm, can_access = osutils.normalized_filename(f)
1113
if f == f_norm or not can_access:
1114
# No change, so treat this file normally
1117
# this file can be accessed by a normalized path
1118
# check again if it is versioned
1119
# these lines are repeated here for performance
1121
fp = from_dir_relpath + '/' + f
1122
fap = from_dir_abspath + '/' + f
1123
f_ie = inv.get_child(from_dir_id, f)
1126
elif self.is_ignored(fp[1:]):
1131
fk = osutils.file_kind(fap)
1132
if fk == 'directory' and self._directory_is_tree_reference(f):
1133
if not recurse_nested:
1134
fk = 'tree-reference'
1136
subtree = self.get_nested_tree(f)
1137
exit_stack.enter_context(subtree.lock_read())
1138
inv = subtree.root_inventory
1139
f_ie = inv.get_entry(f_ie.file_id)
1142
# make a last minute entry
1144
yield fp[1:], c, fk, f_ie
1147
yield fp[1:], c, fk, fk_entries[fk]()
1149
yield fp[1:], c, fk, TreeEntry()
1152
if fk != 'directory':
1155
# But do this child first if recursing down
1157
new_children = sorted(os.listdir(fap))
1158
new_children = deque(new_children)
1159
stack.append((inv, f_ie.file_id, fp, fap, new_children))
1160
# Break out of inner loop,
1161
# so that we start outer loop with child
1164
# if we finished all children, pop it off the stack
1167
def move(self, from_paths, to_dir=None, after=False):
1170
to_dir must exist in the inventory.
1172
If to_dir exists and is a directory, the files are moved into
1173
it, keeping their old names.
1175
Note that to_dir is only the last component of the new name;
1176
this doesn't change the directory.
1178
For each entry in from_paths the move mode will be determined
1181
The first mode moves the file in the filesystem and updates the
1182
inventory. The second mode only updates the inventory without
1183
touching the file on the filesystem.
1185
move uses the second mode if 'after == True' and the target is
1186
either not versioned or newly added, and present in the working tree.
1188
move uses the second mode if 'after == False' and the source is
1189
versioned but no longer in the working tree, and the target is not
1190
versioned but present in the working tree.
1192
move uses the first mode if 'after == False' and the source is
1193
versioned and present in the working tree, and the target is not
1194
versioned and not present in the working tree.
1196
Everything else results in an error.
1198
This returns a list of (from_path, to_path) pairs for each
1199
entry that is moved.
1204
# check for deprecated use of signature
1206
raise TypeError('You must supply a target directory')
1207
# check destination directory
1208
if isinstance(from_paths, str):
1210
with self.lock_tree_write():
1211
to_abs = self.abspath(to_dir)
1212
if not osutils.isdir(to_abs):
1213
raise errors.BzrMoveFailedError(
1214
'', to_dir, errors.NotADirectory(to_abs))
1215
if not self.has_filename(to_dir):
1216
raise errors.BzrMoveFailedError(
1217
'', to_dir, errors.NotInWorkingDirectory(to_dir))
1218
to_inv, to_dir_id = self._path2inv_file_id(to_dir)
1219
if to_dir_id is None:
1220
raise errors.BzrMoveFailedError(
1221
'', to_dir, errors.NotVersionedError(path=to_dir))
1223
to_dir_ie = to_inv.get_entry(to_dir_id)
1224
if to_dir_ie.kind != 'directory':
1225
raise errors.BzrMoveFailedError(
1226
'', to_dir, errors.NotADirectory(to_abs))
1228
# create rename entries and tuples
1229
for from_rel in from_paths:
1230
from_tail = osutils.splitpath(from_rel)[-1]
1231
from_inv, from_id = self._path2inv_file_id(from_rel)
1233
raise errors.BzrMoveFailedError(from_rel, to_dir,
1234
errors.NotVersionedError(path=from_rel))
1236
from_entry = from_inv.get_entry(from_id)
1237
from_parent_id = from_entry.parent_id
1238
to_rel = osutils.pathjoin(to_dir, from_tail)
1239
rename_entry = InventoryWorkingTree._RenameEntry(
1242
from_tail=from_tail,
1243
from_parent_id=from_parent_id,
1244
to_rel=to_rel, to_tail=from_tail,
1245
to_parent_id=to_dir_id)
1246
rename_entries.append(rename_entry)
1247
rename_tuples.append((from_rel, to_rel))
1249
# determine which move mode to use. checks also for movability
1250
rename_entries = self._determine_mv_mode(rename_entries, after)
1252
original_modified = self._inventory_is_modified
1255
self._inventory_is_modified = True
1256
self._move(rename_entries)
1257
except BaseException:
1258
# restore the inventory on error
1259
self._inventory_is_modified = original_modified
1261
# TODO(jelmer): what about the from_invs?
1262
self._write_inventory(to_inv)
1263
return rename_tuples
1265
def rename_one(self, from_rel, to_rel, after=False):
1268
This can change the directory or the filename or both.
1270
rename_one has several 'modes' to work. First, it can rename a physical
1271
file and change the file_id. That is the normal mode. Second, it can
1272
only change the file_id without touching any physical file.
1274
rename_one uses the second mode if 'after == True' and 'to_rel' is not
1275
versioned but present in the working tree.
1277
rename_one uses the second mode if 'after == False' and 'from_rel' is
1278
versioned but no longer in the working tree, and 'to_rel' is not
1279
versioned but present in the working tree.
1281
rename_one uses the first mode if 'after == False' and 'from_rel' is
1282
versioned and present in the working tree, and 'to_rel' is not
1283
versioned and not present in the working tree.
1285
Everything else results in an error.
1287
with self.lock_tree_write():
1290
# create rename entries and tuples
1291
from_tail = osutils.splitpath(from_rel)[-1]
1292
from_inv, from_id = self._path2inv_file_id(from_rel)
1294
# if file is missing in the inventory maybe it's in the
1296
# TODO(jelmer): This is going to break with nested trees.
1297
from_inv = self.root_inventory
1298
basis_tree = self.branch.basis_tree()
1299
basis_from_inv, from_id = basis_tree._path2inv_file_id(from_rel)
1301
raise errors.BzrRenameFailedError(
1303
errors.NotVersionedError(path=from_rel))
1305
from_entry = from_inv.get_entry(from_id)
1306
except errors.NoSuchId:
1307
# put entry back in the inventory so we can rename it
1308
from_entry = basis_from_inv.get_entry(from_id).copy()
1309
from_inv.add(from_entry)
1311
from_inv, from_inv_id = self._unpack_file_id(from_id)
1312
from_entry = from_inv.get_entry(from_inv_id)
1313
from_parent_id = from_entry.parent_id
1314
to_dir, to_tail = os.path.split(to_rel)
1315
to_inv, to_dir_id = self._path2inv_file_id(to_dir)
1316
rename_entry = InventoryWorkingTree._RenameEntry(
1319
from_tail=from_tail,
1320
from_parent_id=from_parent_id,
1321
to_rel=to_rel, to_tail=to_tail,
1322
to_parent_id=to_dir_id)
1323
rename_entries.append(rename_entry)
1325
# determine which move mode to use. checks also for movability
1326
rename_entries = self._determine_mv_mode(rename_entries, after)
1328
# check if the target changed directory and if the target directory
1330
if to_dir_id is None:
1331
raise errors.BzrMoveFailedError(
1332
from_rel, to_rel, errors.NotVersionedError(path=to_dir))
1334
# all checks done. now we can continue with our actual work
1335
mutter('rename_one:\n'
1340
' to_dir_id {%s}\n',
1341
from_id, from_rel, to_rel, to_dir, to_dir_id)
1343
self._move(rename_entries)
1344
self._write_inventory(to_inv)
1346
class _RenameEntry(object):
1347
def __init__(self, from_rel, from_id, from_tail, from_parent_id,
1348
to_rel, to_tail, to_parent_id, only_change_inv=False,
1350
self.from_rel = from_rel
1351
self.from_id = from_id
1352
self.from_tail = from_tail
1353
self.from_parent_id = from_parent_id
1354
self.to_rel = to_rel
1355
self.to_tail = to_tail
1356
self.to_parent_id = to_parent_id
1357
self.change_id = change_id
1358
self.only_change_inv = only_change_inv
1360
def _determine_mv_mode(self, rename_entries, after=False):
1361
"""Determines for each from-to pair if both inventory and working tree
1362
or only the inventory has to be changed.
1364
Also does basic plausability tests.
1366
# FIXME: Handling of nested trees
1367
inv = self.root_inventory
1369
for rename_entry in rename_entries:
1370
# store to local variables for easier reference
1371
from_rel = rename_entry.from_rel
1372
from_id = rename_entry.from_id
1373
to_rel = rename_entry.to_rel
1374
to_id = inv.path2id(to_rel)
1375
only_change_inv = False
1377
# check the inventory for source and destination
1379
raise errors.BzrMoveFailedError(
1380
from_rel, to_rel, errors.NotVersionedError(path=from_rel))
1381
if to_id is not None:
1383
# allow it with --after but only if dest is newly added
1385
basis = self.basis_tree()
1386
with basis.lock_read():
1388
basis.id2path(to_id)
1389
except errors.NoSuchId:
1390
rename_entry.change_id = True
1393
raise errors.BzrMoveFailedError(
1395
errors.AlreadyVersionedError(path=to_rel))
1397
# try to determine the mode for rename (only change inv or change
1398
# inv and file system)
1400
if not self.has_filename(to_rel):
1401
raise errors.BzrMoveFailedError(
1405
extra="New file has not been created yet"))
1406
only_change_inv = True
1407
elif not self.has_filename(from_rel) and self.has_filename(to_rel):
1408
only_change_inv = True
1409
elif self.has_filename(from_rel) and not self.has_filename(to_rel):
1410
only_change_inv = False
1411
elif (not self.case_sensitive and
1412
from_rel.lower() == to_rel.lower() and
1413
self.has_filename(from_rel)):
1414
only_change_inv = False
1416
# something is wrong, so lets determine what exactly
1417
if not self.has_filename(from_rel) and \
1418
not self.has_filename(to_rel):
1419
raise errors.BzrRenameFailedError(
1421
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
1423
raise errors.RenameFailedFilesExist(from_rel, to_rel)
1424
rename_entry.only_change_inv = only_change_inv
1425
return rename_entries
1427
def _move(self, rename_entries):
1428
"""Moves a list of files.
1430
Depending on the value of the flag 'only_change_inv', the
1431
file will be moved on the file system or not.
1435
for entry in rename_entries:
1437
self._move_entry(entry)
1438
except BaseException:
1439
self._rollback_move(moved)
1443
def _rollback_move(self, moved):
1444
"""Try to rollback a previous move in case of an filesystem error."""
1447
self._move_entry(WorkingTree._RenameEntry(
1448
entry.to_rel, entry.from_id,
1449
entry.to_tail, entry.to_parent_id, entry.from_rel,
1450
entry.from_tail, entry.from_parent_id,
1451
entry.only_change_inv))
1452
except errors.BzrMoveFailedError as e:
1453
raise errors.BzrMoveFailedError(
1454
'', '', "Rollback failed."
1455
" The working tree is in an inconsistent state."
1456
" Please consider doing a 'bzr revert'."
1457
" Error message is: %s" % e)
1459
def _move_entry(self, entry):
1460
inv = self.root_inventory
1461
from_rel_abs = self.abspath(entry.from_rel)
1462
to_rel_abs = self.abspath(entry.to_rel)
1463
if from_rel_abs == to_rel_abs:
1464
raise errors.BzrMoveFailedError(entry.from_rel, entry.to_rel,
1465
"Source and target are identical.")
1467
if not entry.only_change_inv:
1469
osutils.rename(from_rel_abs, to_rel_abs)
1470
except OSError as e:
1471
raise errors.BzrMoveFailedError(
1472
entry.from_rel, entry.to_rel, e[1])
1474
to_id = inv.path2id(entry.to_rel)
1475
inv.remove_recursive_id(to_id)
1476
inv.rename(entry.from_id, entry.to_parent_id, entry.to_tail)
1478
def unversion(self, paths):
1479
"""Remove the paths in paths from the current versioned set.
1481
When a path is unversioned, all of its children are automatically
1484
:param paths: The paths to stop versioning.
1485
:raises NoSuchFile: if any path is not currently versioned.
1487
with self.lock_tree_write():
1490
file_id = self._inventory.path2id(path)
1492
raise errors.NoSuchFile(path, self)
1493
file_ids.add(file_id)
1494
for file_id in file_ids:
1495
if self._inventory.has_id(file_id):
1496
self._inventory.remove_recursive_id(file_id)
1498
# in the future this should just set a dirty bit to wait for
1499
# the final unlock. However, until all methods of workingtree
1500
# start with the current in -memory inventory rather than
1501
# triggering a read, it is more complex - we need to teach
1502
# read_inventory to know when to read, and when to not read
1503
# first... and possibly to save first when the in memory one
1504
# may be corrupted. so for now, we just only write it if it is
1505
# indeed dirty. - RBC 20060907
1506
self._write_inventory(self._inventory)
1508
def stored_kind(self, path):
1509
"""See Tree.stored_kind"""
1510
return self._path2ie(path).kind
1513
"""Yield all unversioned files in this WorkingTree.
1515
If there are any unversioned directories then only the directory is
1516
returned, not all its children. But if there are unversioned files
1517
under a versioned subdirectory, they are returned.
1519
Currently returned depth-first, sorted by name within directories.
1520
This is the same order used by 'osutils.walkdirs'.
1522
# TODO: Work from given directory downwards
1523
for path, dir_entry in self.iter_entries_by_dir():
1524
if dir_entry.kind != 'directory':
1526
# mutter("search for unknowns in %r", path)
1527
dirabs = self.abspath(path)
1528
if not osutils.isdir(dirabs):
1529
# e.g. directory deleted
1533
for subf in os.listdir(dirabs.encode(osutils._fs_enc)):
1535
subf = subf.decode(osutils._fs_enc)
1536
except UnicodeDecodeError:
1537
path_os_enc = path.encode(osutils._fs_enc)
1538
relpath = path_os_enc + b'/' + subf
1539
raise errors.BadFilenameEncoding(relpath,
1542
if self.controldir.is_control_filename(subf):
1544
if subf not in dir_entry.children:
1547
can_access) = osutils.normalized_filename(subf)
1548
except UnicodeDecodeError:
1549
path_os_enc = path.encode(osutils._fs_enc)
1550
relpath = path_os_enc + '/' + subf
1551
raise errors.BadFilenameEncoding(relpath,
1553
if subf_norm != subf and can_access:
1554
if subf_norm not in dir_entry.children:
1555
fl.append(subf_norm)
1561
subp = osutils.pathjoin(path, subf)
1564
def walkdirs(self, prefix=""):
1565
"""Walk the directories of this tree.
1567
returns a generator which yields items in the form:
1568
(current_directory_path,
1569
[(file1_path, file1_name, file1_kind, (lstat),
1572
This API returns a generator, which is only valid during the current
1573
tree transaction - within a single lock_read or lock_write duration.
1575
If the tree is not locked, it may cause an error to be raised,
1576
depending on the tree implementation.
1578
disk_top = self.abspath(prefix)
1579
if disk_top.endswith('/'):
1580
disk_top = disk_top[:-1]
1581
top_strip_len = len(disk_top) + 1
1582
inventory_iterator = self._walkdirs(prefix)
1583
disk_iterator = osutils.walkdirs(disk_top, prefix)
1585
current_disk = next(disk_iterator)
1586
disk_finished = False
1587
except OSError as e:
1588
if not (e.errno == errno.ENOENT
1589
or (sys.platform == 'win32' and e.errno == ERROR_PATH_NOT_FOUND)):
1592
disk_finished = True
1594
current_inv = next(inventory_iterator)
1595
inv_finished = False
1596
except StopIteration:
1599
while not inv_finished or not disk_finished:
1601
((cur_disk_dir_relpath, cur_disk_dir_path_from_top),
1602
cur_disk_dir_content) = current_disk
1604
((cur_disk_dir_relpath, cur_disk_dir_path_from_top),
1605
cur_disk_dir_content) = ((None, None), None)
1606
if not disk_finished:
1607
# strip out .bzr dirs
1608
if (cur_disk_dir_path_from_top[top_strip_len:] == ''
1609
and len(cur_disk_dir_content) > 0):
1610
# osutils.walkdirs can be made nicer -
1611
# yield the path-from-prefix rather than the pathjoined
1613
bzrdir_loc = bisect_left(cur_disk_dir_content,
1615
if (bzrdir_loc < len(cur_disk_dir_content) and
1616
self.controldir.is_control_filename(
1617
cur_disk_dir_content[bzrdir_loc][0])):
1618
# we dont yield the contents of, or, .bzr itself.
1619
del cur_disk_dir_content[bzrdir_loc]
1621
# everything is unknown
1624
# everything is missing
1627
direction = ((current_inv[0][0] > cur_disk_dir_relpath)
1628
- (current_inv[0][0] < cur_disk_dir_relpath))
1631
# disk is before inventory - unknown
1632
dirblock = [(relpath, basename, kind, stat, None) for
1633
relpath, basename, kind, stat, top_path in
1634
cur_disk_dir_content]
1635
yield cur_disk_dir_relpath, dirblock
1637
current_disk = next(disk_iterator)
1638
except StopIteration:
1639
disk_finished = True
1641
# inventory is before disk - missing.
1642
dirblock = [(relpath, basename, 'unknown', None, kind)
1643
for relpath, basename, dkind, stat, fileid, kind in
1645
yield current_inv[0][0], dirblock
1647
current_inv = next(inventory_iterator)
1648
except StopIteration:
1651
# versioned present directory
1652
# merge the inventory and disk data together
1654
for relpath, subiterator in itertools.groupby(sorted(
1655
current_inv[1] + cur_disk_dir_content,
1656
key=operator.itemgetter(0)), operator.itemgetter(1)):
1657
path_elements = list(subiterator)
1658
if len(path_elements) == 2:
1659
inv_row, disk_row = path_elements
1660
# versioned, present file
1661
dirblock.append((inv_row[0],
1662
inv_row[1], disk_row[2],
1663
disk_row[3], inv_row[5]))
1664
elif len(path_elements[0]) == 5:
1667
(path_elements[0][0], path_elements[0][1],
1668
path_elements[0][2], path_elements[0][3], None))
1669
elif len(path_elements[0]) == 6:
1670
# versioned, absent file.
1672
(path_elements[0][0], path_elements[0][1],
1673
'unknown', None, path_elements[0][5]))
1675
raise NotImplementedError('unreachable code')
1676
yield current_inv[0][0], dirblock
1678
current_inv = next(inventory_iterator)
1679
except StopIteration:
1682
current_disk = next(disk_iterator)
1683
except StopIteration:
1684
disk_finished = True
1686
def _walkdirs(self, prefix=""):
1687
"""Walk the directories of this tree.
1689
:param prefix: is used as the directrory to start with.
1690
:returns: a generator which yields items in the form::
1692
((curren_directory_path, fileid),
1693
[(file1_path, file1_name, file1_kind, None, file1_id,
1696
_directory = 'directory'
1697
# get the root in the inventory
1698
inv, top_id = self._path2inv_file_id(prefix)
1702
pending = [(prefix, '', _directory, None, top_id, None)]
1705
currentdir = pending.pop()
1706
# 0 - relpath, 1- basename, 2- kind, 3- stat, 4-id, 5-kind
1707
top_id = currentdir[4]
1709
relroot = currentdir[0] + '/'
1712
# FIXME: stash the node in pending
1713
entry = inv.get_entry(top_id)
1714
if entry.kind == 'directory':
1715
for name, child in entry.sorted_children():
1716
dirblock.append((relroot + name, name, child.kind, None,
1717
child.file_id, child.kind
1719
yield (currentdir[0], entry.file_id), dirblock
1720
# push the user specified dirs from dirblock
1721
for dir in reversed(dirblock):
1722
if dir[2] == _directory:
1725
def update_feature_flags(self, updated_flags):
1726
"""Update the feature flags for this branch.
1728
:param updated_flags: Dictionary mapping feature names to necessities
1729
A necessity can be None to indicate the feature should be removed
1731
with self.lock_write():
1732
self._format._update_feature_flags(updated_flags)
1733
self.control_transport.put_bytes(
1734
'format', self._format.as_string())
1736
def _check_for_tree_references(self, iterator, recurse_nested, specific_files=None):
1737
"""See if directories have become tree-references."""
1738
blocked_parent_ids = set()
1739
for path, ie in iterator:
1740
if ie.parent_id in blocked_parent_ids:
1741
# This entry was pruned because one of its parents became a
1742
# TreeReference. If this is a directory, mark it as blocked.
1743
if ie.kind == 'directory':
1744
blocked_parent_ids.add(ie.file_id)
1746
if (ie.kind == 'directory' and
1747
self._directory_is_tree_reference(path)):
1749
# This InventoryDirectory needs to be a TreeReference
1750
ie = inventory.TreeReference(ie.file_id, ie.name, ie.parent_id)
1751
blocked_parent_ids.add(ie.file_id)
1753
if ie.kind == 'tree-reference' and recurse_nested:
1754
subtree = self.get_nested_tree(path)
1755
for subpath, ie in subtree.iter_entries_by_dir(
1756
recurse_nested=recurse_nested,
1757
specific_files=specific_files):
1759
full_subpath = osutils.pathjoin(path, subpath)
1762
yield full_subpath, ie
1766
def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
1767
"""See Tree.iter_entries_by_dir()"""
1768
# The only trick here is that if we supports_tree_reference then we
1769
# need to detect if a directory becomes a tree-reference.
1770
iterator = super(WorkingTree, self).iter_entries_by_dir(
1771
specific_files=specific_files)
1772
if not self.supports_tree_reference():
1775
return self._check_for_tree_references(
1776
iterator, recurse_nested=recurse_nested,
1777
specific_files=specific_files)
1779
def get_canonical_paths(self, paths):
1780
"""Look up canonical paths for multiple items.
1782
:param paths: A sequence of paths relative to the root of the tree.
1783
:return: A iterator over paths, with each item the corresponding input
1784
path adjusted to account for existing elements that match case
1787
with self.lock_read():
1788
if not self.case_sensitive:
1791
elif sys.platform == 'darwin':
1795
return unicodedata.normalize('NFC', x)
1799
if normalize is None or self.is_versioned(path):
1802
yield get_canonical_path(self, path, normalize)
1804
def get_reference_info(self, path, branch=None):
1805
file_id = self.path2id(path)
1808
return self.branch.get_reference_info(file_id)[0]
1810
def set_reference_info(self, tree_path, branch_location):
1811
file_id = self.path2id(tree_path)
1813
raise errors.NoSuchFile(tree_path)
1814
self.branch.set_reference_info(file_id, branch_location, tree_path)
1816
def reference_parent(self, path, branch=None, possible_transports=None):
1817
return self.branch.reference_parent(
1819
path, possible_transports=possible_transports)
1821
def has_changes(self, _from_tree=None):
1822
"""Quickly check that the tree contains at least one commitable change.
1824
:param _from_tree: tree to compare against to find changes (default to
1825
the basis tree and is intended to be used by tests).
1827
:return: True if a change is found. False otherwise
1829
with self.lock_read():
1830
# Check pending merges
1831
if len(self.get_parent_ids()) > 1:
1833
if _from_tree is None:
1834
_from_tree = self.basis_tree()
1835
changes = self.iter_changes(_from_tree)
1836
if self.supports_symlinks():
1837
# Fast path for has_changes.
1839
change = next(changes)
1840
# Exclude root (talk about black magic... --vila 20090629)
1841
if change.parent_id == (None, None):
1842
change = next(changes)
1844
except StopIteration:
1848
# Slow path for has_changes.
1849
# Handle platforms that do not support symlinks in the
1850
# conditional below. This is slower than the try/except
1851
# approach below that but we don't have a choice as we
1852
# need to be sure that all symlinks are removed from the
1853
# entire changeset. This is because in platforms that
1854
# do not support symlinks, they show up as None in the
1855
# working copy as compared to the repository.
1856
# Also, exclude root as mention in the above fast path.
1858
lambda c: c[6][0] != 'symlink' and c[4] != (None, None),
1862
except StopIteration:
1868
def update(self, change_reporter=None, possible_transports=None,
1869
revision=None, old_tip=_marker, show_base=False):
1870
"""Update a working tree along its branch.
1872
This will update the branch if its bound too, which means we have
1873
multiple trees involved:
1875
- The new basis tree of the master.
1876
- The old basis tree of the branch.
1877
- The old basis tree of the working tree.
1878
- The current working tree state.
1880
Pathologically, all three may be different, and non-ancestors of each
1881
other. Conceptually we want to:
1883
- Preserve the wt.basis->wt.state changes
1884
- Transform the wt.basis to the new master basis.
1885
- Apply a merge of the old branch basis to get any 'local' changes from
1887
- Restore the wt.basis->wt.state changes.
1889
There isn't a single operation at the moment to do that, so we:
1891
- Merge current state -> basis tree of the master w.r.t. the old tree
1893
- Do a 'normal' merge of the old branch basis if it is relevant.
1895
:param revision: The target revision to update to. Must be in the
1897
:param old_tip: If branch.update() has already been run, the value it
1898
returned (old tip of the branch or None). _marker is used
1901
if self.branch.get_bound_location() is not None:
1903
update_branch = (old_tip is self._marker)
1905
self.lock_tree_write()
1906
update_branch = False
1909
old_tip = self.branch.update(possible_transports)
1911
if old_tip is self._marker:
1913
return self._update_tree(old_tip, change_reporter, revision, show_base)
1917
def _update_tree(self, old_tip=None, change_reporter=None, revision=None,
1919
"""Update a tree to the master branch.
1921
:param old_tip: if supplied, the previous tip revision the branch,
1922
before it was changed to the master branch's tip.
1924
# here if old_tip is not None, it is the old tip of the branch before
1925
# it was updated from the master branch. This should become a pending
1926
# merge in the working tree to preserve the user existing work. we
1927
# cant set that until we update the working trees last revision to be
1928
# one from the new branch, because it will just get absorbed by the
1929
# parent de-duplication logic.
1931
# We MUST save it even if an error occurs, because otherwise the users
1932
# local work is unreferenced and will appear to have been lost.
1934
with self.lock_tree_write():
1937
last_rev = self.get_parent_ids()[0]
1939
last_rev = _mod_revision.NULL_REVISION
1940
if revision is None:
1941
revision = self.branch.last_revision()
1943
old_tip = old_tip or _mod_revision.NULL_REVISION
1945
if not _mod_revision.is_null(old_tip) and old_tip != last_rev:
1946
# the branch we are bound to was updated
1947
# merge those changes in first
1948
base_tree = self.basis_tree()
1949
other_tree = self.branch.repository.revision_tree(old_tip)
1950
nb_conflicts = merge.merge_inner(self.branch, other_tree,
1951
base_tree, this_tree=self,
1952
change_reporter=change_reporter,
1953
show_base=show_base)
1955
self.add_parent_tree((old_tip, other_tree))
1958
if last_rev != _mod_revision.ensure_null(revision):
1959
# the working tree is up to date with the branch
1960
# we can merge the specified revision from master
1961
to_tree = self.branch.repository.revision_tree(revision)
1962
to_root_id = to_tree.path2id('')
1964
basis = self.basis_tree()
1965
with basis.lock_read():
1966
if (basis.path2id('') is None or basis.path2id('') != to_root_id):
1967
self.set_root_id(to_root_id)
1970
# determine the branch point
1971
graph = self.branch.repository.get_graph()
1972
base_rev_id = graph.find_unique_lca(self.branch.last_revision(),
1974
base_tree = self.branch.repository.revision_tree(base_rev_id)
1976
nb_conflicts = merge.merge_inner(self.branch, to_tree, base_tree,
1978
change_reporter=change_reporter,
1979
show_base=show_base)
1980
self.set_last_revision(revision)
1981
# TODO - dedup parents list with things merged by pull ?
1982
# reuse the tree we've updated to to set the basis:
1983
parent_trees = [(revision, to_tree)]
1984
merges = self.get_parent_ids()[1:]
1985
# Ideally we ask the tree for the trees here, that way the working
1986
# tree can decide whether to give us the entire tree or give us a
1987
# lazy initialised tree. dirstate for instance will have the trees
1988
# in ram already, whereas a last-revision + basis-inventory tree
1989
# will not, but also does not need them when setting parents.
1990
for parent in merges:
1991
parent_trees.append(
1992
(parent, self.branch.repository.revision_tree(parent)))
1993
if not _mod_revision.is_null(old_tip):
1994
parent_trees.append(
1995
(old_tip, self.branch.repository.revision_tree(old_tip)))
1996
self.set_parent_trees(parent_trees)
1997
last_rev = parent_trees[0][0]
2001
class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
2002
"""Base class for working trees that live in bzr meta directories."""
2004
ignore_filename = '.bzrignore'
2007
WorkingTreeFormat.__init__(self)
2008
bzrdir.BzrFormat.__init__(self)
2011
def find_format_string(klass, controldir):
2012
"""Return format name for the working tree object in controldir."""
2014
transport = controldir.get_workingtree_transport(None)
2015
return transport.get_bytes("format")
2016
except errors.NoSuchFile:
2017
raise errors.NoWorkingTree(base=transport.base)
2020
def find_format(klass, controldir):
2021
"""Return the format for the working tree object in controldir."""
2022
format_string = klass.find_format_string(controldir)
2023
return klass._find_format(format_registry, 'working tree',
2026
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
2028
WorkingTreeFormat.check_support_status(
2029
self, allow_unsupported=allow_unsupported,
2030
recommend_upgrade=recommend_upgrade, basedir=basedir)
2031
bzrdir.BzrFormat.check_support_status(
2032
self, allow_unsupported=allow_unsupported,
2033
recommend_upgrade=recommend_upgrade, basedir=basedir)