From b5864958558c98ed95f831a9276f3cd33eb8998f Mon Sep 17 00:00:00 2001 From: Jon Turney Date: Mon, 27 May 2024 15:19:00 +0100 Subject: [PATCH] Produce a report to help identify long-inactive maintainers --- calm/maintainers.py | 9 +++ calm/pkg2html.py | 5 +- calm/reports.py | 70 +++++++++++++++++++++- test/testdata/process_arch/htdocs.expected | 7 ++- 4 files changed, 88 insertions(+), 3 deletions(-) diff --git a/calm/maintainers.py b/calm/maintainers.py index e2c60c7..2a02dc0 100644 --- a/calm/maintainers.py +++ b/calm/maintainers.py @@ -33,6 +33,7 @@ # - an email address (in HOME/!email (or !mail), as we don't want to publish # it, and want to allow the maintainer to change it) # - the timestamp when 'ignoring' warnings were last emitted +# - the timestamp of their last ssh connection # import logging @@ -84,6 +85,7 @@ def __init__(self, name, email=None, pkgs=None): self.email = email self.pkgs = pkgs self.quiet = False + self.has_homedir = os.path.isdir(self.homedir()) # the mtime of this file records the timestamp reminder_file = os.path.join(self.homedir(), '!reminder-timestamp') @@ -94,6 +96,13 @@ def __init__(self, name, email=None, pkgs=None): self.reminders_issued = False self.reminders_timestamp_checked = False + # the mtime of this file records the last ssh session + last_seen_file = os.path.join(self.homedir(), '.last-seen') + if os.path.isfile(last_seen_file): + self.last_seen = os.path.getmtime(last_seen_file) + else: + self.last_seen = 0 # meaning 'unknown' + def __repr__(self): return "maintainers.Maintainer('%s', %s, %s)" % (self.name, self.email, self.pkgs) diff --git a/calm/pkg2html.py b/calm/pkg2html.py index 979914b..ab7591a 100755 --- a/calm/pkg2html.py +++ b/calm/pkg2html.py @@ -136,7 +136,10 @@ def ensure_dir_exists(args, path): # format a unix epoch time (UTC) # def tsformat(ts): - return time.strftime('%Y-%m-%d %H:%M', time.gmtime(ts)) + if ts == 0: + return 'Unknown' + else: + return time.strftime('%Y-%m-%d %H:%M', time.gmtime(ts)) # diff --git a/calm/reports.py b/calm/reports.py index 02e9082..240b5d9 100644 --- a/calm/reports.py +++ b/calm/reports.py @@ -69,7 +69,7 @@ def write_report(args, title, body, fn, reportlist, not_empty=True): def linkify(pn, po): - return '{1}'.format(pn, po.orig_name) + return '{1}'.format(po.name, po.orig_name) # @@ -281,6 +281,72 @@ def unstable(args, packages, reportlist): write_report(args, 'Packages marked as unstable', body, 'unstable.html', reportlist) +# produce a report on maintainer (in)activity +# +def maintainer_activity(args, packages, reportlist): + activity_list = [] + + arch = 'x86_64' + # XXX: look into how we can make this 'src', after x86 is dropped + + ml = maintainers.maintainer_list(args) + for m in ml.values(): + if m.name == 'ORPHANED': + continue + + a = types.SimpleNamespace() + a.name = m.name + a.last_seen = m.last_seen + + count = 0 + mtime = 0 + pkgs = [] + for p in m.pkgs: + if not p.is_orphaned(): + count += 1 + + pn = p.data + '-src' + # do something reasonable for sourceless packages + if pn not in packages[arch]: + pn = p.data + + po = packages[arch].get(pn, None) + if po: + pkgs.append(pn) + + for v in po.versions(): + if po.tar(v).mtime > mtime: + mtime = po.tar(v).mtime + + # ignore if all their packages are orphaned + # (key should be already disabled in this case) + if count == 0: + continue + + a.count = count + a.pkgs = pkgs + a.last_package = mtime + + activity_list.append(a) + + body = io.StringIO() + print('

Maintainer activity.

', file=body) + + print('', file=body) + print('', file=body) + + for a in sorted(activity_list, key=lambda i: (i.last_seen, i.last_package)): + def pkg_details(pkgs): + return '
%d%s
' % (len(pkgs), ', '.join(linkify(p, packages[arch][p]) for p in pkgs)) + + print('' % + (a.name, pkg_details(a.pkgs), pkg2html.tsformat(a.last_seen), pkg2html.tsformat(a.last_package)), file=body) + + print('
Maintainer# packagesLast sshLatest package
%s%s%s%s
', file=body) + + write_report(args, 'Maintainer activity', body, 'maintainer_activity.html', reportlist, not_empty=False) + + # produce a report of packages which need rebuilding for the latest major # version version provides # @@ -463,6 +529,8 @@ def do_reports(args, packages): provides_rebuild(args, packages, 'ruby_rebuilds.html', 'ruby', reportlist) python_rebuild(args, packages, 'python_rebuilds.html', reportlist) + maintainer_activity(args, packages, reportlist) + fn = os.path.join(args.htdocs, 'reports_list.inc') with utils.open_amifc(fn) as f: print('