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
# This is only needed on win32, where this is the only way
350
# we know the executable bit.
351
inv_entry.executable = executable
352
# not strictly needed: working tree
353
#inv_entry.text_size = size
354
#inv_entry.text_sha1 = sha1
355
elif kind == 'directory':
356
# add this entry to the parent map.
357
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
358
elif kind == 'tree-reference':
359
assert self._repo_supports_tree_reference
360
inv_entry.reference_revision = link_or_sha1 or None
362
assert 'unknown kind'
363
# These checks cost us around 40ms on a 55k entry tree
364
assert file_id not in inv_byid, ('file_id %s already in'
365
' inventory as %s' % (file_id, inv_byid[file_id]))
366
assert name_unicode not in parent_ie.children
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)
392
assert entry[0] is not None, 'what error should this raise'
394
path = pathjoin(entry[0][0], entry[0][1]).decode('utf8')
396
file_abspath = self.abspath(path)
397
state = self.current_dirstate()
398
link_or_sha1 = state.update_entry(entry, file_abspath,
399
stat_value=stat_value)
400
if entry[1][0][0] == 'f':
404
def _get_inventory(self):
405
"""Get the inventory for the tree. This is only valid within a lock."""
406
if self._inventory is not None:
407
return self._inventory
408
self._must_be_locked()
409
self._generate_inventory()
410
return self._inventory
412
inventory = property(_get_inventory,
413
doc="Inventory of this Tree")
416
def get_parent_ids(self):
417
"""See Tree.get_parent_ids.
419
This implementation requests the ids list from the dirstate file.
421
return self.current_dirstate().get_parent_ids()
423
def get_reference_revision(self, file_id, path=None):
424
# referenced tree's revision is whatever's currently there
425
return self.get_nested_tree(file_id, path).last_revision()
427
def get_nested_tree(self, file_id, path=None):
429
path = self.id2path(file_id)
430
# else: check file_id is at path?
431
return WorkingTree.open(self.abspath(path))
434
def get_root_id(self):
435
"""Return the id of this trees root"""
436
return self._get_entry(path='')[0][2]
438
def has_id(self, file_id):
439
state = self.current_dirstate()
440
file_id = osutils.safe_file_id(file_id)
441
row, parents = self._get_entry(file_id=file_id)
444
return osutils.lexists(pathjoin(
445
self.basedir, row[0].decode('utf8'), row[1].decode('utf8')))
448
def id2path(self, file_id):
449
file_id = osutils.safe_file_id(file_id)
450
state = self.current_dirstate()
451
entry = self._get_entry(file_id=file_id)
452
if entry == (None, None):
453
raise errors.NoSuchId(tree=self, file_id=file_id)
454
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
455
return path_utf8.decode('utf8')
457
if not osutils.supports_executable():
458
def is_executable(self, file_id, path=None):
459
file_id = osutils.safe_file_id(file_id)
460
entry = self._get_entry(file_id=file_id, path=path)
461
if entry == (None, None):
463
return entry[1][0][3]
465
def is_executable(self, file_id, path=None):
467
file_id = osutils.safe_file_id(file_id)
468
path = self.id2path(file_id)
469
mode = os.lstat(self.abspath(path)).st_mode
470
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
474
"""Iterate through file_ids for this tree.
476
file_ids are in a WorkingTree if they are in the working inventory
477
and the working file exists.
480
for key, tree_details in self.current_dirstate()._iter_entries():
481
if tree_details[0][0] in ('a', 'r'): # absent, relocated
482
# not relevant to the working tree
484
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
485
if osutils.lexists(path):
486
result.append(key[2])
489
def iter_references(self):
490
for key, tree_details in self.current_dirstate()._iter_entries():
491
if tree_details[0][0] in ('a', 'r'): # absent, relocated
492
# not relevant to the working tree
495
# the root is not a reference.
497
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
499
if self._kind(path) == 'tree-reference':
501
except errors.NoSuchFile:
502
# path is missing on disk.
506
def kind(self, file_id):
507
"""Return the kind of a file.
509
This is always the actual kind that's on disk, regardless of what it
512
relpath = self.id2path(file_id)
513
assert relpath != None, \
514
"path for id {%s} is None!" % file_id
515
return self._kind(relpath)
517
def _kind(self, relpath):
518
abspath = self.abspath(relpath)
519
kind = file_kind(abspath)
520
if (self._repo_supports_tree_reference and
521
kind == 'directory' and
522
self._directory_is_tree_reference(relpath)):
523
kind = 'tree-reference'
527
def _last_revision(self):
528
"""See Mutable.last_revision."""
529
parent_ids = self.current_dirstate().get_parent_ids()
536
"""See Branch.lock_read, and WorkingTree.unlock."""
537
self.branch.lock_read()
539
self._control_files.lock_read()
541
state = self.current_dirstate()
542
if not state._lock_token:
544
# set our support for tree references from the repository in
546
self._repo_supports_tree_reference = getattr(
547
self.branch.repository._format, "support_tree_reference",
550
self._control_files.unlock()
556
def _lock_self_write(self):
557
"""This should be called after the branch is locked."""
559
self._control_files.lock_write()
561
state = self.current_dirstate()
562
if not state._lock_token:
564
# set our support for tree references from the repository in
566
self._repo_supports_tree_reference = getattr(
567
self.branch.repository._format, "support_tree_reference",
570
self._control_files.unlock()
576
def lock_tree_write(self):
577
"""See MutableTree.lock_tree_write, and WorkingTree.unlock."""
578
self.branch.lock_read()
579
self._lock_self_write()
581
def lock_write(self):
582
"""See MutableTree.lock_write, and WorkingTree.unlock."""
583
self.branch.lock_write()
584
self._lock_self_write()
586
@needs_tree_write_lock
587
def move(self, from_paths, to_dir, after=False):
588
"""See WorkingTree.move()."""
593
state = self.current_dirstate()
595
assert not isinstance(from_paths, basestring)
596
to_dir_utf8 = to_dir.encode('utf8')
597
to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
598
id_index = state._get_id_index()
599
# check destination directory
600
# get the details for it
601
to_entry_block_index, to_entry_entry_index, dir_present, entry_present = \
602
state._get_block_entry_index(to_entry_dirname, to_basename, 0)
603
if not entry_present:
604
raise errors.BzrMoveFailedError('', to_dir,
605
errors.NotVersionedError(to_dir))
606
to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
607
# get a handle on the block itself.
608
to_block_index = state._ensure_block(
609
to_entry_block_index, to_entry_entry_index, to_dir_utf8)
610
to_block = state._dirblocks[to_block_index]
611
to_abs = self.abspath(to_dir)
612
if not isdir(to_abs):
613
raise errors.BzrMoveFailedError('',to_dir,
614
errors.NotADirectory(to_abs))
616
if to_entry[1][0][0] != 'd':
617
raise errors.BzrMoveFailedError('',to_dir,
618
errors.NotADirectory(to_abs))
620
if self._inventory is not None:
621
update_inventory = True
623
to_dir_ie = inv[to_dir_id]
624
to_dir_id = to_entry[0][2]
626
update_inventory = False
629
def move_one(old_entry, from_path_utf8, minikind, executable,
630
fingerprint, packed_stat, size,
631
to_block, to_key, to_path_utf8):
632
state._make_absent(old_entry)
633
from_key = old_entry[0]
635
lambda:state.update_minimal(from_key,
637
executable=executable,
638
fingerprint=fingerprint,
639
packed_stat=packed_stat,
641
path_utf8=from_path_utf8))
642
state.update_minimal(to_key,
644
executable=executable,
645
fingerprint=fingerprint,
646
packed_stat=packed_stat,
648
path_utf8=to_path_utf8)
649
added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
650
new_entry = to_block[1][added_entry_index]
651
rollbacks.append(lambda:state._make_absent(new_entry))
653
# create rename entries and tuples
654
for from_rel in from_paths:
655
# from_rel is 'pathinroot/foo/bar'
656
from_rel_utf8 = from_rel.encode('utf8')
657
from_dirname, from_tail = osutils.split(from_rel)
658
from_dirname, from_tail_utf8 = osutils.split(from_rel_utf8)
659
from_entry = self._get_entry(path=from_rel)
660
if from_entry == (None, None):
661
raise errors.BzrMoveFailedError(from_rel,to_dir,
662
errors.NotVersionedError(path=str(from_rel)))
664
from_id = from_entry[0][2]
665
to_rel = pathjoin(to_dir, from_tail)
666
to_rel_utf8 = pathjoin(to_dir_utf8, from_tail_utf8)
667
item_to_entry = self._get_entry(path=to_rel)
668
if item_to_entry != (None, None):
669
raise errors.BzrMoveFailedError(from_rel, to_rel,
670
"Target is already versioned.")
672
if from_rel == to_rel:
673
raise errors.BzrMoveFailedError(from_rel, to_rel,
674
"Source and target are identical.")
676
from_missing = not self.has_filename(from_rel)
677
to_missing = not self.has_filename(to_rel)
684
raise errors.BzrMoveFailedError(from_rel, to_rel,
685
errors.NoSuchFile(path=to_rel,
686
extra="New file has not been created yet"))
688
# neither path exists
689
raise errors.BzrRenameFailedError(from_rel, to_rel,
690
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
692
if from_missing: # implicitly just update our path mapping
695
raise errors.RenameFailedFilesExist(from_rel, to_rel,
696
extra="(Use --after to update the Bazaar id)")
699
def rollback_rename():
700
"""A single rename has failed, roll it back."""
701
# roll back everything, even if we encounter trouble doing one
704
# TODO: at least log the other exceptions rather than just
705
# losing them mbp 20070307
707
for rollback in reversed(rollbacks):
711
exc_info = sys.exc_info()
713
raise exc_info[0], exc_info[1], exc_info[2]
715
# perform the disk move first - its the most likely failure point.
717
from_rel_abs = self.abspath(from_rel)
718
to_rel_abs = self.abspath(to_rel)
720
osutils.rename(from_rel_abs, to_rel_abs)
722
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
723
rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
725
# perform the rename in the inventory next if needed: its easy
729
from_entry = inv[from_id]
730
current_parent = from_entry.parent_id
731
inv.rename(from_id, to_dir_id, from_tail)
733
lambda: inv.rename(from_id, current_parent, from_tail))
734
# finally do the rename in the dirstate, which is a little
735
# tricky to rollback, but least likely to need it.
736
old_block_index, old_entry_index, dir_present, file_present = \
737
state._get_block_entry_index(from_dirname, from_tail_utf8, 0)
738
old_block = state._dirblocks[old_block_index][1]
739
old_entry = old_block[old_entry_index]
740
from_key, old_entry_details = old_entry
741
cur_details = old_entry_details[0]
743
to_key = ((to_block[0],) + from_key[1:3])
744
minikind = cur_details[0]
745
move_one(old_entry, from_path_utf8=from_rel_utf8,
747
executable=cur_details[3],
748
fingerprint=cur_details[1],
749
packed_stat=cur_details[4],
753
to_path_utf8=to_rel_utf8)
756
def update_dirblock(from_dir, to_key, to_dir_utf8):
757
"""all entries in this block need updating.
759
TODO: This is pretty ugly, and doesn't support
760
reverting, but it works.
762
assert from_dir != '', "renaming root not supported"
763
from_key = (from_dir, '')
764
from_block_idx, present = \
765
state._find_block_index_from_key(from_key)
767
# This is the old record, if it isn't present, then
768
# there is theoretically nothing to update.
769
# (Unless it isn't present because of lazy loading,
770
# but we don't do that yet)
772
from_block = state._dirblocks[from_block_idx]
773
to_block_index, to_entry_index, _, _ = \
774
state._get_block_entry_index(to_key[0], to_key[1], 0)
775
to_block_index = state._ensure_block(
776
to_block_index, to_entry_index, to_dir_utf8)
777
to_block = state._dirblocks[to_block_index]
778
for entry in from_block[1]:
779
assert entry[0][0] == from_dir
780
cur_details = entry[1][0]
781
to_key = (to_dir_utf8, entry[0][1], entry[0][2])
782
from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
783
to_path_utf8 = osutils.pathjoin(to_dir_utf8, entry[0][1])
784
minikind = cur_details[0]
785
move_one(entry, from_path_utf8=from_path_utf8,
787
executable=cur_details[3],
788
fingerprint=cur_details[1],
789
packed_stat=cur_details[4],
793
to_path_utf8=to_rel_utf8)
795
# We need to move all the children of this
797
update_dirblock(from_path_utf8, to_key,
799
update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
803
result.append((from_rel, to_rel))
804
state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
805
self._make_dirty(reset_inventory=False)
809
def _must_be_locked(self):
810
if not self._control_files._lock_count:
811
raise errors.ObjectNotLocked(self)
814
"""Initialize the state in this tree to be a new tree."""
818
def path2id(self, path):
819
"""Return the id for path in this tree."""
820
path = path.strip('/')
821
entry = self._get_entry(path=path)
822
if entry == (None, None):
826
def paths2ids(self, paths, trees=[], require_versioned=True):
827
"""See Tree.paths2ids().
829
This specialisation fast-paths the case where all the trees are in the
834
parents = self.get_parent_ids()
836
if not (isinstance(tree, DirStateRevisionTree) and tree._revision_id in
838
return super(WorkingTree4, self).paths2ids(paths, trees, require_versioned)
839
search_indexes = [0] + [1 + parents.index(tree._revision_id) for tree in trees]
840
# -- make all paths utf8 --
843
paths_utf8.add(path.encode('utf8'))
845
# -- paths is now a utf8 path set --
846
# -- get the state object and prepare it.
847
state = self.current_dirstate()
848
if False and (state._dirblock_state == dirstate.DirState.NOT_IN_MEMORY
849
and '' not in paths):
850
paths2ids = self._paths2ids_using_bisect
852
paths2ids = self._paths2ids_in_memory
853
return paths2ids(paths, search_indexes,
854
require_versioned=require_versioned)
856
def _paths2ids_in_memory(self, paths, search_indexes,
857
require_versioned=True):
858
state = self.current_dirstate()
859
state._read_dirblocks_if_needed()
860
def _entries_for_path(path):
861
"""Return a list with all the entries that match path for all ids.
863
dirname, basename = os.path.split(path)
864
key = (dirname, basename, '')
865
block_index, present = state._find_block_index_from_key(key)
867
# the block which should contain path is absent.
870
block = state._dirblocks[block_index][1]
871
entry_index, _ = state._find_entry_index(key, block)
872
# we may need to look at multiple entries at this path: walk while the paths match.
873
while (entry_index < len(block) and
874
block[entry_index][0][0:2] == key[0:2]):
875
result.append(block[entry_index])
878
if require_versioned:
879
# -- check all supplied paths are versioned in a search tree. --
882
path_entries = _entries_for_path(path)
884
# this specified path is not present at all: error
885
all_versioned = False
887
found_versioned = False
888
# for each id at this path
889
for entry in path_entries:
891
for index in search_indexes:
892
if entry[1][index][0] != 'a': # absent
893
found_versioned = True
894
# all good: found a versioned cell
896
if not found_versioned:
897
# none of the indexes was not 'absent' at all ids for this
899
all_versioned = False
901
if not all_versioned:
902
raise errors.PathsNotVersionedError(paths)
903
# -- remove redundancy in supplied paths to prevent over-scanning --
906
other_paths = paths.difference(set([path]))
907
if not osutils.is_inside_any(other_paths, path):
908
# this is a top level path, we must check it.
909
search_paths.add(path)
911
# for all search_indexs in each path at or under each element of
912
# search_paths, if the detail is relocated: add the id, and add the
913
# relocated path as one to search if its not searched already. If the
914
# detail is not relocated, add the id.
915
searched_paths = set()
917
def _process_entry(entry):
918
"""Look at search_indexes within entry.
920
If a specific tree's details are relocated, add the relocation
921
target to search_paths if not searched already. If it is absent, do
922
nothing. Otherwise add the id to found_ids.
924
for index in search_indexes:
925
if entry[1][index][0] == 'r': # relocated
926
if not osutils.is_inside_any(searched_paths, entry[1][index][1]):
927
search_paths.add(entry[1][index][1])
928
elif entry[1][index][0] != 'a': # absent
929
found_ids.add(entry[0][2])
931
current_root = search_paths.pop()
932
searched_paths.add(current_root)
933
# process the entries for this containing directory: the rest will be
934
# found by their parents recursively.
935
root_entries = _entries_for_path(current_root)
937
# this specified path is not present at all, skip it.
939
for entry in root_entries:
940
_process_entry(entry)
941
initial_key = (current_root, '', '')
942
block_index, _ = state._find_block_index_from_key(initial_key)
943
while (block_index < len(state._dirblocks) and
944
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
945
for entry in state._dirblocks[block_index][1]:
946
_process_entry(entry)
950
def _paths2ids_using_bisect(self, paths, search_indexes,
951
require_versioned=True):
952
state = self.current_dirstate()
955
split_paths = sorted(osutils.split(p) for p in paths)
956
found = state._bisect_recursive(split_paths)
958
if require_versioned:
959
found_dir_names = set(dir_name_id[:2] for dir_name_id in found)
960
for dir_name in split_paths:
961
if dir_name not in found_dir_names:
962
raise errors.PathsNotVersionedError(paths)
964
for dir_name_id, trees_info in found.iteritems():
965
for index in search_indexes:
966
if trees_info[index][0] not in ('r', 'a'):
967
found_ids.add(dir_name_id[2])
970
def read_working_inventory(self):
971
"""Read the working inventory.
973
This is a meaningless operation for dirstate, but we obey it anyhow.
975
return self.inventory
978
def revision_tree(self, revision_id):
979
"""See Tree.revision_tree.
981
WorkingTree4 supplies revision_trees for any basis tree.
983
revision_id = osutils.safe_revision_id(revision_id)
984
dirstate = self.current_dirstate()
985
parent_ids = dirstate.get_parent_ids()
986
if revision_id not in parent_ids:
987
raise errors.NoSuchRevisionInTree(self, revision_id)
988
if revision_id in dirstate.get_ghosts():
989
raise errors.NoSuchRevisionInTree(self, revision_id)
990
return DirStateRevisionTree(dirstate, revision_id,
991
self.branch.repository)
993
@needs_tree_write_lock
994
def set_last_revision(self, new_revision):
995
"""Change the last revision in the working tree."""
996
new_revision = osutils.safe_revision_id(new_revision)
997
parents = self.get_parent_ids()
998
if new_revision in (NULL_REVISION, None):
999
assert len(parents) < 2, (
1000
"setting the last parent to none with a pending merge is "
1002
self.set_parent_ids([])
1004
self.set_parent_ids([new_revision] + parents[1:],
1005
allow_leftmost_as_ghost=True)
1007
@needs_tree_write_lock
1008
def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
1009
"""Set the parent ids to revision_ids.
1011
See also set_parent_trees. This api will try to retrieve the tree data
1012
for each element of revision_ids from the trees repository. If you have
1013
tree data already available, it is more efficient to use
1014
set_parent_trees rather than set_parent_ids. set_parent_ids is however
1015
an easier API to use.
1017
:param revision_ids: The revision_ids to set as the parent ids of this
1018
working tree. Any of these may be ghosts.
1020
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1022
for revision_id in revision_ids:
1024
revtree = self.branch.repository.revision_tree(revision_id)
1025
# TODO: jam 20070213 KnitVersionedFile raises
1026
# RevisionNotPresent rather than NoSuchRevision if a
1027
# given revision_id is not present. Should Repository be
1028
# catching it and re-raising NoSuchRevision?
1029
except (errors.NoSuchRevision, errors.RevisionNotPresent):
1031
trees.append((revision_id, revtree))
1032
self.current_dirstate()._validate()
1033
self.set_parent_trees(trees,
1034
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
1035
self.current_dirstate()._validate()
1037
@needs_tree_write_lock
1038
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
1039
"""Set the parents of the working tree.
1041
:param parents_list: A list of (revision_id, tree) tuples.
1042
If tree is None, then that element is treated as an unreachable
1043
parent tree - i.e. a ghost.
1045
dirstate = self.current_dirstate()
1046
dirstate._validate()
1047
if len(parents_list) > 0:
1048
if not allow_leftmost_as_ghost and parents_list[0][1] is None:
1049
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1052
# convert absent trees to the null tree, which we convert back to
1053
# missing on access.
1054
for rev_id, tree in parents_list:
1055
rev_id = osutils.safe_revision_id(rev_id)
1056
if tree is not None:
1057
real_trees.append((rev_id, tree))
1059
real_trees.append((rev_id,
1060
self.branch.repository.revision_tree(None)))
1061
ghosts.append(rev_id)
1062
dirstate._validate()
1063
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1064
dirstate._validate()
1065
self._make_dirty(reset_inventory=False)
1066
dirstate._validate()
1068
def _set_root_id(self, file_id):
1069
"""See WorkingTree.set_root_id."""
1070
state = self.current_dirstate()
1071
state.set_path_id('', file_id)
1072
if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1073
self._make_dirty(reset_inventory=True)
1076
def supports_tree_reference(self):
1077
return self._repo_supports_tree_reference
1080
"""Unlock in format 4 trees needs to write the entire dirstate."""
1081
if self._control_files._lock_count == 1:
1082
# eventually we should do signature checking during read locks for
1084
if self._control_files._lock_mode == 'w':
1087
if self._dirstate is not None:
1088
# This is a no-op if there are no modifications.
1089
self._dirstate.save()
1090
self._dirstate.unlock()
1091
# TODO: jam 20070301 We shouldn't have to wipe the dirstate at this
1092
# point. Instead, it could check if the header has been
1093
# modified when it is locked, and if not, it can hang on to
1094
# the data it has in memory.
1095
self._dirstate = None
1096
self._inventory = None
1097
# reverse order of locking.
1099
return self._control_files.unlock()
1101
self.branch.unlock()
1103
@needs_tree_write_lock
1104
def unversion(self, file_ids):
1105
"""Remove the file ids in file_ids from the current versioned set.
1107
When a file_id is unversioned, all of its children are automatically
1110
:param file_ids: The file ids to stop versioning.
1111
:raises: NoSuchId if any fileid is not currently versioned.
1115
state = self.current_dirstate()
1116
state._read_dirblocks_if_needed()
1117
ids_to_unversion = set()
1118
for file_id in file_ids:
1119
ids_to_unversion.add(osutils.safe_file_id(file_id))
1120
paths_to_unversion = set()
1122
# check if the root is to be unversioned, if so, assert for now.
1123
# walk the state marking unversioned things as absent.
1124
# if there are any un-unversioned ids at the end, raise
1125
for key, details in state._dirblocks[0][1]:
1126
if (details[0][0] not in ('a', 'r') and # absent or relocated
1127
key[2] in ids_to_unversion):
1128
# I haven't written the code to unversion / yet - it should be
1130
raise errors.BzrError('Unversioning the / is not currently supported')
1132
while block_index < len(state._dirblocks):
1133
# process one directory at a time.
1134
block = state._dirblocks[block_index]
1135
# first check: is the path one to remove - it or its children
1136
delete_block = False
1137
for path in paths_to_unversion:
1138
if (block[0].startswith(path) and
1139
(len(block[0]) == len(path) or
1140
block[0][len(path)] == '/')):
1141
# this entire block should be deleted - its the block for a
1142
# path to unversion; or the child of one
1145
# TODO: trim paths_to_unversion as we pass by paths
1147
# this block is to be deleted: process it.
1148
# TODO: we can special case the no-parents case and
1149
# just forget the whole block.
1151
while entry_index < len(block[1]):
1152
# Mark this file id as having been removed
1153
ids_to_unversion.discard(block[1][entry_index][0][2])
1154
if not state._make_absent(block[1][entry_index]):
1156
# go to the next block. (At the moment we dont delete empty
1161
while entry_index < len(block[1]):
1162
entry = block[1][entry_index]
1163
if (entry[1][0][0] in ('a', 'r') or # absent, relocated
1164
# ^ some parent row.
1165
entry[0][2] not in ids_to_unversion):
1166
# ^ not an id to unversion
1169
if entry[1][0][0] == 'd':
1170
paths_to_unversion.add(pathjoin(entry[0][0], entry[0][1]))
1171
if not state._make_absent(entry):
1173
# we have unversioned this id
1174
ids_to_unversion.remove(entry[0][2])
1176
if ids_to_unversion:
1177
raise errors.NoSuchId(self, iter(ids_to_unversion).next())
1178
self._make_dirty(reset_inventory=False)
1179
# have to change the legacy inventory too.
1180
if self._inventory is not None:
1181
for file_id in file_ids:
1182
self._inventory.remove_recursive_id(file_id)
1184
@needs_tree_write_lock
1185
def _write_inventory(self, inv):
1186
"""Write inventory as the current inventory."""
1187
assert not self._dirty, "attempting to write an inventory when the dirstate is dirty will cause data loss"
1188
self.current_dirstate().set_state_from_inventory(inv)
1189
self._make_dirty(reset_inventory=False)
1190
if self._inventory is not None:
1191
self._inventory = inv
1195
class WorkingTreeFormat4(WorkingTreeFormat3):
1196
"""The first consolidated dirstate working tree format.
1199
- exists within a metadir controlling .bzr
1200
- includes an explicit version marker for the workingtree control
1201
files, separate from the BzrDir format
1202
- modifies the hash cache format
1203
- is new in bzr 0.15
1204
- uses a LockDir to guard access to it.
1207
def get_format_string(self):
1208
"""See WorkingTreeFormat.get_format_string()."""
1209
return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1211
def get_format_description(self):
1212
"""See WorkingTreeFormat.get_format_description()."""
1213
return "Working tree format 4"
1215
def initialize(self, a_bzrdir, revision_id=None):
1216
"""See WorkingTreeFormat.initialize().
1218
:param revision_id: allows creating a working tree at a different
1219
revision than the branch is at.
1221
These trees get an initial random root id.
1223
revision_id = osutils.safe_revision_id(revision_id)
1224
if not isinstance(a_bzrdir.transport, LocalTransport):
1225
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1226
transport = a_bzrdir.get_workingtree_transport(self)
1227
control_files = self._open_control_files(a_bzrdir)
1228
control_files.create_lock()
1229
control_files.lock_write()
1230
control_files.put_utf8('format', self.get_format_string())
1231
branch = a_bzrdir.open_branch()
1232
if revision_id is None:
1233
revision_id = branch.last_revision()
1234
local_path = transport.local_abspath('dirstate')
1235
# write out new dirstate (must exist when we create the tree)
1236
state = dirstate.DirState.initialize(local_path)
1238
wt = WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1242
_control_files=control_files)
1244
wt.lock_tree_write()
1247
if revision_id in (None, NULL_REVISION):
1248
wt._set_root_id(generate_ids.gen_root_id())
1250
wt.current_dirstate()._validate()
1251
wt.set_last_revision(revision_id)
1253
basis = wt.basis_tree()
1255
# if the basis has a root id we have to use that; otherwise we use
1257
basis_root_id = basis.get_root_id()
1258
if basis_root_id is not None:
1259
wt._set_root_id(basis_root_id)
1261
transform.build_tree(basis, wt)
1264
control_files.unlock()
1268
def _open(self, a_bzrdir, control_files):
1269
"""Open the tree itself.
1271
:param a_bzrdir: the dir for the tree.
1272
:param control_files: the control files for the tree.
1274
return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1275
branch=a_bzrdir.open_branch(),
1278
_control_files=control_files)
1280
def __get_matchingbzrdir(self):
1281
# please test against something that will let us do tree references
1282
return bzrdir.format_registry.make_bzrdir(
1283
'dirstate-with-subtree')
1285
_matchingbzrdir = property(__get_matchingbzrdir)
1288
class DirStateRevisionTree(Tree):
1289
"""A revision tree pulling the inventory from a dirstate."""
1291
def __init__(self, dirstate, revision_id, repository):
1292
self._dirstate = dirstate
1293
self._revision_id = osutils.safe_revision_id(revision_id)
1294
self._repository = repository
1295
self._inventory = None
1297
self._dirstate_locked = False
1300
return "<%s of %s in %s>" % \
1301
(self.__class__.__name__, self._revision_id, self._dirstate)
1303
def annotate_iter(self, file_id):
1304
"""See Tree.annotate_iter"""
1305
w = self._repository.weave_store.get_weave(file_id,
1306
self._repository.get_transaction())
1307
return w.annotate_iter(self.inventory[file_id].revision)
1309
def _comparison_data(self, entry, path):
1310
"""See Tree._comparison_data."""
1312
return None, False, None
1313
# trust the entry as RevisionTree does, but this may not be
1314
# sensible: the entry might not have come from us?
1315
return entry.kind, entry.executable, None
1317
def _file_size(self, entry, stat_value):
1318
return entry.text_size
1320
def filter_unversioned_files(self, paths):
1321
"""Filter out paths that are not versioned.
1323
:return: set of paths.
1325
pred = self.has_filename
1326
return set((p for p in paths if not pred(p)))
1328
def get_root_id(self):
1329
return self.path2id('')
1331
def _get_parent_index(self):
1332
"""Return the index in the dirstate referenced by this tree."""
1333
return self._dirstate.get_parent_ids().index(self._revision_id) + 1
1335
def _get_entry(self, file_id=None, path=None):
1336
"""Get the dirstate row for file_id or path.
1338
If either file_id or path is supplied, it is used as the key to lookup.
1339
If both are supplied, the fastest lookup is used, and an error is
1340
raised if they do not both point at the same row.
1342
:param file_id: An optional unicode file_id to be looked up.
1343
:param path: An optional unicode path to be looked up.
1344
:return: The dirstate row tuple for path/file_id, or (None, None)
1346
if file_id is None and path is None:
1347
raise errors.BzrError('must supply file_id or path')
1348
file_id = osutils.safe_file_id(file_id)
1349
if path is not None:
1350
path = path.encode('utf8')
1351
parent_index = self._get_parent_index()
1352
return self._dirstate._get_entry(parent_index, fileid_utf8=file_id, path_utf8=path)
1354
def _generate_inventory(self):
1355
"""Create and set self.inventory from the dirstate object.
1357
(So this is only called the first time the inventory is requested for
1358
this tree; it then remains in memory until it's out of date.)
1360
This is relatively expensive: we have to walk the entire dirstate.
1362
assert self._locked, 'cannot generate inventory of an unlocked '\
1363
'dirstate revision tree'
1364
# separate call for profiling - makes it clear where the costs are.
1365
self._dirstate._read_dirblocks_if_needed()
1366
assert self._revision_id in self._dirstate.get_parent_ids(), \
1367
'parent %s has disappeared from %s' % (
1368
self._revision_id, self._dirstate.get_parent_ids())
1369
parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
1370
# This is identical now to the WorkingTree _generate_inventory except
1371
# for the tree index use.
1372
root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1373
current_id = root_key[2]
1374
assert current_entry[parent_index][0] == 'd'
1375
inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1376
inv.root.revision = current_entry[parent_index][4]
1377
# Turn some things into local variables
1378
minikind_to_kind = dirstate.DirState._minikind_to_kind
1379
factory = entry_factory
1380
utf8_decode = cache_utf8._utf8_decode
1381
inv_byid = inv._byid
1382
# we could do this straight out of the dirstate; it might be fast
1383
# and should be profiled - RBC 20070216
1384
parent_ies = {'' : inv.root}
1385
for block in self._dirstate._dirblocks[1:]: #skip root
1388
parent_ie = parent_ies[dirname]
1390
# all the paths in this block are not versioned in this tree
1392
for key, entry in block[1]:
1393
minikind, fingerprint, size, executable, revid = entry[parent_index]
1394
if minikind in ('a', 'r'): # absent, relocated
1398
name_unicode = utf8_decode(name)[0]
1400
kind = minikind_to_kind[minikind]
1401
inv_entry = factory[kind](file_id, name_unicode,
1403
inv_entry.revision = revid
1405
inv_entry.executable = executable
1406
inv_entry.text_size = size
1407
inv_entry.text_sha1 = fingerprint
1408
elif kind == 'directory':
1409
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1410
elif kind == 'symlink':
1411
inv_entry.executable = False
1412
inv_entry.text_size = size
1413
inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1414
elif kind == 'tree-reference':
1415
inv_entry.reference_revision = fingerprint or None
1417
raise AssertionError("cannot convert entry %r into an InventoryEntry"
1419
# These checks cost us around 40ms on a 55k entry tree
1420
assert file_id not in inv_byid
1421
assert name_unicode not in parent_ie.children
1422
inv_byid[file_id] = inv_entry
1423
parent_ie.children[name_unicode] = inv_entry
1424
self._inventory = inv
1426
def get_file_mtime(self, file_id, path=None):
1427
"""Return the modification time for this record.
1429
We return the timestamp of the last-changed revision.
1431
# Make sure the file exists
1432
entry = self._get_entry(file_id, path=path)
1433
if entry == (None, None): # do we raise?
1435
parent_index = self._get_parent_index()
1436
last_changed_revision = entry[1][parent_index][4]
1437
return self._repository.get_revision(last_changed_revision).timestamp
1439
def get_file_sha1(self, file_id, path=None, stat_value=None):
1440
entry = self._get_entry(file_id=file_id, path=path)
1441
parent_index = self._get_parent_index()
1442
parent_details = entry[1][parent_index]
1443
if parent_details[0] == 'f':
1444
return parent_details[1]
1447
def get_file(self, file_id):
1448
return StringIO(self.get_file_text(file_id))
1450
def get_file_lines(self, file_id):
1451
ie = self.inventory[file_id]
1452
return self._repository.weave_store.get_weave(file_id,
1453
self._repository.get_transaction()).get_lines(ie.revision)
1455
def get_file_size(self, file_id):
1456
return self.inventory[file_id].text_size
1458
def get_file_text(self, file_id):
1459
return ''.join(self.get_file_lines(file_id))
1461
def get_reference_revision(self, file_id, path=None):
1462
return self.inventory[file_id].reference_revision
1464
def get_symlink_target(self, file_id):
1465
entry = self._get_entry(file_id=file_id)
1466
parent_index = self._get_parent_index()
1467
if entry[1][parent_index][0] != 'l':
1470
# At present, none of the tree implementations supports non-ascii
1471
# symlink targets. So we will just assume that the dirstate path is
1473
return entry[1][parent_index][1]
1475
def get_revision_id(self):
1476
"""Return the revision id for this tree."""
1477
return self._revision_id
1479
def _get_inventory(self):
1480
if self._inventory is not None:
1481
return self._inventory
1482
self._must_be_locked()
1483
self._generate_inventory()
1484
return self._inventory
1486
inventory = property(_get_inventory,
1487
doc="Inventory of this Tree")
1489
def get_parent_ids(self):
1490
"""The parents of a tree in the dirstate are not cached."""
1491
return self._repository.get_revision(self._revision_id).parent_ids
1493
def has_filename(self, filename):
1494
return bool(self.path2id(filename))
1496
def kind(self, file_id):
1497
return self.inventory[file_id].kind
1499
def is_executable(self, file_id, path=None):
1500
ie = self.inventory[file_id]
1501
if ie.kind != "file":
1503
return ie.executable
1505
def list_files(self, include_root=False):
1506
# We use a standard implementation, because DirStateRevisionTree is
1507
# dealing with one of the parents of the current state
1508
inv = self._get_inventory()
1509
entries = inv.iter_entries()
1510
if self.inventory.root is not None and not include_root:
1512
for path, entry in entries:
1513
yield path, 'V', entry.kind, entry.file_id, entry
1515
def lock_read(self):
1516
"""Lock the tree for a set of operations."""
1517
if not self._locked:
1518
self._repository.lock_read()
1519
if self._dirstate._lock_token is None:
1520
self._dirstate.lock_read()
1521
self._dirstate_locked = True
1524
def _must_be_locked(self):
1525
if not self._locked:
1526
raise errors.ObjectNotLocked(self)
1529
def path2id(self, path):
1530
"""Return the id for path in this tree."""
1531
# lookup by path: faster than splitting and walking the ivnentory.
1532
entry = self._get_entry(path=path)
1533
if entry == (None, None):
1538
"""Unlock, freeing any cache memory used during the lock."""
1539
# outside of a lock, the inventory is suspect: release it.
1541
if not self._locked:
1542
self._inventory = None
1544
if self._dirstate_locked:
1545
self._dirstate.unlock()
1546
self._dirstate_locked = False
1547
self._repository.unlock()
1549
def walkdirs(self, prefix=""):
1550
# TODO: jam 20070215 This is the lazy way by using the RevisionTree
1551
# implementation based on an inventory.
1552
# This should be cleaned up to use the much faster Dirstate code
1553
# So for now, we just build up the parent inventory, and extract
1554
# it the same way RevisionTree does.
1555
_directory = 'directory'
1556
inv = self._get_inventory()
1557
top_id = inv.path2id(prefix)
1561
pending = [(prefix, top_id)]
1564
relpath, file_id = pending.pop()
1565
# 0 - relpath, 1- file-id
1567
relroot = relpath + '/'
1570
# FIXME: stash the node in pending
1571
entry = inv[file_id]
1572
for name, child in entry.sorted_children():
1573
toppath = relroot + name
1574
dirblock.append((toppath, name, child.kind, None,
1575
child.file_id, child.kind
1577
yield (relpath, entry.file_id), dirblock
1578
# push the user specified dirs from dirblock
1579
for dir in reversed(dirblock):
1580
if dir[2] == _directory:
1581
pending.append((dir[0], dir[4]))
1584
class InterDirStateTree(InterTree):
1585
"""Fast path optimiser for changes_from with dirstate trees.
1587
This is used only when both trees are in the dirstate working file, and
1588
the source is any parent within the dirstate, and the destination is
1589
the current working tree of the same dirstate.
1591
# this could be generalized to allow comparisons between any trees in the
1592
# dirstate, and possibly between trees stored in different dirstates.
1594
def __init__(self, source, target):
1595
super(InterDirStateTree, self).__init__(source, target)
1596
if not InterDirStateTree.is_compatible(source, target):
1597
raise Exception, "invalid source %r and target %r" % (source, target)
1600
def make_source_parent_tree(source, target):
1601
"""Change the source tree into a parent of the target."""
1602
revid = source.commit('record tree')
1603
target.branch.repository.fetch(source.branch.repository, revid)
1604
target.set_parent_ids([revid])
1605
return target.basis_tree(), target
1607
_matching_from_tree_format = WorkingTreeFormat4()
1608
_matching_to_tree_format = WorkingTreeFormat4()
1609
_test_mutable_trees_to_test_trees = make_source_parent_tree
1611
def _iter_changes(self, include_unchanged=False,
1612
specific_files=None, pb=None, extra_trees=[],
1613
require_versioned=True, want_unversioned=False):
1614
"""Return the changes from source to target.
1616
:return: An iterator that yields tuples. See InterTree._iter_changes
1618
:param specific_files: An optional list of file paths to restrict the
1619
comparison to. When mapping filenames to ids, all matches in all
1620
trees (including optional extra_trees) are used, and all children of
1621
matched directories are included.
1622
:param include_unchanged: An optional boolean requesting the inclusion of
1623
unchanged entries in the result.
1624
:param extra_trees: An optional list of additional trees to use when
1625
mapping the contents of specific_files (paths) to file_ids.
1626
:param require_versioned: If True, all files in specific_files must be
1627
versioned in one of source, target, extra_trees or
1628
PathsNotVersionedError is raised.
1629
:param want_unversioned: Should unversioned files be returned in the
1630
output. An unversioned file is defined as one with (False, False)
1631
for the versioned pair.
1633
utf8_decode = cache_utf8._utf8_decode_with_None
1634
_minikind_to_kind = dirstate.DirState._minikind_to_kind
1635
# NB: show_status depends on being able to pass in non-versioned files
1636
# and report them as unknown
1637
# TODO: handle extra trees in the dirstate.
1638
# TODO: handle comparisons as an empty tree as a different special
1639
# case? mbp 20070226
1640
if extra_trees or (self.source._revision_id == NULL_REVISION):
1641
# we can't fast-path these cases (yet)
1642
for f in super(InterDirStateTree, self)._iter_changes(
1643
include_unchanged, specific_files, pb, extra_trees,
1644
require_versioned, want_unversioned=want_unversioned):
1647
parent_ids = self.target.get_parent_ids()
1648
assert (self.source._revision_id in parent_ids), \
1649
"revision {%s} is not stored in {%s}, but %s " \
1650
"can only be used for trees stored in the dirstate" \
1651
% (self.source._revision_id, self.target, self._iter_changes)
1653
if self.source._revision_id == NULL_REVISION:
1655
indices = (target_index,)
1657
assert (self.source._revision_id in parent_ids), \
1658
"Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
1659
self.source._revision_id, parent_ids)
1660
source_index = 1 + parent_ids.index(self.source._revision_id)
1661
indices = (source_index,target_index)
1662
# -- make all specific_files utf8 --
1664
specific_files_utf8 = set()
1665
for path in specific_files:
1666
specific_files_utf8.add(path.encode('utf8'))
1667
specific_files = specific_files_utf8
1669
specific_files = set([''])
1670
# -- specific_files is now a utf8 path set --
1671
# -- get the state object and prepare it.
1672
state = self.target.current_dirstate()
1673
state._read_dirblocks_if_needed()
1674
def _entries_for_path(path):
1675
"""Return a list with all the entries that match path for all ids.
1677
dirname, basename = os.path.split(path)
1678
key = (dirname, basename, '')
1679
block_index, present = state._find_block_index_from_key(key)
1681
# the block which should contain path is absent.
1684
block = state._dirblocks[block_index][1]
1685
entry_index, _ = state._find_entry_index(key, block)
1686
# we may need to look at multiple entries at this path: walk while the specific_files match.
1687
while (entry_index < len(block) and
1688
block[entry_index][0][0:2] == key[0:2]):
1689
result.append(block[entry_index])
1692
if require_versioned:
1693
# -- check all supplied paths are versioned in a search tree. --
1694
all_versioned = True
1695
for path in specific_files:
1696
path_entries = _entries_for_path(path)
1697
if not path_entries:
1698
# this specified path is not present at all: error
1699
all_versioned = False
1701
found_versioned = False
1702
# for each id at this path
1703
for entry in path_entries:
1705
for index in indices:
1706
if entry[1][index][0] != 'a': # absent
1707
found_versioned = True
1708
# all good: found a versioned cell
1710
if not found_versioned:
1711
# none of the indexes was not 'absent' at all ids for this
1713
all_versioned = False
1715
if not all_versioned:
1716
raise errors.PathsNotVersionedError(specific_files)
1717
# -- remove redundancy in supplied specific_files to prevent over-scanning --
1718
search_specific_files = set()
1719
for path in specific_files:
1720
other_specific_files = specific_files.difference(set([path]))
1721
if not osutils.is_inside_any(other_specific_files, path):
1722
# this is a top level path, we must check it.
1723
search_specific_files.add(path)
1725
# compare source_index and target_index at or under each element of search_specific_files.
1726
# follow the following comparison table. Note that we only want to do diff operations when
1727
# the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
1731
# Source | Target | disk | action
1732
# r | fdlt | | add source to search, add id path move and perform
1733
# | | | diff check on source-target
1734
# r | fdlt | a | dangling file that was present in the basis.
1736
# r | a | | add source to search
1738
# r | r | | this path is present in a non-examined tree, skip.
1739
# r | r | a | this path is present in a non-examined tree, skip.
1740
# a | fdlt | | add new id
1741
# a | fdlt | a | dangling locally added file, skip
1742
# a | a | | not present in either tree, skip
1743
# a | a | a | not present in any tree, skip
1744
# a | r | | not present in either tree at this path, skip as it
1745
# | | | may not be selected by the users list of paths.
1746
# a | r | a | not present in either tree at this path, skip as it
1747
# | | | may not be selected by the users list of paths.
1748
# fdlt | fdlt | | content in both: diff them
1749
# fdlt | fdlt | a | deleted locally, but not unversioned - show as deleted ?
1750
# fdlt | a | | unversioned: output deleted id for now
1751
# fdlt | a | a | unversioned and deleted: output deleted id
1752
# fdlt | r | | relocated in this tree, so add target to search.
1753
# | | | Dont diff, we will see an r,fd; pair when we reach
1754
# | | | this id at the other path.
1755
# fdlt | r | a | relocated in this tree, so add target to search.
1756
# | | | Dont diff, we will see an r,fd; pair when we reach
1757
# | | | this id at the other path.
1759
# for all search_indexs in each path at or under each element of
1760
# search_specific_files, if the detail is relocated: add the id, and add the
1761
# relocated path as one to search if its not searched already. If the
1762
# detail is not relocated, add the id.
1763
searched_specific_files = set()
1764
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1765
# Using a list so that we can access the values and change them in
1766
# nested scope. Each one is [path, file_id, entry]
1767
last_source_parent = [None, None, None]
1768
last_target_parent = [None, None, None]
1770
use_filesystem_for_exec = (sys.platform != 'win32')
1772
def _process_entry(entry, path_info):
1773
"""Compare an entry and real disk to generate delta information.
1775
:param path_info: top_relpath, basename, kind, lstat, abspath for
1776
the path of entry. If None, then the path is considered absent.
1777
(Perhaps we should pass in a concrete entry for this ?)
1778
Basename is returned as a utf8 string because we expect this
1779
tuple will be ignored, and don't want to take the time to
1782
if source_index is None:
1783
source_details = NULL_PARENT_DETAILS
1785
source_details = entry[1][source_index]
1786
target_details = entry[1][target_index]
1787
target_minikind = target_details[0]
1788
if path_info is not None and target_minikind in 'fdlt':
1789
assert target_index == 0
1790
link_or_sha1 = state.update_entry(entry, abspath=path_info[4],
1791
stat_value=path_info[3])
1792
# The entry may have been modified by update_entry
1793
target_details = entry[1][target_index]
1794
target_minikind = target_details[0]
1797
source_minikind = source_details[0]
1798
if source_minikind in 'fdltr' and target_minikind in 'fdlt':
1799
# claimed content in both: diff
1800
# r | fdlt | | add source to search, add id path move and perform
1801
# | | | diff check on source-target
1802
# r | fdlt | a | dangling file that was present in the basis.
1804
if source_minikind in 'r':
1805
# add the source to the search path to find any children it
1806
# has. TODO ? : only add if it is a container ?
1807
if not osutils.is_inside_any(searched_specific_files,
1809
search_specific_files.add(source_details[1])
1810
# generate the old path; this is needed for stating later
1812
old_path = source_details[1]
1813
old_dirname, old_basename = os.path.split(old_path)
1814
path = pathjoin(entry[0][0], entry[0][1])
1815
old_entry = state._get_entry(source_index,
1817
# update the source details variable to be the real
1819
source_details = old_entry[1][source_index]
1820
source_minikind = source_details[0]
1822
old_dirname = entry[0][0]
1823
old_basename = entry[0][1]
1824
old_path = path = pathjoin(old_dirname, old_basename)
1825
if path_info is None:
1826
# the file is missing on disk, show as removed.
1827
content_change = True
1831
# source and target are both versioned and disk file is present.
1832
target_kind = path_info[2]
1833
if target_kind == 'directory':
1834
if source_minikind != 'd':
1835
content_change = True
1837
# directories have no fingerprint
1838
content_change = False
1840
elif target_kind == 'file':
1841
if source_minikind != 'f':
1842
content_change = True
1844
# We could check the size, but we already have the
1846
content_change = (link_or_sha1 != source_details[1])
1847
# Target details is updated at update_entry time
1848
if use_filesystem_for_exec:
1849
# We don't need S_ISREG here, because we are sure
1850
# we are dealing with a file.
1851
target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
1853
target_exec = target_details[3]
1854
elif target_kind == 'symlink':
1855
if source_minikind != 'l':
1856
content_change = True
1858
content_change = (link_or_sha1 != source_details[1])
1860
elif target_kind == 'tree-reference':
1861
if source_minikind != 't':
1862
content_change = True
1864
content_change = False
1867
raise Exception, "unknown kind %s" % path_info[2]
1868
# parent id is the entry for the path in the target tree
1869
if old_dirname == last_source_parent[0]:
1870
source_parent_id = last_source_parent[1]
1872
source_parent_entry = state._get_entry(source_index,
1873
path_utf8=old_dirname)
1874
source_parent_id = source_parent_entry[0][2]
1875
if source_parent_id == entry[0][2]:
1876
# This is the root, so the parent is None
1877
source_parent_id = None
1879
last_source_parent[0] = old_dirname
1880
last_source_parent[1] = source_parent_id
1881
last_source_parent[2] = source_parent_entry
1883
new_dirname = entry[0][0]
1884
if new_dirname == last_target_parent[0]:
1885
target_parent_id = last_target_parent[1]
1887
# TODO: We don't always need to do the lookup, because the
1888
# parent entry will be the same as the source entry.
1889
target_parent_entry = state._get_entry(target_index,
1890
path_utf8=new_dirname)
1891
target_parent_id = target_parent_entry[0][2]
1892
if target_parent_id == entry[0][2]:
1893
# This is the root, so the parent is None
1894
target_parent_id = None
1896
last_target_parent[0] = new_dirname
1897
last_target_parent[1] = target_parent_id
1898
last_target_parent[2] = target_parent_entry
1900
source_exec = source_details[3]
1901
return ((entry[0][2], (old_path, path), content_change,
1903
(source_parent_id, target_parent_id),
1904
(old_basename, entry[0][1]),
1905
(_minikind_to_kind[source_minikind], target_kind),
1906
(source_exec, target_exec)),)
1907
elif source_minikind in 'a' and target_minikind in 'fdlt':
1908
# looks like a new file
1909
if path_info is not None:
1910
path = pathjoin(entry[0][0], entry[0][1])
1911
# parent id is the entry for the path in the target tree
1912
# TODO: these are the same for an entire directory: cache em.
1913
parent_id = state._get_entry(target_index,
1914
path_utf8=entry[0][0])[0][2]
1915
if parent_id == entry[0][2]:
1917
if use_filesystem_for_exec:
1918
# We need S_ISREG here, because we aren't sure if this
1921
stat.S_ISREG(path_info[3].st_mode)
1922
and stat.S_IEXEC & path_info[3].st_mode)
1924
target_exec = target_details[3]
1925
return ((entry[0][2], (None, path), True,
1928
(None, entry[0][1]),
1929
(None, path_info[2]),
1930
(None, target_exec)),)
1932
# but its not on disk: we deliberately treat this as just
1933
# never-present. (Why ?! - RBC 20070224)
1935
elif source_minikind in 'fdlt' and target_minikind in 'a':
1936
# unversioned, possibly, or possibly not deleted: we dont care.
1937
# if its still on disk, *and* theres no other entry at this
1938
# path [we dont know this in this routine at the moment -
1939
# perhaps we should change this - then it would be an unknown.
1940
old_path = pathjoin(entry[0][0], entry[0][1])
1941
# parent id is the entry for the path in the target tree
1942
parent_id = state._get_entry(source_index, path_utf8=entry[0][0])[0][2]
1943
if parent_id == entry[0][2]:
1945
return ((entry[0][2], (old_path, None), True,
1948
(entry[0][1], None),
1949
(_minikind_to_kind[source_minikind], None),
1950
(source_details[3], None)),)
1951
elif source_minikind in 'fdlt' and target_minikind in 'r':
1952
# a rename; could be a true rename, or a rename inherited from
1953
# a renamed parent. TODO: handle this efficiently. Its not
1954
# common case to rename dirs though, so a correct but slow
1955
# implementation will do.
1956
if not osutils.is_inside_any(searched_specific_files, target_details[1]):
1957
search_specific_files.add(target_details[1])
1958
elif source_minikind in 'r' and target_minikind in 'r':
1959
# neither of the selected trees contain this file,
1960
# so skip over it. This is not currently directly tested, but
1961
# is indirectly via test_too_much.TestCommands.test_conflicts.
1964
raise AssertionError("don't know how to compare "
1965
"source_minikind=%r, target_minikind=%r"
1966
% (source_minikind, target_minikind))
1967
## import pdb;pdb.set_trace()
1969
while search_specific_files:
1970
# TODO: the pending list should be lexically sorted? the
1971
# interface doesn't require it.
1972
current_root = search_specific_files.pop()
1973
searched_specific_files.add(current_root)
1974
# process the entries for this containing directory: the rest will be
1975
# found by their parents recursively.
1976
root_entries = _entries_for_path(current_root)
1977
root_abspath = self.target.abspath(current_root)
1979
root_stat = os.lstat(root_abspath)
1981
if e.errno == errno.ENOENT:
1982
# the path does not exist: let _process_entry know that.
1983
root_dir_info = None
1985
# some other random error: hand it up.
1988
root_dir_info = ('', current_root,
1989
osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
1991
if root_dir_info[2] == 'directory':
1992
if self.target._directory_is_tree_reference(
1993
current_root.decode('utf8')):
1994
root_dir_info = root_dir_info[:2] + \
1995
('tree-reference',) + root_dir_info[3:]
1997
if not root_entries and not root_dir_info:
1998
# this specified path is not present at all, skip it.
2000
path_handled = False
2001
for entry in root_entries:
2002
for result in _process_entry(entry, root_dir_info):
2003
# this check should probably be outside the loop: one
2004
# 'iterate two trees' api, and then _iter_changes filters
2005
# unchanged pairs. - RBC 20070226
2007
if (include_unchanged
2008
or result[2] # content change
2009
or result[3][0] != result[3][1] # versioned status
2010
or result[4][0] != result[4][1] # parent id
2011
or result[5][0] != result[5][1] # name
2012
or result[6][0] != result[6][1] # kind
2013
or result[7][0] != result[7][1] # executable
2015
result = (result[0],
2016
((utf8_decode(result[1][0])[0]),
2017
utf8_decode(result[1][1])[0]),) + result[2:]
2019
if want_unversioned and not path_handled:
2020
new_executable = bool(
2021
stat.S_ISREG(root_dir_info[3].st_mode)
2022
and stat.S_IEXEC & root_dir_info[3].st_mode)
2023
yield (None, (None, current_root), True, (False, False),
2025
(None, splitpath(current_root)[-1]),
2026
(None, root_dir_info[2]), (None, new_executable))
2027
dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
2028
initial_key = (current_root, '', '')
2029
block_index, _ = state._find_block_index_from_key(initial_key)
2030
if block_index == 0:
2031
# we have processed the total root already, but because the
2032
# initial key matched it we should skip it here.
2035
current_dir_info = dir_iterator.next()
2037
if e.errno in (errno.ENOENT, errno.ENOTDIR):
2038
# there may be directories in the inventory even though
2039
# this path is not a file on disk: so mark it as end of
2041
current_dir_info = None
2045
if current_dir_info[0][0] == '':
2046
# remove .bzr from iteration
2047
bzr_index = bisect_left(current_dir_info[1], ('.bzr',))
2048
assert current_dir_info[1][bzr_index][0] == '.bzr'
2049
del current_dir_info[1][bzr_index]
2050
# walk until both the directory listing and the versioned metadata
2052
if (block_index < len(state._dirblocks) and
2053
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
2054
current_block = state._dirblocks[block_index]
2056
current_block = None
2057
while (current_dir_info is not None or
2058
current_block is not None):
2059
if (current_dir_info and current_block
2060
and current_dir_info[0][0] != current_block[0]):
2061
if current_dir_info[0][0] < current_block[0] :
2062
# filesystem data refers to paths not covered by the dirblock.
2063
# this has two possibilities:
2064
# A) it is versioned but empty, so there is no block for it
2065
# B) it is not versioned.
2066
# in either case it was processed by the containing directories walk:
2067
# if it is root/foo, when we walked root we emitted it,
2068
# or if we ere given root/foo to walk specifically, we
2069
# emitted it when checking the walk-root entries
2070
# advance the iterator and loop - we dont need to emit it.
2072
current_dir_info = dir_iterator.next()
2073
except StopIteration:
2074
current_dir_info = None
2076
# We have a dirblock entry for this location, but there
2077
# is no filesystem path for this. This is most likely
2078
# because a directory was removed from the disk.
2079
# We don't have to report the missing directory,
2080
# because that should have already been handled, but we
2081
# need to handle all of the files that are contained
2083
for current_entry in current_block[1]:
2084
# entry referring to file not present on disk.
2085
# advance the entry only, after processing.
2086
for result in _process_entry(current_entry, None):
2087
# this check should probably be outside the loop: one
2088
# 'iterate two trees' api, and then _iter_changes filters
2089
# unchanged pairs. - RBC 20070226
2090
if (include_unchanged
2091
or result[2] # content change
2092
or result[3][0] != result[3][1] # versioned status
2093
or result[4][0] != result[4][1] # parent id
2094
or result[5][0] != result[5][1] # name
2095
or result[6][0] != result[6][1] # kind
2096
or result[7][0] != result[7][1] # executable
2098
result = (result[0],
2099
((utf8_decode(result[1][0])[0]),
2100
utf8_decode(result[1][1])[0]),) + result[2:]
2103
if (block_index < len(state._dirblocks) and
2104
osutils.is_inside(current_root,
2105
state._dirblocks[block_index][0])):
2106
current_block = state._dirblocks[block_index]
2108
current_block = None
2111
if current_block and entry_index < len(current_block[1]):
2112
current_entry = current_block[1][entry_index]
2114
current_entry = None
2115
advance_entry = True
2117
if current_dir_info and path_index < len(current_dir_info[1]):
2118
current_path_info = current_dir_info[1][path_index]
2119
if current_path_info[2] == 'directory':
2120
if self.target._directory_is_tree_reference(
2121
current_path_info[0].decode('utf8')):
2122
current_path_info = current_path_info[:2] + \
2123
('tree-reference',) + current_path_info[3:]
2125
current_path_info = None
2127
path_handled = False
2128
while (current_entry is not None or
2129
current_path_info is not None):
2130
if current_entry is None:
2131
# the check for path_handled when the path is adnvaced
2132
# will yield this path if needed.
2134
elif current_path_info is None:
2135
# no path is fine: the per entry code will handle it.
2136
for result in _process_entry(current_entry, current_path_info):
2137
# this check should probably be outside the loop: one
2138
# 'iterate two trees' api, and then _iter_changes filters
2139
# unchanged pairs. - RBC 20070226
2140
if (include_unchanged
2141
or result[2] # content change
2142
or result[3][0] != result[3][1] # versioned status
2143
or result[4][0] != result[4][1] # parent id
2144
or result[5][0] != result[5][1] # name
2145
or result[6][0] != result[6][1] # kind
2146
or result[7][0] != result[7][1] # executable
2148
result = (result[0],
2149
((utf8_decode(result[1][0])[0]),
2150
utf8_decode(result[1][1])[0]),) + result[2:]
2152
elif current_entry[0][1] != current_path_info[1]:
2153
if current_path_info[1] < current_entry[0][1]:
2154
# extra file on disk: pass for now, but only
2155
# increment the path, not the entry
2156
advance_entry = False
2158
# entry referring to file not present on disk.
2159
# advance the entry only, after processing.
2160
for result in _process_entry(current_entry, None):
2161
# this check should probably be outside the loop: one
2162
# 'iterate two trees' api, and then _iter_changes filters
2163
# unchanged pairs. - RBC 20070226
2165
if (include_unchanged
2166
or result[2] # content change
2167
or result[3][0] != result[3][1] # versioned status
2168
or result[4][0] != result[4][1] # parent id
2169
or result[5][0] != result[5][1] # name
2170
or result[6][0] != result[6][1] # kind
2171
or result[7][0] != result[7][1] # executable
2173
result = (result[0],
2174
((utf8_decode(result[1][0])[0]),
2175
utf8_decode(result[1][1])[0]),) + result[2:]
2177
advance_path = False
2179
for result in _process_entry(current_entry, current_path_info):
2180
# this check should probably be outside the loop: one
2181
# 'iterate two trees' api, and then _iter_changes filters
2182
# unchanged pairs. - RBC 20070226
2184
if (include_unchanged
2185
or result[2] # content change
2186
or result[3][0] != result[3][1] # versioned status
2187
or result[4][0] != result[4][1] # parent id
2188
or result[5][0] != result[5][1] # name
2189
or result[6][0] != result[6][1] # kind
2190
or result[7][0] != result[7][1] # executable
2192
result = (result[0],
2193
((utf8_decode(result[1][0])[0]),
2194
utf8_decode(result[1][1])[0]),) + result[2:]
2196
if advance_entry and current_entry is not None:
2198
if entry_index < len(current_block[1]):
2199
current_entry = current_block[1][entry_index]
2201
current_entry = None
2203
advance_entry = True # reset the advance flaga
2204
if advance_path and current_path_info is not None:
2205
if not path_handled:
2206
# unversioned in all regards
2207
if want_unversioned:
2208
new_executable = bool(
2209
stat.S_ISREG(current_path_info[3].st_mode)
2210
and stat.S_IEXEC & current_path_info[3].st_mode)
2211
if want_unversioned:
2212
yield (None, (None, current_path_info[0]),
2216
(None, current_path_info[1]),
2217
(None, current_path_info[2]),
2218
(None, new_executable))
2219
# dont descend into this unversioned path if it is
2221
if current_path_info[2] in (
2222
'directory', 'tree-referene'):
2223
del current_dir_info[1][path_index]
2226
if path_index < len(current_dir_info[1]):
2227
current_path_info = current_dir_info[1][path_index]
2228
if current_path_info[2] == 'directory':
2229
if self.target._directory_is_tree_reference(
2230
current_path_info[0].decode('utf8')):
2231
current_path_info = current_path_info[:2] + \
2232
('tree-reference',) + current_path_info[3:]
2234
current_path_info = None
2235
path_handled = False
2237
advance_path = True # reset the advance flagg.
2238
if current_block is not None:
2240
if (block_index < len(state._dirblocks) and
2241
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
2242
current_block = state._dirblocks[block_index]
2244
current_block = None
2245
if current_dir_info is not None:
2247
current_dir_info = dir_iterator.next()
2248
except StopIteration:
2249
current_dir_info = None
2253
def is_compatible(source, target):
2254
# the target must be a dirstate working tree
2255
if not isinstance(target, WorkingTree4):
2257
# the source must be a revtreee or dirstate rev tree.
2258
if not isinstance(source,
2259
(revisiontree.RevisionTree, DirStateRevisionTree)):
2261
# the source revid must be in the target dirstate
2262
if not (source._revision_id == NULL_REVISION or
2263
source._revision_id in target.get_parent_ids()):
2264
# TODO: what about ghosts? it may well need to
2265
# check for them explicitly.
2269
InterTree.register_optimiser(InterDirStateTree)
2272
class Converter3to4(object):
2273
"""Perform an in-place upgrade of format 3 to format 4 trees."""
2276
self.target_format = WorkingTreeFormat4()
2278
def convert(self, tree):
2279
# lock the control files not the tree, so that we dont get tree
2280
# on-unlock behaviours, and so that noone else diddles with the
2281
# tree during upgrade.
2282
tree._control_files.lock_write()
2284
self.create_dirstate_data(tree)
2285
self.update_format(tree)
2286
self.remove_xml_files(tree)
2288
tree._control_files.unlock()
2290
def create_dirstate_data(self, tree):
2291
"""Create the dirstate based data for tree."""
2292
local_path = tree.bzrdir.get_workingtree_transport(None
2293
).local_abspath('dirstate')
2294
state = dirstate.DirState.from_tree(tree, local_path)
2298
def remove_xml_files(self, tree):
2299
"""Remove the oldformat 3 data."""
2300
transport = tree.bzrdir.get_workingtree_transport(None)
2301
for path in ['basis-inventory-cache', 'inventory', 'last-revision',
2302
'pending-merges', 'stat-cache']:
2304
transport.delete(path)
2305
except errors.NoSuchFile:
2306
# some files are optional - just deal.
2309
def update_format(self, tree):
2310
"""Change the format marker."""
2311
tree._control_files.put_utf8('format',
2312
self.target_format.get_format_string())