source: soft/build_system/build_system/repsys/tags/V1_6_5/RepSys/log.py @ 1

Last change on this file since 1 was 1, checked in by fasma, 12 years ago

Initial Import from Mandriva's soft revision 224062 and package revision 45733

File size: 12.1 KB
Line 
1#!/usr/bin/python
2from RepSys import Error, config
3from RepSys.svn import SVN
4from RepSys.util import execcmd
5
6from Cheetah.Template import Template
7
8import sys
9import os
10import re
11import time
12import locale
13import glob
14import tempfile
15import shutil
16
17
18locale.setlocale(locale.LC_ALL, "C")
19
20default_template = """
21#for $rel in $releases_by_author
22* $rel.date $rel.author_name <$rel.author_email> $rel.version-$rel.release
23 ##
24 #if not $rel.released
25  (not released yet)
26 #end if
27 #for $rev in $rel.release_revisions
28  #for $line in $rev.lines
29  $line
30  #end for
31 #end for
32
33 #for $author in $rel.authors
34  + $author.name <$author.email>
35  #for $rev in $author.revisions
36    #for $line in $rev.lines
37    $line
38    #end for
39  #end for
40
41 #end for
42#end for
43"""
44
45def getrelease(pkgdirurl, rev=None):
46    """Tries to obtain the version-release of the package for a
47    yet-not-markrelease revision of the package.
48
49    Is here where things should be changed if "automatic release increasing"
50    will be used.
51    """
52    svn = SVN(baseurl=pkgdirurl)
53    tmpdir = tempfile.mktemp()
54    try:
55        pkgname = os.path.basename(pkgdirurl)
56        pkgcurrenturl = os.path.join(pkgdirurl, "current")
57        specurl = os.path.join(pkgcurrenturl, "SPECS")
58        if svn.ls(specurl, noerror=1):
59            svn.export(specurl, tmpdir, rev=rev)
60            found = glob.glob(os.path.join(tmpdir, "*.spec"))
61            if found:
62                specpath = found[0]
63                command = (("rpm -q --qf '%%{VERSION}-%%{RELEASE}\n' "
64                           "--specfile %s") % specpath)
65                status, output = execcmd(command)
66                if status != 0:
67                    raise Error, "Error in command %s: %s" % (command, output)
68                releases = output.split()
69                try:
70                    version, release = releases[0].split("-", 1)
71                except ValueError:
72                    raise Error, "Invalid command output: %s: %s" % \
73                            (command, output)
74                return version, release
75    finally:
76        if os.path.isdir(tmpdir):
77            shutil.rmtree(tmpdir)
78           
79
80class _Revision:
81    lines = []
82    date = None
83    raw_date = None
84    revision = None
85    author_name = None
86    author_email = None
87
88    def __init__(self, **kwargs):
89        self.__dict__.update(kwargs)
90
91
92class _Release(_Revision):
93    version = None
94    release = None
95    revisions = None
96
97    def __init__(self, **kwargs):
98        self.revisions = []
99        _Revision.__init__(self, **kwargs)
100
101
102def format_lines(lines):
103    first = 1
104    entrylines = []
105    perexpr = re.compile(r"([^%])%([^%])")
106    for line in lines:
107        if line:
108            line = perexpr.sub("\\1%%\\2", line)
109            if first:
110                first = 0
111                line = line.lstrip()
112                if line[0] != "-":
113                    nextline = "- " + line
114                else:
115                    nextline = line
116            elif line[0] != " " and line[0] != "-":
117                nextline = "  " + line
118            else:
119                nextline = line
120            if nextline not in entrylines:
121                entrylines.append(nextline)
122    return entrylines
123
124
125class _Author:
126    name = None
127    email = None
128    revisions = None
129
130
131def group_releases_by_author(releases):
132    allauthors = []
133    for release in releases:
134        authors = {}
135        for revision in release.revisions:
136            authors.setdefault(revision.author, []).append(revision)
137
138        # all the mess below is to sort by author and by revision number
139        decorated = []
140        for authorname, revs in authors.iteritems():
141            author = _Author()
142            author.name = revs[0].author_name
143            author.email = revs[0].author_email
144            revdeco = [(r.revision, r) for r in revs]
145            revdeco.sort(reverse=1)
146            author.revisions = [t[1] for t in revdeco]
147            decorated.append((max(revdeco)[0], author))
148
149        decorated.sort(reverse=1)
150        release.authors = [t[1] for t in decorated]
151        # the difference between a released and a not released _Release is
152        # the way the release numbers is obtained. So, when this is a
153        # released, we already have it, but if we don't, we should get de
154        # version/release string using getrelease and then get the first
155        first, release.authors = release.authors[0], release.authors[1:]
156        release.author_name = first.name
157        release.author_email = first.email
158        release.date = first.revisions[0].date
159        release.raw_date = first.revisions[0].raw_date
160        release.release_revisions = first.revisions
161        release.revision = first.revisions[0].revision
162
163    return releases
164           
165
166emailpat = re.compile("(?P<name>.*?)\s*<(?P<email>.*?)>")
167
168
169def make_release(author=None, revision=None, date=None, lines=None,
170        entries=[], released=True, version=None, release=None):
171    rel = _Release()
172    rel.author = author
173    found = emailpat.match(config.get("users", author, author or ""))
174    rel.author_name = (found and found.group("name")) or author
175    rel.author_email = (found and found.group("email")) or author
176    rel.revision = revision
177    rel.version = version
178    rel.release = release
179    rel.date = (date and time.strftime("%a %b %d %Y", date)) or None
180    rel.lines = lines
181    rel.released = released
182    for entry in entries:
183        revision = _Revision()
184        revision.revision = entry.revision
185        revision.lines = format_lines(entry.lines)
186        revision.date = time.strftime("%a %b %d %Y", entry.date)
187        revision.raw_date = entry.date
188        revision.author = entry.author
189        found = emailpat.match(config.get("users", entry.author, entry.author))
190        revision.author_name = ((found and found.group("name")) or
191                entry.author)
192        revision.author_email = ((found and found.group("email")) or
193                entry.author)
194        rel.revisions.append(revision)
195    return rel
196
197
198def dump_file(releases, template=None):
199   
200    templpath = template or config.get("template", "path", None)
201    params = {}
202    if templpath is None or not os.path.exists(templpath):
203        params["source"] = default_template
204        sys.stderr.write("warning: %s not found. using built-in template.\n"%
205                templpath)
206    else:
207        params["file"] = templpath
208    releases_author = group_releases_by_author(releases)
209    params["searchList"] = [{"releases_by_author" : releases_author,
210                             "releases" : releases}]
211    t = Template(**params)
212    return repr(t)
213
214
215class InvalidEntryError(Exception):
216    pass
217
218def parse_repsys_entry(revlog):
219    # parse entries in the format:
220    # %repsys <operation>
221    # key: value
222    # ..
223    # <newline>
224    # <comments>
225    #
226    if len(revlog.lines) == 0 or not revlog.lines[0].startswith("%repsys"):
227        raise InvalidEntryError
228    try:       
229        data = {"operation" : revlog.lines[0].split()[1]}
230    except IndexError:
231        raise InvalidEntryError
232    for line in revlog.lines[1:]:
233        if not line:
234            break
235        try:
236            key, value = line.split(":", 1)
237        except ValueError:
238            raise InvalidEntryError
239        data[key.strip().lower()] = value.strip() # ???
240    return data
241       
242
243def get_revision_offset():
244    try:
245        revoffset = config.getint("log", "revision-offset", 0)
246    except (ValueError, TypeError):
247        raise Error, ("Invalid revision-offset number in configuration "
248                      "file(s).")
249    return revoffset or 0
250
251
252def svn2rpm(pkgdirurl, rev=None, size=None, submit=False, template=None):
253    concat = config.get("log", "concat", "").split()
254    revoffset = get_revision_offset()
255    svn = SVN(baseurl=pkgdirurl)
256    pkgreleasesurl = os.path.join(pkgdirurl, "releases")
257    pkgcurrenturl = os.path.join(pkgdirurl, "current")
258    releaseslog = svn.log(pkgreleasesurl, noerror=1)
259    currentlog = svn.log(pkgcurrenturl, limit=size, start=rev,
260            end=revoffset)
261    lastauthor = None
262    previous_revision = 0
263    currelease = None
264    releases = []
265
266    # for the emergency bug fixer: the [].sort() is done using the
267    # decorate-sort-undecorate pattern
268    releases_data = []
269    if releaseslog:
270        for relentry in releaseslog[::-1]:
271            try:
272                revinfo = parse_repsys_entry(relentry)
273            except InvalidEntryError:
274                continue
275            try:
276                release_number = int(revinfo["revision"])
277            except (KeyError, ValueError):
278                raise Error, "Error parsing data from log entry from r%s" % \
279                                relentry.revision 
280            releases_data.append((release_number, relentry, revinfo))
281    releases_data.sort()
282
283    for release_number, relentry, revinfo in releases_data:
284        try:
285            revinfo = parse_repsys_entry(relentry)
286        except InvalidEntryError:
287            continue
288
289        try:
290            release_revision = int(revinfo["revision"])
291        except (ValueError, KeyError):
292            raise Error, "Error parsing data from log entry from r%s" % \
293                            relentry.revision 
294       
295        # get entries newer than 'previous' and older than 'relentry'
296        entries = [entry for entry in currentlog
297                    if release_revision >= entry.revision and
298                      (previous_revision < entry.revision)]
299        if not entries:
300            #XXX probably a forced release, without commits in current/,
301            # check if this is the right behavior and if some release is
302            # not being lost.
303            continue
304
305        release = make_release(author=relentry.author,
306                revision=relentry.revision, date=relentry.date,
307                lines=relentry.lines, entries=entries,
308                version=revinfo["version"], release=revinfo["release"])
309        releases.append(release)
310        previous_revision = release_revision
311
312    # look for commits that have been not submited (released) yet
313    # this is done by getting all log entries newer (revision larger)
314    # than releaseslog[0]
315    if releaseslog:
316        latest_revision = releaseslog[0].revision
317        notsubmitted = [entry for entry in currentlog
318                        if entry.revision > latest_revision]
319        if notsubmitted:
320            # if they are not submitted yet, what we have to do is to add
321            # a release/version number from getrelease()
322            version, release = getrelease(pkgdirurl)
323            toprelease = make_release(entries=notsubmitted, released=False,
324                            version=version, release=release)
325            releases.append(toprelease)
326
327    data = dump_file(releases[::-1], template=template)
328    return data
329
330
331
332def specfile_svn2rpm(pkgdirurl, specfile, rev=None, size=None,
333        submit=False, template=None):
334    newlines = []
335    found = 0
336   
337    # Strip old changelogs
338    for line in open(specfile):
339        if line.startswith("%changelog"):
340            found = 1
341        elif not found:
342            newlines.append(line)
343        elif line.startswith("%"):
344            found = 0
345            newlines.append(line)
346
347    # Create new changelog
348    newlines.append("\n\n%changelog\n")
349    newlines.append(svn2rpm(pkgdirurl, rev=rev, size=size, submit=submit,
350        template=template))
351
352    # Merge old changelog, if available
353    oldurl = config.get("log", "oldurl")
354    if oldurl:
355        svn = SVN(baseurl=pkgdirurl)
356        tmpdir = tempfile.mktemp()
357        try:
358            pkgname = os.path.basename(pkgdirurl)
359            pkgoldurl = os.path.join(oldurl, pkgname)
360            if svn.ls(pkgoldurl, noerror=1):
361                svn.export(pkgoldurl, tmpdir, rev=rev)
362                logfile = os.path.join(tmpdir, "log")
363                if os.path.isfile(logfile):
364                    file = open(logfile)
365                    newlines.append("\n")
366                    newlines.append(file.read())
367                    file.close()
368        finally:
369            if os.path.isdir(tmpdir):
370                shutil.rmtree(tmpdir)
371
372    # Write new specfile
373    file = open(specfile, "w")
374    file.write("".join(newlines))
375    file.close()
376
377
378if __name__ == "__main__":
379    l = svn2rpm(sys.argv[1])
380    print l
381
382# vim:et:ts=4:sw=4
Note: See TracBrowser for help on using the repository browser.