1
# Copyright (C) 2007-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""WorkingTree4 format and implementation.
19
WorkingTree4 provides the dirstate based working tree logic.
21
To get a WorkingTree, call bzrdir.open_workingtree() or
22
WorkingTree.open(dir).
25
from __future__ import absolute_import
27
from cStringIO import StringIO
31
from bzrlib.lazy_import import lazy_import
32
lazy_import(globals(), """
40
conflicts as _mod_conflicts,
45
filters as _mod_filters,
48
revision as _mod_revision,
56
from bzrlib.decorators import needs_read_lock, needs_write_lock
57
from bzrlib.inventory import Inventory, ROOT_ID, entry_factory
58
from bzrlib.lock import LogicalLockResult
59
from bzrlib.lockable_files import LockableFiles
60
from bzrlib.lockdir import LockDir
61
from bzrlib.mutabletree import (
63
needs_tree_write_lock,
65
from bzrlib.osutils import (
72
from bzrlib.symbol_versioning import (
76
from bzrlib.transport.local import LocalTransport
77
from bzrlib.tree import (
81
from bzrlib.workingtree import (
84
WorkingTreeFormatMetaDir,
88
class DirStateWorkingTree(InventoryWorkingTree):
90
def __init__(self, basedir,
95
"""Construct a WorkingTree for basedir.
97
If the branch is not supplied, it is opened automatically.
98
If the branch is supplied, it must be the branch for this basedir.
99
(branch.base is not cross checked, because for remote branches that
100
would be meaningless).
102
self._format = _format
103
self.bzrdir = _bzrdir
104
basedir = safe_unicode(basedir)
105
trace.mutter("opening working tree %r", basedir)
106
self._branch = branch
107
self.basedir = realpath(basedir)
108
# if branch is at our basedir and is a format 6 or less
109
# assume all other formats have their own control files.
110
self._control_files = _control_files
111
self._transport = self._control_files._transport
114
# during a read or write lock these objects are set, and are
115
# None the rest of the time.
116
self._dirstate = None
117
self._inventory = None
119
self._setup_directory_is_tree_reference()
120
self._detect_case_handling()
121
self._rules_searcher = None
122
self.views = self._make_views()
123
#--- allow tests to select the dirstate iter_changes implementation
124
self._iter_changes = dirstate._process_entry
126
@needs_tree_write_lock
127
def _add(self, files, ids, kinds):
128
"""See MutableTree._add."""
129
state = self.current_dirstate()
130
for f, file_id, kind in zip(files, ids, kinds):
133
# special case tree root handling.
134
if f == '' and self.path2id(f) == ROOT_ID:
135
state.set_path_id('', generate_ids.gen_file_id(f))
138
file_id = generate_ids.gen_file_id(f)
139
# deliberately add the file with no cached stat or sha1
140
# - on the first access it will be gathered, and we can
141
# always change this once tests are all passing.
142
state.add(f, file_id, kind, None, '')
143
self._make_dirty(reset_inventory=True)
145
def _get_check_refs(self):
146
"""Return the references needed to perform a check of this tree."""
147
return [('trees', self.last_revision())]
149
def _make_dirty(self, reset_inventory):
150
"""Make the tree state dirty.
152
:param reset_inventory: True if the cached inventory should be removed
153
(presuming there is one).
156
if reset_inventory and self._inventory is not None:
157
self._inventory = None
159
@needs_tree_write_lock
160
def add_reference(self, sub_tree):
161
# use standard implementation, which calls back to self._add
163
# So we don't store the reference_revision in the working dirstate,
164
# it's just recorded at the moment of commit.
165
self._add_reference(sub_tree)
167
def break_lock(self):
168
"""Break a lock if one is present from another instance.
170
Uses the ui factory to ask for confirmation if the lock may be from
173
This will probe the repository for its lock as well.
175
# if the dirstate is locked by an active process, reject the break lock
178
if self._dirstate is None:
182
state = self._current_dirstate()
183
if state._lock_token is not None:
184
# we already have it locked. sheese, cant break our own lock.
185
raise errors.LockActive(self.basedir)
188
# try for a write lock - need permission to get one anyhow
191
except errors.LockContention:
192
# oslocks fail when a process is still live: fail.
193
# TODO: get the locked lockdir info and give to the user to
194
# assist in debugging.
195
raise errors.LockActive(self.basedir)
200
self._dirstate = None
201
self._control_files.break_lock()
202
self.branch.break_lock()
204
def _comparison_data(self, entry, path):
205
kind, executable, stat_value = \
206
WorkingTree._comparison_data(self, entry, path)
207
# it looks like a plain directory, but it's really a reference -- see
209
if (self._repo_supports_tree_reference and kind == 'directory'
210
and entry is not None and entry.kind == 'tree-reference'):
211
kind = 'tree-reference'
212
return kind, executable, stat_value
215
def commit(self, message=None, revprops=None, *args, **kwargs):
216
# mark the tree as dirty post commit - commit
217
# can change the current versioned list by doing deletes.
218
result = WorkingTree.commit(self, message, revprops, *args, **kwargs)
219
self._make_dirty(reset_inventory=True)
222
def current_dirstate(self):
223
"""Return the current dirstate object.
225
This is not part of the tree interface and only exposed for ease of
228
:raises errors.NotWriteLocked: when not in a lock.
230
self._must_be_locked()
231
return self._current_dirstate()
233
def _current_dirstate(self):
234
"""Internal function that does not check lock status.
236
This is needed for break_lock which also needs the dirstate.
238
if self._dirstate is not None:
239
return self._dirstate
240
local_path = self.bzrdir.get_workingtree_transport(None
241
).local_abspath('dirstate')
242
self._dirstate = dirstate.DirState.on_file(local_path,
243
self._sha1_provider(), self._worth_saving_limit())
244
return self._dirstate
246
def _sha1_provider(self):
247
"""A function that returns a SHA1Provider suitable for this tree.
249
:return: None if content filtering is not supported by this tree.
250
Otherwise, a SHA1Provider is returned that sha's the canonical
251
form of files, i.e. after read filters are applied.
253
if self.supports_content_filtering():
254
return ContentFilterAwareSHA1Provider(self)
258
def _worth_saving_limit(self):
259
"""How many hash changes are ok before we must save the dirstate.
261
:return: an integer. -1 means never save.
263
conf = self.get_config_stack()
264
return conf.get('bzr.workingtree.worth_saving_limit')
266
def filter_unversioned_files(self, paths):
267
"""Filter out paths that are versioned.
269
:return: set of paths.
271
# TODO: make a generic multi-bisect routine roughly that should list
272
# the paths, then process one half at a time recursively, and feed the
273
# results of each bisect in further still
274
paths = sorted(paths)
276
state = self.current_dirstate()
277
# TODO we want a paths_to_dirblocks helper I think
279
dirname, basename = os.path.split(path.encode('utf8'))
280
_, _, _, path_is_versioned = state._get_block_entry_index(
281
dirname, basename, 0)
282
if not path_is_versioned:
287
"""Write all cached data to disk."""
288
if self._control_files._lock_mode != 'w':
289
raise errors.NotWriteLocked(self)
290
self.current_dirstate().save()
291
self._inventory = None
294
@needs_tree_write_lock
295
def _gather_kinds(self, files, kinds):
296
"""See MutableTree._gather_kinds."""
297
for pos, f in enumerate(files):
298
if kinds[pos] is None:
299
kinds[pos] = self._kind(f)
301
def _generate_inventory(self):
302
"""Create and set self.inventory from the dirstate object.
304
This is relatively expensive: we have to walk the entire dirstate.
305
Ideally we would not, and can deprecate this function.
307
#: uncomment to trap on inventory requests.
308
# import pdb;pdb.set_trace()
309
state = self.current_dirstate()
310
state._read_dirblocks_if_needed()
311
root_key, current_entry = self._get_entry(path='')
312
current_id = root_key[2]
313
if not (current_entry[0][0] == 'd'): # directory
314
raise AssertionError(current_entry)
315
inv = Inventory(root_id=current_id)
316
# Turn some things into local variables
317
minikind_to_kind = dirstate.DirState._minikind_to_kind
318
factory = entry_factory
319
utf8_decode = cache_utf8._utf8_decode
321
# we could do this straight out of the dirstate; it might be fast
322
# and should be profiled - RBC 20070216
323
parent_ies = {'' : inv.root}
324
for block in state._dirblocks[1:]: # skip the root
327
parent_ie = parent_ies[dirname]
329
# all the paths in this block are not versioned in this tree
331
for key, entry in block[1]:
332
minikind, link_or_sha1, size, executable, stat = entry[0]
333
if minikind in ('a', 'r'): # absent, relocated
334
# a parent tree only entry
337
name_unicode = utf8_decode(name)[0]
339
kind = minikind_to_kind[minikind]
340
inv_entry = factory[kind](file_id, name_unicode,
343
# This is only needed on win32, where this is the only way
344
# we know the executable bit.
345
inv_entry.executable = executable
346
# not strictly needed: working tree
347
#inv_entry.text_size = size
348
#inv_entry.text_sha1 = sha1
349
elif kind == 'directory':
350
# add this entry to the parent map.
351
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
352
elif kind == 'tree-reference':
353
if not self._repo_supports_tree_reference:
354
raise errors.UnsupportedOperation(
355
self._generate_inventory,
356
self.branch.repository)
357
inv_entry.reference_revision = link_or_sha1 or None
358
elif kind != 'symlink':
359
raise AssertionError("unknown kind %r" % kind)
360
# These checks cost us around 40ms on a 55k entry tree
361
if file_id in inv_byid:
362
raise AssertionError('file_id %s already in'
363
' inventory as %s' % (file_id, inv_byid[file_id]))
364
if name_unicode in parent_ie.children:
365
raise AssertionError('name %r already in parent'
367
inv_byid[file_id] = inv_entry
368
parent_ie.children[name_unicode] = inv_entry
369
self._inventory = inv
371
def _get_entry(self, file_id=None, path=None):
372
"""Get the dirstate row for file_id or path.
374
If either file_id or path is supplied, it is used as the key to lookup.
375
If both are supplied, the fastest lookup is used, and an error is
376
raised if they do not both point at the same row.
378
:param file_id: An optional unicode file_id to be looked up.
379
:param path: An optional unicode path to be looked up.
380
:return: The dirstate row tuple for path/file_id, or (None, None)
382
if file_id is None and path is None:
383
raise errors.BzrError('must supply file_id or path')
384
state = self.current_dirstate()
386
path = path.encode('utf8')
387
return state._get_entry(0, fileid_utf8=file_id, path_utf8=path)
389
def get_file_sha1(self, file_id, path=None, stat_value=None):
390
# check file id is valid unconditionally.
391
entry = self._get_entry(file_id=file_id, path=path)
393
raise errors.NoSuchId(self, file_id)
395
path = pathjoin(entry[0][0], entry[0][1]).decode('utf8')
397
file_abspath = self.abspath(path)
398
state = self.current_dirstate()
399
if stat_value is None:
401
stat_value = osutils.lstat(file_abspath)
403
if e.errno == errno.ENOENT:
407
link_or_sha1 = dirstate.update_entry(state, entry, file_abspath,
408
stat_value=stat_value)
409
if entry[1][0][0] == 'f':
410
if link_or_sha1 is None:
411
file_obj, statvalue = self.get_file_with_stat(file_id, path)
413
sha1 = osutils.sha_file(file_obj)
416
self._observed_sha1(file_id, path, (sha1, statvalue))
422
def _get_root_inventory(self):
423
"""Get the inventory for the tree. This is only valid within a lock."""
424
if 'evil' in debug.debug_flags:
425
trace.mutter_callsite(2,
426
"accessing .inventory forces a size of tree translation.")
427
if self._inventory is not None:
428
return self._inventory
429
self._must_be_locked()
430
self._generate_inventory()
431
return self._inventory
433
@deprecated_method(deprecated_in((2, 5, 0)))
434
def _get_inventory(self):
435
return self.root_inventory
437
inventory = property(_get_inventory,
438
doc="Inventory of this Tree")
440
root_inventory = property(_get_root_inventory,
441
"Root inventory of this tree")
444
def get_parent_ids(self):
445
"""See Tree.get_parent_ids.
447
This implementation requests the ids list from the dirstate file.
449
return self.current_dirstate().get_parent_ids()
451
def get_reference_revision(self, file_id, path=None):
452
# referenced tree's revision is whatever's currently there
453
return self.get_nested_tree(file_id, path).last_revision()
455
def get_nested_tree(self, file_id, path=None):
457
path = self.id2path(file_id)
458
# else: check file_id is at path?
459
return WorkingTree.open(self.abspath(path))
462
def get_root_id(self):
463
"""Return the id of this trees root"""
464
return self._get_entry(path='')[0][2]
466
def has_id(self, file_id):
467
state = self.current_dirstate()
468
row, parents = self._get_entry(file_id=file_id)
471
return osutils.lexists(pathjoin(
472
self.basedir, row[0].decode('utf8'), row[1].decode('utf8')))
474
def has_or_had_id(self, file_id):
475
state = self.current_dirstate()
476
row, parents = self._get_entry(file_id=file_id)
477
return row is not None
480
def id2path(self, file_id):
481
"Convert a file-id to a path."
482
state = self.current_dirstate()
483
entry = self._get_entry(file_id=file_id)
484
if entry == (None, None):
485
raise errors.NoSuchId(tree=self, file_id=file_id)
486
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
487
return path_utf8.decode('utf8')
489
def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
490
entry = self._get_entry(path=path)
491
if entry == (None, None):
492
return False # Missing entries are not executable
493
return entry[1][0][3] # Executable?
495
def is_executable(self, file_id, path=None):
496
"""Test if a file is executable or not.
498
Note: The caller is expected to take a read-lock before calling this.
500
if not self._supports_executable():
501
entry = self._get_entry(file_id=file_id, path=path)
502
if entry == (None, None):
504
return entry[1][0][3]
506
self._must_be_locked()
508
path = self.id2path(file_id)
509
mode = osutils.lstat(self.abspath(path)).st_mode
510
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
512
def all_file_ids(self):
513
"""See Tree.iter_all_file_ids"""
514
self._must_be_locked()
516
for key, tree_details in self.current_dirstate()._iter_entries():
517
if tree_details[0][0] in ('a', 'r'): # relocated
524
"""Iterate through file_ids for this tree.
526
file_ids are in a WorkingTree if they are in the working inventory
527
and the working file exists.
530
for key, tree_details in self.current_dirstate()._iter_entries():
531
if tree_details[0][0] in ('a', 'r'): # absent, relocated
532
# not relevant to the working tree
534
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
535
if osutils.lexists(path):
536
result.append(key[2])
539
def iter_references(self):
540
if not self._repo_supports_tree_reference:
541
# When the repo doesn't support references, we will have nothing to
544
for key, tree_details in self.current_dirstate()._iter_entries():
545
if tree_details[0][0] in ('a', 'r'): # absent, relocated
546
# not relevant to the working tree
549
# the root is not a reference.
551
relpath = pathjoin(key[0].decode('utf8'), key[1].decode('utf8'))
553
if self._kind(relpath) == 'tree-reference':
554
yield relpath, key[2]
555
except errors.NoSuchFile:
556
# path is missing on disk.
559
def _observed_sha1(self, file_id, path, (sha1, statvalue)):
560
"""See MutableTree._observed_sha1."""
561
state = self.current_dirstate()
562
entry = self._get_entry(file_id=file_id, path=path)
563
state._observed_sha1(entry, sha1, statvalue)
565
def kind(self, file_id):
566
"""Return the kind of a file.
568
This is always the actual kind that's on disk, regardless of what it
571
Note: The caller is expected to take a read-lock before calling this.
573
relpath = self.id2path(file_id)
575
raise AssertionError(
576
"path for id {%s} is None!" % file_id)
577
return self._kind(relpath)
579
def _kind(self, relpath):
580
abspath = self.abspath(relpath)
581
kind = file_kind(abspath)
582
if (self._repo_supports_tree_reference and kind == 'directory'):
583
entry = self._get_entry(path=relpath)
584
if entry[1] is not None:
585
if entry[1][0][0] == 't':
586
kind = 'tree-reference'
590
def _last_revision(self):
591
"""See Mutable.last_revision."""
592
parent_ids = self.current_dirstate().get_parent_ids()
596
return _mod_revision.NULL_REVISION
599
"""See Branch.lock_read, and WorkingTree.unlock.
601
:return: A bzrlib.lock.LogicalLockResult.
603
self.branch.lock_read()
605
self._control_files.lock_read()
607
state = self.current_dirstate()
608
if not state._lock_token:
610
# set our support for tree references from the repository in
612
self._repo_supports_tree_reference = getattr(
613
self.branch.repository._format, "supports_tree_reference",
616
self._control_files.unlock()
621
return LogicalLockResult(self.unlock)
623
def _lock_self_write(self):
624
"""This should be called after the branch is locked."""
626
self._control_files.lock_write()
628
state = self.current_dirstate()
629
if not state._lock_token:
631
# set our support for tree references from the repository in
633
self._repo_supports_tree_reference = getattr(
634
self.branch.repository._format, "supports_tree_reference",
637
self._control_files.unlock()
642
return LogicalLockResult(self.unlock)
644
def lock_tree_write(self):
645
"""See MutableTree.lock_tree_write, and WorkingTree.unlock.
647
:return: A bzrlib.lock.LogicalLockResult.
649
self.branch.lock_read()
650
return self._lock_self_write()
652
def lock_write(self):
653
"""See MutableTree.lock_write, and WorkingTree.unlock.
655
:return: A bzrlib.lock.LogicalLockResult.
657
self.branch.lock_write()
658
return self._lock_self_write()
660
@needs_tree_write_lock
661
def move(self, from_paths, to_dir, after=False):
662
"""See WorkingTree.move()."""
666
state = self.current_dirstate()
667
if isinstance(from_paths, basestring):
669
to_dir_utf8 = to_dir.encode('utf8')
670
to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
671
id_index = state._get_id_index()
672
# check destination directory
673
# get the details for it
674
to_entry_block_index, to_entry_entry_index, dir_present, entry_present = \
675
state._get_block_entry_index(to_entry_dirname, to_basename, 0)
676
if not entry_present:
677
raise errors.BzrMoveFailedError('', to_dir,
678
errors.NotVersionedError(to_dir))
679
to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
680
# get a handle on the block itself.
681
to_block_index = state._ensure_block(
682
to_entry_block_index, to_entry_entry_index, to_dir_utf8)
683
to_block = state._dirblocks[to_block_index]
684
to_abs = self.abspath(to_dir)
685
if not isdir(to_abs):
686
raise errors.BzrMoveFailedError('',to_dir,
687
errors.NotADirectory(to_abs))
689
if to_entry[1][0][0] != 'd':
690
raise errors.BzrMoveFailedError('',to_dir,
691
errors.NotADirectory(to_abs))
693
if self._inventory is not None:
694
update_inventory = True
695
inv = self.root_inventory
696
to_dir_id = to_entry[0][2]
697
to_dir_ie = inv[to_dir_id]
699
update_inventory = False
702
def move_one(old_entry, from_path_utf8, minikind, executable,
703
fingerprint, packed_stat, size,
704
to_block, to_key, to_path_utf8):
705
state._make_absent(old_entry)
706
from_key = old_entry[0]
708
lambda:state.update_minimal(from_key,
710
executable=executable,
711
fingerprint=fingerprint,
712
packed_stat=packed_stat,
714
path_utf8=from_path_utf8))
715
state.update_minimal(to_key,
717
executable=executable,
718
fingerprint=fingerprint,
719
packed_stat=packed_stat,
721
path_utf8=to_path_utf8)
722
added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
723
new_entry = to_block[1][added_entry_index]
724
rollbacks.append(lambda:state._make_absent(new_entry))
726
for from_rel in from_paths:
727
# from_rel is 'pathinroot/foo/bar'
728
from_rel_utf8 = from_rel.encode('utf8')
729
from_dirname, from_tail = osutils.split(from_rel)
730
from_dirname, from_tail_utf8 = osutils.split(from_rel_utf8)
731
from_entry = self._get_entry(path=from_rel)
732
if from_entry == (None, None):
733
raise errors.BzrMoveFailedError(from_rel,to_dir,
734
errors.NotVersionedError(path=from_rel))
736
from_id = from_entry[0][2]
737
to_rel = pathjoin(to_dir, from_tail)
738
to_rel_utf8 = pathjoin(to_dir_utf8, from_tail_utf8)
739
item_to_entry = self._get_entry(path=to_rel)
740
if item_to_entry != (None, None):
741
raise errors.BzrMoveFailedError(from_rel, to_rel,
742
"Target is already versioned.")
744
if from_rel == to_rel:
745
raise errors.BzrMoveFailedError(from_rel, to_rel,
746
"Source and target are identical.")
748
from_missing = not self.has_filename(from_rel)
749
to_missing = not self.has_filename(to_rel)
756
raise errors.BzrMoveFailedError(from_rel, to_rel,
757
errors.NoSuchFile(path=to_rel,
758
extra="New file has not been created yet"))
760
# neither path exists
761
raise errors.BzrRenameFailedError(from_rel, to_rel,
762
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
764
if from_missing: # implicitly just update our path mapping
767
raise errors.RenameFailedFilesExist(from_rel, to_rel)
770
def rollback_rename():
771
"""A single rename has failed, roll it back."""
772
# roll back everything, even if we encounter trouble doing one
775
# TODO: at least log the other exceptions rather than just
776
# losing them mbp 20070307
778
for rollback in reversed(rollbacks):
782
exc_info = sys.exc_info()
784
raise exc_info[0], exc_info[1], exc_info[2]
786
# perform the disk move first - its the most likely failure point.
788
from_rel_abs = self.abspath(from_rel)
789
to_rel_abs = self.abspath(to_rel)
791
osutils.rename(from_rel_abs, to_rel_abs)
793
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
794
rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
796
# perform the rename in the inventory next if needed: its easy
800
from_entry = inv[from_id]
801
current_parent = from_entry.parent_id
802
inv.rename(from_id, to_dir_id, from_tail)
804
lambda: inv.rename(from_id, current_parent, from_tail))
805
# finally do the rename in the dirstate, which is a little
806
# tricky to rollback, but least likely to need it.
807
old_block_index, old_entry_index, dir_present, file_present = \
808
state._get_block_entry_index(from_dirname, from_tail_utf8, 0)
809
old_block = state._dirblocks[old_block_index][1]
810
old_entry = old_block[old_entry_index]
811
from_key, old_entry_details = old_entry
812
cur_details = old_entry_details[0]
814
to_key = ((to_block[0],) + from_key[1:3])
815
minikind = cur_details[0]
816
move_one(old_entry, from_path_utf8=from_rel_utf8,
818
executable=cur_details[3],
819
fingerprint=cur_details[1],
820
packed_stat=cur_details[4],
824
to_path_utf8=to_rel_utf8)
827
def update_dirblock(from_dir, to_key, to_dir_utf8):
828
"""Recursively update all entries in this dirblock."""
830
raise AssertionError("renaming root not supported")
831
from_key = (from_dir, '')
832
from_block_idx, present = \
833
state._find_block_index_from_key(from_key)
835
# This is the old record, if it isn't present, then
836
# there is theoretically nothing to update.
837
# (Unless it isn't present because of lazy loading,
838
# but we don't do that yet)
840
from_block = state._dirblocks[from_block_idx]
841
to_block_index, to_entry_index, _, _ = \
842
state._get_block_entry_index(to_key[0], to_key[1], 0)
843
to_block_index = state._ensure_block(
844
to_block_index, to_entry_index, to_dir_utf8)
845
to_block = state._dirblocks[to_block_index]
847
# Grab a copy since move_one may update the list.
848
for entry in from_block[1][:]:
849
if not (entry[0][0] == from_dir):
850
raise AssertionError()
851
cur_details = entry[1][0]
852
to_key = (to_dir_utf8, entry[0][1], entry[0][2])
853
from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
854
to_path_utf8 = osutils.pathjoin(to_dir_utf8, entry[0][1])
855
minikind = cur_details[0]
857
# Deleted children of a renamed directory
858
# Do not need to be updated.
859
# Children that have been renamed out of this
860
# directory should also not be updated
862
move_one(entry, from_path_utf8=from_path_utf8,
864
executable=cur_details[3],
865
fingerprint=cur_details[1],
866
packed_stat=cur_details[4],
870
to_path_utf8=to_path_utf8)
872
# We need to move all the children of this
874
update_dirblock(from_path_utf8, to_key,
876
update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
880
result.append((from_rel, to_rel))
881
state._mark_modified()
882
self._make_dirty(reset_inventory=False)
886
def _must_be_locked(self):
887
if not self._control_files._lock_count:
888
raise errors.ObjectNotLocked(self)
891
"""Initialize the state in this tree to be a new tree."""
895
def path2id(self, path):
896
"""Return the id for path in this tree."""
897
path = path.strip('/')
898
entry = self._get_entry(path=path)
899
if entry == (None, None):
903
def paths2ids(self, paths, trees=[], require_versioned=True):
904
"""See Tree.paths2ids().
906
This specialisation fast-paths the case where all the trees are in the
911
parents = self.get_parent_ids()
913
if not (isinstance(tree, DirStateRevisionTree) and tree._revision_id in
915
return super(DirStateWorkingTree, self).paths2ids(paths,
916
trees, require_versioned)
917
search_indexes = [0] + [1 + parents.index(tree._revision_id) for tree in trees]
918
# -- make all paths utf8 --
921
paths_utf8.add(path.encode('utf8'))
923
# -- paths is now a utf8 path set --
924
# -- get the state object and prepare it.
925
state = self.current_dirstate()
926
if False and (state._dirblock_state == dirstate.DirState.NOT_IN_MEMORY
927
and '' not in paths):
928
paths2ids = self._paths2ids_using_bisect
930
paths2ids = self._paths2ids_in_memory
931
return paths2ids(paths, search_indexes,
932
require_versioned=require_versioned)
934
def _paths2ids_in_memory(self, paths, search_indexes,
935
require_versioned=True):
936
state = self.current_dirstate()
937
state._read_dirblocks_if_needed()
938
def _entries_for_path(path):
939
"""Return a list with all the entries that match path for all ids.
941
dirname, basename = os.path.split(path)
942
key = (dirname, basename, '')
943
block_index, present = state._find_block_index_from_key(key)
945
# the block which should contain path is absent.
948
block = state._dirblocks[block_index][1]
949
entry_index, _ = state._find_entry_index(key, block)
950
# we may need to look at multiple entries at this path: walk while the paths match.
951
while (entry_index < len(block) and
952
block[entry_index][0][0:2] == key[0:2]):
953
result.append(block[entry_index])
956
if require_versioned:
957
# -- check all supplied paths are versioned in a search tree. --
960
path_entries = _entries_for_path(path)
962
# this specified path is not present at all: error
963
all_versioned = False
965
found_versioned = False
966
# for each id at this path
967
for entry in path_entries:
969
for index in search_indexes:
970
if entry[1][index][0] != 'a': # absent
971
found_versioned = True
972
# all good: found a versioned cell
974
if not found_versioned:
975
# none of the indexes was not 'absent' at all ids for this
977
all_versioned = False
979
if not all_versioned:
980
raise errors.PathsNotVersionedError(
981
[p.decode('utf-8') for p in paths])
982
# -- remove redundancy in supplied paths to prevent over-scanning --
983
search_paths = osutils.minimum_path_selection(paths)
985
# for all search_indexs in each path at or under each element of
986
# search_paths, if the detail is relocated: add the id, and add the
987
# relocated path as one to search if its not searched already. If the
988
# detail is not relocated, add the id.
989
searched_paths = set()
991
def _process_entry(entry):
992
"""Look at search_indexes within entry.
994
If a specific tree's details are relocated, add the relocation
995
target to search_paths if not searched already. If it is absent, do
996
nothing. Otherwise add the id to found_ids.
998
for index in search_indexes:
999
if entry[1][index][0] == 'r': # relocated
1000
if not osutils.is_inside_any(searched_paths, entry[1][index][1]):
1001
search_paths.add(entry[1][index][1])
1002
elif entry[1][index][0] != 'a': # absent
1003
found_ids.add(entry[0][2])
1005
current_root = search_paths.pop()
1006
searched_paths.add(current_root)
1007
# process the entries for this containing directory: the rest will be
1008
# found by their parents recursively.
1009
root_entries = _entries_for_path(current_root)
1010
if not root_entries:
1011
# this specified path is not present at all, skip it.
1013
for entry in root_entries:
1014
_process_entry(entry)
1015
initial_key = (current_root, '', '')
1016
block_index, _ = state._find_block_index_from_key(initial_key)
1017
while (block_index < len(state._dirblocks) and
1018
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
1019
for entry in state._dirblocks[block_index][1]:
1020
_process_entry(entry)
1024
def _paths2ids_using_bisect(self, paths, search_indexes,
1025
require_versioned=True):
1026
state = self.current_dirstate()
1029
split_paths = sorted(osutils.split(p) for p in paths)
1030
found = state._bisect_recursive(split_paths)
1032
if require_versioned:
1033
found_dir_names = set(dir_name_id[:2] for dir_name_id in found)
1034
for dir_name in split_paths:
1035
if dir_name not in found_dir_names:
1036
raise errors.PathsNotVersionedError(
1037
[p.decode('utf-8') for p in paths])
1039
for dir_name_id, trees_info in found.iteritems():
1040
for index in search_indexes:
1041
if trees_info[index][0] not in ('r', 'a'):
1042
found_ids.add(dir_name_id[2])
1045
def read_working_inventory(self):
1046
"""Read the working inventory.
1048
This is a meaningless operation for dirstate, but we obey it anyhow.
1050
return self.root_inventory
1053
def revision_tree(self, revision_id):
1054
"""See Tree.revision_tree.
1056
WorkingTree4 supplies revision_trees for any basis tree.
1058
dirstate = self.current_dirstate()
1059
parent_ids = dirstate.get_parent_ids()
1060
if revision_id not in parent_ids:
1061
raise errors.NoSuchRevisionInTree(self, revision_id)
1062
if revision_id in dirstate.get_ghosts():
1063
raise errors.NoSuchRevisionInTree(self, revision_id)
1064
return DirStateRevisionTree(dirstate, revision_id,
1065
self.branch.repository)
1067
@needs_tree_write_lock
1068
def set_last_revision(self, new_revision):
1069
"""Change the last revision in the working tree."""
1070
parents = self.get_parent_ids()
1071
if new_revision in (_mod_revision.NULL_REVISION, None):
1072
if len(parents) >= 2:
1073
raise AssertionError(
1074
"setting the last parent to none with a pending merge is "
1076
self.set_parent_ids([])
1078
self.set_parent_ids([new_revision] + parents[1:],
1079
allow_leftmost_as_ghost=True)
1081
@needs_tree_write_lock
1082
def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
1083
"""Set the parent ids to revision_ids.
1085
See also set_parent_trees. This api will try to retrieve the tree data
1086
for each element of revision_ids from the trees repository. If you have
1087
tree data already available, it is more efficient to use
1088
set_parent_trees rather than set_parent_ids. set_parent_ids is however
1089
an easier API to use.
1091
:param revision_ids: The revision_ids to set as the parent ids of this
1092
working tree. Any of these may be ghosts.
1095
for revision_id in revision_ids:
1097
revtree = self.branch.repository.revision_tree(revision_id)
1098
# TODO: jam 20070213 KnitVersionedFile raises
1099
# RevisionNotPresent rather than NoSuchRevision if a
1100
# given revision_id is not present. Should Repository be
1101
# catching it and re-raising NoSuchRevision?
1102
except (errors.NoSuchRevision, errors.RevisionNotPresent):
1104
trees.append((revision_id, revtree))
1105
self.set_parent_trees(trees,
1106
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
1108
@needs_tree_write_lock
1109
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
1110
"""Set the parents of the working tree.
1112
:param parents_list: A list of (revision_id, tree) tuples.
1113
If tree is None, then that element is treated as an unreachable
1114
parent tree - i.e. a ghost.
1116
dirstate = self.current_dirstate()
1117
if len(parents_list) > 0:
1118
if not allow_leftmost_as_ghost and parents_list[0][1] is None:
1119
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1123
parent_ids = [rev_id for rev_id, tree in parents_list]
1124
graph = self.branch.repository.get_graph()
1125
heads = graph.heads(parent_ids)
1126
accepted_revisions = set()
1128
# convert absent trees to the null tree, which we convert back to
1129
# missing on access.
1130
for rev_id, tree in parents_list:
1131
if len(accepted_revisions) > 0:
1132
# we always accept the first tree
1133
if rev_id in accepted_revisions or rev_id not in heads:
1134
# We have already included either this tree, or its
1135
# descendent, so we skip it.
1137
_mod_revision.check_not_reserved_id(rev_id)
1138
if tree is not None:
1139
real_trees.append((rev_id, tree))
1141
real_trees.append((rev_id,
1142
self.branch.repository.revision_tree(
1143
_mod_revision.NULL_REVISION)))
1144
ghosts.append(rev_id)
1145
accepted_revisions.add(rev_id)
1147
if (len(real_trees) == 1
1149
and self.branch.repository._format.fast_deltas
1150
and isinstance(real_trees[0][1],
1151
revisiontree.InventoryRevisionTree)
1152
and self.get_parent_ids()):
1153
rev_id, rev_tree = real_trees[0]
1154
basis_id = self.get_parent_ids()[0]
1155
# There are times when basis_tree won't be in
1156
# self.branch.repository, (switch, for example)
1158
basis_tree = self.branch.repository.revision_tree(basis_id)
1159
except errors.NoSuchRevision:
1160
# Fall back to the set_parent_trees(), since we can't use
1161
# _make_delta if we can't get the RevisionTree
1164
delta = rev_tree.root_inventory._make_delta(
1165
basis_tree.root_inventory)
1166
dirstate.update_basis_by_delta(delta, rev_id)
1169
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1170
self._make_dirty(reset_inventory=False)
1172
def _set_root_id(self, file_id):
1173
"""See WorkingTree.set_root_id."""
1174
state = self.current_dirstate()
1175
state.set_path_id('', file_id)
1176
if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1177
self._make_dirty(reset_inventory=True)
1179
def _sha_from_stat(self, path, stat_result):
1180
"""Get a sha digest from the tree's stat cache.
1182
The default implementation assumes no stat cache is present.
1184
:param path: The path.
1185
:param stat_result: The stat result being looked up.
1187
return self.current_dirstate().sha1_from_stat(path, stat_result)
1190
def supports_tree_reference(self):
1191
return self._repo_supports_tree_reference
1194
"""Unlock in format 4 trees needs to write the entire dirstate."""
1195
if self._control_files._lock_count == 1:
1196
# do non-implementation specific cleanup
1199
# eventually we should do signature checking during read locks for
1201
if self._control_files._lock_mode == 'w':
1204
if self._dirstate is not None:
1205
# This is a no-op if there are no modifications.
1206
self._dirstate.save()
1207
self._dirstate.unlock()
1208
# TODO: jam 20070301 We shouldn't have to wipe the dirstate at this
1209
# point. Instead, it could check if the header has been
1210
# modified when it is locked, and if not, it can hang on to
1211
# the data it has in memory.
1212
self._dirstate = None
1213
self._inventory = None
1214
# reverse order of locking.
1216
return self._control_files.unlock()
1218
self.branch.unlock()
1220
@needs_tree_write_lock
1221
def unversion(self, file_ids):
1222
"""Remove the file ids in file_ids from the current versioned set.
1224
When a file_id is unversioned, all of its children are automatically
1227
:param file_ids: The file ids to stop versioning.
1228
:raises: NoSuchId if any fileid is not currently versioned.
1232
state = self.current_dirstate()
1233
state._read_dirblocks_if_needed()
1234
ids_to_unversion = set(file_ids)
1235
paths_to_unversion = set()
1237
# check if the root is to be unversioned, if so, assert for now.
1238
# walk the state marking unversioned things as absent.
1239
# if there are any un-unversioned ids at the end, raise
1240
for key, details in state._dirblocks[0][1]:
1241
if (details[0][0] not in ('a', 'r') and # absent or relocated
1242
key[2] in ids_to_unversion):
1243
# I haven't written the code to unversion / yet - it should be
1245
raise errors.BzrError('Unversioning the / is not currently supported')
1247
while block_index < len(state._dirblocks):
1248
# process one directory at a time.
1249
block = state._dirblocks[block_index]
1250
# first check: is the path one to remove - it or its children
1251
delete_block = False
1252
for path in paths_to_unversion:
1253
if (block[0].startswith(path) and
1254
(len(block[0]) == len(path) or
1255
block[0][len(path)] == '/')):
1256
# this entire block should be deleted - its the block for a
1257
# path to unversion; or the child of one
1260
# TODO: trim paths_to_unversion as we pass by paths
1262
# this block is to be deleted: process it.
1263
# TODO: we can special case the no-parents case and
1264
# just forget the whole block.
1266
while entry_index < len(block[1]):
1267
entry = block[1][entry_index]
1268
if entry[1][0][0] in 'ar':
1269
# don't remove absent or renamed entries
1272
# Mark this file id as having been removed
1273
ids_to_unversion.discard(entry[0][2])
1274
if not state._make_absent(entry):
1275
# The block has not shrunk.
1277
# go to the next block. (At the moment we dont delete empty
1282
while entry_index < len(block[1]):
1283
entry = block[1][entry_index]
1284
if (entry[1][0][0] in ('a', 'r') or # absent, relocated
1285
# ^ some parent row.
1286
entry[0][2] not in ids_to_unversion):
1287
# ^ not an id to unversion
1290
if entry[1][0][0] == 'd':
1291
paths_to_unversion.add(pathjoin(entry[0][0], entry[0][1]))
1292
if not state._make_absent(entry):
1294
# we have unversioned this id
1295
ids_to_unversion.remove(entry[0][2])
1297
if ids_to_unversion:
1298
raise errors.NoSuchId(self, iter(ids_to_unversion).next())
1299
self._make_dirty(reset_inventory=False)
1300
# have to change the legacy inventory too.
1301
if self._inventory is not None:
1302
for file_id in file_ids:
1303
if self._inventory.has_id(file_id):
1304
self._inventory.remove_recursive_id(file_id)
1306
@needs_tree_write_lock
1307
def rename_one(self, from_rel, to_rel, after=False):
1308
"""See WorkingTree.rename_one"""
1310
super(DirStateWorkingTree, self).rename_one(from_rel, to_rel, after)
1312
@needs_tree_write_lock
1313
def apply_inventory_delta(self, changes):
1314
"""See MutableTree.apply_inventory_delta"""
1315
state = self.current_dirstate()
1316
state.update_by_delta(changes)
1317
self._make_dirty(reset_inventory=True)
1319
def update_basis_by_delta(self, new_revid, delta):
1320
"""See MutableTree.update_basis_by_delta."""
1321
if self.last_revision() == new_revid:
1322
raise AssertionError()
1323
self.current_dirstate().update_basis_by_delta(delta, new_revid)
1326
def _validate(self):
1327
self._dirstate._validate()
1329
@needs_tree_write_lock
1330
def _write_inventory(self, inv):
1331
"""Write inventory as the current inventory."""
1333
raise AssertionError("attempting to write an inventory when the "
1334
"dirstate is dirty will lose pending changes")
1335
had_inventory = self._inventory is not None
1336
# Setting self._inventory = None forces the dirstate to regenerate the
1337
# working inventory. We do this because self.inventory may be inv, or
1338
# may have been modified, and either case would prevent a clean delta
1340
self._inventory = None
1342
delta = inv._make_delta(self.root_inventory)
1344
self.apply_inventory_delta(delta)
1346
self._inventory = inv
1349
@needs_tree_write_lock
1350
def reset_state(self, revision_ids=None):
1351
"""Reset the state of the working tree.
1353
This does a hard-reset to a last-known-good state. This is a way to
1354
fix if something got corrupted (like the .bzr/checkout/dirstate file)
1356
if revision_ids is None:
1357
revision_ids = self.get_parent_ids()
1358
if not revision_ids:
1359
base_tree = self.branch.repository.revision_tree(
1360
_mod_revision.NULL_REVISION)
1363
trees = zip(revision_ids,
1364
self.branch.repository.revision_trees(revision_ids))
1365
base_tree = trees[0][1]
1366
state = self.current_dirstate()
1367
# We don't support ghosts yet
1368
state.set_state_from_scratch(base_tree.root_inventory, trees, [])
1371
class ContentFilterAwareSHA1Provider(dirstate.SHA1Provider):
1373
def __init__(self, tree):
1376
def sha1(self, abspath):
1377
"""See dirstate.SHA1Provider.sha1()."""
1378
filters = self.tree._content_filter_stack(
1379
self.tree.relpath(osutils.safe_unicode(abspath)))
1380
return _mod_filters.internal_size_sha_file_byname(abspath, filters)[1]
1382
def stat_and_sha1(self, abspath):
1383
"""See dirstate.SHA1Provider.stat_and_sha1()."""
1384
filters = self.tree._content_filter_stack(
1385
self.tree.relpath(osutils.safe_unicode(abspath)))
1386
file_obj = file(abspath, 'rb', 65000)
1388
statvalue = os.fstat(file_obj.fileno())
1390
file_obj = _mod_filters.filtered_input_file(file_obj, filters)
1391
sha1 = osutils.size_sha_file(file_obj)[1]
1394
return statvalue, sha1
1397
class ContentFilteringDirStateWorkingTree(DirStateWorkingTree):
1398
"""Dirstate working tree that supports content filtering.
1400
The dirstate holds the hash and size of the canonical form of the file,
1401
and most methods must return that.
1404
def _file_content_summary(self, path, stat_result):
1405
# This is to support the somewhat obsolete path_content_summary method
1406
# with content filtering: see
1407
# <https://bugs.launchpad.net/bzr/+bug/415508>.
1409
# If the dirstate cache is up to date and knows the hash and size,
1411
# Otherwise if there are no content filters, return the on-disk size
1412
# and leave the hash blank.
1413
# Otherwise, read and filter the on-disk file and use its size and
1416
# The dirstate doesn't store the size of the canonical form so we
1417
# can't trust it for content-filtered trees. We just return None.
1418
dirstate_sha1 = self._dirstate.sha1_from_stat(path, stat_result)
1419
executable = self._is_executable_from_path_and_stat(path, stat_result)
1420
return ('file', None, executable, dirstate_sha1)
1423
class WorkingTree4(DirStateWorkingTree):
1424
"""This is the Format 4 working tree.
1426
This differs from WorkingTree by:
1427
- Having a consolidated internal dirstate, stored in a
1428
randomly-accessible sorted file on disk.
1429
- Not having a regular inventory attribute. One can be synthesized
1430
on demand but this is expensive and should be avoided.
1432
This is new in bzr 0.15.
1436
class WorkingTree5(ContentFilteringDirStateWorkingTree):
1437
"""This is the Format 5 working tree.
1439
This differs from WorkingTree4 by:
1440
- Supporting content filtering.
1442
This is new in bzr 1.11.
1446
class WorkingTree6(ContentFilteringDirStateWorkingTree):
1447
"""This is the Format 6 working tree.
1449
This differs from WorkingTree5 by:
1450
- Supporting a current view that may mask the set of files in a tree
1451
impacted by most user operations.
1453
This is new in bzr 1.14.
1456
def _make_views(self):
1457
return views.PathBasedViews(self)
1460
class DirStateWorkingTreeFormat(WorkingTreeFormatMetaDir):
1462
missing_parent_conflicts = True
1464
supports_versioned_directories = True
1466
_lock_class = LockDir
1467
_lock_file_name = 'lock'
1469
def _open_control_files(self, a_bzrdir):
1470
transport = a_bzrdir.get_workingtree_transport(None)
1471
return LockableFiles(transport, self._lock_file_name,
1474
def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
1475
accelerator_tree=None, hardlink=False):
1476
"""See WorkingTreeFormat.initialize().
1478
:param revision_id: allows creating a working tree at a different
1479
revision than the branch is at.
1480
:param accelerator_tree: A tree which can be used for retrieving file
1481
contents more quickly than the revision tree, i.e. a workingtree.
1482
The revision tree will be used for cases where accelerator_tree's
1483
content is different.
1484
:param hardlink: If true, hard-link files from accelerator_tree,
1487
These trees get an initial random root id, if their repository supports
1488
rich root data, TREE_ROOT otherwise.
1490
if not isinstance(a_bzrdir.transport, LocalTransport):
1491
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1492
transport = a_bzrdir.get_workingtree_transport(self)
1493
control_files = self._open_control_files(a_bzrdir)
1494
control_files.create_lock()
1495
control_files.lock_write()
1496
transport.put_bytes('format', self.as_string(),
1497
mode=a_bzrdir._get_file_mode())
1498
if from_branch is not None:
1499
branch = from_branch
1501
branch = a_bzrdir.open_branch()
1502
if revision_id is None:
1503
revision_id = branch.last_revision()
1504
local_path = transport.local_abspath('dirstate')
1505
# write out new dirstate (must exist when we create the tree)
1506
state = dirstate.DirState.initialize(local_path)
1509
wt = self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
1513
_control_files=control_files)
1515
wt.lock_tree_write()
1517
self._init_custom_control_files(wt)
1518
if revision_id in (None, _mod_revision.NULL_REVISION):
1519
if branch.repository.supports_rich_root():
1520
wt._set_root_id(generate_ids.gen_root_id())
1522
wt._set_root_id(ROOT_ID)
1525
# frequently, we will get here due to branching. The accelerator
1526
# tree will be the tree from the branch, so the desired basis
1527
# tree will often be a parent of the accelerator tree.
1528
if accelerator_tree is not None:
1530
basis = accelerator_tree.revision_tree(revision_id)
1531
except errors.NoSuchRevision:
1534
basis = branch.repository.revision_tree(revision_id)
1535
if revision_id == _mod_revision.NULL_REVISION:
1538
parents_list = [(revision_id, basis)]
1541
wt.set_parent_trees(parents_list, allow_leftmost_as_ghost=True)
1543
# if the basis has a root id we have to use that; otherwise we
1544
# use a new random one
1545
basis_root_id = basis.get_root_id()
1546
if basis_root_id is not None:
1547
wt._set_root_id(basis_root_id)
1549
if wt.supports_content_filtering():
1550
# The original tree may not have the same content filters
1551
# applied so we can't safely build the inventory delta from
1553
delta_from_tree = False
1555
delta_from_tree = True
1556
# delta_from_tree is safe even for DirStateRevisionTrees,
1557
# because wt4.apply_inventory_delta does not mutate the input
1558
# inventory entries.
1559
transform.build_tree(basis, wt, accelerator_tree,
1561
delta_from_tree=delta_from_tree)
1562
for hook in MutableTree.hooks['post_build_tree']:
1567
control_files.unlock()
1571
def _init_custom_control_files(self, wt):
1572
"""Subclasses with custom control files should override this method.
1574
The working tree and control files are locked for writing when this
1577
:param wt: the WorkingTree object
1580
def open(self, a_bzrdir, _found=False):
1581
"""Return the WorkingTree object for a_bzrdir
1583
_found is a private parameter, do not use it. It is used to indicate
1584
if format probing has already been done.
1587
# we are being called directly and must probe.
1588
raise NotImplementedError
1589
if not isinstance(a_bzrdir.transport, LocalTransport):
1590
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1591
wt = self._open(a_bzrdir, self._open_control_files(a_bzrdir))
1594
def _open(self, a_bzrdir, control_files):
1595
"""Open the tree itself.
1597
:param a_bzrdir: the dir for the tree.
1598
:param control_files: the control files for the tree.
1600
return self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
1601
branch=a_bzrdir.open_branch(),
1604
_control_files=control_files)
1606
def __get_matchingbzrdir(self):
1607
return self._get_matchingbzrdir()
1609
def _get_matchingbzrdir(self):
1610
"""Overrideable method to get a bzrdir for testing."""
1611
# please test against something that will let us do tree references
1612
return controldir.format_registry.make_bzrdir(
1613
'development-subtree')
1615
_matchingbzrdir = property(__get_matchingbzrdir)
1618
class WorkingTreeFormat4(DirStateWorkingTreeFormat):
1619
"""The first consolidated dirstate working tree format.
1622
- exists within a metadir controlling .bzr
1623
- includes an explicit version marker for the workingtree control
1624
files, separate from the ControlDir format
1625
- modifies the hash cache format
1626
- is new in bzr 0.15
1627
- uses a LockDir to guard access to it.
1630
upgrade_recommended = False
1632
_tree_class = WorkingTree4
1635
def get_format_string(cls):
1636
"""See WorkingTreeFormat.get_format_string()."""
1637
return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1639
def get_format_description(self):
1640
"""See WorkingTreeFormat.get_format_description()."""
1641
return "Working tree format 4"
1644
class WorkingTreeFormat5(DirStateWorkingTreeFormat):
1645
"""WorkingTree format supporting content filtering.
1648
upgrade_recommended = False
1650
_tree_class = WorkingTree5
1653
def get_format_string(cls):
1654
"""See WorkingTreeFormat.get_format_string()."""
1655
return "Bazaar Working Tree Format 5 (bzr 1.11)\n"
1657
def get_format_description(self):
1658
"""See WorkingTreeFormat.get_format_description()."""
1659
return "Working tree format 5"
1661
def supports_content_filtering(self):
1665
class WorkingTreeFormat6(DirStateWorkingTreeFormat):
1666
"""WorkingTree format supporting views.
1669
upgrade_recommended = False
1671
_tree_class = WorkingTree6
1674
def get_format_string(cls):
1675
"""See WorkingTreeFormat.get_format_string()."""
1676
return "Bazaar Working Tree Format 6 (bzr 1.14)\n"
1678
def get_format_description(self):
1679
"""See WorkingTreeFormat.get_format_description()."""
1680
return "Working tree format 6"
1682
def _init_custom_control_files(self, wt):
1683
"""Subclasses with custom control files should override this method."""
1684
wt._transport.put_bytes('views', '', mode=wt.bzrdir._get_file_mode())
1686
def supports_content_filtering(self):
1689
def supports_views(self):
1693
class DirStateRevisionTree(InventoryTree):
1694
"""A revision tree pulling the inventory from a dirstate.
1696
Note that this is one of the historical (ie revision) trees cached in the
1697
dirstate for easy access, not the workingtree.
1700
def __init__(self, dirstate, revision_id, repository):
1701
self._dirstate = dirstate
1702
self._revision_id = revision_id
1703
self._repository = repository
1704
self._inventory = None
1706
self._dirstate_locked = False
1707
self._repo_supports_tree_reference = getattr(
1708
repository._format, "supports_tree_reference",
1712
return "<%s of %s in %s>" % \
1713
(self.__class__.__name__, self._revision_id, self._dirstate)
1715
def annotate_iter(self, file_id,
1716
default_revision=_mod_revision.CURRENT_REVISION):
1717
"""See Tree.annotate_iter"""
1718
text_key = (file_id, self.get_file_revision(file_id))
1719
annotations = self._repository.texts.annotate(text_key)
1720
return [(key[-1], line) for (key, line) in annotations]
1722
def _comparison_data(self, entry, path):
1723
"""See Tree._comparison_data."""
1725
return None, False, None
1726
# trust the entry as RevisionTree does, but this may not be
1727
# sensible: the entry might not have come from us?
1728
return entry.kind, entry.executable, None
1730
def _file_size(self, entry, stat_value):
1731
return entry.text_size
1733
def filter_unversioned_files(self, paths):
1734
"""Filter out paths that are not versioned.
1736
:return: set of paths.
1738
pred = self.has_filename
1739
return set((p for p in paths if not pred(p)))
1741
def get_root_id(self):
1742
return self.path2id('')
1744
def id2path(self, file_id):
1745
"Convert a file-id to a path."
1746
entry = self._get_entry(file_id=file_id)
1747
if entry == (None, None):
1748
raise errors.NoSuchId(tree=self, file_id=file_id)
1749
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
1750
return path_utf8.decode('utf8')
1752
def iter_references(self):
1753
if not self._repo_supports_tree_reference:
1754
# When the repo doesn't support references, we will have nothing to
1757
# Otherwise, fall back to the default implementation
1758
return super(DirStateRevisionTree, self).iter_references()
1760
def _get_parent_index(self):
1761
"""Return the index in the dirstate referenced by this tree."""
1762
return self._dirstate.get_parent_ids().index(self._revision_id) + 1
1764
def _get_entry(self, file_id=None, path=None):
1765
"""Get the dirstate row for file_id or path.
1767
If either file_id or path is supplied, it is used as the key to lookup.
1768
If both are supplied, the fastest lookup is used, and an error is
1769
raised if they do not both point at the same row.
1771
:param file_id: An optional unicode file_id to be looked up.
1772
:param path: An optional unicode path to be looked up.
1773
:return: The dirstate row tuple for path/file_id, or (None, None)
1775
if file_id is None and path is None:
1776
raise errors.BzrError('must supply file_id or path')
1777
if path is not None:
1778
path = path.encode('utf8')
1779
parent_index = self._get_parent_index()
1780
return self._dirstate._get_entry(parent_index, fileid_utf8=file_id, path_utf8=path)
1782
def _generate_inventory(self):
1783
"""Create and set self.inventory from the dirstate object.
1785
(So this is only called the first time the inventory is requested for
1786
this tree; it then remains in memory until it's out of date.)
1788
This is relatively expensive: we have to walk the entire dirstate.
1790
if not self._locked:
1791
raise AssertionError(
1792
'cannot generate inventory of an unlocked '
1793
'dirstate revision tree')
1794
# separate call for profiling - makes it clear where the costs are.
1795
self._dirstate._read_dirblocks_if_needed()
1796
if self._revision_id not in self._dirstate.get_parent_ids():
1797
raise AssertionError(
1798
'parent %s has disappeared from %s' % (
1799
self._revision_id, self._dirstate.get_parent_ids()))
1800
parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
1801
# This is identical now to the WorkingTree _generate_inventory except
1802
# for the tree index use.
1803
root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1804
current_id = root_key[2]
1805
if current_entry[parent_index][0] != 'd':
1806
raise AssertionError()
1807
inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1808
inv.root.revision = current_entry[parent_index][4]
1809
# Turn some things into local variables
1810
minikind_to_kind = dirstate.DirState._minikind_to_kind
1811
factory = entry_factory
1812
utf8_decode = cache_utf8._utf8_decode
1813
inv_byid = inv._byid
1814
# we could do this straight out of the dirstate; it might be fast
1815
# and should be profiled - RBC 20070216
1816
parent_ies = {'' : inv.root}
1817
for block in self._dirstate._dirblocks[1:]: #skip root
1820
parent_ie = parent_ies[dirname]
1822
# all the paths in this block are not versioned in this tree
1824
for key, entry in block[1]:
1825
minikind, fingerprint, size, executable, revid = entry[parent_index]
1826
if minikind in ('a', 'r'): # absent, relocated
1830
name_unicode = utf8_decode(name)[0]
1832
kind = minikind_to_kind[minikind]
1833
inv_entry = factory[kind](file_id, name_unicode,
1835
inv_entry.revision = revid
1837
inv_entry.executable = executable
1838
inv_entry.text_size = size
1839
inv_entry.text_sha1 = fingerprint
1840
elif kind == 'directory':
1841
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1842
elif kind == 'symlink':
1843
inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1844
elif kind == 'tree-reference':
1845
inv_entry.reference_revision = fingerprint or None
1847
raise AssertionError("cannot convert entry %r into an InventoryEntry"
1849
# These checks cost us around 40ms on a 55k entry tree
1850
if file_id in inv_byid:
1851
raise AssertionError('file_id %s already in'
1852
' inventory as %s' % (file_id, inv_byid[file_id]))
1853
if name_unicode in parent_ie.children:
1854
raise AssertionError('name %r already in parent'
1856
inv_byid[file_id] = inv_entry
1857
parent_ie.children[name_unicode] = inv_entry
1858
self._inventory = inv
1860
def get_file_mtime(self, file_id, path=None):
1861
"""Return the modification time for this record.
1863
We return the timestamp of the last-changed revision.
1865
# Make sure the file exists
1866
entry = self._get_entry(file_id, path=path)
1867
if entry == (None, None): # do we raise?
1868
raise errors.NoSuchId(self, file_id)
1869
parent_index = self._get_parent_index()
1870
last_changed_revision = entry[1][parent_index][4]
1872
rev = self._repository.get_revision(last_changed_revision)
1873
except errors.NoSuchRevision:
1874
raise errors.FileTimestampUnavailable(self.id2path(file_id))
1875
return rev.timestamp
1877
def get_file_sha1(self, file_id, path=None, stat_value=None):
1878
entry = self._get_entry(file_id=file_id, path=path)
1879
parent_index = self._get_parent_index()
1880
parent_details = entry[1][parent_index]
1881
if parent_details[0] == 'f':
1882
return parent_details[1]
1886
def get_file_revision(self, file_id):
1887
inv, inv_file_id = self._unpack_file_id(file_id)
1888
return inv[inv_file_id].revision
1890
def get_file(self, file_id, path=None):
1891
return StringIO(self.get_file_text(file_id))
1893
def get_file_size(self, file_id):
1894
"""See Tree.get_file_size"""
1895
inv, inv_file_id = self._unpack_file_id(file_id)
1896
return inv[inv_file_id].text_size
1898
def get_file_text(self, file_id, path=None):
1899
_, content = list(self.iter_files_bytes([(file_id, None)]))[0]
1900
return ''.join(content)
1902
def get_reference_revision(self, file_id, path=None):
1903
inv, inv_file_id = self._unpack_file_id(file_id)
1904
return inv[inv_file_id].reference_revision
1906
def iter_files_bytes(self, desired_files):
1907
"""See Tree.iter_files_bytes.
1909
This version is implemented on top of Repository.iter_files_bytes"""
1910
parent_index = self._get_parent_index()
1911
repo_desired_files = []
1912
for file_id, identifier in desired_files:
1913
entry = self._get_entry(file_id)
1914
if entry == (None, None):
1915
raise errors.NoSuchId(self, file_id)
1916
repo_desired_files.append((file_id, entry[1][parent_index][4],
1918
return self._repository.iter_files_bytes(repo_desired_files)
1920
def get_symlink_target(self, file_id, path=None):
1921
entry = self._get_entry(file_id=file_id)
1922
parent_index = self._get_parent_index()
1923
if entry[1][parent_index][0] != 'l':
1926
target = entry[1][parent_index][1]
1927
target = target.decode('utf8')
1930
def get_revision_id(self):
1931
"""Return the revision id for this tree."""
1932
return self._revision_id
1934
def _get_root_inventory(self):
1935
if self._inventory is not None:
1936
return self._inventory
1937
self._must_be_locked()
1938
self._generate_inventory()
1939
return self._inventory
1941
root_inventory = property(_get_root_inventory,
1942
doc="Inventory of this Tree")
1944
@deprecated_method(deprecated_in((2, 5, 0)))
1945
def _get_inventory(self):
1946
return self.root_inventory
1948
inventory = property(_get_inventory,
1949
doc="Inventory of this Tree")
1951
def get_parent_ids(self):
1952
"""The parents of a tree in the dirstate are not cached."""
1953
return self._repository.get_revision(self._revision_id).parent_ids
1955
def has_filename(self, filename):
1956
return bool(self.path2id(filename))
1958
def kind(self, file_id):
1959
entry = self._get_entry(file_id=file_id)[1]
1961
raise errors.NoSuchId(tree=self, file_id=file_id)
1962
parent_index = self._get_parent_index()
1963
return dirstate.DirState._minikind_to_kind[entry[parent_index][0]]
1965
def stored_kind(self, file_id):
1966
"""See Tree.stored_kind"""
1967
return self.kind(file_id)
1969
def path_content_summary(self, path):
1970
"""See Tree.path_content_summary."""
1971
inv, inv_file_id = self._path2inv_file_id(path)
1972
if inv_file_id is None:
1973
return ('missing', None, None, None)
1974
entry = inv[inv_file_id]
1977
return (kind, entry.text_size, entry.executable, entry.text_sha1)
1978
elif kind == 'symlink':
1979
return (kind, None, None, entry.symlink_target)
1981
return (kind, None, None, None)
1983
def is_executable(self, file_id, path=None):
1984
inv, inv_file_id = self._unpack_file_id(file_id)
1985
ie = inv[inv_file_id]
1986
if ie.kind != "file":
1988
return ie.executable
1990
def is_locked(self):
1993
def list_files(self, include_root=False, from_dir=None, recursive=True):
1994
# We use a standard implementation, because DirStateRevisionTree is
1995
# dealing with one of the parents of the current state
1996
if from_dir is None:
1997
inv = self.root_inventory
2000
inv, from_dir_id = self._path2inv_file_id(from_dir)
2001
if from_dir_id is None:
2002
# Directory not versioned
2004
# FIXME: Support nested trees
2005
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
2006
if inv.root is not None and not include_root and from_dir is None:
2008
for path, entry in entries:
2009
yield path, 'V', entry.kind, entry.file_id, entry
2011
def lock_read(self):
2012
"""Lock the tree for a set of operations.
2014
:return: A bzrlib.lock.LogicalLockResult.
2016
if not self._locked:
2017
self._repository.lock_read()
2018
if self._dirstate._lock_token is None:
2019
self._dirstate.lock_read()
2020
self._dirstate_locked = True
2022
return LogicalLockResult(self.unlock)
2024
def _must_be_locked(self):
2025
if not self._locked:
2026
raise errors.ObjectNotLocked(self)
2029
def path2id(self, path):
2030
"""Return the id for path in this tree."""
2031
# lookup by path: faster than splitting and walking the ivnentory.
2032
entry = self._get_entry(path=path)
2033
if entry == (None, None):
2038
"""Unlock, freeing any cache memory used during the lock."""
2039
# outside of a lock, the inventory is suspect: release it.
2041
if not self._locked:
2042
self._inventory = None
2044
if self._dirstate_locked:
2045
self._dirstate.unlock()
2046
self._dirstate_locked = False
2047
self._repository.unlock()
2050
def supports_tree_reference(self):
2051
return self._repo_supports_tree_reference
2053
def walkdirs(self, prefix=""):
2054
# TODO: jam 20070215 This is the lazy way by using the RevisionTree
2055
# implementation based on an inventory.
2056
# This should be cleaned up to use the much faster Dirstate code
2057
# So for now, we just build up the parent inventory, and extract
2058
# it the same way RevisionTree does.
2059
_directory = 'directory'
2060
inv = self._get_root_inventory()
2061
top_id = inv.path2id(prefix)
2065
pending = [(prefix, top_id)]
2068
relpath, file_id = pending.pop()
2069
# 0 - relpath, 1- file-id
2071
relroot = relpath + '/'
2074
# FIXME: stash the node in pending
2075
entry = inv[file_id]
2076
for name, child in entry.sorted_children():
2077
toppath = relroot + name
2078
dirblock.append((toppath, name, child.kind, None,
2079
child.file_id, child.kind
2081
yield (relpath, entry.file_id), dirblock
2082
# push the user specified dirs from dirblock
2083
for dir in reversed(dirblock):
2084
if dir[2] == _directory:
2085
pending.append((dir[0], dir[4]))
2088
class InterDirStateTree(InterTree):
2089
"""Fast path optimiser for changes_from with dirstate trees.
2091
This is used only when both trees are in the dirstate working file, and
2092
the source is any parent within the dirstate, and the destination is
2093
the current working tree of the same dirstate.
2095
# this could be generalized to allow comparisons between any trees in the
2096
# dirstate, and possibly between trees stored in different dirstates.
2098
def __init__(self, source, target):
2099
super(InterDirStateTree, self).__init__(source, target)
2100
if not InterDirStateTree.is_compatible(source, target):
2101
raise Exception, "invalid source %r and target %r" % (source, target)
2104
def make_source_parent_tree(source, target):
2105
"""Change the source tree into a parent of the target."""
2106
revid = source.commit('record tree')
2107
target.branch.fetch(source.branch, revid)
2108
target.set_parent_ids([revid])
2109
return target.basis_tree(), target
2112
def make_source_parent_tree_python_dirstate(klass, test_case, source, target):
2113
result = klass.make_source_parent_tree(source, target)
2114
result[1]._iter_changes = dirstate.ProcessEntryPython
2118
def make_source_parent_tree_compiled_dirstate(klass, test_case, source,
2120
from bzrlib.tests.test__dirstate_helpers import \
2121
compiled_dirstate_helpers_feature
2122
test_case.requireFeature(compiled_dirstate_helpers_feature)
2123
from bzrlib._dirstate_helpers_pyx import ProcessEntryC
2124
result = klass.make_source_parent_tree(source, target)
2125
result[1]._iter_changes = ProcessEntryC
2128
_matching_from_tree_format = WorkingTreeFormat4()
2129
_matching_to_tree_format = WorkingTreeFormat4()
2132
def _test_mutable_trees_to_test_trees(klass, test_case, source, target):
2133
# This method shouldn't be called, because we have python and C
2134
# specific flavours.
2135
raise NotImplementedError
2137
def iter_changes(self, include_unchanged=False,
2138
specific_files=None, pb=None, extra_trees=[],
2139
require_versioned=True, want_unversioned=False):
2140
"""Return the changes from source to target.
2142
:return: An iterator that yields tuples. See InterTree.iter_changes
2144
:param specific_files: An optional list of file paths to restrict the
2145
comparison to. When mapping filenames to ids, all matches in all
2146
trees (including optional extra_trees) are used, and all children of
2147
matched directories are included.
2148
:param include_unchanged: An optional boolean requesting the inclusion of
2149
unchanged entries in the result.
2150
:param extra_trees: An optional list of additional trees to use when
2151
mapping the contents of specific_files (paths) to file_ids.
2152
:param require_versioned: If True, all files in specific_files must be
2153
versioned in one of source, target, extra_trees or
2154
PathsNotVersionedError is raised.
2155
:param want_unversioned: Should unversioned files be returned in the
2156
output. An unversioned file is defined as one with (False, False)
2157
for the versioned pair.
2159
# TODO: handle extra trees in the dirstate.
2160
if (extra_trees or specific_files == []):
2161
# we can't fast-path these cases (yet)
2162
return super(InterDirStateTree, self).iter_changes(
2163
include_unchanged, specific_files, pb, extra_trees,
2164
require_versioned, want_unversioned=want_unversioned)
2165
parent_ids = self.target.get_parent_ids()
2166
if not (self.source._revision_id in parent_ids
2167
or self.source._revision_id == _mod_revision.NULL_REVISION):
2168
raise AssertionError(
2169
"revision {%s} is not stored in {%s}, but %s "
2170
"can only be used for trees stored in the dirstate"
2171
% (self.source._revision_id, self.target, self.iter_changes))
2173
if self.source._revision_id == _mod_revision.NULL_REVISION:
2175
indices = (target_index,)
2177
if not (self.source._revision_id in parent_ids):
2178
raise AssertionError(
2179
"Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
2180
self.source._revision_id, parent_ids))
2181
source_index = 1 + parent_ids.index(self.source._revision_id)
2182
indices = (source_index, target_index)
2183
# -- make all specific_files utf8 --
2185
specific_files_utf8 = set()
2186
for path in specific_files:
2187
# Note, if there are many specific files, using cache_utf8
2188
# would be good here.
2189
specific_files_utf8.add(path.encode('utf8'))
2190
specific_files = specific_files_utf8
2192
specific_files = set([''])
2193
# -- specific_files is now a utf8 path set --
2195
# -- get the state object and prepare it.
2196
state = self.target.current_dirstate()
2197
state._read_dirblocks_if_needed()
2198
if require_versioned:
2199
# -- check all supplied paths are versioned in a search tree. --
2201
for path in specific_files:
2202
path_entries = state._entries_for_path(path)
2203
if not path_entries:
2204
# this specified path is not present at all: error
2205
not_versioned.append(path.decode('utf-8'))
2207
found_versioned = False
2208
# for each id at this path
2209
for entry in path_entries:
2211
for index in indices:
2212
if entry[1][index][0] != 'a': # absent
2213
found_versioned = True
2214
# all good: found a versioned cell
2216
if not found_versioned:
2217
# none of the indexes was not 'absent' at all ids for this
2219
not_versioned.append(path.decode('utf-8'))
2220
if len(not_versioned) > 0:
2221
raise errors.PathsNotVersionedError(not_versioned)
2222
# -- remove redundancy in supplied specific_files to prevent over-scanning --
2223
search_specific_files = osutils.minimum_path_selection(specific_files)
2225
use_filesystem_for_exec = (sys.platform != 'win32')
2226
iter_changes = self.target._iter_changes(include_unchanged,
2227
use_filesystem_for_exec, search_specific_files, state,
2228
source_index, target_index, want_unversioned, self.target)
2229
return iter_changes.iter_changes()
2232
def is_compatible(source, target):
2233
# the target must be a dirstate working tree
2234
if not isinstance(target, DirStateWorkingTree):
2236
# the source must be a revtree or dirstate rev tree.
2237
if not isinstance(source,
2238
(revisiontree.RevisionTree, DirStateRevisionTree)):
2240
# the source revid must be in the target dirstate
2241
if not (source._revision_id == _mod_revision.NULL_REVISION or
2242
source._revision_id in target.get_parent_ids()):
2243
# TODO: what about ghosts? it may well need to
2244
# check for them explicitly.
2248
InterTree.register_optimiser(InterDirStateTree)
2251
class Converter3to4(object):
2252
"""Perform an in-place upgrade of format 3 to format 4 trees."""
2255
self.target_format = WorkingTreeFormat4()
2257
def convert(self, tree):
2258
# lock the control files not the tree, so that we dont get tree
2259
# on-unlock behaviours, and so that noone else diddles with the
2260
# tree during upgrade.
2261
tree._control_files.lock_write()
2263
tree.read_working_inventory()
2264
self.create_dirstate_data(tree)
2265
self.update_format(tree)
2266
self.remove_xml_files(tree)
2268
tree._control_files.unlock()
2270
def create_dirstate_data(self, tree):
2271
"""Create the dirstate based data for tree."""
2272
local_path = tree.bzrdir.get_workingtree_transport(None
2273
).local_abspath('dirstate')
2274
state = dirstate.DirState.from_tree(tree, local_path)
2278
def remove_xml_files(self, tree):
2279
"""Remove the oldformat 3 data."""
2280
transport = tree.bzrdir.get_workingtree_transport(None)
2281
for path in ['basis-inventory-cache', 'inventory', 'last-revision',
2282
'pending-merges', 'stat-cache']:
2284
transport.delete(path)
2285
except errors.NoSuchFile:
2286
# some files are optional - just deal.
2289
def update_format(self, tree):
2290
"""Change the format marker."""
2291
tree._transport.put_bytes('format',
2292
self.target_format.as_string(),
2293
mode=tree.bzrdir._get_file_mode())
2296
class Converter4to5(object):
2297
"""Perform an in-place upgrade of format 4 to format 5 trees."""
2300
self.target_format = WorkingTreeFormat5()
2302
def convert(self, tree):
2303
# lock the control files not the tree, so that we don't get tree
2304
# on-unlock behaviours, and so that no-one else diddles with the
2305
# tree during upgrade.
2306
tree._control_files.lock_write()
2308
self.update_format(tree)
2310
tree._control_files.unlock()
2312
def update_format(self, tree):
2313
"""Change the format marker."""
2314
tree._transport.put_bytes('format',
2315
self.target_format.as_string(),
2316
mode=tree.bzrdir._get_file_mode())
2319
class Converter4or5to6(object):
2320
"""Perform an in-place upgrade of format 4 or 5 to format 6 trees."""
2323
self.target_format = WorkingTreeFormat6()
2325
def convert(self, tree):
2326
# lock the control files not the tree, so that we don't get tree
2327
# on-unlock behaviours, and so that no-one else diddles with the
2328
# tree during upgrade.
2329
tree._control_files.lock_write()
2331
self.init_custom_control_files(tree)
2332
self.update_format(tree)
2334
tree._control_files.unlock()
2336
def init_custom_control_files(self, tree):
2337
"""Initialize custom control files."""
2338
tree._transport.put_bytes('views', '',
2339
mode=tree.bzrdir._get_file_mode())
2341
def update_format(self, tree):
2342
"""Change the format marker."""
2343
tree._transport.put_bytes('format',
2344
self.target_format.as_string(),
2345
mode=tree.bzrdir._get_file_mode())