~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/mutabletree.py

  • Committer: John Arbash Meinel
  • Date: 2007-07-13 02:23:34 UTC
  • mfrom: (2592 +trunk) (2612 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2614.
  • Revision ID: john@arbash-meinel.com-20070713022334-qb6ewgo6v4251yd9
[merge] bzr.dev 2612

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
"""
21
21
 
22
22
 
 
23
from bzrlib.lazy_import import lazy_import
 
24
lazy_import(globals(), """
 
25
import os
 
26
 
 
27
from bzrlib import (
 
28
    add,
 
29
    bzrdir,
 
30
    )
 
31
from bzrlib.osutils import dirname
 
32
from bzrlib.trace import mutter, warning
 
33
""")
 
34
 
23
35
from bzrlib import (
24
36
    errors,
25
37
    osutils,
260
272
            parent tree - i.e. a ghost.
261
273
        """
262
274
        raise NotImplementedError(self.set_parent_trees)
 
275
 
 
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.
 
279
 
 
280
        This is designed more towards DWIM for humans than API clarity.
 
281
        For the specific behaviour see the help for cmd_add().
 
282
 
 
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.
 
289
        :return: A tuple - files_added, ignored_files. files_added is the count
 
290
            of added files, and ignored_files is a dict mapping files that were
 
291
            ignored to the rule that caused them to be ignored.
 
292
        """
 
293
        # not in an inner loop; and we want to remove direct use of this,
 
294
        # so here as a reminder for now. RBC 20070703
 
295
        from bzrlib.inventory import InventoryEntry
 
296
        assert isinstance(recurse, bool)
 
297
        if action is None:
 
298
            action = add.AddAction()
 
299
        
 
300
        if not file_list:
 
301
            # no paths supplied: add the entire tree.
 
302
            file_list = [u'.']
 
303
        # mutter("smart add of %r")
 
304
        inv = self.inventory
 
305
        added = []
 
306
        ignored = {}
 
307
        dirs_to_add = []
 
308
        user_dirs = set()
 
309
 
 
310
        # validate user file paths and convert all paths to tree 
 
311
        # relative : it's cheaper to make a tree relative path an abspath
 
312
        # than to convert an abspath to tree relative.
 
313
        for filepath in file_list:
 
314
            rf = _FastPath(self.relpath(filepath))
 
315
            # validate user parameters. Our recursive code avoids adding new files
 
316
            # that need such validation 
 
317
            if self.is_control_filename(rf.raw_path):
 
318
                raise errors.ForbiddenControlFileError(filename=rf.raw_path)
 
319
            
 
320
            abspath = self.abspath(rf.raw_path)
 
321
            kind = osutils.file_kind(abspath)
 
322
            if kind == 'directory':
 
323
                # schedule the dir for scanning
 
324
                user_dirs.add(rf)
 
325
            else:
 
326
                if not InventoryEntry.versionable_kind(kind):
 
327
                    raise errors.BadFileKindError(filename=abspath, kind=kind)
 
328
            # ensure the named path is added, so that ignore rules in the later directory
 
329
            # walk dont skip it.
 
330
            # we dont have a parent ie known yet.: use the relatively slower inventory 
 
331
            # probing method
 
332
            versioned = inv.has_filename(rf.raw_path)
 
333
            if versioned:
 
334
                continue
 
335
            added.extend(_add_one_and_parent(self, inv, None, rf, kind, action))
 
336
 
 
337
        if not recurse:
 
338
            # no need to walk any directories at all.
 
339
            if len(added) > 0 and save:
 
340
                self._write_inventory(inv)
 
341
            return added, ignored
 
342
 
 
343
        # only walk the minimal parents needed: we have user_dirs to override
 
344
        # ignores.
 
345
        prev_dir = None
 
346
 
 
347
        is_inside = osutils.is_inside_or_parent_of_any
 
348
        for path in sorted(user_dirs):
 
349
            if (prev_dir is None or not is_inside([prev_dir], path.raw_path)):
 
350
                dirs_to_add.append((path, None))
 
351
            prev_dir = path.raw_path
 
352
 
 
353
        # dirs_to_add is initialised to a list of directories, but as we scan
 
354
        # directories we append files to it.
 
355
        # XXX: We should determine kind of files when we scan them rather than
 
356
        # adding to this list. RBC 20070703
 
357
        for directory, parent_ie in dirs_to_add:
 
358
            # directory is tree-relative
 
359
            abspath = self.abspath(directory.raw_path)
 
360
 
 
361
            # get the contents of this directory.
 
362
 
 
363
            # find the kind of the path being added.
 
364
            kind = osutils.file_kind(abspath)
 
365
 
 
366
            if not InventoryEntry.versionable_kind(kind):
 
367
                warning("skipping %s (can't add file of kind '%s')", abspath, kind)
 
368
                continue
 
369
 
 
370
            if parent_ie is not None:
 
371
                versioned = directory.base_path in parent_ie.children
 
372
            else:
 
373
                # without the parent ie, use the relatively slower inventory 
 
374
                # probing method
 
375
                versioned = inv.has_filename(directory.raw_path)
 
376
 
 
377
            if kind == 'directory':
 
378
                try:
 
379
                    sub_branch = bzrdir.BzrDir.open(abspath)
 
380
                    sub_tree = True
 
381
                except errors.NotBranchError:
 
382
                    sub_tree = False
 
383
                except errors.UnsupportedFormatError:
 
384
                    sub_tree = True
 
385
            else:
 
386
                sub_tree = False
 
387
 
 
388
            if directory.raw_path == '':
 
389
                # mutter("tree root doesn't need to be added")
 
390
                sub_tree = False
 
391
            elif versioned:
 
392
                pass
 
393
                # mutter("%r is already versioned", abspath)
 
394
            elif sub_tree:
 
395
                # XXX: This is wrong; people *might* reasonably be trying to add
 
396
                # subtrees as subtrees.  This should probably only be done in formats 
 
397
                # which can represent subtrees, and even then perhaps only when
 
398
                # the user asked to add subtrees.  At the moment you can add them
 
399
                # specially through 'join --reference', which is perhaps
 
400
                # reasonable: adding a new reference is a special operation and
 
401
                # can have a special behaviour.  mbp 20070306
 
402
                mutter("%r is a nested bzr tree", abspath)
 
403
            else:
 
404
                _add_one(self, inv, parent_ie, directory, kind, action)
 
405
                added.append(directory.raw_path)
 
406
 
 
407
            if kind == 'directory' and not sub_tree:
 
408
                if parent_ie is not None:
 
409
                    # must be present:
 
410
                    this_ie = parent_ie.children[directory.base_path]
 
411
                else:
 
412
                    # without the parent ie, use the relatively slower inventory 
 
413
                    # probing method
 
414
                    this_id = inv.path2id(directory.raw_path)
 
415
                    if this_id is None:
 
416
                        this_ie = None
 
417
                    else:
 
418
                        this_ie = inv[this_id]
 
419
 
 
420
                for subf in sorted(os.listdir(abspath)):
 
421
                    # here we could use TreeDirectory rather than 
 
422
                    # string concatenation.
 
423
                    subp = osutils.pathjoin(directory.raw_path, subf)
 
424
                    # TODO: is_control_filename is very slow. Make it faster. 
 
425
                    # TreeDirectory.is_control_filename could also make this 
 
426
                    # faster - its impossible for a non root dir to have a 
 
427
                    # control file.
 
428
                    if self.is_control_filename(subp):
 
429
                        mutter("skip control directory %r", subp)
 
430
                    elif subf in this_ie.children:
 
431
                        # recurse into this already versioned subdir.
 
432
                        dirs_to_add.append((_FastPath(subp, subf), this_ie))
 
433
                    else:
 
434
                        # user selection overrides ignoes
 
435
                        # ignore while selecting files - if we globbed in the
 
436
                        # outer loop we would ignore user files.
 
437
                        ignore_glob = self.is_ignored(subp)
 
438
                        if ignore_glob is not None:
 
439
                            # mutter("skip ignored sub-file %r", subp)
 
440
                            ignored.setdefault(ignore_glob, []).append(subp)
 
441
                        else:
 
442
                            #mutter("queue to add sub-file %r", subp)
 
443
                            dirs_to_add.append((_FastPath(subp, subf), this_ie))
 
444
 
 
445
        if len(added) > 0:
 
446
            if save:
 
447
                self._write_inventory(inv)
 
448
            else:
 
449
                self.read_working_inventory()
 
450
        return added, ignored
 
451
 
 
452
 
 
453
class _FastPath(object):
 
454
    """A path object with fast accessors for things like basename."""
 
455
 
 
456
    __slots__ = ['raw_path', 'base_path']
 
457
 
 
458
    def __init__(self, path, base_path=None):
 
459
        """Construct a FastPath from path."""
 
460
        if base_path is None:
 
461
            self.base_path = osutils.basename(path)
 
462
        else:
 
463
            self.base_path = base_path
 
464
        self.raw_path = path
 
465
 
 
466
    def __cmp__(self, other):
 
467
        return cmp(self.raw_path, other.raw_path)
 
468
 
 
469
    def __hash__(self):
 
470
        return hash(self.raw_path)
 
471
 
 
472
 
 
473
def _add_one_and_parent(tree, inv, parent_ie, path, kind, action):
 
474
    """Add a new entry to the inventory and automatically add unversioned parents.
 
475
 
 
476
    :param inv: Inventory which will receive the new entry.
 
477
    :param parent_ie: Parent inventory entry if known, or None.  If
 
478
        None, the parent is looked up by name and used if present, otherwise it
 
479
        is recursively added.
 
480
    :param kind: Kind of new entry (file, directory, etc)
 
481
    :param action: callback(inv, parent_ie, path, kind); return ignored.
 
482
    :return: A list of paths which have been added.
 
483
    """
 
484
    # Nothing to do if path is already versioned.
 
485
    # This is safe from infinite recursion because the tree root is
 
486
    # always versioned.
 
487
    if parent_ie is not None:
 
488
        # we have a parent ie already
 
489
        added = []
 
490
    else:
 
491
        # slower but does not need parent_ie
 
492
        if inv.has_filename(path.raw_path):
 
493
            return []
 
494
        # its really not there : add the parent
 
495
        # note that the dirname use leads to some extra str copying etc but as
 
496
        # there are a limited number of dirs we can be nested under, it should
 
497
        # generally find it very fast and not recurse after that.
 
498
        added = _add_one_and_parent(tree, inv, None,
 
499
            _FastPath(dirname(path.raw_path)), 'directory', action)
 
500
        parent_id = inv.path2id(dirname(path.raw_path))
 
501
        parent_ie = inv[parent_id]
 
502
    _add_one(tree, inv, parent_ie, path, kind, action)
 
503
    return added + [path.raw_path]
 
504
 
 
505
 
 
506
def _add_one(tree, inv, parent_ie, path, kind, file_id_callback):
 
507
    """Add a new entry to the inventory.
 
508
 
 
509
    :param inv: Inventory which will receive the new entry.
 
510
    :param parent_ie: Parent inventory entry.
 
511
    :param kind: Kind of new entry (file, directory, etc)
 
512
    :param file_id_callback: callback(inv, parent_ie, path, kind); return a
 
513
        file_id or None to generate a new file id
 
514
    :returns: None
 
515
    """
 
516
    file_id = file_id_callback(inv, parent_ie, path, kind)
 
517
    entry = inv.make_entry(kind, path.base_path, parent_ie.file_id,
 
518
        file_id=file_id)
 
519
    inv.add(entry)