203
"""Construct a WorkingTree for basedir.
211
"""Construct a WorkingTree instance. This is not a public API.
205
If the branch is not supplied, it is opened automatically.
206
If the branch is supplied, it must be the branch for this basedir.
207
(branch.base is not cross checked, because for remote branches that
208
would be meaningless).
213
:param branch: A branch to override probing for the branch.
210
215
self._format = _format
211
216
self.bzrdir = _bzrdir
212
217
if not _internal:
213
# not created via open etc.
214
warnings.warn("WorkingTree() is deprecated as of bzr version 0.8. "
215
"Please use bzrdir.open_workingtree or WorkingTree.open().",
218
wt = WorkingTree.open(basedir)
219
self._branch = wt.branch
220
self.basedir = wt.basedir
221
self._control_files = wt._control_files
222
self._hashcache = wt._hashcache
223
self._set_inventory(wt._inventory, dirty=False)
224
self._format = wt._format
225
self.bzrdir = wt.bzrdir
218
raise errors.BzrError("Please use bzrdir.open_workingtree or "
219
"WorkingTree.open() to obtain a WorkingTree.")
226
220
assert isinstance(basedir, basestring), \
227
221
"base directory %r is not a string" % basedir
228
222
basedir = safe_unicode(basedir)
229
223
mutter("opening working tree %r", basedir)
230
224
if deprecated_passed(branch):
232
warnings.warn("WorkingTree(..., branch=XXX) is deprecated"
233
" as of bzr 0.8. Please use bzrdir.open_workingtree() or"
234
" WorkingTree.open().",
238
225
self._branch = branch
240
227
self._branch = self.bzrdir.open_branch()
470
465
incorrectly attributed to CURRENT_REVISION (but after committing, the
471
466
attribution will be correct).
468
file_id = osutils.safe_file_id(file_id)
473
469
basis = self.basis_tree()
474
changes = self._iter_changes(basis, True, [file_id]).next()
475
changed_content, kind = changes[2], changes[6]
476
if not changed_content:
477
return basis.annotate_iter(file_id)
481
if kind[0] != 'file':
484
old_lines = list(basis.annotate_iter(file_id))
486
for tree in self.branch.repository.revision_trees(
487
self.get_parent_ids()[1:]):
488
if file_id not in tree:
490
old.append(list(tree.annotate_iter(file_id)))
491
return annotate.reannotate(old, self.get_file(file_id).readlines(),
472
changes = self._iter_changes(basis, True, [self.id2path(file_id)],
473
require_versioned=True).next()
474
changed_content, kind = changes[2], changes[6]
475
if not changed_content:
476
return basis.annotate_iter(file_id)
480
if kind[0] != 'file':
483
old_lines = list(basis.annotate_iter(file_id))
485
for tree in self.branch.repository.revision_trees(
486
self.get_parent_ids()[1:]):
487
if file_id not in tree:
489
old.append(list(tree.annotate_iter(file_id)))
490
return annotate.reannotate(old, self.get_file(file_id).readlines(),
494
495
def get_parent_ids(self):
495
496
"""See Tree.get_parent_ids.
574
580
__contains__ = has_id
576
582
def get_file_size(self, file_id):
583
file_id = osutils.safe_file_id(file_id)
577
584
return os.path.getsize(self.id2abspath(file_id))
580
587
def get_file_sha1(self, file_id, path=None, stat_value=None):
588
file_id = osutils.safe_file_id(file_id)
582
590
path = self._inventory.id2path(file_id)
583
591
return self._hashcache.get_sha1(path, stat_value)
585
593
def get_file_mtime(self, file_id, path=None):
594
file_id = osutils.safe_file_id(file_id)
587
path = self._inventory.id2path(file_id)
596
path = self.inventory.id2path(file_id)
588
597
return os.lstat(self.abspath(path)).st_mtime
590
599
if not supports_executable():
591
600
def is_executable(self, file_id, path=None):
601
file_id = osutils.safe_file_id(file_id)
592
602
return self._inventory[file_id].executable
594
604
def is_executable(self, file_id, path=None):
596
path = self._inventory.id2path(file_id)
606
file_id = osutils.safe_file_id(file_id)
607
path = self.id2path(file_id)
597
608
mode = os.lstat(self.abspath(path)).st_mode
598
609
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
611
@needs_tree_write_lock
601
612
def _add(self, files, ids, kinds):
602
613
"""See MutableTree._add."""
603
614
# TODO: Re-adding a file that is removed in the working copy
850
876
def get_symlink_target(self, file_id):
877
file_id = osutils.safe_file_id(file_id)
851
878
return os.readlink(self.id2abspath(file_id))
853
def file_class(self, filename):
854
if self.path2id(filename):
856
elif self.is_ignored(filename):
881
def subsume(self, other_tree):
882
def add_children(inventory, entry):
883
for child_entry in entry.children.values():
884
inventory._byid[child_entry.file_id] = child_entry
885
if child_entry.kind == 'directory':
886
add_children(inventory, child_entry)
887
if other_tree.get_root_id() == self.get_root_id():
888
raise errors.BadSubsumeSource(self, other_tree,
889
'Trees have the same root')
891
other_tree_path = self.relpath(other_tree.basedir)
892
except errors.PathNotChild:
893
raise errors.BadSubsumeSource(self, other_tree,
894
'Tree is not contained by the other')
895
new_root_parent = self.path2id(osutils.dirname(other_tree_path))
896
if new_root_parent is None:
897
raise errors.BadSubsumeSource(self, other_tree,
898
'Parent directory is not versioned.')
899
# We need to ensure that the result of a fetch will have a
900
# versionedfile for the other_tree root, and only fetching into
901
# RepositoryKnit2 guarantees that.
902
if not self.branch.repository.supports_rich_root():
903
raise errors.SubsumeTargetNeedsUpgrade(other_tree)
904
other_tree.lock_tree_write()
906
new_parents = other_tree.get_parent_ids()
907
other_root = other_tree.inventory.root
908
other_root.parent_id = new_root_parent
909
other_root.name = osutils.basename(other_tree_path)
910
self.inventory.add(other_root)
911
add_children(self.inventory, other_root)
912
self._write_inventory(self.inventory)
913
# normally we don't want to fetch whole repositories, but i think
914
# here we really do want to consolidate the whole thing.
915
for parent_id in other_tree.get_parent_ids():
916
self.branch.fetch(other_tree.branch, parent_id)
917
self.add_parent_tree_id(parent_id)
920
other_tree.bzrdir.retire_bzrdir()
922
@needs_tree_write_lock
923
def extract(self, file_id, format=None):
924
"""Extract a subtree from this tree.
926
A new branch will be created, relative to the path for this tree.
930
segments = osutils.splitpath(path)
931
transport = self.branch.bzrdir.root_transport
932
for name in segments:
933
transport = transport.clone(name)
936
except errors.FileExists:
940
sub_path = self.id2path(file_id)
941
branch_transport = mkdirs(sub_path)
943
format = bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
945
branch_transport.mkdir('.')
946
except errors.FileExists:
948
branch_bzrdir = format.initialize_on_transport(branch_transport)
950
repo = branch_bzrdir.find_repository()
951
except errors.NoRepositoryPresent:
952
repo = branch_bzrdir.create_repository()
953
assert repo.supports_rich_root()
955
if not repo.supports_rich_root():
956
raise errors.RootNotRich()
957
new_branch = branch_bzrdir.create_branch()
958
new_branch.pull(self.branch)
959
for parent_id in self.get_parent_ids():
960
new_branch.fetch(self.branch, parent_id)
961
tree_transport = self.bzrdir.root_transport.clone(sub_path)
962
if tree_transport.base != branch_transport.base:
963
tree_bzrdir = format.initialize_on_transport(tree_transport)
964
branch.BranchReferenceFormat().initialize(tree_bzrdir, new_branch)
966
tree_bzrdir = branch_bzrdir
967
wt = tree_bzrdir.create_workingtree(NULL_REVISION)
968
wt.set_parent_ids(self.get_parent_ids())
969
my_inv = self.inventory
970
child_inv = Inventory(root_id=None)
971
new_root = my_inv[file_id]
972
my_inv.remove_recursive_id(file_id)
973
new_root.parent_id = None
974
child_inv.add(new_root)
975
self._write_inventory(my_inv)
976
wt._write_inventory(child_inv)
979
def _serialize(self, inventory, out_file):
980
xml5.serializer_v5.write_inventory(self._inventory, out_file)
982
def _deserialize(selt, in_file):
983
return xml5.serializer_v5.read_inventory(in_file)
862
986
"""Write the in memory inventory to disk."""
1900
2102
file_id=self.path2id(conflicted)))
1901
2103
return conflicts
2105
def walkdirs(self, prefix=""):
2106
"""Walk the directories of this tree.
2108
This API returns a generator, which is only valid during the current
2109
tree transaction - within a single lock_read or lock_write duration.
2111
If the tree is not locked, it may cause an error to be raised, depending
2112
on the tree implementation.
2114
disk_top = self.abspath(prefix)
2115
if disk_top.endswith('/'):
2116
disk_top = disk_top[:-1]
2117
top_strip_len = len(disk_top) + 1
2118
inventory_iterator = self._walkdirs(prefix)
2119
disk_iterator = osutils.walkdirs(disk_top, prefix)
2121
current_disk = disk_iterator.next()
2122
disk_finished = False
2124
if e.errno != errno.ENOENT:
2127
disk_finished = True
2129
current_inv = inventory_iterator.next()
2130
inv_finished = False
2131
except StopIteration:
2134
while not inv_finished or not disk_finished:
2135
if not disk_finished:
2136
# strip out .bzr dirs
2137
if current_disk[0][1][top_strip_len:] == '':
2138
# osutils.walkdirs can be made nicer -
2139
# yield the path-from-prefix rather than the pathjoined
2141
bzrdir_loc = bisect_left(current_disk[1], ('.bzr', '.bzr'))
2142
if current_disk[1][bzrdir_loc][0] == '.bzr':
2143
# we dont yield the contents of, or, .bzr itself.
2144
del current_disk[1][bzrdir_loc]
2146
# everything is unknown
2149
# everything is missing
2152
direction = cmp(current_inv[0][0], current_disk[0][0])
2154
# disk is before inventory - unknown
2155
dirblock = [(relpath, basename, kind, stat, None, None) for
2156
relpath, basename, kind, stat, top_path in current_disk[1]]
2157
yield (current_disk[0][0], None), dirblock
2159
current_disk = disk_iterator.next()
2160
except StopIteration:
2161
disk_finished = True
2163
# inventory is before disk - missing.
2164
dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
2165
for relpath, basename, dkind, stat, fileid, kind in
2167
yield (current_inv[0][0], current_inv[0][1]), dirblock
2169
current_inv = inventory_iterator.next()
2170
except StopIteration:
2173
# versioned present directory
2174
# merge the inventory and disk data together
2176
for relpath, subiterator in itertools.groupby(sorted(
2177
current_inv[1] + current_disk[1], key=operator.itemgetter(0)), operator.itemgetter(1)):
2178
path_elements = list(subiterator)
2179
if len(path_elements) == 2:
2180
inv_row, disk_row = path_elements
2181
# versioned, present file
2182
dirblock.append((inv_row[0],
2183
inv_row[1], disk_row[2],
2184
disk_row[3], inv_row[4],
2186
elif len(path_elements[0]) == 5:
2188
dirblock.append((path_elements[0][0],
2189
path_elements[0][1], path_elements[0][2],
2190
path_elements[0][3], None, None))
2191
elif len(path_elements[0]) == 6:
2192
# versioned, absent file.
2193
dirblock.append((path_elements[0][0],
2194
path_elements[0][1], 'unknown', None,
2195
path_elements[0][4], path_elements[0][5]))
2197
raise NotImplementedError('unreachable code')
2198
yield current_inv[0], dirblock
2200
current_inv = inventory_iterator.next()
2201
except StopIteration:
2204
current_disk = disk_iterator.next()
2205
except StopIteration:
2206
disk_finished = True
2208
def _walkdirs(self, prefix=""):
2209
_directory = 'directory'
2210
# get the root in the inventory
2211
inv = self.inventory
2212
top_id = inv.path2id(prefix)
2216
pending = [(prefix, '', _directory, None, top_id, None)]
2219
currentdir = pending.pop()
2220
# 0 - relpath, 1- basename, 2- kind, 3- stat, 4-id, 5-kind
2221
top_id = currentdir[4]
2223
relroot = currentdir[0] + '/'
2226
# FIXME: stash the node in pending
2228
for name, child in entry.sorted_children():
2229
dirblock.append((relroot + name, name, child.kind, None,
2230
child.file_id, child.kind
2232
yield (currentdir[0], entry.file_id), dirblock
2233
# push the user specified dirs from dirblock
2234
for dir in reversed(dirblock):
2235
if dir[2] == _directory:
2238
@needs_tree_write_lock
2239
def auto_resolve(self):
2240
"""Automatically resolve text conflicts according to contents.
2242
Only text conflicts are auto_resolvable. Files with no conflict markers
2243
are considered 'resolved', because bzr always puts conflict markers
2244
into files that have text conflicts. The corresponding .THIS .BASE and
2245
.OTHER files are deleted, as per 'resolve'.
2246
:return: a tuple of ConflictLists: (un_resolved, resolved).
2248
un_resolved = _mod_conflicts.ConflictList()
2249
resolved = _mod_conflicts.ConflictList()
2250
conflict_re = re.compile('^(<{7}|={7}|>{7})')
2251
for conflict in self.conflicts():
2252
if (conflict.typestring != 'text conflict' or
2253
self.kind(conflict.file_id) != 'file'):
2254
un_resolved.append(conflict)
2256
my_file = open(self.id2abspath(conflict.file_id), 'rb')
2258
for line in my_file:
2259
if conflict_re.search(line):
2260
un_resolved.append(conflict)
2263
resolved.append(conflict)
2266
resolved.remove_files(self)
2267
self.set_conflicts(un_resolved)
2268
return un_resolved, resolved
1904
2271
class WorkingTree2(WorkingTree):
1905
2272
"""This is the Format 2 working tree.