~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Lalo Martins
  • Date: 2005-09-09 10:58:51 UTC
  • mto: (1185.1.22)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: lalo@exoweb.net-20050909105851-25aa36ea27f4ce7b
creating the new branch constructors

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
 
# 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)
 
46
 
 
47
def find_branch(base, init=False, find_root=True):
 
48
    if init:
 
49
        return Branch.initialize(base)
 
50
    if find_root:
 
51
        return Branch.open_containing(base)
 
52
    return Branch.open(base)
56
53
 
57
54
 
58
55
def find_cached_branch(f, cache_root, **args):
140
137
    """Branch holding a history of revisions.
141
138
 
142
139
    base
143
 
        Base directory of the branch.
 
140
        Base directory/url of the branch.
 
141
    """
 
142
    base = None
 
143
 
 
144
    def __new__(cls, *a, **kw):
 
145
        """this is temporary, till we get rid of all code that does
 
146
        b = Branch()
 
147
        """
 
148
        # XXX: AAARGH!  MY EYES!  UUUUGLY!!!
 
149
        if cls == Branch:
 
150
            cls = LocalBranch
 
151
        b = object.__new__(cls)
 
152
        return b
 
153
 
 
154
    @staticmethod
 
155
    def open(base):
 
156
        """Open an existing branch, rooted at 'base' (url)"""
 
157
        if base and (base.startswith('http://') or base.startswith('https://')):
 
158
            from bzrlib.remotebranch import RemoteBranch
 
159
            return RemoteBranch(base, find_root=False)
 
160
        else:
 
161
            return LocalBranch(base, find_root=False)
 
162
 
 
163
    @staticmethod
 
164
    def open_containing(url):
 
165
        """Open an existing branch, containing url (search upwards for the root)
 
166
        """
 
167
        if url and (url.startswith('http://') or url.startswith('https://')):
 
168
            from bzrlib.remotebranch import RemoteBranch
 
169
            return RemoteBranch(url)
 
170
        else:
 
171
            return LocalBranch(url)
 
172
 
 
173
    @staticmethod
 
174
    def initialize(base):
 
175
        """Create a new branch, rooted at 'base' (url)"""
 
176
        if base and (base.startswith('http://') or base.startswith('https://')):
 
177
            from bzrlib.remotebranch import RemoteBranch
 
178
            return RemoteBranch(base, init=True)
 
179
        else:
 
180
            return LocalBranch(base, init=True)
 
181
 
 
182
 
 
183
class LocalBranch(Branch):
 
184
    """A branch stored in the actual filesystem.
 
185
 
 
186
    Note that it's "local" in the context of the filesystem; it doesn't
 
187
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
 
188
    it's writable, and can be accessed via the normal filesystem API.
144
189
 
145
190
    _lock_mode
146
191
        None, or 'r' or 'w'
152
197
    _lock
153
198
        Lock object from bzrlib.lock.
154
199
    """
155
 
    base = None
 
200
    # We actually expect this class to be somewhat short-lived; part of its
 
201
    # purpose is to try to isolate what bits of the branch logic are tied to
 
202
    # filesystem access, so that in a later step, we can extricate them to
 
203
    # a separarte ("storage") class.
156
204
    _lock_mode = None
157
205
    _lock_count = None
158
206
    _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 = {}
164
207
 
165
208
    def __init__(self, base, init=False, find_root=True):
166
209
        """Create new branch object at a particular location.
167
210
 
168
 
        base -- Base directory for the branch. May be a file:// url.
 
211
        base -- Base directory for the branch.
169
212
        
170
213
        init -- If True, create new control files in a previously
171
214
             unversioned directory.  If False, the branch must already
184
227
        elif find_root:
185
228
            self.base = find_branch_root(base)
186
229
        else:
187
 
            if base.startswith("file://"):
188
 
                base = base[7:]
189
230
            self.base = os.path.realpath(base)
190
231
            if not isdir(self.controlfilename('.')):
191
232
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
211
252
            warn("branch %r was not explicitly unlocked" % self)
212
253
            self._lock.unlock()
213
254
 
 
255
 
214
256
    def lock_write(self):
215
257
        if self._lock_mode:
216
258
            if self._lock_mode != 'w':
463
505
        """Print `file` to stdout."""
464
506
        self.lock_read()
465
507
        try:
466
 
            tree = self.revision_tree(self.lookup_revision(revno))
 
508
            tree = self.revision_tree(self.get_rev_id(revno))
467
509
            # use inventory as it was in that revision
468
510
            file_id = tree.inventory.path2id(file)
469
511
            if not file_id:
576
618
        try:
577
619
            try:
578
620
                return self.revision_store[revision_id]
579
 
            except (IndexError, KeyError):
 
621
            except IndexError:
580
622
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
581
623
        finally:
582
624
            self.unlock()
797
839
        """Pull in all new revisions from other branch.
798
840
        """
799
841
        from bzrlib.fetch import greedy_fetch
800
 
        from bzrlib.revision import get_intervening_revisions
801
842
 
802
843
        pb = bzrlib.ui.ui_factory.progress_bar()
803
844
        pb.update('comparing histories')
804
 
        if stop_revision is None:
805
 
            other_revision = other.last_patch()
 
845
 
 
846
        revision_ids = self.missing_revisions(other, stop_revision)
 
847
 
 
848
        if len(revision_ids) > 0:
 
849
            count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
806
850
        else:
807
 
            other_revision = other.lookup_revision(stop_revision)
808
 
        count = greedy_fetch(self, other, other_revision, pb)[0]
809
 
        try:
810
 
            revision_ids = self.missing_revisions(other, stop_revision)
811
 
        except DivergedBranches, e:
812
 
            try:
813
 
                revision_ids = get_intervening_revisions(self.last_patch(), 
814
 
                                                         other_revision, self)
815
 
                assert self.last_patch() not in revision_ids
816
 
            except bzrlib.errors.NotAncestor:
817
 
                raise e
818
 
 
 
851
            count = 0
819
852
        self.append_revision(*revision_ids)
 
853
        ## note("Added %d revisions." % count)
820
854
        pb.clear()
821
855
 
822
856
    def install_revisions(self, other, revision_ids, pb):
823
857
        if hasattr(other.revision_store, "prefetch"):
824
858
            other.revision_store.prefetch(revision_ids)
825
859
        if hasattr(other.inventory_store, "prefetch"):
826
 
            inventory_ids = []
827
 
            for rev_id in revision_ids:
828
 
                try:
829
 
                    revision = other.get_revision(rev_id).inventory_id
830
 
                    inventory_ids.append(revision)
831
 
                except bzrlib.errors.NoSuchRevision:
832
 
                    pass
 
860
            inventory_ids = [other.get_revision(r).inventory_id
 
861
                             for r in revision_ids]
833
862
            other.inventory_store.prefetch(inventory_ids)
834
863
 
835
864
        if pb is None:
879
908
        commit(self, *args, **kw)
880
909
        
881
910
 
882
 
    def lookup_revision(self, revision):
883
 
        """Return the revision identifier for a given revision information."""
884
 
        revno, info = self._get_revision_info(revision)
885
 
        return info
886
 
 
887
 
 
888
911
    def revision_id_to_revno(self, revision_id):
889
912
        """Given a revision id, return its revno"""
890
913
        history = self.revision_history()
894
917
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
895
918
 
896
919
 
897
 
    def get_revision_info(self, revision):
898
 
        """Return (revno, revision id) for revision identifier.
899
 
 
900
 
        revision can be an integer, in which case it is assumed to be revno (though
901
 
            this will translate negative values into positive ones)
902
 
        revision can also be a string, in which case it is parsed for something like
903
 
            'date:' or 'revid:' etc.
904
 
        """
905
 
        revno, rev_id = self._get_revision_info(revision)
906
 
        if revno is None:
907
 
            raise bzrlib.errors.NoSuchRevision(self, revision)
908
 
        return revno, rev_id
909
 
 
910
920
    def get_rev_id(self, revno, history=None):
911
921
        """Find the revision id of the specified revno."""
912
922
        if revno == 0:
917
927
            raise bzrlib.errors.NoSuchRevision(self, revno)
918
928
        return history[revno - 1]
919
929
 
920
 
    def _get_revision_info(self, revision):
921
 
        """Return (revno, revision id) for revision specifier.
922
 
 
923
 
        revision can be an integer, in which case it is assumed to be revno
924
 
        (though this will translate negative values into positive ones)
925
 
        revision can also be a string, in which case it is parsed for something
926
 
        like 'date:' or 'revid:' etc.
927
 
 
928
 
        A revid is always returned.  If it is None, the specifier referred to
929
 
        the null revision.  If the revid does not occur in the revision
930
 
        history, revno will be None.
931
 
        """
932
 
        
933
 
        if revision is None:
934
 
            return 0, None
935
 
        revno = None
936
 
        try:# Convert to int if possible
937
 
            revision = int(revision)
938
 
        except ValueError:
939
 
            pass
940
 
        revs = self.revision_history()
941
 
        if isinstance(revision, int):
942
 
            if revision < 0:
943
 
                revno = len(revs) + revision + 1
944
 
            else:
945
 
                revno = revision
946
 
            rev_id = self.get_rev_id(revno, revs)
947
 
        elif isinstance(revision, basestring):
948
 
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
949
 
                if revision.startswith(prefix):
950
 
                    result = func(self, revs, revision)
951
 
                    if len(result) > 1:
952
 
                        revno, rev_id = result
953
 
                    else:
954
 
                        revno = result[0]
955
 
                        rev_id = self.get_rev_id(revno, revs)
956
 
                    break
957
 
            else:
958
 
                raise BzrError('No namespace registered for string: %r' %
959
 
                               revision)
960
 
        else:
961
 
            raise TypeError('Unhandled revision type %s' % revision)
962
 
 
963
 
        if revno is None:
964
 
            if rev_id is None:
965
 
                raise bzrlib.errors.NoSuchRevision(self, revision)
966
 
        return revno, rev_id
967
 
 
968
 
    def _namespace_revno(self, revs, revision):
969
 
        """Lookup a revision by revision number"""
970
 
        assert revision.startswith('revno:')
971
 
        try:
972
 
            return (int(revision[6:]),)
973
 
        except ValueError:
974
 
            return None
975
 
    REVISION_NAMESPACES['revno:'] = _namespace_revno
976
 
 
977
 
    def _namespace_revid(self, revs, revision):
978
 
        assert revision.startswith('revid:')
979
 
        rev_id = revision[len('revid:'):]
980
 
        try:
981
 
            return revs.index(rev_id) + 1, rev_id
982
 
        except ValueError:
983
 
            return None, rev_id
984
 
    REVISION_NAMESPACES['revid:'] = _namespace_revid
985
 
 
986
 
    def _namespace_last(self, revs, revision):
987
 
        assert revision.startswith('last:')
988
 
        try:
989
 
            offset = int(revision[5:])
990
 
        except ValueError:
991
 
            return (None,)
992
 
        else:
993
 
            if offset <= 0:
994
 
                raise BzrError('You must supply a positive value for --revision last:XXX')
995
 
            return (len(revs) - offset + 1,)
996
 
    REVISION_NAMESPACES['last:'] = _namespace_last
997
 
 
998
 
    def _namespace_tag(self, revs, revision):
999
 
        assert revision.startswith('tag:')
1000
 
        raise BzrError('tag: namespace registered, but not implemented.')
1001
 
    REVISION_NAMESPACES['tag:'] = _namespace_tag
1002
 
 
1003
 
    def _namespace_date(self, revs, revision):
1004
 
        assert revision.startswith('date:')
1005
 
        import datetime
1006
 
        # Spec for date revisions:
1007
 
        #   date:value
1008
 
        #   value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
1009
 
        #   it can also start with a '+/-/='. '+' says match the first
1010
 
        #   entry after the given date. '-' is match the first entry before the date
1011
 
        #   '=' is match the first entry after, but still on the given date.
1012
 
        #
1013
 
        #   +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
1014
 
        #   -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
1015
 
        #   =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
1016
 
        #       May 13th, 2005 at 0:00
1017
 
        #
1018
 
        #   So the proper way of saying 'give me all entries for today' is:
1019
 
        #       -r {date:+today}:{date:-tomorrow}
1020
 
        #   The default is '=' when not supplied
1021
 
        val = revision[5:]
1022
 
        match_style = '='
1023
 
        if val[:1] in ('+', '-', '='):
1024
 
            match_style = val[:1]
1025
 
            val = val[1:]
1026
 
 
1027
 
        today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
1028
 
        if val.lower() == 'yesterday':
1029
 
            dt = today - datetime.timedelta(days=1)
1030
 
        elif val.lower() == 'today':
1031
 
            dt = today
1032
 
        elif val.lower() == 'tomorrow':
1033
 
            dt = today + datetime.timedelta(days=1)
1034
 
        else:
1035
 
            import re
1036
 
            # This should be done outside the function to avoid recompiling it.
1037
 
            _date_re = re.compile(
1038
 
                    r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1039
 
                    r'(,|T)?\s*'
1040
 
                    r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1041
 
                )
1042
 
            m = _date_re.match(val)
1043
 
            if not m or (not m.group('date') and not m.group('time')):
1044
 
                raise BzrError('Invalid revision date %r' % revision)
1045
 
 
1046
 
            if m.group('date'):
1047
 
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1048
 
            else:
1049
 
                year, month, day = today.year, today.month, today.day
1050
 
            if m.group('time'):
1051
 
                hour = int(m.group('hour'))
1052
 
                minute = int(m.group('minute'))
1053
 
                if m.group('second'):
1054
 
                    second = int(m.group('second'))
1055
 
                else:
1056
 
                    second = 0
1057
 
            else:
1058
 
                hour, minute, second = 0,0,0
1059
 
 
1060
 
            dt = datetime.datetime(year=year, month=month, day=day,
1061
 
                    hour=hour, minute=minute, second=second)
1062
 
        first = dt
1063
 
        last = None
1064
 
        reversed = False
1065
 
        if match_style == '-':
1066
 
            reversed = True
1067
 
        elif match_style == '=':
1068
 
            last = dt + datetime.timedelta(days=1)
1069
 
 
1070
 
        if reversed:
1071
 
            for i in range(len(revs)-1, -1, -1):
1072
 
                r = self.get_revision(revs[i])
1073
 
                # TODO: Handle timezone.
1074
 
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1075
 
                if first >= dt and (last is None or dt >= last):
1076
 
                    return (i+1,)
1077
 
        else:
1078
 
            for i in range(len(revs)):
1079
 
                r = self.get_revision(revs[i])
1080
 
                # TODO: Handle timezone.
1081
 
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1082
 
                if first <= dt and (last is None or dt <= last):
1083
 
                    return (i+1,)
1084
 
    REVISION_NAMESPACES['date:'] = _namespace_date
1085
 
 
1086
 
 
1087
 
    def _namespace_ancestor(self, revs, revision):
1088
 
        from revision import common_ancestor, MultipleRevisionSources
1089
 
        other_branch = find_branch(_trim_namespace('ancestor', revision))
1090
 
        revision_a = self.last_patch()
1091
 
        revision_b = other_branch.last_patch()
1092
 
        for r, b in ((revision_a, self), (revision_b, other_branch)):
1093
 
            if r is None:
1094
 
                raise bzrlib.errors.NoCommits(b)
1095
 
        revision_source = MultipleRevisionSources(self, other_branch)
1096
 
        result = common_ancestor(revision_a, revision_b, revision_source)
1097
 
        try:
1098
 
            revno = self.revision_id_to_revno(result)
1099
 
        except bzrlib.errors.NoSuchRevision:
1100
 
            revno = None
1101
 
        return revno,result
1102
 
        
1103
 
 
1104
 
    REVISION_NAMESPACES['ancestor:'] = _namespace_ancestor
1105
 
 
1106
930
    def revision_tree(self, revision_id):
1107
931
        """Return Tree for a revision on this branch.
1108
932
 
1392
1216
        
1393
1217
 
1394
1218
 
1395
 
class ScratchBranch(Branch):
 
1219
class ScratchBranch(LocalBranch):
1396
1220
    """Special test class: a branch that cleans up after itself.
1397
1221
 
1398
1222
    >>> b = ScratchBranch()
1415
1239
        if base is None:
1416
1240
            base = mkdtemp()
1417
1241
            init = True
1418
 
        Branch.__init__(self, base, init=init)
 
1242
        LocalBranch.__init__(self, base, init=init)
1419
1243
        for d in dirs:
1420
1244
            os.mkdir(self.abspath(d))
1421
1245
            
1527
1351
        The name of a local directory that exists but is empty.
1528
1352
    """
1529
1353
    from bzrlib.merge import merge
 
1354
    from bzrlib.revisionspec import RevisionSpec
1530
1355
 
1531
1356
    assert isinstance(branch_from, Branch)
1532
1357
    assert isinstance(to_location, basestring)
1536
1361
    if revision is None:
1537
1362
        revno = branch_from.revno()
1538
1363
    else:
1539
 
        revno, rev_id = branch_from.get_revision_info(revision)
 
1364
        revno, rev_id = RevisionSpec(branch_from, revision)
1540
1365
    br_to.update_revisions(branch_from, stop_revision=revno)
1541
1366
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1542
1367
          check_clean=False, ignore_zero=True)
 
1368
    
 
1369
    from_location = branch_from.base
1543
1370
    br_to.set_parent(branch_from.base)
1544
 
    return br_to
1545
1371
 
1546
 
def _trim_namespace(namespace, spec):
1547
 
    full_namespace = namespace + ':'
1548
 
    assert spec.startswith(full_namespace)
1549
 
    return spec[len(full_namespace):]