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