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,
55
revision as _mod_revision,
65
from bzrlib.transport import get_transport
69
from bzrlib import symbol_versioning
70
from bzrlib.decorators import needs_read_lock, needs_write_lock
71
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID, entry_factory
72
from bzrlib.lockable_files import LockableFiles, TransportLock
73
from bzrlib.lockdir import LockDir
74
import bzrlib.mutabletree
75
from bzrlib.mutabletree import needs_tree_write_lock
76
from bzrlib.osutils import (
86
from bzrlib.trace import mutter, note
87
from bzrlib.transport.local import LocalTransport
88
from bzrlib.tree import InterTree
89
from bzrlib.progress import DummyProgress, ProgressPhase
90
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
91
from bzrlib.rio import RioReader, rio_file, Stanza
92
from bzrlib.symbol_versioning import (deprecated_passed,
97
from bzrlib.tree import Tree
98
from bzrlib.workingtree import WorkingTree, WorkingTree3, WorkingTreeFormat3
101
# This is the Windows equivalent of ENOTDIR
102
# It is defined in pywin32.winerror, but we don't want a strong dependency for
103
# just an error code.
104
ERROR_PATH_NOT_FOUND = 3
105
ERROR_DIRECTORY = 267
108
class WorkingTree4(WorkingTree3):
109
"""This is the Format 4 working tree.
111
This differs from WorkingTree3 by:
112
- Having a consolidated internal dirstate, stored in a
113
randomly-accessible sorted file on disk.
114
- Not having a regular inventory attribute. One can be synthesized
115
on demand but this is expensive and should be avoided.
117
This is new in bzr 0.15.
120
def __init__(self, basedir,
125
"""Construct a WorkingTree for basedir.
127
If the branch is not supplied, it is opened automatically.
128
If the branch is supplied, it must be the branch for this basedir.
129
(branch.base is not cross checked, because for remote branches that
130
would be meaningless).
132
self._format = _format
133
self.bzrdir = _bzrdir
134
assert isinstance(basedir, basestring), \
135
"base directory %r is not a string" % basedir
136
basedir = safe_unicode(basedir)
137
mutter("opening working tree %r", basedir)
138
self._branch = branch
139
assert isinstance(self.branch, bzrlib.branch.Branch), \
140
"branch %r is not a Branch" % self.branch
141
self.basedir = realpath(basedir)
142
# if branch is at our basedir and is a format 6 or less
143
# assume all other formats have their own control files.
144
assert isinstance(_control_files, LockableFiles), \
145
"_control_files must be a LockableFiles, not %r" % _control_files
146
self._control_files = _control_files
149
# during a read or write lock these objects are set, and are
150
# None the rest of the time.
151
self._dirstate = None
152
self._inventory = None
154
self._setup_directory_is_tree_reference()
155
self._detect_case_handling()
157
@needs_tree_write_lock
158
def _add(self, files, ids, kinds):
159
"""See MutableTree._add."""
160
state = self.current_dirstate()
161
for f, file_id, kind in zip(files, ids, kinds):
164
# special case tree root handling.
165
if f == '' and self.path2id(f) == ROOT_ID:
166
state.set_path_id('', generate_ids.gen_file_id(f))
169
file_id = generate_ids.gen_file_id(f)
170
# deliberately add the file with no cached stat or sha1
171
# - on the first access it will be gathered, and we can
172
# always change this once tests are all passing.
173
state.add(f, file_id, kind, None, '')
174
self._make_dirty(reset_inventory=True)
176
def _make_dirty(self, reset_inventory):
177
"""Make the tree state dirty.
179
:param reset_inventory: True if the cached inventory should be removed
180
(presuming there is one).
183
if reset_inventory and self._inventory is not None:
184
self._inventory = None
186
@needs_tree_write_lock
187
def add_reference(self, sub_tree):
188
# use standard implementation, which calls back to self._add
190
# So we don't store the reference_revision in the working dirstate,
191
# it's just recorded at the moment of commit.
192
self._add_reference(sub_tree)
194
def break_lock(self):
195
"""Break a lock if one is present from another instance.
197
Uses the ui factory to ask for confirmation if the lock may be from
200
This will probe the repository for its lock as well.
202
# if the dirstate is locked by an active process, reject the break lock
205
if self._dirstate is None:
209
state = self._current_dirstate()
210
if state._lock_token is not None:
211
# we already have it locked. sheese, cant break our own lock.
212
raise errors.LockActive(self.basedir)
215
# try for a write lock - need permission to get one anyhow
218
except errors.LockContention:
219
# oslocks fail when a process is still live: fail.
220
# TODO: get the locked lockdir info and give to the user to
221
# assist in debugging.
222
raise errors.LockActive(self.basedir)
227
self._dirstate = None
228
self._control_files.break_lock()
229
self.branch.break_lock()
231
def _comparison_data(self, entry, path):
232
kind, executable, stat_value = \
233
WorkingTree3._comparison_data(self, entry, path)
234
# it looks like a plain directory, but it's really a reference -- see
236
if (self._repo_supports_tree_reference and
237
kind == 'directory' and
238
self._directory_is_tree_reference(path)):
239
kind = 'tree-reference'
240
return kind, executable, stat_value
243
def commit(self, message=None, revprops=None, *args, **kwargs):
244
# mark the tree as dirty post commit - commit
245
# can change the current versioned list by doing deletes.
246
result = WorkingTree3.commit(self, message, revprops, *args, **kwargs)
247
self._make_dirty(reset_inventory=True)
250
def current_dirstate(self):
251
"""Return the current dirstate object.
253
This is not part of the tree interface and only exposed for ease of
256
:raises errors.NotWriteLocked: when not in a lock.
258
self._must_be_locked()
259
return self._current_dirstate()
261
def _current_dirstate(self):
262
"""Internal function that does not check lock status.
264
This is needed for break_lock which also needs the dirstate.
266
if self._dirstate is not None:
267
return self._dirstate
268
local_path = self.bzrdir.get_workingtree_transport(None
269
).local_abspath('dirstate')
270
self._dirstate = dirstate.DirState.on_file(local_path)
271
return self._dirstate
273
def filter_unversioned_files(self, paths):
274
"""Filter out paths that are versioned.
276
:return: set of paths.
278
# TODO: make a generic multi-bisect routine roughly that should list
279
# the paths, then process one half at a time recursively, and feed the
280
# results of each bisect in further still
281
paths = sorted(paths)
283
state = self.current_dirstate()
284
# TODO we want a paths_to_dirblocks helper I think
286
dirname, basename = os.path.split(path.encode('utf8'))
287
_, _, _, path_is_versioned = state._get_block_entry_index(
288
dirname, basename, 0)
289
if not path_is_versioned:
294
"""Write all cached data to disk."""
295
if self._control_files._lock_mode != 'w':
296
raise errors.NotWriteLocked(self)
297
self.current_dirstate().save()
298
self._inventory = None
301
@needs_tree_write_lock
302
def _gather_kinds(self, files, kinds):
303
"""See MutableTree._gather_kinds."""
304
for pos, f in enumerate(files):
305
if kinds[pos] is None:
306
kinds[pos] = self._kind(f)
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
"repository of %r " \
361
"doesn't support tree references " \
362
"required by entry %r" \
364
inv_entry.reference_revision = link_or_sha1 or None
365
elif kind != 'symlink':
366
raise AssertionError("unknown kind %r" % kind)
367
# These checks cost us around 40ms on a 55k entry tree
368
assert file_id not in inv_byid, ('file_id %s already in'
369
' inventory as %s' % (file_id, inv_byid[file_id]))
370
assert name_unicode not in parent_ie.children
371
inv_byid[file_id] = inv_entry
372
parent_ie.children[name_unicode] = inv_entry
373
self._inventory = inv
375
def _get_entry(self, file_id=None, path=None):
376
"""Get the dirstate row for file_id or path.
378
If either file_id or path is supplied, it is used as the key to lookup.
379
If both are supplied, the fastest lookup is used, and an error is
380
raised if they do not both point at the same row.
382
:param file_id: An optional unicode file_id to be looked up.
383
:param path: An optional unicode path to be looked up.
384
:return: The dirstate row tuple for path/file_id, or (None, None)
386
if file_id is None and path is None:
387
raise errors.BzrError('must supply file_id or path')
388
state = self.current_dirstate()
390
path = path.encode('utf8')
391
return state._get_entry(0, fileid_utf8=file_id, path_utf8=path)
393
def get_file_sha1(self, file_id, path=None, stat_value=None):
394
# check file id is valid unconditionally.
395
entry = self._get_entry(file_id=file_id, path=path)
397
raise errors.NoSuchId(self, file_id)
399
path = pathjoin(entry[0][0], entry[0][1]).decode('utf8')
401
file_abspath = self.abspath(path)
402
state = self.current_dirstate()
403
if stat_value is None:
405
stat_value = os.lstat(file_abspath)
407
if e.errno == errno.ENOENT:
411
link_or_sha1 = state.update_entry(entry, file_abspath,
412
stat_value=stat_value)
413
if entry[1][0][0] == 'f':
417
def _get_inventory(self):
418
"""Get the inventory for the tree. This is only valid within a lock."""
419
if 'evil' in debug.debug_flags:
420
trace.mutter_callsite(2,
421
"accessing .inventory forces a size of tree translation.")
422
if self._inventory is not None:
423
return self._inventory
424
self._must_be_locked()
425
self._generate_inventory()
426
return self._inventory
428
inventory = property(_get_inventory,
429
doc="Inventory of this Tree")
432
def get_parent_ids(self):
433
"""See Tree.get_parent_ids.
435
This implementation requests the ids list from the dirstate file.
437
return self.current_dirstate().get_parent_ids()
439
def get_reference_revision(self, file_id, path=None):
440
# referenced tree's revision is whatever's currently there
441
return self.get_nested_tree(file_id, path).last_revision()
443
def get_nested_tree(self, file_id, path=None):
445
path = self.id2path(file_id)
446
# else: check file_id is at path?
447
return WorkingTree.open(self.abspath(path))
450
def get_root_id(self):
451
"""Return the id of this trees root"""
452
return self._get_entry(path='')[0][2]
454
def has_id(self, file_id):
455
state = self.current_dirstate()
456
row, parents = self._get_entry(file_id=file_id)
459
return osutils.lexists(pathjoin(
460
self.basedir, row[0].decode('utf8'), row[1].decode('utf8')))
463
def id2path(self, file_id):
464
"Convert a file-id to a path."
465
state = self.current_dirstate()
466
entry = self._get_entry(file_id=file_id)
467
if entry == (None, None):
468
raise errors.NoSuchId(tree=self, file_id=file_id)
469
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
470
return path_utf8.decode('utf8')
472
def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
473
entry = self._get_entry(path=path)
474
if entry == (None, None):
475
return False # Missing entries are not executable
476
return entry[1][0][3] # Executable?
478
if not osutils.supports_executable():
479
def is_executable(self, file_id, path=None):
480
"""Test if a file is executable or not.
482
Note: The caller is expected to take a read-lock before calling this.
484
entry = self._get_entry(file_id=file_id, path=path)
485
if entry == (None, None):
487
return entry[1][0][3]
489
_is_executable_from_path_and_stat = \
490
_is_executable_from_path_and_stat_from_basis
492
def is_executable(self, file_id, path=None):
493
"""Test if a file is executable or not.
495
Note: The caller is expected to take a read-lock before calling this.
498
path = self.id2path(file_id)
499
mode = os.lstat(self.abspath(path)).st_mode
500
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
502
def iter_all_file_ids(self):
503
"""See Tree.iter_all_file_ids"""
504
self._must_be_locked()
505
for key, tree_details in self.current_dirstate()._iter_entries():
506
if tree_details[0][0] in ('a', 'r'): # relocated
512
"""Iterate through file_ids for this tree.
514
file_ids are in a WorkingTree if they are in the working inventory
515
and the working file exists.
518
for key, tree_details in self.current_dirstate()._iter_entries():
519
if tree_details[0][0] in ('a', 'r'): # absent, relocated
520
# not relevant to the working tree
522
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
523
if osutils.lexists(path):
524
result.append(key[2])
527
def iter_references(self):
528
for key, tree_details in self.current_dirstate()._iter_entries():
529
if tree_details[0][0] in ('a', 'r'): # absent, relocated
530
# not relevant to the working tree
533
# the root is not a reference.
535
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
537
if self._kind(path) == 'tree-reference':
539
except errors.NoSuchFile:
540
# path is missing on disk.
543
def kind(self, file_id):
544
"""Return the kind of a file.
546
This is always the actual kind that's on disk, regardless of what it
549
Note: The caller is expected to take a read-lock before calling this.
551
relpath = self.id2path(file_id)
552
assert relpath != None, \
553
"path for id {%s} is None!" % file_id
554
return self._kind(relpath)
556
def _kind(self, relpath):
557
abspath = self.abspath(relpath)
558
kind = file_kind(abspath)
559
if (self._repo_supports_tree_reference and
560
kind == 'directory' and
561
self._directory_is_tree_reference(relpath)):
562
kind = 'tree-reference'
566
def _last_revision(self):
567
"""See Mutable.last_revision."""
568
parent_ids = self.current_dirstate().get_parent_ids()
572
return _mod_revision.NULL_REVISION
575
"""See Branch.lock_read, and WorkingTree.unlock."""
576
self.branch.lock_read()
578
self._control_files.lock_read()
580
state = self.current_dirstate()
581
if not state._lock_token:
583
# set our support for tree references from the repository in
585
self._repo_supports_tree_reference = getattr(
586
self.branch.repository._format, "supports_tree_reference",
589
self._control_files.unlock()
595
def _lock_self_write(self):
596
"""This should be called after the branch is locked."""
598
self._control_files.lock_write()
600
state = self.current_dirstate()
601
if not state._lock_token:
603
# set our support for tree references from the repository in
605
self._repo_supports_tree_reference = getattr(
606
self.branch.repository._format, "supports_tree_reference",
609
self._control_files.unlock()
615
def lock_tree_write(self):
616
"""See MutableTree.lock_tree_write, and WorkingTree.unlock."""
617
self.branch.lock_read()
618
self._lock_self_write()
620
def lock_write(self):
621
"""See MutableTree.lock_write, and WorkingTree.unlock."""
622
self.branch.lock_write()
623
self._lock_self_write()
625
@needs_tree_write_lock
626
def move(self, from_paths, to_dir, after=False):
627
"""See WorkingTree.move()."""
632
state = self.current_dirstate()
634
assert not isinstance(from_paths, basestring)
635
to_dir_utf8 = to_dir.encode('utf8')
636
to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
637
id_index = state._get_id_index()
638
# check destination directory
639
# get the details for it
640
to_entry_block_index, to_entry_entry_index, dir_present, entry_present = \
641
state._get_block_entry_index(to_entry_dirname, to_basename, 0)
642
if not entry_present:
643
raise errors.BzrMoveFailedError('', to_dir,
644
errors.NotVersionedError(to_dir))
645
to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
646
# get a handle on the block itself.
647
to_block_index = state._ensure_block(
648
to_entry_block_index, to_entry_entry_index, to_dir_utf8)
649
to_block = state._dirblocks[to_block_index]
650
to_abs = self.abspath(to_dir)
651
if not isdir(to_abs):
652
raise errors.BzrMoveFailedError('',to_dir,
653
errors.NotADirectory(to_abs))
655
if to_entry[1][0][0] != 'd':
656
raise errors.BzrMoveFailedError('',to_dir,
657
errors.NotADirectory(to_abs))
659
if self._inventory is not None:
660
update_inventory = True
662
to_dir_ie = inv[to_dir_id]
663
to_dir_id = to_entry[0][2]
665
update_inventory = False
668
def move_one(old_entry, from_path_utf8, minikind, executable,
669
fingerprint, packed_stat, size,
670
to_block, to_key, to_path_utf8):
671
state._make_absent(old_entry)
672
from_key = old_entry[0]
674
lambda:state.update_minimal(from_key,
676
executable=executable,
677
fingerprint=fingerprint,
678
packed_stat=packed_stat,
680
path_utf8=from_path_utf8))
681
state.update_minimal(to_key,
683
executable=executable,
684
fingerprint=fingerprint,
685
packed_stat=packed_stat,
687
path_utf8=to_path_utf8)
688
added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
689
new_entry = to_block[1][added_entry_index]
690
rollbacks.append(lambda:state._make_absent(new_entry))
692
for from_rel in from_paths:
693
# from_rel is 'pathinroot/foo/bar'
694
from_rel_utf8 = from_rel.encode('utf8')
695
from_dirname, from_tail = osutils.split(from_rel)
696
from_dirname, from_tail_utf8 = osutils.split(from_rel_utf8)
697
from_entry = self._get_entry(path=from_rel)
698
if from_entry == (None, None):
699
raise errors.BzrMoveFailedError(from_rel,to_dir,
700
errors.NotVersionedError(path=str(from_rel)))
702
from_id = from_entry[0][2]
703
to_rel = pathjoin(to_dir, from_tail)
704
to_rel_utf8 = pathjoin(to_dir_utf8, from_tail_utf8)
705
item_to_entry = self._get_entry(path=to_rel)
706
if item_to_entry != (None, None):
707
raise errors.BzrMoveFailedError(from_rel, to_rel,
708
"Target is already versioned.")
710
if from_rel == to_rel:
711
raise errors.BzrMoveFailedError(from_rel, to_rel,
712
"Source and target are identical.")
714
from_missing = not self.has_filename(from_rel)
715
to_missing = not self.has_filename(to_rel)
722
raise errors.BzrMoveFailedError(from_rel, to_rel,
723
errors.NoSuchFile(path=to_rel,
724
extra="New file has not been created yet"))
726
# neither path exists
727
raise errors.BzrRenameFailedError(from_rel, to_rel,
728
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
730
if from_missing: # implicitly just update our path mapping
733
raise errors.RenameFailedFilesExist(from_rel, to_rel)
736
def rollback_rename():
737
"""A single rename has failed, roll it back."""
738
# roll back everything, even if we encounter trouble doing one
741
# TODO: at least log the other exceptions rather than just
742
# losing them mbp 20070307
744
for rollback in reversed(rollbacks):
748
exc_info = sys.exc_info()
750
raise exc_info[0], exc_info[1], exc_info[2]
752
# perform the disk move first - its the most likely failure point.
754
from_rel_abs = self.abspath(from_rel)
755
to_rel_abs = self.abspath(to_rel)
757
osutils.rename(from_rel_abs, to_rel_abs)
759
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
760
rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
762
# perform the rename in the inventory next if needed: its easy
766
from_entry = inv[from_id]
767
current_parent = from_entry.parent_id
768
inv.rename(from_id, to_dir_id, from_tail)
770
lambda: inv.rename(from_id, current_parent, from_tail))
771
# finally do the rename in the dirstate, which is a little
772
# tricky to rollback, but least likely to need it.
773
old_block_index, old_entry_index, dir_present, file_present = \
774
state._get_block_entry_index(from_dirname, from_tail_utf8, 0)
775
old_block = state._dirblocks[old_block_index][1]
776
old_entry = old_block[old_entry_index]
777
from_key, old_entry_details = old_entry
778
cur_details = old_entry_details[0]
780
to_key = ((to_block[0],) + from_key[1:3])
781
minikind = cur_details[0]
782
move_one(old_entry, from_path_utf8=from_rel_utf8,
784
executable=cur_details[3],
785
fingerprint=cur_details[1],
786
packed_stat=cur_details[4],
790
to_path_utf8=to_rel_utf8)
793
def update_dirblock(from_dir, to_key, to_dir_utf8):
794
"""Recursively update all entries in this dirblock."""
795
assert from_dir != '', "renaming root not supported"
796
from_key = (from_dir, '')
797
from_block_idx, present = \
798
state._find_block_index_from_key(from_key)
800
# This is the old record, if it isn't present, then
801
# there is theoretically nothing to update.
802
# (Unless it isn't present because of lazy loading,
803
# but we don't do that yet)
805
from_block = state._dirblocks[from_block_idx]
806
to_block_index, to_entry_index, _, _ = \
807
state._get_block_entry_index(to_key[0], to_key[1], 0)
808
to_block_index = state._ensure_block(
809
to_block_index, to_entry_index, to_dir_utf8)
810
to_block = state._dirblocks[to_block_index]
812
# Grab a copy since move_one may update the list.
813
for entry in from_block[1][:]:
814
assert entry[0][0] == from_dir
815
cur_details = entry[1][0]
816
to_key = (to_dir_utf8, entry[0][1], entry[0][2])
817
from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
818
to_path_utf8 = osutils.pathjoin(to_dir_utf8, entry[0][1])
819
minikind = cur_details[0]
821
# Deleted children of a renamed directory
822
# Do not need to be updated.
823
# Children that have been renamed out of this
824
# directory should also not be updated
826
move_one(entry, from_path_utf8=from_path_utf8,
828
executable=cur_details[3],
829
fingerprint=cur_details[1],
830
packed_stat=cur_details[4],
834
to_path_utf8=to_path_utf8)
836
# We need to move all the children of this
838
update_dirblock(from_path_utf8, to_key,
840
update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
844
result.append((from_rel, to_rel))
845
state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
846
self._make_dirty(reset_inventory=False)
850
def _must_be_locked(self):
851
if not self._control_files._lock_count:
852
raise errors.ObjectNotLocked(self)
855
"""Initialize the state in this tree to be a new tree."""
859
def path2id(self, path):
860
"""Return the id for path in this tree."""
861
path = path.strip('/')
862
entry = self._get_entry(path=path)
863
if entry == (None, None):
867
def paths2ids(self, paths, trees=[], require_versioned=True):
868
"""See Tree.paths2ids().
870
This specialisation fast-paths the case where all the trees are in the
875
parents = self.get_parent_ids()
877
if not (isinstance(tree, DirStateRevisionTree) and tree._revision_id in
879
return super(WorkingTree4, self).paths2ids(paths, trees, require_versioned)
880
search_indexes = [0] + [1 + parents.index(tree._revision_id) for tree in trees]
881
# -- make all paths utf8 --
884
paths_utf8.add(path.encode('utf8'))
886
# -- paths is now a utf8 path set --
887
# -- get the state object and prepare it.
888
state = self.current_dirstate()
889
if False and (state._dirblock_state == dirstate.DirState.NOT_IN_MEMORY
890
and '' not in paths):
891
paths2ids = self._paths2ids_using_bisect
893
paths2ids = self._paths2ids_in_memory
894
return paths2ids(paths, search_indexes,
895
require_versioned=require_versioned)
897
def _paths2ids_in_memory(self, paths, search_indexes,
898
require_versioned=True):
899
state = self.current_dirstate()
900
state._read_dirblocks_if_needed()
901
def _entries_for_path(path):
902
"""Return a list with all the entries that match path for all ids.
904
dirname, basename = os.path.split(path)
905
key = (dirname, basename, '')
906
block_index, present = state._find_block_index_from_key(key)
908
# the block which should contain path is absent.
911
block = state._dirblocks[block_index][1]
912
entry_index, _ = state._find_entry_index(key, block)
913
# we may need to look at multiple entries at this path: walk while the paths match.
914
while (entry_index < len(block) and
915
block[entry_index][0][0:2] == key[0:2]):
916
result.append(block[entry_index])
919
if require_versioned:
920
# -- check all supplied paths are versioned in a search tree. --
923
path_entries = _entries_for_path(path)
925
# this specified path is not present at all: error
926
all_versioned = False
928
found_versioned = False
929
# for each id at this path
930
for entry in path_entries:
932
for index in search_indexes:
933
if entry[1][index][0] != 'a': # absent
934
found_versioned = True
935
# all good: found a versioned cell
937
if not found_versioned:
938
# none of the indexes was not 'absent' at all ids for this
940
all_versioned = False
942
if not all_versioned:
943
raise errors.PathsNotVersionedError(paths)
944
# -- remove redundancy in supplied paths to prevent over-scanning --
945
search_paths = osutils.minimum_path_selection(paths)
947
# for all search_indexs in each path at or under each element of
948
# search_paths, if the detail is relocated: add the id, and add the
949
# relocated path as one to search if its not searched already. If the
950
# detail is not relocated, add the id.
951
searched_paths = set()
953
def _process_entry(entry):
954
"""Look at search_indexes within entry.
956
If a specific tree's details are relocated, add the relocation
957
target to search_paths if not searched already. If it is absent, do
958
nothing. Otherwise add the id to found_ids.
960
for index in search_indexes:
961
if entry[1][index][0] == 'r': # relocated
962
if not osutils.is_inside_any(searched_paths, entry[1][index][1]):
963
search_paths.add(entry[1][index][1])
964
elif entry[1][index][0] != 'a': # absent
965
found_ids.add(entry[0][2])
967
current_root = search_paths.pop()
968
searched_paths.add(current_root)
969
# process the entries for this containing directory: the rest will be
970
# found by their parents recursively.
971
root_entries = _entries_for_path(current_root)
973
# this specified path is not present at all, skip it.
975
for entry in root_entries:
976
_process_entry(entry)
977
initial_key = (current_root, '', '')
978
block_index, _ = state._find_block_index_from_key(initial_key)
979
while (block_index < len(state._dirblocks) and
980
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
981
for entry in state._dirblocks[block_index][1]:
982
_process_entry(entry)
986
def _paths2ids_using_bisect(self, paths, search_indexes,
987
require_versioned=True):
988
state = self.current_dirstate()
991
split_paths = sorted(osutils.split(p) for p in paths)
992
found = state._bisect_recursive(split_paths)
994
if require_versioned:
995
found_dir_names = set(dir_name_id[:2] for dir_name_id in found)
996
for dir_name in split_paths:
997
if dir_name not in found_dir_names:
998
raise errors.PathsNotVersionedError(paths)
1000
for dir_name_id, trees_info in found.iteritems():
1001
for index in search_indexes:
1002
if trees_info[index][0] not in ('r', 'a'):
1003
found_ids.add(dir_name_id[2])
1006
def read_working_inventory(self):
1007
"""Read the working inventory.
1009
This is a meaningless operation for dirstate, but we obey it anyhow.
1011
return self.inventory
1014
def revision_tree(self, revision_id):
1015
"""See Tree.revision_tree.
1017
WorkingTree4 supplies revision_trees for any basis tree.
1019
dirstate = self.current_dirstate()
1020
parent_ids = dirstate.get_parent_ids()
1021
if revision_id not in parent_ids:
1022
raise errors.NoSuchRevisionInTree(self, revision_id)
1023
if revision_id in dirstate.get_ghosts():
1024
raise errors.NoSuchRevisionInTree(self, revision_id)
1025
return DirStateRevisionTree(dirstate, revision_id,
1026
self.branch.repository)
1028
@needs_tree_write_lock
1029
def set_last_revision(self, new_revision):
1030
"""Change the last revision in the working tree."""
1031
parents = self.get_parent_ids()
1032
if new_revision in (NULL_REVISION, None):
1033
assert len(parents) < 2, (
1034
"setting the last parent to none with a pending merge is "
1036
self.set_parent_ids([])
1038
self.set_parent_ids([new_revision] + parents[1:],
1039
allow_leftmost_as_ghost=True)
1041
@needs_tree_write_lock
1042
def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
1043
"""Set the parent ids to revision_ids.
1045
See also set_parent_trees. This api will try to retrieve the tree data
1046
for each element of revision_ids from the trees repository. If you have
1047
tree data already available, it is more efficient to use
1048
set_parent_trees rather than set_parent_ids. set_parent_ids is however
1049
an easier API to use.
1051
:param revision_ids: The revision_ids to set as the parent ids of this
1052
working tree. Any of these may be ghosts.
1055
for revision_id in revision_ids:
1057
revtree = self.branch.repository.revision_tree(revision_id)
1058
# TODO: jam 20070213 KnitVersionedFile raises
1059
# RevisionNotPresent rather than NoSuchRevision if a
1060
# given revision_id is not present. Should Repository be
1061
# catching it and re-raising NoSuchRevision?
1062
except (errors.NoSuchRevision, errors.RevisionNotPresent):
1064
trees.append((revision_id, revtree))
1065
self.set_parent_trees(trees,
1066
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
1068
@needs_tree_write_lock
1069
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
1070
"""Set the parents of the working tree.
1072
:param parents_list: A list of (revision_id, tree) tuples.
1073
If tree is None, then that element is treated as an unreachable
1074
parent tree - i.e. a ghost.
1076
dirstate = self.current_dirstate()
1077
if len(parents_list) > 0:
1078
if not allow_leftmost_as_ghost and parents_list[0][1] is None:
1079
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1082
# convert absent trees to the null tree, which we convert back to
1083
# missing on access.
1084
for rev_id, tree in parents_list:
1085
_mod_revision.check_not_reserved_id(rev_id)
1086
if tree is not None:
1087
real_trees.append((rev_id, tree))
1089
real_trees.append((rev_id,
1090
self.branch.repository.revision_tree(None)))
1091
ghosts.append(rev_id)
1092
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1093
self._make_dirty(reset_inventory=False)
1095
def _set_root_id(self, file_id):
1096
"""See WorkingTree.set_root_id."""
1097
state = self.current_dirstate()
1098
state.set_path_id('', file_id)
1099
if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1100
self._make_dirty(reset_inventory=True)
1102
def _sha_from_stat(self, path, stat_result):
1103
"""Get a sha digest from the tree's stat cache.
1105
The default implementation assumes no stat cache is present.
1107
:param path: The path.
1108
:param stat_result: The stat result being looked up.
1110
return self.current_dirstate().sha1_from_stat(path, stat_result)
1113
def supports_tree_reference(self):
1114
return self._repo_supports_tree_reference
1117
"""Unlock in format 4 trees needs to write the entire dirstate."""
1118
# do non-implementation specific cleanup
1121
if self._control_files._lock_count == 1:
1122
# eventually we should do signature checking during read locks for
1124
if self._control_files._lock_mode == 'w':
1127
if self._dirstate is not None:
1128
# This is a no-op if there are no modifications.
1129
self._dirstate.save()
1130
self._dirstate.unlock()
1131
# TODO: jam 20070301 We shouldn't have to wipe the dirstate at this
1132
# point. Instead, it could check if the header has been
1133
# modified when it is locked, and if not, it can hang on to
1134
# the data it has in memory.
1135
self._dirstate = None
1136
self._inventory = None
1137
# reverse order of locking.
1139
return self._control_files.unlock()
1141
self.branch.unlock()
1143
@needs_tree_write_lock
1144
def unversion(self, file_ids):
1145
"""Remove the file ids in file_ids from the current versioned set.
1147
When a file_id is unversioned, all of its children are automatically
1150
:param file_ids: The file ids to stop versioning.
1151
:raises: NoSuchId if any fileid is not currently versioned.
1155
state = self.current_dirstate()
1156
state._read_dirblocks_if_needed()
1157
ids_to_unversion = set(file_ids)
1158
paths_to_unversion = set()
1160
# check if the root is to be unversioned, if so, assert for now.
1161
# walk the state marking unversioned things as absent.
1162
# if there are any un-unversioned ids at the end, raise
1163
for key, details in state._dirblocks[0][1]:
1164
if (details[0][0] not in ('a', 'r') and # absent or relocated
1165
key[2] in ids_to_unversion):
1166
# I haven't written the code to unversion / yet - it should be
1168
raise errors.BzrError('Unversioning the / is not currently supported')
1170
while block_index < len(state._dirblocks):
1171
# process one directory at a time.
1172
block = state._dirblocks[block_index]
1173
# first check: is the path one to remove - it or its children
1174
delete_block = False
1175
for path in paths_to_unversion:
1176
if (block[0].startswith(path) and
1177
(len(block[0]) == len(path) or
1178
block[0][len(path)] == '/')):
1179
# this entire block should be deleted - its the block for a
1180
# path to unversion; or the child of one
1183
# TODO: trim paths_to_unversion as we pass by paths
1185
# this block is to be deleted: process it.
1186
# TODO: we can special case the no-parents case and
1187
# just forget the whole block.
1189
while entry_index < len(block[1]):
1190
# Mark this file id as having been removed
1191
entry = block[1][entry_index]
1192
ids_to_unversion.discard(entry[0][2])
1193
if (entry[1][0][0] in 'ar' # don't remove absent or renamed
1195
or not state._make_absent(entry)):
1197
# go to the next block. (At the moment we dont delete empty
1202
while entry_index < len(block[1]):
1203
entry = block[1][entry_index]
1204
if (entry[1][0][0] in ('a', 'r') or # absent, relocated
1205
# ^ some parent row.
1206
entry[0][2] not in ids_to_unversion):
1207
# ^ not an id to unversion
1210
if entry[1][0][0] == 'd':
1211
paths_to_unversion.add(pathjoin(entry[0][0], entry[0][1]))
1212
if not state._make_absent(entry):
1214
# we have unversioned this id
1215
ids_to_unversion.remove(entry[0][2])
1217
if ids_to_unversion:
1218
raise errors.NoSuchId(self, iter(ids_to_unversion).next())
1219
self._make_dirty(reset_inventory=False)
1220
# have to change the legacy inventory too.
1221
if self._inventory is not None:
1222
for file_id in file_ids:
1223
self._inventory.remove_recursive_id(file_id)
1225
@needs_tree_write_lock
1226
def apply_inventory_delta(self, changes):
1227
"""See MutableTree.apply_inventory_delta"""
1228
self.current_dirstate().update_by_delta(changes, self.basedir)
1230
def update_basis_by_delta(self, new_revid, delta):
1231
"""See MutableTree.update_basis_by_delta."""
1232
assert self.last_revision() != new_revid
1233
self.current_dirstate().update_basis_by_delta(delta, new_revid)
1236
def _validate(self):
1237
self._dirstate._validate()
1239
@needs_tree_write_lock
1240
def _write_inventory(self, inv):
1241
"""Write inventory as the current inventory."""
1242
assert not self._dirty, ("attempting to write an inventory when the "
1243
"dirstate is dirty will cause data loss")
1244
self.current_dirstate().set_state_from_inventory(inv)
1245
self._make_dirty(reset_inventory=False)
1246
if self._inventory is not None:
1247
self._inventory = inv
1251
class WorkingTreeFormat4(WorkingTreeFormat3):
1252
"""The first consolidated dirstate working tree format.
1255
- exists within a metadir controlling .bzr
1256
- includes an explicit version marker for the workingtree control
1257
files, separate from the BzrDir format
1258
- modifies the hash cache format
1259
- is new in bzr 0.15
1260
- uses a LockDir to guard access to it.
1263
upgrade_recommended = False
1265
def get_format_string(self):
1266
"""See WorkingTreeFormat.get_format_string()."""
1267
return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1269
def get_format_description(self):
1270
"""See WorkingTreeFormat.get_format_description()."""
1271
return "Working tree format 4"
1273
def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
1274
accelerator_tree=None):
1275
"""See WorkingTreeFormat.initialize().
1277
:param revision_id: allows creating a working tree at a different
1278
revision than the branch is at.
1279
:param accelerator_tree: A tree which can be used for retrieving file
1280
contents more quickly than the revision tree, i.e. a workingtree.
1281
The revision tree will be used for cases where accelerator_tree's
1282
content is different.
1284
These trees get an initial random root id, if their repository supports
1285
rich root data, TREE_ROOT otherwise.
1287
if not isinstance(a_bzrdir.transport, LocalTransport):
1288
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1289
transport = a_bzrdir.get_workingtree_transport(self)
1290
control_files = self._open_control_files(a_bzrdir)
1291
control_files.create_lock()
1292
control_files.lock_write()
1293
control_files.put_utf8('format', self.get_format_string())
1294
if from_branch is not None:
1295
branch = from_branch
1297
branch = a_bzrdir.open_branch()
1298
if revision_id is None:
1299
revision_id = branch.last_revision()
1300
local_path = transport.local_abspath('dirstate')
1301
# write out new dirstate (must exist when we create the tree)
1302
state = dirstate.DirState.initialize(local_path)
1305
wt = WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1309
_control_files=control_files)
1311
wt.lock_tree_write()
1313
if revision_id in (None, NULL_REVISION):
1314
if branch.repository.supports_rich_root():
1315
wt._set_root_id(generate_ids.gen_root_id())
1317
wt._set_root_id(ROOT_ID)
1320
# frequently, we will get here due to branching. The accelerator
1321
# tree will be the tree from the branch, so the desired basis
1322
# tree will often be a parent of the accelerator tree.
1323
if accelerator_tree is not None:
1325
basis = accelerator_tree.revision_tree(revision_id)
1326
except errors.NoSuchRevision:
1329
basis = branch.repository.revision_tree(revision_id)
1330
if revision_id == NULL_REVISION:
1333
parents_list = [(revision_id, basis)]
1335
wt.set_parent_trees(parents_list, allow_leftmost_as_ghost=True)
1337
# if the basis has a root id we have to use that; otherwise we use
1339
basis_root_id = basis.get_root_id()
1340
if basis_root_id is not None:
1341
wt._set_root_id(basis_root_id)
1343
transform.build_tree(basis, wt, accelerator_tree)
1346
control_files.unlock()
1350
def _open(self, a_bzrdir, control_files):
1351
"""Open the tree itself.
1353
:param a_bzrdir: the dir for the tree.
1354
:param control_files: the control files for the tree.
1356
return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1357
branch=a_bzrdir.open_branch(),
1360
_control_files=control_files)
1362
def __get_matchingbzrdir(self):
1363
# please test against something that will let us do tree references
1364
return bzrdir.format_registry.make_bzrdir(
1365
'dirstate-with-subtree')
1367
_matchingbzrdir = property(__get_matchingbzrdir)
1370
class DirStateRevisionTree(Tree):
1371
"""A revision tree pulling the inventory from a dirstate."""
1373
def __init__(self, dirstate, revision_id, repository):
1374
self._dirstate = dirstate
1375
self._revision_id = revision_id
1376
self._repository = repository
1377
self._inventory = None
1379
self._dirstate_locked = False
1382
return "<%s of %s in %s>" % \
1383
(self.__class__.__name__, self._revision_id, self._dirstate)
1385
def annotate_iter(self, file_id,
1386
default_revision=_mod_revision.CURRENT_REVISION):
1387
"""See Tree.annotate_iter"""
1388
w = self._get_weave(file_id)
1389
return w.annotate_iter(self.inventory[file_id].revision)
1391
def _get_ancestors(self, default_revision):
1392
return set(self._repository.get_ancestry(self._revision_id,
1394
def _comparison_data(self, entry, path):
1395
"""See Tree._comparison_data."""
1397
return None, False, None
1398
# trust the entry as RevisionTree does, but this may not be
1399
# sensible: the entry might not have come from us?
1400
return entry.kind, entry.executable, None
1402
def _file_size(self, entry, stat_value):
1403
return entry.text_size
1405
def filter_unversioned_files(self, paths):
1406
"""Filter out paths that are not versioned.
1408
:return: set of paths.
1410
pred = self.has_filename
1411
return set((p for p in paths if not pred(p)))
1413
def get_root_id(self):
1414
return self.path2id('')
1416
def id2path(self, file_id):
1417
"Convert a file-id to a path."
1418
entry = self._get_entry(file_id=file_id)
1419
if entry == (None, None):
1420
raise errors.NoSuchId(tree=self, file_id=file_id)
1421
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
1422
return path_utf8.decode('utf8')
1424
def _get_parent_index(self):
1425
"""Return the index in the dirstate referenced by this tree."""
1426
return self._dirstate.get_parent_ids().index(self._revision_id) + 1
1428
def _get_entry(self, file_id=None, path=None):
1429
"""Get the dirstate row for file_id or path.
1431
If either file_id or path is supplied, it is used as the key to lookup.
1432
If both are supplied, the fastest lookup is used, and an error is
1433
raised if they do not both point at the same row.
1435
:param file_id: An optional unicode file_id to be looked up.
1436
:param path: An optional unicode path to be looked up.
1437
:return: The dirstate row tuple for path/file_id, or (None, None)
1439
if file_id is None and path is None:
1440
raise errors.BzrError('must supply file_id or path')
1441
if path is not None:
1442
path = path.encode('utf8')
1443
parent_index = self._get_parent_index()
1444
return self._dirstate._get_entry(parent_index, fileid_utf8=file_id, path_utf8=path)
1446
def _generate_inventory(self):
1447
"""Create and set self.inventory from the dirstate object.
1449
(So this is only called the first time the inventory is requested for
1450
this tree; it then remains in memory until it's out of date.)
1452
This is relatively expensive: we have to walk the entire dirstate.
1454
assert self._locked, 'cannot generate inventory of an unlocked '\
1455
'dirstate revision tree'
1456
# separate call for profiling - makes it clear where the costs are.
1457
self._dirstate._read_dirblocks_if_needed()
1458
assert self._revision_id in self._dirstate.get_parent_ids(), \
1459
'parent %s has disappeared from %s' % (
1460
self._revision_id, self._dirstate.get_parent_ids())
1461
parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
1462
# This is identical now to the WorkingTree _generate_inventory except
1463
# for the tree index use.
1464
root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1465
current_id = root_key[2]
1466
assert current_entry[parent_index][0] == 'd'
1467
inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1468
inv.root.revision = current_entry[parent_index][4]
1469
# Turn some things into local variables
1470
minikind_to_kind = dirstate.DirState._minikind_to_kind
1471
factory = entry_factory
1472
utf8_decode = cache_utf8._utf8_decode
1473
inv_byid = inv._byid
1474
# we could do this straight out of the dirstate; it might be fast
1475
# and should be profiled - RBC 20070216
1476
parent_ies = {'' : inv.root}
1477
for block in self._dirstate._dirblocks[1:]: #skip root
1480
parent_ie = parent_ies[dirname]
1482
# all the paths in this block are not versioned in this tree
1484
for key, entry in block[1]:
1485
minikind, fingerprint, size, executable, revid = entry[parent_index]
1486
if minikind in ('a', 'r'): # absent, relocated
1490
name_unicode = utf8_decode(name)[0]
1492
kind = minikind_to_kind[minikind]
1493
inv_entry = factory[kind](file_id, name_unicode,
1495
inv_entry.revision = revid
1497
inv_entry.executable = executable
1498
inv_entry.text_size = size
1499
inv_entry.text_sha1 = fingerprint
1500
elif kind == 'directory':
1501
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1502
elif kind == 'symlink':
1503
inv_entry.executable = False
1504
inv_entry.text_size = None
1505
inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1506
elif kind == 'tree-reference':
1507
inv_entry.reference_revision = fingerprint or None
1509
raise AssertionError("cannot convert entry %r into an InventoryEntry"
1511
# These checks cost us around 40ms on a 55k entry tree
1512
assert file_id not in inv_byid
1513
assert name_unicode not in parent_ie.children
1514
inv_byid[file_id] = inv_entry
1515
parent_ie.children[name_unicode] = inv_entry
1516
self._inventory = inv
1518
def get_file_mtime(self, file_id, path=None):
1519
"""Return the modification time for this record.
1521
We return the timestamp of the last-changed revision.
1523
# Make sure the file exists
1524
entry = self._get_entry(file_id, path=path)
1525
if entry == (None, None): # do we raise?
1527
parent_index = self._get_parent_index()
1528
last_changed_revision = entry[1][parent_index][4]
1529
return self._repository.get_revision(last_changed_revision).timestamp
1531
def get_file_sha1(self, file_id, path=None, stat_value=None):
1532
entry = self._get_entry(file_id=file_id, path=path)
1533
parent_index = self._get_parent_index()
1534
parent_details = entry[1][parent_index]
1535
if parent_details[0] == 'f':
1536
return parent_details[1]
1539
@symbol_versioning.deprecated_method(symbol_versioning.zero_ninety)
1540
def get_weave(self, file_id):
1541
return self._get_weave(file_id)
1543
def _get_weave(self, file_id):
1544
return self._repository.weave_store.get_weave(file_id,
1545
self._repository.get_transaction())
1547
def get_file(self, file_id, path=None):
1548
return StringIO(self.get_file_text(file_id))
1550
def get_file_lines(self, file_id):
1551
entry = self._get_entry(file_id=file_id)[1]
1553
raise errors.NoSuchId(tree=self, file_id=file_id)
1554
return self._get_weave(file_id).get_lines(entry[1][4])
1556
def get_file_size(self, file_id):
1557
return self.inventory[file_id].text_size
1559
def get_file_text(self, file_id):
1560
return ''.join(self.get_file_lines(file_id))
1562
def get_reference_revision(self, file_id, path=None):
1563
return self.inventory[file_id].reference_revision
1565
def iter_files_bytes(self, desired_files):
1566
"""See Tree.iter_files_bytes.
1568
This version is implemented on top of Repository.iter_files_bytes"""
1569
parent_index = self._get_parent_index()
1570
repo_desired_files = []
1571
for file_id, identifier in desired_files:
1572
entry = self._get_entry(file_id)
1573
if entry == (None, None):
1574
raise errors.NoSuchId(self, file_id)
1575
repo_desired_files.append((file_id, entry[1][parent_index][4],
1577
return self._repository.iter_files_bytes(repo_desired_files)
1579
def get_symlink_target(self, file_id):
1580
entry = self._get_entry(file_id=file_id)
1581
parent_index = self._get_parent_index()
1582
if entry[1][parent_index][0] != 'l':
1585
# At present, none of the tree implementations supports non-ascii
1586
# symlink targets. So we will just assume that the dirstate path is
1588
return entry[1][parent_index][1]
1590
def get_revision_id(self):
1591
"""Return the revision id for this tree."""
1592
return self._revision_id
1594
def _get_inventory(self):
1595
if self._inventory is not None:
1596
return self._inventory
1597
self._must_be_locked()
1598
self._generate_inventory()
1599
return self._inventory
1601
inventory = property(_get_inventory,
1602
doc="Inventory of this Tree")
1604
def get_parent_ids(self):
1605
"""The parents of a tree in the dirstate are not cached."""
1606
return self._repository.get_revision(self._revision_id).parent_ids
1608
def has_filename(self, filename):
1609
return bool(self.path2id(filename))
1611
def kind(self, file_id):
1612
entry = self._get_entry(file_id=file_id)[1]
1614
raise errors.NoSuchId(tree=self, file_id=file_id)
1615
return dirstate.DirState._minikind_to_kind[entry[1][0]]
1617
def stored_kind(self, file_id):
1618
"""See Tree.stored_kind"""
1619
return self.kind(file_id)
1621
def path_content_summary(self, path):
1622
"""See Tree.path_content_summary."""
1623
id = self.inventory.path2id(path)
1625
return ('missing', None, None, None)
1626
entry = self._inventory[id]
1629
return (kind, entry.text_size, entry.executable, entry.text_sha1)
1630
elif kind == 'symlink':
1631
return (kind, None, None, entry.symlink_target)
1633
return (kind, None, None, None)
1635
def is_executable(self, file_id, path=None):
1636
ie = self.inventory[file_id]
1637
if ie.kind != "file":
1639
return ie.executable
1641
def list_files(self, include_root=False):
1642
# We use a standard implementation, because DirStateRevisionTree is
1643
# dealing with one of the parents of the current state
1644
inv = self._get_inventory()
1645
entries = inv.iter_entries()
1646
if self.inventory.root is not None and not include_root:
1648
for path, entry in entries:
1649
yield path, 'V', entry.kind, entry.file_id, entry
1651
def lock_read(self):
1652
"""Lock the tree for a set of operations."""
1653
if not self._locked:
1654
self._repository.lock_read()
1655
if self._dirstate._lock_token is None:
1656
self._dirstate.lock_read()
1657
self._dirstate_locked = True
1660
def _must_be_locked(self):
1661
if not self._locked:
1662
raise errors.ObjectNotLocked(self)
1665
def path2id(self, path):
1666
"""Return the id for path in this tree."""
1667
# lookup by path: faster than splitting and walking the ivnentory.
1668
entry = self._get_entry(path=path)
1669
if entry == (None, None):
1674
"""Unlock, freeing any cache memory used during the lock."""
1675
# outside of a lock, the inventory is suspect: release it.
1677
if not self._locked:
1678
self._inventory = None
1680
if self._dirstate_locked:
1681
self._dirstate.unlock()
1682
self._dirstate_locked = False
1683
self._repository.unlock()
1685
def walkdirs(self, prefix=""):
1686
# TODO: jam 20070215 This is the lazy way by using the RevisionTree
1687
# implementation based on an inventory.
1688
# This should be cleaned up to use the much faster Dirstate code
1689
# So for now, we just build up the parent inventory, and extract
1690
# it the same way RevisionTree does.
1691
_directory = 'directory'
1692
inv = self._get_inventory()
1693
top_id = inv.path2id(prefix)
1697
pending = [(prefix, top_id)]
1700
relpath, file_id = pending.pop()
1701
# 0 - relpath, 1- file-id
1703
relroot = relpath + '/'
1706
# FIXME: stash the node in pending
1707
entry = inv[file_id]
1708
for name, child in entry.sorted_children():
1709
toppath = relroot + name
1710
dirblock.append((toppath, name, child.kind, None,
1711
child.file_id, child.kind
1713
yield (relpath, entry.file_id), dirblock
1714
# push the user specified dirs from dirblock
1715
for dir in reversed(dirblock):
1716
if dir[2] == _directory:
1717
pending.append((dir[0], dir[4]))
1720
class InterDirStateTree(InterTree):
1721
"""Fast path optimiser for changes_from with dirstate trees.
1723
This is used only when both trees are in the dirstate working file, and
1724
the source is any parent within the dirstate, and the destination is
1725
the current working tree of the same dirstate.
1727
# this could be generalized to allow comparisons between any trees in the
1728
# dirstate, and possibly between trees stored in different dirstates.
1730
def __init__(self, source, target):
1731
super(InterDirStateTree, self).__init__(source, target)
1732
if not InterDirStateTree.is_compatible(source, target):
1733
raise Exception, "invalid source %r and target %r" % (source, target)
1736
def make_source_parent_tree(source, target):
1737
"""Change the source tree into a parent of the target."""
1738
revid = source.commit('record tree')
1739
target.branch.repository.fetch(source.branch.repository, revid)
1740
target.set_parent_ids([revid])
1741
return target.basis_tree(), target
1743
_matching_from_tree_format = WorkingTreeFormat4()
1744
_matching_to_tree_format = WorkingTreeFormat4()
1745
_test_mutable_trees_to_test_trees = make_source_parent_tree
1747
def _iter_changes(self, include_unchanged=False,
1748
specific_files=None, pb=None, extra_trees=[],
1749
require_versioned=True, want_unversioned=False):
1750
"""Return the changes from source to target.
1752
:return: An iterator that yields tuples. See InterTree._iter_changes
1754
:param specific_files: An optional list of file paths to restrict the
1755
comparison to. When mapping filenames to ids, all matches in all
1756
trees (including optional extra_trees) are used, and all children of
1757
matched directories are included.
1758
:param include_unchanged: An optional boolean requesting the inclusion of
1759
unchanged entries in the result.
1760
:param extra_trees: An optional list of additional trees to use when
1761
mapping the contents of specific_files (paths) to file_ids.
1762
:param require_versioned: If True, all files in specific_files must be
1763
versioned in one of source, target, extra_trees or
1764
PathsNotVersionedError is raised.
1765
:param want_unversioned: Should unversioned files be returned in the
1766
output. An unversioned file is defined as one with (False, False)
1767
for the versioned pair.
1769
utf8_decode = cache_utf8._utf8_decode
1770
_minikind_to_kind = dirstate.DirState._minikind_to_kind
1771
cmp_by_dirs = dirstate.cmp_by_dirs
1772
# NB: show_status depends on being able to pass in non-versioned files
1773
# and report them as unknown
1774
# TODO: handle extra trees in the dirstate.
1775
if (extra_trees or specific_files == []):
1776
# we can't fast-path these cases (yet)
1777
for f in super(InterDirStateTree, self)._iter_changes(
1778
include_unchanged, specific_files, pb, extra_trees,
1779
require_versioned, want_unversioned=want_unversioned):
1782
parent_ids = self.target.get_parent_ids()
1783
assert (self.source._revision_id in parent_ids
1784
or self.source._revision_id == NULL_REVISION), \
1785
"revision {%s} is not stored in {%s}, but %s " \
1786
"can only be used for trees stored in the dirstate" \
1787
% (self.source._revision_id, self.target, self._iter_changes)
1789
if self.source._revision_id == NULL_REVISION:
1791
indices = (target_index,)
1793
assert (self.source._revision_id in parent_ids), \
1794
"Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
1795
self.source._revision_id, parent_ids)
1796
source_index = 1 + parent_ids.index(self.source._revision_id)
1797
indices = (source_index, target_index)
1798
# -- make all specific_files utf8 --
1800
specific_files_utf8 = set()
1801
for path in specific_files:
1802
specific_files_utf8.add(path.encode('utf8'))
1803
specific_files = specific_files_utf8
1805
specific_files = set([''])
1806
# -- specific_files is now a utf8 path set --
1807
# -- get the state object and prepare it.
1808
state = self.target.current_dirstate()
1809
state._read_dirblocks_if_needed()
1810
def _entries_for_path(path):
1811
"""Return a list with all the entries that match path for all ids.
1813
dirname, basename = os.path.split(path)
1814
key = (dirname, basename, '')
1815
block_index, present = state._find_block_index_from_key(key)
1817
# the block which should contain path is absent.
1820
block = state._dirblocks[block_index][1]
1821
entry_index, _ = state._find_entry_index(key, block)
1822
# we may need to look at multiple entries at this path: walk while the specific_files match.
1823
while (entry_index < len(block) and
1824
block[entry_index][0][0:2] == key[0:2]):
1825
result.append(block[entry_index])
1828
if require_versioned:
1829
# -- check all supplied paths are versioned in a search tree. --
1830
all_versioned = True
1831
for path in specific_files:
1832
path_entries = _entries_for_path(path)
1833
if not path_entries:
1834
# this specified path is not present at all: error
1835
all_versioned = False
1837
found_versioned = False
1838
# for each id at this path
1839
for entry in path_entries:
1841
for index in indices:
1842
if entry[1][index][0] != 'a': # absent
1843
found_versioned = True
1844
# all good: found a versioned cell
1846
if not found_versioned:
1847
# none of the indexes was not 'absent' at all ids for this
1849
all_versioned = False
1851
if not all_versioned:
1852
raise errors.PathsNotVersionedError(specific_files)
1853
# -- remove redundancy in supplied specific_files to prevent over-scanning --
1854
search_specific_files = set()
1855
for path in specific_files:
1856
other_specific_files = specific_files.difference(set([path]))
1857
if not osutils.is_inside_any(other_specific_files, path):
1858
# this is a top level path, we must check it.
1859
search_specific_files.add(path)
1861
# compare source_index and target_index at or under each element of search_specific_files.
1862
# follow the following comparison table. Note that we only want to do diff operations when
1863
# the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
1867
# Source | Target | disk | action
1868
# r | fdlt | | add source to search, add id path move and perform
1869
# | | | diff check on source-target
1870
# r | fdlt | a | dangling file that was present in the basis.
1872
# r | a | | add source to search
1874
# r | r | | this path is present in a non-examined tree, skip.
1875
# r | r | a | this path is present in a non-examined tree, skip.
1876
# a | fdlt | | add new id
1877
# a | fdlt | a | dangling locally added file, skip
1878
# a | a | | not present in either tree, skip
1879
# a | a | a | not present in any tree, skip
1880
# a | r | | not present in either tree at this path, skip as it
1881
# | | | may not be selected by the users list of paths.
1882
# a | r | a | not present in either tree at this path, skip as it
1883
# | | | may not be selected by the users list of paths.
1884
# fdlt | fdlt | | content in both: diff them
1885
# fdlt | fdlt | a | deleted locally, but not unversioned - show as deleted ?
1886
# fdlt | a | | unversioned: output deleted id for now
1887
# fdlt | a | a | unversioned and deleted: output deleted id
1888
# fdlt | r | | relocated in this tree, so add target to search.
1889
# | | | Dont diff, we will see an r,fd; pair when we reach
1890
# | | | this id at the other path.
1891
# fdlt | r | a | relocated in this tree, so add target to search.
1892
# | | | Dont diff, we will see an r,fd; pair when we reach
1893
# | | | this id at the other path.
1895
# for all search_indexs in each path at or under each element of
1896
# search_specific_files, if the detail is relocated: add the id, and add the
1897
# relocated path as one to search if its not searched already. If the
1898
# detail is not relocated, add the id.
1899
searched_specific_files = set()
1900
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1901
# Using a list so that we can access the values and change them in
1902
# nested scope. Each one is [path, file_id, entry]
1903
last_source_parent = [None, None]
1904
last_target_parent = [None, None]
1906
use_filesystem_for_exec = (sys.platform != 'win32')
1908
# Just a sentry, so that _process_entry can say that this
1909
# record is handled, but isn't interesting to process (unchanged)
1910
uninteresting = object()
1913
old_dirname_to_file_id = {}
1914
new_dirname_to_file_id = {}
1915
# TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
1916
# keeping a cache of directories that we have seen.
1918
def _process_entry(entry, path_info):
1919
"""Compare an entry and real disk to generate delta information.
1921
:param path_info: top_relpath, basename, kind, lstat, abspath for
1922
the path of entry. If None, then the path is considered absent.
1923
(Perhaps we should pass in a concrete entry for this ?)
1924
Basename is returned as a utf8 string because we expect this
1925
tuple will be ignored, and don't want to take the time to
1927
:return: None if these don't match
1928
A tuple of information about the change, or
1929
the object 'uninteresting' if these match, but are
1930
basically identical.
1932
if source_index is None:
1933
source_details = NULL_PARENT_DETAILS
1935
source_details = entry[1][source_index]
1936
target_details = entry[1][target_index]
1937
target_minikind = target_details[0]
1938
if path_info is not None and target_minikind in 'fdlt':
1939
assert target_index == 0
1940
link_or_sha1 = state.update_entry(entry, abspath=path_info[4],
1941
stat_value=path_info[3])
1942
# The entry may have been modified by update_entry
1943
target_details = entry[1][target_index]
1944
target_minikind = target_details[0]
1947
file_id = entry[0][2]
1948
source_minikind = source_details[0]
1949
if source_minikind in 'fdltr' and target_minikind in 'fdlt':
1950
# claimed content in both: diff
1951
# r | fdlt | | add source to search, add id path move and perform
1952
# | | | diff check on source-target
1953
# r | fdlt | a | dangling file that was present in the basis.
1955
if source_minikind in 'r':
1956
# add the source to the search path to find any children it
1957
# has. TODO ? : only add if it is a container ?
1958
if not osutils.is_inside_any(searched_specific_files,
1960
search_specific_files.add(source_details[1])
1961
# generate the old path; this is needed for stating later
1963
old_path = source_details[1]
1964
old_dirname, old_basename = os.path.split(old_path)
1965
path = pathjoin(entry[0][0], entry[0][1])
1966
old_entry = state._get_entry(source_index,
1968
# update the source details variable to be the real
1970
source_details = old_entry[1][source_index]
1971
source_minikind = source_details[0]
1973
old_dirname = entry[0][0]
1974
old_basename = entry[0][1]
1975
old_path = path = None
1976
if path_info is None:
1977
# the file is missing on disk, show as removed.
1978
content_change = True
1982
# source and target are both versioned and disk file is present.
1983
target_kind = path_info[2]
1984
if target_kind == 'directory':
1986
old_path = path = pathjoin(old_dirname, old_basename)
1987
new_dirname_to_file_id[path] = file_id
1988
if source_minikind != 'd':
1989
content_change = True
1991
# directories have no fingerprint
1992
content_change = False
1994
elif target_kind == 'file':
1995
if source_minikind != 'f':
1996
content_change = True
1998
# We could check the size, but we already have the
2000
content_change = (link_or_sha1 != source_details[1])
2001
# Target details is updated at update_entry time
2002
if use_filesystem_for_exec:
2003
# We don't need S_ISREG here, because we are sure
2004
# we are dealing with a file.
2005
target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
2007
target_exec = target_details[3]
2008
elif target_kind == 'symlink':
2009
if source_minikind != 'l':
2010
content_change = True
2012
content_change = (link_or_sha1 != source_details[1])
2014
elif target_kind == 'tree-reference':
2015
if source_minikind != 't':
2016
content_change = True
2018
content_change = False
2021
raise Exception, "unknown kind %s" % path_info[2]
2022
if source_minikind == 'd':
2024
old_path = path = pathjoin(old_dirname, old_basename)
2025
old_dirname_to_file_id[old_path] = file_id
2026
# parent id is the entry for the path in the target tree
2027
if old_dirname == last_source_parent[0]:
2028
source_parent_id = last_source_parent[1]
2031
source_parent_id = old_dirname_to_file_id[old_dirname]
2033
source_parent_entry = state._get_entry(source_index,
2034
path_utf8=old_dirname)
2035
source_parent_id = source_parent_entry[0][2]
2036
if source_parent_id == entry[0][2]:
2037
# This is the root, so the parent is None
2038
source_parent_id = None
2040
last_source_parent[0] = old_dirname
2041
last_source_parent[1] = source_parent_id
2042
new_dirname = entry[0][0]
2043
if new_dirname == last_target_parent[0]:
2044
target_parent_id = last_target_parent[1]
2047
target_parent_id = new_dirname_to_file_id[new_dirname]
2049
# TODO: We don't always need to do the lookup, because the
2050
# parent entry will be the same as the source entry.
2051
target_parent_entry = state._get_entry(target_index,
2052
path_utf8=new_dirname)
2053
assert target_parent_entry != (None, None), (
2054
"Could not find target parent in wt: %s\nparent of: %s"
2055
% (new_dirname, entry))
2056
target_parent_id = target_parent_entry[0][2]
2057
if target_parent_id == entry[0][2]:
2058
# This is the root, so the parent is None
2059
target_parent_id = None
2061
last_target_parent[0] = new_dirname
2062
last_target_parent[1] = target_parent_id
2064
source_exec = source_details[3]
2065
if (include_unchanged
2067
or source_parent_id != target_parent_id
2068
or old_basename != entry[0][1]
2069
or source_exec != target_exec
2071
if old_path is None:
2072
old_path = path = pathjoin(old_dirname, old_basename)
2073
old_path_u = utf8_decode(old_path)[0]
2076
old_path_u = utf8_decode(old_path)[0]
2077
if old_path == path:
2080
path_u = utf8_decode(path)[0]
2081
source_kind = _minikind_to_kind[source_minikind]
2082
return (entry[0][2],
2083
(old_path_u, path_u),
2086
(source_parent_id, target_parent_id),
2087
(utf8_decode(old_basename)[0], utf8_decode(entry[0][1])[0]),
2088
(source_kind, target_kind),
2089
(source_exec, target_exec))
2091
return uninteresting
2092
elif source_minikind in 'a' and target_minikind in 'fdlt':
2093
# looks like a new file
2094
if path_info is not None:
2095
path = pathjoin(entry[0][0], entry[0][1])
2096
# parent id is the entry for the path in the target tree
2097
# TODO: these are the same for an entire directory: cache em.
2098
parent_id = state._get_entry(target_index,
2099
path_utf8=entry[0][0])[0][2]
2100
if parent_id == entry[0][2]:
2102
if use_filesystem_for_exec:
2103
# We need S_ISREG here, because we aren't sure if this
2106
stat.S_ISREG(path_info[3].st_mode)
2107
and stat.S_IEXEC & path_info[3].st_mode)
2109
target_exec = target_details[3]
2110
return (entry[0][2],
2111
(None, utf8_decode(path)[0]),
2115
(None, utf8_decode(entry[0][1])[0]),
2116
(None, path_info[2]),
2117
(None, target_exec))
2119
# but its not on disk: we deliberately treat this as just
2120
# never-present. (Why ?! - RBC 20070224)
2122
elif source_minikind in 'fdlt' and target_minikind in 'a':
2123
# unversioned, possibly, or possibly not deleted: we dont care.
2124
# if its still on disk, *and* theres no other entry at this
2125
# path [we dont know this in this routine at the moment -
2126
# perhaps we should change this - then it would be an unknown.
2127
old_path = pathjoin(entry[0][0], entry[0][1])
2128
# parent id is the entry for the path in the target tree
2129
parent_id = state._get_entry(source_index, path_utf8=entry[0][0])[0][2]
2130
if parent_id == entry[0][2]:
2132
return (entry[0][2],
2133
(utf8_decode(old_path)[0], None),
2137
(utf8_decode(entry[0][1])[0], None),
2138
(_minikind_to_kind[source_minikind], None),
2139
(source_details[3], None))
2140
elif source_minikind in 'fdlt' and target_minikind in 'r':
2141
# a rename; could be a true rename, or a rename inherited from
2142
# a renamed parent. TODO: handle this efficiently. Its not
2143
# common case to rename dirs though, so a correct but slow
2144
# implementation will do.
2145
if not osutils.is_inside_any(searched_specific_files, target_details[1]):
2146
search_specific_files.add(target_details[1])
2147
elif source_minikind in 'ra' and target_minikind in 'ra':
2148
# neither of the selected trees contain this file,
2149
# so skip over it. This is not currently directly tested, but
2150
# is indirectly via test_too_much.TestCommands.test_conflicts.
2153
raise AssertionError("don't know how to compare "
2154
"source_minikind=%r, target_minikind=%r"
2155
% (source_minikind, target_minikind))
2156
## import pdb;pdb.set_trace()
2159
while search_specific_files:
2160
# TODO: the pending list should be lexically sorted? the
2161
# interface doesn't require it.
2162
current_root = search_specific_files.pop()
2163
current_root_unicode = current_root.decode('utf8')
2164
searched_specific_files.add(current_root)
2165
# process the entries for this containing directory: the rest will be
2166
# found by their parents recursively.
2167
root_entries = _entries_for_path(current_root)
2168
root_abspath = self.target.abspath(current_root_unicode)
2170
root_stat = os.lstat(root_abspath)
2172
if e.errno == errno.ENOENT:
2173
# the path does not exist: let _process_entry know that.
2174
root_dir_info = None
2176
# some other random error: hand it up.
2179
root_dir_info = ('', current_root,
2180
osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
2182
if root_dir_info[2] == 'directory':
2183
if self.target._directory_is_tree_reference(
2184
current_root.decode('utf8')):
2185
root_dir_info = root_dir_info[:2] + \
2186
('tree-reference',) + root_dir_info[3:]
2188
if not root_entries and not root_dir_info:
2189
# this specified path is not present at all, skip it.
2191
path_handled = False
2192
for entry in root_entries:
2193
result = _process_entry(entry, root_dir_info)
2194
if result is not None:
2196
if result is not uninteresting:
2198
if want_unversioned and not path_handled and root_dir_info:
2199
new_executable = bool(
2200
stat.S_ISREG(root_dir_info[3].st_mode)
2201
and stat.S_IEXEC & root_dir_info[3].st_mode)
2203
(None, current_root_unicode),
2207
(None, splitpath(current_root_unicode)[-1]),
2208
(None, root_dir_info[2]),
2209
(None, new_executable)
2211
initial_key = (current_root, '', '')
2212
block_index, _ = state._find_block_index_from_key(initial_key)
2213
if block_index == 0:
2214
# we have processed the total root already, but because the
2215
# initial key matched it we should skip it here.
2217
if root_dir_info and root_dir_info[2] == 'tree-reference':
2218
current_dir_info = None
2220
dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
2222
current_dir_info = dir_iterator.next()
2224
# on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
2225
# python 2.5 has e.errno == EINVAL,
2226
# and e.winerror == ERROR_DIRECTORY
2227
e_winerror = getattr(e, 'winerror', None)
2228
win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
2229
# there may be directories in the inventory even though
2230
# this path is not a file on disk: so mark it as end of
2232
if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
2233
current_dir_info = None
2234
elif (sys.platform == 'win32'
2235
and (e.errno in win_errors
2236
or e_winerror in win_errors)):
2237
current_dir_info = None
2241
if current_dir_info[0][0] == '':
2242
# remove .bzr from iteration
2243
bzr_index = bisect_left(current_dir_info[1], ('.bzr',))
2244
assert current_dir_info[1][bzr_index][0] == '.bzr'
2245
del current_dir_info[1][bzr_index]
2246
# walk until both the directory listing and the versioned metadata
2248
if (block_index < len(state._dirblocks) and
2249
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
2250
current_block = state._dirblocks[block_index]
2252
current_block = None
2253
while (current_dir_info is not None or
2254
current_block is not None):
2255
if (current_dir_info and current_block
2256
and current_dir_info[0][0] != current_block[0]):
2257
if cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
2258
# filesystem data refers to paths not covered by the dirblock.
2259
# this has two possibilities:
2260
# A) it is versioned but empty, so there is no block for it
2261
# B) it is not versioned.
2263
# if (A) then we need to recurse into it to check for
2264
# new unknown files or directories.
2265
# if (B) then we should ignore it, because we don't
2266
# recurse into unknown directories.
2268
while path_index < len(current_dir_info[1]):
2269
current_path_info = current_dir_info[1][path_index]
2270
if want_unversioned:
2271
if current_path_info[2] == 'directory':
2272
if self.target._directory_is_tree_reference(
2273
current_path_info[0].decode('utf8')):
2274
current_path_info = current_path_info[:2] + \
2275
('tree-reference',) + current_path_info[3:]
2276
new_executable = bool(
2277
stat.S_ISREG(current_path_info[3].st_mode)
2278
and stat.S_IEXEC & current_path_info[3].st_mode)
2280
(None, utf8_decode(current_path_info[0])[0]),
2284
(None, utf8_decode(current_path_info[1])[0]),
2285
(None, current_path_info[2]),
2286
(None, new_executable))
2287
# dont descend into this unversioned path if it is
2289
if current_path_info[2] in ('directory',
2291
del current_dir_info[1][path_index]
2295
# This dir info has been handled, go to the next
2297
current_dir_info = dir_iterator.next()
2298
except StopIteration:
2299
current_dir_info = None
2301
# We have a dirblock entry for this location, but there
2302
# is no filesystem path for this. This is most likely
2303
# because a directory was removed from the disk.
2304
# We don't have to report the missing directory,
2305
# because that should have already been handled, but we
2306
# need to handle all of the files that are contained
2308
for current_entry in current_block[1]:
2309
# entry referring to file not present on disk.
2310
# advance the entry only, after processing.
2311
result = _process_entry(current_entry, None)
2312
if result is not None:
2313
if result is not uninteresting:
2316
if (block_index < len(state._dirblocks) and
2317
osutils.is_inside(current_root,
2318
state._dirblocks[block_index][0])):
2319
current_block = state._dirblocks[block_index]
2321
current_block = None
2324
if current_block and entry_index < len(current_block[1]):
2325
current_entry = current_block[1][entry_index]
2327
current_entry = None
2328
advance_entry = True
2330
if current_dir_info and path_index < len(current_dir_info[1]):
2331
current_path_info = current_dir_info[1][path_index]
2332
if current_path_info[2] == 'directory':
2333
if self.target._directory_is_tree_reference(
2334
current_path_info[0].decode('utf8')):
2335
current_path_info = current_path_info[:2] + \
2336
('tree-reference',) + current_path_info[3:]
2338
current_path_info = None
2340
path_handled = False
2341
while (current_entry is not None or
2342
current_path_info is not None):
2343
if current_entry is None:
2344
# the check for path_handled when the path is adnvaced
2345
# will yield this path if needed.
2347
elif current_path_info is None:
2348
# no path is fine: the per entry code will handle it.
2349
result = _process_entry(current_entry, current_path_info)
2350
if result is not None:
2351
if result is not uninteresting:
2353
elif (current_entry[0][1] != current_path_info[1]
2354
or current_entry[1][target_index][0] in 'ar'):
2355
# The current path on disk doesn't match the dirblock
2356
# record. Either the dirblock is marked as absent, or
2357
# the file on disk is not present at all in the
2358
# dirblock. Either way, report about the dirblock
2359
# entry, and let other code handle the filesystem one.
2361
# Compare the basename for these files to determine
2363
if current_path_info[1] < current_entry[0][1]:
2364
# extra file on disk: pass for now, but only
2365
# increment the path, not the entry
2366
advance_entry = False
2368
# entry referring to file not present on disk.
2369
# advance the entry only, after processing.
2370
result = _process_entry(current_entry, None)
2371
if result is not None:
2372
if result is not uninteresting:
2374
advance_path = False
2376
result = _process_entry(current_entry, current_path_info)
2377
if result is not None:
2379
if result is not uninteresting:
2381
if advance_entry and current_entry is not None:
2383
if entry_index < len(current_block[1]):
2384
current_entry = current_block[1][entry_index]
2386
current_entry = None
2388
advance_entry = True # reset the advance flaga
2389
if advance_path and current_path_info is not None:
2390
if not path_handled:
2391
# unversioned in all regards
2392
if want_unversioned:
2393
new_executable = bool(
2394
stat.S_ISREG(current_path_info[3].st_mode)
2395
and stat.S_IEXEC & current_path_info[3].st_mode)
2397
(None, utf8_decode(current_path_info[0])[0]),
2401
(None, utf8_decode(current_path_info[1])[0]),
2402
(None, current_path_info[2]),
2403
(None, new_executable))
2404
# dont descend into this unversioned path if it is
2406
if current_path_info[2] in ('directory'):
2407
del current_dir_info[1][path_index]
2409
# dont descend the disk iterator into any tree
2411
if current_path_info[2] == 'tree-reference':
2412
del current_dir_info[1][path_index]
2415
if path_index < len(current_dir_info[1]):
2416
current_path_info = current_dir_info[1][path_index]
2417
if current_path_info[2] == 'directory':
2418
if self.target._directory_is_tree_reference(
2419
current_path_info[0].decode('utf8')):
2420
current_path_info = current_path_info[:2] + \
2421
('tree-reference',) + current_path_info[3:]
2423
current_path_info = None
2424
path_handled = False
2426
advance_path = True # reset the advance flagg.
2427
if current_block is not None:
2429
if (block_index < len(state._dirblocks) and
2430
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
2431
current_block = state._dirblocks[block_index]
2433
current_block = None
2434
if current_dir_info is not None:
2436
current_dir_info = dir_iterator.next()
2437
except StopIteration:
2438
current_dir_info = None
2442
def is_compatible(source, target):
2443
# the target must be a dirstate working tree
2444
if not isinstance(target, WorkingTree4):
2446
# the source must be a revtreee or dirstate rev tree.
2447
if not isinstance(source,
2448
(revisiontree.RevisionTree, DirStateRevisionTree)):
2450
# the source revid must be in the target dirstate
2451
if not (source._revision_id == NULL_REVISION or
2452
source._revision_id in target.get_parent_ids()):
2453
# TODO: what about ghosts? it may well need to
2454
# check for them explicitly.
2458
InterTree.register_optimiser(InterDirStateTree)
2461
class Converter3to4(object):
2462
"""Perform an in-place upgrade of format 3 to format 4 trees."""
2465
self.target_format = WorkingTreeFormat4()
2467
def convert(self, tree):
2468
# lock the control files not the tree, so that we dont get tree
2469
# on-unlock behaviours, and so that noone else diddles with the
2470
# tree during upgrade.
2471
tree._control_files.lock_write()
2473
tree.read_working_inventory()
2474
self.create_dirstate_data(tree)
2475
self.update_format(tree)
2476
self.remove_xml_files(tree)
2478
tree._control_files.unlock()
2480
def create_dirstate_data(self, tree):
2481
"""Create the dirstate based data for tree."""
2482
local_path = tree.bzrdir.get_workingtree_transport(None
2483
).local_abspath('dirstate')
2484
state = dirstate.DirState.from_tree(tree, local_path)
2488
def remove_xml_files(self, tree):
2489
"""Remove the oldformat 3 data."""
2490
transport = tree.bzrdir.get_workingtree_transport(None)
2491
for path in ['basis-inventory-cache', 'inventory', 'last-revision',
2492
'pending-merges', 'stat-cache']:
2494
transport.delete(path)
2495
except errors.NoSuchFile:
2496
# some files are optional - just deal.
2499
def update_format(self, tree):
2500
"""Change the format marker."""
2501
tree._control_files.put_utf8('format',
2502
self.target_format.get_format_string())