3
# This program is free software; you can redistribute it and/or modify
 
 
4
# it under the terms of the GNU General Public License as published by
 
 
5
# the Free Software Foundation; either version 2 of the License, or
 
 
6
# (at your option) any later version.
 
 
8
# This program is distributed in the hope that it will be useful,
 
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 
11
# GNU General Public License for more details.
 
 
13
# You should have received a copy of the GNU General Public License
 
 
14
# along with this program; if not, write to the Free Software
 
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 
21
class Revision(object):
 
 
22
    """Single revision on a branch.
 
 
24
    Revisions may know their revision_hash, but only once they've been
 
 
25
    written out.  This is not stored because you cannot write the hash
 
 
26
    into the file it describes.
 
 
28
    After bzr 0.0.5 revisions are allowed to have multiple parents.
 
 
31
        List of parent revision_ids
 
 
41
    def __init__(self, **args):
 
 
42
        self.__dict__.update(args)
 
 
44
        self.parent_sha1s = []
 
 
48
        return "<Revision id %s>" % self.revision_id
 
 
50
    def __eq__(self, other):
 
 
51
        if not isinstance(other, Revision):
 
 
53
        return (self.inventory_id == other.inventory_id
 
 
54
                and self.inventory_sha1 == other.inventory_sha1
 
 
55
                and self.revision_id == other.revision_id
 
 
56
                and self.timestamp == other.timestamp
 
 
57
                and self.message == other.message
 
 
58
                and self.timezone == other.timezone
 
 
59
                and self.committer == other.committer)
 
 
61
    def __ne__(self, other):
 
 
62
        return not self.__eq__(other)
 
 
68
def validate_revision_id(rid):
 
 
69
    """Check rid is syntactically valid for a revision id."""
 
 
71
    if not REVISION_ID_RE:
 
 
73
        REVISION_ID_RE = re.compile('[\w.-]+@[\w.-]+--?\d+--?[0-9a-f]+\Z')
 
 
75
    if not REVISION_ID_RE.match(rid):
 
 
76
        raise ValueError("malformed revision-id %r" % rid)
 
 
79
def is_ancestor(revision_id, candidate_id, branch):
 
 
80
    """Return true if candidate_id is an ancestor of revision_id.
 
 
82
    A false negative will be returned if any intermediate descendent of
 
 
83
    candidate_id is not present in any of the revision_sources.
 
 
85
    revisions_source is an object supporting a get_revision operation that
 
 
86
    behaves like Branch's.
 
 
88
    return candidate_id in branch.get_ancestry(revision_id)
 
 
91
def iter_ancestors(revision_id, revision_source, only_present=False):
 
 
92
    ancestors = (revision_id,)
 
 
94
    while len(ancestors) > 0:
 
 
96
        for ancestor in ancestors:
 
 
98
                yield ancestor, distance
 
 
100
                revision = revision_source.get_revision(ancestor)
 
 
101
            except bzrlib.errors.NoSuchRevision, e:
 
 
102
                if e.revision == revision_id:
 
 
107
                yield ancestor, distance
 
 
108
            new_ancestors.extend(revision.parent_ids)
 
 
109
        ancestors = new_ancestors
 
 
113
def find_present_ancestors(revision_id, revision_source):
 
 
114
    """Return the ancestors of a revision present in a branch.
 
 
116
    It's possible that a branch won't have the complete ancestry of
 
 
117
    one of its revisions.  
 
 
121
    anc_iter = enumerate(iter_ancestors(revision_id, revision_source,
 
 
123
    for anc_order, (anc_id, anc_distance) in anc_iter:
 
 
124
        if not found_ancestors.has_key(anc_id):
 
 
125
            found_ancestors[anc_id] = (anc_order, anc_distance)
 
 
126
    return found_ancestors
 
 
129
def __get_closest(intersection):
 
 
132
    for entry in intersection:
 
 
133
        if entry[0] == intersection[0][0]:
 
 
134
            matches.append(entry[2])
 
 
138
def common_ancestor(revision_a, revision_b, revision_source):
 
 
139
    """Find the ancestor common to both revisions that is closest to both.
 
 
141
    from bzrlib.trace import mutter
 
 
142
    a_ancestors = find_present_ancestors(revision_a, revision_source)
 
 
143
    b_ancestors = find_present_ancestors(revision_b, revision_source)
 
 
146
    # a_order is used as a tie-breaker when two equally-good bases are found
 
 
147
    for revision, (a_order, a_distance) in a_ancestors.iteritems():
 
 
148
        if b_ancestors.has_key(revision):
 
 
149
            a_intersection.append((a_distance, a_order, revision))
 
 
150
            b_intersection.append((b_ancestors[revision][1], a_order, revision))
 
 
151
    mutter("a intersection: %r" % a_intersection)
 
 
152
    mutter("b intersection: %r" % b_intersection)
 
 
154
    a_closest = __get_closest(a_intersection)
 
 
155
    if len(a_closest) == 0:
 
 
157
    b_closest = __get_closest(b_intersection)
 
 
158
    assert len(b_closest) != 0
 
 
159
    mutter ("a_closest %r" % a_closest)
 
 
160
    mutter ("b_closest %r" % b_closest)
 
 
161
    if a_closest[0] in b_closest:
 
 
163
    elif b_closest[0] in a_closest:
 
 
166
        raise bzrlib.errors.AmbiguousBase((a_closest[0], b_closest[0]))
 
 
169
class MultipleRevisionSources(object):
 
 
170
    """Proxy that looks in multiple branches for revisions."""
 
 
171
    def __init__(self, *args):
 
 
172
        object.__init__(self)
 
 
173
        assert len(args) != 0
 
 
174
        self._revision_sources = args
 
 
176
    def get_revision(self, revision_id):
 
 
177
        for source in self._revision_sources:
 
 
179
                return source.get_revision(revision_id)
 
 
180
            except bzrlib.errors.NoSuchRevision, e:
 
 
184
def get_intervening_revisions(ancestor_id, rev_id, rev_source, 
 
 
185
                              revision_history=None):
 
 
186
    """Find the longest line of descent from maybe_ancestor to revision.
 
 
187
    Revision history is followed where possible.
 
 
189
    If ancestor_id == rev_id, list will be empty.
 
 
190
    Otherwise, rev_id will be the last entry.  ancestor_id will never appear.
 
 
191
    If ancestor_id is not an ancestor, NotAncestor will be thrown
 
 
193
    [rev_source.get_revision(r) for r in (ancestor_id, rev_id)]
 
 
194
    if ancestor_id == rev_id:
 
 
196
    def historical_lines(line):
 
 
197
        """Return a tuple of historical/non_historical lines, for sorting.
 
 
198
        The non_historical count is negative, since non_historical lines are
 
 
203
        for revision in line:
 
 
204
            if revision in revision_history:
 
 
208
        return good_count, bad_count
 
 
210
    successful_lines = []
 
 
211
    while len(active) > 0:
 
 
214
            for parent in rev_source.get_revision(line[-1]).parent_ids:
 
 
216
                if parent == ancestor_id:
 
 
217
                    successful_lines.append(line_copy)
 
 
219
                    line_copy.append(parent)
 
 
220
                    new_active.append(line_copy)
 
 
222
    if len(successful_lines) == 0:
 
 
223
        raise bzrlib.errors.NotAncestor(rev_id, ancestor_id)
 
 
224
    for line in successful_lines:
 
 
226
    if revision_history is not None:
 
 
227
        by_historical_lines = []
 
 
228
        for line in successful_lines:
 
 
229
            count = historical_lines(line)
 
 
230
            by_historical_lines.append((count, line))
 
 
231
        by_historical_lines.sort()
 
 
232
        if by_historical_lines[-1][0][0] > 0:
 
 
233
            return by_historical_lines[-1][1]
 
 
234
    assert len(successful_lines)
 
 
235
    successful_lines.sort(cmp, len)
 
 
236
    return successful_lines[-1]