import codecs
import logging
import optparse
+import subprocess
+import time
+import textwrap
try:
import portage
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
from repoman.utilities import parse_metadata_use
from xml.parsers.expat import ExpatError
+from repoman.utilities import FindVCS
+
if sys.hexversion >= 0x3000000:
long = int
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')
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 <author@email>
+ # 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 '<CP>: ' and '[<CP>] ' 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)
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:
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)