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