110
136
self._gather_kinds(files, kinds)
111
137
self._add(files, ids, kinds)
139
def add_reference(self, sub_tree):
140
"""Add a TreeReference to the tree, pointing at sub_tree"""
141
raise errors.UnsupportedOperation(self.add_reference, self)
143
def _add_reference(self, sub_tree):
144
"""Standard add_reference implementation, for use by subclasses"""
146
sub_tree_path = self.relpath(sub_tree.basedir)
147
except errors.PathNotChild:
148
raise errors.BadReferenceTarget(self, sub_tree,
149
'Target not inside tree.')
150
sub_tree_id = sub_tree.get_root_id()
151
if sub_tree_id == self.get_root_id():
152
raise errors.BadReferenceTarget(self, sub_tree,
153
'Trees have the same root id.')
154
if sub_tree_id in self.inventory:
155
raise errors.BadReferenceTarget(self, sub_tree,
156
'Root id already present in tree')
157
self._add([sub_tree_path], [sub_tree_id], ['tree-reference'])
113
159
def _add(self, files, ids, kinds):
114
"""Helper function for add - updates the inventory."""
160
"""Helper function for add - updates the inventory.
162
:param files: sequence of pathnames, relative to the tree root
163
:param ids: sequence of suggested ids for the files (may be None)
164
:param kinds: sequence of inventory kinds of the files (i.e. may
165
contain "tree-reference")
115
167
raise NotImplementedError(self._add)
169
@needs_tree_write_lock
170
def apply_inventory_delta(self, changes):
171
"""Apply changes to the inventory as an atomic operation.
173
:param changes: An inventory delta to apply to the working tree's
176
:seealso Inventory.apply_delta: For details on the changes parameter.
180
inv.apply_delta(changes)
181
self._write_inventory(inv)
117
183
@needs_write_lock
118
def commit(self, message=None, revprops=None, *args, **kwargs):
184
def commit(self, message=None, revprops=None, *args,
119
186
# avoid circular imports
120
187
from bzrlib import commit
121
188
if revprops is None:
190
possible_master_transports=[]
123
191
if not 'branch-nick' in revprops:
124
revprops['branch-nick'] = self.branch.nick
192
revprops['branch-nick'] = self.branch._get_nick(
193
kwargs.get('local', False),
194
possible_master_transports)
195
author = kwargs.pop('author', None)
196
if author is not None:
197
if 'author' in revprops:
198
# XXX: maybe we should just accept one of them?
199
raise AssertionError('author property given twice')
200
revprops['author'] = author
125
201
# args for wt.commit start at message from the Commit.commit method,
126
# but with branch a kwarg now, passing in args as is results in the
127
#message being used for the branch
128
args = (DEPRECATED_PARAMETER, message, ) + args
202
args = (message, ) + args
203
for hook in MutableTree.hooks['start_commit']:
129
205
committed_id = commit.Commit().commit(working_tree=self,
130
revprops=revprops, *args, **kwargs)
207
possible_master_transports=possible_master_transports,
131
209
return committed_id
133
211
def _gather_kinds(self, files, kinds):
134
212
"""Helper function for add - sets the entries of kinds."""
135
213
raise NotImplementedError(self._gather_kinds)
215
def get_file_with_stat(self, file_id, path=None):
216
"""Get a file handle and stat object for file_id.
218
The default implementation returns (self.get_file, None) for backwards
221
:param file_id: The file id to read.
222
:param path: The path of the file, if it is known.
223
:return: A tuple (file_handle, stat_value_or_None). If the tree has
224
no stat facility, or need for a stat cache feedback during commit,
225
it may return None for the second element of the tuple.
227
return (self.get_file(file_id, path), None)
138
230
def last_revision(self):
139
231
"""Return the revision id of the last commit performed in this tree.
188
323
parent tree - i.e. a ghost.
190
325
raise NotImplementedError(self.set_parent_trees)
327
@needs_tree_write_lock
328
def smart_add(self, file_list, recurse=True, action=None, save=True):
329
"""Version file_list, optionally recursing into directories.
331
This is designed more towards DWIM for humans than API clarity.
332
For the specific behaviour see the help for cmd_add().
334
:param action: A reporter to be called with the inventory, parent_ie,
335
path and kind of the path being added. It may return a file_id if
336
a specific one should be used.
337
:param save: Save the inventory after completing the adds. If False
338
this provides dry-run functionality by doing the add and not saving
340
:return: A tuple - files_added, ignored_files. files_added is the count
341
of added files, and ignored_files is a dict mapping files that were
342
ignored to the rule that caused them to be ignored.
344
# not in an inner loop; and we want to remove direct use of this,
345
# so here as a reminder for now. RBC 20070703
346
from bzrlib.inventory import InventoryEntry
348
action = add.AddAction()
351
# no paths supplied: add the entire tree.
353
# mutter("smart add of %r")
360
# validate user file paths and convert all paths to tree
361
# relative : it's cheaper to make a tree relative path an abspath
362
# than to convert an abspath to tree relative, and it's cheaper to
363
# perform the canonicalization in bulk.
364
for filepath in osutils.canonical_relpaths(self.basedir, file_list):
365
rf = _FastPath(filepath)
366
# validate user parameters. Our recursive code avoids adding new files
367
# that need such validation
368
if self.is_control_filename(rf.raw_path):
369
raise errors.ForbiddenControlFileError(filename=rf.raw_path)
371
abspath = self.abspath(rf.raw_path)
372
kind = osutils.file_kind(abspath)
373
if kind == 'directory':
374
# schedule the dir for scanning
377
if not InventoryEntry.versionable_kind(kind):
378
raise errors.BadFileKindError(filename=abspath, kind=kind)
379
# ensure the named path is added, so that ignore rules in the later directory
381
# we dont have a parent ie known yet.: use the relatively slower inventory
383
versioned = inv.has_filename(rf.raw_path)
386
added.extend(_add_one_and_parent(self, inv, None, rf, kind, action))
389
# no need to walk any directories at all.
390
if len(added) > 0 and save:
391
self._write_inventory(inv)
392
return added, ignored
394
# only walk the minimal parents needed: we have user_dirs to override
398
is_inside = osutils.is_inside_or_parent_of_any
399
for path in sorted(user_dirs):
400
if (prev_dir is None or not is_inside([prev_dir], path.raw_path)):
401
dirs_to_add.append((path, None))
402
prev_dir = path.raw_path
404
# dirs_to_add is initialised to a list of directories, but as we scan
405
# directories we append files to it.
406
# XXX: We should determine kind of files when we scan them rather than
407
# adding to this list. RBC 20070703
408
for directory, parent_ie in dirs_to_add:
409
# directory is tree-relative
410
abspath = self.abspath(directory.raw_path)
412
# get the contents of this directory.
414
# find the kind of the path being added.
415
kind = osutils.file_kind(abspath)
417
if not InventoryEntry.versionable_kind(kind):
418
warning("skipping %s (can't add file of kind '%s')", abspath, kind)
421
if parent_ie is not None:
422
versioned = directory.base_path in parent_ie.children
424
# without the parent ie, use the relatively slower inventory
426
versioned = inv.has_filename(
427
self._fix_case_of_inventory_path(directory.raw_path))
429
if kind == 'directory':
431
sub_branch = bzrdir.BzrDir.open(abspath)
433
except errors.NotBranchError:
435
except errors.UnsupportedFormatError:
440
if directory.raw_path == '':
441
# mutter("tree root doesn't need to be added")
445
# mutter("%r is already versioned", abspath)
447
# XXX: This is wrong; people *might* reasonably be trying to add
448
# subtrees as subtrees. This should probably only be done in formats
449
# which can represent subtrees, and even then perhaps only when
450
# the user asked to add subtrees. At the moment you can add them
451
# specially through 'join --reference', which is perhaps
452
# reasonable: adding a new reference is a special operation and
453
# can have a special behaviour. mbp 20070306
454
mutter("%r is a nested bzr tree", abspath)
456
_add_one(self, inv, parent_ie, directory, kind, action)
457
added.append(directory.raw_path)
459
if kind == 'directory' and not sub_tree:
460
if parent_ie is not None:
462
this_ie = parent_ie.children[directory.base_path]
464
# without the parent ie, use the relatively slower inventory
466
this_id = inv.path2id(
467
self._fix_case_of_inventory_path(directory.raw_path))
471
this_ie = inv[this_id]
473
for subf in sorted(os.listdir(abspath)):
474
# here we could use TreeDirectory rather than
475
# string concatenation.
476
subp = osutils.pathjoin(directory.raw_path, subf)
477
# TODO: is_control_filename is very slow. Make it faster.
478
# TreeDirectory.is_control_filename could also make this
479
# faster - its impossible for a non root dir to have a
481
if self.is_control_filename(subp):
482
mutter("skip control directory %r", subp)
483
elif subf in this_ie.children:
484
# recurse into this already versioned subdir.
485
dirs_to_add.append((_FastPath(subp, subf), this_ie))
487
# user selection overrides ignoes
488
# ignore while selecting files - if we globbed in the
489
# outer loop we would ignore user files.
490
ignore_glob = self.is_ignored(subp)
491
if ignore_glob is not None:
492
# mutter("skip ignored sub-file %r", subp)
493
ignored.setdefault(ignore_glob, []).append(subp)
495
#mutter("queue to add sub-file %r", subp)
496
dirs_to_add.append((_FastPath(subp, subf), this_ie))
500
self._write_inventory(inv)
502
self.read_working_inventory()
503
return added, ignored
505
def update_basis_by_delta(self, new_revid, delta):
506
"""Update the parents of this tree after a commit.
508
This gives the tree one parent, with revision id new_revid. The
509
inventory delta is applied to the current basis tree to generate the
510
inventory for the parent new_revid, and all other parent trees are
513
All the changes in the delta should be changes synchronising the basis
514
tree with some or all of the working tree, with a change to a directory
515
requiring that its contents have been recursively included. That is,
516
this is not a general purpose tree modification routine, but a helper
517
for commit which is not required to handle situations that do not arise
520
:param new_revid: The new revision id for the trees parent.
521
:param delta: An inventory delta (see apply_inventory_delta) describing
522
the changes from the current left most parent revision to new_revid.
524
# if the tree is updated by a pull to the branch, as happens in
525
# WorkingTree2, when there was no separation between branch and tree,
526
# then just clear merges, efficiency is not a concern for now as this
527
# is legacy environments only, and they are slow regardless.
528
if self.last_revision() == new_revid:
529
self.set_parent_ids([new_revid])
531
# generic implementation based on Inventory manipulation. See
532
# WorkingTree classes for optimised versions for specific format trees.
533
basis = self.basis_tree()
535
inventory = basis.inventory
537
inventory.apply_delta(delta)
538
rev_tree = RevisionTree(self.branch.repository, inventory, new_revid)
539
self.set_parent_trees([(new_revid, rev_tree)])
542
class MutableTreeHooks(hooks.Hooks):
543
"""A dictionary mapping a hook name to a list of callables for mutabletree
548
"""Create the default hooks.
551
hooks.Hooks.__init__(self)
552
# Invoked before a commit is done in a tree. New in 1.4
553
self['start_commit'] = []
556
# install the default hooks into the MutableTree class.
557
MutableTree.hooks = MutableTreeHooks()
560
class _FastPath(object):
561
"""A path object with fast accessors for things like basename."""
563
__slots__ = ['raw_path', 'base_path']
565
def __init__(self, path, base_path=None):
566
"""Construct a FastPath from path."""
567
if base_path is None:
568
self.base_path = osutils.basename(path)
570
self.base_path = base_path
573
def __cmp__(self, other):
574
return cmp(self.raw_path, other.raw_path)
577
return hash(self.raw_path)
580
def _add_one_and_parent(tree, inv, parent_ie, path, kind, action):
581
"""Add a new entry to the inventory and automatically add unversioned parents.
583
:param inv: Inventory which will receive the new entry.
584
:param parent_ie: Parent inventory entry if known, or None. If
585
None, the parent is looked up by name and used if present, otherwise it
586
is recursively added.
587
:param kind: Kind of new entry (file, directory, etc)
588
:param action: callback(inv, parent_ie, path, kind); return ignored.
589
:return: A list of paths which have been added.
591
# Nothing to do if path is already versioned.
592
# This is safe from infinite recursion because the tree root is
594
if parent_ie is not None:
595
# we have a parent ie already
598
# slower but does not need parent_ie
599
if inv.has_filename(tree._fix_case_of_inventory_path(path.raw_path)):
601
# its really not there : add the parent
602
# note that the dirname use leads to some extra str copying etc but as
603
# there are a limited number of dirs we can be nested under, it should
604
# generally find it very fast and not recurse after that.
605
added = _add_one_and_parent(tree, inv, None,
606
_FastPath(dirname(path.raw_path)), 'directory', action)
607
parent_id = inv.path2id(dirname(path.raw_path))
608
parent_ie = inv[parent_id]
609
_add_one(tree, inv, parent_ie, path, kind, action)
610
return added + [path.raw_path]
613
def _add_one(tree, inv, parent_ie, path, kind, file_id_callback):
614
"""Add a new entry to the inventory.
616
:param inv: Inventory which will receive the new entry.
617
:param parent_ie: Parent inventory entry.
618
:param kind: Kind of new entry (file, directory, etc)
619
:param file_id_callback: callback(inv, parent_ie, path, kind); return a
620
file_id or None to generate a new file id
623
file_id = file_id_callback(inv, parent_ie, path, kind)
624
entry = inv.make_entry(kind, path.base_path, parent_ie.file_id,