260
272
parent tree - i.e. a ghost.
262
274
raise NotImplementedError(self.set_parent_trees)
276
@needs_tree_write_lock
277
def smart_add(self, file_list, recurse=True, action=None, save=True):
278
"""Version file_list, optionally recursing into directories.
280
This is designed more towards DWIM for humans than API clarity.
281
For the specific behaviour see the help for cmd_add().
283
:param action: A reporter to be called with the inventory, parent_ie,
284
path and kind of the path being added. It may return a file_id if
285
a specific one should be used.
286
:param save: Save the inventory after completing the adds. If False
287
this provides dry-run functionality by doing the add and not saving
288
the inventory. Note that the modified inventory is left in place,
289
allowing further dry-run tasks to take place. To restore the
290
original inventory call self.read_working_inventory().
291
:return: A tuple - files_added, ignored_files. files_added is the count
292
of added files, and ignored_files is a dict mapping files that were
293
ignored to the rule that caused them to be ignored.
295
# not in an inner loop; and we want to remove direct use of this,
296
# so here as a reminder for now. RBC 20070703
297
from bzrlib.inventory import InventoryEntry
298
assert isinstance(recurse, bool)
300
action = add.AddAction()
303
# no paths supplied: add the entire tree.
305
# mutter("smart add of %r")
312
# validate user file paths and convert all paths to tree
313
# relative : it's cheaper to make a tree relative path an abspath
314
# than to convert an abspath to tree relative.
315
for filepath in file_list:
316
rf = _FastPath(self.relpath(filepath))
317
# validate user parameters. Our recursive code avoids adding new files
318
# that need such validation
319
if self.is_control_filename(rf.raw_path):
320
raise errors.ForbiddenControlFileError(filename=rf.raw_path)
322
abspath = self.abspath(rf.raw_path)
323
kind = osutils.file_kind(abspath)
324
if kind == 'directory':
325
# schedule the dir for scanning
328
if not InventoryEntry.versionable_kind(kind):
329
raise errors.BadFileKindError(filename=abspath, kind=kind)
330
# ensure the named path is added, so that ignore rules in the later directory
332
# we dont have a parent ie known yet.: use the relatively slower inventory
334
versioned = inv.has_filename(rf.raw_path)
337
added.extend(_add_one_and_parent(self, inv, None, rf, kind, action))
340
# no need to walk any directories at all.
341
if len(added) > 0 and save:
342
self._write_inventory(inv)
343
return added, ignored
345
# only walk the minimal parents needed: we have user_dirs to override
349
is_inside = osutils.is_inside_or_parent_of_any
350
for path in sorted(user_dirs):
351
if (prev_dir is None or not is_inside([prev_dir], path.raw_path)):
352
dirs_to_add.append((path, None))
353
prev_dir = path.raw_path
355
# dirs_to_add is initialised to a list of directories, but as we scan
356
# directories we append files to it.
357
# XXX: We should determine kind of files when we scan them rather than
358
# adding to this list. RBC 20070703
359
for directory, parent_ie in dirs_to_add:
360
# directory is tree-relative
361
abspath = self.abspath(directory.raw_path)
363
# get the contents of this directory.
365
# find the kind of the path being added.
366
kind = osutils.file_kind(abspath)
368
if not InventoryEntry.versionable_kind(kind):
369
warning("skipping %s (can't add file of kind '%s')", abspath, kind)
372
if parent_ie is not None:
373
versioned = directory.base_path in parent_ie.children
375
# without the parent ie, use the relatively slower inventory
377
versioned = inv.has_filename(directory.raw_path)
379
if kind == 'directory':
381
sub_branch = bzrdir.BzrDir.open(abspath)
383
except errors.NotBranchError:
385
except errors.UnsupportedFormatError:
390
if directory.raw_path == '':
391
# mutter("tree root doesn't need to be added")
395
# mutter("%r is already versioned", abspath)
397
# XXX: This is wrong; people *might* reasonably be trying to add
398
# subtrees as subtrees. This should probably only be done in formats
399
# which can represent subtrees, and even then perhaps only when
400
# the user asked to add subtrees. At the moment you can add them
401
# specially through 'join --reference', which is perhaps
402
# reasonable: adding a new reference is a special operation and
403
# can have a special behaviour. mbp 20070306
404
mutter("%r is a nested bzr tree", abspath)
406
_add_one(self, inv, parent_ie, directory, kind, action)
407
added.append(directory.raw_path)
409
if kind == 'directory' and not sub_tree:
410
if parent_ie is not None:
412
this_ie = parent_ie.children[directory.base_path]
414
# without the parent ie, use the relatively slower inventory
416
this_id = inv.path2id(directory.raw_path)
420
this_ie = inv[this_id]
422
for subf in sorted(os.listdir(abspath)):
423
# here we could use TreeDirectory rather than
424
# string concatenation.
425
subp = osutils.pathjoin(directory.raw_path, subf)
426
# TODO: is_control_filename is very slow. Make it faster.
427
# TreeDirectory.is_control_filename could also make this
428
# faster - its impossible for a non root dir to have a
430
if self.is_control_filename(subp):
431
mutter("skip control directory %r", subp)
432
elif subf in this_ie.children:
433
# recurse into this already versioned subdir.
434
dirs_to_add.append((_FastPath(subp, subf), this_ie))
436
# user selection overrides ignoes
437
# ignore while selecting files - if we globbed in the
438
# outer loop we would ignore user files.
439
ignore_glob = self.is_ignored(subp)
440
if ignore_glob is not None:
441
# mutter("skip ignored sub-file %r", subp)
442
ignored.setdefault(ignore_glob, []).append(subp)
444
#mutter("queue to add sub-file %r", subp)
445
dirs_to_add.append((_FastPath(subp, subf), this_ie))
447
if len(added) > 0 and save:
448
self._write_inventory(inv)
449
return added, ignored
452
class _FastPath(object):
453
"""A path object with fast accessors for things like basename."""
455
__slots__ = ['raw_path', 'base_path']
457
def __init__(self, path, base_path=None):
458
"""Construct a FastPath from path."""
459
if base_path is None:
460
self.base_path = osutils.basename(path)
462
self.base_path = base_path
465
def __cmp__(self, other):
466
return cmp(self.raw_path, other.raw_path)
469
return hash(self.raw_path)
472
def _add_one_and_parent(tree, inv, parent_ie, path, kind, action):
473
"""Add a new entry to the inventory and automatically add unversioned parents.
475
:param inv: Inventory which will receive the new entry.
476
:param parent_ie: Parent inventory entry if known, or None. If
477
None, the parent is looked up by name and used if present, otherwise it
478
is recursively added.
479
:param kind: Kind of new entry (file, directory, etc)
480
:param action: callback(inv, parent_ie, path, kind); return ignored.
481
:return: A list of paths which have been added.
483
# Nothing to do if path is already versioned.
484
# This is safe from infinite recursion because the tree root is
486
if parent_ie is not None:
487
# we have a parent ie already
490
# slower but does not need parent_ie
491
if inv.has_filename(path.raw_path):
493
# its really not there : add the parent
494
# note that the dirname use leads to some extra str copying etc but as
495
# there are a limited number of dirs we can be nested under, it should
496
# generally find it very fast and not recurse after that.
497
added = _add_one_and_parent(tree, inv, None,
498
_FastPath(dirname(path.raw_path)), 'directory', action)
499
parent_id = inv.path2id(dirname(path.raw_path))
500
parent_ie = inv[parent_id]
501
_add_one(tree, inv, parent_ie, path, kind, action)
502
return added + [path.raw_path]
505
def _add_one(tree, inv, parent_ie, path, kind, file_id_callback):
506
"""Add a new entry to the inventory.
508
:param inv: Inventory which will receive the new entry.
509
:param parent_ie: Parent inventory entry.
510
:param kind: Kind of new entry (file, directory, etc)
511
:param file_id_callback: callback(inv, parent_ie, path, kind); return a
512
file_id or None to generate a new file id
515
file_id = file_id_callback(inv, parent_ie, path, kind)
516
entry = inv.make_entry(kind, path.base_path, parent_ie.file_id,