From 7bc3eef7cd21fa3ea49e699e58b6040b60e9691e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sat, 2 Oct 2010 22:57:16 +0200 Subject: [PATCH] Add support for 'egencache --update-changelogs'. --- bin/egencache | 145 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 141 insertions(+), 4 deletions(-) diff --git a/bin/egencache b/bin/egencache index a16ac4f93..48350e713 100755 --- a/bin/egencache +++ b/bin/egencache @@ -23,6 +23,9 @@ except KeyboardInterrupt: import codecs import logging import optparse +import subprocess +import time +import textwrap try: import portage @@ -31,7 +34,7 @@ except ImportError: sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")) import portage -from portage import os, _encodings, _unicode_encode +from portage import os, _encodings, _unicode_encode, _unicode_decode from _emerge.MetadataRegen import MetadataRegen from portage.cache.cache_errors import CacheError, StatCollision from portage.util import cmp_sort_key, writemsg_level @@ -47,6 +50,8 @@ else: from repoman.utilities import parse_metadata_use from xml.parsers.expat import ExpatError +from repoman.utilities import FindVCS + if sys.hexversion >= 0x3000000: long = int @@ -61,6 +66,9 @@ def parse_args(args): actions.add_option("--update-use-local-desc", action="store_true", help="update the use.local.desc file from metadata.xml") + actions.add_option("--update-changelogs", + action="store_true", + help="update the ChangeLog files from SCM logs") parser.add_option_group(actions) common = optparse.OptionGroup(parser, 'Common options') @@ -454,6 +462,130 @@ class GenUseLocalDesc(object): output.close() +class GenChangeLogs(object): + def __init__(self, portdb): + self.returncode = os.EX_OK + self._portdb = portdb + self._wrapper = textwrap.TextWrapper( + width = 78, + initial_indent = ' ', + subsequent_indent = ' ' + ) + + def generate_changelog(self, cp): + try: + output = codecs.open('ChangeLog', + mode='w', encoding=_encodings['repo.content'], + errors='replace') + except IOError as e: + writemsg_level( + "ERROR: failed to open ChangeLog for %s: %s\n" % (cp,e,), + level=logging.ERROR, noiselevel=-1) + self.returncode |= 2 + return + + output.write((''' +# ChangeLog for %s +# Copyright 1999-%s Gentoo Foundation; Distributed under the GPL v2 +# $Header: $ + +''' % (cp, time.strftime('%Y'))).lstrip()) + + def grab(cmd): + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + return _unicode_decode(p.communicate()[0], + encoding=_encodings['stdio'], errors='strict') + + # now grab all the commits + commits = grab(['git', 'rev-list', 'HEAD', '--', '.']).split() + + for c in commits: + # Explaining the arguments: + # --name-status to get a list of added/removed files + # --no-renames to avoid getting more complex records on the list + # --format to get the timestamp, author and commit description + # --root to make it work fine even with the initial commit + # --relative to get paths relative to ebuilddir + # -r (recursive) to get per-file changes + # then the commit-id and path. + + cinfo = grab(['git', 'diff-tree', '--name-status', '--no-renames', + '--format=%ct %cN <%cE>%n%B', '--root', '--relative', '-r', + c, '--', '.']).rstrip('\n').split('\n') + + # Expected output: + # timestamp Author Name + # commit message l1 + # ... + # commit message ln + # + # status1 filename1 + # ... + # statusn filenamen + + changed = [] + for n, l in enumerate(reversed(cinfo)): + if not l: + body = cinfo[1:-n] + break + else: + f = l.split() + if f[1] == 'Manifest': + pass # XXX: remanifest commits? + elif f[1] == 'ChangeLog': + pass + elif f[0].startswith('A'): + changed.append('+%s' % f[1]) + elif f[0].startswith('D'): + changed.append('-%s' % f[1]) + elif f[0].startswith('M'): + changed.append(f[1]) + else: + writemsg_level( + "ERROR: unexpected git file status for %s: %s\n" % (cp,f,), + level=logging.ERROR, noiselevel=-1) + self.returncode |= 1 + + if not changed: + continue + + (ts, author) = cinfo[0].split(' ', 1) + date = time.strftime('%d %b %Y', time.gmtime(float(ts))) + + # XXX: sort changes (ebuilds should go by PV) + + wroteheader = False + for c in changed: + if c.startswith('+') and c.endswith('.ebuild'): + output.write('*%s (%s)\n' % (c[1:-7], date)) + wroteheader = True + if wroteheader: + output.write('\n') + + # XXX: strip ': ' and '[] ' from the commit message + # XXX: strip the repoman suffix note from the commit message + + body.insert(0, '%s; %s %s:' % (date, author, ', '.join(changed))) + output.write('\n'.join([self._wrapper.fill(x) for x in body])) + + output.close() + + def run(self): + repo_path = self._portdb.porttrees[0] + os.chdir(repo_path) + if 'git' not in FindVCS(): + writemsg_level( + "ERROR: --update-changelogs supported only in git repos\n", + level=logging.ERROR, noiselevel=-1) + self.returncode = 127 + return + + for cp in self._portdb.cp_all(): + os.chdir(os.path.join(repo_path, cp)) + # XXX: support checking somehow whether the ChangeLog is up-to-date. + if 1: + self.generate_changelog(cp) + def egencache_main(args): parser, options, atoms = parse_args(args) @@ -493,9 +625,9 @@ def egencache_main(args): settings = portage.config(config_root=config_root, target_root='/', local_config=False, env=env) - if not options.update and not options.update_use_local_desc: - parser.error('No action specified (--update ' + \ - 'and/or --update-use-local-desc)') + if not options.update and not options.update_use_local_desc \ + and not options.update_changelogs: + parser.error('No action specified') return 1 if options.update and 'metadata-transfer' not in settings.features: @@ -538,6 +670,11 @@ def egencache_main(args): gen_desc.run() ret.append(gen_desc.returncode) + if options.update_changelogs: + gen_clogs = GenChangeLogs(portdb) + gen_clogs.run() + ret.append(gen_clogs.returncode) + if options.tolerant: return ret[0] return max(ret) -- 2.26.2