67
67
entirely in memory.
69
69
For now, we are not treating MutableTree as an interface to provide
70
conformance tests for - rather we are testing MemoryTree specifically, and
70
conformance tests for - rather we are testing MemoryTree specifically, and
71
71
interface testing implementations of WorkingTree.
73
73
A mutable tree always has an associated Branch and BzrDir object - the
74
74
branch and bzrdir attributes.
76
def __init__(self, *args, **kw):
77
super(MutableTree, self).__init__(*args, **kw)
78
# Is this tree on a case-insensitive or case-preserving file-system?
79
# Sub-classes may initialize to False if they detect they are being
80
# used on media which doesn't differentiate the case of names.
81
self.case_sensitive = True
83
77
@needs_tree_write_lock
84
78
def add(self, files, ids=None, kinds=None):
118
108
ids = [None] * len(files)
120
if not (len(ids) == len(files)):
121
raise AssertionError()
110
assert(len(ids) == len(files))
111
ids = [osutils.safe_file_id(file_id) for file_id in ids]
122
113
if kinds is None:
123
114
kinds = [None] * len(files)
124
elif not len(kinds) == len(files):
125
raise AssertionError()
116
assert(len(kinds) == len(files))
127
118
# generic constraint checks:
128
119
if self.is_control_filename(f):
129
120
raise errors.ForbiddenControlFileError(filename=f)
130
fp = osutils.splitpath(f)
131
# fill out file kinds for all files [not needed when we stop
122
# fill out file kinds for all files [not needed when we stop
132
123
# caring about the instantaneous file kind within a uncommmitted tree
134
125
self._gather_kinds(files, kinds)
168
159
def apply_inventory_delta(self, changes):
169
160
"""Apply changes to the inventory as an atomic operation.
171
:param changes: An inventory delta to apply to the working tree's
174
:seealso Inventory.apply_delta: For details on the changes parameter.
162
The argument is a set of changes to apply. It must describe a
163
valid result, but the order is not important. Specifically,
164
intermediate stages *may* be invalid, such as when two files
167
The changes should be structured as a list of tuples, of the form
168
(old_path, new_path, file_id, new_entry). For creation, old_path
169
must be None. For deletion, new_path and new_entry must be None.
170
file_id is always non-None. For renames and other mutations, all
171
values must be non-None.
173
If the new_entry is a directory, its children should be an empty
174
dict. Children are handled by apply_inventory_delta itself.
176
:param changes: A list of tuples for the change to apply:
177
[(old_path, new_path, file_id, new_inventory_entry), ...]
177
180
inv = self.inventory
178
inv.apply_delta(changes)
182
for old_path, file_id in sorted(((op, f) for op, np, f, e in changes
183
if op is not None), reverse=True):
184
if file_id not in inv:
186
children[file_id] = getattr(inv[file_id], 'children', {})
187
inv.remove_recursive_id(file_id)
188
for new_path, new_entry in sorted((np, e) for op, np, f, e in
189
changes if np is not None):
190
if getattr(new_entry, 'children', None) is not None:
191
new_entry.children = children.get(new_entry.file_id, {})
179
193
self._write_inventory(inv)
181
195
@needs_write_lock
184
198
# avoid circular imports
185
199
from bzrlib import commit
186
possible_master_transports=[]
187
revprops = commit.Commit.update_revprops(
190
kwargs.pop('authors', None),
191
kwargs.pop('author', None),
192
kwargs.get('local', False),
193
possible_master_transports)
202
if not 'branch-nick' in revprops:
203
revprops['branch-nick'] = self.branch.nick
194
204
# args for wt.commit start at message from the Commit.commit method,
195
205
args = (message, ) + args
196
for hook in MutableTree.hooks['start_commit']:
198
206
committed_id = commit.Commit().commit(working_tree=self,
200
possible_master_transports=possible_master_transports,
202
post_hook_params = PostCommitHookParams(self)
203
for hook in MutableTree.hooks['post_commit']:
204
hook(post_hook_params)
207
revprops=revprops, *args, **kwargs)
205
208
return committed_id
207
210
def _gather_kinds(self, files, kinds):
209
212
raise NotImplementedError(self._gather_kinds)
212
def has_changes(self, _from_tree=None):
213
"""Quickly check that the tree contains at least one commitable change.
215
:param _from_tree: tree to compare against to find changes (default to
216
the basis tree and is intended to be used by tests).
218
:return: True if a change is found. False otherwise
220
# Check pending merges
221
if len(self.get_parent_ids()) > 1:
223
if _from_tree is None:
224
_from_tree = self.basis_tree()
225
changes = self.iter_changes(_from_tree)
227
change = changes.next()
228
# Exclude root (talk about black magic... --vila 20090629)
229
if change[4] == (None, None):
230
change = changes.next()
232
except StopIteration:
237
def check_changed_or_out_of_date(self, strict, opt_name,
238
more_error, more_warning):
239
"""Check the tree for uncommitted changes and branch synchronization.
241
If strict is None and not set in the config files, a warning is issued.
242
If strict is True, an error is raised.
243
If strict is False, no checks are done and no warning is issued.
245
:param strict: True, False or None, searched in branch config if None.
247
:param opt_name: strict option name to search in config file.
249
:param more_error: Details about how to avoid the check.
251
:param more_warning: Details about what is happening.
254
strict = self.branch.get_config().get_user_option_as_bool(opt_name)
255
if strict is not False:
257
if (self.has_changes()):
258
err_class = errors.UncommittedChanges
259
elif self.last_revision() != self.branch.last_revision():
260
# The tree has lost sync with its branch, there is little
261
# chance that the user is aware of it but he can still force
262
# the action with --no-strict
263
err_class = errors.OutOfDateTree
264
if err_class is not None:
266
err = err_class(self, more=more_warning)
267
# We don't want to interrupt the user if he expressed no
268
# preference about strict.
269
trace.warning('%s', err._format())
271
err = err_class(self, more=more_error)
275
215
def last_revision(self):
276
216
"""Return the revision id of the last commit performed in this tree.
278
218
In early tree formats the result of last_revision is the same as the
279
219
branch last_revision, but that is no longer the case for modern tree
282
222
last_revision returns the left most parent id, or None if there are no
318
258
raise NotImplementedError(self.mkdir)
320
def _observed_sha1(self, file_id, path, (sha1, stat_value)):
321
"""Tell the tree we have observed a paths sha1.
323
The intent of this function is to allow trees that have a hashcache to
324
update the hashcache during commit. If the observed file is too new
325
(based on the stat_value) to be safely hash-cached the tree will ignore
328
The default implementation does nothing.
330
:param file_id: The file id
331
:param path: The file path
332
:param sha1: The sha 1 that was observed.
333
:param stat_value: A stat result for the file the sha1 was read from.
337
def _fix_case_of_inventory_path(self, path):
338
"""If our tree isn't case sensitive, return the canonical path"""
339
if not self.case_sensitive:
340
path = self.get_canonical_inventory_path(path)
344
def put_file_bytes_non_atomic(self, file_id, bytes):
345
"""Update the content of a file in the tree.
347
Note that the file is written in-place rather than being
348
written to a temporary location and renamed. As a consequence,
349
readers can potentially see the file half-written.
351
:param file_id: file-id of the file
352
:param bytes: the new file contents
354
raise NotImplementedError(self.put_file_bytes_non_atomic)
356
260
def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
357
261
"""Set the parents ids of the working tree.
376
280
This is designed more towards DWIM for humans than API clarity.
377
281
For the specific behaviour see the help for cmd_add().
379
:param file_list: List of zero or more paths. *NB: these are
380
interpreted relative to the process cwd, not relative to the
381
tree.* (Add and most other tree methods use tree-relative
383
283
:param action: A reporter to be called with the inventory, parent_ie,
384
path and kind of the path being added. It may return a file_id if
284
path and kind of the path being added. It may return a file_id if
385
285
a specific one should be used.
386
286
:param save: Save the inventory after completing the adds. If False
387
287
this provides dry-run functionality by doing the add and not saving
409
308
user_dirs = set()
410
conflicts_related = set()
411
# Not all mutable trees can have conflicts
412
if getattr(self, 'conflicts', None) is not None:
413
# Collect all related files without checking whether they exist or
414
# are versioned. It's cheaper to do that once for all conflicts
415
# than trying to find the relevant conflict for each added file.
416
for c in self.conflicts():
417
conflicts_related.update(c.associated_filenames())
419
# expand any symlinks in the directory part, while leaving the
421
file_list = map(osutils.normalizepath, file_list)
423
# validate user file paths and convert all paths to tree
310
# validate user file paths and convert all paths to tree
424
311
# relative : it's cheaper to make a tree relative path an abspath
425
# than to convert an abspath to tree relative, and it's cheaper to
426
# perform the canonicalization in bulk.
427
for filepath in osutils.canonical_relpaths(self.basedir, file_list):
428
rf = _FastPath(filepath)
429
# validate user parameters. Our recursive code avoids adding new
430
# files that need such validation
312
# than to convert an abspath to tree relative.
313
for filepath in file_list:
314
rf = _FastPath(self.relpath(filepath))
315
# validate user parameters. Our recursive code avoids adding new files
316
# that need such validation
431
317
if self.is_control_filename(rf.raw_path):
432
318
raise errors.ForbiddenControlFileError(filename=rf.raw_path)
434
320
abspath = self.abspath(rf.raw_path)
435
321
kind = osutils.file_kind(abspath)
436
322
if kind == 'directory':
440
326
if not InventoryEntry.versionable_kind(kind):
441
327
raise errors.BadFileKindError(filename=abspath, kind=kind)
442
# ensure the named path is added, so that ignore rules in the later
443
# directory walk dont skip it.
444
# we dont have a parent ie known yet.: use the relatively slower
445
# inventory probing method
328
# ensure the named path is added, so that ignore rules in the later directory
330
# we dont have a parent ie known yet.: use the relatively slower inventory
446
332
versioned = inv.has_filename(rf.raw_path)
479
364
kind = osutils.file_kind(abspath)
481
366
if not InventoryEntry.versionable_kind(kind):
482
trace.warning("skipping %s (can't add file of kind '%s')",
485
if illegalpath_re.search(directory.raw_path):
486
trace.warning("skipping %r (contains \\n or \\r)" % abspath)
488
if directory.raw_path in conflicts_related:
489
# If the file looks like one generated for a conflict, don't
492
'skipping %s (generated to help resolve conflicts)',
367
warning("skipping %s (can't add file of kind '%s')", abspath, kind)
496
370
if parent_ie is not None:
497
371
versioned = directory.base_path in parent_ie.children
499
# without the parent ie, use the relatively slower inventory
373
# without the parent ie, use the relatively slower inventory
501
versioned = inv.has_filename(
502
self._fix_case_of_inventory_path(directory.raw_path))
375
versioned = inv.has_filename(directory.raw_path)
504
377
if kind == 'directory':
520
393
# mutter("%r is already versioned", abspath)
522
# XXX: This is wrong; people *might* reasonably be trying to
523
# add subtrees as subtrees. This should probably only be done
524
# in formats which can represent subtrees, and even then
525
# perhaps only when the user asked to add subtrees. At the
526
# moment you can add them specially through 'join --reference',
527
# which is perhaps reasonable: adding a new reference is a
528
# special operation and can have a special behaviour. mbp
530
trace.mutter("%r is a nested bzr tree", abspath)
395
# XXX: This is wrong; people *might* reasonably be trying to add
396
# subtrees as subtrees. This should probably only be done in formats
397
# which can represent subtrees, and even then perhaps only when
398
# the user asked to add subtrees. At the moment you can add them
399
# specially through 'join --reference', which is perhaps
400
# reasonable: adding a new reference is a special operation and
401
# can have a special behaviour. mbp 20070306
402
mutter("%r is a nested bzr tree", abspath)
532
404
_add_one(self, inv, parent_ie, directory, kind, action)
533
405
added.append(directory.raw_path)
537
409
# must be present:
538
410
this_ie = parent_ie.children[directory.base_path]
540
# without the parent ie, use the relatively slower inventory
412
# without the parent ie, use the relatively slower inventory
542
this_id = inv.path2id(
543
self._fix_case_of_inventory_path(directory.raw_path))
414
this_id = inv.path2id(directory.raw_path)
544
415
if this_id is None:
547
418
this_ie = inv[this_id]
549
420
for subf in sorted(os.listdir(abspath)):
550
# here we could use TreeDirectory rather than
421
# here we could use TreeDirectory rather than
551
422
# string concatenation.
552
423
subp = osutils.pathjoin(directory.raw_path, subf)
553
# TODO: is_control_filename is very slow. Make it faster.
554
# TreeDirectory.is_control_filename could also make this
555
# faster - its impossible for a non root dir to have a
424
# TODO: is_control_filename is very slow. Make it faster.
425
# TreeDirectory.is_control_filename could also make this
426
# faster - its impossible for a non root dir to have a
557
428
if self.is_control_filename(subp):
558
trace.mutter("skip control directory %r", subp)
429
mutter("skip control directory %r", subp)
559
430
elif subf in this_ie.children:
560
431
# recurse into this already versioned subdir.
561
432
dirs_to_add.append((_FastPath(subp, subf), this_ie))
578
449
self.read_working_inventory()
579
450
return added, ignored
581
def update_basis_by_delta(self, new_revid, delta):
582
"""Update the parents of this tree after a commit.
584
This gives the tree one parent, with revision id new_revid. The
585
inventory delta is applied to the current basis tree to generate the
586
inventory for the parent new_revid, and all other parent trees are
589
All the changes in the delta should be changes synchronising the basis
590
tree with some or all of the working tree, with a change to a directory
591
requiring that its contents have been recursively included. That is,
592
this is not a general purpose tree modification routine, but a helper
593
for commit which is not required to handle situations that do not arise
596
See the inventory developers documentation for the theory behind
599
:param new_revid: The new revision id for the trees parent.
600
:param delta: An inventory delta (see apply_inventory_delta) describing
601
the changes from the current left most parent revision to new_revid.
603
# if the tree is updated by a pull to the branch, as happens in
604
# WorkingTree2, when there was no separation between branch and tree,
605
# then just clear merges, efficiency is not a concern for now as this
606
# is legacy environments only, and they are slow regardless.
607
if self.last_revision() == new_revid:
608
self.set_parent_ids([new_revid])
610
# generic implementation based on Inventory manipulation. See
611
# WorkingTree classes for optimised versions for specific format trees.
612
basis = self.basis_tree()
614
# TODO: Consider re-evaluating the need for this with CHKInventory
615
# we don't strictly need to mutate an inventory for this
616
# it only makes sense when apply_delta is cheaper than get_inventory()
617
inventory = basis.inventory._get_mutable_inventory()
619
inventory.apply_delta(delta)
620
rev_tree = revisiontree.RevisionTree(self.branch.repository,
621
inventory, new_revid)
622
self.set_parent_trees([(new_revid, rev_tree)])
625
class MutableTreeHooks(hooks.Hooks):
626
"""A dictionary mapping a hook name to a list of callables for mutabletree
631
"""Create the default hooks.
634
hooks.Hooks.__init__(self)
635
self.create_hook(hooks.HookPoint('start_commit',
636
"Called before a commit is performed on a tree. The start commit "
637
"hook is able to change the tree before the commit takes place. "
638
"start_commit is called with the bzrlib.mutabletree.MutableTree "
639
"that the commit is being performed on.", (1, 4), None))
640
self.create_hook(hooks.HookPoint('post_commit',
641
"Called after a commit is performed on a tree. The hook is "
642
"called with a bzrlib.mutabletree.PostCommitHookParams object. "
643
"The mutable tree the commit was performed on is available via "
644
"the mutable_tree attribute of that object.", (2, 0), None))
647
# install the default hooks into the MutableTree class.
648
MutableTree.hooks = MutableTreeHooks()
651
class PostCommitHookParams(object):
652
"""Parameters for the post_commit hook.
654
To access the parameters, use the following attributes:
656
* mutable_tree - the MutableTree object
659
def __init__(self, mutable_tree):
660
"""Create the parameters for the post_commit hook."""
661
self.mutable_tree = mutable_tree
664
453
class _FastPath(object):
665
454
"""A path object with fast accessors for things like basename."""
702
491
# slower but does not need parent_ie
703
if inv.has_filename(tree._fix_case_of_inventory_path(path.raw_path)):
492
if inv.has_filename(path.raw_path):
705
494
# its really not there : add the parent
706
495
# note that the dirname use leads to some extra str copying etc but as
707
496
# there are a limited number of dirs we can be nested under, it should
708
497
# generally find it very fast and not recurse after that.
709
498
added = _add_one_and_parent(tree, inv, None,
710
_FastPath(osutils.dirname(path.raw_path)), 'directory', action)
711
parent_id = inv.path2id(osutils.dirname(path.raw_path))
499
_FastPath(dirname(path.raw_path)), 'directory', action)
500
parent_id = inv.path2id(dirname(path.raw_path))
712
501
parent_ie = inv[parent_id]
713
502
_add_one(tree, inv, parent_ie, path, kind, action)
714
503
return added + [path.raw_path]
724
513
file_id or None to generate a new file id
727
# if the parent exists, but isn't a directory, we have to do the
728
# kind change now -- really the inventory shouldn't pretend to know
729
# the kind of wt files, but it does.
730
if parent_ie.kind != 'directory':
731
# nb: this relies on someone else checking that the path we're using
732
# doesn't contain symlinks.
733
new_parent_ie = inventory.make_entry('directory', parent_ie.name,
734
parent_ie.parent_id, parent_ie.file_id)
735
del inv[parent_ie.file_id]
736
inv.add(new_parent_ie)
737
parent_ie = new_parent_ie
738
516
file_id = file_id_callback(inv, parent_ie, path, kind)
739
517
entry = inv.make_entry(kind, path.base_path, parent_ie.file_id,