~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

Nathaniel McCallums patch for urandom friendliness on aix.

Show diffs side-by-side

added added

removed removed

Lines of Context:
43
43
# repeatedly to calculate deltas.  We could perhaps have a weakref
44
44
# cache in memory to make this faster.
45
45
 
46
 
def find_branch(*ignored, **ignored_too):
47
 
    # XXX: leave this here for about one release, then remove it
48
 
    raise NotImplementedError('find_branch() is not supported anymore, '
49
 
                              'please use one of the new branch constructors')
 
46
# TODO: please move the revision-string syntax stuff out of the branch
 
47
# object; it's clutter
 
48
 
 
49
 
 
50
def find_branch(f, **args):
 
51
    if f and (f.startswith('http://') or f.startswith('https://')):
 
52
        from bzrlib.remotebranch import RemoteBranch
 
53
        return RemoteBranch(f, **args)
 
54
    else:
 
55
        return Branch(f, **args)
 
56
 
 
57
 
 
58
def find_cached_branch(f, cache_root, **args):
 
59
    from bzrlib.remotebranch import RemoteBranch
 
60
    br = find_branch(f, **args)
 
61
    def cacheify(br, store_name):
 
62
        from bzrlib.meta_store import CachedStore
 
63
        cache_path = os.path.join(cache_root, store_name)
 
64
        os.mkdir(cache_path)
 
65
        new_store = CachedStore(getattr(br, store_name), cache_path)
 
66
        setattr(br, store_name, new_store)
 
67
 
 
68
    if isinstance(br, RemoteBranch):
 
69
        cacheify(br, 'inventory_store')
 
70
        cacheify(br, 'text_store')
 
71
        cacheify(br, 'revision_store')
 
72
    return br
 
73
 
50
74
 
51
75
def _relpath(base, path):
52
76
    """Return path relative to base, or raise exception.
116
140
    """Branch holding a history of revisions.
117
141
 
118
142
    base
119
 
        Base directory/url of the branch.
120
 
    """
121
 
    base = None
122
 
 
123
 
    def __init__(self, *ignored, **ignored_too):
124
 
        raise NotImplementedError('The Branch class is abstract')
125
 
 
126
 
    @staticmethod
127
 
    def open(base):
128
 
        """Open an existing branch, rooted at 'base' (url)"""
129
 
        if base and (base.startswith('http://') or base.startswith('https://')):
130
 
            from bzrlib.remotebranch import RemoteBranch
131
 
            return RemoteBranch(base, find_root=False)
132
 
        else:
133
 
            return LocalBranch(base, find_root=False)
134
 
 
135
 
    @staticmethod
136
 
    def open_containing(url):
137
 
        """Open an existing branch, containing url (search upwards for the root)
138
 
        """
139
 
        if url and (url.startswith('http://') or url.startswith('https://')):
140
 
            from bzrlib.remotebranch import RemoteBranch
141
 
            return RemoteBranch(url)
142
 
        else:
143
 
            return LocalBranch(url)
144
 
 
145
 
    @staticmethod
146
 
    def initialize(base):
147
 
        """Create a new branch, rooted at 'base' (url)"""
148
 
        if base and (base.startswith('http://') or base.startswith('https://')):
149
 
            from bzrlib.remotebranch import RemoteBranch
150
 
            return RemoteBranch(base, init=True)
151
 
        else:
152
 
            return LocalBranch(base, init=True)
153
 
 
154
 
    def setup_caching(self, cache_root):
155
 
        """Subclasses that care about caching should override this, and set
156
 
        up cached stores located under cache_root.
157
 
        """
158
 
 
159
 
 
160
 
class LocalBranch(Branch):
161
 
    """A branch stored in the actual filesystem.
162
 
 
163
 
    Note that it's "local" in the context of the filesystem; it doesn't
164
 
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
165
 
    it's writable, and can be accessed via the normal filesystem API.
 
143
        Base directory of the branch.
166
144
 
167
145
    _lock_mode
168
146
        None, or 'r' or 'w'
174
152
    _lock
175
153
        Lock object from bzrlib.lock.
176
154
    """
177
 
    # We actually expect this class to be somewhat short-lived; part of its
178
 
    # purpose is to try to isolate what bits of the branch logic are tied to
179
 
    # filesystem access, so that in a later step, we can extricate them to
180
 
    # a separarte ("storage") class.
 
155
    base = None
181
156
    _lock_mode = None
182
157
    _lock_count = None
183
158
    _lock = None
 
159
    
 
160
    # Map some sort of prefix into a namespace
 
161
    # stuff like "revno:10", "revid:", etc.
 
162
    # This should match a prefix with a function which accepts
 
163
    REVISION_NAMESPACES = {}
184
164
 
185
165
    def __init__(self, base, init=False, find_root=True):
186
166
        """Create new branch object at a particular location.
187
167
 
188
 
        base -- Base directory for the branch. May be a file:// url.
 
168
        base -- Base directory for the branch.
189
169
        
190
170
        init -- If True, create new control files in a previously
191
171
             unversioned directory.  If False, the branch must already
204
184
        elif find_root:
205
185
            self.base = find_branch_root(base)
206
186
        else:
207
 
            if base.startswith("file://"):
208
 
                base = base[7:]
209
187
            self.base = os.path.realpath(base)
210
188
            if not isdir(self.controlfilename('.')):
211
189
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
231
209
            warn("branch %r was not explicitly unlocked" % self)
232
210
            self._lock.unlock()
233
211
 
 
212
 
234
213
    def lock_write(self):
235
214
        if self._lock_mode:
236
215
            if self._lock_mode != 'w':
483
462
        """Print `file` to stdout."""
484
463
        self.lock_read()
485
464
        try:
486
 
            tree = self.revision_tree(self.get_rev_id(revno))
 
465
            tree = self.revision_tree(self.lookup_revision(revno))
487
466
            # use inventory as it was in that revision
488
467
            file_id = tree.inventory.path2id(file)
489
468
            if not file_id:
596
575
        try:
597
576
            try:
598
577
                return self.revision_store[revision_id]
599
 
            except (IndexError, KeyError):
 
578
            except IndexError:
600
579
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
601
580
        finally:
602
581
            self.unlock()
817
796
        """Pull in all new revisions from other branch.
818
797
        """
819
798
        from bzrlib.fetch import greedy_fetch
820
 
        from bzrlib.revision import get_intervening_revisions
821
799
 
822
800
        pb = bzrlib.ui.ui_factory.progress_bar()
823
801
        pb.update('comparing histories')
824
 
        if stop_revision is None:
825
 
            other_revision = other.last_patch()
 
802
 
 
803
        revision_ids = self.missing_revisions(other, stop_revision)
 
804
 
 
805
        if len(revision_ids) > 0:
 
806
            count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
826
807
        else:
827
 
            other_revision = other.get_rev_id(stop_revision)
828
 
        count = greedy_fetch(self, other, other_revision, pb)[0]
829
 
        try:
830
 
            revision_ids = self.missing_revisions(other, stop_revision)
831
 
        except DivergedBranches, e:
832
 
            try:
833
 
                revision_ids = get_intervening_revisions(self.last_patch(), 
834
 
                                                         other_revision, self)
835
 
                assert self.last_patch() not in revision_ids
836
 
            except bzrlib.errors.NotAncestor:
837
 
                raise e
838
 
 
 
808
            count = 0
839
809
        self.append_revision(*revision_ids)
 
810
        ## note("Added %d revisions." % count)
840
811
        pb.clear()
841
812
 
842
813
    def install_revisions(self, other, revision_ids, pb):
843
814
        if hasattr(other.revision_store, "prefetch"):
844
815
            other.revision_store.prefetch(revision_ids)
845
816
        if hasattr(other.inventory_store, "prefetch"):
846
 
            inventory_ids = []
847
 
            for rev_id in revision_ids:
848
 
                try:
849
 
                    revision = other.get_revision(rev_id).inventory_id
850
 
                    inventory_ids.append(revision)
851
 
                except bzrlib.errors.NoSuchRevision:
852
 
                    pass
 
817
            inventory_ids = [other.get_revision(r).inventory_id
 
818
                             for r in revision_ids]
853
819
            other.inventory_store.prefetch(inventory_ids)
854
820
 
855
821
        if pb is None:
898
864
        from bzrlib.commit import commit
899
865
        commit(self, *args, **kw)
900
866
        
 
867
 
 
868
    def lookup_revision(self, revision):
 
869
        """Return the revision identifier for a given revision information."""
 
870
        revno, info = self._get_revision_info(revision)
 
871
        return info
 
872
 
 
873
 
901
874
    def revision_id_to_revno(self, revision_id):
902
875
        """Given a revision id, return its revno"""
903
876
        history = self.revision_history()
906
879
        except ValueError:
907
880
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
908
881
 
 
882
 
 
883
    def get_revision_info(self, revision):
 
884
        """Return (revno, revision id) for revision identifier.
 
885
 
 
886
        revision can be an integer, in which case it is assumed to be revno (though
 
887
            this will translate negative values into positive ones)
 
888
        revision can also be a string, in which case it is parsed for something like
 
889
            'date:' or 'revid:' etc.
 
890
        """
 
891
        revno, rev_id = self._get_revision_info(revision)
 
892
        if revno is None:
 
893
            raise bzrlib.errors.NoSuchRevision(self, revision)
 
894
        return revno, rev_id
 
895
 
909
896
    def get_rev_id(self, revno, history=None):
910
897
        """Find the revision id of the specified revno."""
911
898
        if revno == 0:
916
903
            raise bzrlib.errors.NoSuchRevision(self, revno)
917
904
        return history[revno - 1]
918
905
 
 
906
    def _get_revision_info(self, revision):
 
907
        """Return (revno, revision id) for revision specifier.
 
908
 
 
909
        revision can be an integer, in which case it is assumed to be revno
 
910
        (though this will translate negative values into positive ones)
 
911
        revision can also be a string, in which case it is parsed for something
 
912
        like 'date:' or 'revid:' etc.
 
913
 
 
914
        A revid is always returned.  If it is None, the specifier referred to
 
915
        the null revision.  If the revid does not occur in the revision
 
916
        history, revno will be None.
 
917
        """
 
918
        
 
919
        if revision is None:
 
920
            return 0, None
 
921
        revno = None
 
922
        try:# Convert to int if possible
 
923
            revision = int(revision)
 
924
        except ValueError:
 
925
            pass
 
926
        revs = self.revision_history()
 
927
        if isinstance(revision, int):
 
928
            if revision < 0:
 
929
                revno = len(revs) + revision + 1
 
930
            else:
 
931
                revno = revision
 
932
            rev_id = self.get_rev_id(revno, revs)
 
933
        elif isinstance(revision, basestring):
 
934
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
 
935
                if revision.startswith(prefix):
 
936
                    result = func(self, revs, revision)
 
937
                    if len(result) > 1:
 
938
                        revno, rev_id = result
 
939
                    else:
 
940
                        revno = result[0]
 
941
                        rev_id = self.get_rev_id(revno, revs)
 
942
                    break
 
943
            else:
 
944
                raise BzrError('No namespace registered for string: %r' %
 
945
                               revision)
 
946
        else:
 
947
            raise TypeError('Unhandled revision type %s' % revision)
 
948
 
 
949
        if revno is None:
 
950
            if rev_id is None:
 
951
                raise bzrlib.errors.NoSuchRevision(self, revision)
 
952
        return revno, rev_id
 
953
 
 
954
    def _namespace_revno(self, revs, revision):
 
955
        """Lookup a revision by revision number"""
 
956
        assert revision.startswith('revno:')
 
957
        try:
 
958
            return (int(revision[6:]),)
 
959
        except ValueError:
 
960
            return None
 
961
    REVISION_NAMESPACES['revno:'] = _namespace_revno
 
962
 
 
963
    def _namespace_revid(self, revs, revision):
 
964
        assert revision.startswith('revid:')
 
965
        rev_id = revision[len('revid:'):]
 
966
        try:
 
967
            return revs.index(rev_id) + 1, rev_id
 
968
        except ValueError:
 
969
            return None, rev_id
 
970
    REVISION_NAMESPACES['revid:'] = _namespace_revid
 
971
 
 
972
    def _namespace_last(self, revs, revision):
 
973
        assert revision.startswith('last:')
 
974
        try:
 
975
            offset = int(revision[5:])
 
976
        except ValueError:
 
977
            return (None,)
 
978
        else:
 
979
            if offset <= 0:
 
980
                raise BzrError('You must supply a positive value for --revision last:XXX')
 
981
            return (len(revs) - offset + 1,)
 
982
    REVISION_NAMESPACES['last:'] = _namespace_last
 
983
 
 
984
    def _namespace_tag(self, revs, revision):
 
985
        assert revision.startswith('tag:')
 
986
        raise BzrError('tag: namespace registered, but not implemented.')
 
987
    REVISION_NAMESPACES['tag:'] = _namespace_tag
 
988
 
 
989
    def _namespace_date(self, revs, revision):
 
990
        assert revision.startswith('date:')
 
991
        import datetime
 
992
        # Spec for date revisions:
 
993
        #   date:value
 
994
        #   value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
995
        #   it can also start with a '+/-/='. '+' says match the first
 
996
        #   entry after the given date. '-' is match the first entry before the date
 
997
        #   '=' is match the first entry after, but still on the given date.
 
998
        #
 
999
        #   +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
 
1000
        #   -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
 
1001
        #   =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
 
1002
        #       May 13th, 2005 at 0:00
 
1003
        #
 
1004
        #   So the proper way of saying 'give me all entries for today' is:
 
1005
        #       -r {date:+today}:{date:-tomorrow}
 
1006
        #   The default is '=' when not supplied
 
1007
        val = revision[5:]
 
1008
        match_style = '='
 
1009
        if val[:1] in ('+', '-', '='):
 
1010
            match_style = val[:1]
 
1011
            val = val[1:]
 
1012
 
 
1013
        today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
 
1014
        if val.lower() == 'yesterday':
 
1015
            dt = today - datetime.timedelta(days=1)
 
1016
        elif val.lower() == 'today':
 
1017
            dt = today
 
1018
        elif val.lower() == 'tomorrow':
 
1019
            dt = today + datetime.timedelta(days=1)
 
1020
        else:
 
1021
            import re
 
1022
            # This should be done outside the function to avoid recompiling it.
 
1023
            _date_re = re.compile(
 
1024
                    r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
1025
                    r'(,|T)?\s*'
 
1026
                    r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
1027
                )
 
1028
            m = _date_re.match(val)
 
1029
            if not m or (not m.group('date') and not m.group('time')):
 
1030
                raise BzrError('Invalid revision date %r' % revision)
 
1031
 
 
1032
            if m.group('date'):
 
1033
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
 
1034
            else:
 
1035
                year, month, day = today.year, today.month, today.day
 
1036
            if m.group('time'):
 
1037
                hour = int(m.group('hour'))
 
1038
                minute = int(m.group('minute'))
 
1039
                if m.group('second'):
 
1040
                    second = int(m.group('second'))
 
1041
                else:
 
1042
                    second = 0
 
1043
            else:
 
1044
                hour, minute, second = 0,0,0
 
1045
 
 
1046
            dt = datetime.datetime(year=year, month=month, day=day,
 
1047
                    hour=hour, minute=minute, second=second)
 
1048
        first = dt
 
1049
        last = None
 
1050
        reversed = False
 
1051
        if match_style == '-':
 
1052
            reversed = True
 
1053
        elif match_style == '=':
 
1054
            last = dt + datetime.timedelta(days=1)
 
1055
 
 
1056
        if reversed:
 
1057
            for i in range(len(revs)-1, -1, -1):
 
1058
                r = self.get_revision(revs[i])
 
1059
                # TODO: Handle timezone.
 
1060
                dt = datetime.datetime.fromtimestamp(r.timestamp)
 
1061
                if first >= dt and (last is None or dt >= last):
 
1062
                    return (i+1,)
 
1063
        else:
 
1064
            for i in range(len(revs)):
 
1065
                r = self.get_revision(revs[i])
 
1066
                # TODO: Handle timezone.
 
1067
                dt = datetime.datetime.fromtimestamp(r.timestamp)
 
1068
                if first <= dt and (last is None or dt <= last):
 
1069
                    return (i+1,)
 
1070
    REVISION_NAMESPACES['date:'] = _namespace_date
919
1071
 
920
1072
    def revision_tree(self, revision_id):
921
1073
        """Return Tree for a revision on this branch.
1204
1356
            raise InvalidRevisionNumber(revno)
1205
1357
        
1206
1358
        
1207
 
        
1208
 
 
1209
 
 
1210
 
class ScratchBranch(LocalBranch):
 
1359
 
 
1360
 
 
1361
class ScratchBranch(Branch):
1211
1362
    """Special test class: a branch that cleans up after itself.
1212
1363
 
1213
1364
    >>> b = ScratchBranch()
1230
1381
        if base is None:
1231
1382
            base = mkdtemp()
1232
1383
            init = True
1233
 
        LocalBranch.__init__(self, base, init=init)
 
1384
        Branch.__init__(self, base, init=init)
1234
1385
        for d in dirs:
1235
1386
            os.mkdir(self.abspath(d))
1236
1387
            
1342
1493
        The name of a local directory that exists but is empty.
1343
1494
    """
1344
1495
    from bzrlib.merge import merge
1345
 
    from bzrlib.revisionspec import RevisionSpec
1346
1496
 
1347
1497
    assert isinstance(branch_from, Branch)
1348
1498
    assert isinstance(to_location, basestring)
1349
1499
    
1350
 
    br_to = Branch.initialize(to_location)
 
1500
    br_to = Branch(to_location, init=True)
1351
1501
    br_to.set_root_id(branch_from.get_root_id())
1352
1502
    if revision is None:
1353
1503
        revno = branch_from.revno()
1354
1504
    else:
1355
 
        revno, rev_id = RevisionSpec(revision).in_history(branch_from)
 
1505
        revno, rev_id = branch_from.get_revision_info(revision)
1356
1506
    br_to.update_revisions(branch_from, stop_revision=revno)
1357
1507
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1358
1508
          check_clean=False, ignore_zero=True)
 
1509
    
 
1510
    from_location = branch_from.base
1359
1511
    br_to.set_parent(branch_from.base)
1360
 
    return br_to
 
1512