1
# Copyright (C) 2005, 2006, 2007, 2008 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
class WorkingTree4(WorkingTree3):
102
"""This is the Format 4 working tree.
104
This differs from WorkingTree3 by:
105
- Having a consolidated internal dirstate, stored in a
106
randomly-accessible sorted file on disk.
107
- Not having a regular inventory attribute. One can be synthesized
108
on demand but this is expensive and should be avoided.
110
This is new in bzr 0.15.
113
def __init__(self, basedir,
118
"""Construct a WorkingTree for basedir.
120
If the branch is not supplied, it is opened automatically.
121
If the branch is supplied, it must be the branch for this basedir.
122
(branch.base is not cross checked, because for remote branches that
123
would be meaningless).
125
self._format = _format
126
self.bzrdir = _bzrdir
127
basedir = safe_unicode(basedir)
128
mutter("opening working tree %r", basedir)
129
self._branch = branch
130
self.basedir = realpath(basedir)
131
# if branch is at our basedir and is a format 6 or less
132
# assume all other formats have their own control files.
133
self._control_files = _control_files
134
self._transport = self._control_files._transport
137
# during a read or write lock these objects are set, and are
138
# None the rest of the time.
139
self._dirstate = None
140
self._inventory = None
142
self._setup_directory_is_tree_reference()
143
self._detect_case_handling()
144
self._rules_searcher = None
145
#--- allow tests to select the dirstate iter_changes implementation
146
self._iter_changes = dirstate._process_entry
148
@needs_tree_write_lock
149
def _add(self, files, ids, kinds):
150
"""See MutableTree._add."""
151
state = self.current_dirstate()
152
for f, file_id, kind in zip(files, ids, kinds):
155
# special case tree root handling.
156
if f == '' and self.path2id(f) == ROOT_ID:
157
state.set_path_id('', generate_ids.gen_file_id(f))
160
file_id = generate_ids.gen_file_id(f)
161
# deliberately add the file with no cached stat or sha1
162
# - on the first access it will be gathered, and we can
163
# always change this once tests are all passing.
164
state.add(f, file_id, kind, None, '')
165
self._make_dirty(reset_inventory=True)
167
def _make_dirty(self, reset_inventory):
168
"""Make the tree state dirty.
170
:param reset_inventory: True if the cached inventory should be removed
171
(presuming there is one).
174
if reset_inventory and self._inventory is not None:
175
self._inventory = None
177
@needs_tree_write_lock
178
def add_reference(self, sub_tree):
179
# use standard implementation, which calls back to self._add
181
# So we don't store the reference_revision in the working dirstate,
182
# it's just recorded at the moment of commit.
183
self._add_reference(sub_tree)
185
def break_lock(self):
186
"""Break a lock if one is present from another instance.
188
Uses the ui factory to ask for confirmation if the lock may be from
191
This will probe the repository for its lock as well.
193
# if the dirstate is locked by an active process, reject the break lock
196
if self._dirstate is None:
200
state = self._current_dirstate()
201
if state._lock_token is not None:
202
# we already have it locked. sheese, cant break our own lock.
203
raise errors.LockActive(self.basedir)
206
# try for a write lock - need permission to get one anyhow
209
except errors.LockContention:
210
# oslocks fail when a process is still live: fail.
211
# TODO: get the locked lockdir info and give to the user to
212
# assist in debugging.
213
raise errors.LockActive(self.basedir)
218
self._dirstate = None
219
self._control_files.break_lock()
220
self.branch.break_lock()
222
def _comparison_data(self, entry, path):
223
kind, executable, stat_value = \
224
WorkingTree3._comparison_data(self, entry, path)
225
# it looks like a plain directory, but it's really a reference -- see
227
if (self._repo_supports_tree_reference and
228
kind == 'directory' and
229
self._directory_is_tree_reference(path)):
230
kind = 'tree-reference'
231
return kind, executable, stat_value
234
def commit(self, message=None, revprops=None, *args, **kwargs):
235
# mark the tree as dirty post commit - commit
236
# can change the current versioned list by doing deletes.
237
result = WorkingTree3.commit(self, message, revprops, *args, **kwargs)
238
self._make_dirty(reset_inventory=True)
241
def current_dirstate(self):
242
"""Return the current dirstate object.
244
This is not part of the tree interface and only exposed for ease of
247
:raises errors.NotWriteLocked: when not in a lock.
249
self._must_be_locked()
250
return self._current_dirstate()
252
def _current_dirstate(self):
253
"""Internal function that does not check lock status.
255
This is needed for break_lock which also needs the dirstate.
257
if self._dirstate is not None:
258
return self._dirstate
259
local_path = self.bzrdir.get_workingtree_transport(None
260
).local_abspath('dirstate')
261
self._dirstate = dirstate.DirState.on_file(local_path)
262
return self._dirstate
264
def filter_unversioned_files(self, paths):
265
"""Filter out paths that are versioned.
267
:return: set of paths.
269
# TODO: make a generic multi-bisect routine roughly that should list
270
# the paths, then process one half at a time recursively, and feed the
271
# results of each bisect in further still
272
paths = sorted(paths)
274
state = self.current_dirstate()
275
# TODO we want a paths_to_dirblocks helper I think
277
dirname, basename = os.path.split(path.encode('utf8'))
278
_, _, _, path_is_versioned = state._get_block_entry_index(
279
dirname, basename, 0)
280
if not path_is_versioned:
285
"""Write all cached data to disk."""
286
if self._control_files._lock_mode != 'w':
287
raise errors.NotWriteLocked(self)
288
self.current_dirstate().save()
289
self._inventory = None
292
@needs_tree_write_lock
293
def _gather_kinds(self, files, kinds):
294
"""See MutableTree._gather_kinds."""
295
for pos, f in enumerate(files):
296
if kinds[pos] is None:
297
kinds[pos] = self._kind(f)
299
def _generate_inventory(self):
300
"""Create and set self.inventory from the dirstate object.
302
This is relatively expensive: we have to walk the entire dirstate.
303
Ideally we would not, and can deprecate this function.
305
#: uncomment to trap on inventory requests.
306
# import pdb;pdb.set_trace()
307
state = self.current_dirstate()
308
state._read_dirblocks_if_needed()
309
root_key, current_entry = self._get_entry(path='')
310
current_id = root_key[2]
311
if not (current_entry[0][0] == 'd'): # directory
312
raise AssertionError(current_entry)
313
inv = Inventory(root_id=current_id)
314
# Turn some things into local variables
315
minikind_to_kind = dirstate.DirState._minikind_to_kind
316
factory = entry_factory
317
utf8_decode = cache_utf8._utf8_decode
319
# we could do this straight out of the dirstate; it might be fast
320
# and should be profiled - RBC 20070216
321
parent_ies = {'' : inv.root}
322
for block in state._dirblocks[1:]: # skip the root
325
parent_ie = parent_ies[dirname]
327
# all the paths in this block are not versioned in this tree
329
for key, entry in block[1]:
330
minikind, link_or_sha1, size, executable, stat = entry[0]
331
if minikind in ('a', 'r'): # absent, relocated
332
# a parent tree only entry
335
name_unicode = utf8_decode(name)[0]
337
kind = minikind_to_kind[minikind]
338
inv_entry = factory[kind](file_id, name_unicode,
341
# This is only needed on win32, where this is the only way
342
# we know the executable bit.
343
inv_entry.executable = executable
344
# not strictly needed: working tree
345
#inv_entry.text_size = size
346
#inv_entry.text_sha1 = sha1
347
elif kind == 'directory':
348
# add this entry to the parent map.
349
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
350
elif kind == 'tree-reference':
351
if not self._repo_supports_tree_reference:
352
raise AssertionError(
354
"doesn't support tree references "
355
"required by entry %r"
357
inv_entry.reference_revision = link_or_sha1 or None
358
elif kind != 'symlink':
359
raise AssertionError("unknown kind %r" % kind)
360
# These checks cost us around 40ms on a 55k entry tree
361
if file_id in inv_byid:
362
raise AssertionError('file_id %s already in'
363
' inventory as %s' % (file_id, inv_byid[file_id]))
364
if name_unicode in parent_ie.children:
365
raise AssertionError('name %r already in parent'
367
inv_byid[file_id] = inv_entry
368
parent_ie.children[name_unicode] = inv_entry
369
self._inventory = inv
371
def _get_entry(self, file_id=None, path=None):
372
"""Get the dirstate row for file_id or path.
374
If either file_id or path is supplied, it is used as the key to lookup.
375
If both are supplied, the fastest lookup is used, and an error is
376
raised if they do not both point at the same row.
378
:param file_id: An optional unicode file_id to be looked up.
379
:param path: An optional unicode path to be looked up.
380
:return: The dirstate row tuple for path/file_id, or (None, None)
382
if file_id is None and path is None:
383
raise errors.BzrError('must supply file_id or path')
384
state = self.current_dirstate()
386
path = path.encode('utf8')
387
return state._get_entry(0, fileid_utf8=file_id, path_utf8=path)
389
def get_file_sha1(self, file_id, path=None, stat_value=None):
390
# check file id is valid unconditionally.
391
entry = self._get_entry(file_id=file_id, path=path)
393
raise errors.NoSuchId(self, file_id)
395
path = pathjoin(entry[0][0], entry[0][1]).decode('utf8')
397
file_abspath = self.abspath(path)
398
state = self.current_dirstate()
399
if stat_value is None:
401
stat_value = os.lstat(file_abspath)
403
if e.errno == errno.ENOENT:
407
link_or_sha1 = dirstate.update_entry(state, entry, file_abspath,
408
stat_value=stat_value)
409
if entry[1][0][0] == 'f':
410
if link_or_sha1 is None:
411
file_obj, statvalue = self.get_file_with_stat(file_id, path)
413
sha1 = osutils.sha_file(file_obj)
416
self._observed_sha1(file_id, path, (sha1, statvalue))
422
def _get_inventory(self):
423
"""Get the inventory for the tree. This is only valid within a lock."""
424
if 'evil' in debug.debug_flags:
425
trace.mutter_callsite(2,
426
"accessing .inventory forces a size of tree translation.")
427
if self._inventory is not None:
428
return self._inventory
429
self._must_be_locked()
430
self._generate_inventory()
431
return self._inventory
433
inventory = property(_get_inventory,
434
doc="Inventory of this Tree")
437
def get_parent_ids(self):
438
"""See Tree.get_parent_ids.
440
This implementation requests the ids list from the dirstate file.
442
return self.current_dirstate().get_parent_ids()
444
def get_reference_revision(self, file_id, path=None):
445
# referenced tree's revision is whatever's currently there
446
return self.get_nested_tree(file_id, path).last_revision()
448
def get_nested_tree(self, file_id, path=None):
450
path = self.id2path(file_id)
451
# else: check file_id is at path?
452
return WorkingTree.open(self.abspath(path))
455
def get_root_id(self):
456
"""Return the id of this trees root"""
457
return self._get_entry(path='')[0][2]
459
def has_id(self, file_id):
460
state = self.current_dirstate()
461
row, parents = self._get_entry(file_id=file_id)
464
return osutils.lexists(pathjoin(
465
self.basedir, row[0].decode('utf8'), row[1].decode('utf8')))
468
def id2path(self, file_id):
469
"Convert a file-id to a path."
470
state = self.current_dirstate()
471
entry = self._get_entry(file_id=file_id)
472
if entry == (None, None):
473
raise errors.NoSuchId(tree=self, file_id=file_id)
474
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
475
return path_utf8.decode('utf8')
477
def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
478
entry = self._get_entry(path=path)
479
if entry == (None, None):
480
return False # Missing entries are not executable
481
return entry[1][0][3] # Executable?
483
if not osutils.supports_executable():
484
def is_executable(self, file_id, path=None):
485
"""Test if a file is executable or not.
487
Note: The caller is expected to take a read-lock before calling this.
489
entry = self._get_entry(file_id=file_id, path=path)
490
if entry == (None, None):
492
return entry[1][0][3]
494
_is_executable_from_path_and_stat = \
495
_is_executable_from_path_and_stat_from_basis
497
def is_executable(self, file_id, path=None):
498
"""Test if a file is executable or not.
500
Note: The caller is expected to take a read-lock before calling this.
502
self._must_be_locked()
504
path = self.id2path(file_id)
505
mode = os.lstat(self.abspath(path)).st_mode
506
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
508
def all_file_ids(self):
509
"""See Tree.iter_all_file_ids"""
510
self._must_be_locked()
512
for key, tree_details in self.current_dirstate()._iter_entries():
513
if tree_details[0][0] in ('a', 'r'): # relocated
520
"""Iterate through file_ids for this tree.
522
file_ids are in a WorkingTree if they are in the working inventory
523
and the working file exists.
526
for key, tree_details in self.current_dirstate()._iter_entries():
527
if tree_details[0][0] in ('a', 'r'): # absent, relocated
528
# not relevant to the working tree
530
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
531
if osutils.lexists(path):
532
result.append(key[2])
535
def iter_references(self):
536
if not self._repo_supports_tree_reference:
537
# When the repo doesn't support references, we will have nothing to
540
for key, tree_details in self.current_dirstate()._iter_entries():
541
if tree_details[0][0] in ('a', 'r'): # absent, relocated
542
# not relevant to the working tree
545
# the root is not a reference.
547
relpath = pathjoin(key[0].decode('utf8'), key[1].decode('utf8'))
549
if self._kind(relpath) == 'tree-reference':
550
yield relpath, key[2]
551
except errors.NoSuchFile:
552
# path is missing on disk.
555
def _observed_sha1(self, file_id, path, (sha1, statvalue)):
556
"""See MutableTree._observed_sha1."""
557
state = self.current_dirstate()
558
entry = self._get_entry(file_id=file_id, path=path)
559
state._observed_sha1(entry, sha1, statvalue)
561
def kind(self, file_id):
562
"""Return the kind of a file.
564
This is always the actual kind that's on disk, regardless of what it
567
Note: The caller is expected to take a read-lock before calling this.
569
relpath = self.id2path(file_id)
571
raise AssertionError(
572
"path for id {%s} is None!" % file_id)
573
return self._kind(relpath)
575
def _kind(self, relpath):
576
abspath = self.abspath(relpath)
577
kind = file_kind(abspath)
578
if (self._repo_supports_tree_reference and
579
kind == 'directory' and
580
self._directory_is_tree_reference(relpath)):
581
kind = 'tree-reference'
585
def _last_revision(self):
586
"""See Mutable.last_revision."""
587
parent_ids = self.current_dirstate().get_parent_ids()
591
return _mod_revision.NULL_REVISION
594
"""See Branch.lock_read, and WorkingTree.unlock."""
595
self.branch.lock_read()
597
self._control_files.lock_read()
599
state = self.current_dirstate()
600
if not state._lock_token:
602
# set our support for tree references from the repository in
604
self._repo_supports_tree_reference = getattr(
605
self.branch.repository._format, "supports_tree_reference",
608
self._control_files.unlock()
614
def _lock_self_write(self):
615
"""This should be called after the branch is locked."""
617
self._control_files.lock_write()
619
state = self.current_dirstate()
620
if not state._lock_token:
622
# set our support for tree references from the repository in
624
self._repo_supports_tree_reference = getattr(
625
self.branch.repository._format, "supports_tree_reference",
628
self._control_files.unlock()
634
def lock_tree_write(self):
635
"""See MutableTree.lock_tree_write, and WorkingTree.unlock."""
636
self.branch.lock_read()
637
self._lock_self_write()
639
def lock_write(self):
640
"""See MutableTree.lock_write, and WorkingTree.unlock."""
641
self.branch.lock_write()
642
self._lock_self_write()
644
@needs_tree_write_lock
645
def move(self, from_paths, to_dir, after=False):
646
"""See WorkingTree.move()."""
650
state = self.current_dirstate()
651
if isinstance(from_paths, basestring):
653
to_dir_utf8 = to_dir.encode('utf8')
654
to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
655
id_index = state._get_id_index()
656
# check destination directory
657
# get the details for it
658
to_entry_block_index, to_entry_entry_index, dir_present, entry_present = \
659
state._get_block_entry_index(to_entry_dirname, to_basename, 0)
660
if not entry_present:
661
raise errors.BzrMoveFailedError('', to_dir,
662
errors.NotVersionedError(to_dir))
663
to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
664
# get a handle on the block itself.
665
to_block_index = state._ensure_block(
666
to_entry_block_index, to_entry_entry_index, to_dir_utf8)
667
to_block = state._dirblocks[to_block_index]
668
to_abs = self.abspath(to_dir)
669
if not isdir(to_abs):
670
raise errors.BzrMoveFailedError('',to_dir,
671
errors.NotADirectory(to_abs))
673
if to_entry[1][0][0] != 'd':
674
raise errors.BzrMoveFailedError('',to_dir,
675
errors.NotADirectory(to_abs))
677
if self._inventory is not None:
678
update_inventory = True
680
to_dir_id = to_entry[0][2]
681
to_dir_ie = inv[to_dir_id]
683
update_inventory = False
686
def move_one(old_entry, from_path_utf8, minikind, executable,
687
fingerprint, packed_stat, size,
688
to_block, to_key, to_path_utf8):
689
state._make_absent(old_entry)
690
from_key = old_entry[0]
692
lambda:state.update_minimal(from_key,
694
executable=executable,
695
fingerprint=fingerprint,
696
packed_stat=packed_stat,
698
path_utf8=from_path_utf8))
699
state.update_minimal(to_key,
701
executable=executable,
702
fingerprint=fingerprint,
703
packed_stat=packed_stat,
705
path_utf8=to_path_utf8)
706
added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
707
new_entry = to_block[1][added_entry_index]
708
rollbacks.append(lambda:state._make_absent(new_entry))
710
for from_rel in from_paths:
711
# from_rel is 'pathinroot/foo/bar'
712
from_rel_utf8 = from_rel.encode('utf8')
713
from_dirname, from_tail = osutils.split(from_rel)
714
from_dirname, from_tail_utf8 = osutils.split(from_rel_utf8)
715
from_entry = self._get_entry(path=from_rel)
716
if from_entry == (None, None):
717
raise errors.BzrMoveFailedError(from_rel,to_dir,
718
errors.NotVersionedError(path=str(from_rel)))
720
from_id = from_entry[0][2]
721
to_rel = pathjoin(to_dir, from_tail)
722
to_rel_utf8 = pathjoin(to_dir_utf8, from_tail_utf8)
723
item_to_entry = self._get_entry(path=to_rel)
724
if item_to_entry != (None, None):
725
raise errors.BzrMoveFailedError(from_rel, to_rel,
726
"Target is already versioned.")
728
if from_rel == to_rel:
729
raise errors.BzrMoveFailedError(from_rel, to_rel,
730
"Source and target are identical.")
732
from_missing = not self.has_filename(from_rel)
733
to_missing = not self.has_filename(to_rel)
740
raise errors.BzrMoveFailedError(from_rel, to_rel,
741
errors.NoSuchFile(path=to_rel,
742
extra="New file has not been created yet"))
744
# neither path exists
745
raise errors.BzrRenameFailedError(from_rel, to_rel,
746
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
748
if from_missing: # implicitly just update our path mapping
751
raise errors.RenameFailedFilesExist(from_rel, to_rel)
754
def rollback_rename():
755
"""A single rename has failed, roll it back."""
756
# roll back everything, even if we encounter trouble doing one
759
# TODO: at least log the other exceptions rather than just
760
# losing them mbp 20070307
762
for rollback in reversed(rollbacks):
766
exc_info = sys.exc_info()
768
raise exc_info[0], exc_info[1], exc_info[2]
770
# perform the disk move first - its the most likely failure point.
772
from_rel_abs = self.abspath(from_rel)
773
to_rel_abs = self.abspath(to_rel)
775
osutils.rename(from_rel_abs, to_rel_abs)
777
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
778
rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
780
# perform the rename in the inventory next if needed: its easy
784
from_entry = inv[from_id]
785
current_parent = from_entry.parent_id
786
inv.rename(from_id, to_dir_id, from_tail)
788
lambda: inv.rename(from_id, current_parent, from_tail))
789
# finally do the rename in the dirstate, which is a little
790
# tricky to rollback, but least likely to need it.
791
old_block_index, old_entry_index, dir_present, file_present = \
792
state._get_block_entry_index(from_dirname, from_tail_utf8, 0)
793
old_block = state._dirblocks[old_block_index][1]
794
old_entry = old_block[old_entry_index]
795
from_key, old_entry_details = old_entry
796
cur_details = old_entry_details[0]
798
to_key = ((to_block[0],) + from_key[1:3])
799
minikind = cur_details[0]
800
move_one(old_entry, from_path_utf8=from_rel_utf8,
802
executable=cur_details[3],
803
fingerprint=cur_details[1],
804
packed_stat=cur_details[4],
808
to_path_utf8=to_rel_utf8)
811
def update_dirblock(from_dir, to_key, to_dir_utf8):
812
"""Recursively update all entries in this dirblock."""
814
raise AssertionError("renaming root not supported")
815
from_key = (from_dir, '')
816
from_block_idx, present = \
817
state._find_block_index_from_key(from_key)
819
# This is the old record, if it isn't present, then
820
# there is theoretically nothing to update.
821
# (Unless it isn't present because of lazy loading,
822
# but we don't do that yet)
824
from_block = state._dirblocks[from_block_idx]
825
to_block_index, to_entry_index, _, _ = \
826
state._get_block_entry_index(to_key[0], to_key[1], 0)
827
to_block_index = state._ensure_block(
828
to_block_index, to_entry_index, to_dir_utf8)
829
to_block = state._dirblocks[to_block_index]
831
# Grab a copy since move_one may update the list.
832
for entry in from_block[1][:]:
833
if not (entry[0][0] == from_dir):
834
raise AssertionError()
835
cur_details = entry[1][0]
836
to_key = (to_dir_utf8, entry[0][1], entry[0][2])
837
from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
838
to_path_utf8 = osutils.pathjoin(to_dir_utf8, entry[0][1])
839
minikind = cur_details[0]
841
# Deleted children of a renamed directory
842
# Do not need to be updated.
843
# Children that have been renamed out of this
844
# directory should also not be updated
846
move_one(entry, from_path_utf8=from_path_utf8,
848
executable=cur_details[3],
849
fingerprint=cur_details[1],
850
packed_stat=cur_details[4],
854
to_path_utf8=to_path_utf8)
856
# We need to move all the children of this
858
update_dirblock(from_path_utf8, to_key,
860
update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
864
result.append((from_rel, to_rel))
865
state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
866
self._make_dirty(reset_inventory=False)
870
def _must_be_locked(self):
871
if not self._control_files._lock_count:
872
raise errors.ObjectNotLocked(self)
875
"""Initialize the state in this tree to be a new tree."""
879
def path2id(self, path):
880
"""Return the id for path in this tree."""
881
path = path.strip('/')
882
entry = self._get_entry(path=path)
883
if entry == (None, None):
887
def paths2ids(self, paths, trees=[], require_versioned=True):
888
"""See Tree.paths2ids().
890
This specialisation fast-paths the case where all the trees are in the
895
parents = self.get_parent_ids()
897
if not (isinstance(tree, DirStateRevisionTree) and tree._revision_id in
899
return super(WorkingTree4, self).paths2ids(paths, trees, require_versioned)
900
search_indexes = [0] + [1 + parents.index(tree._revision_id) for tree in trees]
901
# -- make all paths utf8 --
904
paths_utf8.add(path.encode('utf8'))
906
# -- paths is now a utf8 path set --
907
# -- get the state object and prepare it.
908
state = self.current_dirstate()
909
if False and (state._dirblock_state == dirstate.DirState.NOT_IN_MEMORY
910
and '' not in paths):
911
paths2ids = self._paths2ids_using_bisect
913
paths2ids = self._paths2ids_in_memory
914
return paths2ids(paths, search_indexes,
915
require_versioned=require_versioned)
917
def _paths2ids_in_memory(self, paths, search_indexes,
918
require_versioned=True):
919
state = self.current_dirstate()
920
state._read_dirblocks_if_needed()
921
def _entries_for_path(path):
922
"""Return a list with all the entries that match path for all ids.
924
dirname, basename = os.path.split(path)
925
key = (dirname, basename, '')
926
block_index, present = state._find_block_index_from_key(key)
928
# the block which should contain path is absent.
931
block = state._dirblocks[block_index][1]
932
entry_index, _ = state._find_entry_index(key, block)
933
# we may need to look at multiple entries at this path: walk while the paths match.
934
while (entry_index < len(block) and
935
block[entry_index][0][0:2] == key[0:2]):
936
result.append(block[entry_index])
939
if require_versioned:
940
# -- check all supplied paths are versioned in a search tree. --
943
path_entries = _entries_for_path(path)
945
# this specified path is not present at all: error
946
all_versioned = False
948
found_versioned = False
949
# for each id at this path
950
for entry in path_entries:
952
for index in search_indexes:
953
if entry[1][index][0] != 'a': # absent
954
found_versioned = True
955
# all good: found a versioned cell
957
if not found_versioned:
958
# none of the indexes was not 'absent' at all ids for this
960
all_versioned = False
962
if not all_versioned:
963
raise errors.PathsNotVersionedError(paths)
964
# -- remove redundancy in supplied paths to prevent over-scanning --
965
search_paths = osutils.minimum_path_selection(paths)
967
# for all search_indexs in each path at or under each element of
968
# search_paths, if the detail is relocated: add the id, and add the
969
# relocated path as one to search if its not searched already. If the
970
# detail is not relocated, add the id.
971
searched_paths = set()
973
def _process_entry(entry):
974
"""Look at search_indexes within entry.
976
If a specific tree's details are relocated, add the relocation
977
target to search_paths if not searched already. If it is absent, do
978
nothing. Otherwise add the id to found_ids.
980
for index in search_indexes:
981
if entry[1][index][0] == 'r': # relocated
982
if not osutils.is_inside_any(searched_paths, entry[1][index][1]):
983
search_paths.add(entry[1][index][1])
984
elif entry[1][index][0] != 'a': # absent
985
found_ids.add(entry[0][2])
987
current_root = search_paths.pop()
988
searched_paths.add(current_root)
989
# process the entries for this containing directory: the rest will be
990
# found by their parents recursively.
991
root_entries = _entries_for_path(current_root)
993
# this specified path is not present at all, skip it.
995
for entry in root_entries:
996
_process_entry(entry)
997
initial_key = (current_root, '', '')
998
block_index, _ = state._find_block_index_from_key(initial_key)
999
while (block_index < len(state._dirblocks) and
1000
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
1001
for entry in state._dirblocks[block_index][1]:
1002
_process_entry(entry)
1006
def _paths2ids_using_bisect(self, paths, search_indexes,
1007
require_versioned=True):
1008
state = self.current_dirstate()
1011
split_paths = sorted(osutils.split(p) for p in paths)
1012
found = state._bisect_recursive(split_paths)
1014
if require_versioned:
1015
found_dir_names = set(dir_name_id[:2] for dir_name_id in found)
1016
for dir_name in split_paths:
1017
if dir_name not in found_dir_names:
1018
raise errors.PathsNotVersionedError(paths)
1020
for dir_name_id, trees_info in found.iteritems():
1021
for index in search_indexes:
1022
if trees_info[index][0] not in ('r', 'a'):
1023
found_ids.add(dir_name_id[2])
1026
def read_working_inventory(self):
1027
"""Read the working inventory.
1029
This is a meaningless operation for dirstate, but we obey it anyhow.
1031
return self.inventory
1034
def revision_tree(self, revision_id):
1035
"""See Tree.revision_tree.
1037
WorkingTree4 supplies revision_trees for any basis tree.
1039
dirstate = self.current_dirstate()
1040
parent_ids = dirstate.get_parent_ids()
1041
if revision_id not in parent_ids:
1042
raise errors.NoSuchRevisionInTree(self, revision_id)
1043
if revision_id in dirstate.get_ghosts():
1044
raise errors.NoSuchRevisionInTree(self, revision_id)
1045
return DirStateRevisionTree(dirstate, revision_id,
1046
self.branch.repository)
1048
@needs_tree_write_lock
1049
def set_last_revision(self, new_revision):
1050
"""Change the last revision in the working tree."""
1051
parents = self.get_parent_ids()
1052
if new_revision in (NULL_REVISION, None):
1053
if len(parents) >= 2:
1054
raise AssertionError(
1055
"setting the last parent to none with a pending merge is "
1057
self.set_parent_ids([])
1059
self.set_parent_ids([new_revision] + parents[1:],
1060
allow_leftmost_as_ghost=True)
1062
@needs_tree_write_lock
1063
def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
1064
"""Set the parent ids to revision_ids.
1066
See also set_parent_trees. This api will try to retrieve the tree data
1067
for each element of revision_ids from the trees repository. If you have
1068
tree data already available, it is more efficient to use
1069
set_parent_trees rather than set_parent_ids. set_parent_ids is however
1070
an easier API to use.
1072
:param revision_ids: The revision_ids to set as the parent ids of this
1073
working tree. Any of these may be ghosts.
1076
for revision_id in revision_ids:
1078
revtree = self.branch.repository.revision_tree(revision_id)
1079
# TODO: jam 20070213 KnitVersionedFile raises
1080
# RevisionNotPresent rather than NoSuchRevision if a
1081
# given revision_id is not present. Should Repository be
1082
# catching it and re-raising NoSuchRevision?
1083
except (errors.NoSuchRevision, errors.RevisionNotPresent):
1085
trees.append((revision_id, revtree))
1086
self.set_parent_trees(trees,
1087
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
1089
@needs_tree_write_lock
1090
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
1091
"""Set the parents of the working tree.
1093
:param parents_list: A list of (revision_id, tree) tuples.
1094
If tree is None, then that element is treated as an unreachable
1095
parent tree - i.e. a ghost.
1097
dirstate = self.current_dirstate()
1098
if len(parents_list) > 0:
1099
if not allow_leftmost_as_ghost and parents_list[0][1] is None:
1100
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1104
parent_ids = [rev_id for rev_id, tree in parents_list]
1105
graph = self.branch.repository.get_graph()
1106
heads = graph.heads(parent_ids)
1107
accepted_revisions = set()
1109
# convert absent trees to the null tree, which we convert back to
1110
# missing on access.
1111
for rev_id, tree in parents_list:
1112
if len(accepted_revisions) > 0:
1113
# we always accept the first tree
1114
if rev_id in accepted_revisions or rev_id not in heads:
1115
# We have already included either this tree, or its
1116
# descendent, so we skip it.
1118
_mod_revision.check_not_reserved_id(rev_id)
1119
if tree is not None:
1120
real_trees.append((rev_id, tree))
1122
real_trees.append((rev_id,
1123
self.branch.repository.revision_tree(
1124
_mod_revision.NULL_REVISION)))
1125
ghosts.append(rev_id)
1126
accepted_revisions.add(rev_id)
1127
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1128
self._make_dirty(reset_inventory=False)
1130
def _set_root_id(self, file_id):
1131
"""See WorkingTree.set_root_id."""
1132
state = self.current_dirstate()
1133
state.set_path_id('', file_id)
1134
if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1135
self._make_dirty(reset_inventory=True)
1137
def _sha_from_stat(self, path, stat_result):
1138
"""Get a sha digest from the tree's stat cache.
1140
The default implementation assumes no stat cache is present.
1142
:param path: The path.
1143
:param stat_result: The stat result being looked up.
1145
return self.current_dirstate().sha1_from_stat(path, stat_result)
1148
def supports_tree_reference(self):
1149
return self._repo_supports_tree_reference
1152
"""Unlock in format 4 trees needs to write the entire dirstate."""
1153
# do non-implementation specific cleanup
1156
if self._control_files._lock_count == 1:
1157
# eventually we should do signature checking during read locks for
1159
if self._control_files._lock_mode == 'w':
1162
if self._dirstate is not None:
1163
# This is a no-op if there are no modifications.
1164
self._dirstate.save()
1165
self._dirstate.unlock()
1166
# TODO: jam 20070301 We shouldn't have to wipe the dirstate at this
1167
# point. Instead, it could check if the header has been
1168
# modified when it is locked, and if not, it can hang on to
1169
# the data it has in memory.
1170
self._dirstate = None
1171
self._inventory = None
1172
# reverse order of locking.
1174
return self._control_files.unlock()
1176
self.branch.unlock()
1178
@needs_tree_write_lock
1179
def unversion(self, file_ids):
1180
"""Remove the file ids in file_ids from the current versioned set.
1182
When a file_id is unversioned, all of its children are automatically
1185
:param file_ids: The file ids to stop versioning.
1186
:raises: NoSuchId if any fileid is not currently versioned.
1190
state = self.current_dirstate()
1191
state._read_dirblocks_if_needed()
1192
ids_to_unversion = set(file_ids)
1193
paths_to_unversion = set()
1195
# check if the root is to be unversioned, if so, assert for now.
1196
# walk the state marking unversioned things as absent.
1197
# if there are any un-unversioned ids at the end, raise
1198
for key, details in state._dirblocks[0][1]:
1199
if (details[0][0] not in ('a', 'r') and # absent or relocated
1200
key[2] in ids_to_unversion):
1201
# I haven't written the code to unversion / yet - it should be
1203
raise errors.BzrError('Unversioning the / is not currently supported')
1205
while block_index < len(state._dirblocks):
1206
# process one directory at a time.
1207
block = state._dirblocks[block_index]
1208
# first check: is the path one to remove - it or its children
1209
delete_block = False
1210
for path in paths_to_unversion:
1211
if (block[0].startswith(path) and
1212
(len(block[0]) == len(path) or
1213
block[0][len(path)] == '/')):
1214
# this entire block should be deleted - its the block for a
1215
# path to unversion; or the child of one
1218
# TODO: trim paths_to_unversion as we pass by paths
1220
# this block is to be deleted: process it.
1221
# TODO: we can special case the no-parents case and
1222
# just forget the whole block.
1224
while entry_index < len(block[1]):
1225
# Mark this file id as having been removed
1226
entry = block[1][entry_index]
1227
ids_to_unversion.discard(entry[0][2])
1228
if (entry[1][0][0] in 'ar' # don't remove absent or renamed
1230
or not state._make_absent(entry)):
1232
# go to the next block. (At the moment we dont delete empty
1237
while entry_index < len(block[1]):
1238
entry = block[1][entry_index]
1239
if (entry[1][0][0] in ('a', 'r') or # absent, relocated
1240
# ^ some parent row.
1241
entry[0][2] not in ids_to_unversion):
1242
# ^ not an id to unversion
1245
if entry[1][0][0] == 'd':
1246
paths_to_unversion.add(pathjoin(entry[0][0], entry[0][1]))
1247
if not state._make_absent(entry):
1249
# we have unversioned this id
1250
ids_to_unversion.remove(entry[0][2])
1252
if ids_to_unversion:
1253
raise errors.NoSuchId(self, iter(ids_to_unversion).next())
1254
self._make_dirty(reset_inventory=False)
1255
# have to change the legacy inventory too.
1256
if self._inventory is not None:
1257
for file_id in file_ids:
1258
self._inventory.remove_recursive_id(file_id)
1260
@needs_tree_write_lock
1261
def rename_one(self, from_rel, to_rel, after=False):
1262
"""See WorkingTree.rename_one"""
1264
WorkingTree.rename_one(self, from_rel, to_rel, after)
1266
@needs_tree_write_lock
1267
def apply_inventory_delta(self, changes):
1268
"""See MutableTree.apply_inventory_delta"""
1269
state = self.current_dirstate()
1270
state.update_by_delta(changes)
1271
self._make_dirty(reset_inventory=True)
1273
def update_basis_by_delta(self, new_revid, delta):
1274
"""See MutableTree.update_basis_by_delta."""
1275
if self.last_revision() == new_revid:
1276
raise AssertionError()
1277
self.current_dirstate().update_basis_by_delta(delta, new_revid)
1280
def _validate(self):
1281
self._dirstate._validate()
1283
@needs_tree_write_lock
1284
def _write_inventory(self, inv):
1285
"""Write inventory as the current inventory."""
1287
raise AssertionError("attempting to write an inventory when the "
1288
"dirstate is dirty will lose pending changes")
1289
self.current_dirstate().set_state_from_inventory(inv)
1290
self._make_dirty(reset_inventory=False)
1291
if self._inventory is not None:
1292
self._inventory = inv
1296
class WorkingTreeFormat4(WorkingTreeFormat3):
1297
"""The first consolidated dirstate working tree format.
1300
- exists within a metadir controlling .bzr
1301
- includes an explicit version marker for the workingtree control
1302
files, separate from the BzrDir format
1303
- modifies the hash cache format
1304
- is new in bzr 0.15
1305
- uses a LockDir to guard access to it.
1308
upgrade_recommended = False
1310
_tree_class = WorkingTree4
1312
def get_format_string(self):
1313
"""See WorkingTreeFormat.get_format_string()."""
1314
return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1316
def get_format_description(self):
1317
"""See WorkingTreeFormat.get_format_description()."""
1318
return "Working tree format 4"
1320
def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
1321
accelerator_tree=None, hardlink=False):
1322
"""See WorkingTreeFormat.initialize().
1324
:param revision_id: allows creating a working tree at a different
1325
revision than the branch is at.
1326
:param accelerator_tree: A tree which can be used for retrieving file
1327
contents more quickly than the revision tree, i.e. a workingtree.
1328
The revision tree will be used for cases where accelerator_tree's
1329
content is different.
1330
:param hardlink: If true, hard-link files from accelerator_tree,
1333
These trees get an initial random root id, if their repository supports
1334
rich root data, TREE_ROOT otherwise.
1336
if not isinstance(a_bzrdir.transport, LocalTransport):
1337
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1338
transport = a_bzrdir.get_workingtree_transport(self)
1339
control_files = self._open_control_files(a_bzrdir)
1340
control_files.create_lock()
1341
control_files.lock_write()
1342
transport.put_bytes('format', self.get_format_string(),
1343
mode=a_bzrdir._get_file_mode())
1344
if from_branch is not None:
1345
branch = from_branch
1347
branch = a_bzrdir.open_branch()
1348
if revision_id is None:
1349
revision_id = branch.last_revision()
1350
local_path = transport.local_abspath('dirstate')
1351
# write out new dirstate (must exist when we create the tree)
1352
state = dirstate.DirState.initialize(local_path)
1355
wt = self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
1359
_control_files=control_files)
1361
wt.lock_tree_write()
1363
self._init_custom_control_files(wt)
1364
if revision_id in (None, NULL_REVISION):
1365
if branch.repository.supports_rich_root():
1366
wt._set_root_id(generate_ids.gen_root_id())
1368
wt._set_root_id(ROOT_ID)
1371
# frequently, we will get here due to branching. The accelerator
1372
# tree will be the tree from the branch, so the desired basis
1373
# tree will often be a parent of the accelerator tree.
1374
if accelerator_tree is not None:
1376
basis = accelerator_tree.revision_tree(revision_id)
1377
except errors.NoSuchRevision:
1380
basis = branch.repository.revision_tree(revision_id)
1381
if revision_id == NULL_REVISION:
1384
parents_list = [(revision_id, basis)]
1387
wt.set_parent_trees(parents_list, allow_leftmost_as_ghost=True)
1389
# if the basis has a root id we have to use that; otherwise we
1390
# use a new random one
1391
basis_root_id = basis.get_root_id()
1392
if basis_root_id is not None:
1393
wt._set_root_id(basis_root_id)
1395
# delta_from_tree is safe even for DirStateRevisionTrees,
1396
# because wt4.apply_inventory_delta does not mutate the input
1397
# inventory entries.
1398
transform.build_tree(basis, wt, accelerator_tree,
1399
hardlink=hardlink, delta_from_tree=True)
1403
control_files.unlock()
1407
def _init_custom_control_files(self, wt):
1408
"""Subclasses with custom control files should override this method.
1410
The working tree and control files are locked for writing when this
1413
:param wt: the WorkingTree object
1416
def _open(self, a_bzrdir, control_files):
1417
"""Open the tree itself.
1419
:param a_bzrdir: the dir for the tree.
1420
:param control_files: the control files for the tree.
1422
return self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
1423
branch=a_bzrdir.open_branch(),
1426
_control_files=control_files)
1428
def __get_matchingbzrdir(self):
1429
# please test against something that will let us do tree references
1430
return bzrdir.format_registry.make_bzrdir(
1431
'dirstate-with-subtree')
1433
_matchingbzrdir = property(__get_matchingbzrdir)
1436
class DirStateRevisionTree(Tree):
1437
"""A revision tree pulling the inventory from a dirstate."""
1439
def __init__(self, dirstate, revision_id, repository):
1440
self._dirstate = dirstate
1441
self._revision_id = revision_id
1442
self._repository = repository
1443
self._inventory = None
1445
self._dirstate_locked = False
1446
self._repo_supports_tree_reference = getattr(
1447
repository._format, "supports_tree_reference",
1451
return "<%s of %s in %s>" % \
1452
(self.__class__.__name__, self._revision_id, self._dirstate)
1454
def annotate_iter(self, file_id,
1455
default_revision=_mod_revision.CURRENT_REVISION):
1456
"""See Tree.annotate_iter"""
1457
text_key = (file_id, self.inventory[file_id].revision)
1458
annotations = self._repository.texts.annotate(text_key)
1459
return [(key[-1], line) for (key, line) in annotations]
1461
def _get_ancestors(self, default_revision):
1462
return set(self._repository.get_ancestry(self._revision_id,
1464
def _comparison_data(self, entry, path):
1465
"""See Tree._comparison_data."""
1467
return None, False, None
1468
# trust the entry as RevisionTree does, but this may not be
1469
# sensible: the entry might not have come from us?
1470
return entry.kind, entry.executable, None
1472
def _file_size(self, entry, stat_value):
1473
return entry.text_size
1475
def filter_unversioned_files(self, paths):
1476
"""Filter out paths that are not versioned.
1478
:return: set of paths.
1480
pred = self.has_filename
1481
return set((p for p in paths if not pred(p)))
1483
def get_root_id(self):
1484
return self.path2id('')
1486
def id2path(self, file_id):
1487
"Convert a file-id to a path."
1488
entry = self._get_entry(file_id=file_id)
1489
if entry == (None, None):
1490
raise errors.NoSuchId(tree=self, file_id=file_id)
1491
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
1492
return path_utf8.decode('utf8')
1494
def iter_references(self):
1495
if not self._repo_supports_tree_reference:
1496
# When the repo doesn't support references, we will have nothing to
1499
# Otherwise, fall back to the default implementation
1500
return super(DirStateRevisionTree, self).iter_references()
1502
def _get_parent_index(self):
1503
"""Return the index in the dirstate referenced by this tree."""
1504
return self._dirstate.get_parent_ids().index(self._revision_id) + 1
1506
def _get_entry(self, file_id=None, path=None):
1507
"""Get the dirstate row for file_id or path.
1509
If either file_id or path is supplied, it is used as the key to lookup.
1510
If both are supplied, the fastest lookup is used, and an error is
1511
raised if they do not both point at the same row.
1513
:param file_id: An optional unicode file_id to be looked up.
1514
:param path: An optional unicode path to be looked up.
1515
:return: The dirstate row tuple for path/file_id, or (None, None)
1517
if file_id is None and path is None:
1518
raise errors.BzrError('must supply file_id or path')
1519
if path is not None:
1520
path = path.encode('utf8')
1521
parent_index = self._get_parent_index()
1522
return self._dirstate._get_entry(parent_index, fileid_utf8=file_id, path_utf8=path)
1524
def _generate_inventory(self):
1525
"""Create and set self.inventory from the dirstate object.
1527
(So this is only called the first time the inventory is requested for
1528
this tree; it then remains in memory until it's out of date.)
1530
This is relatively expensive: we have to walk the entire dirstate.
1532
if not self._locked:
1533
raise AssertionError(
1534
'cannot generate inventory of an unlocked '
1535
'dirstate revision tree')
1536
# separate call for profiling - makes it clear where the costs are.
1537
self._dirstate._read_dirblocks_if_needed()
1538
if self._revision_id not in self._dirstate.get_parent_ids():
1539
raise AssertionError(
1540
'parent %s has disappeared from %s' % (
1541
self._revision_id, self._dirstate.get_parent_ids()))
1542
parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
1543
# This is identical now to the WorkingTree _generate_inventory except
1544
# for the tree index use.
1545
root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1546
current_id = root_key[2]
1547
if current_entry[parent_index][0] != 'd':
1548
raise AssertionError()
1549
inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1550
inv.root.revision = current_entry[parent_index][4]
1551
# Turn some things into local variables
1552
minikind_to_kind = dirstate.DirState._minikind_to_kind
1553
factory = entry_factory
1554
utf8_decode = cache_utf8._utf8_decode
1555
inv_byid = inv._byid
1556
# we could do this straight out of the dirstate; it might be fast
1557
# and should be profiled - RBC 20070216
1558
parent_ies = {'' : inv.root}
1559
for block in self._dirstate._dirblocks[1:]: #skip root
1562
parent_ie = parent_ies[dirname]
1564
# all the paths in this block are not versioned in this tree
1566
for key, entry in block[1]:
1567
minikind, fingerprint, size, executable, revid = entry[parent_index]
1568
if minikind in ('a', 'r'): # absent, relocated
1572
name_unicode = utf8_decode(name)[0]
1574
kind = minikind_to_kind[minikind]
1575
inv_entry = factory[kind](file_id, name_unicode,
1577
inv_entry.revision = revid
1579
inv_entry.executable = executable
1580
inv_entry.text_size = size
1581
inv_entry.text_sha1 = fingerprint
1582
elif kind == 'directory':
1583
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1584
elif kind == 'symlink':
1585
inv_entry.executable = False
1586
inv_entry.text_size = None
1587
inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1588
elif kind == 'tree-reference':
1589
inv_entry.reference_revision = fingerprint or None
1591
raise AssertionError("cannot convert entry %r into an InventoryEntry"
1593
# These checks cost us around 40ms on a 55k entry tree
1594
if file_id in inv_byid:
1595
raise AssertionError('file_id %s already in'
1596
' inventory as %s' % (file_id, inv_byid[file_id]))
1597
if name_unicode in parent_ie.children:
1598
raise AssertionError('name %r already in parent'
1600
inv_byid[file_id] = inv_entry
1601
parent_ie.children[name_unicode] = inv_entry
1602
self._inventory = inv
1604
def get_file_mtime(self, file_id, path=None):
1605
"""Return the modification time for this record.
1607
We return the timestamp of the last-changed revision.
1609
# Make sure the file exists
1610
entry = self._get_entry(file_id, path=path)
1611
if entry == (None, None): # do we raise?
1613
parent_index = self._get_parent_index()
1614
last_changed_revision = entry[1][parent_index][4]
1615
return self._repository.get_revision(last_changed_revision).timestamp
1617
def get_file_sha1(self, file_id, path=None, stat_value=None):
1618
entry = self._get_entry(file_id=file_id, path=path)
1619
parent_index = self._get_parent_index()
1620
parent_details = entry[1][parent_index]
1621
if parent_details[0] == 'f':
1622
return parent_details[1]
1625
def get_file(self, file_id, path=None):
1626
return StringIO(self.get_file_text(file_id))
1628
def get_file_size(self, file_id):
1629
"""See Tree.get_file_size"""
1630
return self.inventory[file_id].text_size
1632
def get_file_text(self, file_id, path=None):
1633
return list(self.iter_files_bytes([(file_id, None)]))[0][1]
1635
def get_reference_revision(self, file_id, path=None):
1636
return self.inventory[file_id].reference_revision
1638
def iter_files_bytes(self, desired_files):
1639
"""See Tree.iter_files_bytes.
1641
This version is implemented on top of Repository.iter_files_bytes"""
1642
parent_index = self._get_parent_index()
1643
repo_desired_files = []
1644
for file_id, identifier in desired_files:
1645
entry = self._get_entry(file_id)
1646
if entry == (None, None):
1647
raise errors.NoSuchId(self, file_id)
1648
repo_desired_files.append((file_id, entry[1][parent_index][4],
1650
return self._repository.iter_files_bytes(repo_desired_files)
1652
def get_symlink_target(self, file_id):
1653
entry = self._get_entry(file_id=file_id)
1654
parent_index = self._get_parent_index()
1655
if entry[1][parent_index][0] != 'l':
1658
# At present, none of the tree implementations supports non-ascii
1659
# symlink targets. So we will just assume that the dirstate path is
1661
return entry[1][parent_index][1]
1663
def get_revision_id(self):
1664
"""Return the revision id for this tree."""
1665
return self._revision_id
1667
def _get_inventory(self):
1668
if self._inventory is not None:
1669
return self._inventory
1670
self._must_be_locked()
1671
self._generate_inventory()
1672
return self._inventory
1674
inventory = property(_get_inventory,
1675
doc="Inventory of this Tree")
1677
def get_parent_ids(self):
1678
"""The parents of a tree in the dirstate are not cached."""
1679
return self._repository.get_revision(self._revision_id).parent_ids
1681
def has_filename(self, filename):
1682
return bool(self.path2id(filename))
1684
def kind(self, file_id):
1685
entry = self._get_entry(file_id=file_id)[1]
1687
raise errors.NoSuchId(tree=self, file_id=file_id)
1688
return dirstate.DirState._minikind_to_kind[entry[1][0]]
1690
def stored_kind(self, file_id):
1691
"""See Tree.stored_kind"""
1692
return self.kind(file_id)
1694
def path_content_summary(self, path):
1695
"""See Tree.path_content_summary."""
1696
id = self.inventory.path2id(path)
1698
return ('missing', None, None, None)
1699
entry = self._inventory[id]
1702
return (kind, entry.text_size, entry.executable, entry.text_sha1)
1703
elif kind == 'symlink':
1704
return (kind, None, None, entry.symlink_target)
1706
return (kind, None, None, None)
1708
def is_executable(self, file_id, path=None):
1709
ie = self.inventory[file_id]
1710
if ie.kind != "file":
1712
return ie.executable
1714
def list_files(self, include_root=False):
1715
# We use a standard implementation, because DirStateRevisionTree is
1716
# dealing with one of the parents of the current state
1717
inv = self._get_inventory()
1718
entries = inv.iter_entries()
1719
if self.inventory.root is not None and not include_root:
1721
for path, entry in entries:
1722
yield path, 'V', entry.kind, entry.file_id, entry
1724
def lock_read(self):
1725
"""Lock the tree for a set of operations."""
1726
if not self._locked:
1727
self._repository.lock_read()
1728
if self._dirstate._lock_token is None:
1729
self._dirstate.lock_read()
1730
self._dirstate_locked = True
1733
def _must_be_locked(self):
1734
if not self._locked:
1735
raise errors.ObjectNotLocked(self)
1738
def path2id(self, path):
1739
"""Return the id for path in this tree."""
1740
# lookup by path: faster than splitting and walking the ivnentory.
1741
entry = self._get_entry(path=path)
1742
if entry == (None, None):
1747
"""Unlock, freeing any cache memory used during the lock."""
1748
# outside of a lock, the inventory is suspect: release it.
1750
if not self._locked:
1751
self._inventory = None
1753
if self._dirstate_locked:
1754
self._dirstate.unlock()
1755
self._dirstate_locked = False
1756
self._repository.unlock()
1759
def supports_tree_reference(self):
1760
return self._repo_supports_tree_reference
1762
def walkdirs(self, prefix=""):
1763
# TODO: jam 20070215 This is the lazy way by using the RevisionTree
1764
# implementation based on an inventory.
1765
# This should be cleaned up to use the much faster Dirstate code
1766
# So for now, we just build up the parent inventory, and extract
1767
# it the same way RevisionTree does.
1768
_directory = 'directory'
1769
inv = self._get_inventory()
1770
top_id = inv.path2id(prefix)
1774
pending = [(prefix, top_id)]
1777
relpath, file_id = pending.pop()
1778
# 0 - relpath, 1- file-id
1780
relroot = relpath + '/'
1783
# FIXME: stash the node in pending
1784
entry = inv[file_id]
1785
for name, child in entry.sorted_children():
1786
toppath = relroot + name
1787
dirblock.append((toppath, name, child.kind, None,
1788
child.file_id, child.kind
1790
yield (relpath, entry.file_id), dirblock
1791
# push the user specified dirs from dirblock
1792
for dir in reversed(dirblock):
1793
if dir[2] == _directory:
1794
pending.append((dir[0], dir[4]))
1797
class InterDirStateTree(InterTree):
1798
"""Fast path optimiser for changes_from with dirstate trees.
1800
This is used only when both trees are in the dirstate working file, and
1801
the source is any parent within the dirstate, and the destination is
1802
the current working tree of the same dirstate.
1804
# this could be generalized to allow comparisons between any trees in the
1805
# dirstate, and possibly between trees stored in different dirstates.
1807
def __init__(self, source, target):
1808
super(InterDirStateTree, self).__init__(source, target)
1809
if not InterDirStateTree.is_compatible(source, target):
1810
raise Exception, "invalid source %r and target %r" % (source, target)
1813
def make_source_parent_tree(source, target):
1814
"""Change the source tree into a parent of the target."""
1815
revid = source.commit('record tree')
1816
target.branch.repository.fetch(source.branch.repository, revid)
1817
target.set_parent_ids([revid])
1818
return target.basis_tree(), target
1821
def make_source_parent_tree_python_dirstate(klass, test_case, source, target):
1822
result = klass.make_source_parent_tree(source, target)
1823
result[1]._iter_changes = dirstate.ProcessEntryPython
1827
def make_source_parent_tree_compiled_dirstate(klass, test_case, source, target):
1828
from bzrlib.tests.test__dirstate_helpers import \
1829
CompiledDirstateHelpersFeature
1830
if not CompiledDirstateHelpersFeature.available():
1831
from bzrlib.tests import UnavailableFeature
1832
raise UnavailableFeature(CompiledDirstateHelpersFeature)
1833
from bzrlib._dirstate_helpers_c import ProcessEntryC
1834
result = klass.make_source_parent_tree(source, target)
1835
result[1]._iter_changes = ProcessEntryC
1838
_matching_from_tree_format = WorkingTreeFormat4()
1839
_matching_to_tree_format = WorkingTreeFormat4()
1842
def _test_mutable_trees_to_test_trees(klass, test_case, source, target):
1843
# This method shouldn't be called, because we have python and C
1844
# specific flavours.
1845
raise NotImplementedError
1847
def iter_changes(self, include_unchanged=False,
1848
specific_files=None, pb=None, extra_trees=[],
1849
require_versioned=True, want_unversioned=False):
1850
"""Return the changes from source to target.
1852
:return: An iterator that yields tuples. See InterTree.iter_changes
1854
:param specific_files: An optional list of file paths to restrict the
1855
comparison to. When mapping filenames to ids, all matches in all
1856
trees (including optional extra_trees) are used, and all children of
1857
matched directories are included.
1858
:param include_unchanged: An optional boolean requesting the inclusion of
1859
unchanged entries in the result.
1860
:param extra_trees: An optional list of additional trees to use when
1861
mapping the contents of specific_files (paths) to file_ids.
1862
:param require_versioned: If True, all files in specific_files must be
1863
versioned in one of source, target, extra_trees or
1864
PathsNotVersionedError is raised.
1865
:param want_unversioned: Should unversioned files be returned in the
1866
output. An unversioned file is defined as one with (False, False)
1867
for the versioned pair.
1869
# NB: show_status depends on being able to pass in non-versioned files
1870
# and report them as unknown
1871
# TODO: handle extra trees in the dirstate.
1872
if (extra_trees or specific_files == []):
1873
# we can't fast-path these cases (yet)
1874
return super(InterDirStateTree, self).iter_changes(
1875
include_unchanged, specific_files, pb, extra_trees,
1876
require_versioned, want_unversioned=want_unversioned)
1877
parent_ids = self.target.get_parent_ids()
1878
if not (self.source._revision_id in parent_ids
1879
or self.source._revision_id == NULL_REVISION):
1880
raise AssertionError(
1881
"revision {%s} is not stored in {%s}, but %s "
1882
"can only be used for trees stored in the dirstate"
1883
% (self.source._revision_id, self.target, self.iter_changes))
1885
if self.source._revision_id == NULL_REVISION:
1887
indices = (target_index,)
1889
if not (self.source._revision_id in parent_ids):
1890
raise AssertionError(
1891
"Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
1892
self.source._revision_id, parent_ids))
1893
source_index = 1 + parent_ids.index(self.source._revision_id)
1894
indices = (source_index, target_index)
1895
# -- make all specific_files utf8 --
1897
specific_files_utf8 = set()
1898
for path in specific_files:
1899
# Note, if there are many specific files, using cache_utf8
1900
# would be good here.
1901
specific_files_utf8.add(path.encode('utf8'))
1902
specific_files = specific_files_utf8
1904
specific_files = set([''])
1905
# -- specific_files is now a utf8 path set --
1906
search_specific_files = set()
1907
# -- get the state object and prepare it.
1908
state = self.target.current_dirstate()
1909
state._read_dirblocks_if_needed()
1910
if require_versioned:
1911
# -- check all supplied paths are versioned in a search tree. --
1912
all_versioned = True
1913
for path in specific_files:
1914
path_entries = state._entries_for_path(path)
1915
if not path_entries:
1916
# this specified path is not present at all: error
1917
all_versioned = False
1919
found_versioned = False
1920
# for each id at this path
1921
for entry in path_entries:
1923
for index in indices:
1924
if entry[1][index][0] != 'a': # absent
1925
found_versioned = True
1926
# all good: found a versioned cell
1928
if not found_versioned:
1929
# none of the indexes was not 'absent' at all ids for this
1931
all_versioned = False
1933
if not all_versioned:
1934
raise errors.PathsNotVersionedError(specific_files)
1935
# -- remove redundancy in supplied specific_files to prevent over-scanning --
1936
for path in specific_files:
1937
other_specific_files = specific_files.difference(set([path]))
1938
if not osutils.is_inside_any(other_specific_files, path):
1939
# this is a top level path, we must check it.
1940
search_specific_files.add(path)
1942
use_filesystem_for_exec = (sys.platform != 'win32')
1943
iter_changes = self.target._iter_changes(include_unchanged,
1944
use_filesystem_for_exec, search_specific_files, state,
1945
source_index, target_index, want_unversioned, self.target)
1946
return iter_changes.iter_changes()
1949
def is_compatible(source, target):
1950
# the target must be a dirstate working tree
1951
if not isinstance(target, WorkingTree4):
1953
# the source must be a revtreee or dirstate rev tree.
1954
if not isinstance(source,
1955
(revisiontree.RevisionTree, DirStateRevisionTree)):
1957
# the source revid must be in the target dirstate
1958
if not (source._revision_id == NULL_REVISION or
1959
source._revision_id in target.get_parent_ids()):
1960
# TODO: what about ghosts? it may well need to
1961
# check for them explicitly.
1965
InterTree.register_optimiser(InterDirStateTree)
1968
class Converter3to4(object):
1969
"""Perform an in-place upgrade of format 3 to format 4 trees."""
1972
self.target_format = WorkingTreeFormat4()
1974
def convert(self, tree):
1975
# lock the control files not the tree, so that we dont get tree
1976
# on-unlock behaviours, and so that noone else diddles with the
1977
# tree during upgrade.
1978
tree._control_files.lock_write()
1980
tree.read_working_inventory()
1981
self.create_dirstate_data(tree)
1982
self.update_format(tree)
1983
self.remove_xml_files(tree)
1985
tree._control_files.unlock()
1987
def create_dirstate_data(self, tree):
1988
"""Create the dirstate based data for tree."""
1989
local_path = tree.bzrdir.get_workingtree_transport(None
1990
).local_abspath('dirstate')
1991
state = dirstate.DirState.from_tree(tree, local_path)
1995
def remove_xml_files(self, tree):
1996
"""Remove the oldformat 3 data."""
1997
transport = tree.bzrdir.get_workingtree_transport(None)
1998
for path in ['basis-inventory-cache', 'inventory', 'last-revision',
1999
'pending-merges', 'stat-cache']:
2001
transport.delete(path)
2002
except errors.NoSuchFile:
2003
# some files are optional - just deal.
2006
def update_format(self, tree):
2007
"""Change the format marker."""
2008
tree._transport.put_bytes('format',
2009
self.target_format.get_format_string(),
2010
mode=tree.bzrdir._get_file_mode())