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