1*2f1c9601SVegard Nossum#! /usr/bin/env python3 2*2f1c9601SVegard Nossum# SPDX-License-Identifier: GPL-2.0 3*2f1c9601SVegard Nossum# 4*2f1c9601SVegard Nossum# Copyright © 2025, Oracle and/or its affiliates. 5*2f1c9601SVegard Nossum# Author: Vegard Nossum <vegard.nossum@oracle.com> 6*2f1c9601SVegard Nossum 7*2f1c9601SVegard Nossum"""Trawl repository history for renames of Documentation/**.rst files. 8*2f1c9601SVegard Nossum 9*2f1c9601SVegard NossumExample: 10*2f1c9601SVegard Nossum 11*2f1c9601SVegard Nossum tools/docs/gen-renames.py --rev HEAD > Documentation/.renames.txt 12*2f1c9601SVegard Nossum""" 13*2f1c9601SVegard Nossum 14*2f1c9601SVegard Nossumimport argparse 15*2f1c9601SVegard Nossumimport itertools 16*2f1c9601SVegard Nossumimport os 17*2f1c9601SVegard Nossumimport subprocess 18*2f1c9601SVegard Nossumimport sys 19*2f1c9601SVegard Nossum 20*2f1c9601SVegard Nossumparser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) 21*2f1c9601SVegard Nossumparser.add_argument('--rev', default='HEAD', help='generate renames up to this revision') 22*2f1c9601SVegard Nossum 23*2f1c9601SVegard Nossumargs = parser.parse_args() 24*2f1c9601SVegard Nossum 25*2f1c9601SVegard Nossumdef normalize(path): 26*2f1c9601SVegard Nossum prefix = 'Documentation/' 27*2f1c9601SVegard Nossum suffix = '.rst' 28*2f1c9601SVegard Nossum 29*2f1c9601SVegard Nossum assert path.startswith(prefix) 30*2f1c9601SVegard Nossum assert path.endswith(suffix) 31*2f1c9601SVegard Nossum 32*2f1c9601SVegard Nossum return path[len(prefix):-len(suffix)] 33*2f1c9601SVegard Nossum 34*2f1c9601SVegard Nossumclass Name(object): 35*2f1c9601SVegard Nossum def __init__(self, name): 36*2f1c9601SVegard Nossum self.names = [name] 37*2f1c9601SVegard Nossum 38*2f1c9601SVegard Nossum def rename(self, new_name): 39*2f1c9601SVegard Nossum self.names.append(new_name) 40*2f1c9601SVegard Nossum 41*2f1c9601SVegard Nossumnames = { 42*2f1c9601SVegard Nossum} 43*2f1c9601SVegard Nossum 44*2f1c9601SVegard Nossumfor line in subprocess.check_output([ 45*2f1c9601SVegard Nossum 'git', 'log', 46*2f1c9601SVegard Nossum '--reverse', 47*2f1c9601SVegard Nossum '--oneline', 48*2f1c9601SVegard Nossum '--find-renames', 49*2f1c9601SVegard Nossum '--diff-filter=RD', 50*2f1c9601SVegard Nossum '--name-status', 51*2f1c9601SVegard Nossum '--format=commit %H', 52*2f1c9601SVegard Nossum # ~v4.8-ish is when Sphinx/.rst was added in the first place 53*2f1c9601SVegard Nossum f'v4.8..{args.rev}', 54*2f1c9601SVegard Nossum '--', 55*2f1c9601SVegard Nossum 'Documentation/' 56*2f1c9601SVegard Nossum], text=True).splitlines(): 57*2f1c9601SVegard Nossum # rename 58*2f1c9601SVegard Nossum if line.startswith('R'): 59*2f1c9601SVegard Nossum _, old, new = line[1:].split('\t', 2) 60*2f1c9601SVegard Nossum 61*2f1c9601SVegard Nossum if old.endswith('.rst') and new.endswith('.rst'): 62*2f1c9601SVegard Nossum old = normalize(old) 63*2f1c9601SVegard Nossum new = normalize(new) 64*2f1c9601SVegard Nossum 65*2f1c9601SVegard Nossum name = names.get(old) 66*2f1c9601SVegard Nossum if name is None: 67*2f1c9601SVegard Nossum name = Name(old) 68*2f1c9601SVegard Nossum else: 69*2f1c9601SVegard Nossum del names[old] 70*2f1c9601SVegard Nossum 71*2f1c9601SVegard Nossum name.rename(new) 72*2f1c9601SVegard Nossum names[new] = name 73*2f1c9601SVegard Nossum 74*2f1c9601SVegard Nossum continue 75*2f1c9601SVegard Nossum 76*2f1c9601SVegard Nossum # delete 77*2f1c9601SVegard Nossum if line.startswith('D'): 78*2f1c9601SVegard Nossum _, old = line.split('\t', 1) 79*2f1c9601SVegard Nossum 80*2f1c9601SVegard Nossum if old.endswith('.rst'): 81*2f1c9601SVegard Nossum old = normalize(old) 82*2f1c9601SVegard Nossum 83*2f1c9601SVegard Nossum # TODO: we could save added/modified files as well and propose 84*2f1c9601SVegard Nossum # them as alternatives 85*2f1c9601SVegard Nossum name = names.get(old) 86*2f1c9601SVegard Nossum if name is None: 87*2f1c9601SVegard Nossum pass 88*2f1c9601SVegard Nossum else: 89*2f1c9601SVegard Nossum del names[old] 90*2f1c9601SVegard Nossum 91*2f1c9601SVegard Nossum continue 92*2f1c9601SVegard Nossum 93*2f1c9601SVegard Nossum# 94*2f1c9601SVegard Nossum# Get the set of current files so we can sanity check that we aren't 95*2f1c9601SVegard Nossum# redirecting any of those 96*2f1c9601SVegard Nossum# 97*2f1c9601SVegard Nossum 98*2f1c9601SVegard Nossumcurrent_files = set() 99*2f1c9601SVegard Nossumfor line in subprocess.check_output([ 100*2f1c9601SVegard Nossum 'git', 'ls-tree', 101*2f1c9601SVegard Nossum '-r', 102*2f1c9601SVegard Nossum '--name-only', 103*2f1c9601SVegard Nossum args.rev, 104*2f1c9601SVegard Nossum 'Documentation/', 105*2f1c9601SVegard Nossum], text=True).splitlines(): 106*2f1c9601SVegard Nossum if line.endswith('.rst'): 107*2f1c9601SVegard Nossum current_files.add(normalize(line)) 108*2f1c9601SVegard Nossum 109*2f1c9601SVegard Nossum# 110*2f1c9601SVegard Nossum# Format/group/output result 111*2f1c9601SVegard Nossum# 112*2f1c9601SVegard Nossum 113*2f1c9601SVegard Nossumresult = [] 114*2f1c9601SVegard Nossumfor _, v in names.items(): 115*2f1c9601SVegard Nossum old_names = v.names[:-1] 116*2f1c9601SVegard Nossum new_name = v.names[-1] 117*2f1c9601SVegard Nossum 118*2f1c9601SVegard Nossum for old_name in old_names: 119*2f1c9601SVegard Nossum if old_name == new_name: 120*2f1c9601SVegard Nossum # A file was renamed to its new name twice; don't redirect that 121*2f1c9601SVegard Nossum continue 122*2f1c9601SVegard Nossum 123*2f1c9601SVegard Nossum if old_name in current_files: 124*2f1c9601SVegard Nossum # A file was recreated with a former name; don't redirect those 125*2f1c9601SVegard Nossum continue 126*2f1c9601SVegard Nossum 127*2f1c9601SVegard Nossum result.append((old_name, new_name)) 128*2f1c9601SVegard Nossum 129*2f1c9601SVegard Nossumfor old_name, new_name in sorted(result): 130*2f1c9601SVegard Nossum print(f"{old_name} {new_name}") 131