gen-renames.py 3.0 KB

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