~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/mutabletree.py

  • Committer: John Arbash Meinel
  • Date: 2007-03-14 20:15:52 UTC
  • mto: (2353.4.2 locking)
  • mto: This revision was merged to the branch mainline in revision 2360.
  • Revision ID: john@arbash-meinel.com-20070314201552-bjtfua57456dviep
Update the lock code and test code so that if more than one
lock implementation is available, they will both be tested.

It is quite a bit of overhead, for a case where we are likely to only have 1
real lock implementation per platform, but hey, for now we have 2.

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
 
 
35
23
from bzrlib import (
36
24
    errors,
37
25
    osutils,
155
143
        """
156
144
        raise NotImplementedError(self._add)
157
145
 
158
 
    @needs_tree_write_lock
159
 
    def apply_inventory_delta(self, changes):
160
 
        """Apply changes to the inventory as an atomic operation.
161
 
 
162
 
        The argument is a set of changes to apply.  It must describe a
163
 
        valid result, but the order is not important.  Specifically,
164
 
        intermediate stages *may* be invalid, such as when two files
165
 
        swap names.
166
 
 
167
 
        The changes should be structured as a list of tuples, of the form
168
 
        (old_path, new_path, file_id, new_entry).  For creation, old_path
169
 
        must be None.  For deletion, new_path and new_entry must be None.
170
 
        file_id is always non-None.  For renames and other mutations, all
171
 
        values must be non-None.
172
 
 
173
 
        If the new_entry is a directory, its children should be an empty
174
 
        dict.  Children are handled by apply_inventory_delta itself.
175
 
 
176
 
        :param changes: A list of tuples for the change to apply:
177
 
            [(old_path, new_path, file_id, new_inventory_entry), ...]
178
 
        """
179
 
        self.flush()
180
 
        inv = self.inventory
181
 
        children = {}
182
 
        for old_path, file_id in sorted(((op, f) for op, np, f, e in changes
183
 
                                        if op is not None), reverse=True):
184
 
            if file_id not in inv:
185
 
                continue
186
 
            children[file_id] = getattr(inv[file_id], 'children', {})
187
 
            inv.remove_recursive_id(file_id)
188
 
        for new_path, new_entry in sorted((np, e) for op, np, f, e in
189
 
                                          changes if np is not None):
190
 
            if getattr(new_entry, 'children', None) is not None:
191
 
                new_entry.children = children.get(new_entry.file_id, {})
192
 
            inv.add(new_entry)
193
 
        self._write_inventory(inv)
194
 
 
195
146
    @needs_write_lock
196
147
    def commit(self, message=None, revprops=None, *args,
197
148
               **kwargs):
202
153
        if not 'branch-nick' in revprops:
203
154
            revprops['branch-nick'] = self.branch.nick
204
155
        # args for wt.commit start at message from the Commit.commit method,
205
 
        args = (message, ) + args
 
156
        # but with branch a kwarg now, passing in args as is results in the
 
157
        #message being used for the branch
 
158
        args = (DEPRECATED_PARAMETER, message, ) + args
206
159
        committed_id = commit.Commit().commit(working_tree=self,
207
160
            revprops=revprops, *args, **kwargs)
208
161
        return committed_id
257
210
        """
258
211
        raise NotImplementedError(self.mkdir)
259
212
 
260
 
    def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
261
 
        """Set the parents ids of the working tree.
262
 
 
263
 
        :param revision_ids: A list of revision_ids.
264
 
        """
265
 
        raise NotImplementedError(self.set_parent_ids)
266
 
 
267
213
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
268
214
        """Set the parents of the working tree.
269
215
 
272
218
            parent tree - i.e. a ghost.
273
219
        """
274
220
        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)