~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/mutabletree.py

  • Committer: Naoki INADA
  • Date: 2009-10-29 10:01:19 UTC
  • mto: (4634.97.3 2.0)
  • mto: This revision was merged to the branch mainline in revision 4798.
  • Revision ID: inada-n@klab.jp-20091029100119-uckv9t7ej2qrghw3
import doc-ja rev90

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""MutableTree object.
18
18
 
23
23
from bzrlib.lazy_import import lazy_import
24
24
lazy_import(globals(), """
25
25
import os
 
26
import re
26
27
 
27
28
from bzrlib import (
28
29
    add,
29
30
    bzrdir,
 
31
    hooks,
 
32
    symbol_versioning,
30
33
    )
31
34
from bzrlib.osutils import dirname
32
35
from bzrlib.revisiontree import RevisionTree
40
43
    )
41
44
from bzrlib.decorators import needs_read_lock, needs_write_lock
42
45
from bzrlib.osutils import splitpath
43
 
from bzrlib.symbol_versioning import DEPRECATED_PARAMETER
44
46
 
45
47
 
46
48
def needs_tree_write_lock(unbound):
68
70
    entirely in memory.
69
71
 
70
72
    For now, we are not treating MutableTree as an interface to provide
71
 
    conformance tests for - rather we are testing MemoryTree specifically, and 
 
73
    conformance tests for - rather we are testing MemoryTree specifically, and
72
74
    interface testing implementations of WorkingTree.
73
75
 
74
76
    A mutable tree always has an associated Branch and BzrDir object - the
75
77
    branch and bzrdir attributes.
76
78
    """
 
79
    def __init__(self, *args, **kw):
 
80
        super(MutableTree, self).__init__(*args, **kw)
 
81
        # Is this tree on a case-insensitive or case-preserving file-system?
 
82
        # Sub-classes may initialize to False if they detect they are being
 
83
        # used on media which doesn't differentiate the case of names.
 
84
        self.case_sensitive = True
77
85
 
78
86
    @needs_tree_write_lock
79
87
    def add(self, files, ids=None, kinds=None):
95
103
        TODO: Perhaps callback with the ids and paths as they're added.
96
104
        """
97
105
        if isinstance(files, basestring):
98
 
            assert(ids is None or isinstance(ids, basestring))
99
 
            assert(kinds is None or isinstance(kinds, basestring))
 
106
            # XXX: Passing a single string is inconsistent and should be
 
107
            # deprecated.
 
108
            if not (ids is None or isinstance(ids, basestring)):
 
109
                raise AssertionError()
 
110
            if not (kinds is None or isinstance(kinds, basestring)):
 
111
                raise AssertionError()
100
112
            files = [files]
101
113
            if ids is not None:
102
114
                ids = [ids]
108
120
        if ids is None:
109
121
            ids = [None] * len(files)
110
122
        else:
111
 
            assert(len(ids) == len(files))
 
123
            if not (len(ids) == len(files)):
 
124
                raise AssertionError()
112
125
        if kinds is None:
113
126
            kinds = [None] * len(files)
114
 
        else:
115
 
            assert(len(kinds) == len(files))
 
127
        elif not len(kinds) == len(files):
 
128
            raise AssertionError()
116
129
        for f in files:
117
130
            # generic constraint checks:
118
131
            if self.is_control_filename(f):
119
132
                raise errors.ForbiddenControlFileError(filename=f)
120
133
            fp = splitpath(f)
121
 
        # fill out file kinds for all files [not needed when we stop 
 
134
        # fill out file kinds for all files [not needed when we stop
122
135
        # caring about the instantaneous file kind within a uncommmitted tree
123
136
        #
124
137
        self._gather_kinds(files, kinds)
175
188
        from bzrlib import commit
176
189
        if revprops is None:
177
190
            revprops = {}
 
191
        possible_master_transports=[]
178
192
        if not 'branch-nick' in revprops:
179
 
            revprops['branch-nick'] = self.branch.nick
 
193
            revprops['branch-nick'] = self.branch._get_nick(
 
194
                kwargs.get('local', False),
 
195
                possible_master_transports)
 
196
        authors = kwargs.pop('authors', None)
180
197
        author = kwargs.pop('author', None)
 
198
        if authors is not None:
 
199
            if author is not None:
 
200
                raise AssertionError('Specifying both author and authors '
 
201
                        'is not allowed. Specify just authors instead')
 
202
            if 'author' in revprops or 'authors' in revprops:
 
203
                # XXX: maybe we should just accept one of them?
 
204
                raise AssertionError('author property given twice')
 
205
            if authors:
 
206
                for individual in authors:
 
207
                    if '\n' in individual:
 
208
                        raise AssertionError('\\n is not a valid character '
 
209
                                'in an author identity')
 
210
                revprops['authors'] = '\n'.join(authors)
181
211
        if author is not None:
182
 
            assert 'author' not in revprops
183
 
            revprops['author'] = author
 
212
            symbol_versioning.warn('The parameter author was deprecated'
 
213
                   ' in version 1.13. Use authors instead',
 
214
                   DeprecationWarning)
 
215
            if 'author' in revprops or 'authors' in revprops:
 
216
                # XXX: maybe we should just accept one of them?
 
217
                raise AssertionError('author property given twice')
 
218
            if '\n' in author:
 
219
                raise AssertionError('\\n is not a valid character '
 
220
                        'in an author identity')
 
221
            revprops['authors'] = author
184
222
        # args for wt.commit start at message from the Commit.commit method,
185
223
        args = (message, ) + args
 
224
        for hook in MutableTree.hooks['start_commit']:
 
225
            hook(self)
186
226
        committed_id = commit.Commit().commit(working_tree=self,
187
 
            revprops=revprops, *args, **kwargs)
 
227
            revprops=revprops,
 
228
            possible_master_transports=possible_master_transports,
 
229
            *args, **kwargs)
 
230
        post_hook_params = PostCommitHookParams(self)
 
231
        for hook in MutableTree.hooks['post_commit']:
 
232
            hook(post_hook_params)
188
233
        return committed_id
189
234
 
190
235
    def _gather_kinds(self, files, kinds):
192
237
        raise NotImplementedError(self._gather_kinds)
193
238
 
194
239
    @needs_read_lock
 
240
    def has_changes(self, from_tree):
 
241
        """Quickly check that the tree contains at least one change.
 
242
 
 
243
        :return: True if a change is found. False otherwise
 
244
        """
 
245
        changes = self.iter_changes(from_tree)
 
246
        try:
 
247
            change = changes.next()
 
248
            # Exclude root (talk about black magic... --vila 20090629)
 
249
            if change[4] == (None, None):
 
250
                change = changes.next()
 
251
            return True
 
252
        except StopIteration:
 
253
            # No changes
 
254
            return False
 
255
 
 
256
    @needs_read_lock
195
257
    def last_revision(self):
196
258
        """Return the revision id of the last commit performed in this tree.
197
259
 
198
260
        In early tree formats the result of last_revision is the same as the
199
261
        branch last_revision, but that is no longer the case for modern tree
200
262
        formats.
201
 
        
 
263
 
202
264
        last_revision returns the left most parent id, or None if there are no
203
265
        parents.
204
266
 
221
283
    def lock_write(self):
222
284
        """Lock the tree and its branch. This allows mutating calls to be made.
223
285
 
224
 
        Some mutating methods will take out implicit write locks, but in 
 
286
        Some mutating methods will take out implicit write locks, but in
225
287
        general you should always obtain a write lock before calling mutating
226
288
        methods on a tree.
227
289
        """
237
299
        """
238
300
        raise NotImplementedError(self.mkdir)
239
301
 
 
302
    def _observed_sha1(self, file_id, path, (sha1, stat_value)):
 
303
        """Tell the tree we have observed a paths sha1.
 
304
 
 
305
        The intent of this function is to allow trees that have a hashcache to
 
306
        update the hashcache during commit. If the observed file is too new
 
307
        (based on the stat_value) to be safely hash-cached the tree will ignore
 
308
        it.
 
309
 
 
310
        The default implementation does nothing.
 
311
 
 
312
        :param file_id: The file id
 
313
        :param path: The file path
 
314
        :param sha1: The sha 1 that was observed.
 
315
        :param stat_value: A stat result for the file the sha1 was read from.
 
316
        :return: None
 
317
        """
 
318
 
 
319
    def _fix_case_of_inventory_path(self, path):
 
320
        """If our tree isn't case sensitive, return the canonical path"""
 
321
        if not self.case_sensitive:
 
322
            path = self.get_canonical_inventory_path(path)
 
323
        return path
 
324
 
 
325
    @needs_write_lock
 
326
    def put_file_bytes_non_atomic(self, file_id, bytes):
 
327
        """Update the content of a file in the tree.
 
328
 
 
329
        Note that the file is written in-place rather than being
 
330
        written to a temporary location and renamed. As a consequence,
 
331
        readers can potentially see the file half-written.
 
332
 
 
333
        :param file_id: file-id of the file
 
334
        :param bytes: the new file contents
 
335
        """
 
336
        raise NotImplementedError(self.put_file_bytes_non_atomic)
 
337
 
240
338
    def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
241
339
        """Set the parents ids of the working tree.
242
340
 
247
345
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
248
346
        """Set the parents of the working tree.
249
347
 
250
 
        :param parents_list: A list of (revision_id, tree) tuples. 
 
348
        :param parents_list: A list of (revision_id, tree) tuples.
251
349
            If tree is None, then that element is treated as an unreachable
252
350
            parent tree - i.e. a ghost.
253
351
        """
261
359
        For the specific behaviour see the help for cmd_add().
262
360
 
263
361
        :param action: A reporter to be called with the inventory, parent_ie,
264
 
            path and kind of the path being added. It may return a file_id if 
 
362
            path and kind of the path being added. It may return a file_id if
265
363
            a specific one should be used.
266
364
        :param save: Save the inventory after completing the adds. If False
267
365
            this provides dry-run functionality by doing the add and not saving
273
371
        # not in an inner loop; and we want to remove direct use of this,
274
372
        # so here as a reminder for now. RBC 20070703
275
373
        from bzrlib.inventory import InventoryEntry
276
 
        assert isinstance(recurse, bool)
277
374
        if action is None:
278
375
            action = add.AddAction()
279
 
        
 
376
 
280
377
        if not file_list:
281
378
            # no paths supplied: add the entire tree.
282
379
            file_list = [u'.']
287
384
        dirs_to_add = []
288
385
        user_dirs = set()
289
386
 
290
 
        # validate user file paths and convert all paths to tree 
 
387
        # validate user file paths and convert all paths to tree
291
388
        # relative : it's cheaper to make a tree relative path an abspath
292
 
        # than to convert an abspath to tree relative.
293
 
        for filepath in file_list:
294
 
            rf = _FastPath(self.relpath(filepath))
 
389
        # than to convert an abspath to tree relative, and it's cheaper to
 
390
        # perform the canonicalization in bulk.
 
391
        for filepath in osutils.canonical_relpaths(self.basedir, file_list):
 
392
            rf = _FastPath(filepath)
295
393
            # validate user parameters. Our recursive code avoids adding new files
296
 
            # that need such validation 
 
394
            # that need such validation
297
395
            if self.is_control_filename(rf.raw_path):
298
396
                raise errors.ForbiddenControlFileError(filename=rf.raw_path)
299
 
            
 
397
 
300
398
            abspath = self.abspath(rf.raw_path)
301
399
            kind = osutils.file_kind(abspath)
302
400
            if kind == 'directory':
307
405
                    raise errors.BadFileKindError(filename=abspath, kind=kind)
308
406
            # ensure the named path is added, so that ignore rules in the later directory
309
407
            # walk dont skip it.
310
 
            # we dont have a parent ie known yet.: use the relatively slower inventory 
 
408
            # we dont have a parent ie known yet.: use the relatively slower inventory
311
409
            # probing method
312
410
            versioned = inv.has_filename(rf.raw_path)
313
411
            if versioned:
330
428
                dirs_to_add.append((path, None))
331
429
            prev_dir = path.raw_path
332
430
 
 
431
        illegalpath_re = re.compile(r'[\r\n]')
333
432
        # dirs_to_add is initialised to a list of directories, but as we scan
334
433
        # directories we append files to it.
335
434
        # XXX: We should determine kind of files when we scan them rather than
346
445
            if not InventoryEntry.versionable_kind(kind):
347
446
                warning("skipping %s (can't add file of kind '%s')", abspath, kind)
348
447
                continue
 
448
            if illegalpath_re.search(directory.raw_path):
 
449
                warning("skipping %r (contains \\n or \\r)" % abspath)
 
450
                continue
349
451
 
350
452
            if parent_ie is not None:
351
453
                versioned = directory.base_path in parent_ie.children
352
454
            else:
353
 
                # without the parent ie, use the relatively slower inventory 
 
455
                # without the parent ie, use the relatively slower inventory
354
456
                # probing method
355
 
                versioned = inv.has_filename(directory.raw_path)
 
457
                versioned = inv.has_filename(
 
458
                        self._fix_case_of_inventory_path(directory.raw_path))
356
459
 
357
460
            if kind == 'directory':
358
461
                try:
373
476
                # mutter("%r is already versioned", abspath)
374
477
            elif sub_tree:
375
478
                # XXX: This is wrong; people *might* reasonably be trying to add
376
 
                # subtrees as subtrees.  This should probably only be done in formats 
 
479
                # subtrees as subtrees.  This should probably only be done in formats
377
480
                # which can represent subtrees, and even then perhaps only when
378
481
                # the user asked to add subtrees.  At the moment you can add them
379
482
                # specially through 'join --reference', which is perhaps
389
492
                    # must be present:
390
493
                    this_ie = parent_ie.children[directory.base_path]
391
494
                else:
392
 
                    # without the parent ie, use the relatively slower inventory 
 
495
                    # without the parent ie, use the relatively slower inventory
393
496
                    # probing method
394
 
                    this_id = inv.path2id(directory.raw_path)
 
497
                    this_id = inv.path2id(
 
498
                            self._fix_case_of_inventory_path(directory.raw_path))
395
499
                    if this_id is None:
396
500
                        this_ie = None
397
501
                    else:
398
502
                        this_ie = inv[this_id]
399
503
 
400
504
                for subf in sorted(os.listdir(abspath)):
401
 
                    # here we could use TreeDirectory rather than 
 
505
                    # here we could use TreeDirectory rather than
402
506
                    # string concatenation.
403
507
                    subp = osutils.pathjoin(directory.raw_path, subf)
404
 
                    # TODO: is_control_filename is very slow. Make it faster. 
405
 
                    # TreeDirectory.is_control_filename could also make this 
406
 
                    # faster - its impossible for a non root dir to have a 
 
508
                    # TODO: is_control_filename is very slow. Make it faster.
 
509
                    # TreeDirectory.is_control_filename could also make this
 
510
                    # faster - its impossible for a non root dir to have a
407
511
                    # control file.
408
512
                    if self.is_control_filename(subp):
409
513
                        mutter("skip control directory %r", subp)
437
541
        inventory for the parent new_revid, and all other parent trees are
438
542
        discarded.
439
543
 
 
544
        All the changes in the delta should be changes synchronising the basis
 
545
        tree with some or all of the working tree, with a change to a directory
 
546
        requiring that its contents have been recursively included. That is,
 
547
        this is not a general purpose tree modification routine, but a helper
 
548
        for commit which is not required to handle situations that do not arise
 
549
        outside of commit.
 
550
 
 
551
        See the inventory developers documentation for the theory behind
 
552
        inventory deltas.
 
553
 
440
554
        :param new_revid: The new revision id for the trees parent.
441
555
        :param delta: An inventory delta (see apply_inventory_delta) describing
442
556
            the changes from the current left most parent revision to new_revid.
452
566
        # WorkingTree classes for optimised versions for specific format trees.
453
567
        basis = self.basis_tree()
454
568
        basis.lock_read()
455
 
        inventory = basis.inventory
 
569
        # TODO: Consider re-evaluating the need for this with CHKInventory
 
570
        # we don't strictly need to mutate an inventory for this
 
571
        # it only makes sense when apply_delta is cheaper than get_inventory()
 
572
        inventory = basis.inventory._get_mutable_inventory()
456
573
        basis.unlock()
457
574
        inventory.apply_delta(delta)
458
575
        rev_tree = RevisionTree(self.branch.repository, inventory, new_revid)
459
576
        self.set_parent_trees([(new_revid, rev_tree)])
460
577
 
461
578
 
 
579
class MutableTreeHooks(hooks.Hooks):
 
580
    """A dictionary mapping a hook name to a list of callables for mutabletree
 
581
    hooks.
 
582
    """
 
583
 
 
584
    def __init__(self):
 
585
        """Create the default hooks.
 
586
 
 
587
        """
 
588
        hooks.Hooks.__init__(self)
 
589
        self.create_hook(hooks.HookPoint('start_commit',
 
590
            "Called before a commit is performed on a tree. The start commit "
 
591
            "hook is able to change the tree before the commit takes place. "
 
592
            "start_commit is called with the bzrlib.mutabletree.MutableTree "
 
593
            "that the commit is being performed on.", (1, 4), None))
 
594
        self.create_hook(hooks.HookPoint('post_commit',
 
595
            "Called after a commit is performed on a tree. The hook is "
 
596
            "called with a bzrlib.mutabletree.PostCommitHookParams object. "
 
597
            "The mutable tree the commit was performed on is available via "
 
598
            "the mutable_tree attribute of that object.", (2, 0), None))
 
599
 
 
600
 
 
601
# install the default hooks into the MutableTree class.
 
602
MutableTree.hooks = MutableTreeHooks()
 
603
 
 
604
 
 
605
class PostCommitHookParams(object):
 
606
    """Parameters for the post_commit hook.
 
607
 
 
608
    To access the parameters, use the following attributes:
 
609
 
 
610
    * mutable_tree - the MutableTree object
 
611
    """
 
612
 
 
613
    def __init__(self, mutable_tree):
 
614
        """Create the parameters for the post_commit hook."""
 
615
        self.mutable_tree = mutable_tree
 
616
 
 
617
 
462
618
class _FastPath(object):
463
619
    """A path object with fast accessors for things like basename."""
464
620
 
498
654
        added = []
499
655
    else:
500
656
        # slower but does not need parent_ie
501
 
        if inv.has_filename(path.raw_path):
 
657
        if inv.has_filename(tree._fix_case_of_inventory_path(path.raw_path)):
502
658
            return []
503
659
        # its really not there : add the parent
504
660
        # note that the dirname use leads to some extra str copying etc but as