/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/mutabletree.py

  • Committer: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2017-06-10 23:16:14 UTC
  • mfrom: (6672.2.7 move-invtree)
  • Revision ID: breezy.the.bot@gmail.com-20170610231614-s86lsfngizrmse0j
Move inventory-related tree implementations to breezy.inventorytree.

Merged from https://code.launchpad.net/~jelmer/brz/move-invtree/+merge/325444

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
from __future__ import absolute_import
23
23
 
24
 
from .lazy_import import lazy_import
25
 
lazy_import(globals(), """
26
24
import operator
27
25
import os
28
 
import re
29
 
 
30
 
from breezy import (
31
 
    add,
32
 
    controldir,
 
26
from . import (
33
27
    errors,
34
28
    hooks,
35
 
    inventory as _mod_inventory,
36
29
    osutils,
37
 
    revisiontree,
38
30
    trace,
39
 
    transport as _mod_transport,
40
31
    tree,
41
32
    )
42
 
""")
43
33
 
44
34
from .decorators import needs_read_lock, needs_write_lock
45
35
from .sixish import (
396
386
        raise NotImplementedError(self.smart_add)
397
387
 
398
388
 
399
 
class MutableInventoryTree(MutableTree, tree.InventoryTree):
400
 
 
401
 
    @needs_tree_write_lock
402
 
    def apply_inventory_delta(self, changes):
403
 
        """Apply changes to the inventory as an atomic operation.
404
 
 
405
 
        :param changes: An inventory delta to apply to the working tree's
406
 
            inventory.
407
 
        :return None:
408
 
        :seealso Inventory.apply_delta: For details on the changes parameter.
409
 
        """
410
 
        self.flush()
411
 
        inv = self.root_inventory
412
 
        inv.apply_delta(changes)
413
 
        self._write_inventory(inv)
414
 
 
415
 
    def _fix_case_of_inventory_path(self, path):
416
 
        """If our tree isn't case sensitive, return the canonical path"""
417
 
        if not self.case_sensitive:
418
 
            path = self.get_canonical_inventory_path(path)
419
 
        return path
420
 
 
421
 
    @needs_tree_write_lock
422
 
    def smart_add(self, file_list, recurse=True, action=None, save=True):
423
 
        """Version file_list, optionally recursing into directories.
424
 
 
425
 
        This is designed more towards DWIM for humans than API clarity.
426
 
        For the specific behaviour see the help for cmd_add().
427
 
 
428
 
        :param file_list: List of zero or more paths.  *NB: these are 
429
 
            interpreted relative to the process cwd, not relative to the 
430
 
            tree.*  (Add and most other tree methods use tree-relative
431
 
            paths.)
432
 
        :param action: A reporter to be called with the inventory, parent_ie,
433
 
            path and kind of the path being added. It may return a file_id if
434
 
            a specific one should be used.
435
 
        :param save: Save the inventory after completing the adds. If False
436
 
            this provides dry-run functionality by doing the add and not saving
437
 
            the inventory.
438
 
        :return: A tuple - files_added, ignored_files. files_added is the count
439
 
            of added files, and ignored_files is a dict mapping files that were
440
 
            ignored to the rule that caused them to be ignored.
441
 
        """
442
 
        # Not all mutable trees can have conflicts
443
 
        if getattr(self, 'conflicts', None) is not None:
444
 
            # Collect all related files without checking whether they exist or
445
 
            # are versioned. It's cheaper to do that once for all conflicts
446
 
            # than trying to find the relevant conflict for each added file.
447
 
            conflicts_related = set()
448
 
            for c in self.conflicts():
449
 
                conflicts_related.update(c.associated_filenames())
450
 
        else:
451
 
            conflicts_related = None
452
 
        adder = _SmartAddHelper(self, action, conflicts_related)
453
 
        adder.add(file_list, recurse=recurse)
454
 
        if save:
455
 
            invdelta = adder.get_inventory_delta()
456
 
            self.apply_inventory_delta(invdelta)
457
 
        return adder.added, adder.ignored
458
 
 
459
 
    def update_basis_by_delta(self, new_revid, delta):
460
 
        """Update the parents of this tree after a commit.
461
 
 
462
 
        This gives the tree one parent, with revision id new_revid. The
463
 
        inventory delta is applied to the current basis tree to generate the
464
 
        inventory for the parent new_revid, and all other parent trees are
465
 
        discarded.
466
 
 
467
 
        All the changes in the delta should be changes synchronising the basis
468
 
        tree with some or all of the working tree, with a change to a directory
469
 
        requiring that its contents have been recursively included. That is,
470
 
        this is not a general purpose tree modification routine, but a helper
471
 
        for commit which is not required to handle situations that do not arise
472
 
        outside of commit.
473
 
 
474
 
        See the inventory developers documentation for the theory behind
475
 
        inventory deltas.
476
 
 
477
 
        :param new_revid: The new revision id for the trees parent.
478
 
        :param delta: An inventory delta (see apply_inventory_delta) describing
479
 
            the changes from the current left most parent revision to new_revid.
480
 
        """
481
 
        # if the tree is updated by a pull to the branch, as happens in
482
 
        # WorkingTree2, when there was no separation between branch and tree,
483
 
        # then just clear merges, efficiency is not a concern for now as this
484
 
        # is legacy environments only, and they are slow regardless.
485
 
        if self.last_revision() == new_revid:
486
 
            self.set_parent_ids([new_revid])
487
 
            return
488
 
        # generic implementation based on Inventory manipulation. See
489
 
        # WorkingTree classes for optimised versions for specific format trees.
490
 
        basis = self.basis_tree()
491
 
        basis.lock_read()
492
 
        # TODO: Consider re-evaluating the need for this with CHKInventory
493
 
        # we don't strictly need to mutate an inventory for this
494
 
        # it only makes sense when apply_delta is cheaper than get_inventory()
495
 
        inventory = _mod_inventory.mutable_inventory_from_tree(basis)
496
 
        basis.unlock()
497
 
        inventory.apply_delta(delta)
498
 
        rev_tree = revisiontree.InventoryRevisionTree(self.branch.repository,
499
 
                                             inventory, new_revid)
500
 
        self.set_parent_trees([(new_revid, rev_tree)])
501
 
 
502
 
 
503
389
class MutableTreeHooks(hooks.Hooks):
504
390
    """A dictionary mapping a hook name to a list of callables for mutabletree
505
391
    hooks.
548
434
    def __init__(self, mutable_tree):
549
435
        """Create the parameters for the post_commit hook."""
550
436
        self.mutable_tree = mutable_tree
551
 
 
552
 
 
553
 
class _SmartAddHelper(object):
554
 
    """Helper for MutableTree.smart_add."""
555
 
 
556
 
    def get_inventory_delta(self):
557
 
        # GZ 2016-06-05: Returning view would probably be fine but currently
558
 
        # Inventory.apply_delta is documented as requiring a list of changes.
559
 
        return list(viewvalues(self._invdelta))
560
 
 
561
 
    def _get_ie(self, inv_path):
562
 
        """Retrieve the most up to date inventory entry for a path.
563
 
 
564
 
        :param inv_path: Normalized inventory path
565
 
        :return: Inventory entry (with possibly invalid .children for
566
 
            directories)
567
 
        """
568
 
        entry = self._invdelta.get(inv_path)
569
 
        if entry is not None:
570
 
            return entry[3]
571
 
        # Find a 'best fit' match if the filesystem is case-insensitive
572
 
        inv_path = self.tree._fix_case_of_inventory_path(inv_path)
573
 
        file_id = self.tree.path2id(inv_path)
574
 
        if file_id is not None:
575
 
            return self.tree.iter_entries_by_dir([file_id]).next()[1]
576
 
        return None
577
 
 
578
 
    def _convert_to_directory(self, this_ie, inv_path):
579
 
        """Convert an entry to a directory.
580
 
 
581
 
        :param this_ie: Inventory entry
582
 
        :param inv_path: Normalized path for the inventory entry
583
 
        :return: The new inventory entry
584
 
        """
585
 
        # Same as in _add_one below, if the inventory doesn't
586
 
        # think this is a directory, update the inventory
587
 
        this_ie = _mod_inventory.InventoryDirectory(
588
 
            this_ie.file_id, this_ie.name, this_ie.parent_id)
589
 
        self._invdelta[inv_path] = (inv_path, inv_path, this_ie.file_id,
590
 
            this_ie)
591
 
        return this_ie
592
 
 
593
 
    def _add_one_and_parent(self, parent_ie, path, kind, inv_path):
594
 
        """Add a new entry to the inventory and automatically add unversioned parents.
595
 
 
596
 
        :param parent_ie: Parent inventory entry if known, or None.  If
597
 
            None, the parent is looked up by name and used if present, otherwise it
598
 
            is recursively added.
599
 
        :param path: 
600
 
        :param kind: Kind of new entry (file, directory, etc)
601
 
        :param inv_path:
602
 
        :return: Inventory entry for path and a list of paths which have been added.
603
 
        """
604
 
        # Nothing to do if path is already versioned.
605
 
        # This is safe from infinite recursion because the tree root is
606
 
        # always versioned.
607
 
        inv_dirname = osutils.dirname(inv_path)
608
 
        dirname, basename = osutils.split(path)
609
 
        if parent_ie is None:
610
 
            # slower but does not need parent_ie
611
 
            this_ie = self._get_ie(inv_path)
612
 
            if this_ie is not None:
613
 
                return this_ie
614
 
            # its really not there : add the parent
615
 
            # note that the dirname use leads to some extra str copying etc but as
616
 
            # there are a limited number of dirs we can be nested under, it should
617
 
            # generally find it very fast and not recurse after that.
618
 
            parent_ie = self._add_one_and_parent(None,
619
 
                dirname, 'directory', 
620
 
                inv_dirname)
621
 
        # if the parent exists, but isn't a directory, we have to do the
622
 
        # kind change now -- really the inventory shouldn't pretend to know
623
 
        # the kind of wt files, but it does.
624
 
        if parent_ie.kind != 'directory':
625
 
            # nb: this relies on someone else checking that the path we're using
626
 
            # doesn't contain symlinks.
627
 
            parent_ie = self._convert_to_directory(parent_ie, inv_dirname)
628
 
        file_id = self.action(self.tree, parent_ie, path, kind)
629
 
        entry = _mod_inventory.make_entry(kind, basename, parent_ie.file_id,
630
 
            file_id=file_id)
631
 
        self._invdelta[inv_path] = (None, inv_path, entry.file_id, entry)
632
 
        self.added.append(inv_path)
633
 
        return entry
634
 
 
635
 
    def _gather_dirs_to_add(self, user_dirs):
636
 
        # only walk the minimal parents needed: we have user_dirs to override
637
 
        # ignores.
638
 
        prev_dir = None
639
 
 
640
 
        is_inside = osutils.is_inside_or_parent_of_any
641
 
        for path in sorted(user_dirs):
642
 
            if (prev_dir is None or not is_inside([prev_dir], path)):
643
 
                inv_path, this_ie = user_dirs[path]
644
 
                yield (path, inv_path, this_ie, None)
645
 
            prev_dir = path
646
 
 
647
 
    def __init__(self, tree, action, conflicts_related=None):
648
 
        self.tree = tree
649
 
        if action is None:
650
 
            self.action = add.AddAction()
651
 
        else:
652
 
            self.action = action
653
 
        self._invdelta = {}
654
 
        self.added = []
655
 
        self.ignored = {}
656
 
        if conflicts_related is None:
657
 
            self.conflicts_related = frozenset()
658
 
        else:
659
 
            self.conflicts_related = conflicts_related
660
 
 
661
 
    def add(self, file_list, recurse=True):
662
 
        from breezy.inventory import InventoryEntry
663
 
        if not file_list:
664
 
            # no paths supplied: add the entire tree.
665
 
            # FIXME: this assumes we are running in a working tree subdir :-/
666
 
            # -- vila 20100208
667
 
            file_list = [u'.']
668
 
 
669
 
        # expand any symlinks in the directory part, while leaving the
670
 
        # filename alone
671
 
        # only expanding if symlinks are supported avoids windows path bugs
672
 
        if osutils.has_symlinks():
673
 
            file_list = list(map(osutils.normalizepath, file_list))
674
 
 
675
 
        user_dirs = {}
676
 
        # validate user file paths and convert all paths to tree
677
 
        # relative : it's cheaper to make a tree relative path an abspath
678
 
        # than to convert an abspath to tree relative, and it's cheaper to
679
 
        # perform the canonicalization in bulk.
680
 
        for filepath in osutils.canonical_relpaths(self.tree.basedir, file_list):
681
 
            # validate user parameters. Our recursive code avoids adding new
682
 
            # files that need such validation
683
 
            if self.tree.is_control_filename(filepath):
684
 
                raise errors.ForbiddenControlFileError(filename=filepath)
685
 
 
686
 
            abspath = self.tree.abspath(filepath)
687
 
            kind = osutils.file_kind(abspath)
688
 
            # ensure the named path is added, so that ignore rules in the later
689
 
            # directory walk dont skip it.
690
 
            # we dont have a parent ie known yet.: use the relatively slower
691
 
            # inventory probing method
692
 
            inv_path, _ = osutils.normalized_filename(filepath)
693
 
            this_ie = self._get_ie(inv_path)
694
 
            if this_ie is None:
695
 
                this_ie = self._add_one_and_parent(None, filepath, kind, inv_path)
696
 
            if kind == 'directory':
697
 
                # schedule the dir for scanning
698
 
                user_dirs[filepath] = (inv_path, this_ie)
699
 
 
700
 
        if not recurse:
701
 
            # no need to walk any directories at all.
702
 
            return
703
 
 
704
 
        things_to_add = list(self._gather_dirs_to_add(user_dirs))
705
 
 
706
 
        illegalpath_re = re.compile(r'[\r\n]')
707
 
        for directory, inv_path, this_ie, parent_ie in things_to_add:
708
 
            # directory is tree-relative
709
 
            abspath = self.tree.abspath(directory)
710
 
 
711
 
            # get the contents of this directory.
712
 
 
713
 
            # find the kind of the path being added, and save stat_value
714
 
            # for reuse
715
 
            stat_value = None
716
 
            if this_ie is None:
717
 
                stat_value = osutils.file_stat(abspath)
718
 
                kind = osutils.file_kind_from_stat_mode(stat_value.st_mode)
719
 
            else:
720
 
                kind = this_ie.kind
721
 
            
722
 
            # allow AddAction to skip this file
723
 
            if self.action.skip_file(self.tree,  abspath,  kind,  stat_value):
724
 
                continue
725
 
            if not InventoryEntry.versionable_kind(kind):
726
 
                trace.warning("skipping %s (can't add file of kind '%s')",
727
 
                              abspath, kind)
728
 
                continue
729
 
            if illegalpath_re.search(directory):
730
 
                trace.warning("skipping %r (contains \\n or \\r)" % abspath)
731
 
                continue
732
 
            if directory in self.conflicts_related:
733
 
                # If the file looks like one generated for a conflict, don't
734
 
                # add it.
735
 
                trace.warning(
736
 
                    'skipping %s (generated to help resolve conflicts)',
737
 
                    abspath)
738
 
                continue
739
 
 
740
 
            if kind == 'directory' and directory != '':
741
 
                try:
742
 
                    transport = _mod_transport.get_transport_from_path(abspath)
743
 
                    controldir.ControlDirFormat.find_format(transport)
744
 
                    sub_tree = True
745
 
                except errors.NotBranchError:
746
 
                    sub_tree = False
747
 
                except errors.UnsupportedFormatError:
748
 
                    sub_tree = True
749
 
            else:
750
 
                sub_tree = False
751
 
 
752
 
            if this_ie is not None:
753
 
                pass
754
 
            elif sub_tree:
755
 
                # XXX: This is wrong; people *might* reasonably be trying to
756
 
                # add subtrees as subtrees.  This should probably only be done
757
 
                # in formats which can represent subtrees, and even then
758
 
                # perhaps only when the user asked to add subtrees.  At the
759
 
                # moment you can add them specially through 'join --reference',
760
 
                # which is perhaps reasonable: adding a new reference is a
761
 
                # special operation and can have a special behaviour.  mbp
762
 
                # 20070306
763
 
                trace.warning("skipping nested tree %r", abspath)
764
 
            else:
765
 
                this_ie = self._add_one_and_parent(parent_ie, directory, kind,
766
 
                    inv_path)
767
 
 
768
 
            if kind == 'directory' and not sub_tree:
769
 
                if this_ie.kind != 'directory':
770
 
                    this_ie = self._convert_to_directory(this_ie, inv_path)
771
 
 
772
 
                for subf in sorted(os.listdir(abspath)):
773
 
                    inv_f, _ = osutils.normalized_filename(subf)
774
 
                    # here we could use TreeDirectory rather than
775
 
                    # string concatenation.
776
 
                    subp = osutils.pathjoin(directory, subf)
777
 
                    # TODO: is_control_filename is very slow. Make it faster.
778
 
                    # TreeDirectory.is_control_filename could also make this
779
 
                    # faster - its impossible for a non root dir to have a
780
 
                    # control file.
781
 
                    if self.tree.is_control_filename(subp):
782
 
                        trace.mutter("skip control directory %r", subp)
783
 
                        continue
784
 
                    sub_invp = osutils.pathjoin(inv_path, inv_f)
785
 
                    entry = self._invdelta.get(sub_invp)
786
 
                    if entry is not None:
787
 
                        sub_ie = entry[3]
788
 
                    else:
789
 
                        sub_ie = this_ie.children.get(inv_f)
790
 
                    if sub_ie is not None:
791
 
                        # recurse into this already versioned subdir.
792
 
                        things_to_add.append((subp, sub_invp, sub_ie, this_ie))
793
 
                    else:
794
 
                        # user selection overrides ignores
795
 
                        # ignore while selecting files - if we globbed in the
796
 
                        # outer loop we would ignore user files.
797
 
                        ignore_glob = self.tree.is_ignored(subp)
798
 
                        if ignore_glob is not None:
799
 
                            self.ignored.setdefault(ignore_glob, []).append(subp)
800
 
                        else:
801
 
                            things_to_add.append((subp, sub_invp, None, this_ie))