122
125
            kinds = [None] * len(files)
 
123
126
        elif not len(kinds) == len(files):
 
124
127
            raise AssertionError()
 
126
 
            # generic constraint checks:
 
127
 
            if self.is_control_filename(f):
 
128
 
                raise errors.ForbiddenControlFileError(filename=f)
 
129
 
            fp = osutils.splitpath(f)
 
130
 
        # fill out file kinds for all files [not needed when we stop
 
131
 
        # caring about the instantaneous file kind within a uncommmitted tree
 
133
 
        self._gather_kinds(files, kinds)
 
134
 
        self._add(files, ids, kinds)
 
 
128
        with self.lock_tree_write():
 
 
130
                # generic constraint checks:
 
 
131
                if self.is_control_filename(f):
 
 
132
                    raise errors.ForbiddenControlFileError(filename=f)
 
 
133
                fp = osutils.splitpath(f)
 
 
134
            # fill out file kinds for all files [not needed when we stop
 
 
135
            # caring about the instantaneous file kind within a uncommmitted tree
 
 
137
            self._gather_kinds(files, kinds)
 
 
138
            self._add(files, ids, kinds)
 
136
140
    def add_reference(self, sub_tree):
 
137
 
        """Add a TreeReference to the tree, pointing at sub_tree"""
 
 
141
        """Add a TreeReference to the tree, pointing at sub_tree.
 
 
143
        :param sub_tree: subtree to add.
 
138
145
        raise errors.UnsupportedOperation(self.add_reference, self)
 
140
 
    def _add_reference(self, sub_tree):
 
141
 
        """Standard add_reference implementation, for use by subclasses"""
 
143
 
            sub_tree_path = self.relpath(sub_tree.basedir)
 
144
 
        except errors.PathNotChild:
 
145
 
            raise errors.BadReferenceTarget(self, sub_tree,
 
146
 
                                            'Target not inside tree.')
 
147
 
        sub_tree_id = sub_tree.get_root_id()
 
148
 
        if sub_tree_id == self.get_root_id():
 
149
 
            raise errors.BadReferenceTarget(self, sub_tree,
 
150
 
                                     'Trees have the same root id.')
 
151
 
        if sub_tree_id in self.inventory:
 
152
 
            raise errors.BadReferenceTarget(self, sub_tree,
 
153
 
                                            'Root id already present in tree')
 
154
 
        self._add([sub_tree_path], [sub_tree_id], ['tree-reference'])
 
156
147
    def _add(self, files, ids, kinds):
 
157
148
        """Helper function for add - updates the inventory.
 
 
164
155
        raise NotImplementedError(self._add)
 
166
 
    @needs_tree_write_lock
 
167
 
    def apply_inventory_delta(self, changes):
 
168
 
        """Apply changes to the inventory as an atomic operation.
 
170
 
        :param changes: An inventory delta to apply to the working tree's
 
173
 
        :seealso Inventory.apply_delta: For details on the changes parameter.
 
177
 
        inv.apply_delta(changes)
 
178
 
        self._write_inventory(inv)
 
181
 
    def commit(self, message=None, revprops=None, *args,
 
 
157
    def commit(self, message=None, revprops=None, *args, **kwargs):
 
183
158
        # avoid circular imports
 
184
 
        from bzrlib import commit
 
 
159
        from breezy import commit
 
187
160
        possible_master_transports=[]
 
188
 
        if not 'branch-nick' in revprops:
 
189
 
            revprops['branch-nick'] = self.branch._get_nick(
 
190
 
                kwargs.get('local', False),
 
191
 
                possible_master_transports)
 
192
 
        authors = kwargs.pop('authors', None)
 
193
 
        author = kwargs.pop('author', None)
 
194
 
        if authors is not None:
 
195
 
            if author is not None:
 
196
 
                raise AssertionError('Specifying both author and authors '
 
197
 
                        'is not allowed. Specify just authors instead')
 
198
 
            if 'author' in revprops or 'authors' in revprops:
 
199
 
                # XXX: maybe we should just accept one of them?
 
200
 
                raise AssertionError('author property given twice')
 
202
 
                for individual in authors:
 
203
 
                    if '\n' in individual:
 
204
 
                        raise AssertionError('\\n is not a valid character '
 
205
 
                                'in an author identity')
 
206
 
                revprops['authors'] = '\n'.join(authors)
 
207
 
        if author is not None:
 
208
 
            symbol_versioning.warn('The parameter author was deprecated'
 
209
 
                   ' in version 1.13. Use authors instead',
 
211
 
            if 'author' in revprops or 'authors' in revprops:
 
212
 
                # XXX: maybe we should just accept one of them?
 
213
 
                raise AssertionError('author property given twice')
 
215
 
                raise AssertionError('\\n is not a valid character '
 
216
 
                        'in an author identity')
 
217
 
            revprops['authors'] = author
 
218
 
        # args for wt.commit start at message from the Commit.commit method,
 
219
 
        args = (message, ) + args
 
220
 
        for hook in MutableTree.hooks['start_commit']:
 
222
 
        committed_id = commit.Commit().commit(working_tree=self,
 
224
 
            possible_master_transports=possible_master_transports,
 
226
 
        post_hook_params = PostCommitHookParams(self)
 
227
 
        for hook in MutableTree.hooks['post_commit']:
 
228
 
            hook(post_hook_params)
 
 
161
        with self.lock_write():
 
 
162
            revprops = commit.Commit.update_revprops(
 
 
165
                    kwargs.pop('authors', None),
 
 
166
                    kwargs.get('local', False),
 
 
167
                    possible_master_transports)
 
 
168
            # args for wt.commit start at message from the Commit.commit method,
 
 
169
            args = (message, ) + args
 
 
170
            for hook in MutableTree.hooks['start_commit']:
 
 
172
            committed_id = commit.Commit().commit(working_tree=self,
 
 
174
                possible_master_transports=possible_master_transports,
 
 
176
            post_hook_params = PostCommitHookParams(self)
 
 
177
            for hook in MutableTree.hooks['post_commit']:
 
 
178
                hook(post_hook_params)
 
231
181
    def _gather_kinds(self, files, kinds):
 
232
182
        """Helper function for add - sets the entries of kinds."""
 
233
183
        raise NotImplementedError(self._gather_kinds)
 
236
185
    def has_changes(self, _from_tree=None):
 
237
186
        """Quickly check that the tree contains at least one commitable change.
 
 
410
353
            of added files, and ignored_files is a dict mapping files that were
 
411
354
            ignored to the rule that caused them to be ignored.
 
413
 
        # not in an inner loop; and we want to remove direct use of this,
 
414
 
        # so here as a reminder for now. RBC 20070703
 
415
 
        from bzrlib.inventory import InventoryEntry
 
417
 
            action = add.AddAction()
 
420
 
            # no paths supplied: add the entire tree.
 
421
 
            # FIXME: this assumes we are running in a working tree subdir :-/
 
424
 
        # mutter("smart add of %r")
 
430
 
        conflicts_related = set()
 
431
 
        # Not all mutable trees can have conflicts
 
432
 
        if getattr(self, 'conflicts', None) is not None:
 
433
 
            # Collect all related files without checking whether they exist or
 
434
 
            # are versioned. It's cheaper to do that once for all conflicts
 
435
 
            # than trying to find the relevant conflict for each added file.
 
436
 
            for c in self.conflicts():
 
437
 
                conflicts_related.update(c.associated_filenames())
 
439
 
        # validate user file paths and convert all paths to tree
 
440
 
        # relative : it's cheaper to make a tree relative path an abspath
 
441
 
        # than to convert an abspath to tree relative, and it's cheaper to
 
442
 
        # perform the canonicalization in bulk.
 
443
 
        for filepath in osutils.canonical_relpaths(self.basedir, file_list):
 
444
 
            rf = _FastPath(filepath)
 
445
 
            # validate user parameters. Our recursive code avoids adding new
 
446
 
            # files that need such validation
 
447
 
            if self.is_control_filename(rf.raw_path):
 
448
 
                raise errors.ForbiddenControlFileError(filename=rf.raw_path)
 
450
 
            abspath = self.abspath(rf.raw_path)
 
451
 
            kind = osutils.file_kind(abspath)
 
452
 
            if kind == 'directory':
 
453
 
                # schedule the dir for scanning
 
456
 
                if not InventoryEntry.versionable_kind(kind):
 
457
 
                    raise errors.BadFileKindError(filename=abspath, kind=kind)
 
458
 
            # ensure the named path is added, so that ignore rules in the later
 
459
 
            # directory walk dont skip it.
 
460
 
            # we dont have a parent ie known yet.: use the relatively slower
 
461
 
            # inventory probing method
 
462
 
            versioned = inv.has_filename(rf.raw_path)
 
465
 
            added.extend(_add_one_and_parent(self, inv, None, rf, kind, action))
 
468
 
            # no need to walk any directories at all.
 
469
 
            if len(added) > 0 and save:
 
470
 
                self._write_inventory(inv)
 
471
 
            return added, ignored
 
473
 
        # only walk the minimal parents needed: we have user_dirs to override
 
477
 
        is_inside = osutils.is_inside_or_parent_of_any
 
478
 
        for path in sorted(user_dirs):
 
479
 
            if (prev_dir is None or not is_inside([prev_dir], path.raw_path)):
 
480
 
                dirs_to_add.append((path, None))
 
481
 
            prev_dir = path.raw_path
 
483
 
        illegalpath_re = re.compile(r'[\r\n]')
 
484
 
        # dirs_to_add is initialised to a list of directories, but as we scan
 
485
 
        # directories we append files to it.
 
486
 
        # XXX: We should determine kind of files when we scan them rather than
 
487
 
        # adding to this list. RBC 20070703
 
488
 
        for directory, parent_ie in dirs_to_add:
 
489
 
            # directory is tree-relative
 
490
 
            abspath = self.abspath(directory.raw_path)
 
492
 
            # get the contents of this directory.
 
494
 
            # find the kind of the path being added.
 
495
 
            kind = osutils.file_kind(abspath)
 
497
 
            if not InventoryEntry.versionable_kind(kind):
 
498
 
                trace.warning("skipping %s (can't add file of kind '%s')",
 
501
 
            if illegalpath_re.search(directory.raw_path):
 
502
 
                trace.warning("skipping %r (contains \\n or \\r)" % abspath)
 
504
 
            if directory.raw_path in conflicts_related:
 
505
 
                # If the file looks like one generated for a conflict, don't
 
508
 
                    'skipping %s (generated to help resolve conflicts)',
 
512
 
            if parent_ie is not None:
 
513
 
                versioned = directory.base_path in parent_ie.children
 
515
 
                # without the parent ie, use the relatively slower inventory
 
517
 
                versioned = inv.has_filename(
 
518
 
                        self._fix_case_of_inventory_path(directory.raw_path))
 
520
 
            if kind == 'directory':
 
522
 
                    sub_branch = bzrdir.BzrDir.open(abspath)
 
524
 
                except errors.NotBranchError:
 
526
 
                except errors.UnsupportedFormatError:
 
531
 
            if directory.raw_path == '':
 
532
 
                # mutter("tree root doesn't need to be added")
 
536
 
                # mutter("%r is already versioned", abspath)
 
538
 
                # XXX: This is wrong; people *might* reasonably be trying to
 
539
 
                # add subtrees as subtrees.  This should probably only be done
 
540
 
                # in formats which can represent subtrees, and even then
 
541
 
                # perhaps only when the user asked to add subtrees.  At the
 
542
 
                # moment you can add them specially through 'join --reference',
 
543
 
                # which is perhaps reasonable: adding a new reference is a
 
544
 
                # special operation and can have a special behaviour.  mbp
 
546
 
                trace.mutter("%r is a nested bzr tree", abspath)
 
548
 
                _add_one(self, inv, parent_ie, directory, kind, action)
 
549
 
                added.append(directory.raw_path)
 
551
 
            if kind == 'directory' and not sub_tree:
 
552
 
                if parent_ie is not None:
 
554
 
                    this_ie = parent_ie.children[directory.base_path]
 
556
 
                    # without the parent ie, use the relatively slower inventory
 
558
 
                    this_id = inv.path2id(
 
559
 
                        self._fix_case_of_inventory_path(directory.raw_path))
 
563
 
                        this_ie = inv[this_id]
 
565
 
                for subf in sorted(os.listdir(abspath)):
 
566
 
                    # here we could use TreeDirectory rather than
 
567
 
                    # string concatenation.
 
568
 
                    subp = osutils.pathjoin(directory.raw_path, subf)
 
569
 
                    # TODO: is_control_filename is very slow. Make it faster.
 
570
 
                    # TreeDirectory.is_control_filename could also make this
 
571
 
                    # faster - its impossible for a non root dir to have a
 
573
 
                    if self.is_control_filename(subp):
 
574
 
                        trace.mutter("skip control directory %r", subp)
 
575
 
                    elif subf in this_ie.children:
 
576
 
                        # recurse into this already versioned subdir.
 
577
 
                        dirs_to_add.append((_FastPath(subp, subf), this_ie))
 
579
 
                        # user selection overrides ignoes
 
580
 
                        # ignore while selecting files - if we globbed in the
 
581
 
                        # outer loop we would ignore user files.
 
582
 
                        ignore_glob = self.is_ignored(subp)
 
583
 
                        if ignore_glob is not None:
 
584
 
                            # mutter("skip ignored sub-file %r", subp)
 
585
 
                            ignored.setdefault(ignore_glob, []).append(subp)
 
587
 
                            #mutter("queue to add sub-file %r", subp)
 
588
 
                            dirs_to_add.append((_FastPath(subp, subf), this_ie))
 
592
 
                self._write_inventory(inv)
 
594
 
                self.read_working_inventory()
 
595
 
        return added, ignored
 
597
 
    def update_basis_by_delta(self, new_revid, delta):
 
598
 
        """Update the parents of this tree after a commit.
 
600
 
        This gives the tree one parent, with revision id new_revid. The
 
601
 
        inventory delta is applied to the current basis tree to generate the
 
602
 
        inventory for the parent new_revid, and all other parent trees are
 
605
 
        All the changes in the delta should be changes synchronising the basis
 
606
 
        tree with some or all of the working tree, with a change to a directory
 
607
 
        requiring that its contents have been recursively included. That is,
 
608
 
        this is not a general purpose tree modification routine, but a helper
 
609
 
        for commit which is not required to handle situations that do not arise
 
612
 
        See the inventory developers documentation for the theory behind
 
615
 
        :param new_revid: The new revision id for the trees parent.
 
616
 
        :param delta: An inventory delta (see apply_inventory_delta) describing
 
617
 
            the changes from the current left most parent revision to new_revid.
 
619
 
        # if the tree is updated by a pull to the branch, as happens in
 
620
 
        # WorkingTree2, when there was no separation between branch and tree,
 
621
 
        # then just clear merges, efficiency is not a concern for now as this
 
622
 
        # is legacy environments only, and they are slow regardless.
 
623
 
        if self.last_revision() == new_revid:
 
624
 
            self.set_parent_ids([new_revid])
 
626
 
        # generic implementation based on Inventory manipulation. See
 
627
 
        # WorkingTree classes for optimised versions for specific format trees.
 
628
 
        basis = self.basis_tree()
 
630
 
        # TODO: Consider re-evaluating the need for this with CHKInventory
 
631
 
        # we don't strictly need to mutate an inventory for this
 
632
 
        # it only makes sense when apply_delta is cheaper than get_inventory()
 
633
 
        inventory = basis.inventory._get_mutable_inventory()
 
635
 
        inventory.apply_delta(delta)
 
636
 
        rev_tree = revisiontree.RevisionTree(self.branch.repository,
 
637
 
                                             inventory, new_revid)
 
638
 
        self.set_parent_trees([(new_revid, rev_tree)])
 
 
356
        raise NotImplementedError(self.smart_add)
 
 
358
    def rename_one(self, from_rel, to_rel, after=False):
 
 
361
        This can change the directory or the filename or both.
 
 
363
        rename_one has several 'modes' to work. First, it can rename a physical
 
 
364
        file and change the file_id. That is the normal mode. Second, it can
 
 
365
        only change the file_id without touching any physical file.
 
 
367
        rename_one uses the second mode if 'after == True' and 'to_rel' is
 
 
368
        either not versioned or newly added, and present in the working tree.
 
 
370
        rename_one uses the second mode if 'after == False' and 'from_rel' is
 
 
371
        versioned but no longer in the working tree, and 'to_rel' is not
 
 
372
        versioned but present in the working tree.
 
 
374
        rename_one uses the first mode if 'after == False' and 'from_rel' is
 
 
375
        versioned and present in the working tree, and 'to_rel' is not
 
 
376
        versioned and not present in the working tree.
 
 
378
        Everything else results in an error.
 
 
380
        raise NotImplementedError(self.rename_one)
 
 
382
    def copy_one(self, from_rel, to_rel):
 
 
383
        """Copy one file or directory.
 
 
385
        This can change the directory or the filename or both.
 
 
388
        raise NotImplementedError(self.copy_one)
 
641
391
class MutableTreeHooks(hooks.Hooks):
 
 
647
397
        """Create the default hooks.
 
650
 
        hooks.Hooks.__init__(self)
 
651
 
        self.create_hook(hooks.HookPoint('start_commit',
 
 
400
        hooks.Hooks.__init__(self, "breezy.mutabletree", "MutableTree.hooks")
 
 
401
        self.add_hook('start_commit',
 
652
402
            "Called before a commit is performed on a tree. The start commit "
 
653
403
            "hook is able to change the tree before the commit takes place. "
 
654
 
            "start_commit is called with the bzrlib.mutabletree.MutableTree "
 
655
 
            "that the commit is being performed on.", (1, 4), None))
 
656
 
        self.create_hook(hooks.HookPoint('post_commit',
 
 
404
            "start_commit is called with the breezy.mutabletree.MutableTree "
 
 
405
            "that the commit is being performed on.", (1, 4))
 
 
406
        self.add_hook('post_commit',
 
657
407
            "Called after a commit is performed on a tree. The hook is "
 
658
 
            "called with a bzrlib.mutabletree.PostCommitHookParams object. "
 
 
408
            "called with a breezy.mutabletree.PostCommitHookParams object. "
 
659
409
            "The mutable tree the commit was performed on is available via "
 
660
 
            "the mutable_tree attribute of that object.", (2, 0), None))
 
 
410
            "the mutable_tree attribute of that object.", (2, 0))
 
 
411
        self.add_hook('pre_transform',
 
 
412
            "Called before a tree transform on this tree. The hook is called "
 
 
413
            "with the tree that is being transformed and the transform.",
 
 
415
        self.add_hook('post_build_tree',
 
 
416
            "Called after a completely new tree is built. The hook is "
 
 
417
            "called with the tree as its only argument.", (2, 5))
 
 
418
        self.add_hook('post_transform',
 
 
419
            "Called after a tree transform has been performed on a tree. "
 
 
420
            "The hook is called with the tree that is being transformed and "
 
663
424
# install the default hooks into the MutableTree class.
 
664
425
MutableTree.hooks = MutableTreeHooks()
 
 
675
436
    def __init__(self, mutable_tree):
 
676
437
        """Create the parameters for the post_commit hook."""
 
677
438
        self.mutable_tree = mutable_tree
 
680
 
class _FastPath(object):
 
681
 
    """A path object with fast accessors for things like basename."""
 
683
 
    __slots__ = ['raw_path', 'base_path']
 
685
 
    def __init__(self, path, base_path=None):
 
686
 
        """Construct a FastPath from path."""
 
687
 
        if base_path is None:
 
688
 
            self.base_path = osutils.basename(path)
 
690
 
            self.base_path = base_path
 
693
 
    def __cmp__(self, other):
 
694
 
        return cmp(self.raw_path, other.raw_path)
 
697
 
        return hash(self.raw_path)
 
700
 
def _add_one_and_parent(tree, inv, parent_ie, path, kind, action):
 
701
 
    """Add a new entry to the inventory and automatically add unversioned parents.
 
703
 
    :param inv: Inventory which will receive the new entry.
 
704
 
    :param parent_ie: Parent inventory entry if known, or None.  If
 
705
 
        None, the parent is looked up by name and used if present, otherwise it
 
706
 
        is recursively added.
 
707
 
    :param kind: Kind of new entry (file, directory, etc)
 
708
 
    :param action: callback(inv, parent_ie, path, kind); return ignored.
 
709
 
    :return: A list of paths which have been added.
 
711
 
    # Nothing to do if path is already versioned.
 
712
 
    # This is safe from infinite recursion because the tree root is
 
714
 
    if parent_ie is not None:
 
715
 
        # we have a parent ie already
 
718
 
        # slower but does not need parent_ie
 
719
 
        if inv.has_filename(tree._fix_case_of_inventory_path(path.raw_path)):
 
721
 
        # its really not there : add the parent
 
722
 
        # note that the dirname use leads to some extra str copying etc but as
 
723
 
        # there are a limited number of dirs we can be nested under, it should
 
724
 
        # generally find it very fast and not recurse after that.
 
725
 
        added = _add_one_and_parent(tree, inv, None,
 
726
 
            _FastPath(osutils.dirname(path.raw_path)), 'directory', action)
 
727
 
        parent_id = inv.path2id(osutils.dirname(path.raw_path))
 
728
 
        parent_ie = inv[parent_id]
 
729
 
    _add_one(tree, inv, parent_ie, path, kind, action)
 
730
 
    return added + [path.raw_path]
 
733
 
def _add_one(tree, inv, parent_ie, path, kind, file_id_callback):
 
734
 
    """Add a new entry to the inventory.
 
736
 
    :param inv: Inventory which will receive the new entry.
 
737
 
    :param parent_ie: Parent inventory entry.
 
738
 
    :param kind: Kind of new entry (file, directory, etc)
 
739
 
    :param file_id_callback: callback(inv, parent_ie, path, kind); return a
 
740
 
        file_id or None to generate a new file id
 
743
 
    file_id = file_id_callback(inv, parent_ie, path, kind)
 
744
 
    entry = inv.make_entry(kind, path.base_path, parent_ie.file_id,