260
270
parent tree - i.e. a ghost.
262
272
raise NotImplementedError(self.set_parent_trees)
274
@needs_tree_write_lock
275
def smart_add(self, file_list, recurse=True, action=None, save=True):
276
"""Version file_list, optionally recursing into directories.
278
This is designed more towards DWIM for humans than API clarity.
279
For the specific behaviour see the help for cmd_add().
281
Returns the number of files added.
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().
292
# not in an inner loop; and we want to remove direct use of this,
293
# so here as a reminder for now. RBC 20070703
294
from bzrlib.inventory import InventoryEntry
295
assert isinstance(recurse, bool)
297
action = add.AddAction()
300
# no paths supplied: add the entire tree.
302
# mutter("smart add of %r")
309
# validate user file paths and convert all paths to tree
310
# relative : its cheaper to make a tree relative path an abspath
311
# than to convert an abspath to tree relative.
312
for filepath in file_list:
313
rf = FastPath(self.relpath(filepath))
314
# validate user parameters. Our recursive code avoids adding new files
315
# that need such validation
316
if self.is_control_filename(rf.raw_path):
317
raise errors.ForbiddenControlFileError(filename=rf.raw_path)
319
abspath = self.abspath(rf.raw_path)
320
kind = osutils.file_kind(abspath)
321
if kind == 'directory':
322
# schedule the dir for scanning
325
if not InventoryEntry.versionable_kind(kind):
326
raise errors.BadFileKindError(filename=abspath, kind=kind)
327
# ensure the named path is added, so that ignore rules in the later directory
329
# we dont have a parent ie known yet.: use the relatively slower inventory
331
versioned = inv.has_filename(rf.raw_path)
334
added.extend(_add_one_and_parent(self, inv, None, rf, kind, action))
337
# no need to walk any directories at all.
338
if len(added) > 0 and save:
339
self._write_inventory(inv)
340
return added, ignored
342
# only walk the minimal parents needed: we have user_dirs to override
346
is_inside = osutils.is_inside_or_parent_of_any
347
for path in sorted(user_dirs):
348
if (prev_dir is None or not is_inside([prev_dir], path.raw_path)):
349
dirs_to_add.append((path, None))
350
prev_dir = path.raw_path
352
# dirs_to_add is initialised to a list of directories, but as we scan
353
# directories we append files to it.
354
# XXX: We should determine kind of files when we scan them rather than
355
# adding to this list. RBC 20070703
356
for directory, parent_ie in dirs_to_add:
357
# directory is tree-relative
358
abspath = self.abspath(directory.raw_path)
360
# get the contents of this directory.
362
# find the kind of the path being added.
363
kind = osutils.file_kind(abspath)
365
if not InventoryEntry.versionable_kind(kind):
366
warning("skipping %s (can't add file of kind '%s')", abspath, kind)
369
if parent_ie is not None:
370
versioned = directory.base_path in parent_ie.children
372
# without the parent ie, use the relatively slower inventory
374
versioned = inv.has_filename(directory.raw_path)
376
if kind == 'directory':
378
sub_branch = bzrdir.BzrDir.open(abspath)
380
except errors.NotBranchError:
382
except errors.UnsupportedFormatError:
387
if directory.raw_path == '':
388
# mutter("tree root doesn't need to be added")
392
# mutter("%r is already versioned", abspath)
394
# XXX: This is wrong; people *might* reasonably be trying to add
395
# subtrees as subtrees. This should probably only be done in formats
396
# which can represent subtrees, and even then perhaps only when
397
# the user asked to add subtrees. At the moment you can add them
398
# specially through 'join --reference', which is perhaps
399
# reasonable: adding a new reference is a special operation and
400
# can have a special behaviour. mbp 20070306
401
mutter("%r is a nested bzr tree", abspath)
403
_add_one(self, inv, parent_ie, directory, kind, action)
404
added.append(directory.raw_path)
406
if kind == 'directory' and not sub_tree:
407
if parent_ie is not None:
409
this_ie = parent_ie.children[directory.base_path]
411
# without the parent ie, use the relatively slower inventory
413
this_id = inv.path2id(directory.raw_path)
417
this_ie = inv[this_id]
419
for subf in sorted(os.listdir(abspath)):
420
# here we could use TreeDirectory rather than
421
# string concatenation.
422
subp = osutils.pathjoin(directory.raw_path, subf)
423
# TODO: is_control_filename is very slow. Make it faster.
424
# TreeDirectory.is_control_filename could also make this
425
# faster - its impossible for a non root dir to have a
427
if self.is_control_filename(subp):
428
mutter("skip control directory %r", subp)
429
elif subf in this_ie.children:
430
# recurse into this already versioned subdir.
431
dirs_to_add.append((FastPath(subp, subf), this_ie))
433
# user selection overrides ignoes
434
# ignore while selecting files - if we globbed in the
435
# outer loop we would ignore user files.
436
ignore_glob = self.is_ignored(subp)
437
if ignore_glob is not None:
438
# mutter("skip ignored sub-file %r", subp)
439
ignored.setdefault(ignore_glob, []).append(subp)
441
#mutter("queue to add sub-file %r", subp)
442
dirs_to_add.append((FastPath(subp, subf), this_ie))
444
if len(added) > 0 and save:
445
self._write_inventory(inv)
446
return added, ignored
449
class FastPath(object):
450
"""A path object with fast accessors for things like basename."""
452
__slots__ = ['raw_path', 'base_path']
454
def __init__(self, path, base_path=None):
455
"""Construct a FastPath from path."""
456
if base_path is None:
457
self.base_path = osutils.basename(path)
459
self.base_path = base_path
462
def __cmp__(self, other):
463
return cmp(self.raw_path, other.raw_path)
466
return hash(self.raw_path)
469
def _add_one_and_parent(tree, inv, parent_ie, path, kind, action):
470
"""Add a new entry to the inventory and automatically add unversioned parents.
472
:param inv: Inventory which will receive the new entry.
473
:param parent_ie: Parent inventory entry if known, or None. If
474
None, the parent is looked up by name and used if present, otherwise it
475
is recursively added.
476
:param kind: Kind of new entry (file, directory, etc)
477
:param action: callback(inv, parent_ie, path, kind); return ignored.
478
:return: A list of paths which have been added.
480
# Nothing to do if path is already versioned.
481
# This is safe from infinite recursion because the tree root is
483
if parent_ie is not None:
484
# we have a parent ie already
487
# slower but does not need parent_ie
488
if inv.has_filename(path.raw_path):
490
# its really not there : add the parent
491
# note that the dirname use leads to some extra str copying etc but as
492
# there are a limited number of dirs we can be nested under, it should
493
# generally find it very fast and not recurse after that.
494
added = _add_one_and_parent(tree, inv, None, FastPath(dirname(path.raw_path)), 'directory', action)
495
parent_id = inv.path2id(dirname(path.raw_path))
496
parent_ie = inv[parent_id]
497
_add_one(tree, inv, parent_ie, path, kind, action)
498
return added + [path.raw_path]
501
def _add_one(tree, inv, parent_ie, path, kind, file_id_callback):
502
"""Add a new entry to the inventory.
504
:param inv: Inventory which will receive the new entry.
505
:param parent_ie: Parent inventory entry.
506
:param kind: Kind of new entry (file, directory, etc)
507
:param file_id_callback: callback(inv, parent_ie, path, kind); return a
508
file_id or None to generate a new file id
511
file_id = file_id_callback(inv, parent_ie, path, kind)
512
entry = inv.make_entry(kind, path.base_path, parent_ie.file_id,