~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: John Arbash Meinel
  • Date: 2005-09-16 14:57:59 UTC
  • mto: (1393.2.1)
  • mto: This revision was merged to the branch mainline in revision 1396.
  • Revision ID: john@arbash-meinel.com-20050916145758-ad06c9ae86840f17
Merged up-to-date against mainline, still broken.

Show diffs side-by-side

added added

removed removed

Lines of Context:
32
32
from bzrlib.tree import EmptyTree, RevisionTree
33
33
import bzrlib.xml
34
34
import bzrlib.ui
 
35
import bzrlib.transport
35
36
 
36
37
 
37
38
 
43
44
# repeatedly to calculate deltas.  We could perhaps have a weakref
44
45
# cache in memory to make this faster.
45
46
 
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
 
    from bzrlib.transport import transport
52
 
    from bzrlib.transport.local import LocalTransport
53
 
    t = transport(f)
54
 
    # FIXME: This is a hack around transport so that
55
 
    #        We can search the local directories for
56
 
    #        a branch root.
57
 
    if args.has_key('init') and args['init']:
58
 
        # Don't search if we are init-ing
59
 
        return Branch(t, **args)
60
 
    if isinstance(t, LocalTransport):
61
 
        root = find_branch_root(f)
62
 
        if root != f:
63
 
            t = transport(root)
64
 
    return Branch(t, **args)
65
 
 
 
47
def find_branch(*ignored, **ignored_too):
 
48
    # XXX: leave this here for about one release, then remove it
 
49
    raise NotImplementedError('find_branch() is not supported anymore, '
 
50
                              'please use one of the new branch constructors')
66
51
def _relpath(base, path):
67
52
    """Return path relative to base, or raise exception.
68
53
 
90
75
    return os.sep.join(s)
91
76
        
92
77
 
93
 
def find_branch_root(f=None):
94
 
    """Find the branch root enclosing f, or pwd.
95
 
 
96
 
    f may be a filename or a URL.
97
 
 
98
 
    It is not necessary that f exists.
 
78
def find_branch_root(t):
 
79
    """Find the branch root enclosing the transport's base.
 
80
 
 
81
    t is a Transport object.
 
82
 
 
83
    It is not necessary that the base of t exists.
99
84
 
100
85
    Basically we keep looking up until we find the control directory or
101
86
    run into the root.  If there isn't one, raises NotBranchError.
102
87
    """
103
 
    if f == None:
104
 
        f = os.getcwd()
105
 
    else:
106
 
        f = os.path.realpath(f)
107
 
    if not os.path.exists(f):
108
 
        raise BzrError('%r does not exist' % f)
109
 
        
110
 
 
111
 
    orig_f = f
112
 
 
113
88
    while True:
114
 
        if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
115
 
            return f
116
 
        head, tail = os.path.split(f)
117
 
        if head == f:
 
89
        if t.has(bzrlib.BZRDIR):
 
90
            return t
 
91
        new_t = t.clone('..')
 
92
        if new_t.base == t.base:
118
93
            # reached the root, whatever that may be
119
94
            raise NotBranchError('%s is not in a branch' % orig_f)
120
 
        f = head
121
 
 
122
 
 
 
95
        t = new_t
123
96
 
124
97
 
125
98
######################################################################
129
102
    """Branch holding a history of revisions.
130
103
 
131
104
    base
132
 
        Base directory of the branch.
 
105
        Base directory/url of the branch.
 
106
    """
 
107
    base = None
 
108
 
 
109
    def __init__(self, *ignored, **ignored_too):
 
110
        raise NotImplementedError('The Branch class is abstract')
 
111
 
 
112
    @staticmethod
 
113
    def open(base):
 
114
        """Open an existing branch, rooted at 'base' (url)"""
 
115
        t = bzrlib.transport.transport(base)
 
116
        return LocalBranch(t)
 
117
 
 
118
    @staticmethod
 
119
    def open_containing(url):
 
120
        """Open an existing branch, containing url (search upwards for the root)
 
121
        """
 
122
        t = bzrlib.transport.transport(base)
 
123
        found_t = find_branch_root(t)
 
124
        return LocalBranch(t)
 
125
 
 
126
    @staticmethod
 
127
    def initialize(base):
 
128
        """Create a new branch, rooted at 'base' (url)"""
 
129
        t = bzrlib.transport.transport(base)
 
130
        return LocalBranch(t, init=True)
 
131
 
 
132
    def setup_caching(self, cache_root):
 
133
        """Subclasses that care about caching should override this, and set
 
134
        up cached stores located under cache_root.
 
135
        """
 
136
 
 
137
 
 
138
class LocalBranch(Branch):
 
139
    """A branch stored in the actual filesystem.
 
140
 
 
141
    Note that it's "local" in the context of the filesystem; it doesn't
 
142
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
 
143
    it's writable, and can be accessed via the normal filesystem API.
133
144
 
134
145
    _lock_mode
135
146
        None, or 'r' or 'w'
141
152
    _lock
142
153
        Lock object from bzrlib.lock.
143
154
    """
144
 
    base = None
 
155
    # We actually expect this class to be somewhat short-lived; part of its
 
156
    # purpose is to try to isolate what bits of the branch logic are tied to
 
157
    # filesystem access, so that in a later step, we can extricate them to
 
158
    # a separarte ("storage") class.
145
159
    _lock_mode = None
146
160
    _lock_count = None
147
161
    _lock = None
148
 
    cache_root = None
149
 
    
150
 
    # Map some sort of prefix into a namespace
151
 
    # stuff like "revno:10", "revid:", etc.
152
 
    # This should match a prefix with a function which accepts
153
 
    REVISION_NAMESPACES = {}
154
162
 
155
163
    def __init__(self, transport, init=False):
156
164
        """Create new branch object at a particular location.
527
535
        """Print `file` to stdout."""
528
536
        self.lock_read()
529
537
        try:
530
 
            tree = self.revision_tree(self.lookup_revision(revno))
 
538
            tree = self.revision_tree(self.get_rev_id(revno))
531
539
            # use inventory as it was in that revision
532
540
            file_id = tree.inventory.path2id(file)
533
541
            if not file_id:
911
919
        if stop_revision is None:
912
920
            other_revision = other.last_patch()
913
921
        else:
914
 
            other_revision = other.lookup_revision(stop_revision)
 
922
            other_revision = other.get_rev_id(stop_revision)
915
923
        count = greedy_fetch(self, other, other_revision, pb)[0]
916
924
        try:
917
925
            revision_ids = self.missing_revisions(other, stop_revision)
994
1002
        commit(self, *args, **kw)
995
1003
        
996
1004
 
997
 
    def lookup_revision(self, revision):
998
 
        """Return the revision identifier for a given revision information."""
999
 
        revno, info = self._get_revision_info(revision)
1000
 
        return info
1001
 
 
1002
 
 
1003
1005
    def revision_id_to_revno(self, revision_id):
1004
1006
        """Given a revision id, return its revno"""
1005
1007
        history = self.revision_history()
1009
1011
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
1010
1012
 
1011
1013
 
1012
 
    def get_revision_info(self, revision):
1013
 
        """Return (revno, revision id) for revision identifier.
1014
 
 
1015
 
        revision can be an integer, in which case it is assumed to be revno (though
1016
 
            this will translate negative values into positive ones)
1017
 
        revision can also be a string, in which case it is parsed for something like
1018
 
            'date:' or 'revid:' etc.
1019
 
        """
1020
 
        revno, rev_id = self._get_revision_info(revision)
1021
 
        if revno is None:
1022
 
            raise bzrlib.errors.NoSuchRevision(self, revision)
1023
 
        return revno, rev_id
1024
 
 
1025
1014
    def get_rev_id(self, revno, history=None):
1026
1015
        """Find the revision id of the specified revno."""
1027
1016
        if revno == 0:
1032
1021
            raise bzrlib.errors.NoSuchRevision(self, revno)
1033
1022
        return history[revno - 1]
1034
1023
 
1035
 
    def _get_revision_info(self, revision):
1036
 
        """Return (revno, revision id) for revision specifier.
1037
 
 
1038
 
        revision can be an integer, in which case it is assumed to be revno
1039
 
        (though this will translate negative values into positive ones)
1040
 
        revision can also be a string, in which case it is parsed for something
1041
 
        like 'date:' or 'revid:' etc.
1042
 
 
1043
 
        A revid is always returned.  If it is None, the specifier referred to
1044
 
        the null revision.  If the revid does not occur in the revision
1045
 
        history, revno will be None.
1046
 
        """
1047
 
        
1048
 
        if revision is None:
1049
 
            return 0, None
1050
 
        revno = None
1051
 
        try:# Convert to int if possible
1052
 
            revision = int(revision)
1053
 
        except ValueError:
1054
 
            pass
1055
 
        revs = self.revision_history()
1056
 
        if isinstance(revision, int):
1057
 
            if revision < 0:
1058
 
                revno = len(revs) + revision + 1
1059
 
            else:
1060
 
                revno = revision
1061
 
            rev_id = self.get_rev_id(revno, revs)
1062
 
        elif isinstance(revision, basestring):
1063
 
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
1064
 
                if revision.startswith(prefix):
1065
 
                    result = func(self, revs, revision)
1066
 
                    if len(result) > 1:
1067
 
                        revno, rev_id = result
1068
 
                    else:
1069
 
                        revno = result[0]
1070
 
                        rev_id = self.get_rev_id(revno, revs)
1071
 
                    break
1072
 
            else:
1073
 
                raise BzrError('No namespace registered for string: %r' %
1074
 
                               revision)
1075
 
        else:
1076
 
            raise TypeError('Unhandled revision type %s' % revision)
1077
 
 
1078
 
        if revno is None:
1079
 
            if rev_id is None:
1080
 
                raise bzrlib.errors.NoSuchRevision(self, revision)
1081
 
        return revno, rev_id
1082
 
 
1083
 
    def _namespace_revno(self, revs, revision):
1084
 
        """Lookup a revision by revision number"""
1085
 
        assert revision.startswith('revno:')
1086
 
        try:
1087
 
            return (int(revision[6:]),)
1088
 
        except ValueError:
1089
 
            return None
1090
 
    REVISION_NAMESPACES['revno:'] = _namespace_revno
1091
 
 
1092
 
    def _namespace_revid(self, revs, revision):
1093
 
        assert revision.startswith('revid:')
1094
 
        rev_id = revision[len('revid:'):]
1095
 
        try:
1096
 
            return revs.index(rev_id) + 1, rev_id
1097
 
        except ValueError:
1098
 
            return None, rev_id
1099
 
    REVISION_NAMESPACES['revid:'] = _namespace_revid
1100
 
 
1101
 
    def _namespace_last(self, revs, revision):
1102
 
        assert revision.startswith('last:')
1103
 
        try:
1104
 
            offset = int(revision[5:])
1105
 
        except ValueError:
1106
 
            return (None,)
1107
 
        else:
1108
 
            if offset <= 0:
1109
 
                raise BzrError('You must supply a positive value for --revision last:XXX')
1110
 
            return (len(revs) - offset + 1,)
1111
 
    REVISION_NAMESPACES['last:'] = _namespace_last
1112
 
 
1113
 
    def _namespace_tag(self, revs, revision):
1114
 
        assert revision.startswith('tag:')
1115
 
        raise BzrError('tag: namespace registered, but not implemented.')
1116
 
    REVISION_NAMESPACES['tag:'] = _namespace_tag
1117
 
 
1118
 
    def _namespace_date(self, revs, revision):
1119
 
        assert revision.startswith('date:')
1120
 
        import datetime
1121
 
        # Spec for date revisions:
1122
 
        #   date:value
1123
 
        #   value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
1124
 
        #   it can also start with a '+/-/='. '+' says match the first
1125
 
        #   entry after the given date. '-' is match the first entry before the date
1126
 
        #   '=' is match the first entry after, but still on the given date.
1127
 
        #
1128
 
        #   +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
1129
 
        #   -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
1130
 
        #   =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
1131
 
        #       May 13th, 2005 at 0:00
1132
 
        #
1133
 
        #   So the proper way of saying 'give me all entries for today' is:
1134
 
        #       -r {date:+today}:{date:-tomorrow}
1135
 
        #   The default is '=' when not supplied
1136
 
        val = revision[5:]
1137
 
        match_style = '='
1138
 
        if val[:1] in ('+', '-', '='):
1139
 
            match_style = val[:1]
1140
 
            val = val[1:]
1141
 
 
1142
 
        today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
1143
 
        if val.lower() == 'yesterday':
1144
 
            dt = today - datetime.timedelta(days=1)
1145
 
        elif val.lower() == 'today':
1146
 
            dt = today
1147
 
        elif val.lower() == 'tomorrow':
1148
 
            dt = today + datetime.timedelta(days=1)
1149
 
        else:
1150
 
            import re
1151
 
            # This should be done outside the function to avoid recompiling it.
1152
 
            _date_re = re.compile(
1153
 
                    r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1154
 
                    r'(,|T)?\s*'
1155
 
                    r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1156
 
                )
1157
 
            m = _date_re.match(val)
1158
 
            if not m or (not m.group('date') and not m.group('time')):
1159
 
                raise BzrError('Invalid revision date %r' % revision)
1160
 
 
1161
 
            if m.group('date'):
1162
 
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1163
 
            else:
1164
 
                year, month, day = today.year, today.month, today.day
1165
 
            if m.group('time'):
1166
 
                hour = int(m.group('hour'))
1167
 
                minute = int(m.group('minute'))
1168
 
                if m.group('second'):
1169
 
                    second = int(m.group('second'))
1170
 
                else:
1171
 
                    second = 0
1172
 
            else:
1173
 
                hour, minute, second = 0,0,0
1174
 
 
1175
 
            dt = datetime.datetime(year=year, month=month, day=day,
1176
 
                    hour=hour, minute=minute, second=second)
1177
 
        first = dt
1178
 
        last = None
1179
 
        reversed = False
1180
 
        if match_style == '-':
1181
 
            reversed = True
1182
 
        elif match_style == '=':
1183
 
            last = dt + datetime.timedelta(days=1)
1184
 
 
1185
 
        if reversed:
1186
 
            for i in range(len(revs)-1, -1, -1):
1187
 
                r = self.get_revision(revs[i])
1188
 
                # TODO: Handle timezone.
1189
 
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1190
 
                if first >= dt and (last is None or dt >= last):
1191
 
                    return (i+1,)
1192
 
        else:
1193
 
            for i in range(len(revs)):
1194
 
                r = self.get_revision(revs[i])
1195
 
                # TODO: Handle timezone.
1196
 
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1197
 
                if first <= dt and (last is None or dt <= last):
1198
 
                    return (i+1,)
1199
 
    REVISION_NAMESPACES['date:'] = _namespace_date
1200
 
 
1201
 
 
1202
 
    def _namespace_ancestor(self, revs, revision):
1203
 
        from revision import common_ancestor, MultipleRevisionSources
1204
 
        other_branch = find_branch(_trim_namespace('ancestor', revision))
1205
 
        revision_a = self.last_patch()
1206
 
        revision_b = other_branch.last_patch()
1207
 
        for r, b in ((revision_a, self), (revision_b, other_branch)):
1208
 
            if r is None:
1209
 
                raise bzrlib.errors.NoCommits(b)
1210
 
        revision_source = MultipleRevisionSources(self, other_branch)
1211
 
        result = common_ancestor(revision_a, revision_b, revision_source)
1212
 
        try:
1213
 
            revno = self.revision_id_to_revno(result)
1214
 
        except bzrlib.errors.NoSuchRevision:
1215
 
            revno = None
1216
 
        return revno,result
1217
 
        
1218
 
 
1219
 
    REVISION_NAMESPACES['ancestor:'] = _namespace_ancestor
1220
1024
 
1221
1025
    def revision_tree(self, revision_id):
1222
1026
        """Return Tree for a revision on this branch.
1503
1307
            raise InvalidRevisionNumber(revno)
1504
1308
        
1505
1309
        
1506
 
 
1507
 
 
1508
 
class ScratchBranch(Branch):
 
1310
        
 
1311
 
 
1312
 
 
1313
class ScratchBranch(LocalBranch):
1509
1314
    """Special test class: a branch that cleans up after itself.
1510
1315
 
1511
1316
    >>> b = ScratchBranch()
1528
1333
        if base is None:
1529
1334
            base = mkdtemp()
1530
1335
            init = True
1531
 
        Branch.__init__(self, base, init=init)
 
1336
        LocalBranch.__init__(self, base, init=init)
1532
1337
        for d in dirs:
1533
1338
            self._transport.mkdir(d)
1534
1339
            
1640
1445
        The name of a local directory that exists but is empty.
1641
1446
    """
1642
1447
    from bzrlib.merge import merge
 
1448
    from bzrlib.revisionspec import RevisionSpec
1643
1449
 
1644
1450
    assert isinstance(branch_from, Branch)
1645
1451
    assert isinstance(to_location, basestring)
1646
1452
    
1647
 
    br_to = Branch(to_location, init=True)
 
1453
    br_to = Branch.initialize(to_location)
1648
1454
    br_to.set_root_id(branch_from.get_root_id())
1649
1455
    if revision is None:
1650
1456
        revno = branch_from.revno()
1651
1457
    else:
1652
 
        revno, rev_id = branch_from.get_revision_info(revision)
 
1458
        revno, rev_id = RevisionSpec(revision).in_history(branch_from)
1653
1459
    br_to.update_revisions(branch_from, stop_revision=revno)
1654
1460
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1655
1461
          check_clean=False, ignore_zero=True)
1656
1462
    br_to.set_parent(branch_from.base)
1657
1463
    return br_to
1658
 
 
1659
 
def _trim_namespace(namespace, spec):
1660
 
    full_namespace = namespace + ':'
1661
 
    assert spec.startswith(full_namespace)
1662
 
    return spec[len(full_namespace):]