source: soft/build_system/build_system/repsys/tags/V1_6_12/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: 14.0 KB
Line 
1#!/usr/bin/python
2from RepSys import Error, config, RepSysTree
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 = RepSysTree.pkgname(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 2>/dev/null") % 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        line = line.lstrip()
108        if line:
109            line = perexpr.sub("\\1%%\\2", line)
110            if first:
111                first = 0
112                line = line.lstrip()
113                if line[0] != "-":
114                    nextline = "- " + line
115                else:
116                    nextline = line
117            elif line[0] != " " and line[0] != "-":
118                nextline = "  " + line
119            else:
120                nextline = line
121            if nextline not in entrylines:
122                entrylines.append(nextline)
123    return entrylines
124
125
126class _Author:
127    name = None
128    email = None
129    revisions = None
130
131
132def group_releases_by_author(releases):
133    allauthors = []
134    for release in releases:
135        authors = {}
136        for revision in release.revisions:
137            authors.setdefault(revision.author, []).append(revision)
138
139        # all the mess below is to sort by author and by revision number
140        decorated = []
141        for authorname, revs in authors.iteritems():
142            author = _Author()
143            author.name = revs[0].author_name
144            author.email = revs[0].author_email
145            revdeco = [(r.revision, r) for r in revs]
146            revdeco.sort(reverse=1)
147            author.revisions = [t[1] for t in revdeco]
148            decorated.append((max(revdeco)[0], author))
149
150        decorated.sort(reverse=1)
151        release.authors = [t[1] for t in decorated]
152        # the difference between a released and a not released _Release is
153        # the way the release numbers is obtained. So, when this is a
154        # released, we already have it, but if we don't, we should get de
155        # version/release string using getrelease and then get the first
156        first, release.authors = release.authors[0], release.authors[1:]
157        release.author_name = first.name
158        release.author_email = first.email
159        release.date = first.revisions[0].date
160        release.raw_date = first.revisions[0].raw_date
161        release.release_revisions = first.revisions
162        release.revision = first.revisions[0].revision
163
164    return releases
165
166
167def group_revisions_by_author(currentlog):
168    revisions = []
169    last_author = None
170    for entry in currentlog:
171        revision = _Revision()
172        revision.lines = format_lines(entry.lines)
173        revision.raw_date = entry.date
174        revision.date = parse_raw_date(entry.date)
175        revision.revision = entry.revision
176        if entry.author == last_author:
177            revisions[-1].revisions.append(revision)
178        else:
179            author = _Author()
180            author.name, author.email = get_author_name(entry.author)
181            author.revisions = [revision]
182            revisions.append(author)
183        last_author = entry.author
184    return revisions
185
186
187emailpat = re.compile("(?P<name>.*?)\s*<(?P<email>.*?)>")
188
189def get_author_name(author):
190    found = emailpat.match(config.get("users", author, author))
191    name = ((found and found.group("name")) or author)
192    email = ((found and found.group("email")) or author)
193    return name, email
194
195def parse_raw_date(rawdate):
196    return time.strftime("%a %b %d %Y", rawdate)
197
198def filter_log_lines(lines):
199    # lines in commit messages containing SILENT at any position will be
200    # skipped; commits with their log messages beggining with SILENT in the
201    # first positionj of the first line will have all lines ignored.
202    ignstr = config.get("log", "ignore-string", "SILENT")
203    if len(lines) and lines[0].startswith(ignstr):
204        return []
205    filtered = [line for line in lines if ignstr not in line]
206    return filtered
207
208
209def make_release(author=None, revision=None, date=None, lines=None,
210        entries=[], released=True, version=None, release=None):
211    rel = _Release()
212    rel.author = author
213    if author:
214        rel.author_name, rel.author_email = get_author_name(author)
215    rel.revision = revision
216    rel.version = version
217    rel.release = release
218    rel.date = (date and parse_raw_date(date)) or None
219    rel.lines = lines
220    rel.released = released
221    for entry in entries:
222        lines = filter_log_lines(entry.lines)
223        revision = _Revision()
224        revision.revision = entry.revision
225        revision.lines = format_lines(lines)
226        revision.date = parse_raw_date(entry.date)
227        revision.raw_date = entry.date
228        revision.author = entry.author
229        (revision.author_name, revision.author_email) = \
230                get_author_name(entry.author)
231        rel.revisions.append(revision)
232    return rel
233
234
235def dump_file(releases, currentlog=None, template=None):
236    templpath = template or config.get("template", "path", None)
237    params = {}
238    if templpath is None or not os.path.exists(templpath):
239        params["source"] = default_template
240        sys.stderr.write("warning: %s not found. using built-in template.\n"%
241                templpath)
242    else:
243        params["file"] = templpath
244    releases_author = group_releases_by_author(releases)
245    revisions_author = group_revisions_by_author(currentlog)
246    params["searchList"] = [{"releases_by_author" : releases_author,
247                             "releases" : releases,
248                             "revisions_by_author": revisions_author}]
249    t = Template(**params)
250    return repr(t)
251
252
253class InvalidEntryError(Exception):
254    pass
255
256def parse_repsys_entry(revlog):
257    # parse entries in the format:
258    # %repsys <operation>
259    # key: value
260    # ..
261    # <newline>
262    # <comments>
263    #
264    if len(revlog.lines) == 0 or not revlog.lines[0].startswith("%repsys"):
265        raise InvalidEntryError
266    try:       
267        data = {"operation" : revlog.lines[0].split()[1]}
268    except IndexError:
269        raise InvalidEntryError
270    for line in revlog.lines[1:]:
271        if not line:
272            break
273        try:
274            key, value = line.split(":", 1)
275        except ValueError:
276            raise InvalidEntryError
277        data[key.strip().lower()] = value.strip() # ???
278    return data
279       
280
281def get_revision_offset():
282    try:
283        revoffset = config.getint("log", "revision-offset", 0)
284    except (ValueError, TypeError):
285        raise Error, ("Invalid revision-offset number in configuration "
286                      "file(s).")
287    return revoffset or 0
288
289oldmsgpat = re.compile(
290        r"Copying release (?P<rel>[^\s]+) to (?P<dir>[^\s]+) directory\.")
291
292def parse_markrelease_log(relentry):
293    if not ((relentry.lines and oldmsgpat.match(relentry.lines[0]) \
294            or parse_repsys_entry(relentry))):
295        raise InvalidEntryError
296    from_rev = None
297    path = None
298    for changed in relentry.changed:
299        if changed["action"] == "A" and changed["from_rev"]:
300            from_rev = changed["from_rev"]
301            path = changed["path"]
302            break
303    else:
304        raise InvalidEntryError
305    # get the version and release from the names in the path, do not relay
306    # on log messages
307    version, release = path.rsplit(os.path.sep, 3)[-2:]
308    return version, release, from_rev
309
310
311def svn2rpm(pkgdirurl, rev=None, size=None, submit=False, template=None):
312    concat = config.get("log", "concat", "").split()
313    revoffset = get_revision_offset()
314    svn = SVN(baseurl=pkgdirurl)
315    pkgreleasesurl = os.path.join(pkgdirurl, "releases")
316    pkgcurrenturl = os.path.join(pkgdirurl, "current")
317    releaseslog = svn.log(pkgreleasesurl, noerror=1)
318    currentlog = svn.log(pkgcurrenturl, limit=size, start=rev,
319            end=revoffset)
320
321    # sort releases by copyfrom-revision, so that markreleases for same
322    # revisions won't look empty
323    releasesdata = []
324    if releaseslog:
325        for relentry in releaseslog[::-1]:
326            try:
327                (version, release, relrevision) = \
328                        parse_markrelease_log(relentry)
329            except InvalidEntryError:
330                continue
331            releasesdata.append((relrevision, -relentry.revision, relentry, 
332                version, release))
333        releasesdata.sort()
334
335    # collect valid releases using the versions provided by the changes and
336    # the packages
337    prevrevision = 0
338    releases = []
339    for (relrevision, dummy, relentry, version, release) in releasesdata:
340        if prevrevision == relrevision: 
341            # ignore older markrelease of the same revision, since they
342            # will have no history
343            continue
344        entries = [entry for entry in currentlog
345                    if relrevision >= entry.revision and
346                      (prevrevision < entry.revision)]
347        if not entries:
348            #XXX probably a forced release, without commits in current/,
349            # check if this is the right behavior
350            sys.stderr.write("warning: skipping (possible) release "
351                    "%s-%s@%s, no commits since previous markrelease (r%r)\n" %
352                    (version, release, relrevision, prevrevision))
353            continue
354
355        release = make_release(author=relentry.author,
356                revision=relentry.revision, date=relentry.date,
357                lines=relentry.lines, entries=entries,
358                version=version, release=release)
359        releases.append(release)
360        prevrevision = relrevision
361           
362    # look for commits that have been not submited (released) yet
363    # this is done by getting all log entries newer (revision larger)
364    # than releaseslog[0] (in the case it exists)
365    if releaseslog:
366        latest_revision = releaseslog[0].revision
367    else:
368        latest_revision = 0
369    notsubmitted = [entry for entry in currentlog
370                    if entry.revision > latest_revision]
371    if notsubmitted:
372        # if they are not submitted yet, what we have to do is to add
373        # a release/version number from getrelease()
374        version, release = getrelease(pkgdirurl)
375        toprelease = make_release(entries=notsubmitted, released=False,
376                        version=version, release=release)
377        releases.append(toprelease)
378
379    data = dump_file(releases[::-1], currentlog=currentlog, template=template)
380    return data
381
382
383
384def specfile_svn2rpm(pkgdirurl, specfile, rev=None, size=None,
385        submit=False, template=None):
386    newlines = []
387    found = 0
388   
389    # Strip old changelogs
390    for line in open(specfile):
391        if line.startswith("%changelog"):
392            found = 1
393        elif not found:
394            newlines.append(line)
395        elif line.startswith("%"):
396            found = 0
397            newlines.append(line)
398
399    # Create new changelog
400    newlines.append("\n\n%changelog\n")
401    newlines.append(svn2rpm(pkgdirurl, rev=rev, size=size, submit=submit,
402        template=template))
403
404    # Merge old changelog, if available
405    oldurl = config.get("log", "oldurl")
406    if oldurl:
407        svn = SVN(baseurl=pkgdirurl)
408        tmpdir = tempfile.mktemp()
409        try:
410            pkgname = RepSysTree.pkgname(pkgdirurl)
411            pkgoldurl = os.path.join(oldurl, pkgname)
412            if svn.ls(pkgoldurl, noerror=1):
413                svn.export(pkgoldurl, tmpdir, rev=rev)
414                logfile = os.path.join(tmpdir, "log")
415                if os.path.isfile(logfile):
416                    file = open(logfile)
417                    newlines.append("\n")
418                    newlines.append(file.read())
419                    file.close()
420        finally:
421            if os.path.isdir(tmpdir):
422                shutil.rmtree(tmpdir)
423
424    # Write new specfile
425    file = open(specfile, "w")
426    file.write("".join(newlines))
427    file.close()
428
429
430if __name__ == "__main__":
431    l = svn2rpm(sys.argv[1])
432    print l
433
434# vim:et:ts=4:sw=4
Note: See TracBrowser for help on using the repository browser.