1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""WorkingTree4 format and implementation.
19
WorkingTree4 provides the dirstate based working tree logic.
21
To get a WorkingTree, call bzrdir.open_workingtree() or
22
WorkingTree.open(dir).
25
from cStringIO import StringIO
29
from bzrlib.lazy_import import lazy_import
30
lazy_import(globals(), """
31
from bisect import bisect_left
33
from copy import deepcopy
45
conflicts as _mod_conflicts,
63
from bzrlib.transport import get_transport
67
from bzrlib import symbol_versioning
68
from bzrlib.decorators import needs_read_lock, needs_write_lock
69
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID, entry_factory
70
from bzrlib.lockable_files import LockableFiles, TransportLock
71
from bzrlib.lockdir import LockDir
72
import bzrlib.mutabletree
73
from bzrlib.mutabletree import needs_tree_write_lock
74
from bzrlib.osutils import (
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,
100
from bzrlib.tree import Tree
101
from bzrlib.workingtree import WorkingTree, WorkingTree3, WorkingTreeFormat3
104
class WorkingTree4(WorkingTree3):
105
"""This is the Format 4 working tree.
107
This differs from WorkingTree3 by:
108
- having a consolidated internal dirstate.
109
- not having a regular inventory attribute.
111
This is new in bzr TODO FIXME SETMEBEFORE MERGE.
114
def __init__(self, basedir,
119
"""Construct a WorkingTree for basedir.
121
If the branch is not supplied, it is opened automatically.
122
If the branch is supplied, it must be the branch for this basedir.
123
(branch.base is not cross checked, because for remote branches that
124
would be meaningless).
126
self._format = _format
127
self.bzrdir = _bzrdir
128
from bzrlib.trace import note, mutter
129
assert isinstance(basedir, basestring), \
130
"base directory %r is not a string" % basedir
131
basedir = safe_unicode(basedir)
132
mutter("opening working tree %r", basedir)
133
self._branch = branch
134
assert isinstance(self.branch, bzrlib.branch.Branch), \
135
"branch %r is not a Branch" % self.branch
136
self.basedir = realpath(basedir)
137
# if branch is at our basedir and is a format 6 or less
138
# assume all other formats have their own control files.
139
assert isinstance(_control_files, LockableFiles), \
140
"_control_files must be a LockableFiles, not %r" % _control_files
141
self._control_files = _control_files
144
# during a read or write lock these objects are set, and are
145
# None the rest of the time.
146
self._dirstate = None
147
self._inventory = None
150
@needs_tree_write_lock
151
def _add(self, files, ids, kinds):
152
"""See MutableTree._add."""
153
state = self.current_dirstate()
154
for f, file_id, kind in zip(files, ids, kinds):
159
# special case tree root handling.
160
if f == '' and self.path2id(f) == ROOT_ID:
161
state.set_path_id('', generate_ids.gen_file_id(f))
164
file_id = generate_ids.gen_file_id(f)
165
# deliberately add the file with no cached stat or sha1
166
# - on the first access it will be gathered, and we can
167
# always change this once tests are all passing.
168
state.add(f, file_id, kind, None, '')
169
self._make_dirty(reset_inventory=True)
171
def _make_dirty(self, reset_inventory):
172
"""Make the tree state dirty.
174
:param reset_inventory: True if the cached inventory should be removed
175
(presuming there is one).
178
if reset_inventory and self._inventory is not None:
179
self._inventory = None
181
def break_lock(self):
182
"""Break a lock if one is present from another instance.
184
Uses the ui factory to ask for confirmation if the lock may be from
187
This will probe the repository for its lock as well.
189
# if the dirstate is locked by an active process, reject the break lock
192
if self._dirstate is None:
196
state = self._current_dirstate()
197
if state._lock_token is not None:
198
# we already have it locked. sheese, cant break our own lock.
199
raise errors.LockActive(self.basedir)
202
# try for a write lock - need permission to get one anyhow
205
except errors.LockContention:
206
# oslocks fail when a process is still live: fail.
207
# TODO: get the locked lockdir info and give to the user to
208
# assist in debugging.
209
raise errors.LockActive(self.basedir)
214
self._dirstate = None
215
self._control_files.break_lock()
216
self.branch.break_lock()
219
def commit(self, message=None, revprops=None, *args, **kwargs):
220
# mark the tree as dirty post commit - commit
221
# can change the current versioned list by doing deletes.
222
result = WorkingTree3.commit(self, message, revprops, *args, **kwargs)
223
self._make_dirty(reset_inventory=True)
226
def current_dirstate(self):
227
"""Return the current dirstate object.
229
This is not part of the tree interface and only exposed for ease of
232
:raises errors.NotWriteLocked: when not in a lock.
234
if not self._control_files._lock_count:
235
raise errors.ObjectNotLocked(self)
236
return self._current_dirstate()
238
def _current_dirstate(self):
239
"""Internal function that does not check lock status.
241
This is needed for break_lock which also needs the dirstate.
243
if self._dirstate is not None:
244
return self._dirstate
245
local_path = self.bzrdir.get_workingtree_transport(None
246
).local_abspath('dirstate')
247
self._dirstate = dirstate.DirState.on_file(local_path)
248
return self._dirstate
250
def filter_unversioned_files(self, paths):
251
"""Filter out paths that are versioned.
253
:return: set of paths.
255
# TODO: make a generic multi-bisect routine roughly that should list
256
# the paths, then process one half at a time recursively, and feed the
257
# results of each bisect in further still
258
paths = sorted(paths)
260
state = self.current_dirstate()
261
# TODO we want a paths_to_dirblocks helper I think
263
dirname, basename = os.path.split(path.encode('utf8'))
264
_, _, _, path_is_versioned = state._get_block_entry_index(
265
dirname, basename, 0)
266
if not path_is_versioned:
271
"""Write all cached data to disk."""
272
if self._control_files._lock_mode != 'w':
273
raise errors.NotWriteLocked(self)
274
self.current_dirstate().save()
275
self._inventory = None
278
def _generate_inventory(self):
279
"""Create and set self.inventory from the dirstate object.
281
This is relatively expensive: we have to walk the entire dirstate.
282
Ideally we would not, and can deprecate this function.
284
#: uncomment to trap on inventory requests.
285
# import pdb;pdb.set_trace()
286
state = self.current_dirstate()
287
state._read_dirblocks_if_needed()
288
root_key, current_entry = self._get_entry(path='')
289
current_id = root_key[2]
290
assert current_entry[0][0] == 'd' # directory
291
inv = Inventory(root_id=current_id)
292
# Turn some things into local variables
293
minikind_to_kind = dirstate.DirState._minikind_to_kind
294
factory = entry_factory
295
utf8_decode = cache_utf8._utf8_decode
297
# we could do this straight out of the dirstate; it might be fast
298
# and should be profiled - RBC 20070216
299
parent_ies = {'' : inv.root}
300
for block in state._dirblocks[1:]: # skip the root
303
parent_ie = parent_ies[dirname]
305
# all the paths in this block are not versioned in this tree
307
for key, entry in block[1]:
308
minikind, link_or_sha1, size, executable, stat = entry[0]
309
if minikind in ('a', 'r'): # absent, relocated
310
# a parent tree only entry
313
name_unicode = utf8_decode(name)[0]
315
kind = minikind_to_kind[minikind]
316
inv_entry = factory[kind](file_id, name_unicode,
319
# not strictly needed: working tree
320
#entry.executable = executable
321
#entry.text_size = size
322
#entry.text_sha1 = sha1
324
elif kind == 'directory':
325
# add this entry to the parent map.
326
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
327
# These checks cost us around 40ms on a 55k entry tree
328
assert file_id not in inv_byid, ('file_id %s already in'
329
' inventory as %s' % (file_id, inv_byid[file_id]))
330
assert name_unicode not in parent_ie.children
331
inv_byid[file_id] = inv_entry
332
parent_ie.children[name_unicode] = inv_entry
333
self._inventory = inv
335
def _get_entry(self, file_id=None, path=None):
336
"""Get the dirstate row for file_id or path.
338
If either file_id or path is supplied, it is used as the key to lookup.
339
If both are supplied, the fastest lookup is used, and an error is
340
raised if they do not both point at the same row.
342
:param file_id: An optional unicode file_id to be looked up.
343
:param path: An optional unicode path to be looked up.
344
:return: The dirstate row tuple for path/file_id, or (None, None)
346
if file_id is None and path is None:
347
raise errors.BzrError('must supply file_id or path')
348
state = self.current_dirstate()
350
path = path.encode('utf8')
351
return state._get_entry(0, fileid_utf8=file_id, path_utf8=path)
353
def get_file_sha1(self, file_id, path=None, stat_value=None):
354
# check file id is valid unconditionally.
355
entry = self._get_entry(file_id=file_id, path=path)
356
assert entry[0] is not None, 'what error should this raise'
358
# if row stat is valid, use cached sha1, else, get a new sha1.
360
path = pathjoin(entry[0][0], entry[0][1]).decode('utf8')
362
file_abspath = self.abspath(path)
363
state = self.current_dirstate()
364
return state.get_sha1_for_entry(entry, file_abspath,
365
stat_value=stat_value)
367
def _get_inventory(self):
368
"""Get the inventory for the tree. This is only valid within a lock."""
369
if self._inventory is not None:
370
return self._inventory
371
self._generate_inventory()
372
return self._inventory
374
inventory = property(_get_inventory,
375
doc="Inventory of this Tree")
378
def get_parent_ids(self):
379
"""See Tree.get_parent_ids.
381
This implementation requests the ids list from the dirstate file.
383
return self.current_dirstate().get_parent_ids()
386
def get_root_id(self):
387
"""Return the id of this trees root"""
388
return self._get_entry(path='')[0][2]
390
def has_id(self, file_id):
391
state = self.current_dirstate()
392
file_id = osutils.safe_file_id(file_id)
393
row, parents = self._get_entry(file_id=file_id)
396
return osutils.lexists(pathjoin(
397
self.basedir, row[0].decode('utf8'), row[1].decode('utf8')))
400
def id2path(self, file_id):
401
file_id = osutils.safe_file_id(file_id)
402
state = self.current_dirstate()
403
entry = self._get_entry(file_id=file_id)
404
if entry == (None, None):
406
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
407
return path_utf8.decode('utf8')
411
"""Iterate through file_ids for this tree.
413
file_ids are in a WorkingTree if they are in the working inventory
414
and the working file exists.
417
for key, tree_details in self.current_dirstate()._iter_entries():
418
if tree_details[0][0] in ('a', 'r'): # absent, relocated
419
# not relevant to the working tree
421
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
422
if osutils.lexists(path):
423
result.append(key[2])
427
def _last_revision(self):
428
"""See Mutable.last_revision."""
429
parent_ids = self.current_dirstate().get_parent_ids()
436
"""See Branch.lock_read, and WorkingTree.unlock."""
437
self.branch.lock_read()
439
self._control_files.lock_read()
441
state = self.current_dirstate()
442
if not state._lock_token:
445
self._control_files.unlock()
451
def _lock_self_write(self):
452
"""This should be called after the branch is locked."""
454
self._control_files.lock_write()
456
state = self.current_dirstate()
457
if not state._lock_token:
460
self._control_files.unlock()
466
def lock_tree_write(self):
467
"""See MutableTree.lock_tree_write, and WorkingTree.unlock."""
468
self.branch.lock_read()
469
self._lock_self_write()
471
def lock_write(self):
472
"""See MutableTree.lock_write, and WorkingTree.unlock."""
473
self.branch.lock_write()
474
self._lock_self_write()
476
@needs_tree_write_lock
477
def move(self, from_paths, to_dir, after=False):
478
"""See WorkingTree.move()."""
483
state = self.current_dirstate()
485
assert not isinstance(from_paths, basestring)
486
to_dir_utf8 = to_dir.encode('utf8')
487
to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
488
id_index = state._get_id_index()
489
# check destination directory
490
# get the details for it
491
to_entry_block_index, to_entry_entry_index, dir_present, entry_present = \
492
state._get_block_entry_index(to_entry_dirname, to_basename, 0)
493
if not entry_present:
494
raise errors.BzrMoveFailedError('', to_dir,
495
errors.NotVersionedError(to_dir))
496
to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
497
# get a handle on the block itself.
498
to_block_index = state._ensure_block(
499
to_entry_block_index, to_entry_entry_index, to_dir_utf8)
500
to_block = state._dirblocks[to_block_index]
501
to_abs = self.abspath(to_dir)
502
if not isdir(to_abs):
503
raise errors.BzrMoveFailedError('',to_dir,
504
errors.NotADirectory(to_abs))
506
if to_entry[1][0][0] != 'd':
507
raise errors.BzrMoveFailedError('',to_dir,
508
errors.NotADirectory(to_abs))
510
if self._inventory is not None:
511
update_inventory = True
513
to_dir_ie = inv[to_dir_id]
514
to_dir_id = to_entry[0][2]
516
update_inventory = False
519
def move_one(old_entry, from_path_utf8, minikind, executable,
520
fingerprint, packed_stat, size,
521
to_block, to_key, to_path_utf8):
522
state._make_absent(old_entry)
523
from_key = old_entry[0]
525
lambda:state.update_minimal(from_key,
527
executable=executable,
528
fingerprint=fingerprint,
529
packed_stat=packed_stat,
531
path_utf8=from_path_utf8))
532
state.update_minimal(to_key,
534
executable=executable,
535
fingerprint=fingerprint,
536
packed_stat=packed_stat,
538
path_utf8=to_path_utf8)
539
added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
540
new_entry = to_block[1][added_entry_index]
541
rollbacks.append(lambda:state._make_absent(new_entry))
543
# create rename entries and tuples
544
for from_rel in from_paths:
545
# from_rel is 'pathinroot/foo/bar'
546
from_rel_utf8 = from_rel.encode('utf8')
547
from_dirname, from_tail = osutils.split(from_rel)
548
from_dirname, from_tail_utf8 = osutils.split(from_rel_utf8)
549
from_entry = self._get_entry(path=from_rel)
550
if from_entry == (None, None):
551
raise errors.BzrMoveFailedError(from_rel,to_dir,
552
errors.NotVersionedError(path=str(from_rel)))
554
from_id = from_entry[0][2]
555
to_rel = pathjoin(to_dir, from_tail)
556
to_rel_utf8 = pathjoin(to_dir_utf8, from_tail_utf8)
557
item_to_entry = self._get_entry(path=to_rel)
558
if item_to_entry != (None, None):
559
raise errors.BzrMoveFailedError(from_rel, to_rel,
560
"Target is already versioned.")
562
if from_rel == to_rel:
563
raise errors.BzrMoveFailedError(from_rel, to_rel,
564
"Source and target are identical.")
566
from_missing = not self.has_filename(from_rel)
567
to_missing = not self.has_filename(to_rel)
574
raise errors.BzrMoveFailedError(from_rel, to_rel,
575
errors.NoSuchFile(path=to_rel,
576
extra="New file has not been created yet"))
578
# neither path exists
579
raise errors.BzrRenameFailedError(from_rel, to_rel,
580
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
582
if from_missing: # implicitly just update our path mapping
585
raise errors.RenameFailedFilesExist(from_rel, to_rel,
586
extra="(Use --after to update the Bazaar id)")
589
def rollback_rename():
590
"""A single rename has failed, roll it back."""
592
for rollback in reversed(rollbacks):
596
import pdb;pdb.set_trace()
597
exc_info = sys.exc_info()
599
raise exc_info[0], exc_info[1], exc_info[2]
601
# perform the disk move first - its the most likely failure point.
603
from_rel_abs = self.abspath(from_rel)
604
to_rel_abs = self.abspath(to_rel)
606
osutils.rename(from_rel_abs, to_rel_abs)
608
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
609
rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
611
# perform the rename in the inventory next if needed: its easy
615
from_entry = inv[from_id]
616
current_parent = from_entry.parent_id
617
inv.rename(from_id, to_dir_id, from_tail)
619
lambda: inv.rename(from_id, current_parent, from_tail))
620
# finally do the rename in the dirstate, which is a little
621
# tricky to rollback, but least likely to need it.
622
old_block_index, old_entry_index, dir_present, file_present = \
623
state._get_block_entry_index(from_dirname, from_tail_utf8, 0)
624
old_block = state._dirblocks[old_block_index][1]
625
old_entry = old_block[old_entry_index]
626
from_key, old_entry_details = old_entry
627
cur_details = old_entry_details[0]
629
to_key = ((to_block[0],) + from_key[1:3])
630
minikind = cur_details[0]
631
move_one(old_entry, from_path_utf8=from_rel_utf8,
633
executable=cur_details[3],
634
fingerprint=cur_details[1],
635
packed_stat=cur_details[4],
639
to_path_utf8=to_rel_utf8)
642
def update_dirblock(from_dir, to_key, to_dir_utf8):
643
"""all entries in this block need updating.
645
TODO: This is pretty ugly, and doesn't support
646
reverting, but it works.
648
assert from_dir != '', "renaming root not supported"
649
from_key = (from_dir, '')
650
from_block_idx, present = \
651
state._find_block_index_from_key(from_key)
653
# This is the old record, if it isn't present, then
654
# there is theoretically nothing to update.
655
# (Unless it isn't present because of lazy loading,
656
# but we don't do that yet)
658
from_block = state._dirblocks[from_block_idx]
659
to_block_index, to_entry_index, _, _ = \
660
state._get_block_entry_index(to_key[0], to_key[1], 0)
661
to_block_index = state._ensure_block(
662
to_block_index, to_entry_index, to_dir_utf8)
663
to_block = state._dirblocks[to_block_index]
664
for entry in from_block[1]:
665
assert entry[0][0] == from_dir
666
cur_details = entry[1][0]
667
to_key = (to_dir_utf8, entry[0][1], entry[0][2])
668
from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
669
to_path_utf8 = osutils.pathjoin(to_dir_utf8, entry[0][1])
670
minikind = cur_details[0]
671
move_one(entry, from_path_utf8=from_path_utf8,
673
executable=cur_details[3],
674
fingerprint=cur_details[1],
675
packed_stat=cur_details[4],
679
to_path_utf8=to_rel_utf8)
681
# We need to move all the children of this
683
update_dirblock(from_path_utf8, to_key,
685
update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
689
result.append((from_rel, to_rel))
690
state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
691
self._make_dirty(reset_inventory=False)
696
"""Initialize the state in this tree to be a new tree."""
700
def path2id(self, path):
701
"""Return the id for path in this tree."""
702
path = path.strip('/')
703
entry = self._get_entry(path=path)
704
if entry == (None, None):
708
def paths2ids(self, paths, trees=[], require_versioned=True):
709
"""See Tree.paths2ids().
711
This specialisation fast-paths the case where all the trees are in the
716
parents = self.get_parent_ids()
718
if not (isinstance(tree, DirStateRevisionTree) and tree._revision_id in
720
return super(WorkingTree4, self).paths2ids(paths, trees, require_versioned)
721
search_indexes = [0] + [1 + parents.index(tree._revision_id) for tree in trees]
722
# -- make all paths utf8 --
725
paths_utf8.add(path.encode('utf8'))
727
# -- paths is now a utf8 path set --
728
# -- get the state object and prepare it.
729
state = self.current_dirstate()
730
if False and (state._dirblock_state == dirstate.DirState.NOT_IN_MEMORY
731
and '' not in paths):
732
paths2ids = self._paths2ids_using_bisect
734
paths2ids = self._paths2ids_in_memory
735
return paths2ids(paths, search_indexes,
736
require_versioned=require_versioned)
738
def _paths2ids_in_memory(self, paths, search_indexes,
739
require_versioned=True):
740
state = self.current_dirstate()
741
state._read_dirblocks_if_needed()
742
def _entries_for_path(path):
743
"""Return a list with all the entries that match path for all ids.
745
dirname, basename = os.path.split(path)
746
key = (dirname, basename, '')
747
block_index, present = state._find_block_index_from_key(key)
749
# the block which should contain path is absent.
752
block = state._dirblocks[block_index][1]
753
entry_index, _ = state._find_entry_index(key, block)
754
# we may need to look at multiple entries at this path: walk while the paths match.
755
while (entry_index < len(block) and
756
block[entry_index][0][0:2] == key[0:2]):
757
result.append(block[entry_index])
760
if require_versioned:
761
# -- check all supplied paths are versioned in a search tree. --
764
path_entries = _entries_for_path(path)
766
# this specified path is not present at all: error
767
all_versioned = False
769
found_versioned = False
770
# for each id at this path
771
for entry in path_entries:
773
for index in search_indexes:
774
if entry[1][index][0] != 'a': # absent
775
found_versioned = True
776
# all good: found a versioned cell
778
if not found_versioned:
779
# none of the indexes was not 'absent' at all ids for this
781
all_versioned = False
783
if not all_versioned:
784
raise errors.PathsNotVersionedError(paths)
785
# -- remove redundancy in supplied paths to prevent over-scanning --
788
other_paths = paths.difference(set([path]))
789
if not osutils.is_inside_any(other_paths, path):
790
# this is a top level path, we must check it.
791
search_paths.add(path)
793
# for all search_indexs in each path at or under each element of
794
# search_paths, if the detail is relocated: add the id, and add the
795
# relocated path as one to search if its not searched already. If the
796
# detail is not relocated, add the id.
797
searched_paths = set()
799
def _process_entry(entry):
800
"""Look at search_indexes within entry.
802
If a specific tree's details are relocated, add the relocation
803
target to search_paths if not searched already. If it is absent, do
804
nothing. Otherwise add the id to found_ids.
806
for index in search_indexes:
807
if entry[1][index][0] == 'r': # relocated
808
if not osutils.is_inside_any(searched_paths, entry[1][index][1]):
809
search_paths.add(entry[1][index][1])
810
elif entry[1][index][0] != 'a': # absent
811
found_ids.add(entry[0][2])
813
current_root = search_paths.pop()
814
searched_paths.add(current_root)
815
# process the entries for this containing directory: the rest will be
816
# found by their parents recursively.
817
root_entries = _entries_for_path(current_root)
819
# this specified path is not present at all, skip it.
821
for entry in root_entries:
822
_process_entry(entry)
823
initial_key = (current_root, '', '')
824
block_index, _ = state._find_block_index_from_key(initial_key)
825
while (block_index < len(state._dirblocks) and
826
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
827
for entry in state._dirblocks[block_index][1]:
828
_process_entry(entry)
832
def _paths2ids_using_bisect(self, paths, search_indexes,
833
require_versioned=True):
834
state = self.current_dirstate()
837
split_paths = sorted(osutils.split(p) for p in paths)
838
found = state._bisect_recursive(split_paths)
840
if require_versioned:
841
found_dir_names = set(dir_name_id[:2] for dir_name_id in found)
842
for dir_name in split_paths:
843
if dir_name not in found_dir_names:
844
raise errors.PathsNotVersionedError(paths)
846
for dir_name_id, trees_info in found.iteritems():
847
for index in search_indexes:
848
if trees_info[index][0] not in ('r', 'a'):
849
found_ids.add(dir_name_id[2])
852
def read_working_inventory(self):
853
"""Read the working inventory.
855
This is a meaningless operation for dirstate, but we obey it anyhow.
857
return self.inventory
860
def revision_tree(self, revision_id):
861
"""See Tree.revision_tree.
863
WorkingTree4 supplies revision_trees for any basis tree.
865
revision_id = osutils.safe_revision_id(revision_id)
866
dirstate = self.current_dirstate()
867
parent_ids = dirstate.get_parent_ids()
868
if revision_id not in parent_ids:
869
raise errors.NoSuchRevisionInTree(self, revision_id)
870
if revision_id in dirstate.get_ghosts():
871
raise errors.NoSuchRevisionInTree(self, revision_id)
872
return DirStateRevisionTree(dirstate, revision_id,
873
self.branch.repository)
875
@needs_tree_write_lock
876
def set_last_revision(self, new_revision):
877
"""Change the last revision in the working tree."""
878
new_revision = osutils.safe_revision_id(new_revision)
879
parents = self.get_parent_ids()
880
if new_revision in (NULL_REVISION, None):
881
assert len(parents) < 2, (
882
"setting the last parent to none with a pending merge is "
884
self.set_parent_ids([])
886
self.set_parent_ids([new_revision] + parents[1:],
887
allow_leftmost_as_ghost=True)
889
@needs_tree_write_lock
890
def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
891
"""Set the parent ids to revision_ids.
893
See also set_parent_trees. This api will try to retrieve the tree data
894
for each element of revision_ids from the trees repository. If you have
895
tree data already available, it is more efficient to use
896
set_parent_trees rather than set_parent_ids. set_parent_ids is however
897
an easier API to use.
899
:param revision_ids: The revision_ids to set as the parent ids of this
900
working tree. Any of these may be ghosts.
902
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
904
for revision_id in revision_ids:
906
revtree = self.branch.repository.revision_tree(revision_id)
907
# TODO: jam 20070213 KnitVersionedFile raises
908
# RevisionNotPresent rather than NoSuchRevision if a
909
# given revision_id is not present. Should Repository be
910
# catching it and re-raising NoSuchRevision?
911
except (errors.NoSuchRevision, errors.RevisionNotPresent):
913
trees.append((revision_id, revtree))
914
self.set_parent_trees(trees,
915
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
917
@needs_tree_write_lock
918
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
919
"""Set the parents of the working tree.
921
:param parents_list: A list of (revision_id, tree) tuples.
922
If tree is None, then that element is treated as an unreachable
923
parent tree - i.e. a ghost.
925
dirstate = self.current_dirstate()
926
if len(parents_list) > 0:
927
if not allow_leftmost_as_ghost and parents_list[0][1] is None:
928
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
931
# convert absent trees to the null tree, which we convert back to
933
for rev_id, tree in parents_list:
934
rev_id = osutils.safe_revision_id(rev_id)
936
real_trees.append((rev_id, tree))
938
real_trees.append((rev_id,
939
self.branch.repository.revision_tree(None)))
940
ghosts.append(rev_id)
941
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
942
self._make_dirty(reset_inventory=False)
944
def _set_root_id(self, file_id):
945
"""See WorkingTree.set_root_id."""
946
state = self.current_dirstate()
947
state.set_path_id('', file_id)
948
if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
949
self._make_dirty(reset_inventory=True)
952
"""Unlock in format 4 trees needs to write the entire dirstate."""
953
if self._control_files._lock_count == 1:
954
# eventually we should do signature checking during read locks for
956
if self._control_files._lock_mode == 'w':
959
if self._dirstate is not None:
960
# This is a no-op if there are no modifications.
961
self._dirstate.save()
962
self._dirstate.unlock()
963
# TODO: jam 20070301 We shouldn't have to wipe the dirstate at this
964
# point. Instead, it could check if the header has been
965
# modified when it is locked, and if not, it can hang on to
966
# the data it has in memory.
967
self._dirstate = None
968
self._inventory = None
969
# reverse order of locking.
971
return self._control_files.unlock()
975
@needs_tree_write_lock
976
def unversion(self, file_ids):
977
"""Remove the file ids in file_ids from the current versioned set.
979
When a file_id is unversioned, all of its children are automatically
982
:param file_ids: The file ids to stop versioning.
983
:raises: NoSuchId if any fileid is not currently versioned.
987
state = self.current_dirstate()
988
state._read_dirblocks_if_needed()
989
ids_to_unversion = set()
990
for file_id in file_ids:
991
ids_to_unversion.add(osutils.safe_file_id(file_id))
992
paths_to_unversion = set()
994
# check if the root is to be unversioned, if so, assert for now.
995
# walk the state marking unversioned things as absent.
996
# if there are any un-unversioned ids at the end, raise
997
for key, details in state._dirblocks[0][1]:
998
if (details[0][0] not in ('a', 'r') and # absent or relocated
999
key[2] in ids_to_unversion):
1000
# I haven't written the code to unversion / yet - it should be
1002
raise errors.BzrError('Unversioning the / is not currently supported')
1004
while block_index < len(state._dirblocks):
1005
# process one directory at a time.
1006
block = state._dirblocks[block_index]
1007
# first check: is the path one to remove - it or its children
1008
delete_block = False
1009
for path in paths_to_unversion:
1010
if (block[0].startswith(path) and
1011
(len(block[0]) == len(path) or
1012
block[0][len(path)] == '/')):
1013
# this entire block should be deleted - its the block for a
1014
# path to unversion; or the child of one
1017
# TODO: trim paths_to_unversion as we pass by paths
1019
# this block is to be deleted: process it.
1020
# TODO: we can special case the no-parents case and
1021
# just forget the whole block.
1023
while entry_index < len(block[1]):
1024
# Mark this file id as having been removed
1025
ids_to_unversion.discard(block[1][entry_index][0][2])
1026
if not state._make_absent(block[1][entry_index]):
1028
# go to the next block. (At the moment we dont delete empty
1033
while entry_index < len(block[1]):
1034
entry = block[1][entry_index]
1035
if (entry[1][0][0] in ('a', 'r') or # absent, relocated
1036
# ^ some parent row.
1037
entry[0][2] not in ids_to_unversion):
1038
# ^ not an id to unversion
1041
if entry[1][0][0] == 'd':
1042
paths_to_unversion.add(pathjoin(entry[0][0], entry[0][1]))
1043
if not state._make_absent(entry):
1045
# we have unversioned this id
1046
ids_to_unversion.remove(entry[0][2])
1048
if ids_to_unversion:
1049
raise errors.NoSuchId(self, iter(ids_to_unversion).next())
1050
self._make_dirty(reset_inventory=False)
1051
# have to change the legacy inventory too.
1052
if self._inventory is not None:
1053
for file_id in file_ids:
1054
self._inventory.remove_recursive_id(file_id)
1056
@needs_tree_write_lock
1057
def _write_inventory(self, inv):
1058
"""Write inventory as the current inventory."""
1059
assert not self._dirty, "attempting to write an inventory when the dirstate is dirty will cause data loss"
1060
self.current_dirstate().set_state_from_inventory(inv)
1061
self._make_dirty(reset_inventory=False)
1062
if self._inventory is not None:
1063
self._inventory = inv
1068
class WorkingTreeFormat4(WorkingTreeFormat3):
1069
"""The first consolidated dirstate working tree format.
1072
- exists within a metadir controlling .bzr
1073
- includes an explicit version marker for the workingtree control
1074
files, separate from the BzrDir format
1075
- modifies the hash cache format
1076
- is new in bzr TODO FIXME SETBEFOREMERGE
1077
- uses a LockDir to guard access to it.
1080
def get_format_string(self):
1081
"""See WorkingTreeFormat.get_format_string()."""
1082
return "Bazaar Working Tree format 4\n"
1084
def get_format_description(self):
1085
"""See WorkingTreeFormat.get_format_description()."""
1086
return "Working tree format 4"
1088
def initialize(self, a_bzrdir, revision_id=None):
1089
"""See WorkingTreeFormat.initialize().
1091
revision_id allows creating a working tree at a different
1092
revision than the branch is at.
1094
revision_id = osutils.safe_revision_id(revision_id)
1095
if not isinstance(a_bzrdir.transport, LocalTransport):
1096
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1097
transport = a_bzrdir.get_workingtree_transport(self)
1098
control_files = self._open_control_files(a_bzrdir)
1099
control_files.create_lock()
1100
control_files.lock_write()
1101
control_files.put_utf8('format', self.get_format_string())
1102
branch = a_bzrdir.open_branch()
1103
if revision_id is None:
1104
revision_id = branch.last_revision()
1105
local_path = transport.local_abspath('dirstate')
1106
state = dirstate.DirState.initialize(local_path)
1108
wt = WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1112
_control_files=control_files)
1114
wt.lock_tree_write()
1116
wt.set_last_revision(revision_id)
1118
basis = wt.basis_tree()
1120
transform.build_tree(basis, wt)
1123
control_files.unlock()
1128
def _open(self, a_bzrdir, control_files):
1129
"""Open the tree itself.
1131
:param a_bzrdir: the dir for the tree.
1132
:param control_files: the control files for the tree.
1134
return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1135
branch=a_bzrdir.open_branch(),
1138
_control_files=control_files)
1141
class DirStateRevisionTree(Tree):
1142
"""A revision tree pulling the inventory from a dirstate."""
1144
def __init__(self, dirstate, revision_id, repository):
1145
self._dirstate = dirstate
1146
self._revision_id = osutils.safe_revision_id(revision_id)
1147
self._repository = repository
1148
self._inventory = None
1150
self._dirstate_locked = False
1152
def annotate_iter(self, file_id):
1153
"""See Tree.annotate_iter"""
1154
w = self._repository.weave_store.get_weave(file_id,
1155
self._repository.get_transaction())
1156
return w.annotate_iter(self.inventory[file_id].revision)
1158
def _comparison_data(self, entry, path):
1159
"""See Tree._comparison_data."""
1161
return None, False, None
1162
# trust the entry as RevisionTree does, but this may not be
1163
# sensible: the entry might not have come from us?
1164
return entry.kind, entry.executable, None
1166
def _file_size(self, entry, stat_value):
1167
return entry.text_size
1169
def filter_unversioned_files(self, paths):
1170
"""Filter out paths that are not versioned.
1172
:return: set of paths.
1174
pred = self.has_filename
1175
return set((p for p in paths if not pred(p)))
1177
def _get_parent_index(self):
1178
"""Return the index in the dirstate referenced by this tree."""
1179
return self._dirstate.get_parent_ids().index(self._revision_id) + 1
1181
def _get_entry(self, file_id=None, path=None):
1182
"""Get the dirstate row for file_id or path.
1184
If either file_id or path is supplied, it is used as the key to lookup.
1185
If both are supplied, the fastest lookup is used, and an error is
1186
raised if they do not both point at the same row.
1188
:param file_id: An optional unicode file_id to be looked up.
1189
:param path: An optional unicode path to be looked up.
1190
:return: The dirstate row tuple for path/file_id, or (None, None)
1192
if file_id is None and path is None:
1193
raise errors.BzrError('must supply file_id or path')
1194
file_id = osutils.safe_file_id(file_id)
1195
if path is not None:
1196
path = path.encode('utf8')
1197
parent_index = self._get_parent_index()
1198
return self._dirstate._get_entry(parent_index, fileid_utf8=file_id, path_utf8=path)
1200
def _generate_inventory(self):
1201
"""Create and set self.inventory from the dirstate object.
1203
This is relatively expensive: we have to walk the entire dirstate.
1204
Ideally we would not, and instead would """
1205
assert self._locked, 'cannot generate inventory of an unlocked '\
1206
'dirstate revision tree'
1207
# separate call for profiling - makes it clear where the costs are.
1208
self._dirstate._read_dirblocks_if_needed()
1209
assert self._revision_id in self._dirstate.get_parent_ids(), \
1210
'parent %s has disappeared from %s' % (
1211
self._revision_id, self._dirstate.get_parent_ids())
1212
parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
1213
# This is identical now to the WorkingTree _generate_inventory except
1214
# for the tree index use.
1215
root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1216
current_id = root_key[2]
1217
assert current_entry[parent_index][0] == 'd'
1218
inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1219
inv.root.revision = current_entry[parent_index][4]
1220
# Turn some things into local variables
1221
minikind_to_kind = dirstate.DirState._minikind_to_kind
1222
factory = entry_factory
1223
utf8_decode = cache_utf8._utf8_decode
1224
inv_byid = inv._byid
1225
# we could do this straight out of the dirstate; it might be fast
1226
# and should be profiled - RBC 20070216
1227
parent_ies = {'' : inv.root}
1228
for block in self._dirstate._dirblocks[1:]: #skip root
1231
parent_ie = parent_ies[dirname]
1233
# all the paths in this block are not versioned in this tree
1235
for key, entry in block[1]:
1236
minikind, link_or_sha1, size, executable, revid = entry[parent_index]
1237
if minikind in ('a', 'r'): # absent, relocated
1241
name_unicode = utf8_decode(name)[0]
1243
kind = minikind_to_kind[minikind]
1244
inv_entry = factory[kind](file_id, name_unicode,
1246
inv_entry.revision = revid
1248
inv_entry.executable = executable
1249
inv_entry.text_size = size
1250
inv_entry.text_sha1 = link_or_sha1
1251
elif kind == 'directory':
1252
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1253
elif kind == 'symlink':
1254
inv_entry.executable = False
1255
inv_entry.text_size = size
1256
inv_entry.symlink_target = utf8_decode(link_or_sha1)[0]
1258
raise Exception, kind
1259
# These checks cost us around 40ms on a 55k entry tree
1260
assert file_id not in inv_byid
1261
assert name_unicode not in parent_ie.children
1262
inv_byid[file_id] = inv_entry
1263
parent_ie.children[name_unicode] = inv_entry
1264
self._inventory = inv
1266
def get_file_mtime(self, file_id, path=None):
1267
"""Return the modification time for this record.
1269
We return the timestamp of the last-changed revision.
1271
# Make sure the file exists
1272
entry = self._get_entry(file_id, path=path)
1273
if entry == (None, None): # do we raise?
1275
parent_index = self._get_parent_index()
1276
last_changed_revision = entry[1][parent_index][4]
1277
return self._repository.get_revision(last_changed_revision).timestamp
1279
def get_file_sha1(self, file_id, path=None, stat_value=None):
1280
entry = self._get_entry(file_id=file_id, path=path)
1281
parent_index = self._get_parent_index()
1282
parent_details = entry[1][parent_index]
1283
if parent_details[0] == 'f':
1284
return parent_details[1]
1287
def get_file(self, file_id):
1288
return StringIO(self.get_file_text(file_id))
1290
def get_file_lines(self, file_id):
1291
ie = self.inventory[file_id]
1292
return self._repository.weave_store.get_weave(file_id,
1293
self._repository.get_transaction()).get_lines(ie.revision)
1295
def get_file_size(self, file_id):
1296
return self.inventory[file_id].text_size
1298
def get_file_text(self, file_id):
1299
return ''.join(self.get_file_lines(file_id))
1301
def get_symlink_target(self, file_id):
1302
entry = self._get_entry(file_id=file_id)
1303
parent_index = self._get_parent_index()
1304
if entry[1][parent_index][0] != 'l':
1307
# At present, none of the tree implementations supports non-ascii
1308
# symlink targets. So we will just assume that the dirstate path is
1310
return entry[1][parent_index][1]
1312
def get_revision_id(self):
1313
"""Return the revision id for this tree."""
1314
return self._revision_id
1316
def _get_inventory(self):
1317
if self._inventory is not None:
1318
return self._inventory
1319
self._generate_inventory()
1320
return self._inventory
1322
inventory = property(_get_inventory,
1323
doc="Inventory of this Tree")
1325
def get_parent_ids(self):
1326
"""The parents of a tree in the dirstate are not cached."""
1327
return self._repository.get_revision(self._revision_id).parent_ids
1329
def has_filename(self, filename):
1330
return bool(self.path2id(filename))
1332
def kind(self, file_id):
1333
return self.inventory[file_id].kind
1335
def is_executable(self, file_id, path=None):
1336
ie = self.inventory[file_id]
1337
if ie.kind != "file":
1339
return ie.executable
1341
def list_files(self, include_root=False):
1342
# We use a standard implementation, because DirStateRevisionTree is
1343
# dealing with one of the parents of the current state
1344
inv = self._get_inventory()
1345
entries = inv.iter_entries()
1346
if self.inventory.root is not None and not include_root:
1348
for path, entry in entries:
1349
yield path, 'V', entry.kind, entry.file_id, entry
1351
def lock_read(self):
1352
"""Lock the tree for a set of operations."""
1353
if not self._locked:
1354
self._repository.lock_read()
1355
if self._dirstate._lock_token is None:
1356
self._dirstate.lock_read()
1357
self._dirstate_locked = True
1361
def path2id(self, path):
1362
"""Return the id for path in this tree."""
1363
# lookup by path: faster than splitting and walking the ivnentory.
1364
entry = self._get_entry(path=path)
1365
if entry == (None, None):
1370
"""Unlock, freeing any cache memory used during the lock."""
1371
# outside of a lock, the inventory is suspect: release it.
1373
if not self._locked:
1374
self._inventory = None
1376
if self._dirstate_locked:
1377
self._dirstate.unlock()
1378
self._dirstate_locked = False
1379
self._repository.unlock()
1381
def walkdirs(self, prefix=""):
1382
# TODO: jam 20070215 This is the cheap way by cheating and using the
1383
# RevisionTree implementation.
1384
# This should be cleaned up to use the much faster Dirstate code
1385
# This is a little tricky, though, because the dirstate is
1386
# indexed by current path, not by parent path.
1387
# So for now, we just build up the parent inventory, and extract
1388
# it the same way RevisionTree does.
1389
_directory = 'directory'
1390
inv = self._get_inventory()
1391
top_id = inv.path2id(prefix)
1395
pending = [(prefix, top_id)]
1398
relpath, file_id = pending.pop()
1399
# 0 - relpath, 1- file-id
1401
relroot = relpath + '/'
1404
# FIXME: stash the node in pending
1405
entry = inv[file_id]
1406
for name, child in entry.sorted_children():
1407
toppath = relroot + name
1408
dirblock.append((toppath, name, child.kind, None,
1409
child.file_id, child.kind
1411
yield (relpath, entry.file_id), dirblock
1412
# push the user specified dirs from dirblock
1413
for dir in reversed(dirblock):
1414
if dir[2] == _directory:
1415
pending.append((dir[0], dir[4]))
1418
class InterDirStateTree(InterTree):
1419
"""Fast path optimiser for changes_from with dirstate trees."""
1421
def __init__(self, source, target):
1422
super(InterDirStateTree, self).__init__(source, target)
1423
if not InterDirStateTree.is_compatible(source, target):
1424
raise Exception, "invalid source %r and target %r" % (source, target)
1427
def make_source_parent_tree(source, target):
1428
"""Change the source tree into a parent of the target."""
1429
revid = source.commit('record tree')
1430
target.branch.repository.fetch(source.branch.repository, revid)
1431
target.set_parent_ids([revid])
1432
return target.basis_tree(), target
1434
_matching_from_tree_format = WorkingTreeFormat4()
1435
_matching_to_tree_format = WorkingTreeFormat4()
1436
_test_mutable_trees_to_test_trees = make_source_parent_tree
1438
def _iter_changes(self, include_unchanged=False,
1439
specific_files=None, pb=None, extra_trees=[],
1440
require_versioned=True):
1441
"""Return the changes from source to target.
1443
:return: An iterator that yields tuples. See InterTree._iter_changes
1445
:param specific_files: An optional list of file paths to restrict the
1446
comparison to. When mapping filenames to ids, all matches in all
1447
trees (including optional extra_trees) are used, and all children of
1448
matched directories are included.
1449
:param include_unchanged: An optional boolean requesting the inclusion of
1450
unchanged entries in the result.
1451
:param extra_trees: An optional list of additional trees to use when
1452
mapping the contents of specific_files (paths) to file_ids.
1453
:param require_versioned: If True, all files in specific_files must be
1454
versioned in one of source, target, extra_trees or
1455
PathsNotVersionedError is raised.
1457
utf8_decode = cache_utf8._utf8_decode
1458
_minikind_to_kind = dirstate.DirState._minikind_to_kind
1459
# NB: show_status depends on being able to pass in non-versioned files
1460
# and report them as unknown
1461
# TODO: handle extra trees in the dirstate.
1463
for f in super(InterDirStateTree, self)._iter_changes(
1464
include_unchanged, specific_files, pb, extra_trees,
1468
parent_ids = self.target.get_parent_ids()
1470
if self.source._revision_id == NULL_REVISION:
1472
indices = (target_index,)
1474
assert (self.source._revision_id in parent_ids), \
1475
"Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
1476
self.source._revision_id, parent_ids)
1477
source_index = 1 + parent_ids.index(self.source._revision_id)
1478
indices = (source_index,target_index)
1479
# -- make all specific_files utf8 --
1481
specific_files_utf8 = set()
1482
for path in specific_files:
1483
specific_files_utf8.add(path.encode('utf8'))
1484
specific_files = specific_files_utf8
1486
specific_files = set([''])
1487
# -- specific_files is now a utf8 path set --
1488
# -- get the state object and prepare it.
1489
state = self.target.current_dirstate()
1490
state._read_dirblocks_if_needed()
1491
def _entries_for_path(path):
1492
"""Return a list with all the entries that match path for all ids.
1494
dirname, basename = os.path.split(path)
1495
key = (dirname, basename, '')
1496
block_index, present = state._find_block_index_from_key(key)
1498
# the block which should contain path is absent.
1501
block = state._dirblocks[block_index][1]
1502
entry_index, _ = state._find_entry_index(key, block)
1503
# we may need to look at multiple entries at this path: walk while the specific_files match.
1504
while (entry_index < len(block) and
1505
block[entry_index][0][0:2] == key[0:2]):
1506
result.append(block[entry_index])
1509
if require_versioned:
1510
# -- check all supplied paths are versioned in a search tree. --
1511
all_versioned = True
1512
for path in specific_files:
1513
path_entries = _entries_for_path(path)
1514
if not path_entries:
1515
# this specified path is not present at all: error
1516
all_versioned = False
1518
found_versioned = False
1519
# for each id at this path
1520
for entry in path_entries:
1522
for index in indices:
1523
if entry[1][index][0] != 'a': # absent
1524
found_versioned = True
1525
# all good: found a versioned cell
1527
if not found_versioned:
1528
# none of the indexes was not 'absent' at all ids for this
1530
all_versioned = False
1532
if not all_versioned:
1533
raise errors.PathsNotVersionedError(specific_files)
1534
# -- remove redundancy in supplied specific_files to prevent over-scanning --
1535
search_specific_files = set()
1536
for path in specific_files:
1537
other_specific_files = specific_files.difference(set([path]))
1538
if not osutils.is_inside_any(other_specific_files, path):
1539
# this is a top level path, we must check it.
1540
search_specific_files.add(path)
1542
# compare source_index and target_index at or under each element of search_specific_files.
1543
# follow the following comparison table. Note that we only want to do diff operations when
1544
# the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
1548
# Source | Target | disk | action
1549
# r | fdl | | add source to search, add id path move and perform
1550
# | | | diff check on source-target
1551
# r | fdl | a | dangling file that was present in the basis.
1553
# r | a | | add source to search
1555
# r | r | | this path is present in a non-examined tree, skip.
1556
# r | r | a | this path is present in a non-examined tree, skip.
1557
# a | fdl | | add new id
1558
# a | fdl | a | dangling locally added file, skip
1559
# a | a | | not present in either tree, skip
1560
# a | a | a | not present in any tree, skip
1561
# a | r | | not present in either tree at this path, skip as it
1562
# | | | may not be selected by the users list of paths.
1563
# a | r | a | not present in either tree at this path, skip as it
1564
# | | | may not be selected by the users list of paths.
1565
# fdl | fdl | | content in both: diff them
1566
# fdl | fdl | a | deleted locally, but not unversioned - show as deleted ?
1567
# fdl | a | | unversioned: output deleted id for now
1568
# fdl | a | a | unversioned and deleted: output deleted id
1569
# fdl | r | | relocated in this tree, so add target to search.
1570
# | | | Dont diff, we will see an r,fd; pair when we reach
1571
# | | | this id at the other path.
1572
# fdl | r | a | relocated in this tree, so add target to search.
1573
# | | | Dont diff, we will see an r,fd; pair when we reach
1574
# | | | this id at the other path.
1576
# for all search_indexs in each path at or under each element of
1577
# search_specific_files, if the detail is relocated: add the id, and add the
1578
# relocated path as one to search if its not searched already. If the
1579
# detail is not relocated, add the id.
1580
searched_specific_files = set()
1581
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1582
# Using a list so that we can access the values and change them in
1583
# nested scope. Each one is [path, file_id, entry]
1584
last_source_parent = [None, None, None]
1585
last_target_parent = [None, None, None]
1587
def _process_entry(entry, path_info):
1588
"""Compare an entry and real disk to generate delta information.
1590
:param path_info: top_relpath, basename, kind, lstat, abspath for
1591
the path of entry. If None, then the path is considered absent.
1592
(Perhaps we should pass in a concrete entry for this ?)
1593
Basename is returned as a utf8 string because we expect this
1594
tuple will be ignored, and don't want to take the time to
1597
# TODO: when a parent has been renamed, dont emit path renames for children,
1598
if source_index is None:
1599
source_details = NULL_PARENT_DETAILS
1601
source_details = entry[1][source_index]
1602
target_details = entry[1][target_index]
1603
source_minikind = source_details[0]
1604
target_minikind = target_details[0]
1605
if source_minikind in 'fdlr' and target_minikind in 'fdl':
1606
# claimed content in both: diff
1607
# r | fdl | | add source to search, add id path move and perform
1608
# | | | diff check on source-target
1609
# r | fdl | a | dangling file that was present in the basis.
1611
if source_minikind in 'r':
1612
# add the source to the search path to find any children it
1613
# has. TODO ? : only add if it is a container ?
1614
if not osutils.is_inside_any(searched_specific_files,
1616
search_specific_files.add(source_details[1])
1617
# generate the old path; this is needed for stating later
1619
old_path = source_details[1]
1620
old_dirname, old_basename = os.path.split(old_path)
1621
path = pathjoin(entry[0][0], entry[0][1])
1622
old_entry = state._get_entry(source_index,
1624
# update the source details variable to be the real
1626
source_details = old_entry[1][source_index]
1627
source_minikind = source_details[0]
1629
old_dirname = entry[0][0]
1630
old_basename = entry[0][1]
1631
old_path = path = pathjoin(old_dirname, old_basename)
1632
if path_info is None:
1633
# the file is missing on disk, show as removed.
1634
old_path = pathjoin(entry[0][0], entry[0][1])
1635
content_change = True
1639
# source and target are both versioned and disk file is present.
1640
target_kind = path_info[2]
1641
if target_kind == 'directory':
1642
if source_minikind != 'd':
1643
content_change = True
1645
# directories have no fingerprint
1646
content_change = False
1648
elif target_kind == 'file':
1649
if source_minikind != 'f':
1650
content_change = True
1652
# has it changed? fast path: size, slow path: sha1.
1653
if source_details[2] != path_info[3].st_size:
1654
content_change = True
1656
# maybe the same. Get the hash
1657
new_hash = state.get_sha1_for_entry(entry,
1658
abspath=path_info[4],
1659
stat_value=path_info[3])
1660
content_change = (new_hash != source_details[1])
1662
stat.S_ISREG(path_info[3].st_mode)
1663
and stat.S_IEXEC & path_info[3].st_mode)
1664
elif target_kind == 'symlink':
1665
if source_minikind != 'l':
1666
content_change = True
1668
# TODO: check symlink supported for windows users
1669
# and grab from target state here.
1670
link_target = os.readlink(path_info[4])
1671
content_change = (link_target != source_details[1])
1674
raise Exception, "unknown kind %s" % path_info[2]
1675
# parent id is the entry for the path in the target tree
1676
if old_dirname == last_source_parent[0]:
1677
source_parent_id = last_source_parent[1]
1679
source_parent_entry = state._get_entry(source_index,
1680
path_utf8=old_dirname)
1681
source_parent_id = source_parent_entry[0][2]
1682
if source_parent_id == entry[0][2]:
1683
# This is the root, so the parent is None
1684
source_parent_id = None
1686
last_source_parent[0] = old_dirname
1687
last_source_parent[1] = source_parent_id
1688
last_source_parent[2] = source_parent_entry
1690
new_dirname = entry[0][0]
1691
if new_dirname == last_target_parent[0]:
1692
target_parent_id = last_target_parent[1]
1694
# TODO: We don't always need to do the lookup, because the
1695
# parent entry will be the same as the source entry.
1696
target_parent_entry = state._get_entry(target_index,
1697
path_utf8=new_dirname)
1698
target_parent_id = target_parent_entry[0][2]
1699
if target_parent_id == entry[0][2]:
1700
# This is the root, so the parent is None
1701
target_parent_id = None
1703
last_target_parent[0] = new_dirname
1704
last_target_parent[1] = target_parent_id
1705
last_target_parent[2] = target_parent_entry
1707
source_exec = source_details[3]
1708
#path_unicode = utf8_decode(path)[0]
1709
return ((entry[0][2], path, content_change,
1711
(source_parent_id, target_parent_id),
1712
(old_basename, entry[0][1]),
1713
(_minikind_to_kind[source_minikind], target_kind),
1714
(source_exec, target_exec)),)
1715
elif source_minikind in 'a' and target_minikind in 'fdl':
1716
# looks like a new file
1717
if path_info is not None:
1718
path = pathjoin(entry[0][0], entry[0][1])
1719
# parent id is the entry for the path in the target tree
1720
# TODO: these are the same for an entire directory: cache em.
1721
parent_id = state._get_entry(target_index, path_utf8=entry[0][0])[0][2]
1722
if parent_id == entry[0][2]:
1725
new_executable = bool(
1726
stat.S_ISREG(path_info[3].st_mode)
1727
and stat.S_IEXEC & path_info[3].st_mode)
1728
#path_unicode = utf8_decode(path)[0]
1729
return ((entry[0][2], path, True,
1732
(None, entry[0][1]),
1733
(None, path_info[2]),
1734
(None, new_executable)),)
1736
# but its not on disk: we deliberately treat this as just
1737
# never-present. (Why ?! - RBC 20070224)
1739
elif source_minikind in 'fdl' and target_minikind in 'a':
1740
# unversioned, possibly, or possibly not deleted: we dont care.
1741
# if its still on disk, *and* theres no other entry at this
1742
# path [we dont know this in this routine at the moment -
1743
# perhaps we should change this - then it would be an unknown.
1744
old_path = pathjoin(entry[0][0], entry[0][1])
1745
# parent id is the entry for the path in the target tree
1746
parent_id = state._get_entry(source_index, path_utf8=entry[0][0])[0][2]
1747
if parent_id == entry[0][2]:
1749
#old_path_unicode = utf8_decode(old_path)[0]
1750
return ((entry[0][2], old_path, True,
1753
(entry[0][1], None),
1754
(_minikind_to_kind[source_minikind], None),
1755
(source_details[3], None)),)
1756
elif source_minikind in 'fdl' and target_minikind in 'r':
1757
# a rename; could be a true rename, or a rename inherited from
1758
# a renamed parent. TODO: handle this efficiently. Its not
1759
# common case to rename dirs though, so a correct but slow
1760
# implementation will do.
1761
if not osutils.is_inside_any(searched_specific_files, target_details[1]):
1762
search_specific_files.add(target_details[1])
1763
elif source_minikind in 'r' and target_minikind in 'r':
1764
# neither of the selected trees contain this file,
1765
# so skip over it. This is not currently directly tested, but
1766
# is indirectly via test_too_much.TestCommands.test_conflicts.
1769
print "*******", source_minikind, target_minikind
1770
import pdb;pdb.set_trace()
1772
while search_specific_files:
1773
# TODO: the pending list should be lexically sorted?
1774
current_root = search_specific_files.pop()
1775
searched_specific_files.add(current_root)
1776
# process the entries for this containing directory: the rest will be
1777
# found by their parents recursively.
1778
root_entries = _entries_for_path(current_root)
1779
root_abspath = self.target.abspath(current_root)
1781
root_stat = os.lstat(root_abspath)
1783
if e.errno == errno.ENOENT:
1784
# the path does not exist: let _process_entry know that.
1785
root_dir_info = None
1787
# some other random error: hand it up.
1790
root_dir_info = ('', current_root,
1791
osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
1793
if not root_entries and not root_dir_info:
1794
# this specified path is not present at all, skip it.
1796
for entry in root_entries:
1797
for result in _process_entry(entry, root_dir_info):
1798
# this check should probably be outside the loop: one
1799
# 'iterate two trees' api, and then _iter_changes filters
1800
# unchanged pairs. - RBC 20070226
1801
if (include_unchanged
1802
or result[2] # content change
1803
or result[3][0] != result[3][1] # versioned status
1804
or result[4][0] != result[4][1] # parent id
1805
or result[5][0] != result[5][1] # name
1806
or result[6][0] != result[6][1] # kind
1807
or result[7][0] != result[7][1] # executable
1809
result = (result[0],
1810
utf8_decode(result[1])[0]) + result[2:]
1812
dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
1813
initial_key = (current_root, '', '')
1814
block_index, _ = state._find_block_index_from_key(initial_key)
1815
if block_index == 0:
1816
# we have processed the total root already, but because the
1817
# initial key matched it we should skip it here.
1820
current_dir_info = dir_iterator.next()
1822
if e.errno in (errno.ENOENT, errno.ENOTDIR):
1823
# there may be directories in the inventory even though
1824
# this path is not a file on disk: so mark it as end of
1826
current_dir_info = None
1830
if current_dir_info[0][0] == '':
1831
# remove .bzr from iteration
1832
bzr_index = bisect_left(current_dir_info[1], ('.bzr',))
1833
assert current_dir_info[1][bzr_index][0] == '.bzr'
1834
del current_dir_info[1][bzr_index]
1835
# walk until both the directory listing and the versioned metadata
1836
# are exhausted. TODO: reevaluate this, perhaps we should stop when
1837
# the versioned data runs out.
1838
if (block_index < len(state._dirblocks) and
1839
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
1840
current_block = state._dirblocks[block_index]
1842
current_block = None
1843
while (current_dir_info is not None or
1844
current_block is not None):
1845
if (current_dir_info and current_block
1846
and current_dir_info[0][0] != current_block[0]):
1847
if current_dir_info[0][0] < current_block[0] :
1848
# import pdb; pdb.set_trace()
1849
# print 'unversioned dir'
1850
# filesystem data refers to paths not covered by the dirblock.
1851
# this has two possibilities:
1852
# A) it is versioned but empty, so there is no block for it
1853
# B) it is not versioned.
1854
# in either case it was processed by the containing directories walk:
1855
# if it is root/foo, when we walked root we emitted it,
1856
# or if we ere given root/foo to walk specifically, we
1857
# emitted it when checking the walk-root entries
1858
# advance the iterator and loop - we dont need to emit it.
1860
current_dir_info = dir_iterator.next()
1861
except StopIteration:
1862
current_dir_info = None
1864
# We have a dirblock entry for this location, but there
1865
# is no filesystem path for this. This is most likely
1866
# because a directory was removed from the disk.
1867
# We don't have to report the missing directory,
1868
# because that should have already been handled, but we
1869
# need to handle all of the files that are contained
1871
for current_entry in current_block[1]:
1872
# entry referring to file not present on disk.
1873
# advance the entry only, after processing.
1874
for result in _process_entry(current_entry, None):
1875
# this check should probably be outside the loop: one
1876
# 'iterate two trees' api, and then _iter_changes filters
1877
# unchanged pairs. - RBC 20070226
1878
if (include_unchanged
1879
or result[2] # content change
1880
or result[3][0] != result[3][1] # versioned status
1881
or result[4][0] != result[4][1] # parent id
1882
or result[5][0] != result[5][1] # name
1883
or result[6][0] != result[6][1] # kind
1884
or result[7][0] != result[7][1] # executable
1886
result = (result[0],
1887
utf8_decode(result[1])[0]) + result[2:]
1890
if (block_index < len(state._dirblocks) and
1891
osutils.is_inside(current_root,
1892
state._dirblocks[block_index][0])):
1893
current_block = state._dirblocks[block_index]
1895
current_block = None
1898
if current_block and entry_index < len(current_block[1]):
1899
current_entry = current_block[1][entry_index]
1901
current_entry = None
1902
advance_entry = True
1904
if current_dir_info and path_index < len(current_dir_info[1]):
1905
current_path_info = current_dir_info[1][path_index]
1907
current_path_info = None
1909
while (current_entry is not None or
1910
current_path_info is not None):
1911
if current_entry is None:
1912
# no more entries: yield current_pathinfo as an
1913
# unversioned file: its not the same as a path in any
1914
# tree in the dirstate.
1915
new_executable = bool(
1916
stat.S_ISREG(current_path_info[3].st_mode)
1917
and stat.S_IEXEC & current_path_info[3].st_mode)
1918
pass # unversioned file support not added to the
1919
# _iter_changes api yet - breaks status amongst other
1921
# yield (None, current_path_info[0], True,
1924
# (None, current_path_info[1]),
1925
# (None, current_path_info[2]),
1926
# (None, new_executable))
1927
elif current_path_info is None:
1928
# no path is fine: the per entry code will handle it.
1929
for result in _process_entry(current_entry, current_path_info):
1930
# this check should probably be outside the loop: one
1931
# 'iterate two trees' api, and then _iter_changes filters
1932
# unchanged pairs. - RBC 20070226
1933
if (include_unchanged
1934
or result[2] # content change
1935
or result[3][0] != result[3][1] # versioned status
1936
or result[4][0] != result[4][1] # parent id
1937
or result[5][0] != result[5][1] # name
1938
or result[6][0] != result[6][1] # kind
1939
or result[7][0] != result[7][1] # executable
1941
result = (result[0],
1942
utf8_decode(result[1])[0]) + result[2:]
1944
elif current_entry[0][1] != current_path_info[1]:
1945
if current_path_info[1] < current_entry[0][1]:
1946
# extra file on disk: pass for now, but only
1947
# increment the path, not the entry
1948
# import pdb; pdb.set_trace()
1949
# print 'unversioned file'
1950
advance_entry = False
1952
# entry referring to file not present on disk.
1953
# advance the entry only, after processing.
1954
for result in _process_entry(current_entry, None):
1955
# this check should probably be outside the loop: one
1956
# 'iterate two trees' api, and then _iter_changes filters
1957
# unchanged pairs. - RBC 20070226
1958
if (include_unchanged
1959
or result[2] # content change
1960
or result[3][0] != result[3][1] # versioned status
1961
or result[4][0] != result[4][1] # parent id
1962
or result[5][0] != result[5][1] # name
1963
or result[6][0] != result[6][1] # kind
1964
or result[7][0] != result[7][1] # executable
1966
result = (result[0],
1967
utf8_decode(result[1])[0]) + result[2:]
1969
advance_path = False
1971
for result in _process_entry(current_entry, current_path_info):
1972
# this check should probably be outside the loop: one
1973
# 'iterate two trees' api, and then _iter_changes filters
1974
# unchanged pairs. - RBC 20070226
1975
if (include_unchanged
1976
or result[2] # content change
1977
or result[3][0] != result[3][1] # versioned status
1978
or result[4][0] != result[4][1] # parent id
1979
or result[5][0] != result[5][1] # name
1980
or result[6][0] != result[6][1] # kind
1981
or result[7][0] != result[7][1] # executable
1983
result = (result[0],
1984
utf8_decode(result[1])[0]) + result[2:]
1986
if advance_entry and current_entry is not None:
1988
if entry_index < len(current_block[1]):
1989
current_entry = current_block[1][entry_index]
1991
current_entry = None
1993
advance_entry = True # reset the advance flaga
1994
if advance_path and current_path_info is not None:
1996
if path_index < len(current_dir_info[1]):
1997
current_path_info = current_dir_info[1][path_index]
1999
current_path_info = None
2001
advance_path = True # reset the advance flagg.
2002
if current_block is not None:
2004
if (block_index < len(state._dirblocks) and
2005
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
2006
current_block = state._dirblocks[block_index]
2008
current_block = None
2009
if current_dir_info is not None:
2011
current_dir_info = dir_iterator.next()
2012
except StopIteration:
2013
current_dir_info = None
2017
def is_compatible(source, target):
2018
# the target must be a dirstate working tree
2019
if not isinstance(target, WorkingTree4):
2021
# the source must be a revtreee or dirstate rev tree.
2022
if not isinstance(source,
2023
(revisiontree.RevisionTree, DirStateRevisionTree)):
2025
# the source revid must be in the target dirstate
2026
if not (source._revision_id == NULL_REVISION or
2027
source._revision_id in target.get_parent_ids()):
2028
# TODO: what about ghosts? it may well need to
2029
# check for them explicitly.
2033
InterTree.register_optimiser(InterDirStateTree)