/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: Jelmer Vernooij
  • Date: 2017-08-07 11:49:46 UTC
  • mto: (6747.3.4 avoid-set-revid-3)
  • mto: This revision was merged to the branch mainline in revision 6750.
  • Revision ID: jelmer@jelmer.uk-20170807114946-luclmxuawyzhpiot
Avoid setting revision_ids.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""MutableTree object.
 
18
 
 
19
See MutableTree for more details.
 
20
"""
 
21
 
 
22
from __future__ import absolute_import
 
23
 
 
24
import operator
 
25
import os
 
26
from . import (
 
27
    errors,
 
28
    hooks,
 
29
    osutils,
 
30
    trace,
 
31
    tree,
 
32
    )
 
33
 
 
34
from .decorators import needs_read_lock, needs_write_lock
 
35
from .sixish import (
 
36
    text_type,
 
37
    viewvalues,
 
38
    )
 
39
 
 
40
 
 
41
def needs_tree_write_lock(unbound):
 
42
    """Decorate unbound to take out and release a tree_write lock."""
 
43
    def tree_write_locked(self, *args, **kwargs):
 
44
        self.lock_tree_write()
 
45
        try:
 
46
            return unbound(self, *args, **kwargs)
 
47
        finally:
 
48
            self.unlock()
 
49
    tree_write_locked.__doc__ = unbound.__doc__
 
50
    tree_write_locked.__name__ = unbound.__name__
 
51
    return tree_write_locked
 
52
 
 
53
 
 
54
class MutableTree(tree.Tree):
 
55
    """A MutableTree is a specialisation of Tree which is able to be mutated.
 
56
 
 
57
    Generally speaking these mutations are only possible within a lock_write
 
58
    context, and will revert if the lock is broken abnormally - but this cannot
 
59
    be guaranteed - depending on the exact implementation of the mutable state.
 
60
 
 
61
    The most common form of Mutable Tree is WorkingTree, see breezy.workingtree.
 
62
    For tests we also have MemoryTree which is a MutableTree whose contents are
 
63
    entirely in memory.
 
64
 
 
65
    For now, we are not treating MutableTree as an interface to provide
 
66
    conformance tests for - rather we are testing MemoryTree specifically, and
 
67
    interface testing implementations of WorkingTree.
 
68
 
 
69
    A mutable tree always has an associated Branch and ControlDir object - the
 
70
    branch and bzrdir attributes.
 
71
    """
 
72
    def __init__(self, *args, **kw):
 
73
        super(MutableTree, self).__init__(*args, **kw)
 
74
        # Is this tree on a case-insensitive or case-preserving file-system?
 
75
        # Sub-classes may initialize to False if they detect they are being
 
76
        # used on media which doesn't differentiate the case of names.
 
77
        self.case_sensitive = True
 
78
 
 
79
    def is_control_filename(self, filename):
 
80
        """True if filename is the name of a control file in this tree.
 
81
 
 
82
        :param filename: A filename within the tree. This is a relative path
 
83
            from the root of this tree.
 
84
 
 
85
        This is true IF and ONLY IF the filename is part of the meta data
 
86
        that bzr controls in this tree. I.E. a random .bzr directory placed
 
87
        on disk will not be a control file for this tree.
 
88
        """
 
89
        raise NotImplementedError(self.is_control_filename)
 
90
 
 
91
    @needs_tree_write_lock
 
92
    def add(self, files, ids=None, kinds=None):
 
93
        """Add paths to the set of versioned paths.
 
94
 
 
95
        Note that the command line normally calls smart_add instead,
 
96
        which can automatically recurse.
 
97
 
 
98
        This adds the files to the inventory, so that they will be
 
99
        recorded by the next commit.
 
100
 
 
101
        :param files: List of paths to add, relative to the base of the tree.
 
102
        :param ids: If set, use these instead of automatically generated ids.
 
103
            Must be the same length as the list of files, but may
 
104
            contain None for ids that are to be autogenerated.
 
105
        :param kinds: Optional parameter to specify the kinds to be used for
 
106
            each file.
 
107
 
 
108
        TODO: Perhaps callback with the ids and paths as they're added.
 
109
        """
 
110
        if isinstance(files, (str, text_type)):
 
111
            # XXX: Passing a single string is inconsistent and should be
 
112
            # deprecated.
 
113
            if not (ids is None or isinstance(ids, (str, text_type))):
 
114
                raise AssertionError()
 
115
            if not (kinds is None or isinstance(kinds, (str, text_type))):
 
116
                raise AssertionError()
 
117
            files = [files]
 
118
            if ids is not None:
 
119
                ids = [ids]
 
120
            if kinds is not None:
 
121
                kinds = [kinds]
 
122
 
 
123
        files = [path.strip('/') for path in files]
 
124
 
 
125
        if ids is None:
 
126
            ids = [None] * len(files)
 
127
        else:
 
128
            if not (len(ids) == len(files)):
 
129
                raise AssertionError()
 
130
        if kinds is None:
 
131
            kinds = [None] * len(files)
 
132
        elif not len(kinds) == len(files):
 
133
            raise AssertionError()
 
134
        for f in files:
 
135
            # generic constraint checks:
 
136
            if self.is_control_filename(f):
 
137
                raise errors.ForbiddenControlFileError(filename=f)
 
138
            fp = osutils.splitpath(f)
 
139
        # fill out file kinds for all files [not needed when we stop
 
140
        # caring about the instantaneous file kind within a uncommmitted tree
 
141
        #
 
142
        self._gather_kinds(files, kinds)
 
143
        self._add(files, ids, kinds)
 
144
 
 
145
    def add_reference(self, sub_tree):
 
146
        """Add a TreeReference to the tree, pointing at sub_tree"""
 
147
        raise errors.UnsupportedOperation(self.add_reference, self)
 
148
 
 
149
    def _add_reference(self, sub_tree):
 
150
        """Standard add_reference implementation, for use by subclasses"""
 
151
        try:
 
152
            sub_tree_path = self.relpath(sub_tree.basedir)
 
153
        except errors.PathNotChild:
 
154
            raise errors.BadReferenceTarget(self, sub_tree,
 
155
                                            'Target not inside tree.')
 
156
        sub_tree_id = sub_tree.get_root_id()
 
157
        if sub_tree_id == self.get_root_id():
 
158
            raise errors.BadReferenceTarget(self, sub_tree,
 
159
                                     'Trees have the same root id.')
 
160
        if self.has_id(sub_tree_id):
 
161
            raise errors.BadReferenceTarget(self, sub_tree,
 
162
                                            'Root id already present in tree')
 
163
        self._add([sub_tree_path], [sub_tree_id], ['tree-reference'])
 
164
 
 
165
    def _add(self, files, ids, kinds):
 
166
        """Helper function for add - updates the inventory.
 
167
 
 
168
        :param files: sequence of pathnames, relative to the tree root
 
169
        :param ids: sequence of suggested ids for the files (may be None)
 
170
        :param kinds: sequence of  inventory kinds of the files (i.e. may
 
171
            contain "tree-reference")
 
172
        """
 
173
        raise NotImplementedError(self._add)
 
174
 
 
175
    def apply_inventory_delta(self, changes):
 
176
        """Apply changes to the inventory as an atomic operation.
 
177
 
 
178
        :param changes: An inventory delta to apply to the working tree's
 
179
            inventory.
 
180
        :return None:
 
181
        :seealso Inventory.apply_delta: For details on the changes parameter.
 
182
        """
 
183
        raise NotImplementedError(self.apply_inventory_delta)
 
184
 
 
185
    @needs_write_lock
 
186
    def commit(self, message=None, revprops=None, *args, **kwargs):
 
187
        # avoid circular imports
 
188
        from breezy import commit
 
189
        possible_master_transports=[]
 
190
        revprops = commit.Commit.update_revprops(
 
191
                revprops,
 
192
                self.branch,
 
193
                kwargs.pop('authors', None),
 
194
                kwargs.get('local', False),
 
195
                possible_master_transports)
 
196
        # args for wt.commit start at message from the Commit.commit method,
 
197
        args = (message, ) + args
 
198
        for hook in MutableTree.hooks['start_commit']:
 
199
            hook(self)
 
200
        committed_id = commit.Commit().commit(working_tree=self,
 
201
            revprops=revprops,
 
202
            possible_master_transports=possible_master_transports,
 
203
            *args, **kwargs)
 
204
        post_hook_params = PostCommitHookParams(self)
 
205
        for hook in MutableTree.hooks['post_commit']:
 
206
            hook(post_hook_params)
 
207
        return committed_id
 
208
 
 
209
    def _gather_kinds(self, files, kinds):
 
210
        """Helper function for add - sets the entries of kinds."""
 
211
        raise NotImplementedError(self._gather_kinds)
 
212
 
 
213
    @needs_read_lock
 
214
    def has_changes(self, _from_tree=None):
 
215
        """Quickly check that the tree contains at least one commitable change.
 
216
 
 
217
        :param _from_tree: tree to compare against to find changes (default to
 
218
            the basis tree and is intended to be used by tests).
 
219
 
 
220
        :return: True if a change is found. False otherwise
 
221
        """
 
222
        # Check pending merges
 
223
        if len(self.get_parent_ids()) > 1:
 
224
            return True
 
225
        if _from_tree is None:
 
226
            _from_tree = self.basis_tree()
 
227
        changes = self.iter_changes(_from_tree)
 
228
        try:
 
229
            change = next(changes)
 
230
            # Exclude root (talk about black magic... --vila 20090629)
 
231
            if change[4] == (None, None):
 
232
                change = next(changes)
 
233
            return True
 
234
        except StopIteration:
 
235
            # No changes
 
236
            return False
 
237
 
 
238
    @needs_read_lock
 
239
    def check_changed_or_out_of_date(self, strict, opt_name,
 
240
                                     more_error, more_warning):
 
241
        """Check the tree for uncommitted changes and branch synchronization.
 
242
 
 
243
        If strict is None and not set in the config files, a warning is issued.
 
244
        If strict is True, an error is raised.
 
245
        If strict is False, no checks are done and no warning is issued.
 
246
 
 
247
        :param strict: True, False or None, searched in branch config if None.
 
248
 
 
249
        :param opt_name: strict option name to search in config file.
 
250
 
 
251
        :param more_error: Details about how to avoid the check.
 
252
 
 
253
        :param more_warning: Details about what is happening.
 
254
        """
 
255
        if strict is None:
 
256
            strict = self.branch.get_config_stack().get(opt_name)
 
257
        if strict is not False:
 
258
            err_class = None
 
259
            if (self.has_changes()):
 
260
                err_class = errors.UncommittedChanges
 
261
            elif self.last_revision() != self.branch.last_revision():
 
262
                # The tree has lost sync with its branch, there is little
 
263
                # chance that the user is aware of it but he can still force
 
264
                # the action with --no-strict
 
265
                err_class = errors.OutOfDateTree
 
266
            if err_class is not None:
 
267
                if strict is None:
 
268
                    err = err_class(self, more=more_warning)
 
269
                    # We don't want to interrupt the user if he expressed no
 
270
                    # preference about strict.
 
271
                    trace.warning('%s', err._format())
 
272
                else:
 
273
                    err = err_class(self, more=more_error)
 
274
                    raise err
 
275
 
 
276
    @needs_read_lock
 
277
    def last_revision(self):
 
278
        """Return the revision id of the last commit performed in this tree.
 
279
 
 
280
        In early tree formats the result of last_revision is the same as the
 
281
        branch last_revision, but that is no longer the case for modern tree
 
282
        formats.
 
283
 
 
284
        last_revision returns the left most parent id, or None if there are no
 
285
        parents.
 
286
 
 
287
        last_revision was deprecated as of 0.11. Please use get_parent_ids
 
288
        instead.
 
289
        """
 
290
        raise NotImplementedError(self.last_revision)
 
291
 
 
292
    def lock_tree_write(self):
 
293
        """Lock the working tree for write, and the branch for read.
 
294
 
 
295
        This is useful for operations which only need to mutate the working
 
296
        tree. Taking out branch write locks is a relatively expensive process
 
297
        and may fail if the branch is on read only media. So branch write locks
 
298
        should only be taken out when we are modifying branch data - such as in
 
299
        operations like commit, pull, uncommit and update.
 
300
        """
 
301
        raise NotImplementedError(self.lock_tree_write)
 
302
 
 
303
    def lock_write(self):
 
304
        """Lock the tree and its branch. This allows mutating calls to be made.
 
305
 
 
306
        Some mutating methods will take out implicit write locks, but in
 
307
        general you should always obtain a write lock before calling mutating
 
308
        methods on a tree.
 
309
        """
 
310
        raise NotImplementedError(self.lock_write)
 
311
 
 
312
    @needs_write_lock
 
313
    def mkdir(self, path, file_id=None):
 
314
        """Create a directory in the tree. if file_id is None, one is assigned.
 
315
 
 
316
        :param path: A unicode file path.
 
317
        :param file_id: An optional file-id.
 
318
        :return: the file id of the new directory.
 
319
        """
 
320
        raise NotImplementedError(self.mkdir)
 
321
 
 
322
    def _observed_sha1(self, file_id, path, sha_and_stat):
 
323
        """Tell the tree we have observed a paths sha1.
 
324
 
 
325
        The intent of this function is to allow trees that have a hashcache to
 
326
        update the hashcache during commit. If the observed file is too new
 
327
        (based on the stat_value) to be safely hash-cached the tree will ignore
 
328
        it.
 
329
 
 
330
        The default implementation does nothing.
 
331
 
 
332
        :param file_id: The file id
 
333
        :param path: The file path
 
334
        :param sha_and_stat: The sha 1 and stat result observed.
 
335
        :return: None
 
336
        """
 
337
 
 
338
    @needs_write_lock
 
339
    def put_file_bytes_non_atomic(self, file_id, bytes):
 
340
        """Update the content of a file in the tree.
 
341
 
 
342
        Note that the file is written in-place rather than being
 
343
        written to a temporary location and renamed. As a consequence,
 
344
        readers can potentially see the file half-written.
 
345
 
 
346
        :param file_id: file-id of the file
 
347
        :param bytes: the new file contents
 
348
        """
 
349
        raise NotImplementedError(self.put_file_bytes_non_atomic)
 
350
 
 
351
    def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
 
352
        """Set the parents ids of the working tree.
 
353
 
 
354
        :param revision_ids: A list of revision_ids.
 
355
        """
 
356
        raise NotImplementedError(self.set_parent_ids)
 
357
 
 
358
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
 
359
        """Set the parents of the working tree.
 
360
 
 
361
        :param parents_list: A list of (revision_id, tree) tuples.
 
362
            If tree is None, then that element is treated as an unreachable
 
363
            parent tree - i.e. a ghost.
 
364
        """
 
365
        raise NotImplementedError(self.set_parent_trees)
 
366
 
 
367
    def smart_add(self, file_list, recurse=True, action=None, save=True):
 
368
        """Version file_list, optionally recursing into directories.
 
369
 
 
370
        This is designed more towards DWIM for humans than API clarity.
 
371
        For the specific behaviour see the help for cmd_add().
 
372
 
 
373
        :param file_list: List of zero or more paths.  *NB: these are 
 
374
            interpreted relative to the process cwd, not relative to the 
 
375
            tree.*  (Add and most other tree methods use tree-relative
 
376
            paths.)
 
377
        :param action: A reporter to be called with the inventory, parent_ie,
 
378
            path and kind of the path being added. It may return a file_id if
 
379
            a specific one should be used.
 
380
        :param save: Save the inventory after completing the adds. If False
 
381
            this provides dry-run functionality by doing the add and not saving
 
382
            the inventory.
 
383
        :return: A tuple - files_added, ignored_files. files_added is the count
 
384
            of added files, and ignored_files is a dict mapping files that were
 
385
            ignored to the rule that caused them to be ignored.
 
386
        """
 
387
        raise NotImplementedError(self.smart_add)
 
388
 
 
389
 
 
390
class MutableTreeHooks(hooks.Hooks):
 
391
    """A dictionary mapping a hook name to a list of callables for mutabletree
 
392
    hooks.
 
393
    """
 
394
 
 
395
    def __init__(self):
 
396
        """Create the default hooks.
 
397
 
 
398
        """
 
399
        hooks.Hooks.__init__(self, "breezy.mutabletree", "MutableTree.hooks")
 
400
        self.add_hook('start_commit',
 
401
            "Called before a commit is performed on a tree. The start commit "
 
402
            "hook is able to change the tree before the commit takes place. "
 
403
            "start_commit is called with the breezy.mutabletree.MutableTree "
 
404
            "that the commit is being performed on.", (1, 4))
 
405
        self.add_hook('post_commit',
 
406
            "Called after a commit is performed on a tree. The hook is "
 
407
            "called with a breezy.mutabletree.PostCommitHookParams object. "
 
408
            "The mutable tree the commit was performed on is available via "
 
409
            "the mutable_tree attribute of that object.", (2, 0))
 
410
        self.add_hook('pre_transform',
 
411
            "Called before a tree transform on this tree. The hook is called "
 
412
            "with the tree that is being transformed and the transform.",
 
413
            (2, 5))
 
414
        self.add_hook('post_build_tree',
 
415
            "Called after a completely new tree is built. The hook is "
 
416
            "called with the tree as its only argument.", (2, 5))
 
417
        self.add_hook('post_transform',
 
418
            "Called after a tree transform has been performed on a tree. "
 
419
            "The hook is called with the tree that is being transformed and "
 
420
            "the transform.",
 
421
            (2, 5))
 
422
 
 
423
# install the default hooks into the MutableTree class.
 
424
MutableTree.hooks = MutableTreeHooks()
 
425
 
 
426
 
 
427
class PostCommitHookParams(object):
 
428
    """Parameters for the post_commit hook.
 
429
 
 
430
    To access the parameters, use the following attributes:
 
431
 
 
432
    * mutable_tree - the MutableTree object
 
433
    """
 
434
 
 
435
    def __init__(self, mutable_tree):
 
436
        """Create the parameters for the post_commit hook."""
 
437
        self.mutable_tree = mutable_tree