~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to fai/pylon/ancillary.py

  • Committer: Robert Collins
  • Date: 2005-09-13 10:46:27 UTC
  • mto: (147.2.6) (364.1.3 bzrtools)
  • mto: This revision was merged to the branch mainline in revision 324.
  • Revision ID: robertc@robertcollins.net-20050913104627-51f938950a907475
handle inaccessible sibling archives somewhat - note version-0 is still not handled

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2004 Aaron Bentley
 
2
# <aaron.bentley@utoronto.ca>
 
3
#
 
4
#    This program is free software; you can redistribute it and/or modify
 
5
#    it under the terms of the GNU General Public License as published by
 
6
#    the Free Software Foundation; either version 2 of the License, or
 
7
#    (at your option) any later version.
 
8
#
 
9
#    This program is distributed in the hope that it will be useful,
 
10
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
#    GNU General Public License for more details.
 
13
#
 
14
#    You should have received a copy of the GNU General Public License
 
15
#    along with this program; if not, write to the Free Software
 
16
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
17
 
 
18
__docformat__ = "restructuredtext"
 
19
__doc__ = "Utility functionality not part of core Arch"
 
20
 
 
21
import errors
 
22
import os
 
23
import pybaz as arch
 
24
import string
 
25
import urllib
 
26
import urllib2
 
27
import re
 
28
 
 
29
import arch_core
 
30
import arch_compound
 
31
import util
 
32
 
 
33
def iter_alias(my_file):
 
34
    """
 
35
    Iterate through aliases in the supplied file.
 
36
 
 
37
    :param my_file: The name of the file to read.  May include ~/ notation.
 
38
    :type my_file: string
 
39
    :rtype: iterator of list of string
 
40
    """
 
41
    tmp = os.path.expanduser(my_file)
 
42
    if not os.access(tmp, os.R_OK) or not os.path.isfile(tmp):
 
43
        return
 
44
    for line in open(tmp):
 
45
        line=line.rstrip("\n")
 
46
        if line.startswith("#") or len(line)==0 or line.isspace():
 
47
            continue
 
48
        parts=string.split(line, "=")
 
49
        parts[0]=parts[0].strip()
 
50
        parts[1]=parts[1].strip()
 
51
        if len(parts)!=2:
 
52
            raise errors.CantParseAlias(line)
 
53
        if parts[1][0]=="\"":
 
54
            if parts[1][-1]=="\"":
 
55
                parts[1]=parts[1][1:-1]
 
56
            else:
 
57
                raise errors.CantParseAlias(line)
 
58
        check_alias(line, parts)
 
59
        yield parts
 
60
 
 
61
 
 
62
def check_alias(line, parts):
 
63
    """Ensures that a given alias name is suitable for use"""
 
64
    if '@' in parts[0] or '/' in parts[0] or '--' in parts[0]:
 
65
        raise errors.ForbiddenAliasSyntax(line)
 
66
 
 
67
 
 
68
def iter_all_alias(tree):
 
69
    """
 
70
    Iterate all relevent aliases.
 
71
 
 
72
    An alias name may appear more than once in the output.  The last value
 
73
    to appear is the one that should be used.  Iterates through ~/.aba/aliases
 
74
    and per-tree aliases.
 
75
 
 
76
    :param tree: The working tree to seek aliases in (may be None)
 
77
    :type tree: `arch.ArchSourceTree`
 
78
    :rtype: iterator of list of string
 
79
    """
 
80
    for parts in iter_alias("~/.aba/aliases"):
 
81
        yield parts
 
82
    if tree==None:
 
83
        return;
 
84
    treefile=None
 
85
    tmp=str(tree)+"/{arch}/+aliases"
 
86
    if os.access(tmp, os.R_OK) and os.path.isfile(tmp):
 
87
        treefile=tmp
 
88
    if treefile is None:
 
89
        tmp=str(tree)+"/{arch}/=aliases"
 
90
        if os.access(tmp, os.R_OK) and os.path.isfile(tmp):
 
91
            treefile=tmp
 
92
    if treefile!=None:
 
93
        for parts in iter_alias(treefile):
 
94
            yield parts
 
95
 
 
96
 
 
97
def compact_alias(spec, tree):
 
98
    aliases = []
 
99
    for parts in iter_all_alias(tree):
 
100
        if spec.startswith(parts[1]):
 
101
            newalias=parts[0]+spec[len(parts[1]):]
 
102
            aliases.append(newalias)
 
103
    return aliases
 
104
 
 
105
 
 
106
def shortest_alias(spec, tree):
 
107
    """Convenience function to return the shortest alias for a spec.
 
108
 
 
109
    :param spec: The spec to compact
 
110
    :type spec: Convertible to str
 
111
    :param tree: The working tree to use for local alias lookup
 
112
    :type tree: `arch.WorkingTree`
 
113
    :return: The shortest alias, or None if no aliases were found
 
114
    :rtype: str or NoneType
 
115
    """
 
116
    aliases = compact_alias(str(spec), tree)
 
117
    return util.shortest(aliases)
 
118
 
 
119
 
 
120
def alias_or_version(version, tree, full=True):
 
121
    """Return the shortest alias or version name for a version.
 
122
 
 
123
    :param version: The spec to compact
 
124
    :type version: `arch.Version`
 
125
    :param tree: The working tree to use for local alias lookup
 
126
    :type tree: `arch.WorkingTree`
 
127
    :param full: Use the full version name (use nonarch if false).
 
128
    :type full: bool
 
129
    :return: The shortest alias, or the version name
 
130
    :rtype: str
 
131
    """
 
132
    alias = shortest_alias(version, tree)
 
133
    if alias is not None:
 
134
        return alias
 
135
    elif full:
 
136
        return version.fullname
 
137
    else:
 
138
        return version.nonarch
 
139
 
 
140
 
 
141
def iter_partners(tree, skip_version=None):
 
142
    """Generate an iterator of partner versions.
 
143
    If the tree contains {arch}/+partner-versions, it is used.  Otherwise,
 
144
    {arch}/=partner-versions is used.
 
145
 
 
146
    :param tree: The tree to find partner versions for
 
147
    :type tree: `arch.ArchSourceTree`
 
148
    :return: An iterator of partner versions for the tree
 
149
    :rtype: iterator of `arch.Version`
 
150
    """
 
151
    partnerfile=str(tree)+"/{arch}/+partner-versions"
 
152
    if not os.access(partnerfile, os.R_OK) or not os.path.isfile(partnerfile):
 
153
        partnerfile=str(tree)+"/{arch}/=partner-versions"
 
154
        if not os.access(partnerfile, os.R_OK) or not \
 
155
            os.path.isfile(partnerfile):
 
156
                return
 
157
 
 
158
    for line in open(partnerfile):
 
159
        version = arch.Version(line.rstrip("\n"))
 
160
        if version == skip_version:
 
161
            continue
 
162
        yield version
 
163
 
 
164
 
 
165
def iter_partner_revisions(tree, skip_version):
 
166
    """Iterates through the archive-latest revisions of all partner versions.
 
167
 
 
168
    :param tree: The tree to get revisions for
 
169
    :type tree: `arch.ArchSourceTree`
 
170
    :param skip_version: A version to skip (typically the tree version)
 
171
    :type skip_version: `arch.Version`
 
172
    """
 
173
    for partner in iter_partners(tree, skip_version):
 
174
        arch_compound.ensure_archive_registered(partner.archive)
 
175
        try:
 
176
            yield partner.iter_revisions(True).next()
 
177
        except StopIteration, e:
 
178
            continue
 
179
 
 
180
 
 
181
def sc_lookup(archive):
 
182
    """Uses James Blackwell's mirror stats page to look up archive locations.
 
183
 
 
184
    :param archive: The Archive to find a location for
 
185
    :type archive: str or `arch.Archive`
 
186
    :return: home and mirror archive (either or both may be none)
 
187
    :rtype: tuple of str or NoneType
 
188
    """
 
189
    try:
 
190
        mirrorpage = urllib2.urlopen("http://sourcecontrol.net/?m=mirrorstats")
 
191
    except Exception, e:
 
192
        raise LookupError(e, archive, "sourcecontrol.net")
 
193
    expression = re.compile("Name : <a href=\"[^\"]*\">%s</a>" % archive)
 
194
    for line in mirrorpage:
 
195
        if expression.search(line) is not None:
 
196
            home = re.search("Home : <a href=\"([^\"]*)\">", line)
 
197
            if home is not None:
 
198
                home = home.group(1)
 
199
            mirror = re.search("Mirror: <a href=\"([^\"]*)\">", line)
 
200
            if mirror is not None:
 
201
                mirror = mirror.group(1)
 
202
            return (home,mirror)
 
203
    return (None, None)
 
204
 
 
205
 
 
206
def bh_lookup(archive):
 
207
    """Uses Gergely Nagy's archive registry to look up archive locations.
 
208
 
 
209
    :param archive: The Archive to find a location for
 
210
    :type archive: str or `arch.Archive`
 
211
    :return: home and mirror archive (either or both may be none)
 
212
    :rtype: tuple of str or NoneType
 
213
    """
 
214
    url = "http://bonehunter.rulez.org/~arch-registry/%s"\
 
215
        % str(archive)
 
216
    try:
 
217
        page = urllib2.urlopen(url)
 
218
    except Exception, e:
 
219
        raise LookupError(e, archive, "http://bonehunter.rulez.org")
 
220
    for line in page:
 
221
        if line.startswith("Archive not found"):
 
222
            return None
 
223
        loc = line
 
224
        break
 
225
    if loc is not None:
 
226
         loc = loc.rstrip("\n")
 
227
    if len(loc) == 0:
 
228
        loc = None
 
229
    return loc
 
230
 
 
231
 
 
232
def bh_submit(archive, location):
 
233
    """Submit an archive location to Gergely Nagy's archive registry.
 
234
 
 
235
    :param archive: The Archive to find a location for
 
236
    :type archive: str or `arch.Archive`
 
237
    :param location: The location to use for the archive
 
238
    :type location: str
 
239
    """
 
240
    data = urllib.urlencode({"name": str(archive), "location": location})
 
241
    url = "http://bonehunter.rulez.org/~arch-registry/submit.cgi"
 
242
    result = urllib2.urlopen(url, data).next().rstrip()
 
243
    if result != "Archive %s registered with location %s." % \
 
244
        (str(archive), location):
 
245
        raise errors.SubmitError(str(archive), location, url, result)
 
246
 
 
247
def iter_micro(tree, my_version=None):
 
248
    if my_version is None:
 
249
        my_version = tree.tree_version
 
250
    for version in iter_partners(tree, tree.tree_version):
 
251
        miss = arch_core.iter_missing(tree, version) 
 
252
        for log in arch_compound.iter_log_match(miss, "Microbranch", 
 
253
                                                str(my_version)):
 
254
            yield log
 
255
 
 
256
 
 
257
def submit_version(tree):
 
258
    """Determine the submit version of this tree
 
259
    
 
260
    :param tree: The tree to determine the version of
 
261
    :type tree: `arch.ArchSourceTree`
 
262
    :return: The submit version, or None
 
263
    :rtype: `arch.Version`
 
264
    """
 
265
    submit_file = tree+"/{arch}/+submit-version"
 
266
    if not os.access(submit_file, os.R_OK):
 
267
        return None
 
268
    line = open(submit_file).next()
 
269
    return arch.Version(line.rstrip("\n"))
 
270
 
 
271
 
 
272
def submit_revision(tree):
 
273
    """If this tree has new merges from the submit version, return latest
 
274
 
 
275
    :param tree: The tree to determine the revision of
 
276
    :type tree: `arch.ArchSourceTree`
 
277
    :return: the latest submit revision merged, or None
 
278
    :rtype: `arch.Revision`
 
279
    """
 
280
    submit_ver = submit_version(tree)
 
281
    if submit_ver is not None:
 
282
        for log in arch_core.iter_new_merges(tree, tree.tree_version, True):
 
283
            if log.revision.version == submit_ver:
 
284
                return log.revision
 
285
    return None
 
286
    
 
287
 
 
288
def comp_revision(tree):
 
289
    """Return the base revision to compare with.  For most trees, this is just
 
290
    the latest revision of the tree version, but for submit trees with submit
 
291
    version merges, it's different.
 
292
    :param tree: The tree to determine the revision of
 
293
    :type tree: `arch.ArchSourceTree`
 
294
    :return: the latest submit revision merged, or None
 
295
    :rtype: `arch.Revision`
 
296
    """
 
297
    submit_rev = submit_revision(tree)
 
298
    if submit_rev is not None:
 
299
        return submit_rev
 
300
    else:
 
301
        try:
 
302
            return arch_compound.tree_latest(tree)
 
303
        except errors.NoVersionRevisions, e:
 
304
            raise errors.CantDetermineRevision("", "This tree shows no commits"
 
305
                                                   " for version %s" % 
 
306
                                                   e.version)
 
307
 
 
308
 
 
309
def log_template_path(tree):
 
310
    """Return the path of the template to use for commit logs, if any.  Tries
 
311
    tree and .arch-params.
 
312
    
 
313
    :param tree: The tree to find the template for
 
314
    :type tree: `arch.WorkingTree`
 
315
    :return: The path or None if no template exists
 
316
    :rtype: str or NoneType
 
317
    """
 
318
    template = tree+"/{arch}/=log-template"
 
319
    if os.path.exists(template):
 
320
        return template
 
321
 
 
322
    else:
 
323
        template = os.path.expanduser("~/.arch-params/=log-template")
 
324
        if os.path.exists(template):
 
325
            return template
 
326
        else:
 
327
            return None
 
328
 
 
329
def version_difference(version, compare_version):
 
330
    """Compares two versions, and returns the part of the name that differs
 
331
    :param version: The version to describe
 
332
    :type version: `arch.Version`
 
333
    :param compare_version: The version to compare to
 
334
    :type compare_version: `arch.Version`
 
335
    """
 
336
    if version.archive == compare_version.archive:
 
337
        p_version = arch.NameParser(version)
 
338
        pc_version = arch.NameParser(compare_version)
 
339
        if p_version.get_category() != pc_version.get_category():
 
340
            return version.nonarch
 
341
        elif p_version.get_branch() != pc_version.get_branch() and\
 
342
            p_version.get_branch() != "":
 
343
            return "%s--%s" % (p_version.get_branch(), 
 
344
                               p_version.get_version())
 
345
        else:
 
346
            return p_version.get_version()
 
347
 
 
348
    elif version.nonarch == compare_version.nonarch:
 
349
        return str(version.archive)
 
350
    else:
 
351
        return str(version)
 
352
 
 
353
 
 
354
def merge_summary(new_merges, tree_version):
 
355
    """Produce a nice summary line for a merge commit log.
 
356
    :param new_merges: iterator of newly-merged logs
 
357
    :type new_merges: iter of `arch.Patchlog`
 
358
    :param tree_version: The tree version to produce a summary for
 
359
    :type tree_version: `arch.Version`
 
360
    :return: the summary, or "" if there were no new merges.
 
361
    :rtype: str
 
362
    """
 
363
    if len(new_merges) == 0:
 
364
        return ""
 
365
    if len(new_merges) == 1:
 
366
        summary = new_merges[0].summary
 
367
    else:
 
368
        summary = "Merge"
 
369
 
 
370
    log_credits = []
 
371
    for merge in new_merges:
 
372
        if arch.my_id() != merge.creator:
 
373
            credit = re.sub("<.*>", "", merge.creator).rstrip(" ");
 
374
        else:
 
375
            credit = version_difference(merge.revision.version, 
 
376
                                        tree_version)
 
377
        if not credit in log_credits:
 
378
            log_credits.append(credit)
 
379
    return ("%s (%s)") % (summary, ", ".join(log_credits))
 
380
    
 
381
# arch-tag: 56d164d9-53ad-40f4-af41-985f505ea8d8