22
22
WorkingTree.open(dir).
25
from cStringIO import StringIO
25
from __future__ import absolute_import
29
from bzrlib.lazy_import import lazy_import
30
from ..lazy_import import lazy_import
30
31
lazy_import(globals(), """
39
conflicts as _mod_conflicts,
43
filters as _mod_filters,
43
46
revision as _mod_revision,
52
from breezy.bzr import (
53
from bzrlib.decorators import needs_read_lock, needs_write_lock
54
from bzrlib.filters import filtered_input_file, internal_size_sha_file_byname
55
from bzrlib.inventory import Inventory, ROOT_ID, entry_factory
56
from bzrlib.mutabletree import needs_tree_write_lock
57
from bzrlib.osutils import (
58
from ..decorators import needs_read_lock, needs_write_lock
59
from .inventory import Inventory, ROOT_ID, entry_factory
60
from ..lock import LogicalLockResult
61
from ..lockable_files import LockableFiles
62
from ..lockdir import LockDir
63
from .inventorytree import (
65
InventoryRevisionTree,
67
from ..mutabletree import (
69
needs_tree_write_lock,
71
from ..osutils import (
64
from bzrlib.trace import mutter
65
from bzrlib.transport.local import LocalTransport
66
from bzrlib.tree import InterTree
67
from bzrlib.tree import Tree
68
from bzrlib.workingtree import WorkingTree, WorkingTree3, WorkingTreeFormat3
71
class DirStateWorkingTree(WorkingTree3):
78
from ..sixish import (
82
from ..transport.local import LocalTransport
86
from ..workingtree import (
89
from .workingtree import (
91
WorkingTreeFormatMetaDir,
95
class DirStateWorkingTree(InventoryWorkingTree):
72
97
def __init__(self, basedir,
74
99
_control_files=None,
77
102
"""Construct a WorkingTree for basedir.
79
104
If the branch is not supplied, it is opened automatically.
82
107
would be meaningless).
84
109
self._format = _format
110
self.controldir = _controldir
86
111
basedir = safe_unicode(basedir)
87
mutter("opening working tree %r", basedir)
112
trace.mutter("opening working tree %r", basedir)
88
113
self._branch = branch
89
114
self.basedir = realpath(basedir)
90
115
# if branch is at our basedir and is a format 6 or less
110
135
"""See MutableTree._add."""
111
136
state = self.current_dirstate()
112
137
for f, file_id, kind in zip(files, ids, kinds):
114
139
if self.path2id(f):
115
140
# special case tree root handling.
116
if f == '' and self.path2id(f) == ROOT_ID:
117
state.set_path_id('', generate_ids.gen_file_id(f))
141
if f == b'' and self.path2id(f) == ROOT_ID:
142
state.set_path_id(b'', generate_ids.gen_file_id(f))
119
144
if file_id is None:
120
145
file_id = generate_ids.gen_file_id(f)
121
146
# deliberately add the file with no cached stat or sha1
122
147
# - on the first access it will be gathered, and we can
123
148
# always change this once tests are all passing.
124
state.add(f, file_id, kind, None, '')
149
state.add(f, file_id, kind, None, b'')
125
150
self._make_dirty(reset_inventory=True)
152
def _get_check_refs(self):
153
"""Return the references needed to perform a check of this tree."""
154
return [('trees', self.last_revision())]
127
156
def _make_dirty(self, reset_inventory):
128
157
"""Make the tree state dirty.
182
211
def _comparison_data(self, entry, path):
183
212
kind, executable, stat_value = \
184
WorkingTree3._comparison_data(self, entry, path)
213
WorkingTree._comparison_data(self, entry, path)
185
214
# it looks like a plain directory, but it's really a reference -- see
187
216
if (self._repo_supports_tree_reference and kind == 'directory'
193
222
def commit(self, message=None, revprops=None, *args, **kwargs):
194
223
# mark the tree as dirty post commit - commit
195
224
# can change the current versioned list by doing deletes.
196
result = WorkingTree3.commit(self, message, revprops, *args, **kwargs)
225
result = WorkingTree.commit(self, message, revprops, *args, **kwargs)
197
226
self._make_dirty(reset_inventory=True)
216
245
if self._dirstate is not None:
217
246
return self._dirstate
218
local_path = self.bzrdir.get_workingtree_transport(None
247
local_path = self.controldir.get_workingtree_transport(None
219
248
).local_abspath('dirstate')
220
249
self._dirstate = dirstate.DirState.on_file(local_path,
221
self._sha1_provider())
250
self._sha1_provider(), self._worth_saving_limit())
222
251
return self._dirstate
224
253
def _sha1_provider(self):
265
def _worth_saving_limit(self):
266
"""How many hash changes are ok before we must save the dirstate.
268
:return: an integer. -1 means never save.
270
conf = self.get_config_stack()
271
return conf.get('bzr.workingtree.worth_saving_limit')
236
273
def filter_unversioned_files(self, paths):
237
274
"""Filter out paths that are versioned.
280
317
state._read_dirblocks_if_needed()
281
318
root_key, current_entry = self._get_entry(path='')
282
319
current_id = root_key[2]
283
if not (current_entry[0][0] == 'd'): # directory
320
if not (current_entry[0][0] == b'd'): # directory
284
321
raise AssertionError(current_entry)
285
322
inv = Inventory(root_id=current_id)
286
323
# Turn some things into local variables
301
338
for key, entry in block[1]:
302
339
minikind, link_or_sha1, size, executable, stat = entry[0]
303
if minikind in ('a', 'r'): # absent, relocated
340
if minikind in (b'a', b'r'): # absent, relocated
304
341
# a parent tree only entry
318
355
#inv_entry.text_sha1 = sha1
319
356
elif kind == 'directory':
320
357
# add this entry to the parent map.
321
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
358
parent_ies[(dirname + b'/' + name).strip(b'/')] = inv_entry
322
359
elif kind == 'tree-reference':
323
360
if not self._repo_supports_tree_reference:
324
361
raise errors.UnsupportedOperation(
368
405
state = self.current_dirstate()
369
406
if stat_value is None:
371
stat_value = os.lstat(file_abspath)
408
stat_value = osutils.lstat(file_abspath)
373
410
if e.errno == errno.ENOENT:
377
414
link_or_sha1 = dirstate.update_entry(state, entry, file_abspath,
378
415
stat_value=stat_value)
379
if entry[1][0][0] == 'f':
416
if entry[1][0][0] == b'f':
380
417
if link_or_sha1 is None:
381
418
file_obj, statvalue = self.get_file_with_stat(file_id, path)
400
437
self._generate_inventory()
401
438
return self._inventory
403
inventory = property(_get_inventory,
404
doc="Inventory of this Tree")
440
root_inventory = property(_get_root_inventory,
441
"Root inventory of this tree")
407
444
def get_parent_ids(self):
455
492
return False # Missing entries are not executable
456
493
return entry[1][0][3] # Executable?
458
if not osutils.supports_executable():
459
def is_executable(self, file_id, path=None):
460
"""Test if a file is executable or not.
495
def is_executable(self, file_id, path=None):
496
"""Test if a file is executable or not.
462
Note: The caller is expected to take a read-lock before calling this.
498
Note: The caller is expected to take a read-lock before calling this.
500
if not self._supports_executable():
464
501
entry = self._get_entry(file_id=file_id, path=path)
465
502
if entry == (None, None):
467
504
return entry[1][0][3]
469
_is_executable_from_path_and_stat = \
470
_is_executable_from_path_and_stat_from_basis
472
def is_executable(self, file_id, path=None):
473
"""Test if a file is executable or not.
475
Note: The caller is expected to take a read-lock before calling this.
477
506
self._must_be_locked()
479
508
path = self.id2path(file_id)
480
mode = os.lstat(self.abspath(path)).st_mode
509
mode = osutils.lstat(self.abspath(path)).st_mode
481
510
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
483
512
def all_file_ids(self):
485
514
self._must_be_locked()
487
516
for key, tree_details in self.current_dirstate()._iter_entries():
488
if tree_details[0][0] in ('a', 'r'): # relocated
517
if tree_details[0][0] in (b'a', b'r'): # relocated
490
519
result.add(key[2])
501
530
for key, tree_details in self.current_dirstate()._iter_entries():
502
if tree_details[0][0] in ('a', 'r'): # absent, relocated
531
if tree_details[0][0] in (b'a', b'r'): # absent, relocated
503
532
# not relevant to the working tree
505
534
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
515
544
for key, tree_details in self.current_dirstate()._iter_entries():
516
if tree_details[0][0] in ('a', 'r'): # absent, relocated
545
if tree_details[0][0] in (b'a', b'r'): # absent, relocated
517
546
# not relevant to the working tree
527
556
# path is missing on disk.
530
def _observed_sha1(self, file_id, path, (sha1, statvalue)):
559
def _observed_sha1(self, file_id, path, sha_and_stat):
531
560
"""See MutableTree._observed_sha1."""
532
561
state = self.current_dirstate()
533
562
entry = self._get_entry(file_id=file_id, path=path)
534
state._observed_sha1(entry, sha1, statvalue)
563
state._observed_sha1(entry, *sha_and_stat)
536
565
def kind(self, file_id):
537
566
"""Return the kind of a file.
567
596
return _mod_revision.NULL_REVISION
569
598
def lock_read(self):
570
"""See Branch.lock_read, and WorkingTree.unlock."""
599
"""See Branch.lock_read, and WorkingTree.unlock.
601
:return: A breezy.lock.LogicalLockResult.
571
603
self.branch.lock_read()
573
605
self._control_files.lock_read()
607
640
self.branch.unlock()
642
return LogicalLockResult(self.unlock)
610
644
def lock_tree_write(self):
611
"""See MutableTree.lock_tree_write, and WorkingTree.unlock."""
645
"""See MutableTree.lock_tree_write, and WorkingTree.unlock.
647
:return: A breezy.lock.LogicalLockResult.
612
649
self.branch.lock_read()
613
self._lock_self_write()
650
return self._lock_self_write()
615
652
def lock_write(self):
616
"""See MutableTree.lock_write, and WorkingTree.unlock."""
653
"""See MutableTree.lock_write, and WorkingTree.unlock.
655
:return: A breezy.lock.LogicalLockResult.
617
657
self.branch.lock_write()
618
self._lock_self_write()
658
return self._lock_self_write()
620
660
@needs_tree_write_lock
621
661
def move(self, from_paths, to_dir, after=False):
624
664
if not from_paths:
626
666
state = self.current_dirstate()
627
if isinstance(from_paths, basestring):
667
if isinstance(from_paths, (str, bytes)):
628
668
raise ValueError()
629
669
to_dir_utf8 = to_dir.encode('utf8')
630
670
to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
653
693
if self._inventory is not None:
654
694
update_inventory = True
695
inv = self.root_inventory
656
696
to_dir_id = to_entry[0][2]
657
697
to_dir_ie = inv[to_dir_id]
659
699
update_inventory = False
701
# GZ 2017-03-28: The rollbacks variable was shadowed in the loop below
702
# missing those added here, but there's also no test coverage for this.
703
rollbacks = cleanup.ObjectWithCleanups()
662
704
def move_one(old_entry, from_path_utf8, minikind, executable,
663
705
fingerprint, packed_stat, size,
664
706
to_block, to_key, to_path_utf8):
665
707
state._make_absent(old_entry)
666
708
from_key = old_entry[0]
668
lambda:state.update_minimal(from_key,
670
executable=executable,
671
fingerprint=fingerprint,
672
packed_stat=packed_stat,
674
path_utf8=from_path_utf8))
709
rollbacks.add_cleanup(
710
state.update_minimal,
713
executable=executable,
714
fingerprint=fingerprint,
715
packed_stat=packed_stat,
717
path_utf8=from_path_utf8)
675
718
state.update_minimal(to_key,
677
720
executable=executable,
681
724
path_utf8=to_path_utf8)
682
725
added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
683
726
new_entry = to_block[1][added_entry_index]
684
rollbacks.append(lambda:state._make_absent(new_entry))
727
rollbacks.add_cleanup(state._make_absent, new_entry)
686
729
for from_rel in from_paths:
687
730
# from_rel is 'pathinroot/foo/bar'
727
770
raise errors.RenameFailedFilesExist(from_rel, to_rel)
730
def rollback_rename():
731
"""A single rename has failed, roll it back."""
732
# roll back everything, even if we encounter trouble doing one
735
# TODO: at least log the other exceptions rather than just
736
# losing them mbp 20070307
738
for rollback in reversed(rollbacks):
742
exc_info = sys.exc_info()
744
raise exc_info[0], exc_info[1], exc_info[2]
746
772
# perform the disk move first - its the most likely failure point.
748
774
from_rel_abs = self.abspath(from_rel)
749
775
to_rel_abs = self.abspath(to_rel)
751
777
osutils.rename(from_rel_abs, to_rel_abs)
753
779
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
754
rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
780
rollbacks.add_cleanup(osutils.rename, to_rel_abs, from_rel_abs)
756
782
# perform the rename in the inventory next if needed: its easy
760
786
from_entry = inv[from_id]
761
787
current_parent = from_entry.parent_id
762
788
inv.rename(from_id, to_dir_id, from_tail)
764
lambda: inv.rename(from_id, current_parent, from_tail))
789
rollbacks.add_cleanup(
790
inv.rename, from_id, current_parent, from_tail)
765
791
# finally do the rename in the dirstate, which is a little
766
792
# tricky to rollback, but least likely to need it.
767
793
old_block_index, old_entry_index, dir_present, file_present = \
784
810
to_path_utf8=to_rel_utf8)
787
813
def update_dirblock(from_dir, to_key, to_dir_utf8):
788
814
"""Recursively update all entries in this dirblock."""
790
816
raise AssertionError("renaming root not supported")
791
817
from_key = (from_dir, '')
792
818
from_block_idx, present = \
813
839
from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
814
840
to_path_utf8 = osutils.pathjoin(to_dir_utf8, entry[0][1])
815
841
minikind = cur_details[0]
842
if minikind in (b'a', b'r'):
817
843
# Deleted children of a renamed directory
818
844
# Do not need to be updated.
819
845
# Children that have been renamed out of this
828
854
to_block=to_block,
830
856
to_path_utf8=to_path_utf8)
832
858
# We need to move all the children of this
834
860
update_dirblock(from_path_utf8, to_key,
836
862
update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
864
rollbacks.cleanup_now()
840
866
result.append((from_rel, to_rel))
841
state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
867
state._mark_modified()
842
868
self._make_dirty(reset_inventory=False)
855
881
def path2id(self, path):
856
882
"""Return the id for path in this tree."""
883
if isinstance(path, list):
886
path = osutils.pathjoin(*path)
857
887
path = path.strip('/')
858
888
entry = self._get_entry(path=path)
859
889
if entry == (None, None):
884
914
# -- get the state object and prepare it.
885
915
state = self.current_dirstate()
886
916
if False and (state._dirblock_state == dirstate.DirState.NOT_IN_MEMORY
887
and '' not in paths):
917
and b'' not in paths):
888
918
paths2ids = self._paths2ids_using_bisect
890
920
paths2ids = self._paths2ids_in_memory
899
929
"""Return a list with all the entries that match path for all ids.
901
931
dirname, basename = os.path.split(path)
902
key = (dirname, basename, '')
932
key = (dirname, basename, b'')
903
933
block_index, present = state._find_block_index_from_key(key)
905
935
# the block which should contain path is absent.
927
957
for entry in path_entries:
929
959
for index in search_indexes:
930
if entry[1][index][0] != 'a': # absent
960
if entry[1][index][0] != b'a': # absent
931
961
found_versioned = True
932
962
# all good: found a versioned cell
937
967
all_versioned = False
939
969
if not all_versioned:
940
raise errors.PathsNotVersionedError(paths)
970
raise errors.PathsNotVersionedError(
971
[p.decode('utf-8') for p in paths])
941
972
# -- remove redundancy in supplied paths to prevent over-scanning --
942
973
search_paths = osutils.minimum_path_selection(paths)
955
986
nothing. Otherwise add the id to found_ids.
957
988
for index in search_indexes:
958
if entry[1][index][0] == 'r': # relocated
989
if entry[1][index][0] == b'r': # relocated
959
990
if not osutils.is_inside_any(searched_paths, entry[1][index][1]):
960
991
search_paths.add(entry[1][index][1])
961
elif entry[1][index][0] != 'a': # absent
992
elif entry[1][index][0] != b'a': # absent
962
993
found_ids.add(entry[0][2])
963
994
while search_paths:
964
995
current_root = search_paths.pop()
972
1003
for entry in root_entries:
973
1004
_process_entry(entry)
974
initial_key = (current_root, '', '')
1005
initial_key = (current_root, b'', b'')
975
1006
block_index, _ = state._find_block_index_from_key(initial_key)
976
1007
while (block_index < len(state._dirblocks) and
977
1008
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
992
1023
found_dir_names = set(dir_name_id[:2] for dir_name_id in found)
993
1024
for dir_name in split_paths:
994
1025
if dir_name not in found_dir_names:
995
raise errors.PathsNotVersionedError(paths)
1026
raise errors.PathsNotVersionedError(
1027
[p.decode('utf-8') for p in paths])
997
for dir_name_id, trees_info in found.iteritems():
1029
for dir_name_id, trees_info in viewitems(found):
998
1030
for index in search_indexes:
999
if trees_info[index][0] not in ('r', 'a'):
1031
if trees_info[index][0] not in (b'r', b'a'):
1000
1032
found_ids.add(dir_name_id[2])
1001
1033
return found_ids
1101
1133
_mod_revision.NULL_REVISION)))
1102
1134
ghosts.append(rev_id)
1103
1135
accepted_revisions.add(rev_id)
1104
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1137
if (len(real_trees) == 1
1139
and self.branch.repository._format.fast_deltas
1140
and isinstance(real_trees[0][1], InventoryRevisionTree)
1141
and self.get_parent_ids()):
1142
rev_id, rev_tree = real_trees[0]
1143
basis_id = self.get_parent_ids()[0]
1144
# There are times when basis_tree won't be in
1145
# self.branch.repository, (switch, for example)
1147
basis_tree = self.branch.repository.revision_tree(basis_id)
1148
except errors.NoSuchRevision:
1149
# Fall back to the set_parent_trees(), since we can't use
1150
# _make_delta if we can't get the RevisionTree
1153
delta = rev_tree.root_inventory._make_delta(
1154
basis_tree.root_inventory)
1155
dirstate.update_basis_by_delta(delta, rev_id)
1158
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1105
1159
self._make_dirty(reset_inventory=False)
1107
1161
def _set_root_id(self, file_id):
1108
1162
"""See WorkingTree.set_root_id."""
1109
1163
state = self.current_dirstate()
1110
state.set_path_id('', file_id)
1164
state.set_path_id(b'', file_id)
1111
1165
if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1112
1166
self._make_dirty(reset_inventory=True)
1128
1182
def unlock(self):
1129
1183
"""Unlock in format 4 trees needs to write the entire dirstate."""
1130
# do non-implementation specific cleanup
1133
1184
if self._control_files._lock_count == 1:
1185
# do non-implementation specific cleanup
1134
1188
# eventually we should do signature checking during read locks for
1135
1189
# dirstate updates.
1136
1190
if self._control_files._lock_mode == 'w':
1230
1284
ids_to_unversion.remove(entry[0][2])
1231
1285
block_index += 1
1232
1286
if ids_to_unversion:
1233
raise errors.NoSuchId(self, iter(ids_to_unversion).next())
1287
raise errors.NoSuchId(self, next(iter(ids_to_unversion)))
1234
1288
self._make_dirty(reset_inventory=False)
1235
1289
# have to change the legacy inventory too.
1236
1290
if self._inventory is not None:
1237
1291
for file_id in file_ids:
1238
self._inventory.remove_recursive_id(file_id)
1292
if self._inventory.has_id(file_id):
1293
self._inventory.remove_recursive_id(file_id)
1240
1295
@needs_tree_write_lock
1241
1296
def rename_one(self, from_rel, to_rel, after=False):
1242
1297
"""See WorkingTree.rename_one"""
1244
WorkingTree.rename_one(self, from_rel, to_rel, after)
1299
super(DirStateWorkingTree, self).rename_one(from_rel, to_rel, after)
1246
1301
@needs_tree_write_lock
1247
1302
def apply_inventory_delta(self, changes):
1273
1328
# being created.
1274
1329
self._inventory = None
1275
1330
# generate a delta,
1276
delta = inv._make_delta(self.inventory)
1331
delta = inv._make_delta(self.root_inventory)
1277
1332
# and apply it.
1278
1333
self.apply_inventory_delta(delta)
1279
1334
if had_inventory:
1280
1335
self._inventory = inv
1338
@needs_tree_write_lock
1339
def reset_state(self, revision_ids=None):
1340
"""Reset the state of the working tree.
1342
This does a hard-reset to a last-known-good state. This is a way to
1343
fix if something got corrupted (like the .bzr/checkout/dirstate file)
1345
if revision_ids is None:
1346
revision_ids = self.get_parent_ids()
1347
if not revision_ids:
1348
base_tree = self.branch.repository.revision_tree(
1349
_mod_revision.NULL_REVISION)
1352
trees = list(zip(revision_ids,
1353
self.branch.repository.revision_trees(revision_ids)))
1354
base_tree = trees[0][1]
1355
state = self.current_dirstate()
1356
# We don't support ghosts yet
1357
state.set_state_from_scratch(base_tree.root_inventory, trees, [])
1284
1360
class ContentFilterAwareSHA1Provider(dirstate.SHA1Provider):
1290
1366
"""See dirstate.SHA1Provider.sha1()."""
1291
1367
filters = self.tree._content_filter_stack(
1292
1368
self.tree.relpath(osutils.safe_unicode(abspath)))
1293
return internal_size_sha_file_byname(abspath, filters)[1]
1369
return _mod_filters.internal_size_sha_file_byname(abspath, filters)[1]
1295
1371
def stat_and_sha1(self, abspath):
1296
1372
"""See dirstate.SHA1Provider.stat_and_sha1()."""
1301
1377
statvalue = os.fstat(file_obj.fileno())
1303
file_obj = filtered_input_file(file_obj, filters)
1379
file_obj = _mod_filters.filtered_input_file(file_obj, filters)
1304
1380
sha1 = osutils.size_sha_file(file_obj)[1]
1306
1382
file_obj.close()
1317
1393
def _file_content_summary(self, path, stat_result):
1318
1394
# This is to support the somewhat obsolete path_content_summary method
1319
1395
# with content filtering: see
1320
# <https://bugs.edge.launchpad.net/bzr/+bug/415508>.
1396
# <https://bugs.launchpad.net/bzr/+bug/415508>.
1322
1398
# If the dirstate cache is up to date and knows the hash and size,
1336
1412
class WorkingTree4(DirStateWorkingTree):
1337
1413
"""This is the Format 4 working tree.
1339
This differs from WorkingTree3 by:
1415
This differs from WorkingTree by:
1340
1416
- Having a consolidated internal dirstate, stored in a
1341
1417
randomly-accessible sorted file on disk.
1342
1418
- Not having a regular inventory attribute. One can be synthesized
1370
1446
return views.PathBasedViews(self)
1373
class DirStateWorkingTreeFormat(WorkingTreeFormat3):
1375
def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
1449
class DirStateWorkingTreeFormat(WorkingTreeFormatMetaDir):
1451
missing_parent_conflicts = True
1453
supports_versioned_directories = True
1455
_lock_class = LockDir
1456
_lock_file_name = 'lock'
1458
def _open_control_files(self, a_controldir):
1459
transport = a_controldir.get_workingtree_transport(None)
1460
return LockableFiles(transport, self._lock_file_name,
1463
def initialize(self, a_controldir, revision_id=None, from_branch=None,
1376
1464
accelerator_tree=None, hardlink=False):
1377
1465
"""See WorkingTreeFormat.initialize().
1379
1467
:param revision_id: allows creating a working tree at a different
1380
revision than the branch is at.
1468
revision than the branch is at.
1381
1469
:param accelerator_tree: A tree which can be used for retrieving file
1382
1470
contents more quickly than the revision tree, i.e. a workingtree.
1383
1471
The revision tree will be used for cases where accelerator_tree's
1388
1476
These trees get an initial random root id, if their repository supports
1389
1477
rich root data, TREE_ROOT otherwise.
1391
if not isinstance(a_bzrdir.transport, LocalTransport):
1392
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1393
transport = a_bzrdir.get_workingtree_transport(self)
1394
control_files = self._open_control_files(a_bzrdir)
1479
if not isinstance(a_controldir.transport, LocalTransport):
1480
raise errors.NotLocalUrl(a_controldir.transport.base)
1481
transport = a_controldir.get_workingtree_transport(self)
1482
control_files = self._open_control_files(a_controldir)
1395
1483
control_files.create_lock()
1396
1484
control_files.lock_write()
1397
transport.put_bytes('format', self.get_format_string(),
1398
mode=a_bzrdir._get_file_mode())
1485
transport.put_bytes('format', self.as_string(),
1486
mode=a_controldir._get_file_mode())
1399
1487
if from_branch is not None:
1400
1488
branch = from_branch
1402
branch = a_bzrdir.open_branch()
1490
branch = a_controldir.open_branch()
1403
1491
if revision_id is None:
1404
1492
revision_id = branch.last_revision()
1405
1493
local_path = transport.local_abspath('dirstate')
1407
1495
state = dirstate.DirState.initialize(local_path)
1410
wt = self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
1498
wt = self._tree_class(a_controldir.root_transport.local_abspath('.'),
1501
_controldir=a_controldir,
1414
1502
_control_files=control_files)
1416
1504
wt.lock_tree_write()
1460
1548
transform.build_tree(basis, wt, accelerator_tree,
1461
1549
hardlink=hardlink,
1462
1550
delta_from_tree=delta_from_tree)
1551
for hook in MutableTree.hooks['post_build_tree']:
1476
1566
:param wt: the WorkingTree object
1479
def _open(self, a_bzrdir, control_files):
1569
def open(self, a_controldir, _found=False):
1570
"""Return the WorkingTree object for a_controldir
1572
_found is a private parameter, do not use it. It is used to indicate
1573
if format probing has already been done.
1576
# we are being called directly and must probe.
1577
raise NotImplementedError
1578
if not isinstance(a_controldir.transport, LocalTransport):
1579
raise errors.NotLocalUrl(a_controldir.transport.base)
1580
wt = self._open(a_controldir, self._open_control_files(a_controldir))
1583
def _open(self, a_controldir, control_files):
1480
1584
"""Open the tree itself.
1482
:param a_bzrdir: the dir for the tree.
1586
:param a_controldir: the dir for the tree.
1483
1587
:param control_files: the control files for the tree.
1485
return self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
1486
branch=a_bzrdir.open_branch(),
1589
return self._tree_class(a_controldir.root_transport.local_abspath('.'),
1590
branch=a_controldir.open_branch(),
1592
_controldir=a_controldir,
1489
1593
_control_files=control_files)
1491
1595
def __get_matchingbzrdir(self):
1494
1598
def _get_matchingbzrdir(self):
1495
1599
"""Overrideable method to get a bzrdir for testing."""
1496
1600
# please test against something that will let us do tree references
1497
return bzrdir.format_registry.make_bzrdir(
1498
'dirstate-with-subtree')
1601
return controldir.format_registry.make_controldir(
1602
'development-subtree')
1500
1604
_matchingbzrdir = property(__get_matchingbzrdir)
1507
1611
- exists within a metadir controlling .bzr
1508
1612
- includes an explicit version marker for the workingtree control
1509
files, separate from the BzrDir format
1613
files, separate from the ControlDir format
1510
1614
- modifies the hash cache format
1511
1615
- is new in bzr 0.15
1512
1616
- uses a LockDir to guard access to it.
1517
1621
_tree_class = WorkingTree4
1519
def get_format_string(self):
1624
def get_format_string(cls):
1520
1625
"""See WorkingTreeFormat.get_format_string()."""
1521
1626
return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1534
1639
_tree_class = WorkingTree5
1536
def get_format_string(self):
1642
def get_format_string(cls):
1537
1643
"""See WorkingTreeFormat.get_format_string()."""
1538
1644
return "Bazaar Working Tree Format 5 (bzr 1.11)\n"
1554
1660
_tree_class = WorkingTree6
1556
def get_format_string(self):
1663
def get_format_string(cls):
1557
1664
"""See WorkingTreeFormat.get_format_string()."""
1558
1665
return "Bazaar Working Tree Format 6 (bzr 1.14)\n"
1564
1671
def _init_custom_control_files(self, wt):
1565
1672
"""Subclasses with custom control files should override this method."""
1566
wt._transport.put_bytes('views', '', mode=wt.bzrdir._get_file_mode())
1673
wt._transport.put_bytes('views', b'',
1674
mode=wt.controldir._get_file_mode())
1568
1676
def supports_content_filtering(self):
1571
1679
def supports_views(self):
1575
class DirStateRevisionTree(Tree):
1682
def _get_matchingbzrdir(self):
1683
"""Overrideable method to get a bzrdir for testing."""
1684
# We use 'development-subtree' instead of '2a', because we have a
1685
# few tests that want to test tree references
1686
return controldir.format_registry.make_controldir('development-subtree')
1689
class DirStateRevisionTree(InventoryTree):
1576
1690
"""A revision tree pulling the inventory from a dirstate.
1578
1692
Note that this is one of the historical (ie revision) trees cached in the
1597
1711
def annotate_iter(self, file_id,
1598
1712
default_revision=_mod_revision.CURRENT_REVISION):
1599
1713
"""See Tree.annotate_iter"""
1600
text_key = (file_id, self.inventory[file_id].revision)
1714
text_key = (file_id, self.get_file_revision(file_id))
1601
1715
annotations = self._repository.texts.annotate(text_key)
1602
1716
return [(key[-1], line) for (key, line) in annotations]
1604
def _get_ancestors(self, default_revision):
1605
return set(self._repository.get_ancestry(self._revision_id,
1607
1718
def _comparison_data(self, entry, path):
1608
1719
"""See Tree._comparison_data."""
1609
1720
if entry is None:
1662
1773
if path is not None:
1663
1774
path = path.encode('utf8')
1664
1775
parent_index = self._get_parent_index()
1665
return self._dirstate._get_entry(parent_index, fileid_utf8=file_id, path_utf8=path)
1776
return self._dirstate._get_entry(parent_index, fileid_utf8=file_id,
1667
1779
def _generate_inventory(self):
1668
1780
"""Create and set self.inventory from the dirstate object.
1687
1799
# for the tree index use.
1688
1800
root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1689
1801
current_id = root_key[2]
1690
if current_entry[parent_index][0] != 'd':
1802
if current_entry[parent_index][0] != b'd':
1691
1803
raise AssertionError()
1692
1804
inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1693
1805
inv.root.revision = current_entry[parent_index][4]
1709
1821
for key, entry in block[1]:
1710
1822
minikind, fingerprint, size, executable, revid = entry[parent_index]
1711
if minikind in ('a', 'r'): # absent, relocated
1823
if minikind in (b'a', b'r'): # absent, relocated
1712
1824
# not this tree
1723
1835
inv_entry.text_size = size
1724
1836
inv_entry.text_sha1 = fingerprint
1725
1837
elif kind == 'directory':
1726
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1838
parent_ies[(dirname + b'/' + name).strip(b'/')] = inv_entry
1727
1839
elif kind == 'symlink':
1728
inv_entry.executable = False
1729
inv_entry.text_size = None
1730
1840
inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1731
1841
elif kind == 'tree-reference':
1732
1842
inv_entry.reference_revision = fingerprint or None
1752
1862
# Make sure the file exists
1753
1863
entry = self._get_entry(file_id, path=path)
1754
1864
if entry == (None, None): # do we raise?
1865
raise errors.NoSuchId(self, file_id)
1756
1866
parent_index = self._get_parent_index()
1757
1867
last_changed_revision = entry[1][parent_index][4]
1765
1875
entry = self._get_entry(file_id=file_id, path=path)
1766
1876
parent_index = self._get_parent_index()
1767
1877
parent_details = entry[1][parent_index]
1768
if parent_details[0] == 'f':
1878
if parent_details[0] == b'f':
1769
1879
return parent_details[1]
1883
def get_file_revision(self, file_id):
1884
inv, inv_file_id = self._unpack_file_id(file_id)
1885
return inv[inv_file_id].revision
1772
1887
def get_file(self, file_id, path=None):
1773
return StringIO(self.get_file_text(file_id))
1888
return BytesIO(self.get_file_text(file_id))
1775
1890
def get_file_size(self, file_id):
1776
1891
"""See Tree.get_file_size"""
1777
return self.inventory[file_id].text_size
1892
inv, inv_file_id = self._unpack_file_id(file_id)
1893
return inv[inv_file_id].text_size
1779
1895
def get_file_text(self, file_id, path=None):
1780
_, content = list(self.iter_files_bytes([(file_id, None)]))[0]
1781
return ''.join(content)
1897
for _, content_iter in self.iter_files_bytes([(file_id, None)]):
1898
if content is not None:
1899
raise AssertionError('iter_files_bytes returned'
1900
' too many entries')
1901
# For each entry returned by iter_files_bytes, we must consume the
1902
# content_iter before we step the files iterator.
1903
content = ''.join(content_iter)
1905
raise AssertionError('iter_files_bytes did not return'
1906
' the requested data')
1783
1909
def get_reference_revision(self, file_id, path=None):
1784
return self.inventory[file_id].reference_revision
1910
inv, inv_file_id = self._unpack_file_id(file_id)
1911
return inv[inv_file_id].reference_revision
1786
1913
def iter_files_bytes(self, desired_files):
1787
1914
"""See Tree.iter_files_bytes.
1798
1925
return self._repository.iter_files_bytes(repo_desired_files)
1800
def get_symlink_target(self, file_id):
1927
def get_symlink_target(self, file_id, path=None):
1801
1928
entry = self._get_entry(file_id=file_id)
1802
1929
parent_index = self._get_parent_index()
1803
if entry[1][parent_index][0] != 'l':
1930
if entry[1][parent_index][0] != b'l':
1806
1933
target = entry[1][parent_index][1]
1811
1938
"""Return the revision id for this tree."""
1812
1939
return self._revision_id
1814
def _get_inventory(self):
1941
def _get_root_inventory(self):
1815
1942
if self._inventory is not None:
1816
1943
return self._inventory
1817
1944
self._must_be_locked()
1818
1945
self._generate_inventory()
1819
1946
return self._inventory
1821
inventory = property(_get_inventory,
1948
root_inventory = property(_get_root_inventory,
1822
1949
doc="Inventory of this Tree")
1824
1951
def get_parent_ids(self):
1842
1969
def path_content_summary(self, path):
1843
1970
"""See Tree.path_content_summary."""
1844
id = self.inventory.path2id(path)
1971
inv, inv_file_id = self._path2inv_file_id(path)
1972
if inv_file_id is None:
1846
1973
return ('missing', None, None, None)
1847
entry = self._inventory[id]
1974
entry = inv[inv_file_id]
1848
1975
kind = entry.kind
1849
1976
if kind == 'file':
1850
1977
return (kind, entry.text_size, entry.executable, entry.text_sha1)
1854
1981
return (kind, None, None, None)
1856
1983
def is_executable(self, file_id, path=None):
1857
ie = self.inventory[file_id]
1984
inv, inv_file_id = self._unpack_file_id(file_id)
1985
ie = inv[inv_file_id]
1858
1986
if ie.kind != "file":
1860
1988
return ie.executable
1990
def is_locked(self):
1862
1993
def list_files(self, include_root=False, from_dir=None, recursive=True):
1863
1994
# We use a standard implementation, because DirStateRevisionTree is
1864
1995
# dealing with one of the parents of the current state
1865
inv = self._get_inventory()
1866
1996
if from_dir is None:
1997
inv = self.root_inventory
1867
1998
from_dir_id = None
1869
from_dir_id = inv.path2id(from_dir)
2000
inv, from_dir_id = self._path2inv_file_id(from_dir)
1870
2001
if from_dir_id is None:
1871
2002
# Directory not versioned
2004
# FIXME: Support nested trees
1873
2005
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
1874
2006
if inv.root is not None and not include_root and from_dir is None:
1876
2008
for path, entry in entries:
1877
2009
yield path, 'V', entry.kind, entry.file_id, entry
1879
2011
def lock_read(self):
1880
"""Lock the tree for a set of operations."""
2012
"""Lock the tree for a set of operations.
2014
:return: A breezy.lock.LogicalLockResult.
1881
2016
if not self._locked:
1882
2017
self._repository.lock_read()
1883
2018
if self._dirstate._lock_token is None:
1884
2019
self._dirstate.lock_read()
1885
2020
self._dirstate_locked = True
1886
2021
self._locked += 1
2022
return LogicalLockResult(self.unlock)
1888
2024
def _must_be_locked(self):
1889
2025
if not self._locked:
1893
2029
def path2id(self, path):
1894
2030
"""Return the id for path in this tree."""
1895
2031
# lookup by path: faster than splitting and walking the ivnentory.
2032
if isinstance(path, list):
2035
path = osutils.pathjoin(*path)
1896
2036
entry = self._get_entry(path=path)
1897
2037
if entry == (None, None):
1921
2061
# So for now, we just build up the parent inventory, and extract
1922
2062
# it the same way RevisionTree does.
1923
2063
_directory = 'directory'
1924
inv = self._get_inventory()
2064
inv = self._get_root_inventory()
1925
2065
top_id = inv.path2id(prefix)
1926
2066
if top_id is None:
1962
2102
def __init__(self, source, target):
1963
2103
super(InterDirStateTree, self).__init__(source, target)
1964
2104
if not InterDirStateTree.is_compatible(source, target):
1965
raise Exception, "invalid source %r and target %r" % (source, target)
2105
raise Exception("invalid source %r and target %r" % (source, target))
1968
2108
def make_source_parent_tree(source, target):
1969
2109
"""Change the source tree into a parent of the target."""
1970
2110
revid = source.commit('record tree')
1971
target.branch.repository.fetch(source.branch.repository, revid)
2111
target.branch.fetch(source.branch, revid)
1972
2112
target.set_parent_ids([revid])
1973
2113
return target.basis_tree(), target
1982
2122
def make_source_parent_tree_compiled_dirstate(klass, test_case, source,
1984
from bzrlib.tests.test__dirstate_helpers import \
2124
from ..tests.test__dirstate_helpers import \
1985
2125
compiled_dirstate_helpers_feature
1986
2126
test_case.requireFeature(compiled_dirstate_helpers_feature)
1987
from bzrlib._dirstate_helpers_pyx import ProcessEntryC
2127
from ._dirstate_helpers_pyx import ProcessEntryC
1988
2128
result = klass.make_source_parent_tree(source, target)
1989
2129
result[1]._iter_changes = ProcessEntryC
2053
2193
specific_files_utf8.add(path.encode('utf8'))
2054
2194
specific_files = specific_files_utf8
2056
specific_files = set([''])
2196
specific_files = {b''}
2057
2197
# -- specific_files is now a utf8 path set --
2059
2199
# -- get the state object and prepare it.
2066
2206
path_entries = state._entries_for_path(path)
2067
2207
if not path_entries:
2068
2208
# this specified path is not present at all: error
2069
not_versioned.append(path)
2209
not_versioned.append(path.decode('utf-8'))
2071
2211
found_versioned = False
2072
2212
# for each id at this path
2073
2213
for entry in path_entries:
2074
2214
# for each tree.
2075
2215
for index in indices:
2076
if entry[1][index][0] != 'a': # absent
2216
if entry[1][index][0] != b'a': # absent
2077
2217
found_versioned = True
2078
2218
# all good: found a versioned cell
2080
2220
if not found_versioned:
2081
2221
# none of the indexes was not 'absent' at all ids for this
2083
not_versioned.append(path)
2223
not_versioned.append(path.decode('utf-8'))
2084
2224
if len(not_versioned) > 0:
2085
2225
raise errors.PathsNotVersionedError(not_versioned)
2086
2226
# -- remove redundancy in supplied specific_files to prevent over-scanning --
2134
2274
def create_dirstate_data(self, tree):
2135
2275
"""Create the dirstate based data for tree."""
2136
local_path = tree.bzrdir.get_workingtree_transport(None
2276
local_path = tree.controldir.get_workingtree_transport(None
2137
2277
).local_abspath('dirstate')
2138
2278
state = dirstate.DirState.from_tree(tree, local_path)
2142
2282
def remove_xml_files(self, tree):
2143
2283
"""Remove the oldformat 3 data."""
2144
transport = tree.bzrdir.get_workingtree_transport(None)
2284
transport = tree.controldir.get_workingtree_transport(None)
2145
2285
for path in ['basis-inventory-cache', 'inventory', 'last-revision',
2146
2286
'pending-merges', 'stat-cache']:
2153
2293
def update_format(self, tree):
2154
2294
"""Change the format marker."""
2155
2295
tree._transport.put_bytes('format',
2156
self.target_format.get_format_string(),
2157
mode=tree.bzrdir._get_file_mode())
2296
self.target_format.as_string(),
2297
mode=tree.controldir._get_file_mode())
2160
2300
class Converter4to5(object):
2176
2316
def update_format(self, tree):
2177
2317
"""Change the format marker."""
2178
2318
tree._transport.put_bytes('format',
2179
self.target_format.get_format_string(),
2180
mode=tree.bzrdir._get_file_mode())
2319
self.target_format.as_string(),
2320
mode=tree.controldir._get_file_mode())
2183
2323
class Converter4or5to6(object):
2200
2340
def init_custom_control_files(self, tree):
2201
2341
"""Initialize custom control files."""
2202
tree._transport.put_bytes('views', '',
2203
mode=tree.bzrdir._get_file_mode())
2342
tree._transport.put_bytes('views', b'',
2343
mode=tree.controldir._get_file_mode())
2205
2345
def update_format(self, tree):
2206
2346
"""Change the format marker."""
2207
2347
tree._transport.put_bytes('format',
2208
self.target_format.get_format_string(),
2209
mode=tree.bzrdir._get_file_mode())
2348
self.target_format.as_string(),
2349
mode=tree.controldir._get_file_mode())