1
# Copyright (C) 2005, 2006, 2007 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 cStringIO import StringIO
29
from bzrlib.lazy_import import lazy_import
30
lazy_import(globals(), """
31
from bisect import bisect_left
33
from copy import deepcopy
45
conflicts as _mod_conflicts,
63
from bzrlib.transport import get_transport
67
from bzrlib import symbol_versioning
68
from bzrlib.decorators import needs_read_lock, needs_write_lock
69
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID, entry_factory
70
from bzrlib.lockable_files import LockableFiles, TransportLock
71
from bzrlib.lockdir import LockDir
72
import bzrlib.mutabletree
73
from bzrlib.mutabletree import needs_tree_write_lock
74
from bzrlib.osutils import (
84
from bzrlib.trace import mutter, note
85
from bzrlib.transport.local import LocalTransport
86
from bzrlib.tree import InterTree
87
from bzrlib.progress import DummyProgress, ProgressPhase
88
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
89
from bzrlib.rio import RioReader, rio_file, Stanza
90
from bzrlib.symbol_versioning import (deprecated_passed,
95
from bzrlib.tree import Tree
96
from bzrlib.workingtree import WorkingTree, WorkingTree3, WorkingTreeFormat3
99
class WorkingTree4(WorkingTree3):
100
"""This is the Format 4 working tree.
102
This differs from WorkingTree3 by:
103
- Having a consolidated internal dirstate, stored in a
104
randomly-accessible sorted file on disk.
105
- Not having a regular inventory attribute. One can be synthesized
106
on demand but this is expensive and should be avoided.
108
This is new in bzr 0.15.
111
def __init__(self, basedir,
116
"""Construct a WorkingTree for basedir.
118
If the branch is not supplied, it is opened automatically.
119
If the branch is supplied, it must be the branch for this basedir.
120
(branch.base is not cross checked, because for remote branches that
121
would be meaningless).
123
self._format = _format
124
self.bzrdir = _bzrdir
125
from bzrlib.trace import note, mutter
126
assert isinstance(basedir, basestring), \
127
"base directory %r is not a string" % basedir
128
basedir = safe_unicode(basedir)
129
mutter("opening working tree %r", basedir)
130
self._branch = branch
131
assert isinstance(self.branch, bzrlib.branch.Branch), \
132
"branch %r is not a Branch" % self.branch
133
self.basedir = realpath(basedir)
134
# if branch is at our basedir and is a format 6 or less
135
# assume all other formats have their own control files.
136
assert isinstance(_control_files, LockableFiles), \
137
"_control_files must be a LockableFiles, not %r" % _control_files
138
self._control_files = _control_files
141
# during a read or write lock these objects are set, and are
142
# None the rest of the time.
143
self._dirstate = None
144
self._inventory = None
147
@needs_tree_write_lock
148
def _add(self, files, ids, kinds):
149
"""See MutableTree._add."""
150
state = self.current_dirstate()
151
for f, file_id, kind in zip(files, ids, kinds):
156
# special case tree root handling.
157
if f == '' and self.path2id(f) == ROOT_ID:
158
state.set_path_id('', generate_ids.gen_file_id(f))
161
file_id = generate_ids.gen_file_id(f)
162
# deliberately add the file with no cached stat or sha1
163
# - on the first access it will be gathered, and we can
164
# always change this once tests are all passing.
165
state.add(f, file_id, kind, None, '')
166
self._make_dirty(reset_inventory=True)
168
def _make_dirty(self, reset_inventory):
169
"""Make the tree state dirty.
171
:param reset_inventory: True if the cached inventory should be removed
172
(presuming there is one).
175
if reset_inventory and self._inventory is not None:
176
self._inventory = None
178
@needs_tree_write_lock
179
def add_reference(self, sub_tree):
180
# use standard implementation, which calls back to self._add
182
# So we don't store the reference_revision in the working dirstate,
183
# it's just recorded at the moment of commit.
184
self._add_reference(sub_tree)
186
def break_lock(self):
187
"""Break a lock if one is present from another instance.
189
Uses the ui factory to ask for confirmation if the lock may be from
192
This will probe the repository for its lock as well.
194
# if the dirstate is locked by an active process, reject the break lock
197
if self._dirstate is None:
201
state = self._current_dirstate()
202
if state._lock_token is not None:
203
# we already have it locked. sheese, cant break our own lock.
204
raise errors.LockActive(self.basedir)
207
# try for a write lock - need permission to get one anyhow
210
except errors.LockContention:
211
# oslocks fail when a process is still live: fail.
212
# TODO: get the locked lockdir info and give to the user to
213
# assist in debugging.
214
raise errors.LockActive(self.basedir)
219
self._dirstate = None
220
self._control_files.break_lock()
221
self.branch.break_lock()
223
def _comparison_data(self, entry, path):
224
kind, executable, stat_value = \
225
WorkingTree3._comparison_data(self, entry, path)
226
# it looks like a plain directory, but it's really a reference -- see
228
if (self._repo_supports_tree_reference and
229
kind == 'directory' and
230
self._directory_is_tree_reference(path)):
231
kind = 'tree-reference'
232
return kind, executable, stat_value
235
def commit(self, message=None, revprops=None, *args, **kwargs):
236
# mark the tree as dirty post commit - commit
237
# can change the current versioned list by doing deletes.
238
result = WorkingTree3.commit(self, message, revprops, *args, **kwargs)
239
self._make_dirty(reset_inventory=True)
242
def current_dirstate(self):
243
"""Return the current dirstate object.
245
This is not part of the tree interface and only exposed for ease of
248
:raises errors.NotWriteLocked: when not in a lock.
250
self._must_be_locked()
251
return self._current_dirstate()
253
def _current_dirstate(self):
254
"""Internal function that does not check lock status.
256
This is needed for break_lock which also needs the dirstate.
258
if self._dirstate is not None:
259
return self._dirstate
260
local_path = self.bzrdir.get_workingtree_transport(None
261
).local_abspath('dirstate')
262
self._dirstate = dirstate.DirState.on_file(local_path)
263
return self._dirstate
265
def _directory_is_tree_reference(self, relpath):
266
# as a special case, if a directory contains control files then
267
# it's a tree reference, except that the root of the tree is not
268
return relpath and osutils.isdir(self.abspath(relpath) + u"/.bzr")
269
# TODO: We could ask all the control formats whether they
270
# recognize this directory, but at the moment there's no cheap api
271
# to do that. Since we probably can only nest bzr checkouts and
272
# they always use this name it's ok for now. -- mbp 20060306
274
# FIXME: There is an unhandled case here of a subdirectory
275
# containing .bzr but not a branch; that will probably blow up
276
# when you try to commit it. It might happen if there is a
277
# checkout in a subdirectory. This can be avoided by not adding
280
def filter_unversioned_files(self, paths):
281
"""Filter out paths that are versioned.
283
:return: set of paths.
285
# TODO: make a generic multi-bisect routine roughly that should list
286
# the paths, then process one half at a time recursively, and feed the
287
# results of each bisect in further still
288
paths = sorted(paths)
290
state = self.current_dirstate()
291
# TODO we want a paths_to_dirblocks helper I think
293
dirname, basename = os.path.split(path.encode('utf8'))
294
_, _, _, path_is_versioned = state._get_block_entry_index(
295
dirname, basename, 0)
296
if not path_is_versioned:
301
"""Write all cached data to disk."""
302
if self._control_files._lock_mode != 'w':
303
raise errors.NotWriteLocked(self)
304
self.current_dirstate().save()
305
self._inventory = None
308
def _generate_inventory(self):
309
"""Create and set self.inventory from the dirstate object.
311
This is relatively expensive: we have to walk the entire dirstate.
312
Ideally we would not, and can deprecate this function.
314
#: uncomment to trap on inventory requests.
315
# import pdb;pdb.set_trace()
316
state = self.current_dirstate()
317
state._read_dirblocks_if_needed()
318
root_key, current_entry = self._get_entry(path='')
319
current_id = root_key[2]
320
assert current_entry[0][0] == 'd' # directory
321
inv = Inventory(root_id=current_id)
322
# Turn some things into local variables
323
minikind_to_kind = dirstate.DirState._minikind_to_kind
324
factory = entry_factory
325
utf8_decode = cache_utf8._utf8_decode
327
# we could do this straight out of the dirstate; it might be fast
328
# and should be profiled - RBC 20070216
329
parent_ies = {'' : inv.root}
330
for block in state._dirblocks[1:]: # skip the root
333
parent_ie = parent_ies[dirname]
335
# all the paths in this block are not versioned in this tree
337
for key, entry in block[1]:
338
minikind, link_or_sha1, size, executable, stat = entry[0]
339
if minikind in ('a', 'r'): # absent, relocated
340
# a parent tree only entry
343
name_unicode = utf8_decode(name)[0]
345
kind = minikind_to_kind[minikind]
346
inv_entry = factory[kind](file_id, name_unicode,
349
# not strictly needed: working tree
350
#entry.executable = executable
351
#entry.text_size = size
352
#entry.text_sha1 = sha1
354
elif kind == 'directory':
355
# add this entry to the parent map.
356
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
357
elif kind == 'tree-reference':
358
assert self._repo_supports_tree_reference
359
inv_entry.reference_revision = link_or_sha1 or None
361
assert 'unknown kind'
362
# These checks cost us around 40ms on a 55k entry tree
363
assert file_id not in inv_byid, ('file_id %s already in'
364
' inventory as %s' % (file_id, inv_byid[file_id]))
365
assert name_unicode not in parent_ie.children
366
inv_byid[file_id] = inv_entry
367
parent_ie.children[name_unicode] = inv_entry
368
self._inventory = inv
370
def _get_entry(self, file_id=None, path=None):
371
"""Get the dirstate row for file_id or path.
373
If either file_id or path is supplied, it is used as the key to lookup.
374
If both are supplied, the fastest lookup is used, and an error is
375
raised if they do not both point at the same row.
377
:param file_id: An optional unicode file_id to be looked up.
378
:param path: An optional unicode path to be looked up.
379
:return: The dirstate row tuple for path/file_id, or (None, None)
381
if file_id is None and path is None:
382
raise errors.BzrError('must supply file_id or path')
383
state = self.current_dirstate()
385
path = path.encode('utf8')
386
return state._get_entry(0, fileid_utf8=file_id, path_utf8=path)
388
def get_file_sha1(self, file_id, path=None, stat_value=None):
389
# check file id is valid unconditionally.
390
entry = self._get_entry(file_id=file_id, path=path)
391
assert entry[0] is not None, 'what error should this raise'
393
# if row stat is valid, use cached sha1, else, get a new sha1.
395
path = pathjoin(entry[0][0], entry[0][1]).decode('utf8')
397
file_abspath = self.abspath(path)
398
state = self.current_dirstate()
399
link_or_sha1 = state.update_entry(entry, file_abspath,
400
stat_value=stat_value)
401
if entry[1][0][0] == 'f':
405
def _get_inventory(self):
406
"""Get the inventory for the tree. This is only valid within a lock."""
407
if self._inventory is not None:
408
return self._inventory
409
self._must_be_locked()
410
self._generate_inventory()
411
return self._inventory
413
inventory = property(_get_inventory,
414
doc="Inventory of this Tree")
417
def get_parent_ids(self):
418
"""See Tree.get_parent_ids.
420
This implementation requests the ids list from the dirstate file.
422
return self.current_dirstate().get_parent_ids()
424
def get_reference_revision(self, file_id, path=None):
425
# referenced tree's revision is whatever's currently there
426
return self.get_nested_tree(file_id, path).last_revision()
428
def get_nested_tree(self, file_id, path=None):
430
path = self.id2path(file_id)
431
# else: check file_id is at path?
432
return WorkingTree.open(self.abspath(path))
435
def get_root_id(self):
436
"""Return the id of this trees root"""
437
return self._get_entry(path='')[0][2]
439
def has_id(self, file_id):
440
state = self.current_dirstate()
441
file_id = osutils.safe_file_id(file_id)
442
row, parents = self._get_entry(file_id=file_id)
445
return osutils.lexists(pathjoin(
446
self.basedir, row[0].decode('utf8'), row[1].decode('utf8')))
449
def id2path(self, file_id):
450
file_id = osutils.safe_file_id(file_id)
451
state = self.current_dirstate()
452
entry = self._get_entry(file_id=file_id)
453
if entry == (None, None):
454
raise errors.NoSuchId(tree=self, file_id=file_id)
455
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
456
return path_utf8.decode('utf8')
460
"""Iterate through file_ids for this tree.
462
file_ids are in a WorkingTree if they are in the working inventory
463
and the working file exists.
466
for key, tree_details in self.current_dirstate()._iter_entries():
467
if tree_details[0][0] in ('a', 'r'): # absent, relocated
468
# not relevant to the working tree
470
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
471
if osutils.lexists(path):
472
result.append(key[2])
475
def iter_references(self):
476
for key, tree_details in self.current_dirstate()._iter_entries():
477
if tree_details[0][0] in ('a', 'r'): # absent, relocated
478
# not relevant to the working tree
481
# the root is not a reference.
483
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
485
if self._kind(path) == 'tree-reference':
487
except errors.NoSuchFile:
488
# path is missing on disk.
492
def kind(self, file_id):
493
"""Return the kind of a file.
495
This is always the actual kind that's on disk, regardless of what it
498
relpath = self.id2path(file_id)
499
assert relpath != None, \
500
"path for id {%s} is None!" % file_id
501
return self._kind(relpath)
503
def _kind(self, relpath):
504
abspath = self.abspath(relpath)
505
kind = file_kind(abspath)
506
if (self._repo_supports_tree_reference and
507
kind == 'directory' and
508
self._directory_is_tree_reference(relpath)):
509
kind = 'tree-reference'
513
def _last_revision(self):
514
"""See Mutable.last_revision."""
515
parent_ids = self.current_dirstate().get_parent_ids()
522
"""See Branch.lock_read, and WorkingTree.unlock."""
523
self.branch.lock_read()
525
self._control_files.lock_read()
527
state = self.current_dirstate()
528
if not state._lock_token:
530
# set our support for tree references from the repository in
532
self._repo_supports_tree_reference = getattr(
533
self.branch.repository._format, "support_tree_reference",
536
self._control_files.unlock()
542
def _lock_self_write(self):
543
"""This should be called after the branch is locked."""
545
self._control_files.lock_write()
547
state = self.current_dirstate()
548
if not state._lock_token:
550
# set our support for tree references from the repository in
552
self._repo_supports_tree_reference = getattr(
553
self.branch.repository._format, "support_tree_reference",
556
self._control_files.unlock()
562
def lock_tree_write(self):
563
"""See MutableTree.lock_tree_write, and WorkingTree.unlock."""
564
self.branch.lock_read()
565
self._lock_self_write()
567
def lock_write(self):
568
"""See MutableTree.lock_write, and WorkingTree.unlock."""
569
self.branch.lock_write()
570
self._lock_self_write()
572
@needs_tree_write_lock
573
def move(self, from_paths, to_dir, after=False):
574
"""See WorkingTree.move()."""
579
state = self.current_dirstate()
581
assert not isinstance(from_paths, basestring)
582
to_dir_utf8 = to_dir.encode('utf8')
583
to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
584
id_index = state._get_id_index()
585
# check destination directory
586
# get the details for it
587
to_entry_block_index, to_entry_entry_index, dir_present, entry_present = \
588
state._get_block_entry_index(to_entry_dirname, to_basename, 0)
589
if not entry_present:
590
raise errors.BzrMoveFailedError('', to_dir,
591
errors.NotVersionedError(to_dir))
592
to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
593
# get a handle on the block itself.
594
to_block_index = state._ensure_block(
595
to_entry_block_index, to_entry_entry_index, to_dir_utf8)
596
to_block = state._dirblocks[to_block_index]
597
to_abs = self.abspath(to_dir)
598
if not isdir(to_abs):
599
raise errors.BzrMoveFailedError('',to_dir,
600
errors.NotADirectory(to_abs))
602
if to_entry[1][0][0] != 'd':
603
raise errors.BzrMoveFailedError('',to_dir,
604
errors.NotADirectory(to_abs))
606
if self._inventory is not None:
607
update_inventory = True
609
to_dir_ie = inv[to_dir_id]
610
to_dir_id = to_entry[0][2]
612
update_inventory = False
615
def move_one(old_entry, from_path_utf8, minikind, executable,
616
fingerprint, packed_stat, size,
617
to_block, to_key, to_path_utf8):
618
state._make_absent(old_entry)
619
from_key = old_entry[0]
621
lambda:state.update_minimal(from_key,
623
executable=executable,
624
fingerprint=fingerprint,
625
packed_stat=packed_stat,
627
path_utf8=from_path_utf8))
628
state.update_minimal(to_key,
630
executable=executable,
631
fingerprint=fingerprint,
632
packed_stat=packed_stat,
634
path_utf8=to_path_utf8)
635
added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
636
new_entry = to_block[1][added_entry_index]
637
rollbacks.append(lambda:state._make_absent(new_entry))
639
# create rename entries and tuples
640
for from_rel in from_paths:
641
# from_rel is 'pathinroot/foo/bar'
642
from_rel_utf8 = from_rel.encode('utf8')
643
from_dirname, from_tail = osutils.split(from_rel)
644
from_dirname, from_tail_utf8 = osutils.split(from_rel_utf8)
645
from_entry = self._get_entry(path=from_rel)
646
if from_entry == (None, None):
647
raise errors.BzrMoveFailedError(from_rel,to_dir,
648
errors.NotVersionedError(path=str(from_rel)))
650
from_id = from_entry[0][2]
651
to_rel = pathjoin(to_dir, from_tail)
652
to_rel_utf8 = pathjoin(to_dir_utf8, from_tail_utf8)
653
item_to_entry = self._get_entry(path=to_rel)
654
if item_to_entry != (None, None):
655
raise errors.BzrMoveFailedError(from_rel, to_rel,
656
"Target is already versioned.")
658
if from_rel == to_rel:
659
raise errors.BzrMoveFailedError(from_rel, to_rel,
660
"Source and target are identical.")
662
from_missing = not self.has_filename(from_rel)
663
to_missing = not self.has_filename(to_rel)
670
raise errors.BzrMoveFailedError(from_rel, to_rel,
671
errors.NoSuchFile(path=to_rel,
672
extra="New file has not been created yet"))
674
# neither path exists
675
raise errors.BzrRenameFailedError(from_rel, to_rel,
676
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
678
if from_missing: # implicitly just update our path mapping
681
raise errors.RenameFailedFilesExist(from_rel, to_rel,
682
extra="(Use --after to update the Bazaar id)")
685
def rollback_rename():
686
"""A single rename has failed, roll it back."""
688
for rollback in reversed(rollbacks):
692
import pdb;pdb.set_trace()
693
exc_info = sys.exc_info()
695
raise exc_info[0], exc_info[1], exc_info[2]
697
# perform the disk move first - its the most likely failure point.
699
from_rel_abs = self.abspath(from_rel)
700
to_rel_abs = self.abspath(to_rel)
702
osutils.rename(from_rel_abs, to_rel_abs)
704
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
705
rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
707
# perform the rename in the inventory next if needed: its easy
711
from_entry = inv[from_id]
712
current_parent = from_entry.parent_id
713
inv.rename(from_id, to_dir_id, from_tail)
715
lambda: inv.rename(from_id, current_parent, from_tail))
716
# finally do the rename in the dirstate, which is a little
717
# tricky to rollback, but least likely to need it.
718
old_block_index, old_entry_index, dir_present, file_present = \
719
state._get_block_entry_index(from_dirname, from_tail_utf8, 0)
720
old_block = state._dirblocks[old_block_index][1]
721
old_entry = old_block[old_entry_index]
722
from_key, old_entry_details = old_entry
723
cur_details = old_entry_details[0]
725
to_key = ((to_block[0],) + from_key[1:3])
726
minikind = cur_details[0]
727
move_one(old_entry, from_path_utf8=from_rel_utf8,
729
executable=cur_details[3],
730
fingerprint=cur_details[1],
731
packed_stat=cur_details[4],
735
to_path_utf8=to_rel_utf8)
738
def update_dirblock(from_dir, to_key, to_dir_utf8):
739
"""all entries in this block need updating.
741
TODO: This is pretty ugly, and doesn't support
742
reverting, but it works.
744
assert from_dir != '', "renaming root not supported"
745
from_key = (from_dir, '')
746
from_block_idx, present = \
747
state._find_block_index_from_key(from_key)
749
# This is the old record, if it isn't present, then
750
# there is theoretically nothing to update.
751
# (Unless it isn't present because of lazy loading,
752
# but we don't do that yet)
754
from_block = state._dirblocks[from_block_idx]
755
to_block_index, to_entry_index, _, _ = \
756
state._get_block_entry_index(to_key[0], to_key[1], 0)
757
to_block_index = state._ensure_block(
758
to_block_index, to_entry_index, to_dir_utf8)
759
to_block = state._dirblocks[to_block_index]
760
for entry in from_block[1]:
761
assert entry[0][0] == from_dir
762
cur_details = entry[1][0]
763
to_key = (to_dir_utf8, entry[0][1], entry[0][2])
764
from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
765
to_path_utf8 = osutils.pathjoin(to_dir_utf8, entry[0][1])
766
minikind = cur_details[0]
767
move_one(entry, from_path_utf8=from_path_utf8,
769
executable=cur_details[3],
770
fingerprint=cur_details[1],
771
packed_stat=cur_details[4],
775
to_path_utf8=to_rel_utf8)
777
# We need to move all the children of this
779
update_dirblock(from_path_utf8, to_key,
781
update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
785
result.append((from_rel, to_rel))
786
state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
787
self._make_dirty(reset_inventory=False)
791
def _must_be_locked(self):
792
if not self._control_files._lock_count:
793
raise errors.ObjectNotLocked(self)
796
"""Initialize the state in this tree to be a new tree."""
800
def path2id(self, path):
801
"""Return the id for path in this tree."""
802
path = path.strip('/')
803
entry = self._get_entry(path=path)
804
if entry == (None, None):
808
def paths2ids(self, paths, trees=[], require_versioned=True):
809
"""See Tree.paths2ids().
811
This specialisation fast-paths the case where all the trees are in the
816
parents = self.get_parent_ids()
818
if not (isinstance(tree, DirStateRevisionTree) and tree._revision_id in
820
return super(WorkingTree4, self).paths2ids(paths, trees, require_versioned)
821
search_indexes = [0] + [1 + parents.index(tree._revision_id) for tree in trees]
822
# -- make all paths utf8 --
825
paths_utf8.add(path.encode('utf8'))
827
# -- paths is now a utf8 path set --
828
# -- get the state object and prepare it.
829
state = self.current_dirstate()
830
if False and (state._dirblock_state == dirstate.DirState.NOT_IN_MEMORY
831
and '' not in paths):
832
paths2ids = self._paths2ids_using_bisect
834
paths2ids = self._paths2ids_in_memory
835
return paths2ids(paths, search_indexes,
836
require_versioned=require_versioned)
838
def _paths2ids_in_memory(self, paths, search_indexes,
839
require_versioned=True):
840
state = self.current_dirstate()
841
state._read_dirblocks_if_needed()
842
def _entries_for_path(path):
843
"""Return a list with all the entries that match path for all ids.
845
dirname, basename = os.path.split(path)
846
key = (dirname, basename, '')
847
block_index, present = state._find_block_index_from_key(key)
849
# the block which should contain path is absent.
852
block = state._dirblocks[block_index][1]
853
entry_index, _ = state._find_entry_index(key, block)
854
# we may need to look at multiple entries at this path: walk while the paths match.
855
while (entry_index < len(block) and
856
block[entry_index][0][0:2] == key[0:2]):
857
result.append(block[entry_index])
860
if require_versioned:
861
# -- check all supplied paths are versioned in a search tree. --
864
path_entries = _entries_for_path(path)
866
# this specified path is not present at all: error
867
all_versioned = False
869
found_versioned = False
870
# for each id at this path
871
for entry in path_entries:
873
for index in search_indexes:
874
if entry[1][index][0] != 'a': # absent
875
found_versioned = True
876
# all good: found a versioned cell
878
if not found_versioned:
879
# none of the indexes was not 'absent' at all ids for this
881
all_versioned = False
883
if not all_versioned:
884
raise errors.PathsNotVersionedError(paths)
885
# -- remove redundancy in supplied paths to prevent over-scanning --
888
other_paths = paths.difference(set([path]))
889
if not osutils.is_inside_any(other_paths, path):
890
# this is a top level path, we must check it.
891
search_paths.add(path)
893
# for all search_indexs in each path at or under each element of
894
# search_paths, if the detail is relocated: add the id, and add the
895
# relocated path as one to search if its not searched already. If the
896
# detail is not relocated, add the id.
897
searched_paths = set()
899
def _process_entry(entry):
900
"""Look at search_indexes within entry.
902
If a specific tree's details are relocated, add the relocation
903
target to search_paths if not searched already. If it is absent, do
904
nothing. Otherwise add the id to found_ids.
906
for index in search_indexes:
907
if entry[1][index][0] == 'r': # relocated
908
if not osutils.is_inside_any(searched_paths, entry[1][index][1]):
909
search_paths.add(entry[1][index][1])
910
elif entry[1][index][0] != 'a': # absent
911
found_ids.add(entry[0][2])
913
current_root = search_paths.pop()
914
searched_paths.add(current_root)
915
# process the entries for this containing directory: the rest will be
916
# found by their parents recursively.
917
root_entries = _entries_for_path(current_root)
919
# this specified path is not present at all, skip it.
921
for entry in root_entries:
922
_process_entry(entry)
923
initial_key = (current_root, '', '')
924
block_index, _ = state._find_block_index_from_key(initial_key)
925
while (block_index < len(state._dirblocks) and
926
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
927
for entry in state._dirblocks[block_index][1]:
928
_process_entry(entry)
932
def _paths2ids_using_bisect(self, paths, search_indexes,
933
require_versioned=True):
934
state = self.current_dirstate()
937
split_paths = sorted(osutils.split(p) for p in paths)
938
found = state._bisect_recursive(split_paths)
940
if require_versioned:
941
found_dir_names = set(dir_name_id[:2] for dir_name_id in found)
942
for dir_name in split_paths:
943
if dir_name not in found_dir_names:
944
raise errors.PathsNotVersionedError(paths)
946
for dir_name_id, trees_info in found.iteritems():
947
for index in search_indexes:
948
if trees_info[index][0] not in ('r', 'a'):
949
found_ids.add(dir_name_id[2])
952
def read_working_inventory(self):
953
"""Read the working inventory.
955
This is a meaningless operation for dirstate, but we obey it anyhow.
957
return self.inventory
960
def revision_tree(self, revision_id):
961
"""See Tree.revision_tree.
963
WorkingTree4 supplies revision_trees for any basis tree.
965
revision_id = osutils.safe_revision_id(revision_id)
966
dirstate = self.current_dirstate()
967
parent_ids = dirstate.get_parent_ids()
968
if revision_id not in parent_ids:
969
raise errors.NoSuchRevisionInTree(self, revision_id)
970
if revision_id in dirstate.get_ghosts():
971
raise errors.NoSuchRevisionInTree(self, revision_id)
972
return DirStateRevisionTree(dirstate, revision_id,
973
self.branch.repository)
975
@needs_tree_write_lock
976
def set_last_revision(self, new_revision):
977
"""Change the last revision in the working tree."""
978
new_revision = osutils.safe_revision_id(new_revision)
979
parents = self.get_parent_ids()
980
if new_revision in (NULL_REVISION, None):
981
assert len(parents) < 2, (
982
"setting the last parent to none with a pending merge is "
984
self.set_parent_ids([])
986
self.set_parent_ids([new_revision] + parents[1:],
987
allow_leftmost_as_ghost=True)
989
@needs_tree_write_lock
990
def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
991
"""Set the parent ids to revision_ids.
993
See also set_parent_trees. This api will try to retrieve the tree data
994
for each element of revision_ids from the trees repository. If you have
995
tree data already available, it is more efficient to use
996
set_parent_trees rather than set_parent_ids. set_parent_ids is however
997
an easier API to use.
999
:param revision_ids: The revision_ids to set as the parent ids of this
1000
working tree. Any of these may be ghosts.
1002
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1004
for revision_id in revision_ids:
1006
revtree = self.branch.repository.revision_tree(revision_id)
1007
# TODO: jam 20070213 KnitVersionedFile raises
1008
# RevisionNotPresent rather than NoSuchRevision if a
1009
# given revision_id is not present. Should Repository be
1010
# catching it and re-raising NoSuchRevision?
1011
except (errors.NoSuchRevision, errors.RevisionNotPresent):
1013
trees.append((revision_id, revtree))
1014
self.current_dirstate()._validate()
1015
self.set_parent_trees(trees,
1016
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
1017
self.current_dirstate()._validate()
1019
@needs_tree_write_lock
1020
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
1021
"""Set the parents of the working tree.
1023
:param parents_list: A list of (revision_id, tree) tuples.
1024
If tree is None, then that element is treated as an unreachable
1025
parent tree - i.e. a ghost.
1027
dirstate = self.current_dirstate()
1028
dirstate._validate()
1029
if len(parents_list) > 0:
1030
if not allow_leftmost_as_ghost and parents_list[0][1] is None:
1031
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1034
# convert absent trees to the null tree, which we convert back to
1035
# missing on access.
1036
for rev_id, tree in parents_list:
1037
rev_id = osutils.safe_revision_id(rev_id)
1038
if tree is not None:
1039
real_trees.append((rev_id, tree))
1041
real_trees.append((rev_id,
1042
self.branch.repository.revision_tree(None)))
1043
ghosts.append(rev_id)
1044
dirstate._validate()
1045
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1046
dirstate._validate()
1047
self._make_dirty(reset_inventory=False)
1048
dirstate._validate()
1050
def _set_root_id(self, file_id):
1051
"""See WorkingTree.set_root_id."""
1052
state = self.current_dirstate()
1053
state.set_path_id('', file_id)
1054
if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1055
self._make_dirty(reset_inventory=True)
1058
def supports_tree_reference(self):
1059
return self._repo_supports_tree_reference
1062
"""Unlock in format 4 trees needs to write the entire dirstate."""
1063
if self._control_files._lock_count == 1:
1064
# eventually we should do signature checking during read locks for
1066
if self._control_files._lock_mode == 'w':
1069
if self._dirstate is not None:
1070
# This is a no-op if there are no modifications.
1071
self._dirstate.save()
1072
self._dirstate.unlock()
1073
# TODO: jam 20070301 We shouldn't have to wipe the dirstate at this
1074
# point. Instead, it could check if the header has been
1075
# modified when it is locked, and if not, it can hang on to
1076
# the data it has in memory.
1077
self._dirstate = None
1078
self._inventory = None
1079
# reverse order of locking.
1081
return self._control_files.unlock()
1083
self.branch.unlock()
1085
@needs_tree_write_lock
1086
def unversion(self, file_ids):
1087
"""Remove the file ids in file_ids from the current versioned set.
1089
When a file_id is unversioned, all of its children are automatically
1092
:param file_ids: The file ids to stop versioning.
1093
:raises: NoSuchId if any fileid is not currently versioned.
1097
state = self.current_dirstate()
1098
state._read_dirblocks_if_needed()
1099
ids_to_unversion = set()
1100
for file_id in file_ids:
1101
ids_to_unversion.add(osutils.safe_file_id(file_id))
1102
paths_to_unversion = set()
1104
# check if the root is to be unversioned, if so, assert for now.
1105
# walk the state marking unversioned things as absent.
1106
# if there are any un-unversioned ids at the end, raise
1107
for key, details in state._dirblocks[0][1]:
1108
if (details[0][0] not in ('a', 'r') and # absent or relocated
1109
key[2] in ids_to_unversion):
1110
# I haven't written the code to unversion / yet - it should be
1112
raise errors.BzrError('Unversioning the / is not currently supported')
1114
while block_index < len(state._dirblocks):
1115
# process one directory at a time.
1116
block = state._dirblocks[block_index]
1117
# first check: is the path one to remove - it or its children
1118
delete_block = False
1119
for path in paths_to_unversion:
1120
if (block[0].startswith(path) and
1121
(len(block[0]) == len(path) or
1122
block[0][len(path)] == '/')):
1123
# this entire block should be deleted - its the block for a
1124
# path to unversion; or the child of one
1127
# TODO: trim paths_to_unversion as we pass by paths
1129
# this block is to be deleted: process it.
1130
# TODO: we can special case the no-parents case and
1131
# just forget the whole block.
1133
while entry_index < len(block[1]):
1134
# Mark this file id as having been removed
1135
ids_to_unversion.discard(block[1][entry_index][0][2])
1136
if not state._make_absent(block[1][entry_index]):
1138
# go to the next block. (At the moment we dont delete empty
1143
while entry_index < len(block[1]):
1144
entry = block[1][entry_index]
1145
if (entry[1][0][0] in ('a', 'r') or # absent, relocated
1146
# ^ some parent row.
1147
entry[0][2] not in ids_to_unversion):
1148
# ^ not an id to unversion
1151
if entry[1][0][0] == 'd':
1152
paths_to_unversion.add(pathjoin(entry[0][0], entry[0][1]))
1153
if not state._make_absent(entry):
1155
# we have unversioned this id
1156
ids_to_unversion.remove(entry[0][2])
1158
if ids_to_unversion:
1159
raise errors.NoSuchId(self, iter(ids_to_unversion).next())
1160
self._make_dirty(reset_inventory=False)
1161
# have to change the legacy inventory too.
1162
if self._inventory is not None:
1163
for file_id in file_ids:
1164
self._inventory.remove_recursive_id(file_id)
1166
@needs_tree_write_lock
1167
def _write_inventory(self, inv):
1168
"""Write inventory as the current inventory."""
1169
assert not self._dirty, "attempting to write an inventory when the dirstate is dirty will cause data loss"
1170
self.current_dirstate().set_state_from_inventory(inv)
1171
self._make_dirty(reset_inventory=False)
1172
if self._inventory is not None:
1173
self._inventory = inv
1177
class WorkingTreeFormat4(WorkingTreeFormat3):
1178
"""The first consolidated dirstate working tree format.
1181
- exists within a metadir controlling .bzr
1182
- includes an explicit version marker for the workingtree control
1183
files, separate from the BzrDir format
1184
- modifies the hash cache format
1185
- is new in bzr 0.15
1186
- uses a LockDir to guard access to it.
1189
def get_format_string(self):
1190
"""See WorkingTreeFormat.get_format_string()."""
1191
return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1193
def get_format_description(self):
1194
"""See WorkingTreeFormat.get_format_description()."""
1195
return "Working tree format 4"
1197
def initialize(self, a_bzrdir, revision_id=None):
1198
"""See WorkingTreeFormat.initialize().
1200
:param revision_id: allows creating a working tree at a different
1201
revision than the branch is at.
1203
These trees get an initial random root id.
1205
revision_id = osutils.safe_revision_id(revision_id)
1206
if not isinstance(a_bzrdir.transport, LocalTransport):
1207
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1208
transport = a_bzrdir.get_workingtree_transport(self)
1209
control_files = self._open_control_files(a_bzrdir)
1210
control_files.create_lock()
1211
control_files.lock_write()
1212
control_files.put_utf8('format', self.get_format_string())
1213
branch = a_bzrdir.open_branch()
1214
if revision_id is None:
1215
revision_id = branch.last_revision()
1216
local_path = transport.local_abspath('dirstate')
1217
# write out new dirstate (must exist when we create the tree)
1218
state = dirstate.DirState.initialize(local_path)
1220
wt = WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1224
_control_files=control_files)
1226
wt.lock_tree_write()
1229
if revision_id in (None, NULL_REVISION):
1230
wt._set_root_id(generate_ids.gen_root_id())
1232
wt.current_dirstate()._validate()
1233
wt.set_last_revision(revision_id)
1235
basis = wt.basis_tree()
1237
# if the basis has a root id we have to use that; otherwise we use
1239
basis_root_id = basis.get_root_id()
1240
if basis_root_id is not None:
1241
wt._set_root_id(basis_root_id)
1243
transform.build_tree(basis, wt)
1246
control_files.unlock()
1250
def _open(self, a_bzrdir, control_files):
1251
"""Open the tree itself.
1253
:param a_bzrdir: the dir for the tree.
1254
:param control_files: the control files for the tree.
1256
return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1257
branch=a_bzrdir.open_branch(),
1260
_control_files=control_files)
1262
def __get_matchingbzrdir(self):
1263
# please test against something that will let us do tree references
1264
return bzrdir.format_registry.make_bzrdir(
1265
'dirstate-with-subtree')
1267
_matchingbzrdir = property(__get_matchingbzrdir)
1270
class DirStateRevisionTree(Tree):
1271
"""A revision tree pulling the inventory from a dirstate."""
1273
def __init__(self, dirstate, revision_id, repository):
1274
self._dirstate = dirstate
1275
self._revision_id = osutils.safe_revision_id(revision_id)
1276
self._repository = repository
1277
self._inventory = None
1279
self._dirstate_locked = False
1282
return "<%s of %s in %s>" % \
1283
(self.__class__.__name__, self._revision_id, self._dirstate)
1285
def annotate_iter(self, file_id):
1286
"""See Tree.annotate_iter"""
1287
w = self._repository.weave_store.get_weave(file_id,
1288
self._repository.get_transaction())
1289
return w.annotate_iter(self.inventory[file_id].revision)
1291
def _comparison_data(self, entry, path):
1292
"""See Tree._comparison_data."""
1294
return None, False, None
1295
# trust the entry as RevisionTree does, but this may not be
1296
# sensible: the entry might not have come from us?
1297
return entry.kind, entry.executable, None
1299
def _file_size(self, entry, stat_value):
1300
return entry.text_size
1302
def filter_unversioned_files(self, paths):
1303
"""Filter out paths that are not versioned.
1305
:return: set of paths.
1307
pred = self.has_filename
1308
return set((p for p in paths if not pred(p)))
1310
def get_root_id(self):
1311
return self.path2id('')
1313
def _get_parent_index(self):
1314
"""Return the index in the dirstate referenced by this tree."""
1315
return self._dirstate.get_parent_ids().index(self._revision_id) + 1
1317
def _get_entry(self, file_id=None, path=None):
1318
"""Get the dirstate row for file_id or path.
1320
If either file_id or path is supplied, it is used as the key to lookup.
1321
If both are supplied, the fastest lookup is used, and an error is
1322
raised if they do not both point at the same row.
1324
:param file_id: An optional unicode file_id to be looked up.
1325
:param path: An optional unicode path to be looked up.
1326
:return: The dirstate row tuple for path/file_id, or (None, None)
1328
if file_id is None and path is None:
1329
raise errors.BzrError('must supply file_id or path')
1330
file_id = osutils.safe_file_id(file_id)
1331
if path is not None:
1332
path = path.encode('utf8')
1333
parent_index = self._get_parent_index()
1334
return self._dirstate._get_entry(parent_index, fileid_utf8=file_id, path_utf8=path)
1336
def _generate_inventory(self):
1337
"""Create and set self.inventory from the dirstate object.
1339
(So this is only called the first time the inventory is requested for
1340
this tree; it then remains in memory until it's out of date.)
1342
This is relatively expensive: we have to walk the entire dirstate.
1344
assert self._locked, 'cannot generate inventory of an unlocked '\
1345
'dirstate revision tree'
1346
# separate call for profiling - makes it clear where the costs are.
1347
self._dirstate._read_dirblocks_if_needed()
1348
assert self._revision_id in self._dirstate.get_parent_ids(), \
1349
'parent %s has disappeared from %s' % (
1350
self._revision_id, self._dirstate.get_parent_ids())
1351
parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
1352
# This is identical now to the WorkingTree _generate_inventory except
1353
# for the tree index use.
1354
root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1355
current_id = root_key[2]
1356
assert current_entry[parent_index][0] == 'd'
1357
inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1358
inv.root.revision = current_entry[parent_index][4]
1359
# Turn some things into local variables
1360
minikind_to_kind = dirstate.DirState._minikind_to_kind
1361
factory = entry_factory
1362
utf8_decode = cache_utf8._utf8_decode
1363
inv_byid = inv._byid
1364
# we could do this straight out of the dirstate; it might be fast
1365
# and should be profiled - RBC 20070216
1366
parent_ies = {'' : inv.root}
1367
for block in self._dirstate._dirblocks[1:]: #skip root
1370
parent_ie = parent_ies[dirname]
1372
# all the paths in this block are not versioned in this tree
1374
for key, entry in block[1]:
1375
minikind, fingerprint, size, executable, revid = entry[parent_index]
1376
if minikind in ('a', 'r'): # absent, relocated
1380
name_unicode = utf8_decode(name)[0]
1382
kind = minikind_to_kind[minikind]
1383
inv_entry = factory[kind](file_id, name_unicode,
1385
inv_entry.revision = revid
1387
inv_entry.executable = executable
1388
inv_entry.text_size = size
1389
inv_entry.text_sha1 = fingerprint
1390
elif kind == 'directory':
1391
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1392
elif kind == 'symlink':
1393
inv_entry.executable = False
1394
inv_entry.text_size = size
1395
inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1396
elif kind == 'tree-reference':
1397
inv_entry.reference_revision = fingerprint or None
1399
raise AssertionError("cannot convert entry %r into an InventoryEntry"
1401
# These checks cost us around 40ms on a 55k entry tree
1402
assert file_id not in inv_byid
1403
assert name_unicode not in parent_ie.children
1404
inv_byid[file_id] = inv_entry
1405
parent_ie.children[name_unicode] = inv_entry
1406
self._inventory = inv
1408
def get_file_mtime(self, file_id, path=None):
1409
"""Return the modification time for this record.
1411
We return the timestamp of the last-changed revision.
1413
# Make sure the file exists
1414
entry = self._get_entry(file_id, path=path)
1415
if entry == (None, None): # do we raise?
1417
parent_index = self._get_parent_index()
1418
last_changed_revision = entry[1][parent_index][4]
1419
return self._repository.get_revision(last_changed_revision).timestamp
1421
def get_file_sha1(self, file_id, path=None, stat_value=None):
1422
entry = self._get_entry(file_id=file_id, path=path)
1423
parent_index = self._get_parent_index()
1424
parent_details = entry[1][parent_index]
1425
if parent_details[0] == 'f':
1426
return parent_details[1]
1429
def get_file(self, file_id):
1430
return StringIO(self.get_file_text(file_id))
1432
def get_file_lines(self, file_id):
1433
ie = self.inventory[file_id]
1434
return self._repository.weave_store.get_weave(file_id,
1435
self._repository.get_transaction()).get_lines(ie.revision)
1437
def get_file_size(self, file_id):
1438
return self.inventory[file_id].text_size
1440
def get_file_text(self, file_id):
1441
return ''.join(self.get_file_lines(file_id))
1443
def get_reference_revision(self, file_id, path=None):
1444
return self.inventory[file_id].reference_revision
1446
def get_symlink_target(self, file_id):
1447
entry = self._get_entry(file_id=file_id)
1448
parent_index = self._get_parent_index()
1449
if entry[1][parent_index][0] != 'l':
1452
# At present, none of the tree implementations supports non-ascii
1453
# symlink targets. So we will just assume that the dirstate path is
1455
return entry[1][parent_index][1]
1457
def get_revision_id(self):
1458
"""Return the revision id for this tree."""
1459
return self._revision_id
1461
def _get_inventory(self):
1462
if self._inventory is not None:
1463
return self._inventory
1464
self._must_be_locked()
1465
self._generate_inventory()
1466
return self._inventory
1468
inventory = property(_get_inventory,
1469
doc="Inventory of this Tree")
1471
def get_parent_ids(self):
1472
"""The parents of a tree in the dirstate are not cached."""
1473
return self._repository.get_revision(self._revision_id).parent_ids
1475
def has_filename(self, filename):
1476
return bool(self.path2id(filename))
1478
def kind(self, file_id):
1479
return self.inventory[file_id].kind
1481
def is_executable(self, file_id, path=None):
1482
ie = self.inventory[file_id]
1483
if ie.kind != "file":
1485
return ie.executable
1487
def list_files(self, include_root=False):
1488
# We use a standard implementation, because DirStateRevisionTree is
1489
# dealing with one of the parents of the current state
1490
inv = self._get_inventory()
1491
entries = inv.iter_entries()
1492
if self.inventory.root is not None and not include_root:
1494
for path, entry in entries:
1495
yield path, 'V', entry.kind, entry.file_id, entry
1497
def lock_read(self):
1498
"""Lock the tree for a set of operations."""
1499
if not self._locked:
1500
self._repository.lock_read()
1501
if self._dirstate._lock_token is None:
1502
self._dirstate.lock_read()
1503
self._dirstate_locked = True
1506
def _must_be_locked(self):
1507
if not self._locked:
1508
raise errors.ObjectNotLocked(self)
1511
def path2id(self, path):
1512
"""Return the id for path in this tree."""
1513
# lookup by path: faster than splitting and walking the ivnentory.
1514
entry = self._get_entry(path=path)
1515
if entry == (None, None):
1520
"""Unlock, freeing any cache memory used during the lock."""
1521
# outside of a lock, the inventory is suspect: release it.
1523
if not self._locked:
1524
self._inventory = None
1526
if self._dirstate_locked:
1527
self._dirstate.unlock()
1528
self._dirstate_locked = False
1529
self._repository.unlock()
1531
def walkdirs(self, prefix=""):
1532
# TODO: jam 20070215 This is the cheap way by cheating and using the
1533
# RevisionTree implementation.
1534
# This should be cleaned up to use the much faster Dirstate code
1535
# This is a little tricky, though, because the dirstate is
1536
# indexed by current path, not by parent path.
1537
# So for now, we just build up the parent inventory, and extract
1538
# it the same way RevisionTree does.
1539
_directory = 'directory'
1540
inv = self._get_inventory()
1541
top_id = inv.path2id(prefix)
1545
pending = [(prefix, top_id)]
1548
relpath, file_id = pending.pop()
1549
# 0 - relpath, 1- file-id
1551
relroot = relpath + '/'
1554
# FIXME: stash the node in pending
1555
entry = inv[file_id]
1556
for name, child in entry.sorted_children():
1557
toppath = relroot + name
1558
dirblock.append((toppath, name, child.kind, None,
1559
child.file_id, child.kind
1561
yield (relpath, entry.file_id), dirblock
1562
# push the user specified dirs from dirblock
1563
for dir in reversed(dirblock):
1564
if dir[2] == _directory:
1565
pending.append((dir[0], dir[4]))
1568
class InterDirStateTree(InterTree):
1569
"""Fast path optimiser for changes_from with dirstate trees.
1571
This is used only when both trees are in the dirstate working file, and
1572
the source is any parent within the dirstate, and the destination is
1573
the current working tree of the same dirstate.
1575
# this could be generalized to allow comparisons between any trees in the
1576
# dirstate, and possibly between trees stored in different dirstates.
1578
def __init__(self, source, target):
1579
super(InterDirStateTree, self).__init__(source, target)
1580
if not InterDirStateTree.is_compatible(source, target):
1581
raise Exception, "invalid source %r and target %r" % (source, target)
1584
def make_source_parent_tree(source, target):
1585
"""Change the source tree into a parent of the target."""
1586
revid = source.commit('record tree')
1587
target.branch.repository.fetch(source.branch.repository, revid)
1588
target.set_parent_ids([revid])
1589
return target.basis_tree(), target
1591
_matching_from_tree_format = WorkingTreeFormat4()
1592
_matching_to_tree_format = WorkingTreeFormat4()
1593
_test_mutable_trees_to_test_trees = make_source_parent_tree
1595
def _iter_changes(self, include_unchanged=False,
1596
specific_files=None, pb=None, extra_trees=[],
1597
require_versioned=True, want_unversioned=False):
1598
"""Return the changes from source to target.
1600
:return: An iterator that yields tuples. See InterTree._iter_changes
1602
:param specific_files: An optional list of file paths to restrict the
1603
comparison to. When mapping filenames to ids, all matches in all
1604
trees (including optional extra_trees) are used, and all children of
1605
matched directories are included.
1606
:param include_unchanged: An optional boolean requesting the inclusion of
1607
unchanged entries in the result.
1608
:param extra_trees: An optional list of additional trees to use when
1609
mapping the contents of specific_files (paths) to file_ids.
1610
:param require_versioned: If True, all files in specific_files must be
1611
versioned in one of source, target, extra_trees or
1612
PathsNotVersionedError is raised.
1613
:param want_unversioned: Should unversioned files be returned in the
1614
output. An unversioned file is defined as one with (False, False)
1615
for the versioned pair.
1617
utf8_decode = cache_utf8._utf8_decode_with_None
1618
_minikind_to_kind = dirstate.DirState._minikind_to_kind
1619
# NB: show_status depends on being able to pass in non-versioned files
1620
# and report them as unknown
1621
# TODO: handle extra trees in the dirstate.
1622
# TODO: handle comparisons as an empty tree as a different special
1623
# case? mbp 20070226
1624
if extra_trees or (self.source._revision_id == NULL_REVISION):
1625
# we can't fast-path these cases (yet)
1626
for f in super(InterDirStateTree, self)._iter_changes(
1627
include_unchanged, specific_files, pb, extra_trees,
1628
require_versioned, want_unversioned=want_unversioned):
1631
parent_ids = self.target.get_parent_ids()
1632
assert (self.source._revision_id in parent_ids), \
1633
"revision {%s} is not stored in {%s}, but %s " \
1634
"can only be used for trees stored in the dirstate" \
1635
% (self.source._revision_id, self.target, self._iter_changes)
1637
if self.source._revision_id == NULL_REVISION:
1639
indices = (target_index,)
1641
assert (self.source._revision_id in parent_ids), \
1642
"Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
1643
self.source._revision_id, parent_ids)
1644
source_index = 1 + parent_ids.index(self.source._revision_id)
1645
indices = (source_index,target_index)
1646
# -- make all specific_files utf8 --
1648
specific_files_utf8 = set()
1649
for path in specific_files:
1650
specific_files_utf8.add(path.encode('utf8'))
1651
specific_files = specific_files_utf8
1653
specific_files = set([''])
1654
# -- specific_files is now a utf8 path set --
1655
# -- get the state object and prepare it.
1656
state = self.target.current_dirstate()
1657
state._read_dirblocks_if_needed()
1658
def _entries_for_path(path):
1659
"""Return a list with all the entries that match path for all ids.
1661
dirname, basename = os.path.split(path)
1662
key = (dirname, basename, '')
1663
block_index, present = state._find_block_index_from_key(key)
1665
# the block which should contain path is absent.
1668
block = state._dirblocks[block_index][1]
1669
entry_index, _ = state._find_entry_index(key, block)
1670
# we may need to look at multiple entries at this path: walk while the specific_files match.
1671
while (entry_index < len(block) and
1672
block[entry_index][0][0:2] == key[0:2]):
1673
result.append(block[entry_index])
1676
if require_versioned:
1677
# -- check all supplied paths are versioned in a search tree. --
1678
all_versioned = True
1679
for path in specific_files:
1680
path_entries = _entries_for_path(path)
1681
if not path_entries:
1682
# this specified path is not present at all: error
1683
all_versioned = False
1685
found_versioned = False
1686
# for each id at this path
1687
for entry in path_entries:
1689
for index in indices:
1690
if entry[1][index][0] != 'a': # absent
1691
found_versioned = True
1692
# all good: found a versioned cell
1694
if not found_versioned:
1695
# none of the indexes was not 'absent' at all ids for this
1697
all_versioned = False
1699
if not all_versioned:
1700
raise errors.PathsNotVersionedError(specific_files)
1701
# -- remove redundancy in supplied specific_files to prevent over-scanning --
1702
search_specific_files = set()
1703
for path in specific_files:
1704
other_specific_files = specific_files.difference(set([path]))
1705
if not osutils.is_inside_any(other_specific_files, path):
1706
# this is a top level path, we must check it.
1707
search_specific_files.add(path)
1709
# compare source_index and target_index at or under each element of search_specific_files.
1710
# follow the following comparison table. Note that we only want to do diff operations when
1711
# the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
1715
# Source | Target | disk | action
1716
# r | fdlt | | add source to search, add id path move and perform
1717
# | | | diff check on source-target
1718
# r | fdlt | a | dangling file that was present in the basis.
1720
# r | a | | add source to search
1722
# r | r | | this path is present in a non-examined tree, skip.
1723
# r | r | a | this path is present in a non-examined tree, skip.
1724
# a | fdlt | | add new id
1725
# a | fdlt | a | dangling locally added file, skip
1726
# a | a | | not present in either tree, skip
1727
# a | a | a | not present in any tree, skip
1728
# a | r | | not present in either tree at this path, skip as it
1729
# | | | may not be selected by the users list of paths.
1730
# a | r | a | not present in either tree at this path, skip as it
1731
# | | | may not be selected by the users list of paths.
1732
# fdlt | fdlt | | content in both: diff them
1733
# fdlt | fdlt | a | deleted locally, but not unversioned - show as deleted ?
1734
# fdlt | a | | unversioned: output deleted id for now
1735
# fdlt | a | a | unversioned and deleted: output deleted id
1736
# fdlt | r | | relocated in this tree, so add target to search.
1737
# | | | Dont diff, we will see an r,fd; pair when we reach
1738
# | | | this id at the other path.
1739
# fdlt | r | a | relocated in this tree, so add target to search.
1740
# | | | Dont diff, we will see an r,fd; pair when we reach
1741
# | | | this id at the other path.
1743
# for all search_indexs in each path at or under each element of
1744
# search_specific_files, if the detail is relocated: add the id, and add the
1745
# relocated path as one to search if its not searched already. If the
1746
# detail is not relocated, add the id.
1747
searched_specific_files = set()
1748
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1749
# Using a list so that we can access the values and change them in
1750
# nested scope. Each one is [path, file_id, entry]
1751
last_source_parent = [None, None, None]
1752
last_target_parent = [None, None, None]
1754
use_filesystem_for_exec = (sys.platform != 'win32')
1756
def _process_entry(entry, path_info):
1757
"""Compare an entry and real disk to generate delta information.
1759
:param path_info: top_relpath, basename, kind, lstat, abspath for
1760
the path of entry. If None, then the path is considered absent.
1761
(Perhaps we should pass in a concrete entry for this ?)
1762
Basename is returned as a utf8 string because we expect this
1763
tuple will be ignored, and don't want to take the time to
1766
# TODO: when a parent has been renamed, dont emit path renames for children,
1767
## if path_info[1] == 'sub':
1768
## import pdb;pdb.set_trace()
1769
if source_index is None:
1770
source_details = NULL_PARENT_DETAILS
1772
source_details = entry[1][source_index]
1773
target_details = entry[1][target_index]
1774
target_minikind = target_details[0]
1775
if path_info is not None and target_minikind in 'fdlt':
1776
assert target_index == 0
1777
link_or_sha1 = state.update_entry(entry, abspath=path_info[4],
1778
stat_value=path_info[3])
1779
# The entry may have been modified by update_entry
1780
target_details = entry[1][target_index]
1781
target_minikind = target_details[0]
1784
source_minikind = source_details[0]
1785
if source_minikind in 'fdltr' and target_minikind in 'fdlt':
1786
# claimed content in both: diff
1787
# r | fdlt | | add source to search, add id path move and perform
1788
# | | | diff check on source-target
1789
# r | fdlt | a | dangling file that was present in the basis.
1791
if source_minikind in 'r':
1792
# add the source to the search path to find any children it
1793
# has. TODO ? : only add if it is a container ?
1794
if not osutils.is_inside_any(searched_specific_files,
1796
search_specific_files.add(source_details[1])
1797
# generate the old path; this is needed for stating later
1799
old_path = source_details[1]
1800
old_dirname, old_basename = os.path.split(old_path)
1801
path = pathjoin(entry[0][0], entry[0][1])
1802
old_entry = state._get_entry(source_index,
1804
# update the source details variable to be the real
1806
source_details = old_entry[1][source_index]
1807
source_minikind = source_details[0]
1809
old_dirname = entry[0][0]
1810
old_basename = entry[0][1]
1811
old_path = path = pathjoin(old_dirname, old_basename)
1812
if path_info is None:
1813
# the file is missing on disk, show as removed.
1814
content_change = True
1818
# source and target are both versioned and disk file is present.
1819
target_kind = path_info[2]
1820
if target_kind == 'directory':
1821
if source_minikind != 'd':
1822
content_change = True
1824
# directories have no fingerprint
1825
content_change = False
1827
elif target_kind == 'file':
1828
if source_minikind != 'f':
1829
content_change = True
1831
# We could check the size, but we already have the
1833
content_change = (link_or_sha1 != source_details[1])
1834
# Target details is updated at update_entry time
1835
if use_filesystem_for_exec:
1836
# We don't need S_ISREG here, because we are sure
1837
# we are dealing with a file.
1838
target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
1840
target_exec = target_details[3]
1841
elif target_kind == 'symlink':
1842
if source_minikind != 'l':
1843
content_change = True
1845
content_change = (link_or_sha1 != source_details[1])
1847
elif target_kind == 'tree-reference':
1848
if source_minikind != 't':
1849
content_change = True
1851
content_change = False
1854
raise Exception, "unknown kind %s" % path_info[2]
1855
# parent id is the entry for the path in the target tree
1856
if old_dirname == last_source_parent[0]:
1857
source_parent_id = last_source_parent[1]
1859
source_parent_entry = state._get_entry(source_index,
1860
path_utf8=old_dirname)
1861
source_parent_id = source_parent_entry[0][2]
1862
if source_parent_id == entry[0][2]:
1863
# This is the root, so the parent is None
1864
source_parent_id = None
1866
last_source_parent[0] = old_dirname
1867
last_source_parent[1] = source_parent_id
1868
last_source_parent[2] = source_parent_entry
1870
new_dirname = entry[0][0]
1871
if new_dirname == last_target_parent[0]:
1872
target_parent_id = last_target_parent[1]
1874
# TODO: We don't always need to do the lookup, because the
1875
# parent entry will be the same as the source entry.
1876
target_parent_entry = state._get_entry(target_index,
1877
path_utf8=new_dirname)
1878
target_parent_id = target_parent_entry[0][2]
1879
if target_parent_id == entry[0][2]:
1880
# This is the root, so the parent is None
1881
target_parent_id = None
1883
last_target_parent[0] = new_dirname
1884
last_target_parent[1] = target_parent_id
1885
last_target_parent[2] = target_parent_entry
1887
source_exec = source_details[3]
1888
return ((entry[0][2], (old_path, path), content_change,
1890
(source_parent_id, target_parent_id),
1891
(old_basename, entry[0][1]),
1892
(_minikind_to_kind[source_minikind], target_kind),
1893
(source_exec, target_exec)),)
1894
elif source_minikind in 'a' and target_minikind in 'fdlt':
1895
# looks like a new file
1896
if path_info is not None:
1897
path = pathjoin(entry[0][0], entry[0][1])
1898
# parent id is the entry for the path in the target tree
1899
# TODO: these are the same for an entire directory: cache em.
1900
parent_id = state._get_entry(target_index,
1901
path_utf8=entry[0][0])[0][2]
1902
if parent_id == entry[0][2]:
1904
if use_filesystem_for_exec:
1905
# We need S_ISREG here, because we aren't sure if this
1908
stat.S_ISREG(path_info[3].st_mode)
1909
and stat.S_IEXEC & path_info[3].st_mode)
1911
target_exec = target_details[3]
1912
return ((entry[0][2], (None, path), True,
1915
(None, entry[0][1]),
1916
(None, path_info[2]),
1917
(None, target_exec)),)
1919
# but its not on disk: we deliberately treat this as just
1920
# never-present. (Why ?! - RBC 20070224)
1922
elif source_minikind in 'fdlt' and target_minikind in 'a':
1923
# unversioned, possibly, or possibly not deleted: we dont care.
1924
# if its still on disk, *and* theres no other entry at this
1925
# path [we dont know this in this routine at the moment -
1926
# perhaps we should change this - then it would be an unknown.
1927
old_path = pathjoin(entry[0][0], entry[0][1])
1928
# parent id is the entry for the path in the target tree
1929
parent_id = state._get_entry(source_index, path_utf8=entry[0][0])[0][2]
1930
if parent_id == entry[0][2]:
1932
return ((entry[0][2], (old_path, None), True,
1935
(entry[0][1], None),
1936
(_minikind_to_kind[source_minikind], None),
1937
(source_details[3], None)),)
1938
elif source_minikind in 'fdlt' and target_minikind in 'r':
1939
# a rename; could be a true rename, or a rename inherited from
1940
# a renamed parent. TODO: handle this efficiently. Its not
1941
# common case to rename dirs though, so a correct but slow
1942
# implementation will do.
1943
if not osutils.is_inside_any(searched_specific_files, target_details[1]):
1944
search_specific_files.add(target_details[1])
1945
elif source_minikind in 'r' and target_minikind in 'r':
1946
# neither of the selected trees contain this file,
1947
# so skip over it. This is not currently directly tested, but
1948
# is indirectly via test_too_much.TestCommands.test_conflicts.
1951
raise AssertionError("don't know how to compare "
1952
"source_minikind=%r, target_minikind=%r"
1953
% (source_minikind, target_minikind))
1954
## import pdb;pdb.set_trace()
1956
while search_specific_files:
1957
# TODO: the pending list should be lexically sorted?
1958
current_root = search_specific_files.pop()
1959
searched_specific_files.add(current_root)
1960
# process the entries for this containing directory: the rest will be
1961
# found by their parents recursively.
1962
root_entries = _entries_for_path(current_root)
1963
root_abspath = self.target.abspath(current_root)
1965
root_stat = os.lstat(root_abspath)
1967
if e.errno == errno.ENOENT:
1968
# the path does not exist: let _process_entry know that.
1969
root_dir_info = None
1971
# some other random error: hand it up.
1974
root_dir_info = ('', current_root,
1975
osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
1977
if root_dir_info[2] == 'directory':
1978
if self.target._directory_is_tree_reference(
1979
current_root.decode('utf8')):
1980
root_dir_info = root_dir_info[:2] + \
1981
('tree-reference',) + root_dir_info[3:]
1983
if not root_entries and not root_dir_info:
1984
# this specified path is not present at all, skip it.
1986
path_handled = False
1987
for entry in root_entries:
1988
for result in _process_entry(entry, root_dir_info):
1989
# this check should probably be outside the loop: one
1990
# 'iterate two trees' api, and then _iter_changes filters
1991
# unchanged pairs. - RBC 20070226
1993
if (include_unchanged
1994
or result[2] # content change
1995
or result[3][0] != result[3][1] # versioned status
1996
or result[4][0] != result[4][1] # parent id
1997
or result[5][0] != result[5][1] # name
1998
or result[6][0] != result[6][1] # kind
1999
or result[7][0] != result[7][1] # executable
2001
result = (result[0],
2002
((utf8_decode(result[1][0])[0]),
2003
utf8_decode(result[1][1])[0]),) + result[2:]
2005
if want_unversioned and not path_handled:
2006
new_executable = bool(
2007
stat.S_ISREG(root_dir_info[3].st_mode)
2008
and stat.S_IEXEC & root_dir_info[3].st_mode)
2009
yield (None, (None, current_root), True, (False, False),
2011
(None, splitpath(current_root)[-1]),
2012
(None, root_dir_info[2]), (None, new_executable))
2013
dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
2014
initial_key = (current_root, '', '')
2015
block_index, _ = state._find_block_index_from_key(initial_key)
2016
if block_index == 0:
2017
# we have processed the total root already, but because the
2018
# initial key matched it we should skip it here.
2021
current_dir_info = dir_iterator.next()
2023
if e.errno in (errno.ENOENT, errno.ENOTDIR):
2024
# there may be directories in the inventory even though
2025
# this path is not a file on disk: so mark it as end of
2027
current_dir_info = None
2031
if current_dir_info[0][0] == '':
2032
# remove .bzr from iteration
2033
bzr_index = bisect_left(current_dir_info[1], ('.bzr',))
2034
assert current_dir_info[1][bzr_index][0] == '.bzr'
2035
del current_dir_info[1][bzr_index]
2036
# walk until both the directory listing and the versioned metadata
2037
# are exhausted. TODO: reevaluate this, perhaps we should stop when
2038
# the versioned data runs out.
2039
if (block_index < len(state._dirblocks) and
2040
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
2041
current_block = state._dirblocks[block_index]
2043
current_block = None
2044
while (current_dir_info is not None or
2045
current_block is not None):
2046
if (current_dir_info and current_block
2047
and current_dir_info[0][0] != current_block[0]):
2048
if current_dir_info[0][0] < current_block[0] :
2049
# filesystem data refers to paths not covered by the dirblock.
2050
# this has two possibilities:
2051
# A) it is versioned but empty, so there is no block for it
2052
# B) it is not versioned.
2053
# in either case it was processed by the containing directories walk:
2054
# if it is root/foo, when we walked root we emitted it,
2055
# or if we ere given root/foo to walk specifically, we
2056
# emitted it when checking the walk-root entries
2057
# advance the iterator and loop - we dont need to emit it.
2059
current_dir_info = dir_iterator.next()
2060
except StopIteration:
2061
current_dir_info = None
2063
# We have a dirblock entry for this location, but there
2064
# is no filesystem path for this. This is most likely
2065
# because a directory was removed from the disk.
2066
# We don't have to report the missing directory,
2067
# because that should have already been handled, but we
2068
# need to handle all of the files that are contained
2070
for current_entry in current_block[1]:
2071
# entry referring to file not present on disk.
2072
# advance the entry only, after processing.
2073
for result in _process_entry(current_entry, None):
2074
# this check should probably be outside the loop: one
2075
# 'iterate two trees' api, and then _iter_changes filters
2076
# unchanged pairs. - RBC 20070226
2077
if (include_unchanged
2078
or result[2] # content change
2079
or result[3][0] != result[3][1] # versioned status
2080
or result[4][0] != result[4][1] # parent id
2081
or result[5][0] != result[5][1] # name
2082
or result[6][0] != result[6][1] # kind
2083
or result[7][0] != result[7][1] # executable
2085
result = (result[0],
2086
((utf8_decode(result[1][0])[0]),
2087
utf8_decode(result[1][1])[0]),) + result[2:]
2090
if (block_index < len(state._dirblocks) and
2091
osutils.is_inside(current_root,
2092
state._dirblocks[block_index][0])):
2093
current_block = state._dirblocks[block_index]
2095
current_block = None
2098
if current_block and entry_index < len(current_block[1]):
2099
current_entry = current_block[1][entry_index]
2101
current_entry = None
2102
advance_entry = True
2104
if current_dir_info and path_index < len(current_dir_info[1]):
2105
current_path_info = current_dir_info[1][path_index]
2106
if current_path_info[2] == 'directory':
2107
if self.target._directory_is_tree_reference(
2108
current_path_info[0].decode('utf8')):
2109
current_path_info = current_path_info[:2] + \
2110
('tree-reference',) + current_path_info[3:]
2112
current_path_info = None
2114
path_handled = False
2115
while (current_entry is not None or
2116
current_path_info is not None):
2117
if current_entry is None:
2118
# the check for path_handled when the path is adnvaced
2119
# will yield this path if needed.
2121
elif current_path_info is None:
2122
# no path is fine: the per entry code will handle it.
2123
for result in _process_entry(current_entry, current_path_info):
2124
# this check should probably be outside the loop: one
2125
# 'iterate two trees' api, and then _iter_changes filters
2126
# unchanged pairs. - RBC 20070226
2127
if (include_unchanged
2128
or result[2] # content change
2129
or result[3][0] != result[3][1] # versioned status
2130
or result[4][0] != result[4][1] # parent id
2131
or result[5][0] != result[5][1] # name
2132
or result[6][0] != result[6][1] # kind
2133
or result[7][0] != result[7][1] # executable
2135
result = (result[0],
2136
((utf8_decode(result[1][0])[0]),
2137
utf8_decode(result[1][1])[0]),) + result[2:]
2139
elif current_entry[0][1] != current_path_info[1]:
2140
if current_path_info[1] < current_entry[0][1]:
2141
# extra file on disk: pass for now, but only
2142
# increment the path, not the entry
2143
# import pdb; pdb.set_trace()
2144
# print 'unversioned file'
2145
advance_entry = False
2147
# entry referring to file not present on disk.
2148
# advance the entry only, after processing.
2149
for result in _process_entry(current_entry, None):
2150
# this check should probably be outside the loop: one
2151
# 'iterate two trees' api, and then _iter_changes filters
2152
# unchanged pairs. - RBC 20070226
2154
if (include_unchanged
2155
or result[2] # content change
2156
or result[3][0] != result[3][1] # versioned status
2157
or result[4][0] != result[4][1] # parent id
2158
or result[5][0] != result[5][1] # name
2159
or result[6][0] != result[6][1] # kind
2160
or result[7][0] != result[7][1] # executable
2162
result = (result[0],
2163
((utf8_decode(result[1][0])[0]),
2164
utf8_decode(result[1][1])[0]),) + result[2:]
2166
advance_path = False
2168
for result in _process_entry(current_entry, current_path_info):
2169
# this check should probably be outside the loop: one
2170
# 'iterate two trees' api, and then _iter_changes filters
2171
# unchanged pairs. - RBC 20070226
2173
if (include_unchanged
2174
or result[2] # content change
2175
or result[3][0] != result[3][1] # versioned status
2176
or result[4][0] != result[4][1] # parent id
2177
or result[5][0] != result[5][1] # name
2178
or result[6][0] != result[6][1] # kind
2179
or result[7][0] != result[7][1] # executable
2181
result = (result[0],
2182
((utf8_decode(result[1][0])[0]),
2183
utf8_decode(result[1][1])[0]),) + result[2:]
2185
if advance_entry and current_entry is not None:
2187
if entry_index < len(current_block[1]):
2188
current_entry = current_block[1][entry_index]
2190
current_entry = None
2192
advance_entry = True # reset the advance flaga
2193
if advance_path and current_path_info is not None:
2194
if not path_handled:
2195
# unversioned in all regards
2196
if want_unversioned:
2197
new_executable = bool(
2198
stat.S_ISREG(current_path_info[3].st_mode)
2199
and stat.S_IEXEC & current_path_info[3].st_mode)
2200
if want_unversioned:
2201
yield (None, (None, current_path_info[0]),
2205
(None, current_path_info[1]),
2206
(None, current_path_info[2]),
2207
(None, new_executable))
2208
# dont descend into this unversioned path if it is
2210
if current_path_info[2] in (
2211
'directory', 'tree-referene'):
2212
del current_dir_info[1][path_index]
2215
if path_index < len(current_dir_info[1]):
2216
current_path_info = current_dir_info[1][path_index]
2217
if current_path_info[2] == 'directory':
2218
if self.target._directory_is_tree_reference(
2219
current_path_info[0].decode('utf8')):
2220
current_path_info = current_path_info[:2] + \
2221
('tree-reference',) + current_path_info[3:]
2223
current_path_info = None
2224
path_handled = False
2226
advance_path = True # reset the advance flagg.
2227
if current_block is not None:
2229
if (block_index < len(state._dirblocks) and
2230
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
2231
current_block = state._dirblocks[block_index]
2233
current_block = None
2234
if current_dir_info is not None:
2236
current_dir_info = dir_iterator.next()
2237
except StopIteration:
2238
current_dir_info = None
2242
def is_compatible(source, target):
2243
# the target must be a dirstate working tree
2244
if not isinstance(target, WorkingTree4):
2246
# the source must be a revtreee or dirstate rev tree.
2247
if not isinstance(source,
2248
(revisiontree.RevisionTree, DirStateRevisionTree)):
2250
# the source revid must be in the target dirstate
2251
if not (source._revision_id == NULL_REVISION or
2252
source._revision_id in target.get_parent_ids()):
2253
# TODO: what about ghosts? it may well need to
2254
# check for them explicitly.
2258
InterTree.register_optimiser(InterDirStateTree)
2261
class Converter3to4(object):
2262
"""Perform an in-place upgrade of format 3 to format 4 trees."""
2265
self.target_format = WorkingTreeFormat4()
2267
def convert(self, tree):
2268
# lock the control files not the tree, so that we dont get tree
2269
# on-unlock behaviours, and so that noone else diddles with the
2270
# tree during upgrade.
2271
tree._control_files.lock_write()
2273
self.create_dirstate_data(tree)
2274
self.update_format(tree)
2275
self.remove_xml_files(tree)
2277
tree._control_files.unlock()
2279
def create_dirstate_data(self, tree):
2280
"""Create the dirstate based data for tree."""
2281
local_path = tree.bzrdir.get_workingtree_transport(None
2282
).local_abspath('dirstate')
2283
state = dirstate.DirState.from_tree(tree, local_path)
2287
def remove_xml_files(self, tree):
2288
"""Remove the oldformat 3 data."""
2289
transport = tree.bzrdir.get_workingtree_transport(None)
2290
for path in ['basis-inventory-cache', 'inventory', 'last-revision',
2291
'pending-merges', 'stat-cache']:
2293
transport.delete(path)
2294
except errors.NoSuchFile:
2295
# some files are optional - just deal.
2298
def update_format(self, tree):
2299
"""Change the format marker."""
2300
tree._control_files.put_utf8('format',
2301
self.target_format.get_format_string())