~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: John Arbash Meinel
  • Date: 2011-05-11 11:35:28 UTC
  • mto: This revision was merged to the branch mainline in revision 5851.
  • Revision ID: john@arbash-meinel.com-20110511113528-qepibuwxicjrbb2h
Break compatibility with python <2.6.

This includes auditing the code for places where we were doing
explicit 'sys.version' checks and removing them as appropriate.

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
from bzrlib import (
25
25
        bzrdir,
26
26
        cache_utf8,
27
 
        cleanup,
28
27
        config as _mod_config,
29
28
        debug,
30
29
        errors,
36
35
        repository,
37
36
        revision as _mod_revision,
38
37
        rio,
 
38
        symbol_versioning,
39
39
        transport,
 
40
        tsort,
40
41
        ui,
41
42
        urlutils,
42
43
        )
66
67
from bzrlib.trace import mutter, mutter_callsite, note, is_quiet
67
68
 
68
69
 
 
70
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
 
71
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
 
72
BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
 
73
 
 
74
 
69
75
class Branch(controldir.ControlComponent):
70
76
    """Branch holding a history of revisions.
71
77
 
450
456
            after. If None, the rest of history is included.
451
457
        :param stop_rule: if stop_revision_id is not None, the precise rule
452
458
            to use for termination:
453
 
 
454
459
            * 'exclude' - leave the stop revision out of the result (default)
455
460
            * 'include' - the stop revision is the last item in the result
456
461
            * 'with-merges' - include the stop revision and all of its
458
463
            * 'with-merges-without-common-ancestry' - filter out revisions 
459
464
              that are in both ancestries
460
465
        :param direction: either 'reverse' or 'forward':
461
 
 
462
466
            * reverse means return the start_revision_id first, i.e.
463
467
              start at the most recent revision and go backwards in history
464
468
            * forward returns tuples in the opposite order to reverse.
508
512
        rev_iter = iter(merge_sorted_revisions)
509
513
        if start_revision_id is not None:
510
514
            for node in rev_iter:
511
 
                rev_id = node.key
 
515
                rev_id = node.key[-1]
512
516
                if rev_id != start_revision_id:
513
517
                    continue
514
518
                else:
520
524
        if stop_revision_id is None:
521
525
            # Yield everything
522
526
            for node in rev_iter:
523
 
                rev_id = node.key
 
527
                rev_id = node.key[-1]
524
528
                yield (rev_id, node.merge_depth, node.revno,
525
529
                       node.end_of_merge)
526
530
        elif stop_rule == 'exclude':
527
531
            for node in rev_iter:
528
 
                rev_id = node.key
 
532
                rev_id = node.key[-1]
529
533
                if rev_id == stop_revision_id:
530
534
                    return
531
535
                yield (rev_id, node.merge_depth, node.revno,
532
536
                       node.end_of_merge)
533
537
        elif stop_rule == 'include':
534
538
            for node in rev_iter:
535
 
                rev_id = node.key
 
539
                rev_id = node.key[-1]
536
540
                yield (rev_id, node.merge_depth, node.revno,
537
541
                       node.end_of_merge)
538
542
                if rev_id == stop_revision_id:
544
548
            ancestors = graph.find_unique_ancestors(start_revision_id,
545
549
                                                    [stop_revision_id])
546
550
            for node in rev_iter:
547
 
                rev_id = node.key
 
551
                rev_id = node.key[-1]
548
552
                if rev_id not in ancestors:
549
553
                    continue
550
554
                yield (rev_id, node.merge_depth, node.revno,
560
564
            reached_stop_revision_id = False
561
565
            revision_id_whitelist = []
562
566
            for node in rev_iter:
563
 
                rev_id = node.key
 
567
                rev_id = node.key[-1]
564
568
                if rev_id == left_parent:
565
569
                    # reached the left parent after the stop_revision
566
570
                    return
665
669
        raise errors.UnsupportedOperation(self.get_reference_info, self)
666
670
 
667
671
    @needs_write_lock
668
 
    def fetch(self, from_branch, last_revision=None, limit=None):
 
672
    def fetch(self, from_branch, last_revision=None):
669
673
        """Copy revisions from from_branch into this branch.
670
674
 
671
675
        :param from_branch: Where to copy from.
672
676
        :param last_revision: What revision to stop at (None for at the end
673
677
                              of the branch.
674
 
        :param limit: Optional rough limit of revisions to fetch
675
678
        :return: None
676
679
        """
677
 
        return InterBranch.get(from_branch, self).fetch(last_revision, limit=limit)
 
680
        return InterBranch.get(from_branch, self).fetch(last_revision)
678
681
 
679
682
    def get_bound_location(self):
680
683
        """Return the URL of the branch we are bound to.
773
776
        configured to check constraints on history, in which case this may not
774
777
        be permitted.
775
778
        """
776
 
        raise NotImplementedError(self.set_last_revision_info)
 
779
        raise NotImplementedError(self.last_revision_info)
777
780
 
778
781
    @needs_write_lock
779
782
    def generate_revision_history(self, revision_id, last_rev=None,
780
783
                                  other_branch=None):
781
784
        """See Branch.generate_revision_history"""
782
 
        graph = self.repository.get_graph()
783
 
        (last_revno, last_revid) = self.last_revision_info()
784
 
        known_revision_ids = [
785
 
            (last_revid, last_revno),
786
 
            (_mod_revision.NULL_REVISION, 0),
787
 
            ]
788
 
        if last_rev is not None:
789
 
            if not graph.is_ancestor(last_rev, revision_id):
790
 
                # our previous tip is not merged into stop_revision
791
 
                raise errors.DivergedBranches(self, other_branch)
792
 
        revno = graph.find_distance_to_null(revision_id, known_revision_ids)
 
785
        # FIXME: This shouldn't have to fetch the entire history
 
786
        history = self._lefthand_history(revision_id, last_rev, other_branch)
 
787
        revno = len(history)
793
788
        self.set_last_revision_info(revno, revision_id)
 
789
        self._cache_revision_history(history)
794
790
 
795
791
    @needs_write_lock
796
792
    def set_parent(self, url):
928
924
 
929
925
        :seealso: Branch._get_tags_bytes.
930
926
        """
931
 
        op = cleanup.OperationWithCleanups(self._set_tags_bytes_locked)
932
 
        op.add_cleanup(self.lock_write().unlock)
933
 
        return op.run_simple(bytes)
 
927
        return _run_with_write_locked_target(self, self._set_tags_bytes_locked,
 
928
                bytes)
934
929
 
935
930
    def _set_tags_bytes_locked(self, bytes):
936
931
        self._tags_bytes = bytes
1103
1098
            stop_revision=stop_revision,
1104
1099
            possible_transports=possible_transports, *args, **kwargs)
1105
1100
 
1106
 
    def push(self, target, overwrite=False, stop_revision=None, lossy=False,
1107
 
            *args, **kwargs):
 
1101
    def push(self, target, overwrite=False, stop_revision=None, *args,
 
1102
        **kwargs):
1108
1103
        """Mirror this branch into target.
1109
1104
 
1110
1105
        This branch is considered to be 'local', having low latency.
1111
1106
        """
1112
1107
        return InterBranch.get(self, target).push(overwrite, stop_revision,
1113
 
            lossy, *args, **kwargs)
 
1108
            *args, **kwargs)
 
1109
 
 
1110
    def lossy_push(self, target, stop_revision=None):
 
1111
        """Push deltas into another branch.
 
1112
 
 
1113
        :note: This does not, like push, retain the revision ids from 
 
1114
            the source branch and will, rather than adding bzr-specific 
 
1115
            metadata, push only those semantics of the revision that can be 
 
1116
            natively represented by this branch' VCS.
 
1117
 
 
1118
        :param target: Target branch
 
1119
        :param stop_revision: Revision to push, defaults to last revision.
 
1120
        :return: BranchPushResult with an extra member revidmap: 
 
1121
            A dictionary mapping revision ids from the target branch 
 
1122
            to new revision ids in the target branch, for each 
 
1123
            revision that was pushed.
 
1124
        """
 
1125
        inter = InterBranch.get(self, target)
 
1126
        lossy_push = getattr(inter, "lossy_push", None)
 
1127
        if lossy_push is None:
 
1128
            raise errors.LossyPushToSameVCS(self, target)
 
1129
        return lossy_push(stop_revision)
1114
1130
 
1115
1131
    def basis_tree(self):
1116
1132
        """Return `Tree` object for last revision."""
1289
1305
            if repository_policy is not None:
1290
1306
                repository_policy.configure_branch(result)
1291
1307
            self.copy_content_into(result, revision_id=revision_id)
1292
 
            master_url = self.get_bound_location()
1293
 
            if master_url is None:
 
1308
            master_branch = self.get_master_branch()
 
1309
            if master_branch is None:
1294
1310
                result.set_parent(self.bzrdir.root_transport.base)
1295
1311
            else:
1296
 
                result.set_parent(master_url)
 
1312
                result.set_parent(master_branch.bzrdir.root_transport.base)
1297
1313
        finally:
1298
1314
            result.unlock()
1299
1315
        return result
1418
1434
        :param to_location: The url to produce the checkout at
1419
1435
        :param revision_id: The revision to check out
1420
1436
        :param lightweight: If True, produce a lightweight checkout, otherwise,
1421
 
            produce a bound branch (heavyweight checkout)
 
1437
        produce a bound branch (heavyweight checkout)
1422
1438
        :param accelerator_tree: A tree which can be used for retrieving file
1423
1439
            contents more quickly than the revision tree, i.e. a workingtree.
1424
1440
            The revision tree will be used for cases where accelerator_tree's
1470
1486
 
1471
1487
    def reference_parent(self, file_id, path, possible_transports=None):
1472
1488
        """Return the parent branch for a tree-reference file_id
1473
 
 
1474
1489
        :param file_id: The file_id of the tree reference
1475
1490
        :param path: The path of the file_id in the tree
1476
1491
        :return: A branch associated with the file_id
1541
1556
        # For bzr native formats must_fetch is just the tip, and if_present_fetch
1542
1557
        # are the tags.
1543
1558
        must_fetch = set([self.last_revision()])
1544
 
        if_present_fetch = set()
1545
 
        c = self.get_config()
1546
 
        include_tags = c.get_user_option_as_bool('branch.fetch_tags',
1547
 
                                                 default=False)
1548
 
        if include_tags:
1549
 
            try:
1550
 
                if_present_fetch = set(self.tags.get_reverse_tag_dict())
1551
 
            except errors.TagsNotSupported:
1552
 
                pass
 
1559
        try:
 
1560
            if_present_fetch = set(self.tags.get_reverse_tag_dict())
 
1561
        except errors.TagsNotSupported:
 
1562
            if_present_fetch = set()
1553
1563
        must_fetch.discard(_mod_revision.NULL_REVISION)
1554
1564
        if_present_fetch.discard(_mod_revision.NULL_REVISION)
1555
1565
        return must_fetch, if_present_fetch
1871
1881
 
1872
1882
 
1873
1883
class ChangeBranchTipParams(object):
1874
 
    """Object holding parameters passed to `*_change_branch_tip` hooks.
 
1884
    """Object holding parameters passed to *_change_branch_tip hooks.
1875
1885
 
1876
1886
    There are 5 fields that hooks may wish to access:
1877
1887
 
1909
1919
 
1910
1920
 
1911
1921
class BranchInitHookParams(object):
1912
 
    """Object holding parameters passed to `*_branch_init` hooks.
 
1922
    """Object holding parameters passed to *_branch_init hooks.
1913
1923
 
1914
1924
    There are 4 fields that hooks may wish to access:
1915
1925
 
1949
1959
 
1950
1960
 
1951
1961
class SwitchHookParams(object):
1952
 
    """Object holding parameters passed to `*_switch` hooks.
 
1962
    """Object holding parameters passed to *_switch hooks.
1953
1963
 
1954
1964
    There are 4 fields that hooks may wish to access:
1955
1965
 
2798
2808
        self._reference_info = None
2799
2809
 
2800
2810
    def _check_history_violation(self, revision_id):
2801
 
        current_revid = self.last_revision()
2802
 
        last_revision = _mod_revision.ensure_null(current_revid)
 
2811
        last_revision = _mod_revision.ensure_null(self.last_revision())
2803
2812
        if _mod_revision.is_null(last_revision):
2804
2813
            return
2805
 
        graph = self.repository.get_graph()
2806
 
        for lh_ancestor in graph.iter_lefthand_ancestry(revision_id):
2807
 
            if lh_ancestor == current_revid:
2808
 
                return
2809
 
        raise errors.AppendRevisionsOnlyViolation(self.user_url)
 
2814
        if last_revision not in self._lefthand_history(revision_id):
 
2815
            raise errors.AppendRevisionsOnlyViolation(self.user_url)
2810
2816
 
2811
2817
    def _gen_revision_history(self):
2812
2818
        """Generate the revision history from last revision
2943
2949
        # you can always ask for the URL; but you might not be able to use it
2944
2950
        # if the repo can't support stacking.
2945
2951
        ## self._check_stackable_repo()
2946
 
        # stacked_on_location is only ever defined in branch.conf, so don't
2947
 
        # waste effort reading the whole stack of config files.
2948
 
        config = self.get_config()._get_branch_data_config()
2949
 
        stacked_url = self._get_config_location('stacked_on_location',
2950
 
            config=config)
 
2952
        stacked_url = self._get_config_location('stacked_on_location')
2951
2953
        if stacked_url is None:
2952
2954
            raise errors.NotStacked(self)
2953
2955
        return stacked_url
3166
3168
 
3167
3169
 
3168
3170
class Converter7to8(object):
3169
 
    """Perform an in-place upgrade of format 7 to format 8"""
 
3171
    """Perform an in-place upgrade of format 6 to format 7"""
3170
3172
 
3171
3173
    def convert(self, branch):
3172
3174
        format = BzrBranchFormat8()
3175
3177
        branch._transport.put_bytes('format', format.get_format_string())
3176
3178
 
3177
3179
 
 
3180
def _run_with_write_locked_target(target, callable, *args, **kwargs):
 
3181
    """Run ``callable(*args, **kwargs)``, write-locking target for the
 
3182
    duration.
 
3183
 
 
3184
    _run_with_write_locked_target will attempt to release the lock it acquires.
 
3185
 
 
3186
    If an exception is raised by callable, then that exception *will* be
 
3187
    propagated, even if the unlock attempt raises its own error.  Thus
 
3188
    _run_with_write_locked_target should be preferred to simply doing::
 
3189
 
 
3190
        target.lock_write()
 
3191
        try:
 
3192
            return callable(*args, **kwargs)
 
3193
        finally:
 
3194
            target.unlock()
 
3195
 
 
3196
    """
 
3197
    # This is very similar to bzrlib.decorators.needs_write_lock.  Perhaps they
 
3198
    # should share code?
 
3199
    target.lock_write()
 
3200
    try:
 
3201
        result = callable(*args, **kwargs)
 
3202
    except:
 
3203
        exc_info = sys.exc_info()
 
3204
        try:
 
3205
            target.unlock()
 
3206
        finally:
 
3207
            raise exc_info[0], exc_info[1], exc_info[2]
 
3208
    else:
 
3209
        target.unlock()
 
3210
        return result
 
3211
 
 
3212
 
3178
3213
class InterBranch(InterObject):
3179
3214
    """This class represents operations taking place between two branches.
3180
3215
 
3208
3243
        raise NotImplementedError(self.pull)
3209
3244
 
3210
3245
    @needs_write_lock
3211
 
    def push(self, overwrite=False, stop_revision=None, lossy=False,
 
3246
    def push(self, overwrite=False, stop_revision=None,
3212
3247
             _override_hook_source_branch=None):
3213
3248
        """Mirror the source branch into the target branch.
3214
3249
 
3226
3261
        raise NotImplementedError(self.copy_content_into)
3227
3262
 
3228
3263
    @needs_write_lock
3229
 
    def fetch(self, stop_revision=None, limit=None):
 
3264
    def fetch(self, stop_revision=None):
3230
3265
        """Fetch revisions.
3231
3266
 
3232
3267
        :param stop_revision: Last revision to fetch
3233
 
        :param limit: Optional rough limit of revisions to fetch
3234
3268
        """
3235
3269
        raise NotImplementedError(self.fetch)
3236
3270
 
3274
3308
            self.source.tags.merge_to(self.target.tags)
3275
3309
 
3276
3310
    @needs_write_lock
3277
 
    def fetch(self, stop_revision=None, limit=None):
 
3311
    def fetch(self, stop_revision=None):
3278
3312
        if self.target.base == self.source.base:
3279
3313
            return (0, [])
3280
3314
        self.source.lock_read()
3285
3319
            fetch_spec_factory.source_repo = self.source.repository
3286
3320
            fetch_spec_factory.target_repo = self.target.repository
3287
3321
            fetch_spec_factory.target_repo_kind = fetch.TargetRepoKinds.PREEXISTING
3288
 
            fetch_spec_factory.limit = limit
3289
3322
            fetch_spec = fetch_spec_factory.make_fetch_spec()
3290
3323
            return self.target.repository.fetch(self.source.repository,
3291
3324
                fetch_spec=fetch_spec)
3345
3378
        if local and not bound_location:
3346
3379
            raise errors.LocalRequiresBoundBranch()
3347
3380
        master_branch = None
3348
 
        source_is_master = False
3349
 
        if bound_location:
3350
 
            # bound_location comes from a config file, some care has to be
3351
 
            # taken to relate it to source.user_url
3352
 
            normalized = urlutils.normalize_url(bound_location)
3353
 
            try:
3354
 
                relpath = self.source.user_transport.relpath(normalized)
3355
 
                source_is_master = (relpath == '')
3356
 
            except (errors.PathNotChild, errors.InvalidURL):
3357
 
                source_is_master = False
 
3381
        source_is_master = (self.source.user_url == bound_location)
3358
3382
        if not local and bound_location and not source_is_master:
3359
3383
            # not pulling from master, so we need to update master.
3360
3384
            master_branch = self.target.get_master_branch(possible_transports)
3373
3397
            if master_branch:
3374
3398
                master_branch.unlock()
3375
3399
 
3376
 
    def push(self, overwrite=False, stop_revision=None, lossy=False,
 
3400
    def push(self, overwrite=False, stop_revision=None,
3377
3401
             _override_hook_source_branch=None):
3378
3402
        """See InterBranch.push.
3379
3403
 
3380
3404
        This is the basic concrete implementation of push()
3381
3405
 
3382
 
        :param _override_hook_source_branch: If specified, run the hooks
3383
 
            passing this Branch as the source, rather than self.  This is for
3384
 
            use of RemoteBranch, where push is delegated to the underlying
3385
 
            vfs-based Branch.
 
3406
        :param _override_hook_source_branch: If specified, run
 
3407
        the hooks passing this Branch as the source, rather than self.
 
3408
        This is for use of RemoteBranch, where push is delegated to the
 
3409
        underlying vfs-based Branch.
3386
3410
        """
3387
 
        if lossy:
3388
 
            raise errors.LossyPushToSameVCS(self.source, self.target)
3389
3411
        # TODO: Public option to disable running hooks - should be trivial but
3390
3412
        # needs tests.
3391
 
 
3392
 
        op = cleanup.OperationWithCleanups(self._push_with_bound_branches)
3393
 
        op.add_cleanup(self.source.lock_read().unlock)
3394
 
        op.add_cleanup(self.target.lock_write().unlock)
3395
 
        return op.run(overwrite, stop_revision,
3396
 
            _override_hook_source_branch=_override_hook_source_branch)
 
3413
        self.source.lock_read()
 
3414
        try:
 
3415
            return _run_with_write_locked_target(
 
3416
                self.target, self._push_with_bound_branches, overwrite,
 
3417
                stop_revision,
 
3418
                _override_hook_source_branch=_override_hook_source_branch)
 
3419
        finally:
 
3420
            self.source.unlock()
3397
3421
 
3398
3422
    def _basic_push(self, overwrite, stop_revision):
3399
3423
        """Basic implementation of push without bound branches or hooks.
3417
3441
        result.new_revno, result.new_revid = self.target.last_revision_info()
3418
3442
        return result
3419
3443
 
3420
 
    def _push_with_bound_branches(self, operation, overwrite, stop_revision,
 
3444
    def _push_with_bound_branches(self, overwrite, stop_revision,
3421
3445
            _override_hook_source_branch=None):
3422
3446
        """Push from source into target, and into target's master if any.
3423
3447
        """
3435
3459
            # be bound to itself? -- mbp 20070507
3436
3460
            master_branch = self.target.get_master_branch()
3437
3461
            master_branch.lock_write()
3438
 
            operation.add_cleanup(master_branch.unlock)
3439
 
            # push into the master from the source branch.
3440
 
            master_inter = InterBranch.get(self.source, master_branch)
3441
 
            master_inter._basic_push(overwrite, stop_revision)
3442
 
            # and push into the target branch from the source. Note that
3443
 
            # we push from the source branch again, because it's considered
3444
 
            # the highest bandwidth repository.
3445
 
            result = self._basic_push(overwrite, stop_revision)
3446
 
            result.master_branch = master_branch
3447
 
            result.local_branch = self.target
 
3462
            try:
 
3463
                # push into the master from the source branch.
 
3464
                master_inter = InterBranch.get(self.source, master_branch)
 
3465
                master_inter._basic_push(overwrite, stop_revision)
 
3466
                # and push into the target branch from the source. Note that
 
3467
                # we push from the source branch again, because it's considered
 
3468
                # the highest bandwidth repository.
 
3469
                result = self._basic_push(overwrite, stop_revision)
 
3470
                result.master_branch = master_branch
 
3471
                result.local_branch = self.target
 
3472
                _run_hooks()
 
3473
                return result
 
3474
            finally:
 
3475
                master_branch.unlock()
3448
3476
        else:
3449
 
            master_branch = None
3450
3477
            # no master branch
3451
3478
            result = self._basic_push(overwrite, stop_revision)
3452
3479
            # TODO: Why set master_branch and local_branch if there's no
3454
3481
            # 20070504
3455
3482
            result.master_branch = self.target
3456
3483
            result.local_branch = None
3457
 
        _run_hooks()
3458
 
        return result
 
3484
            _run_hooks()
 
3485
            return result
3459
3486
 
3460
3487
    def _pull(self, overwrite=False, stop_revision=None,
3461
3488
             possible_transports=None, _hook_master=None, run_hooks=True,