#!/usr/bin/env python3 """Helper for generating GNU ChangeLog entries.""" import argparse import datetime import importlib.machinery import os from pathlib import Path import subprocess import sys import unidiff _loader = lambda *args: importlib.machinery.SourceFileLoader(*args).load_module() mklog = _loader('mklog', '/usr/local/src/gnu/gcc/contrib/mklog.py') del _loader NAME = 'Mike Frysinger ' DATE = datetime.datetime.now().strftime('%Y-%m-%d') def get_parser(): """Get CLI parser.""" parser = argparse.ArgumentParser(description=__doc__) return parser def main(argv): """The main entry point for scripts.""" parser = get_parser() opts = parser.parse_args(argv) # Get the patchset from git. full_diff = subprocess.run( ['git', 'log', '-p', '--relative', '-1'], check=True, capture_output=True, encoding='utf-8').stdout # Filter out entries we don't want. patchset = unidiff.PatchSet(full_diff) i = 0 while i < len(patchset): if Path(patchset[i].path).name.startswith('ChangeLog'): patchset.pop(i) else: i += 1 # List of files that are always generated. generated_files = {'aclocal.m4', 'config.in', 'configure'} # Move the generated patches to the end of the patchset so the generated # ChangeLog lists them at the end. generated_patches = [] i = 0 while i < len(patchset): if Path(patchset[i].path).name in generated_files: generated_patches.append(patchset.pop(i)) else: i += 1 patchset.extend(generated_patches) # Find the ChangeLog for each path. all_dirs = {Path(x.path).parent for x in patchset} dirs_to_logs = {} for index_dir in all_dirs: d = index_dir while True: log = d / 'ChangeLog' if log.is_file(): break d = d.parent dirs_to_logs[index_dir] = log # Group the patches based on the ChangeLogs they'll go into. logs_to_patches = {} for pfile in patchset: log = dirs_to_logs[Path(pfile.path).parent] ps = logs_to_patches.setdefault(log, unidiff.PatchSet('')) ps.append(pfile) # Now generate the logs for each subdir. for log, ps in logs_to_patches.items(): new_log = mklog.generate_changelog(str(ps)) new_log = '\n'.join(new_log.splitlines()[2:]).rstrip() # Hack: If we rebased the changes to a subdir, strip the path down. # e.g. bfin/foo.c goes into bfin/ChangeLog, so drop bfin/ prefix. for pfile in ps: relpath = os.path.relpath(pfile.path, log.parent) if relpath != pfile.path: new_log = new_log.replace(pfile.path, relpath) # Read the old ChangeLog file and strip spurious whitespace. # Specify the whitespace to strip explciitly as we want to leave the # \f (^L) characters alone. with open(log, encoding='utf-8') as fp: old_log = '\n'.join(x.rstrip(' \t\r') for x in fp.read().strip().split('\n')) # Now update the ChangeLog file with the new entry at top. with open(log, 'w', encoding='utf-8') as fp: fp.write(f'{DATE} {NAME}\n\n') fp.write(new_log) fp.write('\n\n') fp.write(old_log) fp.write('\n') if __name__ == '__main__': sys.exit(main(sys.argv[1:]))