1# Copyright (C) 2007-2012 Canonical Ltd 2# 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. 7# 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. 12# 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 16 17"""WorkingTree4 format and implementation. 18 19WorkingTree4 provides the dirstate based working tree logic. 20 21To get a WorkingTree, call bzrdir.open_workingtree() or 22WorkingTree.open(dir). 23""" 24 25from io import BytesIO 26import os 27 28from ..lazy_import import lazy_import 29lazy_import(globals(), """ 30import contextlib 31import errno 32import stat 33 34from breezy import ( 35 branch as _mod_branch, 36 cache_utf8, 37 controldir, 38 debug, 39 filters as _mod_filters, 40 osutils, 41 revision as _mod_revision, 42 revisiontree, 43 trace, 44 views, 45 ) 46from breezy.bzr import ( 47 dirstate, 48 generate_ids, 49 transform as bzr_transform, 50 ) 51""") 52 53from .. import ( 54 errors, 55 ) 56from .inventory import Inventory, ROOT_ID, entry_factory 57from ..lock import LogicalLockResult 58from ..lockable_files import LockableFiles 59from ..lockdir import LockDir 60from .inventorytree import ( 61 InventoryTree, 62 InterInventoryTree, 63 InventoryRevisionTree, 64 ) 65from ..mutabletree import ( 66 BadReferenceTarget, 67 MutableTree, 68 ) 69from ..osutils import ( 70 file_kind, 71 isdir, 72 pathjoin, 73 realpath, 74 safe_unicode, 75 ) 76from ..transport import get_transport_from_path 77from ..transport.local import LocalTransport 78from ..tree import ( 79 FileTimestampUnavailable, 80 InterTree, 81 MissingNestedTree, 82 ) 83from ..workingtree import ( 84 WorkingTree, 85 ) 86from .workingtree import ( 87 InventoryWorkingTree, 88 WorkingTreeFormatMetaDir, 89 ) 90 91 92class DirStateWorkingTree(InventoryWorkingTree): 93 94 def __init__(self, basedir, 95 branch, 96 _control_files=None, 97 _format=None, 98 _controldir=None): 99 """Construct a WorkingTree for basedir. 100 101 If the branch is not supplied, it is opened automatically. 102 If the branch is supplied, it must be the branch for this basedir. 103 (branch.base is not cross checked, because for remote branches that 104 would be meaningless). 105 """ 106 self._format = _format 107 self.controldir = _controldir 108 basedir = safe_unicode(basedir) 109 trace.mutter("opening working tree %r", basedir) 110 self._branch = branch 111 self.basedir = realpath(basedir) 112 # if branch is at our basedir and is a format 6 or less 113 # assume all other formats have their own control files. 114 self._control_files = _control_files 115 self._transport = self._control_files._transport 116 self._dirty = None 117 # ------------- 118 # during a read or write lock these objects are set, and are 119 # None the rest of the time. 120 self._dirstate = None 121 self._inventory = None 122 # ------------- 123 self._setup_directory_is_tree_reference() 124 self._detect_case_handling() 125 self._rules_searcher = None 126 self.views = self._make_views() 127 # --- allow tests to select the dirstate iter_changes implementation 128 self._iter_changes = dirstate._process_entry 129 self._repo_supports_tree_reference = getattr( 130 self._branch.repository._format, "supports_tree_reference", 131 False) 132 133 def _add(self, files, ids, kinds): 134 """See MutableTree._add.""" 135 with self.lock_tree_write(): 136 state = self.current_dirstate() 137 for f, file_id, kind in zip(files, ids, kinds): 138 f = f.strip(u'/') 139 if self.path2id(f): 140 # special case tree root handling. 141 if f == b'' and self.path2id(f) == ROOT_ID: 142 state.set_path_id(b'', generate_ids.gen_file_id(f)) 143 continue 144 if file_id is None: 145 file_id = generate_ids.gen_file_id(f) 146 # deliberately add the file with no cached stat or sha1 147 # - on the first access it will be gathered, and we can 148 # always change this once tests are all passing. 149 state.add(f, file_id, kind, None, b'') 150 self._make_dirty(reset_inventory=True) 151 152 def _get_check_refs(self): 153 """Return the references needed to perform a check of this tree.""" 154 return [('trees', self.last_revision())] 155 156 def _make_dirty(self, reset_inventory): 157 """Make the tree state dirty. 158 159 :param reset_inventory: True if the cached inventory should be removed 160 (presuming there is one). 161 """ 162 self._dirty = True 163 if reset_inventory and self._inventory is not None: 164 self._inventory = None 165 166 def add_reference(self, sub_tree): 167 # use standard implementation, which calls back to self._add 168 # 169 # So we don't store the reference_revision in the working dirstate, 170 # it's just recorded at the moment of commit. 171 with self.lock_tree_write(): 172 try: 173 sub_tree_path = self.relpath(sub_tree.basedir) 174 except errors.PathNotChild: 175 raise BadReferenceTarget(self, sub_tree, 176 'Target not inside tree.') 177 sub_tree_id = sub_tree.path2id('') 178 if sub_tree_id == self.path2id(''): 179 raise BadReferenceTarget(self, sub_tree, 180 'Trees have the same root id.') 181 try: 182 self.id2path(sub_tree_id) 183 except errors.NoSuchId: 184 pass 185 else: 186 raise BadReferenceTarget( 187 self, sub_tree, 'Root id already present in tree') 188 self._add([sub_tree_path], [sub_tree_id], ['tree-reference']) 189 190 def break_lock(self): 191 """Break a lock if one is present from another instance. 192 193 Uses the ui factory to ask for confirmation if the lock may be from 194 an active process. 195 196 This will probe the repository for its lock as well. 197 """ 198 # if the dirstate is locked by an active process, reject the break lock 199 # call. 200 try: 201 if self._dirstate is None: 202 clear = True 203 else: 204 clear = False 205 state = self._current_dirstate() 206 if state._lock_token is not None: 207 # we already have it locked. sheese, cant break our own lock. 208 raise errors.LockActive(self.basedir) 209 else: 210 try: 211 # try for a write lock - need permission to get one anyhow 212 # to break locks. 213 state.lock_write() 214 except errors.LockContention: 215 # oslocks fail when a process is still live: fail. 216 # TODO: get the locked lockdir info and give to the user to 217 # assist in debugging. 218 raise errors.LockActive(self.basedir) 219 else: 220 state.unlock() 221 finally: 222 if clear: 223 self._dirstate = None 224 self._control_files.break_lock() 225 self.branch.break_lock() 226 227 def _comparison_data(self, entry, path): 228 kind, executable, stat_value = \ 229 WorkingTree._comparison_data(self, entry, path) 230 # it looks like a plain directory, but it's really a reference -- see 231 # also kind() 232 if (self._repo_supports_tree_reference and kind == 'directory' 233 and entry is not None and entry.kind == 'tree-reference'): 234 kind = 'tree-reference' 235 return kind, executable, stat_value 236 237 def commit(self, message=None, revprops=None, *args, **kwargs): 238 with self.lock_write(): 239 # mark the tree as dirty post commit - commit 240 # can change the current versioned list by doing deletes. 241 result = WorkingTree.commit(self, message, revprops, *args, 242 **kwargs) 243 self._make_dirty(reset_inventory=True) 244 return result 245 246 def current_dirstate(self): 247 """Return the current dirstate object. 248 249 This is not part of the tree interface and only exposed for ease of 250 testing. 251 252 :raises errors.NotWriteLocked: when not in a lock. 253 """ 254 self._must_be_locked() 255 return self._current_dirstate() 256 257 def _current_dirstate(self): 258 """Internal function that does not check lock status. 259 260 This is needed for break_lock which also needs the dirstate. 261 """ 262 if self._dirstate is not None: 263 return self._dirstate 264 local_path = self.controldir.get_workingtree_transport( 265 None).local_abspath('dirstate') 266 self._dirstate = dirstate.DirState.on_file( 267 local_path, self._sha1_provider(), self._worth_saving_limit(), 268 self._supports_executable()) 269 return self._dirstate 270 271 def _sha1_provider(self): 272 """A function that returns a SHA1Provider suitable for this tree. 273 274 :return: None if content filtering is not supported by this tree. 275 Otherwise, a SHA1Provider is returned that sha's the canonical 276 form of files, i.e. after read filters are applied. 277 """ 278 if self.supports_content_filtering(): 279 return ContentFilterAwareSHA1Provider(self) 280 else: 281 return None 282 283 def _worth_saving_limit(self): 284 """How many hash changes are ok before we must save the dirstate. 285 286 :return: an integer. -1 means never save. 287 """ 288 conf = self.get_config_stack() 289 return conf.get('bzr.workingtree.worth_saving_limit') 290 291 def filter_unversioned_files(self, paths): 292 """Filter out paths that are versioned. 293 294 :return: set of paths. 295 """ 296 # TODO: make a generic multi-bisect routine roughly that should list 297 # the paths, then process one half at a time recursively, and feed the 298 # results of each bisect in further still 299 paths = sorted(paths) 300 result = set() 301 state = self.current_dirstate() 302 # TODO we want a paths_to_dirblocks helper I think 303 for path in paths: 304 dirname, basename = os.path.split(path.encode('utf8')) 305 _, _, _, path_is_versioned = state._get_block_entry_index( 306 dirname, basename, 0) 307 if not path_is_versioned: 308 result.add(path) 309 return result 310 311 def flush(self): 312 """Write all cached data to disk.""" 313 if self._control_files._lock_mode != 'w': 314 raise errors.NotWriteLocked(self) 315 self.current_dirstate().save() 316 self._inventory = None 317 self._dirty = False 318 319 def _gather_kinds(self, files, kinds): 320 """See MutableTree._gather_kinds.""" 321 with self.lock_tree_write(): 322 for pos, f in enumerate(files): 323 if kinds[pos] is None: 324 kinds[pos] = self.kind(f) 325 326 def _generate_inventory(self): 327 """Create and set self.inventory from the dirstate object. 328 329 This is relatively expensive: we have to walk the entire dirstate. 330 Ideally we would not, and can deprecate this function. 331 """ 332 #: uncomment to trap on inventory requests. 333 # import pdb;pdb.set_trace() 334 state = self.current_dirstate() 335 state._read_dirblocks_if_needed() 336 root_key, current_entry = self._get_entry(path='') 337 current_id = root_key[2] 338 if not (current_entry[0][0] == b'd'): # directory 339 raise AssertionError(current_entry) 340 inv = Inventory(root_id=current_id) 341 # Turn some things into local variables 342 minikind_to_kind = dirstate.DirState._minikind_to_kind 343 factory = entry_factory 344 utf8_decode = cache_utf8._utf8_decode 345 inv_byid = inv._byid 346 # we could do this straight out of the dirstate; it might be fast 347 # and should be profiled - RBC 20070216 348 parent_ies = {b'': inv.root} 349 for block in state._dirblocks[1:]: # skip the root 350 dirname = block[0] 351 try: 352 parent_ie = parent_ies[dirname] 353 except KeyError: 354 # all the paths in this block are not versioned in this tree 355 continue 356 for key, entry in block[1]: 357 minikind, link_or_sha1, size, executable, stat = entry[0] 358 if minikind in (b'a', b'r'): # absent, relocated 359 # a parent tree only entry 360 continue 361 name = key[1] 362 name_unicode = utf8_decode(name)[0] 363 file_id = key[2] 364 kind = minikind_to_kind[minikind] 365 inv_entry = factory[kind](file_id, name_unicode, 366 parent_ie.file_id) 367 if kind == 'file': 368 # This is only needed on win32, where this is the only way 369 # we know the executable bit. 370 inv_entry.executable = executable 371 # not strictly needed: working tree 372 # inv_entry.text_size = size 373 # inv_entry.text_sha1 = sha1 374 elif kind == 'directory': 375 # add this entry to the parent map. 376 parent_ies[(dirname + b'/' + name).strip(b'/')] = inv_entry 377 elif kind == 'tree-reference': 378 inv_entry.reference_revision = link_or_sha1 or None 379 elif kind != 'symlink': 380 raise AssertionError("unknown kind %r" % kind) 381 # These checks cost us around 40ms on a 55k entry tree 382 if file_id in inv_byid: 383 raise AssertionError( 384 'file_id %s already in' 385 ' inventory as %s' % (file_id, inv_byid[file_id])) 386 if name_unicode in parent_ie.children: 387 raise AssertionError('name %r already in parent' 388 % (name_unicode,)) 389 inv_byid[file_id] = inv_entry 390 parent_ie.children[name_unicode] = inv_entry 391 self._inventory = inv 392 393 def _get_entry(self, file_id=None, path=None): 394 """Get the dirstate row for file_id or path. 395 396 If either file_id or path is supplied, it is used as the key to lookup. 397 If both are supplied, the fastest lookup is used, and an error is 398 raised if they do not both point at the same row. 399 400 :param file_id: An optional unicode file_id to be looked up. 401 :param path: An optional unicode path to be looked up. 402 :return: The dirstate row tuple for path/file_id, or (None, None) 403 """ 404 if file_id is None and path is None: 405 raise errors.BzrError('must supply file_id or path') 406 state = self.current_dirstate() 407 if path is not None: 408 path = path.encode('utf8') 409 return state._get_entry(0, fileid_utf8=file_id, path_utf8=path) 410 411 def get_file_sha1(self, path, stat_value=None): 412 # check file id is valid unconditionally. 413 entry = self._get_entry(path=path) 414 if entry[0] is None: 415 raise errors.NoSuchFile(self, path) 416 if path is None: 417 path = pathjoin(entry[0][0], entry[0][1]).decode('utf8') 418 419 file_abspath = self.abspath(path) 420 state = self.current_dirstate() 421 if stat_value is None: 422 try: 423 stat_value = osutils.lstat(file_abspath) 424 except OSError as e: 425 if e.errno == errno.ENOENT: 426 return None 427 else: 428 raise 429 link_or_sha1 = dirstate.update_entry(state, entry, file_abspath, 430 stat_value=stat_value) 431 if entry[1][0][0] == b'f': 432 if link_or_sha1 is None: 433 file_obj, statvalue = self.get_file_with_stat(path) 434 try: 435 sha1 = osutils.sha_file(file_obj) 436 finally: 437 file_obj.close() 438 self._observed_sha1(path, (sha1, statvalue)) 439 return sha1 440 else: 441 return link_or_sha1 442 return None 443 444 def _get_root_inventory(self): 445 """Get the inventory for the tree. This is only valid within a lock.""" 446 if 'evil' in debug.debug_flags: 447 trace.mutter_callsite( 448 2, "accessing .inventory forces a size of tree translation.") 449 if self._inventory is not None: 450 return self._inventory 451 self._must_be_locked() 452 self._generate_inventory() 453 return self._inventory 454 455 root_inventory = property(_get_root_inventory, 456 "Root inventory of this tree") 457 458 def get_parent_ids(self): 459 """See Tree.get_parent_ids. 460 461 This implementation requests the ids list from the dirstate file. 462 """ 463 with self.lock_read(): 464 return self.current_dirstate().get_parent_ids() 465 466 def get_reference_revision(self, path): 467 # referenced tree's revision is whatever's currently there 468 try: 469 return self.get_nested_tree(path).last_revision() 470 except errors.NotBranchError: 471 entry = self._get_entry(path=path) 472 if entry == (None, None): 473 return False 474 return entry[1][0][1] 475 476 def get_nested_tree(self, path): 477 return WorkingTree.open(self.abspath(path)) 478 479 def id2path(self, file_id, recurse='down'): 480 "Convert a file-id to a path." 481 with self.lock_read(): 482 state = self.current_dirstate() 483 entry = self._get_entry(file_id=file_id) 484 if entry == (None, None): 485 if recurse == 'down': 486 if 'evil' in debug.debug_flags: 487 trace.mutter_callsite( 488 2, "Tree.id2path scans all nested trees.") 489 for nested_path in self.iter_references(): 490 nested_tree = self.get_nested_tree(nested_path) 491 try: 492 return osutils.pathjoin( 493 nested_path, nested_tree.id2path(file_id)) 494 except errors.NoSuchId: 495 pass 496 raise errors.NoSuchId(tree=self, file_id=file_id) 497 path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1]) 498 return path_utf8.decode('utf8') 499 500 def _is_executable_from_path_and_stat_from_basis(self, path, stat_result): 501 entry = self._get_entry(path=path) 502 if entry == (None, None): 503 return False # Missing entries are not executable 504 return entry[1][0][3] # Executable? 505 506 def is_executable(self, path): 507 """Test if a file is executable or not. 508 509 Note: The caller is expected to take a read-lock before calling this. 510 """ 511 if not self._supports_executable(): 512 entry = self._get_entry(path=path) 513 if entry == (None, None): 514 return False 515 return entry[1][0][3] 516 else: 517 self._must_be_locked() 518 mode = osutils.lstat(self.abspath(path)).st_mode 519 return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode) 520 521 def all_file_ids(self): 522 """See Tree.iter_all_file_ids""" 523 self._must_be_locked() 524 result = set() 525 for key, tree_details in self.current_dirstate()._iter_entries(): 526 if tree_details[0][0] in (b'a', b'r'): # relocated 527 continue 528 result.add(key[2]) 529 return result 530 531 def all_versioned_paths(self): 532 self._must_be_locked() 533 return {path for path, entry in 534 self.root_inventory.iter_entries(recursive=True)} 535 536 def __iter__(self): 537 """Iterate through file_ids for this tree. 538 539 file_ids are in a WorkingTree if they are in the working inventory 540 and the working file exists. 541 """ 542 with self.lock_read(): 543 result = [] 544 for key, tree_details in self.current_dirstate()._iter_entries(): 545 if tree_details[0][0] in (b'a', b'r'): # absent, relocated 546 # not relevant to the working tree 547 continue 548 path = pathjoin(self.basedir, key[0].decode( 549 'utf8'), key[1].decode('utf8')) 550 if osutils.lexists(path): 551 result.append(key[2]) 552 return iter(result) 553 554 def iter_references(self): 555 if not self._repo_supports_tree_reference: 556 # When the repo doesn't support references, we will have nothing to 557 # return 558 return 559 with self.lock_read(): 560 for key, tree_details in self.current_dirstate()._iter_entries(): 561 if tree_details[0][0] in (b'a', b'r'): # absent, relocated 562 # not relevant to the working tree 563 continue 564 if not key[1]: 565 # the root is not a reference. 566 continue 567 relpath = pathjoin(key[0].decode('utf8'), key[1].decode('utf8')) 568 try: 569 if self.kind(relpath) == 'tree-reference': 570 yield relpath 571 except errors.NoSuchFile: 572 # path is missing on disk. 573 continue 574 575 def _observed_sha1(self, path, sha_and_stat): 576 """See MutableTree._observed_sha1.""" 577 state = self.current_dirstate() 578 entry = self._get_entry(path=path) 579 state._observed_sha1(entry, *sha_and_stat) 580 581 def kind(self, relpath): 582 abspath = self.abspath(relpath) 583 kind = file_kind(abspath) 584 if (self._repo_supports_tree_reference and kind == 'directory'): 585 with self.lock_read(): 586 entry = self._get_entry(path=relpath) 587 if entry[1] is not None: 588 if entry[1][0][0] == b't': 589 kind = 'tree-reference' 590 return kind 591 592 def _last_revision(self): 593 """See Mutable.last_revision.""" 594 with self.lock_read(): 595 parent_ids = self.current_dirstate().get_parent_ids() 596 if parent_ids: 597 return parent_ids[0] 598 else: 599 return _mod_revision.NULL_REVISION 600 601 def lock_read(self): 602 """See Branch.lock_read, and WorkingTree.unlock. 603 604 :return: A breezy.lock.LogicalLockResult. 605 """ 606 self.branch.lock_read() 607 try: 608 self._control_files.lock_read() 609 try: 610 state = self.current_dirstate() 611 if not state._lock_token: 612 state.lock_read() 613 # set our support for tree references from the repository in 614 # use. 615 self._repo_supports_tree_reference = getattr( 616 self.branch.repository._format, "supports_tree_reference", 617 False) 618 except BaseException: 619 self._control_files.unlock() 620 raise 621 except BaseException: 622 self.branch.unlock() 623 raise 624 return LogicalLockResult(self.unlock) 625 626 def _lock_self_write(self): 627 """This should be called after the branch is locked.""" 628 try: 629 self._control_files.lock_write() 630 try: 631 state = self.current_dirstate() 632 if not state._lock_token: 633 state.lock_write() 634 # set our support for tree references from the repository in 635 # use. 636 self._repo_supports_tree_reference = getattr( 637 self.branch.repository._format, "supports_tree_reference", 638 False) 639 except BaseException: 640 self._control_files.unlock() 641 raise 642 except BaseException: 643 self.branch.unlock() 644 raise 645 return LogicalLockResult(self.unlock) 646 647 def lock_tree_write(self): 648 """See MutableTree.lock_tree_write, and WorkingTree.unlock. 649 650 :return: A breezy.lock.LogicalLockResult. 651 """ 652 self.branch.lock_read() 653 return self._lock_self_write() 654 655 def lock_write(self): 656 """See MutableTree.lock_write, and WorkingTree.unlock. 657 658 :return: A breezy.lock.LogicalLockResult. 659 """ 660 self.branch.lock_write() 661 return self._lock_self_write() 662 663 def move(self, from_paths, to_dir, after=False): 664 """See WorkingTree.move().""" 665 result = [] 666 if not from_paths: 667 return result 668 with self.lock_tree_write(): 669 state = self.current_dirstate() 670 if isinstance(from_paths, (str, bytes)): 671 raise ValueError() 672 to_dir_utf8 = to_dir.encode('utf8') 673 to_entry_dirname, to_basename = os.path.split(to_dir_utf8) 674 # check destination directory 675 # get the details for it 676 (to_entry_block_index, to_entry_entry_index, dir_present, 677 entry_present) = state._get_block_entry_index( 678 to_entry_dirname, to_basename, 0) 679 if not entry_present: 680 raise errors.BzrMoveFailedError( 681 '', to_dir, errors.NotVersionedError(to_dir)) 682 to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index] 683 # get a handle on the block itself. 684 to_block_index = state._ensure_block( 685 to_entry_block_index, to_entry_entry_index, to_dir_utf8) 686 to_block = state._dirblocks[to_block_index] 687 to_abs = self.abspath(to_dir) 688 if not isdir(to_abs): 689 raise errors.BzrMoveFailedError('', to_dir, 690 errors.NotADirectory(to_abs)) 691 692 if to_entry[1][0][0] != b'd': 693 raise errors.BzrMoveFailedError('', to_dir, 694 errors.NotADirectory(to_abs)) 695 696 if self._inventory is not None: 697 update_inventory = True 698 inv = self.root_inventory 699 to_dir_id = to_entry[0][2] 700 else: 701 update_inventory = False 702 703 # GZ 2017-03-28: The rollbacks variable was shadowed in the loop below 704 # missing those added here, but there's also no test coverage for this. 705 rollbacks = contextlib.ExitStack() 706 707 def move_one(old_entry, from_path_utf8, minikind, executable, 708 fingerprint, packed_stat, size, 709 to_block, to_key, to_path_utf8): 710 state._make_absent(old_entry) 711 from_key = old_entry[0] 712 rollbacks.callback( 713 state.update_minimal, 714 from_key, 715 minikind, 716 executable=executable, 717 fingerprint=fingerprint, 718 packed_stat=packed_stat, 719 size=size, 720 path_utf8=from_path_utf8) 721 state.update_minimal(to_key, 722 minikind, 723 executable=executable, 724 fingerprint=fingerprint, 725 packed_stat=packed_stat, 726 size=size, 727 path_utf8=to_path_utf8) 728 added_entry_index, _ = state._find_entry_index( 729 to_key, to_block[1]) 730 new_entry = to_block[1][added_entry_index] 731 rollbacks.callback(state._make_absent, new_entry) 732 733 for from_rel in from_paths: 734 # from_rel is 'pathinroot/foo/bar' 735 from_rel_utf8 = from_rel.encode('utf8') 736 from_dirname, from_tail = osutils.split(from_rel) 737 from_dirname, from_tail_utf8 = osutils.split(from_rel_utf8) 738 from_entry = self._get_entry(path=from_rel) 739 if from_entry == (None, None): 740 raise errors.BzrMoveFailedError( 741 from_rel, to_dir, 742 errors.NotVersionedError(path=from_rel)) 743 744 from_id = from_entry[0][2] 745 to_rel = pathjoin(to_dir, from_tail) 746 to_rel_utf8 = pathjoin(to_dir_utf8, from_tail_utf8) 747 item_to_entry = self._get_entry(path=to_rel) 748 if item_to_entry != (None, None): 749 raise errors.BzrMoveFailedError( 750 from_rel, to_rel, "Target is already versioned.") 751 752 if from_rel == to_rel: 753 raise errors.BzrMoveFailedError( 754 from_rel, to_rel, "Source and target are identical.") 755 756 from_missing = not self.has_filename(from_rel) 757 to_missing = not self.has_filename(to_rel) 758 if after: 759 move_file = False 760 else: 761 move_file = True 762 if to_missing: 763 if not move_file: 764 raise errors.BzrMoveFailedError( 765 from_rel, to_rel, 766 errors.NoSuchFile( 767 path=to_rel, 768 extra="New file has not been created yet")) 769 elif from_missing: 770 # neither path exists 771 raise errors.BzrRenameFailedError( 772 from_rel, to_rel, 773 errors.PathsDoNotExist(paths=(from_rel, to_rel))) 774 else: 775 if from_missing: # implicitly just update our path mapping 776 move_file = False 777 elif not after: 778 raise errors.RenameFailedFilesExist(from_rel, to_rel) 779 780 # perform the disk move first - its the most likely failure point. 781 if move_file: 782 from_rel_abs = self.abspath(from_rel) 783 to_rel_abs = self.abspath(to_rel) 784 try: 785 osutils.rename(from_rel_abs, to_rel_abs) 786 except OSError as e: 787 raise errors.BzrMoveFailedError(from_rel, to_rel, e[1]) 788 rollbacks.callback( 789 osutils.rename, to_rel_abs, from_rel_abs) 790 try: 791 # perform the rename in the inventory next if needed: its easy 792 # to rollback 793 if update_inventory: 794 # rename the entry 795 from_entry = inv.get_entry(from_id) 796 current_parent = from_entry.parent_id 797 inv.rename(from_id, to_dir_id, from_tail) 798 rollbacks.callback( 799 inv.rename, from_id, current_parent, from_tail) 800 # finally do the rename in the dirstate, which is a little 801 # tricky to rollback, but least likely to need it. 802 old_block_index, old_entry_index, dir_present, file_present = \ 803 state._get_block_entry_index( 804 from_dirname, from_tail_utf8, 0) 805 old_block = state._dirblocks[old_block_index][1] 806 old_entry = old_block[old_entry_index] 807 from_key, old_entry_details = old_entry 808 cur_details = old_entry_details[0] 809 # remove the old row 810 to_key = ((to_block[0],) + from_key[1:3]) 811 minikind = cur_details[0] 812 move_one(old_entry, from_path_utf8=from_rel_utf8, 813 minikind=minikind, 814 executable=cur_details[3], 815 fingerprint=cur_details[1], 816 packed_stat=cur_details[4], 817 size=cur_details[2], 818 to_block=to_block, 819 to_key=to_key, 820 to_path_utf8=to_rel_utf8) 821 822 if minikind == b'd': 823 def update_dirblock(from_dir, to_key, to_dir_utf8): 824 """Recursively update all entries in this dirblock.""" 825 if from_dir == b'': 826 raise AssertionError( 827 "renaming root not supported") 828 from_key = (from_dir, '') 829 from_block_idx, present = \ 830 state._find_block_index_from_key(from_key) 831 if not present: 832 # This is the old record, if it isn't present, 833 # then there is theoretically nothing to 834 # update. (Unless it isn't present because of 835 # lazy loading, but we don't do that yet) 836 return 837 from_block = state._dirblocks[from_block_idx] 838 to_block_index, to_entry_index, _, _ = \ 839 state._get_block_entry_index( 840 to_key[0], to_key[1], 0) 841 to_block_index = state._ensure_block( 842 to_block_index, to_entry_index, to_dir_utf8) 843 to_block = state._dirblocks[to_block_index] 844 845 # Grab a copy since move_one may update the list. 846 for entry in from_block[1][:]: 847 if not (entry[0][0] == from_dir): 848 raise AssertionError() 849 cur_details = entry[1][0] 850 to_key = ( 851 to_dir_utf8, entry[0][1], entry[0][2]) 852 from_path_utf8 = osutils.pathjoin( 853 entry[0][0], entry[0][1]) 854 to_path_utf8 = osutils.pathjoin( 855 to_dir_utf8, entry[0][1]) 856 minikind = cur_details[0] 857 if minikind in (b'a', b'r'): 858 # Deleted children of a renamed directory 859 # Do not need to be updated. Children that 860 # have been renamed out of this directory 861 # should also not be updated 862 continue 863 move_one(entry, from_path_utf8=from_path_utf8, 864 minikind=minikind, 865 executable=cur_details[3], 866 fingerprint=cur_details[1], 867 packed_stat=cur_details[4], 868 size=cur_details[2], 869 to_block=to_block, 870 to_key=to_key, 871 to_path_utf8=to_path_utf8) 872 if minikind == b'd': 873 # We need to move all the children of this 874 # entry 875 update_dirblock(from_path_utf8, to_key, 876 to_path_utf8) 877 update_dirblock(from_rel_utf8, to_key, to_rel_utf8) 878 except BaseException: 879 rollbacks.close() 880 raise 881 result.append((from_rel, to_rel)) 882 state._mark_modified() 883 self._make_dirty(reset_inventory=False) 884 885 return result 886 887 def _must_be_locked(self): 888 if not self._control_files._lock_count: 889 raise errors.ObjectNotLocked(self) 890 891 def _new_tree(self): 892 """Initialize the state in this tree to be a new tree.""" 893 self._dirty = True 894 895 def path2id(self, path): 896 """Return the id for path in this tree.""" 897 with self.lock_read(): 898 if isinstance(path, list): 899 if path == []: 900 path = [""] 901 path = osutils.pathjoin(*path) 902 path = path.strip('/') 903 entry = self._get_entry(path=path) 904 if entry == (None, None): 905 nested_tree, subpath = self.get_containing_nested_tree(path) 906 if nested_tree is not None: 907 return nested_tree.path2id(subpath) 908 return None 909 return entry[0][2] 910 911 def paths2ids(self, paths, trees=[], require_versioned=True): 912 """See Tree.paths2ids(). 913 914 This specialisation fast-paths the case where all the trees are in the 915 dirstate. 916 """ 917 if paths is None: 918 return None 919 parents = self.get_parent_ids() 920 for tree in trees: 921 if not (isinstance(tree, DirStateRevisionTree) and 922 tree._revision_id in parents): 923 return super(DirStateWorkingTree, self).paths2ids( 924 paths, trees, require_versioned) 925 search_indexes = [ 926 0] + [1 + parents.index(tree._revision_id) for tree in trees] 927 paths_utf8 = set() 928 for path in paths: 929 paths_utf8.add(path.encode('utf8')) 930 # -- get the state object and prepare it. 931 state = self.current_dirstate() 932 if False and (state._dirblock_state == dirstate.DirState.NOT_IN_MEMORY 933 and b'' not in paths): 934 paths2ids = self._paths2ids_using_bisect 935 else: 936 paths2ids = self._paths2ids_in_memory 937 return paths2ids(paths_utf8, search_indexes, 938 require_versioned=require_versioned) 939 940 def _paths2ids_in_memory(self, paths, search_indexes, 941 require_versioned=True): 942 state = self.current_dirstate() 943 state._read_dirblocks_if_needed() 944 945 def _entries_for_path(path): 946 """Return a list with all the entries that match path for all ids. 947 """ 948 dirname, basename = os.path.split(path) 949 key = (dirname, basename, b'') 950 block_index, present = state._find_block_index_from_key(key) 951 if not present: 952 # the block which should contain path is absent. 953 return [] 954 result = [] 955 block = state._dirblocks[block_index][1] 956 entry_index, _ = state._find_entry_index(key, block) 957 # we may need to look at multiple entries at this path: walk while 958 # the paths match. 959 while (entry_index < len(block) and 960 block[entry_index][0][0:2] == key[0:2]): 961 result.append(block[entry_index]) 962 entry_index += 1 963 return result 964 if require_versioned: 965 # -- check all supplied paths are versioned in a search tree. -- 966 all_versioned = True 967 for path in paths: 968 path_entries = _entries_for_path(path) 969 if not path_entries: 970 # this specified path is not present at all: error 971 all_versioned = False 972 break 973 found_versioned = False 974 # for each id at this path 975 for entry in path_entries: 976 # for each tree. 977 for index in search_indexes: 978 if entry[1][index][0] != b'a': # absent 979 found_versioned = True 980 # all good: found a versioned cell 981 break 982 if not found_versioned: 983 # none of the indexes was not 'absent' at all ids for this 984 # path. 985 all_versioned = False 986 break 987 if not all_versioned: 988 raise errors.PathsNotVersionedError( 989 [p.decode('utf-8') for p in paths]) 990 # -- remove redundancy in supplied paths to prevent over-scanning -- 991 search_paths = osutils.minimum_path_selection(paths) 992 # sketch: 993 # for all search_indexs in each path at or under each element of 994 # search_paths, if the detail is relocated: add the id, and add the 995 # relocated path as one to search if its not searched already. If the 996 # detail is not relocated, add the id. 997 searched_paths = set() 998 found_ids = set() 999 1000 def _process_entry(entry): 1001 """Look at search_indexes within entry. 1002 1003 If a specific tree's details are relocated, add the relocation 1004 target to search_paths if not searched already. If it is absent, do 1005 nothing. Otherwise add the id to found_ids. 1006 """ 1007 for index in search_indexes: 1008 if entry[1][index][0] == b'r': # relocated 1009 if not osutils.is_inside_any(searched_paths, 1010 entry[1][index][1]): 1011 search_paths.add(entry[1][index][1]) 1012 elif entry[1][index][0] != b'a': # absent 1013 found_ids.add(entry[0][2]) 1014 while search_paths: 1015 current_root = search_paths.pop() 1016 searched_paths.add(current_root) 1017 # process the entries for this containing directory: the rest will 1018 # be found by their parents recursively. 1019 root_entries = _entries_for_path(current_root) 1020 if not root_entries: 1021 # this specified path is not present at all, skip it. 1022 continue 1023 for entry in root_entries: 1024 _process_entry(entry) 1025 initial_key = (current_root, b'', b'') 1026 block_index, _ = state._find_block_index_from_key(initial_key) 1027 while (block_index < len(state._dirblocks) and 1028 osutils.is_inside(current_root, state._dirblocks[block_index][0])): 1029 for entry in state._dirblocks[block_index][1]: 1030 _process_entry(entry) 1031 block_index += 1 1032 return found_ids 1033 1034 def _paths2ids_using_bisect(self, paths, search_indexes, 1035 require_versioned=True): 1036 state = self.current_dirstate() 1037 found_ids = set() 1038 1039 split_paths = sorted(osutils.split(p) for p in paths) 1040 found = state._bisect_recursive(split_paths) 1041 1042 if require_versioned: 1043 found_dir_names = set(dir_name_id[:2] for dir_name_id in found) 1044 for dir_name in split_paths: 1045 if dir_name not in found_dir_names: 1046 raise errors.PathsNotVersionedError( 1047 [p.decode('utf-8') for p in paths]) 1048 1049 for dir_name_id, trees_info in found.items(): 1050 for index in search_indexes: 1051 if trees_info[index][0] not in (b'r', b'a'): 1052 found_ids.add(dir_name_id[2]) 1053 return found_ids 1054 1055 def read_working_inventory(self): 1056 """Read the working inventory. 1057 1058 This is a meaningless operation for dirstate, but we obey it anyhow. 1059 """ 1060 return self.root_inventory 1061 1062 def revision_tree(self, revision_id): 1063 """See Tree.revision_tree. 1064 1065 WorkingTree4 supplies revision_trees for any basis tree. 1066 """ 1067 with self.lock_read(): 1068 dirstate = self.current_dirstate() 1069 parent_ids = dirstate.get_parent_ids() 1070 if revision_id not in parent_ids: 1071 raise errors.NoSuchRevisionInTree(self, revision_id) 1072 if revision_id in dirstate.get_ghosts(): 1073 raise errors.NoSuchRevisionInTree(self, revision_id) 1074 return DirStateRevisionTree( 1075 dirstate, revision_id, self.branch.repository, 1076 get_transport_from_path(self.basedir)) 1077 1078 def set_last_revision(self, new_revision): 1079 """Change the last revision in the working tree.""" 1080 with self.lock_tree_write(): 1081 parents = self.get_parent_ids() 1082 if new_revision in (_mod_revision.NULL_REVISION, None): 1083 if len(parents) >= 2: 1084 raise AssertionError( 1085 "setting the last parent to none with a pending merge " 1086 "is unsupported.") 1087 self.set_parent_ids([]) 1088 else: 1089 self.set_parent_ids( 1090 [new_revision] + parents[1:], 1091 allow_leftmost_as_ghost=True) 1092 1093 def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False): 1094 """Set the parent ids to revision_ids. 1095 1096 See also set_parent_trees. This api will try to retrieve the tree data 1097 for each element of revision_ids from the trees repository. If you have 1098 tree data already available, it is more efficient to use 1099 set_parent_trees rather than set_parent_ids. set_parent_ids is however 1100 an easier API to use. 1101 1102 :param revision_ids: The revision_ids to set as the parent ids of this 1103 working tree. Any of these may be ghosts. 1104 """ 1105 with self.lock_tree_write(): 1106 trees = [] 1107 for revision_id in revision_ids: 1108 try: 1109 revtree = self.branch.repository.revision_tree(revision_id) 1110 # TODO: jam 20070213 KnitVersionedFile raises 1111 # RevisionNotPresent rather than NoSuchRevision if a given 1112 # revision_id is not present. Should Repository be catching 1113 # it and re-raising NoSuchRevision? 1114 except (errors.NoSuchRevision, errors.RevisionNotPresent): 1115 revtree = None 1116 trees.append((revision_id, revtree)) 1117 self.set_parent_trees( 1118 trees, allow_leftmost_as_ghost=allow_leftmost_as_ghost) 1119 1120 def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False): 1121 """Set the parents of the working tree. 1122 1123 :param parents_list: A list of (revision_id, tree) tuples. 1124 If tree is None, then that element is treated as an unreachable 1125 parent tree - i.e. a ghost. 1126 """ 1127 with self.lock_tree_write(): 1128 dirstate = self.current_dirstate() 1129 if len(parents_list) > 0: 1130 if not allow_leftmost_as_ghost and parents_list[0][1] is None: 1131 raise errors.GhostRevisionUnusableHere(parents_list[0][0]) 1132 real_trees = [] 1133 ghosts = [] 1134 1135 parent_ids = [rev_id for rev_id, tree in parents_list] 1136 graph = self.branch.repository.get_graph() 1137 heads = graph.heads(parent_ids) 1138 accepted_revisions = set() 1139 1140 # convert absent trees to the null tree, which we convert back to 1141 # missing on access. 1142 for rev_id, tree in parents_list: 1143 if len(accepted_revisions) > 0: 1144 # we always accept the first tree 1145 if rev_id in accepted_revisions or rev_id not in heads: 1146 # We have already included either this tree, or its 1147 # descendent, so we skip it. 1148 continue 1149 _mod_revision.check_not_reserved_id(rev_id) 1150 if tree is not None: 1151 real_trees.append((rev_id, tree)) 1152 else: 1153 real_trees.append((rev_id, 1154 self.branch.repository.revision_tree( 1155 _mod_revision.NULL_REVISION))) 1156 ghosts.append(rev_id) 1157 accepted_revisions.add(rev_id) 1158 updated = False 1159 if (len(real_trees) == 1 1160 and not ghosts 1161 and self.branch.repository._format.fast_deltas 1162 and isinstance(real_trees[0][1], InventoryRevisionTree) 1163 and self.get_parent_ids()): 1164 rev_id, rev_tree = real_trees[0] 1165 basis_id = self.get_parent_ids()[0] 1166 # There are times when basis_tree won't be in 1167 # self.branch.repository, (switch, for example) 1168 try: 1169 basis_tree = self.branch.repository.revision_tree(basis_id) 1170 except errors.NoSuchRevision: 1171 # Fall back to the set_parent_trees(), since we can't use 1172 # _make_delta if we can't get the RevisionTree 1173 pass 1174 else: 1175 delta = rev_tree.root_inventory._make_delta( 1176 basis_tree.root_inventory) 1177 dirstate.update_basis_by_delta(delta, rev_id) 1178 updated = True 1179 if not updated: 1180 dirstate.set_parent_trees(real_trees, ghosts=ghosts) 1181 self._make_dirty(reset_inventory=False) 1182 1183 def _set_root_id(self, file_id): 1184 """See WorkingTree.set_root_id.""" 1185 state = self.current_dirstate() 1186 state.set_path_id(b'', file_id) 1187 if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED: 1188 self._make_dirty(reset_inventory=True) 1189 1190 def _sha_from_stat(self, path, stat_result): 1191 """Get a sha digest from the tree's stat cache. 1192 1193 The default implementation assumes no stat cache is present. 1194 1195 :param path: The path. 1196 :param stat_result: The stat result being looked up. 1197 """ 1198 return self.current_dirstate().sha1_from_stat(path, stat_result) 1199 1200 def supports_tree_reference(self): 1201 return self._repo_supports_tree_reference 1202 1203 def unlock(self): 1204 """Unlock in format 4 trees needs to write the entire dirstate.""" 1205 if self._control_files._lock_count == 1: 1206 # do non-implementation specific cleanup 1207 self._cleanup() 1208 1209 # eventually we should do signature checking during read locks for 1210 # dirstate updates. 1211 if self._control_files._lock_mode == 'w': 1212 if self._dirty: 1213 self.flush() 1214 if self._dirstate is not None: 1215 # This is a no-op if there are no modifications. 1216 self._dirstate.save() 1217 self._dirstate.unlock() 1218 # TODO: jam 20070301 We shouldn't have to wipe the dirstate at this 1219 # point. Instead, it could check if the header has been 1220 # modified when it is locked, and if not, it can hang on to 1221 # the data it has in memory. 1222 self._dirstate = None 1223 self._inventory = None 1224 # reverse order of locking. 1225 try: 1226 return self._control_files.unlock() 1227 finally: 1228 self.branch.unlock() 1229 1230 def unversion(self, paths): 1231 """Remove the file ids in paths from the current versioned set. 1232 1233 When a directory is unversioned, all of its children are automatically 1234 unversioned. 1235 1236 :param paths: The file ids to stop versioning. 1237 :raises: NoSuchId if any fileid is not currently versioned. 1238 """ 1239 with self.lock_tree_write(): 1240 if not paths: 1241 return 1242 state = self.current_dirstate() 1243 state._read_dirblocks_if_needed() 1244 file_ids = set() 1245 for path in paths: 1246 file_id = self.path2id(path) 1247 if file_id is None: 1248 raise errors.NoSuchFile(self, path) 1249 file_ids.add(file_id) 1250 ids_to_unversion = set(file_ids) 1251 paths_to_unversion = set() 1252 # sketch: 1253 # check if the root is to be unversioned, if so, assert for now. 1254 # walk the state marking unversioned things as absent. 1255 # if there are any un-unversioned ids at the end, raise 1256 for key, details in state._dirblocks[0][1]: 1257 if (details[0][0] not in (b'a', b'r') and # absent or relocated 1258 key[2] in ids_to_unversion): 1259 # I haven't written the code to unversion / yet - it should 1260 # be supported. 1261 raise errors.BzrError( 1262 'Unversioning the / is not currently supported') 1263 block_index = 0 1264 while block_index < len(state._dirblocks): 1265 # process one directory at a time. 1266 block = state._dirblocks[block_index] 1267 # first check: is the path one to remove - it or its children 1268 delete_block = False 1269 for path in paths_to_unversion: 1270 if (block[0].startswith(path) and 1271 (len(block[0]) == len(path) or 1272 block[0][len(path)] == '/')): 1273 # this entire block should be deleted - its the block for a 1274 # path to unversion; or the child of one 1275 delete_block = True 1276 break 1277 # TODO: trim paths_to_unversion as we pass by paths 1278 if delete_block: 1279 # this block is to be deleted: process it. 1280 # TODO: we can special case the no-parents case and 1281 # just forget the whole block. 1282 entry_index = 0 1283 while entry_index < len(block[1]): 1284 entry = block[1][entry_index] 1285 if entry[1][0][0] in (b'a', b'r'): 1286 # don't remove absent or renamed entries 1287 entry_index += 1 1288 else: 1289 # Mark this file id as having been removed 1290 ids_to_unversion.discard(entry[0][2]) 1291 if not state._make_absent(entry): 1292 # The block has not shrunk. 1293 entry_index += 1 1294 # go to the next block. (At the moment we dont delete empty 1295 # dirblocks) 1296 block_index += 1 1297 continue 1298 entry_index = 0 1299 while entry_index < len(block[1]): 1300 entry = block[1][entry_index] 1301 if (entry[1][0][0] in (b'a', b'r') or # absent, relocated 1302 # ^ some parent row. 1303 entry[0][2] not in ids_to_unversion): 1304 # ^ not an id to unversion 1305 entry_index += 1 1306 continue 1307 if entry[1][0][0] == b'd': 1308 paths_to_unversion.add( 1309 pathjoin(entry[0][0], entry[0][1])) 1310 if not state._make_absent(entry): 1311 entry_index += 1 1312 # we have unversioned this id 1313 ids_to_unversion.remove(entry[0][2]) 1314 block_index += 1 1315 if ids_to_unversion: 1316 raise errors.NoSuchId(self, next(iter(ids_to_unversion))) 1317 self._make_dirty(reset_inventory=False) 1318 # have to change the legacy inventory too. 1319 if self._inventory is not None: 1320 for file_id in file_ids: 1321 if self._inventory.has_id(file_id): 1322 self._inventory.remove_recursive_id(file_id) 1323 1324 def rename_one(self, from_rel, to_rel, after=False): 1325 """See WorkingTree.rename_one""" 1326 with self.lock_tree_write(): 1327 self.flush() 1328 super(DirStateWorkingTree, self).rename_one( 1329 from_rel, to_rel, after) 1330 1331 def apply_inventory_delta(self, changes): 1332 """See MutableTree.apply_inventory_delta""" 1333 with self.lock_tree_write(): 1334 state = self.current_dirstate() 1335 state.update_by_delta(changes) 1336 self._make_dirty(reset_inventory=True) 1337 1338 def update_basis_by_delta(self, new_revid, delta): 1339 """See MutableTree.update_basis_by_delta.""" 1340 if self.last_revision() == new_revid: 1341 raise AssertionError() 1342 self.current_dirstate().update_basis_by_delta(delta, new_revid) 1343 1344 def _validate(self): 1345 with self.lock_read(): 1346 self._dirstate._validate() 1347 1348 def _write_inventory(self, inv): 1349 """Write inventory as the current inventory.""" 1350 if self._dirty: 1351 raise AssertionError("attempting to write an inventory when the " 1352 "dirstate is dirty will lose pending changes") 1353 with self.lock_tree_write(): 1354 had_inventory = self._inventory is not None 1355 # Setting self._inventory = None forces the dirstate to regenerate the 1356 # working inventory. We do this because self.inventory may be inv, or 1357 # may have been modified, and either case would prevent a clean delta 1358 # being created. 1359 self._inventory = None 1360 # generate a delta, 1361 delta = inv._make_delta(self.root_inventory) 1362 # and apply it. 1363 self.apply_inventory_delta(delta) 1364 if had_inventory: 1365 self._inventory = inv 1366 self.flush() 1367 1368 def reset_state(self, revision_ids=None): 1369 """Reset the state of the working tree. 1370 1371 This does a hard-reset to a last-known-good state. This is a way to 1372 fix if something got corrupted (like the .bzr/checkout/dirstate file) 1373 """ 1374 with self.lock_tree_write(): 1375 if revision_ids is None: 1376 revision_ids = self.get_parent_ids() 1377 if not revision_ids: 1378 base_tree = self.branch.repository.revision_tree( 1379 _mod_revision.NULL_REVISION) 1380 trees = [] 1381 else: 1382 trees = list(zip(revision_ids, 1383 self.branch.repository.revision_trees(revision_ids))) 1384 base_tree = trees[0][1] 1385 state = self.current_dirstate() 1386 # We don't support ghosts yet 1387 state.set_state_from_scratch(base_tree.root_inventory, trees, []) 1388 1389 1390class ContentFilterAwareSHA1Provider(dirstate.SHA1Provider): 1391 1392 def __init__(self, tree): 1393 self.tree = tree 1394 1395 def sha1(self, abspath): 1396 """See dirstate.SHA1Provider.sha1().""" 1397 filters = self.tree._content_filter_stack( 1398 self.tree.relpath(osutils.safe_unicode(abspath))) 1399 return _mod_filters.internal_size_sha_file_byname(abspath, filters)[1] 1400 1401 def stat_and_sha1(self, abspath): 1402 """See dirstate.SHA1Provider.stat_and_sha1().""" 1403 filters = self.tree._content_filter_stack( 1404 self.tree.relpath(osutils.safe_unicode(abspath))) 1405 with open(abspath, 'rb', 65000) as file_obj: 1406 statvalue = os.fstat(file_obj.fileno()) 1407 if filters: 1408 file_obj, size = _mod_filters.filtered_input_file(file_obj, filters) 1409 statvalue = _mod_filters.FilteredStat(statvalue, size) 1410 sha1 = osutils.size_sha_file(file_obj)[1] 1411 return statvalue, sha1 1412 1413 1414class ContentFilteringDirStateWorkingTree(DirStateWorkingTree): 1415 """Dirstate working tree that supports content filtering. 1416 1417 The dirstate holds the hash and size of the canonical form of the file, 1418 and most methods must return that. 1419 """ 1420 1421 def _file_content_summary(self, path, stat_result): 1422 # This is to support the somewhat obsolete path_content_summary method 1423 # with content filtering: see 1424 # <https://bugs.launchpad.net/bzr/+bug/415508>. 1425 # 1426 # If the dirstate cache is up to date and knows the hash and size, 1427 # return that. 1428 # Otherwise if there are no content filters, return the on-disk size 1429 # and leave the hash blank. 1430 # Otherwise, read and filter the on-disk file and use its size and 1431 # hash. 1432 # 1433 # The dirstate doesn't store the size of the canonical form so we 1434 # can't trust it for content-filtered trees. We just return None. 1435 dirstate_sha1 = self._dirstate.sha1_from_stat(path, stat_result) 1436 executable = self._is_executable_from_path_and_stat(path, stat_result) 1437 return ('file', None, executable, dirstate_sha1) 1438 1439 1440class WorkingTree4(DirStateWorkingTree): 1441 """This is the Format 4 working tree. 1442 1443 This differs from WorkingTree by: 1444 - Having a consolidated internal dirstate, stored in a 1445 randomly-accessible sorted file on disk. 1446 - Not having a regular inventory attribute. One can be synthesized 1447 on demand but this is expensive and should be avoided. 1448 1449 This is new in bzr 0.15. 1450 """ 1451 1452 1453class WorkingTree5(ContentFilteringDirStateWorkingTree): 1454 """This is the Format 5 working tree. 1455 1456 This differs from WorkingTree4 by: 1457 - Supporting content filtering. 1458 1459 This is new in bzr 1.11. 1460 """ 1461 1462 1463class WorkingTree6(ContentFilteringDirStateWorkingTree): 1464 """This is the Format 6 working tree. 1465 1466 This differs from WorkingTree5 by: 1467 - Supporting a current view that may mask the set of files in a tree 1468 impacted by most user operations. 1469 1470 This is new in bzr 1.14. 1471 """ 1472 1473 def _make_views(self): 1474 return views.PathBasedViews(self) 1475 1476 1477class DirStateWorkingTreeFormat(WorkingTreeFormatMetaDir): 1478 1479 missing_parent_conflicts = True 1480 1481 supports_versioned_directories = True 1482 1483 _lock_class = LockDir 1484 _lock_file_name = 'lock' 1485 1486 def _open_control_files(self, a_controldir): 1487 transport = a_controldir.get_workingtree_transport(None) 1488 return LockableFiles(transport, self._lock_file_name, 1489 self._lock_class) 1490 1491 def initialize(self, a_controldir, revision_id=None, from_branch=None, 1492 accelerator_tree=None, hardlink=False): 1493 """See WorkingTreeFormat.initialize(). 1494 1495 :param revision_id: allows creating a working tree at a different 1496 revision than the branch is at. 1497 :param accelerator_tree: A tree which can be used for retrieving file 1498 contents more quickly than the revision tree, i.e. a workingtree. 1499 The revision tree will be used for cases where accelerator_tree's 1500 content is different. 1501 :param hardlink: If true, hard-link files from accelerator_tree, 1502 where possible. 1503 1504 These trees get an initial random root id, if their repository supports 1505 rich root data, TREE_ROOT otherwise. 1506 """ 1507 if not isinstance(a_controldir.transport, LocalTransport): 1508 raise errors.NotLocalUrl(a_controldir.transport.base) 1509 transport = a_controldir.get_workingtree_transport(self) 1510 control_files = self._open_control_files(a_controldir) 1511 control_files.create_lock() 1512 control_files.lock_write() 1513 transport.put_bytes('format', self.as_string(), 1514 mode=a_controldir._get_file_mode()) 1515 if from_branch is not None: 1516 branch = from_branch 1517 else: 1518 branch = a_controldir.open_branch() 1519 if revision_id is None: 1520 revision_id = branch.last_revision() 1521 local_path = transport.local_abspath('dirstate') 1522 # write out new dirstate (must exist when we create the tree) 1523 state = dirstate.DirState.initialize(local_path) 1524 state.unlock() 1525 del state 1526 wt = self._tree_class(a_controldir.root_transport.local_abspath('.'), 1527 branch, 1528 _format=self, 1529 _controldir=a_controldir, 1530 _control_files=control_files) 1531 wt._new_tree() 1532 wt.lock_tree_write() 1533 try: 1534 self._init_custom_control_files(wt) 1535 if revision_id in (None, _mod_revision.NULL_REVISION): 1536 if branch.repository.supports_rich_root(): 1537 wt._set_root_id(generate_ids.gen_root_id()) 1538 else: 1539 wt._set_root_id(ROOT_ID) 1540 wt.flush() 1541 basis = None 1542 # frequently, we will get here due to branching. The accelerator 1543 # tree will be the tree from the branch, so the desired basis 1544 # tree will often be a parent of the accelerator tree. 1545 if accelerator_tree is not None: 1546 try: 1547 basis = accelerator_tree.revision_tree(revision_id) 1548 except errors.NoSuchRevision: 1549 pass 1550 if basis is None: 1551 basis = branch.repository.revision_tree(revision_id) 1552 if revision_id == _mod_revision.NULL_REVISION: 1553 parents_list = [] 1554 else: 1555 parents_list = [(revision_id, basis)] 1556 with basis.lock_read(): 1557 wt.set_parent_trees(parents_list, allow_leftmost_as_ghost=True) 1558 wt.flush() 1559 # if the basis has a root id we have to use that; otherwise we 1560 # use a new random one 1561 basis_root_id = basis.path2id('') 1562 if basis_root_id is not None: 1563 wt._set_root_id(basis_root_id) 1564 wt.flush() 1565 if wt.supports_content_filtering(): 1566 # The original tree may not have the same content filters 1567 # applied so we can't safely build the inventory delta from 1568 # the source tree. 1569 delta_from_tree = False 1570 else: 1571 delta_from_tree = True 1572 # delta_from_tree is safe even for DirStateRevisionTrees, 1573 # because wt4.apply_inventory_delta does not mutate the input 1574 # inventory entries. 1575 bzr_transform.build_tree( 1576 basis, wt, accelerator_tree, 1577 hardlink=hardlink, 1578 delta_from_tree=delta_from_tree) 1579 for hook in MutableTree.hooks['post_build_tree']: 1580 hook(wt) 1581 finally: 1582 control_files.unlock() 1583 wt.unlock() 1584 return wt 1585 1586 def _init_custom_control_files(self, wt): 1587 """Subclasses with custom control files should override this method. 1588 1589 The working tree and control files are locked for writing when this 1590 method is called. 1591 1592 :param wt: the WorkingTree object 1593 """ 1594 1595 def open(self, a_controldir, _found=False): 1596 """Return the WorkingTree object for a_controldir 1597 1598 _found is a private parameter, do not use it. It is used to indicate 1599 if format probing has already been done. 1600 """ 1601 if not _found: 1602 # we are being called directly and must probe. 1603 raise NotImplementedError 1604 if not isinstance(a_controldir.transport, LocalTransport): 1605 raise errors.NotLocalUrl(a_controldir.transport.base) 1606 wt = self._open(a_controldir, self._open_control_files(a_controldir)) 1607 return wt 1608 1609 def _open(self, a_controldir, control_files): 1610 """Open the tree itself. 1611 1612 :param a_controldir: the dir for the tree. 1613 :param control_files: the control files for the tree. 1614 """ 1615 return self._tree_class(a_controldir.root_transport.local_abspath('.'), 1616 branch=a_controldir.open_branch(), 1617 _format=self, 1618 _controldir=a_controldir, 1619 _control_files=control_files) 1620 1621 def __get_matchingcontroldir(self): 1622 return self._get_matchingcontroldir() 1623 1624 def _get_matchingcontroldir(self): 1625 """Overrideable method to get a bzrdir for testing.""" 1626 # please test against something that will let us do tree references 1627 return controldir.format_registry.make_controldir( 1628 'development-subtree') 1629 1630 _matchingcontroldir = property(__get_matchingcontroldir) 1631 1632 1633class WorkingTreeFormat4(DirStateWorkingTreeFormat): 1634 """The first consolidated dirstate working tree format. 1635 1636 This format: 1637 - exists within a metadir controlling .bzr 1638 - includes an explicit version marker for the workingtree control 1639 files, separate from the ControlDir format 1640 - modifies the hash cache format 1641 - is new in bzr 0.15 1642 - uses a LockDir to guard access to it. 1643 """ 1644 1645 upgrade_recommended = False 1646 1647 _tree_class = WorkingTree4 1648 1649 @classmethod 1650 def get_format_string(cls): 1651 """See WorkingTreeFormat.get_format_string().""" 1652 return b"Bazaar Working Tree Format 4 (bzr 0.15)\n" 1653 1654 def get_format_description(self): 1655 """See WorkingTreeFormat.get_format_description().""" 1656 return "Working tree format 4" 1657 1658 1659class WorkingTreeFormat5(DirStateWorkingTreeFormat): 1660 """WorkingTree format supporting content filtering. 1661 """ 1662 1663 upgrade_recommended = False 1664 1665 _tree_class = WorkingTree5 1666 1667 @classmethod 1668 def get_format_string(cls): 1669 """See WorkingTreeFormat.get_format_string().""" 1670 return b"Bazaar Working Tree Format 5 (bzr 1.11)\n" 1671 1672 def get_format_description(self): 1673 """See WorkingTreeFormat.get_format_description().""" 1674 return "Working tree format 5" 1675 1676 def supports_content_filtering(self): 1677 return True 1678 1679 1680class WorkingTreeFormat6(DirStateWorkingTreeFormat): 1681 """WorkingTree format supporting views. 1682 """ 1683 1684 upgrade_recommended = False 1685 1686 _tree_class = WorkingTree6 1687 1688 @classmethod 1689 def get_format_string(cls): 1690 """See WorkingTreeFormat.get_format_string().""" 1691 return b"Bazaar Working Tree Format 6 (bzr 1.14)\n" 1692 1693 def get_format_description(self): 1694 """See WorkingTreeFormat.get_format_description().""" 1695 return "Working tree format 6" 1696 1697 def _init_custom_control_files(self, wt): 1698 """Subclasses with custom control files should override this method.""" 1699 wt._transport.put_bytes('views', b'', 1700 mode=wt.controldir._get_file_mode()) 1701 1702 def supports_content_filtering(self): 1703 return True 1704 1705 def supports_views(self): 1706 return True 1707 1708 def _get_matchingcontroldir(self): 1709 """Overrideable method to get a bzrdir for testing.""" 1710 # We use 'development-subtree' instead of '2a', because we have a 1711 # few tests that want to test tree references 1712 return controldir.format_registry.make_controldir('development-subtree') 1713 1714 1715class DirStateRevisionTree(InventoryTree): 1716 """A revision tree pulling the inventory from a dirstate. 1717 1718 Note that this is one of the historical (ie revision) trees cached in the 1719 dirstate for easy access, not the workingtree. 1720 """ 1721 1722 def __init__(self, dirstate, revision_id, repository, nested_tree_transport): 1723 self._dirstate = dirstate 1724 self._revision_id = revision_id 1725 self._repository = repository 1726 self._inventory = None 1727 self._locked = 0 1728 self._dirstate_locked = False 1729 self._nested_tree_transport = nested_tree_transport 1730 self._repo_supports_tree_reference = getattr( 1731 repository._format, "supports_tree_reference", 1732 False) 1733 1734 def __repr__(self): 1735 return "<%s of %s in %s>" % \ 1736 (self.__class__.__name__, self._revision_id, self._dirstate) 1737 1738 def annotate_iter(self, path, 1739 default_revision=_mod_revision.CURRENT_REVISION): 1740 """See Tree.annotate_iter""" 1741 file_id = self.path2id(path) 1742 text_key = (file_id, self.get_file_revision(path)) 1743 annotations = self._repository.texts.annotate(text_key) 1744 return [(key[-1], line) for (key, line) in annotations] 1745 1746 def _comparison_data(self, entry, path): 1747 """See Tree._comparison_data.""" 1748 if entry is None: 1749 return None, False, None 1750 # trust the entry as RevisionTree does, but this may not be 1751 # sensible: the entry might not have come from us? 1752 return entry.kind, entry.executable, None 1753 1754 def _get_file_revision(self, path, file_id, vf, tree_revision): 1755 """Ensure that file_id, tree_revision is in vf to plan the merge.""" 1756 last_revision = self.get_file_revision(path) 1757 base_vf = self._repository.texts 1758 if base_vf not in vf.fallback_versionedfiles: 1759 vf.fallback_versionedfiles.append(base_vf) 1760 return last_revision 1761 1762 def filter_unversioned_files(self, paths): 1763 """Filter out paths that are not versioned. 1764 1765 :return: set of paths. 1766 """ 1767 pred = self.has_filename 1768 return set((p for p in paths if not pred(p))) 1769 1770 def id2path(self, file_id, recurse='down'): 1771 "Convert a file-id to a path." 1772 with self.lock_read(): 1773 entry = self._get_entry(file_id=file_id) 1774 if entry == (None, None): 1775 if recurse == 'down': 1776 if 'evil' in debug.debug_flags: 1777 trace.mutter_callsite( 1778 2, "Tree.id2path scans all nested trees.") 1779 1780 for nested_path in self.iter_references(): 1781 nested_tree = self.get_nested_tree(nested_path) 1782 try: 1783 return osutils.pathjoin(nested_path, nested_tree.id2path(file_id)) 1784 except errors.NoSuchId: 1785 pass 1786 raise errors.NoSuchId(tree=self, file_id=file_id) 1787 path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1]) 1788 return path_utf8.decode('utf8') 1789 1790 def get_nested_tree(self, path): 1791 with self.lock_read(): 1792 nested_revid = self.get_reference_revision(path) 1793 return self._get_nested_tree(path, None, nested_revid) 1794 1795 def _get_nested_tree(self, path, file_id, reference_revision): 1796 branch = _mod_branch.Branch.open_from_transport( 1797 self._nested_tree_transport.clone(path)) 1798 try: 1799 revtree = branch.repository.revision_tree(reference_revision) 1800 except errors.NoSuchRevision: 1801 raise MissingNestedTree(path) 1802 if file_id is not None and revtree.path2id('') != file_id: 1803 raise AssertionError('mismatching file id: %r != %r' % ( 1804 revtree.path2id(''), file_id)) 1805 return revtree 1806 1807 def iter_references(self): 1808 if not self._repo_supports_tree_reference: 1809 # When the repo doesn't support references, we will have nothing to 1810 # return 1811 return iter([]) 1812 # Otherwise, fall back to the default implementation 1813 return super(DirStateRevisionTree, self).iter_references() 1814 1815 def _get_parent_index(self): 1816 """Return the index in the dirstate referenced by this tree.""" 1817 return self._dirstate.get_parent_ids().index(self._revision_id) + 1 1818 1819 def _get_entry(self, file_id=None, path=None): 1820 """Get the dirstate row for file_id or path. 1821 1822 If either file_id or path is supplied, it is used as the key to lookup. 1823 If both are supplied, the fastest lookup is used, and an error is 1824 raised if they do not both point at the same row. 1825 1826 :param file_id: An optional unicode file_id to be looked up. 1827 :param path: An optional unicode path to be looked up. 1828 :return: The dirstate row tuple for path/file_id, or (None, None) 1829 """ 1830 if file_id is None and path is None: 1831 raise errors.BzrError('must supply file_id or path') 1832 if path is not None: 1833 path = path.encode('utf8') 1834 try: 1835 parent_index = self._get_parent_index() 1836 except ValueError: 1837 raise errors.NoSuchRevisionInTree(self._dirstate, self._revision_id) 1838 return self._dirstate._get_entry(parent_index, fileid_utf8=file_id, 1839 path_utf8=path) 1840 1841 def _generate_inventory(self): 1842 """Create and set self.inventory from the dirstate object. 1843 1844 (So this is only called the first time the inventory is requested for 1845 this tree; it then remains in memory until it's out of date.) 1846 1847 This is relatively expensive: we have to walk the entire dirstate. 1848 """ 1849 if not self._locked: 1850 raise AssertionError( 1851 'cannot generate inventory of an unlocked ' 1852 'dirstate revision tree') 1853 # separate call for profiling - makes it clear where the costs are. 1854 self._dirstate._read_dirblocks_if_needed() 1855 if self._revision_id not in self._dirstate.get_parent_ids(): 1856 raise AssertionError( 1857 'parent %s has disappeared from %s' % ( 1858 self._revision_id, self._dirstate.get_parent_ids())) 1859 parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1 1860 # This is identical now to the WorkingTree _generate_inventory except 1861 # for the tree index use. 1862 root_key, current_entry = self._dirstate._get_entry( 1863 parent_index, path_utf8=b'') 1864 current_id = root_key[2] 1865 if current_entry[parent_index][0] != b'd': 1866 raise AssertionError() 1867 inv = Inventory(root_id=current_id, revision_id=self._revision_id) 1868 inv.root.revision = current_entry[parent_index][4] 1869 # Turn some things into local variables 1870 minikind_to_kind = dirstate.DirState._minikind_to_kind 1871 factory = entry_factory 1872 utf8_decode = cache_utf8._utf8_decode 1873 inv_byid = inv._byid 1874 # we could do this straight out of the dirstate; it might be fast 1875 # and should be profiled - RBC 20070216 1876 parent_ies = {b'': inv.root} 1877 for block in self._dirstate._dirblocks[1:]: # skip root 1878 dirname = block[0] 1879 try: 1880 parent_ie = parent_ies[dirname] 1881 except KeyError: 1882 # all the paths in this block are not versioned in this tree 1883 continue 1884 for key, entry in block[1]: 1885 (minikind, fingerprint, size, executable, 1886 revid) = entry[parent_index] 1887 if minikind in (b'a', b'r'): # absent, relocated 1888 # not this tree 1889 continue 1890 name = key[1] 1891 name_unicode = utf8_decode(name)[0] 1892 file_id = key[2] 1893 kind = minikind_to_kind[minikind] 1894 inv_entry = factory[kind](file_id, name_unicode, 1895 parent_ie.file_id) 1896 inv_entry.revision = revid 1897 if kind == 'file': 1898 inv_entry.executable = executable 1899 inv_entry.text_size = size 1900 inv_entry.text_sha1 = fingerprint 1901 elif kind == 'directory': 1902 parent_ies[(dirname + b'/' + name).strip(b'/')] = inv_entry 1903 elif kind == 'symlink': 1904 inv_entry.symlink_target = utf8_decode(fingerprint)[0] 1905 elif kind == 'tree-reference': 1906 inv_entry.reference_revision = fingerprint or None 1907 else: 1908 raise AssertionError( 1909 "cannot convert entry %r into an InventoryEntry" 1910 % entry) 1911 # These checks cost us around 40ms on a 55k entry tree 1912 if file_id in inv_byid: 1913 raise AssertionError( 1914 'file_id %s already in' 1915 ' inventory as %s' % (file_id, inv_byid[file_id])) 1916 if name_unicode in parent_ie.children: 1917 raise AssertionError('name %r already in parent' 1918 % (name_unicode,)) 1919 inv_byid[file_id] = inv_entry 1920 parent_ie.children[name_unicode] = inv_entry 1921 self._inventory = inv 1922 1923 def get_file_mtime(self, path): 1924 """Return the modification time for this record. 1925 1926 We return the timestamp of the last-changed revision. 1927 """ 1928 # Make sure the file exists 1929 entry = self._get_entry(path=path) 1930 if entry == (None, None): # do we raise? 1931 raise errors.NoSuchFile(path) 1932 parent_index = self._get_parent_index() 1933 last_changed_revision = entry[1][parent_index][4] 1934 try: 1935 rev = self._repository.get_revision(last_changed_revision) 1936 except errors.NoSuchRevision: 1937 raise FileTimestampUnavailable(path) 1938 return rev.timestamp 1939 1940 def get_file_sha1(self, path, stat_value=None): 1941 entry = self._get_entry(path=path) 1942 parent_index = self._get_parent_index() 1943 parent_details = entry[1][parent_index] 1944 if parent_details[0] == b'f': 1945 return parent_details[1] 1946 return None 1947 1948 def get_file_revision(self, path): 1949 with self.lock_read(): 1950 inv, inv_file_id = self._path2inv_file_id(path) 1951 return inv.get_entry(inv_file_id).revision 1952 1953 def get_file(self, path): 1954 return BytesIO(self.get_file_text(path)) 1955 1956 def get_file_size(self, path): 1957 """See Tree.get_file_size""" 1958 inv, inv_file_id = self._path2inv_file_id(path) 1959 return inv.get_entry(inv_file_id).text_size 1960 1961 def get_file_text(self, path): 1962 content = None 1963 for _, content_iter in self.iter_files_bytes([(path, None)]): 1964 if content is not None: 1965 raise AssertionError('iter_files_bytes returned' 1966 ' too many entries') 1967 # For each entry returned by iter_files_bytes, we must consume the 1968 # content_iter before we step the files iterator. 1969 content = b''.join(content_iter) 1970 if content is None: 1971 raise AssertionError('iter_files_bytes did not return' 1972 ' the requested data') 1973 return content 1974 1975 def get_reference_revision(self, path): 1976 inv, inv_file_id = self._path2inv_file_id(path) 1977 return inv.get_entry(inv_file_id).reference_revision 1978 1979 def iter_files_bytes(self, desired_files): 1980 """See Tree.iter_files_bytes. 1981 1982 This version is implemented on top of Repository.iter_files_bytes""" 1983 parent_index = self._get_parent_index() 1984 repo_desired_files = [] 1985 for path, identifier in desired_files: 1986 entry = self._get_entry(path=path) 1987 if entry == (None, None): 1988 raise errors.NoSuchFile(path) 1989 repo_desired_files.append((entry[0][2], entry[1][parent_index][4], 1990 identifier)) 1991 return self._repository.iter_files_bytes(repo_desired_files) 1992 1993 def get_symlink_target(self, path): 1994 entry = self._get_entry(path=path) 1995 if entry is None: 1996 raise errors.NoSuchFile(tree=self, path=path) 1997 parent_index = self._get_parent_index() 1998 if entry[1][parent_index][0] != b'l': 1999 return None 2000 else: 2001 target = entry[1][parent_index][1] 2002 target = target.decode('utf8') 2003 return target 2004 2005 def get_revision_id(self): 2006 """Return the revision id for this tree.""" 2007 return self._revision_id 2008 2009 def _get_root_inventory(self): 2010 if self._inventory is not None: 2011 return self._inventory 2012 self._must_be_locked() 2013 self._generate_inventory() 2014 return self._inventory 2015 2016 root_inventory = property(_get_root_inventory, 2017 doc="Inventory of this Tree") 2018 2019 def get_parent_ids(self): 2020 """The parents of a tree in the dirstate are not cached.""" 2021 return self._repository.get_revision(self._revision_id).parent_ids 2022 2023 def has_filename(self, filename): 2024 return bool(self.path2id(filename)) 2025 2026 def kind(self, path): 2027 entry = self._get_entry(path=path)[1] 2028 if entry is None: 2029 raise errors.NoSuchFile(path) 2030 parent_index = self._get_parent_index() 2031 return dirstate.DirState._minikind_to_kind[entry[parent_index][0]] 2032 2033 def stored_kind(self, path): 2034 """See Tree.stored_kind""" 2035 return self.kind(path) 2036 2037 def path_content_summary(self, path): 2038 """See Tree.path_content_summary.""" 2039 inv, inv_file_id = self._path2inv_file_id(path) 2040 if inv_file_id is None: 2041 return ('missing', None, None, None) 2042 entry = inv.get_entry(inv_file_id) 2043 kind = entry.kind 2044 if kind == 'file': 2045 return (kind, entry.text_size, entry.executable, entry.text_sha1) 2046 elif kind == 'symlink': 2047 return (kind, None, None, entry.symlink_target) 2048 else: 2049 return (kind, None, None, None) 2050 2051 def is_executable(self, path): 2052 inv, inv_file_id = self._path2inv_file_id(path) 2053 if inv_file_id is None: 2054 raise errors.NoSuchFile(path) 2055 ie = inv.get_entry(inv_file_id) 2056 if ie.kind != "file": 2057 return False 2058 return ie.executable 2059 2060 def is_locked(self): 2061 return self._locked 2062 2063 def list_files(self, include_root=False, from_dir=None, recursive=True, 2064 recurse_nested=False): 2065 # The only files returned by this are those from the version 2066 if from_dir is None: 2067 from_dir_id = None 2068 inv = self.root_inventory 2069 else: 2070 inv, from_dir_id = self._path2inv_file_id(from_dir) 2071 if from_dir_id is None: 2072 # Directory not versioned 2073 return iter([]) 2074 def iter_entries(inv): 2075 entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive) 2076 if inv.root is not None and not include_root and from_dir is None: 2077 # skip the root for compatibility with the current apis. 2078 next(entries) 2079 for path, entry in entries: 2080 if entry.kind == 'tree-reference' and recurse_nested: 2081 subtree = self._get_nested_tree( 2082 path, entry.file_id, entry.reference_revision) 2083 for subpath, status, kind, entry in subtree.list_files( 2084 include_root=True, recursive=recursive, 2085 recurse_nested=recurse_nested): 2086 if subpath: 2087 full_subpath = osutils.pathjoin(path, subpath) 2088 else: 2089 full_subpath = path 2090 yield full_subpath, status, kind, entry 2091 else: 2092 yield path, 'V', entry.kind, entry 2093 return iter_entries(inv) 2094 2095 def lock_read(self): 2096 """Lock the tree for a set of operations. 2097 2098 :return: A breezy.lock.LogicalLockResult. 2099 """ 2100 if not self._locked: 2101 self._repository.lock_read() 2102 if self._dirstate._lock_token is None: 2103 self._dirstate.lock_read() 2104 self._dirstate_locked = True 2105 self._locked += 1 2106 return LogicalLockResult(self.unlock) 2107 2108 def _must_be_locked(self): 2109 if not self._locked: 2110 raise errors.ObjectNotLocked(self) 2111 2112 def path2id(self, path): 2113 """Return the id for path in this tree.""" 2114 # lookup by path: faster than splitting and walking the ivnentory. 2115 if isinstance(path, list): 2116 if path == []: 2117 path = [""] 2118 path = osutils.pathjoin(*path) 2119 with self.lock_read(): 2120 entry = self._get_entry(path=path) 2121 if entry == (None, None): 2122 nested_tree, subpath = self.get_containing_nested_tree(path) 2123 if nested_tree is not None: 2124 return nested_tree.path2id(subpath) 2125 return None 2126 return entry[0][2] 2127 2128 def unlock(self): 2129 """Unlock, freeing any cache memory used during the lock.""" 2130 # outside of a lock, the inventory is suspect: release it. 2131 self._locked -= 1 2132 if not self._locked: 2133 self._inventory = None 2134 self._locked = 0 2135 if self._dirstate_locked: 2136 self._dirstate.unlock() 2137 self._dirstate_locked = False 2138 self._repository.unlock() 2139 2140 def supports_tree_reference(self): 2141 with self.lock_read(): 2142 return self._repo_supports_tree_reference 2143 2144 def walkdirs(self, prefix=""): 2145 # TODO: jam 20070215 This is the lazy way by using the RevisionTree 2146 # implementation based on an inventory. 2147 # This should be cleaned up to use the much faster Dirstate code 2148 # So for now, we just build up the parent inventory, and extract 2149 # it the same way RevisionTree does. 2150 _directory = 'directory' 2151 inv = self._get_root_inventory() 2152 top_id = inv.path2id(prefix) 2153 if top_id is None: 2154 pending = [] 2155 else: 2156 pending = [(prefix, top_id)] 2157 while pending: 2158 dirblock = [] 2159 relpath, file_id = pending.pop() 2160 # 0 - relpath, 1- file-id 2161 if relpath: 2162 relroot = relpath + '/' 2163 else: 2164 relroot = "" 2165 # FIXME: stash the node in pending 2166 entry = inv.get_entry(file_id) 2167 subdirs = [] 2168 for name, child in entry.sorted_children(): 2169 toppath = relroot + name 2170 dirblock.append((toppath, name, child.kind, None, child.kind)) 2171 if child.kind == _directory: 2172 subdirs.append((toppath, child.file_id)) 2173 yield relpath, dirblock 2174 # push the user specified dirs from dirblock 2175 pending.extend(reversed(subdirs)) 2176 2177 2178class InterDirStateTree(InterInventoryTree): 2179 """Fast path optimiser for changes_from with dirstate trees. 2180 2181 This is used only when both trees are in the dirstate working file, and 2182 the source is any parent within the dirstate, and the destination is 2183 the current working tree of the same dirstate. 2184 """ 2185 # this could be generalized to allow comparisons between any trees in the 2186 # dirstate, and possibly between trees stored in different dirstates. 2187 2188 def __init__(self, source, target): 2189 super(InterDirStateTree, self).__init__(source, target) 2190 if not InterDirStateTree.is_compatible(source, target): 2191 raise Exception("invalid source %r and target %r" % 2192 (source, target)) 2193 2194 @staticmethod 2195 def make_source_parent_tree(source, target): 2196 """Change the source tree into a parent of the target.""" 2197 revid = source.commit('record tree') 2198 target.branch.fetch(source.branch, revid) 2199 target.set_parent_ids([revid]) 2200 return target.basis_tree(), target 2201 2202 @classmethod 2203 def make_source_parent_tree_python_dirstate(klass, test_case, source, target): 2204 result = klass.make_source_parent_tree(source, target) 2205 result[1]._iter_changes = dirstate.ProcessEntryPython 2206 return result 2207 2208 @classmethod 2209 def make_source_parent_tree_compiled_dirstate(klass, test_case, source, 2210 target): 2211 from .tests.test__dirstate_helpers import \ 2212 compiled_dirstate_helpers_feature 2213 test_case.requireFeature(compiled_dirstate_helpers_feature) 2214 from ._dirstate_helpers_pyx import ProcessEntryC 2215 result = klass.make_source_parent_tree(source, target) 2216 result[1]._iter_changes = ProcessEntryC 2217 return result 2218 2219 _matching_from_tree_format = WorkingTreeFormat4() 2220 _matching_to_tree_format = WorkingTreeFormat4() 2221 2222 @classmethod 2223 def _test_mutable_trees_to_test_trees(klass, test_case, source, target): 2224 # This method shouldn't be called, because we have python and C 2225 # specific flavours. 2226 raise NotImplementedError 2227 2228 def iter_changes(self, include_unchanged=False, 2229 specific_files=None, pb=None, extra_trees=[], 2230 require_versioned=True, want_unversioned=False): 2231 """Return the changes from source to target. 2232 2233 :return: An iterator that yields tuples. See InterTree.iter_changes 2234 for details. 2235 :param specific_files: An optional list of file paths to restrict the 2236 comparison to. When mapping filenames to ids, all matches in all 2237 trees (including optional extra_trees) are used, and all children of 2238 matched directories are included. 2239 :param include_unchanged: An optional boolean requesting the inclusion of 2240 unchanged entries in the result. 2241 :param extra_trees: An optional list of additional trees to use when 2242 mapping the contents of specific_files (paths) to file_ids. 2243 :param require_versioned: If True, all files in specific_files must be 2244 versioned in one of source, target, extra_trees or 2245 PathsNotVersionedError is raised. 2246 :param want_unversioned: Should unversioned files be returned in the 2247 output. An unversioned file is defined as one with (False, False) 2248 for the versioned pair. 2249 """ 2250 # TODO: handle extra trees in the dirstate. 2251 if (extra_trees or specific_files == []): 2252 # we can't fast-path these cases (yet) 2253 return super(InterDirStateTree, self).iter_changes( 2254 include_unchanged, specific_files, pb, extra_trees, 2255 require_versioned, want_unversioned=want_unversioned) 2256 parent_ids = self.target.get_parent_ids() 2257 if not (self.source._revision_id in parent_ids 2258 or self.source._revision_id == _mod_revision.NULL_REVISION): 2259 raise AssertionError( 2260 "revision {%s} is not stored in {%s}, but %s " 2261 "can only be used for trees stored in the dirstate" 2262 % (self.source._revision_id, self.target, self.iter_changes)) 2263 target_index = 0 2264 if self.source._revision_id == _mod_revision.NULL_REVISION: 2265 source_index = None 2266 indices = (target_index,) 2267 else: 2268 if not (self.source._revision_id in parent_ids): 2269 raise AssertionError( 2270 "Failure: source._revision_id: %s not in target.parent_ids(%s)" % ( 2271 self.source._revision_id, parent_ids)) 2272 source_index = 1 + parent_ids.index(self.source._revision_id) 2273 indices = (source_index, target_index) 2274 2275 if specific_files is None: 2276 specific_files = {''} 2277 2278 # -- get the state object and prepare it. 2279 state = self.target.current_dirstate() 2280 state._read_dirblocks_if_needed() 2281 if require_versioned: 2282 # -- check all supplied paths are versioned in a search tree. -- 2283 not_versioned = [] 2284 for path in specific_files: 2285 path_entries = state._entries_for_path(path.encode('utf-8')) 2286 if not path_entries: 2287 # this specified path is not present at all: error 2288 not_versioned.append(path) 2289 continue 2290 found_versioned = False 2291 # for each id at this path 2292 for entry in path_entries: 2293 # for each tree. 2294 for index in indices: 2295 if entry[1][index][0] != b'a': # absent 2296 found_versioned = True 2297 # all good: found a versioned cell 2298 break 2299 if not found_versioned: 2300 # none of the indexes was not 'absent' at all ids for this 2301 # path. 2302 not_versioned.append(path) 2303 if len(not_versioned) > 0: 2304 raise errors.PathsNotVersionedError(not_versioned) 2305 2306 # remove redundancy in supplied specific_files to prevent over-scanning 2307 # make all specific_files utf8 2308 search_specific_files_utf8 = set() 2309 for path in osutils.minimum_path_selection(specific_files): 2310 # Note, if there are many specific files, using cache_utf8 2311 # would be good here. 2312 search_specific_files_utf8.add(path.encode('utf8')) 2313 2314 iter_changes = self.target._iter_changes( 2315 include_unchanged, self.target._supports_executable(), 2316 search_specific_files_utf8, state, source_index, target_index, 2317 want_unversioned, self.target) 2318 return iter_changes.iter_changes() 2319 2320 @staticmethod 2321 def is_compatible(source, target): 2322 # the target must be a dirstate working tree 2323 if not isinstance(target, DirStateWorkingTree): 2324 return False 2325 # the source must be a revtree or dirstate rev tree. 2326 if not isinstance(source, 2327 (revisiontree.RevisionTree, DirStateRevisionTree)): 2328 return False 2329 # the source revid must be in the target dirstate 2330 if not (source._revision_id == _mod_revision.NULL_REVISION or 2331 source._revision_id in target.get_parent_ids()): 2332 # TODO: what about ghosts? it may well need to 2333 # check for them explicitly. 2334 return False 2335 return True 2336 2337 2338InterTree.register_optimiser(InterDirStateTree) 2339 2340 2341class Converter3to4(object): 2342 """Perform an in-place upgrade of format 3 to format 4 trees.""" 2343 2344 def __init__(self): 2345 self.target_format = WorkingTreeFormat4() 2346 2347 def convert(self, tree): 2348 # lock the control files not the tree, so that we dont get tree 2349 # on-unlock behaviours, and so that noone else diddles with the 2350 # tree during upgrade. 2351 tree._control_files.lock_write() 2352 try: 2353 tree.read_working_inventory() 2354 self.create_dirstate_data(tree) 2355 self.update_format(tree) 2356 self.remove_xml_files(tree) 2357 finally: 2358 tree._control_files.unlock() 2359 2360 def create_dirstate_data(self, tree): 2361 """Create the dirstate based data for tree.""" 2362 local_path = tree.controldir.get_workingtree_transport( 2363 None).local_abspath('dirstate') 2364 state = dirstate.DirState.from_tree(tree, local_path) 2365 state.save() 2366 state.unlock() 2367 2368 def remove_xml_files(self, tree): 2369 """Remove the oldformat 3 data.""" 2370 transport = tree.controldir.get_workingtree_transport(None) 2371 for path in ['basis-inventory-cache', 'inventory', 'last-revision', 2372 'pending-merges', 'stat-cache']: 2373 try: 2374 transport.delete(path) 2375 except errors.NoSuchFile: 2376 # some files are optional - just deal. 2377 pass 2378 2379 def update_format(self, tree): 2380 """Change the format marker.""" 2381 tree._transport.put_bytes('format', 2382 self.target_format.as_string(), 2383 mode=tree.controldir._get_file_mode()) 2384 2385 2386class Converter4to5(object): 2387 """Perform an in-place upgrade of format 4 to format 5 trees.""" 2388 2389 def __init__(self): 2390 self.target_format = WorkingTreeFormat5() 2391 2392 def convert(self, tree): 2393 # lock the control files not the tree, so that we don't get tree 2394 # on-unlock behaviours, and so that no-one else diddles with the 2395 # tree during upgrade. 2396 tree._control_files.lock_write() 2397 try: 2398 self.update_format(tree) 2399 finally: 2400 tree._control_files.unlock() 2401 2402 def update_format(self, tree): 2403 """Change the format marker.""" 2404 tree._transport.put_bytes('format', 2405 self.target_format.as_string(), 2406 mode=tree.controldir._get_file_mode()) 2407 2408 2409class Converter4or5to6(object): 2410 """Perform an in-place upgrade of format 4 or 5 to format 6 trees.""" 2411 2412 def __init__(self): 2413 self.target_format = WorkingTreeFormat6() 2414 2415 def convert(self, tree): 2416 # lock the control files not the tree, so that we don't get tree 2417 # on-unlock behaviours, and so that no-one else diddles with the 2418 # tree during upgrade. 2419 tree._control_files.lock_write() 2420 try: 2421 self.init_custom_control_files(tree) 2422 self.update_format(tree) 2423 finally: 2424 tree._control_files.unlock() 2425 2426 def init_custom_control_files(self, tree): 2427 """Initialize custom control files.""" 2428 tree._transport.put_bytes('views', b'', 2429 mode=tree.controldir._get_file_mode()) 2430 2431 def update_format(self, tree): 2432 """Change the format marker.""" 2433 tree._transport.put_bytes('format', 2434 self.target_format.as_string(), 2435 mode=tree.controldir._get_file_mode()) 2436