~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to fai/pylon/ancillary.py

  • Committer: Aaron Bentley
  • Date: 2006-06-27 14:36:32 UTC
  • Revision ID: abentley@panoramicfeedback.com-20060627143632-0f4114d7b0a8d7d9
Fix zap for checkouts of branches with no parents

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