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(), """
40
conflicts as _mod_conflicts,
45
filters as _mod_filters,
43
48
revision as _mod_revision,
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.lock import LogicalLockResult
57
from bzrlib.mutabletree import needs_tree_write_lock
58
from bzrlib.osutils import (
56
from .decorators import needs_read_lock, needs_write_lock
57
from .inventory import Inventory, ROOT_ID, entry_factory
58
from .inventorytree import (
60
InventoryRevisionTree,
62
from .lock import LogicalLockResult
63
from .lockable_files import LockableFiles
64
from .lockdir import LockDir
65
from .mutabletree import (
67
needs_tree_write_lock,
69
from .osutils import (
65
from bzrlib.trace import mutter
66
from bzrlib.transport.local import LocalTransport
67
from bzrlib.tree import InterTree
68
from bzrlib.tree import Tree
69
from bzrlib.workingtree import WorkingTree, WorkingTree3, WorkingTreeFormat3
72
class DirStateWorkingTree(WorkingTree3):
80
from .transport.local import LocalTransport
84
from .workingtree import (
87
from .bzrworkingtree import (
89
WorkingTreeFormatMetaDir,
93
class DirStateWorkingTree(InventoryWorkingTree):
73
95
def __init__(self, basedir,
75
97
_control_files=None,
83
105
would be meaningless).
85
107
self._format = _format
108
self.controldir = _bzrdir
87
109
basedir = safe_unicode(basedir)
88
mutter("opening working tree %r", basedir)
110
trace.mutter("opening working tree %r", basedir)
89
111
self._branch = branch
90
112
self.basedir = realpath(basedir)
91
113
# if branch is at our basedir and is a format 6 or less
111
133
"""See MutableTree._add."""
112
134
state = self.current_dirstate()
113
135
for f, file_id, kind in zip(files, ids, kinds):
115
137
if self.path2id(f):
116
138
# special case tree root handling.
117
if f == '' and self.path2id(f) == ROOT_ID:
118
state.set_path_id('', generate_ids.gen_file_id(f))
139
if f == b'' and self.path2id(f) == ROOT_ID:
140
state.set_path_id(b'', generate_ids.gen_file_id(f))
120
142
if file_id is None:
121
143
file_id = generate_ids.gen_file_id(f)
122
144
# deliberately add the file with no cached stat or sha1
123
145
# - on the first access it will be gathered, and we can
124
146
# always change this once tests are all passing.
125
state.add(f, file_id, kind, None, '')
147
state.add(f, file_id, kind, None, b'')
126
148
self._make_dirty(reset_inventory=True)
150
def _get_check_refs(self):
151
"""Return the references needed to perform a check of this tree."""
152
return [('trees', self.last_revision())]
128
154
def _make_dirty(self, reset_inventory):
129
155
"""Make the tree state dirty.
183
209
def _comparison_data(self, entry, path):
184
210
kind, executable, stat_value = \
185
WorkingTree3._comparison_data(self, entry, path)
211
WorkingTree._comparison_data(self, entry, path)
186
212
# it looks like a plain directory, but it's really a reference -- see
188
214
if (self._repo_supports_tree_reference and kind == 'directory'
194
220
def commit(self, message=None, revprops=None, *args, **kwargs):
195
221
# mark the tree as dirty post commit - commit
196
222
# can change the current versioned list by doing deletes.
197
result = WorkingTree3.commit(self, message, revprops, *args, **kwargs)
223
result = WorkingTree.commit(self, message, revprops, *args, **kwargs)
198
224
self._make_dirty(reset_inventory=True)
217
243
if self._dirstate is not None:
218
244
return self._dirstate
219
local_path = self.bzrdir.get_workingtree_transport(None
245
local_path = self.controldir.get_workingtree_transport(None
220
246
).local_abspath('dirstate')
221
247
self._dirstate = dirstate.DirState.on_file(local_path,
222
self._sha1_provider())
248
self._sha1_provider(), self._worth_saving_limit())
223
249
return self._dirstate
225
251
def _sha1_provider(self):
263
def _worth_saving_limit(self):
264
"""How many hash changes are ok before we must save the dirstate.
266
:return: an integer. -1 means never save.
268
conf = self.get_config_stack()
269
return conf.get('bzr.workingtree.worth_saving_limit')
237
271
def filter_unversioned_files(self, paths):
238
272
"""Filter out paths that are versioned.
281
315
state._read_dirblocks_if_needed()
282
316
root_key, current_entry = self._get_entry(path='')
283
317
current_id = root_key[2]
284
if not (current_entry[0][0] == 'd'): # directory
318
if not (current_entry[0][0] == b'd'): # directory
285
319
raise AssertionError(current_entry)
286
320
inv = Inventory(root_id=current_id)
287
321
# Turn some things into local variables
302
336
for key, entry in block[1]:
303
337
minikind, link_or_sha1, size, executable, stat = entry[0]
304
if minikind in ('a', 'r'): # absent, relocated
338
if minikind in (b'a', b'r'): # absent, relocated
305
339
# a parent tree only entry
319
353
#inv_entry.text_sha1 = sha1
320
354
elif kind == 'directory':
321
355
# add this entry to the parent map.
322
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
356
parent_ies[(dirname + b'/' + name).strip(b'/')] = inv_entry
323
357
elif kind == 'tree-reference':
324
358
if not self._repo_supports_tree_reference:
325
359
raise errors.UnsupportedOperation(
369
403
state = self.current_dirstate()
370
404
if stat_value is None:
372
stat_value = os.lstat(file_abspath)
406
stat_value = osutils.lstat(file_abspath)
374
408
if e.errno == errno.ENOENT:
378
412
link_or_sha1 = dirstate.update_entry(state, entry, file_abspath,
379
413
stat_value=stat_value)
380
if entry[1][0][0] == 'f':
414
if entry[1][0][0] == b'f':
381
415
if link_or_sha1 is None:
382
416
file_obj, statvalue = self.get_file_with_stat(file_id, path)
401
435
self._generate_inventory()
402
436
return self._inventory
404
inventory = property(_get_inventory,
405
doc="Inventory of this Tree")
438
root_inventory = property(_get_root_inventory,
439
"Root inventory of this tree")
408
442
def get_parent_ids(self):
456
490
return False # Missing entries are not executable
457
491
return entry[1][0][3] # Executable?
459
if not osutils.supports_executable():
460
def is_executable(self, file_id, path=None):
461
"""Test if a file is executable or not.
493
def is_executable(self, file_id, path=None):
494
"""Test if a file is executable or not.
463
Note: The caller is expected to take a read-lock before calling this.
496
Note: The caller is expected to take a read-lock before calling this.
498
if not self._supports_executable():
465
499
entry = self._get_entry(file_id=file_id, path=path)
466
500
if entry == (None, None):
468
502
return entry[1][0][3]
470
_is_executable_from_path_and_stat = \
471
_is_executable_from_path_and_stat_from_basis
473
def is_executable(self, file_id, path=None):
474
"""Test if a file is executable or not.
476
Note: The caller is expected to take a read-lock before calling this.
478
504
self._must_be_locked()
480
506
path = self.id2path(file_id)
481
mode = os.lstat(self.abspath(path)).st_mode
507
mode = osutils.lstat(self.abspath(path)).st_mode
482
508
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
484
510
def all_file_ids(self):
486
512
self._must_be_locked()
488
514
for key, tree_details in self.current_dirstate()._iter_entries():
489
if tree_details[0][0] in ('a', 'r'): # relocated
515
if tree_details[0][0] in (b'a', b'r'): # relocated
491
517
result.add(key[2])
502
528
for key, tree_details in self.current_dirstate()._iter_entries():
503
if tree_details[0][0] in ('a', 'r'): # absent, relocated
529
if tree_details[0][0] in (b'a', b'r'): # absent, relocated
504
530
# not relevant to the working tree
506
532
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
516
542
for key, tree_details in self.current_dirstate()._iter_entries():
517
if tree_details[0][0] in ('a', 'r'): # absent, relocated
543
if tree_details[0][0] in (b'a', b'r'): # absent, relocated
518
544
# not relevant to the working tree
528
554
# path is missing on disk.
531
def _observed_sha1(self, file_id, path, (sha1, statvalue)):
557
def _observed_sha1(self, file_id, path, sha_and_stat):
532
558
"""See MutableTree._observed_sha1."""
533
559
state = self.current_dirstate()
534
560
entry = self._get_entry(file_id=file_id, path=path)
535
state._observed_sha1(entry, sha1, statvalue)
561
state._observed_sha1(entry, *sha_and_stat)
537
563
def kind(self, file_id):
538
564
"""Return the kind of a file.
616
642
def lock_tree_write(self):
617
643
"""See MutableTree.lock_tree_write, and WorkingTree.unlock.
619
:return: A bzrlib.lock.LogicalLockResult.
645
:return: A breezy.lock.LogicalLockResult.
621
647
self.branch.lock_read()
622
648
return self._lock_self_write()
624
650
def lock_write(self):
625
651
"""See MutableTree.lock_write, and WorkingTree.unlock.
627
:return: A bzrlib.lock.LogicalLockResult.
653
:return: A breezy.lock.LogicalLockResult.
629
655
self.branch.lock_write()
630
656
return self._lock_self_write()
665
691
if self._inventory is not None:
666
692
update_inventory = True
693
inv = self.root_inventory
668
694
to_dir_id = to_entry[0][2]
669
695
to_dir_ie = inv[to_dir_id]
671
697
update_inventory = False
699
# GZ 2017-03-28: The rollbacks variable was shadowed in the loop below
700
# missing those added here, but there's also no test coverage for this.
701
rollbacks = cleanup.ObjectWithCleanups()
674
702
def move_one(old_entry, from_path_utf8, minikind, executable,
675
703
fingerprint, packed_stat, size,
676
704
to_block, to_key, to_path_utf8):
677
705
state._make_absent(old_entry)
678
706
from_key = old_entry[0]
680
lambda:state.update_minimal(from_key,
682
executable=executable,
683
fingerprint=fingerprint,
684
packed_stat=packed_stat,
686
path_utf8=from_path_utf8))
707
rollbacks.add_cleanup(
708
state.update_minimal,
711
executable=executable,
712
fingerprint=fingerprint,
713
packed_stat=packed_stat,
715
path_utf8=from_path_utf8)
687
716
state.update_minimal(to_key,
689
718
executable=executable,
693
722
path_utf8=to_path_utf8)
694
723
added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
695
724
new_entry = to_block[1][added_entry_index]
696
rollbacks.append(lambda:state._make_absent(new_entry))
725
rollbacks.add_cleanup(state._make_absent, new_entry)
698
727
for from_rel in from_paths:
699
728
# from_rel is 'pathinroot/foo/bar'
739
768
raise errors.RenameFailedFilesExist(from_rel, to_rel)
742
def rollback_rename():
743
"""A single rename has failed, roll it back."""
744
# roll back everything, even if we encounter trouble doing one
747
# TODO: at least log the other exceptions rather than just
748
# losing them mbp 20070307
750
for rollback in reversed(rollbacks):
754
exc_info = sys.exc_info()
756
raise exc_info[0], exc_info[1], exc_info[2]
758
770
# perform the disk move first - its the most likely failure point.
760
772
from_rel_abs = self.abspath(from_rel)
761
773
to_rel_abs = self.abspath(to_rel)
763
775
osutils.rename(from_rel_abs, to_rel_abs)
765
777
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
766
rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
778
rollbacks.add_cleanup(osutils.rename, to_rel_abs, from_rel_abs)
768
780
# perform the rename in the inventory next if needed: its easy
772
784
from_entry = inv[from_id]
773
785
current_parent = from_entry.parent_id
774
786
inv.rename(from_id, to_dir_id, from_tail)
776
lambda: inv.rename(from_id, current_parent, from_tail))
787
rollbacks.add_cleanup(
788
inv.rename, from_id, current_parent, from_tail)
777
789
# finally do the rename in the dirstate, which is a little
778
790
# tricky to rollback, but least likely to need it.
779
791
old_block_index, old_entry_index, dir_present, file_present = \
796
808
to_path_utf8=to_rel_utf8)
799
811
def update_dirblock(from_dir, to_key, to_dir_utf8):
800
812
"""Recursively update all entries in this dirblock."""
802
814
raise AssertionError("renaming root not supported")
803
815
from_key = (from_dir, '')
804
816
from_block_idx, present = \
825
837
from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
826
838
to_path_utf8 = osutils.pathjoin(to_dir_utf8, entry[0][1])
827
839
minikind = cur_details[0]
840
if minikind in (b'a', b'r'):
829
841
# Deleted children of a renamed directory
830
842
# Do not need to be updated.
831
843
# Children that have been renamed out of this
840
852
to_block=to_block,
842
854
to_path_utf8=to_path_utf8)
844
856
# We need to move all the children of this
846
858
update_dirblock(from_path_utf8, to_key,
848
860
update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
862
rollbacks.cleanup_now()
852
864
result.append((from_rel, to_rel))
853
state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
865
state._mark_modified()
854
866
self._make_dirty(reset_inventory=False)
867
879
def path2id(self, path):
868
880
"""Return the id for path in this tree."""
881
if isinstance(path, list):
884
path = osutils.pathjoin(*path)
869
885
path = path.strip('/')
870
886
entry = self._get_entry(path=path)
871
887
if entry == (None, None):
896
912
# -- get the state object and prepare it.
897
913
state = self.current_dirstate()
898
914
if False and (state._dirblock_state == dirstate.DirState.NOT_IN_MEMORY
899
and '' not in paths):
915
and b'' not in paths):
900
916
paths2ids = self._paths2ids_using_bisect
902
918
paths2ids = self._paths2ids_in_memory
911
927
"""Return a list with all the entries that match path for all ids.
913
929
dirname, basename = os.path.split(path)
914
key = (dirname, basename, '')
930
key = (dirname, basename, b'')
915
931
block_index, present = state._find_block_index_from_key(key)
917
933
# the block which should contain path is absent.
939
955
for entry in path_entries:
941
957
for index in search_indexes:
942
if entry[1][index][0] != 'a': # absent
958
if entry[1][index][0] != b'a': # absent
943
959
found_versioned = True
944
960
# all good: found a versioned cell
949
965
all_versioned = False
951
967
if not all_versioned:
952
raise errors.PathsNotVersionedError(paths)
968
raise errors.PathsNotVersionedError(
969
[p.decode('utf-8') for p in paths])
953
970
# -- remove redundancy in supplied paths to prevent over-scanning --
954
971
search_paths = osutils.minimum_path_selection(paths)
967
984
nothing. Otherwise add the id to found_ids.
969
986
for index in search_indexes:
970
if entry[1][index][0] == 'r': # relocated
987
if entry[1][index][0] == b'r': # relocated
971
988
if not osutils.is_inside_any(searched_paths, entry[1][index][1]):
972
989
search_paths.add(entry[1][index][1])
973
elif entry[1][index][0] != 'a': # absent
990
elif entry[1][index][0] != b'a': # absent
974
991
found_ids.add(entry[0][2])
975
992
while search_paths:
976
993
current_root = search_paths.pop()
984
1001
for entry in root_entries:
985
1002
_process_entry(entry)
986
initial_key = (current_root, '', '')
1003
initial_key = (current_root, b'', b'')
987
1004
block_index, _ = state._find_block_index_from_key(initial_key)
988
1005
while (block_index < len(state._dirblocks) and
989
1006
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
1004
1021
found_dir_names = set(dir_name_id[:2] for dir_name_id in found)
1005
1022
for dir_name in split_paths:
1006
1023
if dir_name not in found_dir_names:
1007
raise errors.PathsNotVersionedError(paths)
1024
raise errors.PathsNotVersionedError(
1025
[p.decode('utf-8') for p in paths])
1009
for dir_name_id, trees_info in found.iteritems():
1027
for dir_name_id, trees_info in viewitems(found):
1010
1028
for index in search_indexes:
1011
if trees_info[index][0] not in ('r', 'a'):
1029
if trees_info[index][0] not in (b'r', b'a'):
1012
1030
found_ids.add(dir_name_id[2])
1013
1031
return found_ids
1113
1131
_mod_revision.NULL_REVISION)))
1114
1132
ghosts.append(rev_id)
1115
1133
accepted_revisions.add(rev_id)
1116
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1135
if (len(real_trees) == 1
1137
and self.branch.repository._format.fast_deltas
1138
and isinstance(real_trees[0][1], InventoryRevisionTree)
1139
and self.get_parent_ids()):
1140
rev_id, rev_tree = real_trees[0]
1141
basis_id = self.get_parent_ids()[0]
1142
# There are times when basis_tree won't be in
1143
# self.branch.repository, (switch, for example)
1145
basis_tree = self.branch.repository.revision_tree(basis_id)
1146
except errors.NoSuchRevision:
1147
# Fall back to the set_parent_trees(), since we can't use
1148
# _make_delta if we can't get the RevisionTree
1151
delta = rev_tree.root_inventory._make_delta(
1152
basis_tree.root_inventory)
1153
dirstate.update_basis_by_delta(delta, rev_id)
1156
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1117
1157
self._make_dirty(reset_inventory=False)
1119
1159
def _set_root_id(self, file_id):
1120
1160
"""See WorkingTree.set_root_id."""
1121
1161
state = self.current_dirstate()
1122
state.set_path_id('', file_id)
1162
state.set_path_id(b'', file_id)
1123
1163
if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1124
1164
self._make_dirty(reset_inventory=True)
1140
1180
def unlock(self):
1141
1181
"""Unlock in format 4 trees needs to write the entire dirstate."""
1142
# do non-implementation specific cleanup
1145
1182
if self._control_files._lock_count == 1:
1183
# do non-implementation specific cleanup
1146
1186
# eventually we should do signature checking during read locks for
1147
1187
# dirstate updates.
1148
1188
if self._control_files._lock_mode == 'w':
1242
1282
ids_to_unversion.remove(entry[0][2])
1243
1283
block_index += 1
1244
1284
if ids_to_unversion:
1245
raise errors.NoSuchId(self, iter(ids_to_unversion).next())
1285
raise errors.NoSuchId(self, next(iter(ids_to_unversion)))
1246
1286
self._make_dirty(reset_inventory=False)
1247
1287
# have to change the legacy inventory too.
1248
1288
if self._inventory is not None:
1249
1289
for file_id in file_ids:
1250
self._inventory.remove_recursive_id(file_id)
1290
if self._inventory.has_id(file_id):
1291
self._inventory.remove_recursive_id(file_id)
1252
1293
@needs_tree_write_lock
1253
1294
def rename_one(self, from_rel, to_rel, after=False):
1254
1295
"""See WorkingTree.rename_one"""
1256
WorkingTree.rename_one(self, from_rel, to_rel, after)
1297
super(DirStateWorkingTree, self).rename_one(from_rel, to_rel, after)
1258
1299
@needs_tree_write_lock
1259
1300
def apply_inventory_delta(self, changes):
1285
1326
# being created.
1286
1327
self._inventory = None
1287
1328
# generate a delta,
1288
delta = inv._make_delta(self.inventory)
1329
delta = inv._make_delta(self.root_inventory)
1289
1330
# and apply it.
1290
1331
self.apply_inventory_delta(delta)
1291
1332
if had_inventory:
1292
1333
self._inventory = inv
1336
@needs_tree_write_lock
1337
def reset_state(self, revision_ids=None):
1338
"""Reset the state of the working tree.
1340
This does a hard-reset to a last-known-good state. This is a way to
1341
fix if something got corrupted (like the .bzr/checkout/dirstate file)
1343
if revision_ids is None:
1344
revision_ids = self.get_parent_ids()
1345
if not revision_ids:
1346
base_tree = self.branch.repository.revision_tree(
1347
_mod_revision.NULL_REVISION)
1350
trees = list(zip(revision_ids,
1351
self.branch.repository.revision_trees(revision_ids)))
1352
base_tree = trees[0][1]
1353
state = self.current_dirstate()
1354
# We don't support ghosts yet
1355
state.set_state_from_scratch(base_tree.root_inventory, trees, [])
1296
1358
class ContentFilterAwareSHA1Provider(dirstate.SHA1Provider):
1302
1364
"""See dirstate.SHA1Provider.sha1()."""
1303
1365
filters = self.tree._content_filter_stack(
1304
1366
self.tree.relpath(osutils.safe_unicode(abspath)))
1305
return internal_size_sha_file_byname(abspath, filters)[1]
1367
return _mod_filters.internal_size_sha_file_byname(abspath, filters)[1]
1307
1369
def stat_and_sha1(self, abspath):
1308
1370
"""See dirstate.SHA1Provider.stat_and_sha1()."""
1313
1375
statvalue = os.fstat(file_obj.fileno())
1315
file_obj = filtered_input_file(file_obj, filters)
1377
file_obj = _mod_filters.filtered_input_file(file_obj, filters)
1316
1378
sha1 = osutils.size_sha_file(file_obj)[1]
1318
1380
file_obj.close()
1329
1391
def _file_content_summary(self, path, stat_result):
1330
1392
# This is to support the somewhat obsolete path_content_summary method
1331
1393
# with content filtering: see
1332
# <https://bugs.edge.launchpad.net/bzr/+bug/415508>.
1394
# <https://bugs.launchpad.net/bzr/+bug/415508>.
1334
1396
# If the dirstate cache is up to date and knows the hash and size,
1348
1410
class WorkingTree4(DirStateWorkingTree):
1349
1411
"""This is the Format 4 working tree.
1351
This differs from WorkingTree3 by:
1413
This differs from WorkingTree by:
1352
1414
- Having a consolidated internal dirstate, stored in a
1353
1415
randomly-accessible sorted file on disk.
1354
1416
- Not having a regular inventory attribute. One can be synthesized
1382
1444
return views.PathBasedViews(self)
1385
class DirStateWorkingTreeFormat(WorkingTreeFormat3):
1387
def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
1447
class DirStateWorkingTreeFormat(WorkingTreeFormatMetaDir):
1449
missing_parent_conflicts = True
1451
supports_versioned_directories = True
1453
_lock_class = LockDir
1454
_lock_file_name = 'lock'
1456
def _open_control_files(self, a_controldir):
1457
transport = a_controldir.get_workingtree_transport(None)
1458
return LockableFiles(transport, self._lock_file_name,
1461
def initialize(self, a_controldir, revision_id=None, from_branch=None,
1388
1462
accelerator_tree=None, hardlink=False):
1389
1463
"""See WorkingTreeFormat.initialize().
1391
1465
:param revision_id: allows creating a working tree at a different
1392
revision than the branch is at.
1466
revision than the branch is at.
1393
1467
:param accelerator_tree: A tree which can be used for retrieving file
1394
1468
contents more quickly than the revision tree, i.e. a workingtree.
1395
1469
The revision tree will be used for cases where accelerator_tree's
1400
1474
These trees get an initial random root id, if their repository supports
1401
1475
rich root data, TREE_ROOT otherwise.
1403
if not isinstance(a_bzrdir.transport, LocalTransport):
1404
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1405
transport = a_bzrdir.get_workingtree_transport(self)
1406
control_files = self._open_control_files(a_bzrdir)
1477
if not isinstance(a_controldir.transport, LocalTransport):
1478
raise errors.NotLocalUrl(a_controldir.transport.base)
1479
transport = a_controldir.get_workingtree_transport(self)
1480
control_files = self._open_control_files(a_controldir)
1407
1481
control_files.create_lock()
1408
1482
control_files.lock_write()
1409
transport.put_bytes('format', self.get_format_string(),
1410
mode=a_bzrdir._get_file_mode())
1483
transport.put_bytes('format', self.as_string(),
1484
mode=a_controldir._get_file_mode())
1411
1485
if from_branch is not None:
1412
1486
branch = from_branch
1414
branch = a_bzrdir.open_branch()
1488
branch = a_controldir.open_branch()
1415
1489
if revision_id is None:
1416
1490
revision_id = branch.last_revision()
1417
1491
local_path = transport.local_abspath('dirstate')
1419
1493
state = dirstate.DirState.initialize(local_path)
1422
wt = self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
1496
wt = self._tree_class(a_controldir.root_transport.local_abspath('.'),
1499
_bzrdir=a_controldir,
1426
1500
_control_files=control_files)
1428
1502
wt.lock_tree_write()
1472
1546
transform.build_tree(basis, wt, accelerator_tree,
1473
1547
hardlink=hardlink,
1474
1548
delta_from_tree=delta_from_tree)
1549
for hook in MutableTree.hooks['post_build_tree']:
1488
1564
:param wt: the WorkingTree object
1491
def _open(self, a_bzrdir, control_files):
1567
def open(self, a_controldir, _found=False):
1568
"""Return the WorkingTree object for a_controldir
1570
_found is a private parameter, do not use it. It is used to indicate
1571
if format probing has already been done.
1574
# we are being called directly and must probe.
1575
raise NotImplementedError
1576
if not isinstance(a_controldir.transport, LocalTransport):
1577
raise errors.NotLocalUrl(a_controldir.transport.base)
1578
wt = self._open(a_controldir, self._open_control_files(a_controldir))
1581
def _open(self, a_controldir, control_files):
1492
1582
"""Open the tree itself.
1494
:param a_bzrdir: the dir for the tree.
1584
:param a_controldir: the dir for the tree.
1495
1585
:param control_files: the control files for the tree.
1497
return self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
1498
branch=a_bzrdir.open_branch(),
1587
return self._tree_class(a_controldir.root_transport.local_abspath('.'),
1588
branch=a_controldir.open_branch(),
1590
_bzrdir=a_controldir,
1501
1591
_control_files=control_files)
1503
1593
def __get_matchingbzrdir(self):
1506
1596
def _get_matchingbzrdir(self):
1507
1597
"""Overrideable method to get a bzrdir for testing."""
1508
1598
# please test against something that will let us do tree references
1509
return bzrdir.format_registry.make_bzrdir(
1510
'dirstate-with-subtree')
1599
return controldir.format_registry.make_controldir(
1600
'development-subtree')
1512
1602
_matchingbzrdir = property(__get_matchingbzrdir)
1519
1609
- exists within a metadir controlling .bzr
1520
1610
- includes an explicit version marker for the workingtree control
1521
files, separate from the BzrDir format
1611
files, separate from the ControlDir format
1522
1612
- modifies the hash cache format
1523
1613
- is new in bzr 0.15
1524
1614
- uses a LockDir to guard access to it.
1529
1619
_tree_class = WorkingTree4
1531
def get_format_string(self):
1622
def get_format_string(cls):
1532
1623
"""See WorkingTreeFormat.get_format_string()."""
1533
1624
return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1546
1637
_tree_class = WorkingTree5
1548
def get_format_string(self):
1640
def get_format_string(cls):
1549
1641
"""See WorkingTreeFormat.get_format_string()."""
1550
1642
return "Bazaar Working Tree Format 5 (bzr 1.11)\n"
1566
1658
_tree_class = WorkingTree6
1568
def get_format_string(self):
1661
def get_format_string(cls):
1569
1662
"""See WorkingTreeFormat.get_format_string()."""
1570
1663
return "Bazaar Working Tree Format 6 (bzr 1.14)\n"
1576
1669
def _init_custom_control_files(self, wt):
1577
1670
"""Subclasses with custom control files should override this method."""
1578
wt._transport.put_bytes('views', '', mode=wt.bzrdir._get_file_mode())
1671
wt._transport.put_bytes('views', b'',
1672
mode=wt.controldir._get_file_mode())
1580
1674
def supports_content_filtering(self):
1583
1677
def supports_views(self):
1587
class DirStateRevisionTree(Tree):
1680
def _get_matchingbzrdir(self):
1681
"""Overrideable method to get a bzrdir for testing."""
1682
# We use 'development-subtree' instead of '2a', because we have a
1683
# few tests that want to test tree references
1684
return controldir.format_registry.make_controldir('development-subtree')
1687
class DirStateRevisionTree(InventoryTree):
1588
1688
"""A revision tree pulling the inventory from a dirstate.
1590
1690
Note that this is one of the historical (ie revision) trees cached in the
1609
1709
def annotate_iter(self, file_id,
1610
1710
default_revision=_mod_revision.CURRENT_REVISION):
1611
1711
"""See Tree.annotate_iter"""
1612
text_key = (file_id, self.inventory[file_id].revision)
1712
text_key = (file_id, self.get_file_revision(file_id))
1613
1713
annotations = self._repository.texts.annotate(text_key)
1614
1714
return [(key[-1], line) for (key, line) in annotations]
1616
def _get_ancestors(self, default_revision):
1617
return set(self._repository.get_ancestry(self._revision_id,
1619
1716
def _comparison_data(self, entry, path):
1620
1717
"""See Tree._comparison_data."""
1621
1718
if entry is None:
1674
1771
if path is not None:
1675
1772
path = path.encode('utf8')
1676
1773
parent_index = self._get_parent_index()
1677
return self._dirstate._get_entry(parent_index, fileid_utf8=file_id, path_utf8=path)
1774
return self._dirstate._get_entry(parent_index, fileid_utf8=file_id,
1679
1777
def _generate_inventory(self):
1680
1778
"""Create and set self.inventory from the dirstate object.
1699
1797
# for the tree index use.
1700
1798
root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1701
1799
current_id = root_key[2]
1702
if current_entry[parent_index][0] != 'd':
1800
if current_entry[parent_index][0] != b'd':
1703
1801
raise AssertionError()
1704
1802
inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1705
1803
inv.root.revision = current_entry[parent_index][4]
1721
1819
for key, entry in block[1]:
1722
1820
minikind, fingerprint, size, executable, revid = entry[parent_index]
1723
if minikind in ('a', 'r'): # absent, relocated
1821
if minikind in (b'a', b'r'): # absent, relocated
1724
1822
# not this tree
1735
1833
inv_entry.text_size = size
1736
1834
inv_entry.text_sha1 = fingerprint
1737
1835
elif kind == 'directory':
1738
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1836
parent_ies[(dirname + b'/' + name).strip(b'/')] = inv_entry
1739
1837
elif kind == 'symlink':
1740
inv_entry.executable = False
1741
inv_entry.text_size = None
1742
1838
inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1743
1839
elif kind == 'tree-reference':
1744
1840
inv_entry.reference_revision = fingerprint or None
1764
1860
# Make sure the file exists
1765
1861
entry = self._get_entry(file_id, path=path)
1766
1862
if entry == (None, None): # do we raise?
1863
raise errors.NoSuchId(self, file_id)
1768
1864
parent_index = self._get_parent_index()
1769
1865
last_changed_revision = entry[1][parent_index][4]
1777
1873
entry = self._get_entry(file_id=file_id, path=path)
1778
1874
parent_index = self._get_parent_index()
1779
1875
parent_details = entry[1][parent_index]
1780
if parent_details[0] == 'f':
1876
if parent_details[0] == b'f':
1781
1877
return parent_details[1]
1881
def get_file_revision(self, file_id):
1882
inv, inv_file_id = self._unpack_file_id(file_id)
1883
return inv[inv_file_id].revision
1784
1885
def get_file(self, file_id, path=None):
1785
return StringIO(self.get_file_text(file_id))
1886
return BytesIO(self.get_file_text(file_id))
1787
1888
def get_file_size(self, file_id):
1788
1889
"""See Tree.get_file_size"""
1789
return self.inventory[file_id].text_size
1890
inv, inv_file_id = self._unpack_file_id(file_id)
1891
return inv[inv_file_id].text_size
1791
1893
def get_file_text(self, file_id, path=None):
1792
_, content = list(self.iter_files_bytes([(file_id, None)]))[0]
1793
return ''.join(content)
1895
for _, content_iter in self.iter_files_bytes([(file_id, None)]):
1896
if content is not None:
1897
raise AssertionError('iter_files_bytes returned'
1898
' too many entries')
1899
# For each entry returned by iter_files_bytes, we must consume the
1900
# content_iter before we step the files iterator.
1901
content = ''.join(content_iter)
1903
raise AssertionError('iter_files_bytes did not return'
1904
' the requested data')
1795
1907
def get_reference_revision(self, file_id, path=None):
1796
return self.inventory[file_id].reference_revision
1908
inv, inv_file_id = self._unpack_file_id(file_id)
1909
return inv[inv_file_id].reference_revision
1798
1911
def iter_files_bytes(self, desired_files):
1799
1912
"""See Tree.iter_files_bytes.
1810
1923
return self._repository.iter_files_bytes(repo_desired_files)
1812
def get_symlink_target(self, file_id):
1925
def get_symlink_target(self, file_id, path=None):
1813
1926
entry = self._get_entry(file_id=file_id)
1814
1927
parent_index = self._get_parent_index()
1815
if entry[1][parent_index][0] != 'l':
1928
if entry[1][parent_index][0] != b'l':
1818
1931
target = entry[1][parent_index][1]
1823
1936
"""Return the revision id for this tree."""
1824
1937
return self._revision_id
1826
def _get_inventory(self):
1939
def _get_root_inventory(self):
1827
1940
if self._inventory is not None:
1828
1941
return self._inventory
1829
1942
self._must_be_locked()
1830
1943
self._generate_inventory()
1831
1944
return self._inventory
1833
inventory = property(_get_inventory,
1946
root_inventory = property(_get_root_inventory,
1834
1947
doc="Inventory of this Tree")
1836
1949
def get_parent_ids(self):
1854
1967
def path_content_summary(self, path):
1855
1968
"""See Tree.path_content_summary."""
1856
id = self.inventory.path2id(path)
1969
inv, inv_file_id = self._path2inv_file_id(path)
1970
if inv_file_id is None:
1858
1971
return ('missing', None, None, None)
1859
entry = self._inventory[id]
1972
entry = inv[inv_file_id]
1860
1973
kind = entry.kind
1861
1974
if kind == 'file':
1862
1975
return (kind, entry.text_size, entry.executable, entry.text_sha1)
1866
1979
return (kind, None, None, None)
1868
1981
def is_executable(self, file_id, path=None):
1869
ie = self.inventory[file_id]
1982
inv, inv_file_id = self._unpack_file_id(file_id)
1983
ie = inv[inv_file_id]
1870
1984
if ie.kind != "file":
1872
1986
return ie.executable
1874
1988
def is_locked(self):
1877
1991
def list_files(self, include_root=False, from_dir=None, recursive=True):
1878
1992
# We use a standard implementation, because DirStateRevisionTree is
1879
1993
# dealing with one of the parents of the current state
1880
inv = self._get_inventory()
1881
1994
if from_dir is None:
1995
inv = self.root_inventory
1882
1996
from_dir_id = None
1884
from_dir_id = inv.path2id(from_dir)
1998
inv, from_dir_id = self._path2inv_file_id(from_dir)
1885
1999
if from_dir_id is None:
1886
2000
# Directory not versioned
2002
# FIXME: Support nested trees
1888
2003
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
1889
2004
if inv.root is not None and not include_root and from_dir is None:
1891
2006
for path, entry in entries:
1892
2007
yield path, 'V', entry.kind, entry.file_id, entry
1894
2009
def lock_read(self):
1895
2010
"""Lock the tree for a set of operations.
1897
:return: A bzrlib.lock.LogicalLockResult.
2012
:return: A breezy.lock.LogicalLockResult.
1899
2014
if not self._locked:
1900
2015
self._repository.lock_read()
1912
2027
def path2id(self, path):
1913
2028
"""Return the id for path in this tree."""
1914
2029
# lookup by path: faster than splitting and walking the ivnentory.
2030
if isinstance(path, list):
2033
path = osutils.pathjoin(*path)
1915
2034
entry = self._get_entry(path=path)
1916
2035
if entry == (None, None):
1940
2059
# So for now, we just build up the parent inventory, and extract
1941
2060
# it the same way RevisionTree does.
1942
2061
_directory = 'directory'
1943
inv = self._get_inventory()
2062
inv = self._get_root_inventory()
1944
2063
top_id = inv.path2id(prefix)
1945
2064
if top_id is None:
1981
2100
def __init__(self, source, target):
1982
2101
super(InterDirStateTree, self).__init__(source, target)
1983
2102
if not InterDirStateTree.is_compatible(source, target):
1984
raise Exception, "invalid source %r and target %r" % (source, target)
2103
raise Exception("invalid source %r and target %r" % (source, target))
1987
2106
def make_source_parent_tree(source, target):
1988
2107
"""Change the source tree into a parent of the target."""
1989
2108
revid = source.commit('record tree')
1990
target.branch.repository.fetch(source.branch.repository, revid)
2109
target.branch.fetch(source.branch, revid)
1991
2110
target.set_parent_ids([revid])
1992
2111
return target.basis_tree(), target
2001
2120
def make_source_parent_tree_compiled_dirstate(klass, test_case, source,
2003
from bzrlib.tests.test__dirstate_helpers import \
2122
from .tests.test__dirstate_helpers import \
2004
2123
compiled_dirstate_helpers_feature
2005
2124
test_case.requireFeature(compiled_dirstate_helpers_feature)
2006
from bzrlib._dirstate_helpers_pyx import ProcessEntryC
2125
from ._dirstate_helpers_pyx import ProcessEntryC
2007
2126
result = klass.make_source_parent_tree(source, target)
2008
2127
result[1]._iter_changes = ProcessEntryC
2072
2191
specific_files_utf8.add(path.encode('utf8'))
2073
2192
specific_files = specific_files_utf8
2075
specific_files = set([''])
2194
specific_files = {b''}
2076
2195
# -- specific_files is now a utf8 path set --
2078
2197
# -- get the state object and prepare it.
2085
2204
path_entries = state._entries_for_path(path)
2086
2205
if not path_entries:
2087
2206
# this specified path is not present at all: error
2088
not_versioned.append(path)
2207
not_versioned.append(path.decode('utf-8'))
2090
2209
found_versioned = False
2091
2210
# for each id at this path
2092
2211
for entry in path_entries:
2093
2212
# for each tree.
2094
2213
for index in indices:
2095
if entry[1][index][0] != 'a': # absent
2214
if entry[1][index][0] != b'a': # absent
2096
2215
found_versioned = True
2097
2216
# all good: found a versioned cell
2099
2218
if not found_versioned:
2100
2219
# none of the indexes was not 'absent' at all ids for this
2102
not_versioned.append(path)
2221
not_versioned.append(path.decode('utf-8'))
2103
2222
if len(not_versioned) > 0:
2104
2223
raise errors.PathsNotVersionedError(not_versioned)
2105
2224
# -- remove redundancy in supplied specific_files to prevent over-scanning --
2153
2272
def create_dirstate_data(self, tree):
2154
2273
"""Create the dirstate based data for tree."""
2155
local_path = tree.bzrdir.get_workingtree_transport(None
2274
local_path = tree.controldir.get_workingtree_transport(None
2156
2275
).local_abspath('dirstate')
2157
2276
state = dirstate.DirState.from_tree(tree, local_path)
2161
2280
def remove_xml_files(self, tree):
2162
2281
"""Remove the oldformat 3 data."""
2163
transport = tree.bzrdir.get_workingtree_transport(None)
2282
transport = tree.controldir.get_workingtree_transport(None)
2164
2283
for path in ['basis-inventory-cache', 'inventory', 'last-revision',
2165
2284
'pending-merges', 'stat-cache']:
2172
2291
def update_format(self, tree):
2173
2292
"""Change the format marker."""
2174
2293
tree._transport.put_bytes('format',
2175
self.target_format.get_format_string(),
2176
mode=tree.bzrdir._get_file_mode())
2294
self.target_format.as_string(),
2295
mode=tree.controldir._get_file_mode())
2179
2298
class Converter4to5(object):
2195
2314
def update_format(self, tree):
2196
2315
"""Change the format marker."""
2197
2316
tree._transport.put_bytes('format',
2198
self.target_format.get_format_string(),
2199
mode=tree.bzrdir._get_file_mode())
2317
self.target_format.as_string(),
2318
mode=tree.controldir._get_file_mode())
2202
2321
class Converter4or5to6(object):
2219
2338
def init_custom_control_files(self, tree):
2220
2339
"""Initialize custom control files."""
2221
tree._transport.put_bytes('views', '',
2222
mode=tree.bzrdir._get_file_mode())
2340
tree._transport.put_bytes('views', b'',
2341
mode=tree.controldir._get_file_mode())
2224
2343
def update_format(self, tree):
2225
2344
"""Change the format marker."""
2226
2345
tree._transport.put_bytes('format',
2227
self.target_format.get_format_string(),
2228
mode=tree.bzrdir._get_file_mode())
2346
self.target_format.as_string(),
2347
mode=tree.controldir._get_file_mode())