~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revision.py

  • Committer: Martin Pool
  • Date: 2005-08-25 07:46:11 UTC
  • Revision ID: mbp@sourcefrog.net-20050825074611-98130ea6d05d9d2a
- add functions to enable and disable default logging, so that we can
  turn it off while running the tests

- default logging gets turned on from the bzr main function so that
  other applications using the library can make their own decisions

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
 
 
19
 
 
20
 
from xml import XMLMixin
21
 
 
22
 
try:
23
 
    from cElementTree import Element, ElementTree, SubElement
24
 
except ImportError:
25
 
    from elementtree.ElementTree import Element, ElementTree, SubElement
26
 
 
27
 
from errors import BzrError
28
 
 
29
 
 
30
 
class RevisionReference:
 
18
import bzrlib.errors
 
19
 
 
20
 
 
21
class RevisionReference(object):
31
22
    """
32
23
    Reference to a stored revision.
33
24
 
35
26
    """
36
27
    revision_id = None
37
28
    revision_sha1 = None
38
 
    def __init__(self, revision_id, revision_sha1):
 
29
    def __init__(self, revision_id, revision_sha1=None):
39
30
        if revision_id == None \
40
31
           or isinstance(revision_id, basestring):
41
32
            self.revision_id = revision_id
51
42
                
52
43
 
53
44
 
54
 
class Revision(XMLMixin):
 
45
class Revision(object):
55
46
    """Single revision on a branch.
56
47
 
57
48
    Revisions may know their revision_hash, but only once they've been
59
50
    into the file it describes.
60
51
 
61
52
    After bzr 0.0.5 revisions are allowed to have multiple parents.
62
 
    To support old clients this is written out in a slightly redundant
63
 
    form: the first parent as the predecessor.  This will eventually
64
 
    be dropped.
65
53
 
66
54
    parents
67
55
        List of parent revisions, each is a RevisionReference.
78
66
        self.__dict__.update(args)
79
67
        self.parents = []
80
68
 
81
 
    def _get_precursor(self):
82
 
        from warnings import warn
83
 
        warn("Revision.precursor is deprecated", stacklevel=2)
84
 
        if self.parents:
85
 
            return self.parents[0].revision_id
86
 
        else:
87
 
            return None
88
 
 
89
 
 
90
 
    def _get_precursor_sha1(self):
91
 
        from warnings import warn
92
 
        warn("Revision.precursor_sha1 is deprecated", stacklevel=2)
93
 
        if self.parents:
94
 
            return self.parents[0].revision_sha1
95
 
        else:
96
 
            return None    
97
 
 
98
 
 
99
 
    def _fail(self):
100
 
        raise Exception("can't assign to precursor anymore")
101
 
 
102
 
 
103
 
    precursor = property(_get_precursor, _fail, _fail)
104
 
    precursor_sha1 = property(_get_precursor_sha1, _fail, _fail)
105
 
 
106
 
 
107
69
 
108
70
    def __repr__(self):
109
71
        return "<Revision id %s>" % self.revision_id
110
72
 
111
73
        
112
74
    def to_element(self):
 
75
        from bzrlib.xml import Element, SubElement
 
76
        
113
77
        root = Element('revision',
114
78
                       committer = self.committer,
115
79
                       timestamp = '%.9f' % self.timestamp,
126
90
        msg.tail = '\n'
127
91
 
128
92
        if self.parents:
129
 
            # first parent stored as precursor for compatability with 0.0.5 and
130
 
            # earlier
131
 
            pr = self.parents[0]
132
 
            assert pr.revision_id
133
 
            root.set('precursor', pr.revision_id)
134
 
            if pr.revision_sha1:
135
 
                root.set('precursor_sha1', pr.revision_sha1)
136
 
                
137
 
        if self.parents:
138
93
            pelts = SubElement(root, 'parents')
139
94
            pelts.tail = pelts.text = '\n'
140
95
            for rr in self.parents:
160
115
    """Convert XML element into Revision object."""
161
116
    # <changeset> is deprecated...
162
117
    if elt.tag not in ('revision', 'changeset'):
163
 
        raise BzrError("unexpected tag in revision file: %r" % elt)
 
118
        raise bzrlib.errors.BzrError("unexpected tag in revision file: %r" % elt)
164
119
 
165
120
    rev = Revision(committer = elt.get('committer'),
166
121
                   timestamp = float(elt.get('timestamp')),
174
129
 
175
130
    pelts = elt.find('parents')
176
131
 
177
 
    if precursor:
178
 
        # revisions written prior to 0.0.5 have a single precursor
179
 
        # give as an attribute
180
 
        rev_ref = RevisionReference(precursor, precursor_sha1)
181
 
        rev.parents.append(rev_ref)
182
 
    elif pelts:
 
132
    if pelts:
183
133
        for p in pelts:
184
134
            assert p.tag == 'revision_ref', \
185
135
                   "bad parent node tag %r" % p.tag
187
137
                                        p.get('revision_sha1'))
188
138
            rev.parents.append(rev_ref)
189
139
 
 
140
        if precursor:
 
141
            # must be consistent
 
142
            prec_parent = rev.parents[0].revision_id
 
143
            assert prec_parent == precursor
 
144
    elif precursor:
 
145
        # revisions written prior to 0.0.5 have a single precursor
 
146
        # give as an attribute
 
147
        rev_ref = RevisionReference(precursor, precursor_sha1)
 
148
        rev.parents.append(rev_ref)
 
149
 
190
150
    v = elt.get('timezone')
191
151
    rev.timezone = v and int(v)
192
152
 
193
153
    rev.message = elt.findtext('message') # text of <message>
194
154
    return rev
 
155
 
 
156
 
 
157
 
 
158
REVISION_ID_RE = None
 
159
 
 
160
def validate_revision_id(rid):
 
161
    """Check rid is syntactically valid for a revision id."""
 
162
    global REVISION_ID_RE
 
163
    if not REVISION_ID_RE:
 
164
        import re
 
165
        REVISION_ID_RE = re.compile('[\w.-]+@[\w.-]+--?\d+--?[0-9a-f]+\Z')
 
166
 
 
167
    if not REVISION_ID_RE.match(rid):
 
168
        raise ValueError("malformed revision-id %r" % rid)
 
169
 
 
170
def is_ancestor(revision_id, candidate_id, revision_source):
 
171
    """Return true if candidate_id is an ancestor of revision_id.
 
172
    A false negative will be returned if any intermediate descendent of
 
173
    candidate_id is not present in any of the revision_sources.
 
174
    
 
175
    revisions_source is an object supporting a get_revision operation that
 
176
    behaves like Branch's.
 
177
    """
 
178
 
 
179
    for ancestor_id, distance in iter_ancestors(revision_id, revision_source):
 
180
        if ancestor_id == candidate_id:
 
181
            return True
 
182
    return False
 
183
 
 
184
def iter_ancestors(revision_id, revision_source, only_present=False):
 
185
    ancestors = (revision_id,)
 
186
    distance = 0
 
187
    while len(ancestors) > 0:
 
188
        new_ancestors = []
 
189
        for ancestor in ancestors:
 
190
            if not only_present:
 
191
                yield ancestor, distance
 
192
            try:
 
193
                revision = revision_source.get_revision(ancestor)
 
194
            except bzrlib.errors.NoSuchRevision, e:
 
195
                if e.revision == revision_id:
 
196
                    raise 
 
197
                else:
 
198
                    continue
 
199
            if only_present:
 
200
                yield ancestor, distance
 
201
            new_ancestors.extend([p.revision_id for p in revision.parents])
 
202
        ancestors = new_ancestors
 
203
        distance += 1
 
204
 
 
205
 
 
206
def find_present_ancestors(revision_id, revision_source):
 
207
    found_ancestors = {}
 
208
    count = 0
 
209
    anc_iter = enumerate(iter_ancestors(revision_id, revision_source,
 
210
                         only_present=True))
 
211
    for anc_order, (anc_id, anc_distance) in anc_iter:
 
212
        if not found_ancestors.has_key(anc_id):
 
213
            found_ancestors[anc_id] = (anc_order, anc_distance)
 
214
    return found_ancestors
 
215
    
 
216
class AmbiguousBase(bzrlib.errors.BzrError):
 
217
    def __init__(self, bases):
 
218
        msg = "The correct base is unclear, becase %s are all equally close" %\
 
219
            ", ".join(bases)
 
220
        bzrlib.errors.BzrError.__init__(self, msg)
 
221
        self.bases = bases
 
222
 
 
223
def common_ancestor(revision_a, revision_b, revision_source):
 
224
    """Find the ancestor common to both revisions that is closest to both.
 
225
    """
 
226
    from bzrlib.trace import mutter
 
227
    a_ancestors = find_present_ancestors(revision_a, revision_source)
 
228
    b_ancestors = find_present_ancestors(revision_b, revision_source)
 
229
    a_intersection = []
 
230
    b_intersection = []
 
231
    # a_order is used as a tie-breaker when two equally-good bases are found
 
232
    for revision, (a_order, a_distance) in a_ancestors.iteritems():
 
233
        if b_ancestors.has_key(revision):
 
234
            a_intersection.append((a_distance, a_order, revision))
 
235
            b_intersection.append((b_ancestors[revision][1], a_order, revision))
 
236
    mutter("a intersection: %r" % a_intersection)
 
237
    mutter("b intersection: %r" % b_intersection)
 
238
    def get_closest(intersection):
 
239
        intersection.sort()
 
240
        matches = [] 
 
241
        for entry in intersection:
 
242
            if entry[0] == intersection[0][0]:
 
243
                matches.append(entry[2])
 
244
        return matches
 
245
 
 
246
    a_closest = get_closest(a_intersection)
 
247
    if len(a_closest) == 0:
 
248
        return None
 
249
    b_closest = get_closest(b_intersection)
 
250
    assert len(b_closest) != 0
 
251
    mutter ("a_closest %r" % a_closest)
 
252
    mutter ("b_closest %r" % b_closest)
 
253
    if a_closest[0] in b_closest:
 
254
        return a_closest[0]
 
255
    elif b_closest[0] in a_closest:
 
256
        return b_closest[0]
 
257
    else:
 
258
        raise AmbiguousBase((a_closest[0], b_closest[0]))
 
259
    return a_closest[0]
 
260
 
 
261
class MultipleRevisionSources(object):
 
262
    def __init__(self, *args):
 
263
        object.__init__(self)
 
264
        assert len(args) != 0
 
265
        self._revision_sources = args
 
266
 
 
267
    def get_revision(self, revision_id):
 
268
        for source in self._revision_sources:
 
269
            try:
 
270
                return source.get_revision(revision_id)
 
271
            except bzrlib.errors.NoSuchRevision, e:
 
272
                pass
 
273
        raise e