1
# Copyright (C) 2005, 2006 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
28
from bzrlib.lazy_import import lazy_import
29
lazy_import(globals(), """
30
from bisect import bisect_left
32
from copy import deepcopy
43
conflicts as _mod_conflicts,
59
from bzrlib.transport import get_transport
63
from bzrlib import symbol_versioning
64
from bzrlib.decorators import needs_read_lock, needs_write_lock
65
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID, make_entry
66
from bzrlib.lockable_files import LockableFiles, TransportLock
67
from bzrlib.lockdir import LockDir
68
import bzrlib.mutabletree
69
from bzrlib.mutabletree import needs_tree_write_lock
70
from bzrlib.osutils import (
82
from bzrlib.trace import mutter, note
83
from bzrlib.transport.local import LocalTransport
84
from bzrlib.progress import DummyProgress, ProgressPhase
85
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
86
from bzrlib.rio import RioReader, rio_file, Stanza
87
from bzrlib.symbol_versioning import (deprecated_passed,
95
from bzrlib.tree import Tree
96
from bzrlib.workingtree import WorkingTree, WorkingTree3, WorkingTreeFormat3
99
class WorkingTree4(WorkingTree3):
100
"""This is the Format 4 working tree.
102
This differs from WorkingTree3 by:
103
- having a consolidated internal dirstate.
104
- not having a regular inventory attribute.
106
This is new in bzr TODO FIXME SETMEBEFORE MERGE.
109
def __init__(self, basedir,
114
"""Construct a WorkingTree for basedir.
116
If the branch is not supplied, it is opened automatically.
117
If the branch is supplied, it must be the branch for this basedir.
118
(branch.base is not cross checked, because for remote branches that
119
would be meaningless).
121
self._format = _format
122
self.bzrdir = _bzrdir
123
from bzrlib.hashcache import HashCache
124
from bzrlib.trace import note, mutter
125
assert isinstance(basedir, basestring), \
126
"base directory %r is not a string" % basedir
127
basedir = safe_unicode(basedir)
128
mutter("opening working tree %r", basedir)
129
self._branch = branch
130
assert isinstance(self.branch, bzrlib.branch.Branch), \
131
"branch %r is not a Branch" % self.branch
132
self.basedir = realpath(basedir)
133
# if branch is at our basedir and is a format 6 or less
134
# assume all other formats have their own control files.
135
assert isinstance(_control_files, LockableFiles), \
136
"_control_files must be a LockableFiles, not %r" % _control_files
137
self._control_files = _control_files
138
# update the whole cache up front and write to disk if anything changed;
139
# in the future we might want to do this more selectively
140
# two possible ways offer themselves : in self._unlock, write the cache
141
# if needed, or, when the cache sees a change, append it to the hash
142
# cache file, and have the parser take the most recent entry for a
144
cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
145
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
147
# is this scan needed ? it makes things kinda slow.
156
# during a read or write lock these objects are set, and are
157
# None the rest of the time.
158
self._dirstate = None
159
self._inventory = None
162
@needs_tree_write_lock
163
def _add(self, files, ids, kinds):
164
"""See MutableTree._add."""
165
state = self.current_dirstate()
166
for f, file_id, kind in zip(files, ids, kinds):
171
file_id = generate_ids.gen_file_id(f)
172
# deliberately add the file with no cached stat or sha1
173
# - on the first access it will be gathered, and we can
174
# always change this once tests are all passing.
175
state.add(f, file_id, kind, None, '')
178
def current_dirstate(self):
179
"""Return the current dirstate object.
181
This is not part of the tree interface and only exposed for ease of
184
:raises errors.NotWriteLocked: when not in a lock.
185
XXX: This should probably be errors.NotLocked.
187
if not self._control_files._lock_count:
188
raise errors.ObjectNotLocked(self)
189
if self._dirstate is not None:
190
return self._dirstate
191
local_path = self.bzrdir.get_workingtree_transport(None
192
).local_abspath('dirstate')
193
self._dirstate = dirstate.DirState.on_file(local_path)
194
return self._dirstate
197
"""Write all cached data to disk."""
198
if self._control_files._lock_mode != 'w':
199
raise errors.NotWriteLocked(self)
200
self.current_dirstate().save()
201
self._inventory = None
204
def _generate_inventory(self):
205
"""Create and set self.inventory from the dirstate object.
207
This is relatively expensive: we have to walk the entire dirstate.
208
Ideally we would not, and can deprecate this function.
210
dirstate = self.current_dirstate()
211
rows = self._dirstate._iter_rows()
212
root_row = rows.next()
213
inv = Inventory(root_id=root_row[0][3].decode('utf8'))
215
dirname, name, kind, fileid_utf8, size, stat, link_or_sha1 = line[0]
217
# not in this revision tree.
219
parent_id = inv[inv.path2id(dirname.decode('utf8'))].file_id
220
file_id = fileid_utf8.decode('utf8')
221
entry = make_entry(kind, name.decode('utf8'), parent_id, file_id)
223
#entry.executable = executable
224
#entry.text_size = size
225
#entry.text_sha1 = sha1
228
self._inventory = inv
230
def get_file_sha1(self, file_id, path=None, stat_value=None):
232
# path = self.inventory.id2path(file_id)
233
# # now lookup row by path
234
row, parents = self._get_row(file_id=file_id)
235
assert row is not None, 'what error should this raise'
237
# if row stat is valid, use cached sha1, else, get a new sha1.
238
path = (row[0] + '/' + row[1]).strip('/').decode('utf8')
239
return self._hashcache.get_sha1(path, stat_value)
241
def _get_inventory(self):
242
"""Get the inventory for the tree. This is only valid within a lock."""
243
if self._inventory is not None:
244
return self._inventory
245
self._generate_inventory()
246
return self._inventory
248
inventory = property(_get_inventory,
249
doc="Inventory of this Tree")
252
def get_parent_ids(self):
253
"""See Tree.get_parent_ids.
255
This implementation requests the ids list from the dirstate file.
257
return self.current_dirstate().get_parent_ids()
260
def get_root_id(self):
261
"""Return the id of this trees root"""
262
return self.current_dirstate()._iter_rows().next()[0][3].decode('utf8')
264
def _get_row(self, file_id=None, path=None):
265
"""Get the dirstate row for file_id or path.
267
If either file_id or path is supplied, it is used as the key to lookup.
268
If both are supplied, the fastest lookup is used, and an error is
269
raised if they do not both point at the same row.
271
:param file_id: An optional unicode file_id to be looked up.
272
:param path: An optional unicode path to be looked up.
273
:return: The dirstate row tuple for path/file_id, or (None, None)
275
if file_id is None and path is None:
276
raise errors.BzrError('must supply file_id or path')
277
state = self.current_dirstate()
278
state._read_dirblocks_if_needed()
279
if file_id is not None:
280
fileid_utf8 = file_id.encode('utf8')
282
# path lookups are faster
283
row = state._get_row(path)
285
if row[0][3] != fileid_utf8:
286
raise BzrError('integrity error ? : mismatching file_id and path')
289
for row in state._iter_rows():
290
if row[0][3] == fileid_utf8:
294
def has_id(self, file_id):
295
state = self.current_dirstate()
296
fileid_utf8 = file_id.encode('utf8')
297
row, parents = self._get_row(file_id)
300
return osutils.lexists(pathjoin(
301
self.basedir, row[0].decode('utf8'), row[1].decode('utf8')))
304
def id2path(self, fileid):
305
state = self.current_dirstate()
306
fileid_utf8 = fileid.encode('utf8')
307
for row, parents in state._iter_rows():
308
if row[3] == fileid_utf8:
309
return (row[0] + '/' + row[1]).decode('utf8').strip('/')
313
"""Iterate through file_ids for this tree.
315
file_ids are in a WorkingTree if they are in the working inventory
316
and the working file exists.
319
for row, parents in self.current_dirstate()._iter_rows():
322
path = pathjoin(self.basedir, row[0].decode('utf8'), row[1].decode('utf8'))
323
if osutils.lexists(path):
324
result.append(row[3].decode('utf8'))
328
def _last_revision(self):
329
"""See Mutable.last_revision."""
330
parent_ids = self.current_dirstate().get_parent_ids()
332
return parent_ids[0].decode('utf8')
336
@needs_tree_write_lock
337
def move(self, from_paths, to_dir=None, after=False, **kwargs):
338
"""See WorkingTree.move()."""
342
state = self.current_dirstate()
344
# check for deprecated use of signature
346
to_dir = kwargs.get('to_name', None)
348
raise TypeError('You must supply a target directory')
350
symbol_versioning.warn('The parameter to_name was deprecated'
351
' in version 0.13. Use to_dir instead',
354
assert not isinstance(from_paths, basestring)
355
to_dir_utf8 = to_dir.encode('utf8')
356
to_row_dirname, to_basename = os.path.split(to_dir_utf8)
357
# check destination directory
358
# get the details for it
359
to_row_block_index, to_row_row_index, dir_present, row_present = \
360
state._get_block_row_index(to_row_dirname, to_basename)
362
raise errors.BzrMoveFailedError('', to_dir,
363
errors.NotInWorkingDirectory(to_dir))
364
to_row = state._dirblocks[to_row_block_index][1][to_row_row_index][0]
365
# get a handle on the block itself.
366
to_block_index = state._ensure_block(
367
to_row_block_index, to_row_row_index, to_dir_utf8)
368
to_block = state._dirblocks[to_block_index]
369
to_abs = self.abspath(to_dir)
370
if not isdir(to_abs):
371
raise errors.BzrMoveFailedError('',to_dir,
372
errors.NotADirectory(to_abs))
374
if to_row[2] != 'directory':
375
raise errors.BzrMoveFailedError('',to_dir,
376
errors.NotADirectory(to_abs))
378
if self._inventory is not None:
379
update_inventory = True
381
to_dir_ie = inv[to_dir_id]
382
to_dir_id = to_row[3].decode('utf8')
384
update_inventory = False
386
# create rename entries and tuples
387
for from_rel in from_paths:
388
# from_rel is 'pathinroot/foo/bar'
389
from_dirname, from_tail = os.path.split(from_rel)
390
from_dirname = from_dirname.encode('utf8')
391
from_row = self._get_row(path=from_rel)
392
if from_row == (None, None):
393
raise errors.BzrMoveFailedError(from_rel,to_dir,
394
errors.NotVersionedError(path=str(from_rel)))
396
from_id = from_row[0][3].decode('utf8')
397
to_rel = pathjoin(to_dir, from_tail)
398
item_to_row = self._get_row(path=to_rel)
399
if item_to_row != (None, None):
400
raise errors.BzrMoveFailedError(from_rel, to_rel,
401
"Target is already versioned.")
403
if from_rel == to_rel:
404
raise errors.BzrMoveFailedError(from_rel, to_rel,
405
"Source and target are identical.")
407
from_missing = not self.has_filename(from_rel)
408
to_missing = not self.has_filename(to_rel)
415
raise errors.BzrMoveFailedError(from_rel, to_rel,
416
errors.NoSuchFile(path=to_rel,
417
extra="New file has not been created yet"))
419
# neither path exists
420
raise errors.BzrRenameFailedError(from_rel, to_rel,
421
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
423
if from_missing: # implicitly just update our path mapping
426
raise errors.RenameFailedFilesExist(from_rel, to_rel,
427
extra="(Use --after to update the Bazaar id)")
430
def rollback_rename():
431
"""A single rename has failed, roll it back."""
433
for rollback in reversed(rollbacks):
441
# perform the disk move first - its the most likely failure point.
442
from_rel_abs = self.abspath(from_rel)
443
to_rel_abs = self.abspath(to_rel)
445
osutils.rename(from_rel_abs, to_rel_abs)
447
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
448
rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
450
# perform the rename in the inventory next if needed: its easy
454
from_entry = inv[from_id]
455
current_parent = from_entry.parent_id
456
inv.rename(from_id, to_dir_id, from_tail)
458
lambda: inv.rename(from_id, current_parent, from_tail))
459
# finally do the rename in the dirstate, which is a little
460
# tricky to rollback, but least likely to need it.
461
basename = from_tail.encode('utf8')
462
old_block_index, old_row_index, dir_present, file_present = \
463
state._get_block_row_index(from_dirname, basename)
464
old_block = state._dirblocks[old_block_index][1]
466
old_row = old_block.pop(old_row_index)
467
rollbacks.append(lambda:old_block.insert(old_row_index, old_row))
468
# create new row in current block
469
new_row = ((to_block[0],) + old_row[0][1:], old_row[1])
470
insert_position = bisect_left(to_block[1], new_row)
471
to_block[1].insert(insert_position, new_row)
472
rollbacks.append(lambda:to_block[1].pop(insert_position))
473
if new_row[0][2] == 'directory':
474
import pdb;pdb.set_trace()
475
# if a directory, rename all the contents of child blocks
476
# adding rollbacks as each is inserted to remove them and
477
# restore the original
478
# TODO: large scale slice assignment.
480
# save old list region
481
# move up or down the old region
482
# add rollback to move the region back
483
# assign new list to new region
488
state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
491
return #rename_tuples
494
"""Initialize the state in this tree to be a new tree."""
498
def path2id(self, path):
499
"""Return the id for path in this tree."""
500
state = self.current_dirstate()
501
row = self._get_row(path=path)
502
if row == (None, None):
504
return row[0][3].decode('utf8')
506
def read_working_inventory(self):
507
"""Read the working inventory.
509
This is a meaningless operation for dirstate, but we obey it anyhow.
511
return self.inventory
514
def revision_tree(self, revision_id):
515
"""See Tree.revision_tree.
517
WorkingTree4 supplies revision_trees for any basis tree.
519
dirstate = self.current_dirstate()
520
parent_ids = dirstate.get_parent_ids()
521
if revision_id not in parent_ids:
522
raise errors.NoSuchRevisionInTree(self, revision_id)
523
if revision_id in dirstate.get_ghosts():
524
raise errors.NoSuchRevisionInTree(self, revision_id)
525
return DirStateRevisionTree(dirstate, revision_id,
526
self.branch.repository)
528
@needs_tree_write_lock
529
def set_last_revision(self, new_revision):
530
"""Change the last revision in the working tree."""
531
parents = self.get_parent_ids()
532
if new_revision in (NULL_REVISION, None):
533
assert len(parents) < 2, (
534
"setting the last parent to none with a pending merge is "
536
self.set_parent_ids([])
538
self.set_parent_ids([new_revision] + parents[1:],
539
allow_leftmost_as_ghost=True)
541
@needs_tree_write_lock
542
def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
543
"""Set the parent ids to revision_ids.
545
See also set_parent_trees. This api will try to retrieve the tree data
546
for each element of revision_ids from the trees repository. If you have
547
tree data already available, it is more efficient to use
548
set_parent_trees rather than set_parent_ids. set_parent_ids is however
549
an easier API to use.
551
:param revision_ids: The revision_ids to set as the parent ids of this
552
working tree. Any of these may be ghosts.
555
for revision_id in revision_ids:
557
revtree = self.branch.repository.revision_tree(revision_id)
558
# TODO: jam 20070213 KnitVersionedFile raises
559
# RevisionNotPresent rather than NoSuchRevision if a
560
# given revision_id is not present. Should Repository be
561
# catching it and re-raising NoSuchRevision?
562
except (errors.NoSuchRevision, errors.RevisionNotPresent):
564
trees.append((revision_id, revtree))
565
self.set_parent_trees(trees,
566
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
568
@needs_tree_write_lock
569
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
570
"""Set the parents of the working tree.
572
:param parents_list: A list of (revision_id, tree) tuples.
573
If tree is None, then that element is treated as an unreachable
574
parent tree - i.e. a ghost.
576
dirstate = self.current_dirstate()
577
if len(parents_list) > 0:
578
if not allow_leftmost_as_ghost and parents_list[0][1] is None:
579
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
582
# convert absent trees to the null tree, which we convert back to
584
for rev_id, tree in parents_list:
586
real_trees.append((rev_id, tree))
588
real_trees.append((rev_id,
589
self.branch.repository.revision_tree(None)))
590
ghosts.append(rev_id)
591
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
594
def _set_root_id(self, file_id):
595
"""See WorkingTree.set_root_id."""
596
state = self.current_dirstate()
597
state.set_path_id('', file_id)
598
self._dirty = state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED
601
"""Unlock in format 4 trees needs to write the entire dirstate."""
602
if self._control_files._lock_count == 1:
603
self._write_hashcache_if_dirty()
604
# eventually we should do signature checking during read locks for
606
if self._control_files._lock_mode == 'w':
609
self._dirstate = None
610
self._inventory = None
611
# reverse order of locking.
613
return self._control_files.unlock()
617
@needs_tree_write_lock
618
def unversion(self, file_ids):
619
"""Remove the file ids in file_ids from the current versioned set.
621
When a file_id is unversioned, all of its children are automatically
624
:param file_ids: The file ids to stop versioning.
625
:raises: NoSuchId if any fileid is not currently versioned.
629
state = self.current_dirstate()
630
state._read_dirblocks_if_needed()
631
ids_to_unversion = set()
632
for fileid in file_ids:
633
ids_to_unversion.add(fileid.encode('utf8'))
634
paths_to_unversion = set()
636
# check if the root is to be unversioned, if so, assert for now.
637
# make a copy of the _dirblocks data
639
# skip paths in paths_to_unversion
640
# skip ids in ids_to_unversion, and add their paths to
641
# paths_to_unversion if they are a directory
642
# if there are any un-unversioned ids at the end, raise
643
if state._root_row[0][3] in ids_to_unversion:
644
# I haven't written the code to unversion / yet - it should be
646
raise errors.BzrError('Unversioning the / is not currently supported')
649
for block in state._dirblocks:
650
# first check: is the path one to remove - it or its children
652
for path in paths_to_unversion:
653
if (block[0].startswith(path) and
654
(len(block[0]) == len(path) or
655
block[0][len(path)] == '/')):
656
# this path should be deleted
659
# TODO: trim paths_to_unversion as we pass by paths
661
# this block is to be deleted. skip it.
663
# copy undeleted rows from within the the block
664
new_blocks.append((block[0], []))
665
new_row = new_blocks[-1][1]
666
for row, row_parents in block[1]:
667
if row[3] not in ids_to_unversion:
668
new_row.append((row, row_parents))
670
# skip the row, and if its a dir mark its path to be removed
671
if row[2] == 'directory':
672
paths_to_unversion.add((row[0] + '/' + row[1]).strip('/'))
674
deleted_rows.append((row[3], row_parents))
675
ids_to_unversion.remove(row[3])
677
raise errors.NoSuchId(self, iter(ids_to_unversion).next())
678
state._dirblocks = new_blocks
679
for fileid_utf8, parents in deleted_rows:
680
state.add_deleted(fileid_utf8, parents)
681
state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
683
# have to change the legacy inventory too.
684
if self._inventory is not None:
685
for file_id in file_ids:
686
self._inventory.remove_recursive_id(file_id)
688
@needs_tree_write_lock
689
def _write_inventory(self, inv):
690
"""Write inventory as the current inventory."""
691
assert not self._dirty, "attempting to write an inventory when the dirstate is dirty will cause data loss"
692
self.current_dirstate().set_state_from_inventory(inv)
697
class WorkingTreeFormat4(WorkingTreeFormat3):
698
"""The first consolidated dirstate working tree format.
701
- exists within a metadir controlling .bzr
702
- includes an explicit version marker for the workingtree control
703
files, separate from the BzrDir format
704
- modifies the hash cache format
705
- is new in bzr TODO FIXME SETBEFOREMERGE
706
- uses a LockDir to guard access to it.
709
def get_format_string(self):
710
"""See WorkingTreeFormat.get_format_string()."""
711
return "Bazaar Working Tree format 4\n"
713
def get_format_description(self):
714
"""See WorkingTreeFormat.get_format_description()."""
715
return "Working tree format 4"
717
def initialize(self, a_bzrdir, revision_id=None):
718
"""See WorkingTreeFormat.initialize().
720
revision_id allows creating a working tree at a different
721
revision than the branch is at.
723
if not isinstance(a_bzrdir.transport, LocalTransport):
724
raise errors.NotLocalUrl(a_bzrdir.transport.base)
725
transport = a_bzrdir.get_workingtree_transport(self)
726
control_files = self._open_control_files(a_bzrdir)
727
control_files.create_lock()
728
control_files.lock_write()
729
control_files.put_utf8('format', self.get_format_string())
730
branch = a_bzrdir.open_branch()
731
if revision_id is None:
732
revision_id = branch.last_revision()
733
local_path = transport.local_abspath('dirstate')
734
dirstate.DirState.initialize(local_path)
735
wt = WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
739
_control_files=control_files)
743
#wt.current_dirstate().set_path_id('', NEWROOT)
744
wt.set_last_revision(revision_id)
746
basis = wt.basis_tree()
748
transform.build_tree(basis, wt)
751
control_files.unlock()
756
def _open(self, a_bzrdir, control_files):
757
"""Open the tree itself.
759
:param a_bzrdir: the dir for the tree.
760
:param control_files: the control files for the tree.
762
return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
763
branch=a_bzrdir.open_branch(),
766
_control_files=control_files)
769
class DirStateRevisionTree(Tree):
770
"""A revision tree pulling the inventory from a dirstate."""
772
def __init__(self, dirstate, revision_id, repository):
773
self._dirstate = dirstate
774
self._revision_id = revision_id
775
self._repository = repository
776
self._inventory = None
779
def annotate_iter(self, file_id):
780
"""See Tree.annotate_iter"""
781
w = self._repository.weave_store.get_weave(file_id,
782
self._repository.get_transaction())
783
return w.annotate_iter(self.inventory[file_id].revision)
785
def _comparison_data(self, entry, path):
786
"""See Tree._comparison_data."""
788
return None, False, None
789
# trust the entry as RevisionTree does, but this may not be
790
# sensible: the entry might not have come from us?
791
return entry.kind, entry.executable, None
793
def _file_size(self, entry, stat_value):
794
return entry.text_size
796
def _generate_inventory(self):
797
"""Create and set self.inventory from the dirstate object.
799
This is relatively expensive: we have to walk the entire dirstate.
800
Ideally we would not, and instead would """
801
assert self._locked, 'cannot generate inventory of an unlocked '\
802
'dirstate revision tree'
803
assert self._revision_id in self._dirstate.get_parent_ids(), \
804
'parent %s has disappeared from %s' % (
805
self._revision_id, self._dirstate.get_parent_ids())
806
parent_index = self._dirstate.get_parent_ids().index(self._revision_id)
807
rows = self._dirstate._iter_rows()
808
root_row = rows.next()
809
inv = Inventory(root_id=root_row[0][3].decode('utf8'),
810
revision_id=self._revision_id)
812
revid, kind, dirname, name, size, executable, sha1 = line[1][parent_index]
814
# not in this revision tree.
816
parent_id = inv[inv.path2id(dirname.decode('utf8'))].file_id
817
file_id = line[0][3].decode('utf8')
818
entry = make_entry(kind, name.decode('utf8'), parent_id, file_id)
819
entry.revision = revid.decode('utf8')
821
entry.executable = executable
822
entry.text_size = size
823
entry.text_sha1 = sha1
825
self._inventory = inv
827
def get_file_sha1(self, file_id, path=None, stat_value=None):
828
# TODO: if path is present, fast-path on that, as inventory
829
# might not be present
830
ie = self.inventory[file_id]
831
if ie.kind == "file":
835
def get_file(self, file_id):
836
return StringIO(self.get_file_text(file_id))
838
def get_file_lines(self, file_id):
839
ie = self.inventory[file_id]
840
return self._repository.weave_store.get_weave(file_id,
841
self._repository.get_transaction()).get_lines(ie.revision)
843
def get_file_size(self, file_id):
844
return self.inventory[file_id].text_size
846
def get_file_text(self, file_id):
847
return ''.join(self.get_file_lines(file_id))
849
def get_revision_id(self):
850
"""Return the revision id for this tree."""
851
return self._revision_id
853
def _get_inventory(self):
854
if self._inventory is not None:
855
return self._inventory
856
self._generate_inventory()
857
return self._inventory
859
inventory = property(_get_inventory,
860
doc="Inventory of this Tree")
862
def get_parent_ids(self):
863
"""The parents of a tree in the dirstate are not cached."""
864
return self._repository.get_revision(self._revision_id).parent_ids
866
def has_filename(self, filename):
867
return bool(self.inventory.path2id(filename))
869
def kind(self, file_id):
870
return self.inventory[file_id].kind
872
def is_executable(self, file_id, path=None):
873
ie = self.inventory[file_id]
874
if ie.kind != "file":
878
def list_files(self, include_root=False):
879
# We use a standard implementation, because DirStateRevisionTree is
880
# dealing with one of the parents of the current state
881
inv = self._get_inventory()
882
entries = inv.iter_entries()
883
if self.inventory.root is not None and not include_root:
885
for path, entry in entries:
886
yield path, 'V', entry.kind, entry.file_id, entry
889
"""Lock the tree for a set of operations."""
892
def path2id(self, path):
893
"""Return the id for path in this tree."""
894
row = self._dirstate._get_row(path.encode('utf8'))
895
if row == (None, None):
897
return row[0][3].decode('utf8')
900
"""Unlock, freeing any cache memory used during the lock."""
901
# outside of a lock, the inventory is suspect: release it.
904
self._inventory = None
907
def walkdirs(self, prefix=""):
908
# TODO: jam 20070215 This is the cheap way by cheating and using the
909
# RevisionTree implementation.
910
# This should be cleaned up to use the much faster Dirstate code
911
# This is a little tricky, though, because the dirstate is
912
# indexed by current path, not by parent path.
913
# So for now, we just build up the parent inventory, and extract
914
# it the same way RevisionTree does.
915
_directory = 'directory'
916
inv = self._get_inventory()
917
top_id = inv.path2id(prefix)
921
pending = [(prefix, top_id)]
924
relpath, file_id = pending.pop()
925
# 0 - relpath, 1- file-id
927
relroot = relpath + '/'
930
# FIXME: stash the node in pending
932
for name, child in entry.sorted_children():
933
toppath = relroot + name
934
dirblock.append((toppath, name, child.kind, None,
935
child.file_id, child.kind
937
yield (relpath, entry.file_id), dirblock
938
# push the user specified dirs from dirblock
939
for dir in reversed(dirblock):
940
if dir[2] == _directory:
941
pending.append((dir[0], dir[4]))