187
187
from bzrlib import commit
188
188
if revprops is None:
190
possible_master_transports=[]
191
190
if not 'branch-nick' in revprops:
192
revprops['branch-nick'] = self.branch._get_nick(
193
kwargs.get('local', False),
194
possible_master_transports)
195
authors = kwargs.pop('authors', None)
196
author = kwargs.pop('author', None)
197
if authors is not None:
198
if author is not None:
199
raise AssertionError('Specifying both author and authors '
200
'is not allowed. Specify just authors instead')
201
if 'author' in revprops or 'authors' in revprops:
202
# XXX: maybe we should just accept one of them?
203
raise AssertionError('author property given twice')
205
for individual in authors:
206
if '\n' in individual:
207
raise AssertionError('\\n is not a valid character '
208
'in an author identity')
209
revprops['authors'] = '\n'.join(authors)
210
if author is not None:
211
symbol_versioning.warn('The parameter author was deprecated'
212
' in version 1.13. Use authors instead',
214
if 'author' in revprops or 'authors' in revprops:
215
# XXX: maybe we should just accept one of them?
216
raise AssertionError('author property given twice')
218
raise AssertionError('\\n is not a valid character '
219
'in an author identity')
220
revprops['authors'] = author
191
revprops['branch-nick'] = self.branch.nick
221
192
# args for wt.commit start at message from the Commit.commit method,
222
193
args = (message, ) + args
223
for hook in MutableTree.hooks['start_commit']:
225
194
committed_id = commit.Commit().commit(working_tree=self,
227
possible_master_transports=possible_master_transports,
195
revprops=revprops, *args, **kwargs)
229
196
return committed_id
231
198
def _gather_kinds(self, files, kinds):
232
199
"""Helper function for add - sets the entries of kinds."""
233
200
raise NotImplementedError(self._gather_kinds)
235
def get_file_with_stat(self, file_id, path=None):
236
"""Get a file handle and stat object for file_id.
238
The default implementation returns (self.get_file, None) for backwards
241
:param file_id: The file id to read.
242
:param path: The path of the file, if it is known.
243
:return: A tuple (file_handle, stat_value_or_None). If the tree has
244
no stat facility, or need for a stat cache feedback during commit,
245
it may return None for the second element of the tuple.
247
return (self.get_file(file_id, path), None)
250
203
def last_revision(self):
251
204
"""Return the revision id of the last commit performed in this tree.
293
246
raise NotImplementedError(self.mkdir)
295
def _observed_sha1(self, file_id, path, (sha1, stat_value)):
296
"""Tell the tree we have observed a paths sha1.
298
The intent of this function is to allow trees that have a hashcache to
299
update the hashcache during commit. If the observed file is too new
300
(based on the stat_value) to be safely hash-cached the tree will ignore
303
The default implementation does nothing.
305
:param file_id: The file id
306
:param path: The file path
307
:param sha1: The sha 1 that was observed.
308
:param stat_value: A stat result for the file the sha1 was read from.
312
def _fix_case_of_inventory_path(self, path):
313
"""If our tree isn't case sensitive, return the canonical path"""
314
if not self.case_sensitive:
315
path = self.get_canonical_inventory_path(path)
319
def put_file_bytes_non_atomic(self, file_id, bytes):
320
"""Update the content of a file in the tree.
322
Note that the file is written in-place rather than being
323
written to a temporary location and renamed. As a consequence,
324
readers can potentially see the file half-written.
326
:param file_id: file-id of the file
327
:param bytes: the new file contents
329
raise NotImplementedError(self.put_file_bytes_non_atomic)
331
def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
332
"""Set the parents ids of the working tree.
334
:param revision_ids: A list of revision_ids.
336
raise NotImplementedError(self.set_parent_ids)
338
248
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
339
249
"""Set the parents of the working tree.
341
:param parents_list: A list of (revision_id, tree) tuples.
251
:param parents_list: A list of (revision_id, tree) tuples.
342
252
If tree is None, then that element is treated as an unreachable
343
253
parent tree - i.e. a ghost.
345
255
raise NotImplementedError(self.set_parent_trees)
347
@needs_tree_write_lock
348
def smart_add(self, file_list, recurse=True, action=None, save=True):
349
"""Version file_list, optionally recursing into directories.
351
This is designed more towards DWIM for humans than API clarity.
352
For the specific behaviour see the help for cmd_add().
354
:param action: A reporter to be called with the inventory, parent_ie,
355
path and kind of the path being added. It may return a file_id if
356
a specific one should be used.
357
:param save: Save the inventory after completing the adds. If False
358
this provides dry-run functionality by doing the add and not saving
360
:return: A tuple - files_added, ignored_files. files_added is the count
361
of added files, and ignored_files is a dict mapping files that were
362
ignored to the rule that caused them to be ignored.
364
# not in an inner loop; and we want to remove direct use of this,
365
# so here as a reminder for now. RBC 20070703
366
from bzrlib.inventory import InventoryEntry
368
action = add.AddAction()
371
# no paths supplied: add the entire tree.
373
# mutter("smart add of %r")
380
# validate user file paths and convert all paths to tree
381
# relative : it's cheaper to make a tree relative path an abspath
382
# than to convert an abspath to tree relative, and it's cheaper to
383
# perform the canonicalization in bulk.
384
for filepath in osutils.canonical_relpaths(self.basedir, file_list):
385
rf = _FastPath(filepath)
386
# validate user parameters. Our recursive code avoids adding new files
387
# that need such validation
388
if self.is_control_filename(rf.raw_path):
389
raise errors.ForbiddenControlFileError(filename=rf.raw_path)
391
abspath = self.abspath(rf.raw_path)
392
kind = osutils.file_kind(abspath)
393
if kind == 'directory':
394
# schedule the dir for scanning
397
if not InventoryEntry.versionable_kind(kind):
398
raise errors.BadFileKindError(filename=abspath, kind=kind)
399
# ensure the named path is added, so that ignore rules in the later directory
401
# we dont have a parent ie known yet.: use the relatively slower inventory
403
versioned = inv.has_filename(rf.raw_path)
406
added.extend(_add_one_and_parent(self, inv, None, rf, kind, action))
409
# no need to walk any directories at all.
410
if len(added) > 0 and save:
411
self._write_inventory(inv)
412
return added, ignored
414
# only walk the minimal parents needed: we have user_dirs to override
418
is_inside = osutils.is_inside_or_parent_of_any
419
for path in sorted(user_dirs):
420
if (prev_dir is None or not is_inside([prev_dir], path.raw_path)):
421
dirs_to_add.append((path, None))
422
prev_dir = path.raw_path
424
# dirs_to_add is initialised to a list of directories, but as we scan
425
# directories we append files to it.
426
# XXX: We should determine kind of files when we scan them rather than
427
# adding to this list. RBC 20070703
428
for directory, parent_ie in dirs_to_add:
429
# directory is tree-relative
430
abspath = self.abspath(directory.raw_path)
432
# get the contents of this directory.
434
# find the kind of the path being added.
435
kind = osutils.file_kind(abspath)
437
if not InventoryEntry.versionable_kind(kind):
438
warning("skipping %s (can't add file of kind '%s')", abspath, kind)
441
if parent_ie is not None:
442
versioned = directory.base_path in parent_ie.children
444
# without the parent ie, use the relatively slower inventory
446
versioned = inv.has_filename(
447
self._fix_case_of_inventory_path(directory.raw_path))
449
if kind == 'directory':
451
sub_branch = bzrdir.BzrDir.open(abspath)
453
except errors.NotBranchError:
455
except errors.UnsupportedFormatError:
460
if directory.raw_path == '':
461
# mutter("tree root doesn't need to be added")
465
# mutter("%r is already versioned", abspath)
467
# XXX: This is wrong; people *might* reasonably be trying to add
468
# subtrees as subtrees. This should probably only be done in formats
469
# which can represent subtrees, and even then perhaps only when
470
# the user asked to add subtrees. At the moment you can add them
471
# specially through 'join --reference', which is perhaps
472
# reasonable: adding a new reference is a special operation and
473
# can have a special behaviour. mbp 20070306
474
mutter("%r is a nested bzr tree", abspath)
476
_add_one(self, inv, parent_ie, directory, kind, action)
477
added.append(directory.raw_path)
479
if kind == 'directory' and not sub_tree:
480
if parent_ie is not None:
482
this_ie = parent_ie.children[directory.base_path]
484
# without the parent ie, use the relatively slower inventory
486
this_id = inv.path2id(
487
self._fix_case_of_inventory_path(directory.raw_path))
491
this_ie = inv[this_id]
493
for subf in sorted(os.listdir(abspath)):
494
# here we could use TreeDirectory rather than
495
# string concatenation.
496
subp = osutils.pathjoin(directory.raw_path, subf)
497
# TODO: is_control_filename is very slow. Make it faster.
498
# TreeDirectory.is_control_filename could also make this
499
# faster - its impossible for a non root dir to have a
501
if self.is_control_filename(subp):
502
mutter("skip control directory %r", subp)
503
elif subf in this_ie.children:
504
# recurse into this already versioned subdir.
505
dirs_to_add.append((_FastPath(subp, subf), this_ie))
507
# user selection overrides ignoes
508
# ignore while selecting files - if we globbed in the
509
# outer loop we would ignore user files.
510
ignore_glob = self.is_ignored(subp)
511
if ignore_glob is not None:
512
# mutter("skip ignored sub-file %r", subp)
513
ignored.setdefault(ignore_glob, []).append(subp)
515
#mutter("queue to add sub-file %r", subp)
516
dirs_to_add.append((_FastPath(subp, subf), this_ie))
520
self._write_inventory(inv)
522
self.read_working_inventory()
523
return added, ignored
525
def update_basis_by_delta(self, new_revid, delta):
526
"""Update the parents of this tree after a commit.
528
This gives the tree one parent, with revision id new_revid. The
529
inventory delta is applied to the current basis tree to generate the
530
inventory for the parent new_revid, and all other parent trees are
533
All the changes in the delta should be changes synchronising the basis
534
tree with some or all of the working tree, with a change to a directory
535
requiring that its contents have been recursively included. That is,
536
this is not a general purpose tree modification routine, but a helper
537
for commit which is not required to handle situations that do not arise
540
:param new_revid: The new revision id for the trees parent.
541
:param delta: An inventory delta (see apply_inventory_delta) describing
542
the changes from the current left most parent revision to new_revid.
544
# if the tree is updated by a pull to the branch, as happens in
545
# WorkingTree2, when there was no separation between branch and tree,
546
# then just clear merges, efficiency is not a concern for now as this
547
# is legacy environments only, and they are slow regardless.
548
if self.last_revision() == new_revid:
549
self.set_parent_ids([new_revid])
551
# generic implementation based on Inventory manipulation. See
552
# WorkingTree classes for optimised versions for specific format trees.
553
basis = self.basis_tree()
555
# TODO: Consider re-evaluating the need for this with CHKInventory
556
# we don't strictly need to mutate an inventory for this
557
# it only makes sense when apply_delta is cheaper than get_inventory()
558
inventory = basis.inventory._get_mutable_inventory()
560
inventory.apply_delta(delta)
561
rev_tree = RevisionTree(self.branch.repository, inventory, new_revid)
562
self.set_parent_trees([(new_revid, rev_tree)])
565
class MutableTreeHooks(hooks.Hooks):
566
"""A dictionary mapping a hook name to a list of callables for mutabletree
571
"""Create the default hooks.
574
hooks.Hooks.__init__(self)
575
self.create_hook(hooks.HookPoint('start_commit',
576
"Called before a commit is performed on a tree. The start commit "
577
"hook is able to change the tree before the commit takes place. "
578
"start_commit is called with the bzrlib.tree.MutableTree that the "
579
"commit is being performed on.", (1, 4), None))
582
# install the default hooks into the MutableTree class.
583
MutableTree.hooks = MutableTreeHooks()
586
class _FastPath(object):
587
"""A path object with fast accessors for things like basename."""
589
__slots__ = ['raw_path', 'base_path']
591
def __init__(self, path, base_path=None):
592
"""Construct a FastPath from path."""
593
if base_path is None:
594
self.base_path = osutils.basename(path)
596
self.base_path = base_path
599
def __cmp__(self, other):
600
return cmp(self.raw_path, other.raw_path)
603
return hash(self.raw_path)
606
def _add_one_and_parent(tree, inv, parent_ie, path, kind, action):
607
"""Add a new entry to the inventory and automatically add unversioned parents.
609
:param inv: Inventory which will receive the new entry.
610
:param parent_ie: Parent inventory entry if known, or None. If
611
None, the parent is looked up by name and used if present, otherwise it
612
is recursively added.
613
:param kind: Kind of new entry (file, directory, etc)
614
:param action: callback(inv, parent_ie, path, kind); return ignored.
615
:return: A list of paths which have been added.
617
# Nothing to do if path is already versioned.
618
# This is safe from infinite recursion because the tree root is
620
if parent_ie is not None:
621
# we have a parent ie already
624
# slower but does not need parent_ie
625
if inv.has_filename(tree._fix_case_of_inventory_path(path.raw_path)):
627
# its really not there : add the parent
628
# note that the dirname use leads to some extra str copying etc but as
629
# there are a limited number of dirs we can be nested under, it should
630
# generally find it very fast and not recurse after that.
631
added = _add_one_and_parent(tree, inv, None,
632
_FastPath(dirname(path.raw_path)), 'directory', action)
633
parent_id = inv.path2id(dirname(path.raw_path))
634
parent_ie = inv[parent_id]
635
_add_one(tree, inv, parent_ie, path, kind, action)
636
return added + [path.raw_path]
639
def _add_one(tree, inv, parent_ie, path, kind, file_id_callback):
640
"""Add a new entry to the inventory.
642
:param inv: Inventory which will receive the new entry.
643
:param parent_ie: Parent inventory entry.
644
:param kind: Kind of new entry (file, directory, etc)
645
:param file_id_callback: callback(inv, parent_ie, path, kind); return a
646
file_id or None to generate a new file id
649
file_id = file_id_callback(inv, parent_ie, path, kind)
650
entry = inv.make_entry(kind, path.base_path, parent_ie.file_id,