xref: /linux/tools/docs/gen-renames.py (revision 6093a688a07da07808f0122f9aa2a3eed250d853)
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