drm/panthor: Don't add write fences to the shared BOs
[drm/drm-misc.git] / scripts / checktransupdate.py
blob578c3fecfdfdcd639ef64a9c0cb836ff0c703998
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: GPL-2.0
4 """
5 This script helps track the translation status of the documentation
6 in different locales, e.g., zh_CN. More specially, it uses `git log`
7 commit to find the latest english commit from the translation commit
8 (order by author date) and the latest english commits from HEAD. If
9 differences occur, report the file and commits that need to be updated.
11 The usage is as follows:
12 - ./scripts/checktransupdate.py -l zh_CN
13 This will print all the files that need to be updated or translated in the zh_CN locale.
14 - ./scripts/checktransupdate.py Documentation/translations/zh_CN/dev-tools/testing-overview.rst
15 This will only print the status of the specified file.
17 The output is something like:
18 Documentation/dev-tools/kfence.rst
19 No translation in the locale of zh_CN
21 Documentation/translations/zh_CN/dev-tools/testing-overview.rst
22 commit 42fb9cfd5b18 ("Documentation: dev-tools: Add link to RV docs")
23 1 commits needs resolving in total
24 """
26 import os
27 import time
28 import logging
29 from argparse import ArgumentParser, ArgumentTypeError, BooleanOptionalAction
30 from datetime import datetime
33 def get_origin_path(file_path):
34 """Get the origin path from the translation path"""
35 paths = file_path.split("/")
36 tidx = paths.index("translations")
37 opaths = paths[:tidx]
38 opaths += paths[tidx + 2 :]
39 return "/".join(opaths)
42 def get_latest_commit_from(file_path, commit):
43 """Get the latest commit from the specified commit for the specified file"""
44 command = f"git log --pretty=format:%H%n%aD%n%cD%n%n%B {commit} -1 -- {file_path}"
45 logging.debug(command)
46 pipe = os.popen(command)
47 result = pipe.read()
48 result = result.split("\n")
49 if len(result) <= 1:
50 return None
52 logging.debug("Result: %s", result[0])
54 return {
55 "hash": result[0],
56 "author_date": datetime.strptime(result[1], "%a, %d %b %Y %H:%M:%S %z"),
57 "commit_date": datetime.strptime(result[2], "%a, %d %b %Y %H:%M:%S %z"),
58 "message": result[4:],
62 def get_origin_from_trans(origin_path, t_from_head):
63 """Get the latest origin commit from the translation commit"""
64 o_from_t = get_latest_commit_from(origin_path, t_from_head["hash"])
65 while o_from_t is not None and o_from_t["author_date"] > t_from_head["author_date"]:
66 o_from_t = get_latest_commit_from(origin_path, o_from_t["hash"] + "^")
67 if o_from_t is not None:
68 logging.debug("tracked origin commit id: %s", o_from_t["hash"])
69 return o_from_t
72 def get_commits_count_between(opath, commit1, commit2):
73 """Get the commits count between two commits for the specified file"""
74 command = f"git log --pretty=format:%H {commit1}...{commit2} -- {opath}"
75 logging.debug(command)
76 pipe = os.popen(command)
77 result = pipe.read().split("\n")
78 # filter out empty lines
79 result = list(filter(lambda x: x != "", result))
80 return result
83 def pretty_output(commit):
84 """Pretty print the commit message"""
85 command = f"git log --pretty='format:%h (\"%s\")' -1 {commit}"
86 logging.debug(command)
87 pipe = os.popen(command)
88 return pipe.read()
91 def valid_commit(commit):
92 """Check if the commit is valid or not"""
93 msg = pretty_output(commit)
94 return "Merge tag" not in msg
96 def check_per_file(file_path):
97 """Check the translation status for the specified file"""
98 opath = get_origin_path(file_path)
100 if not os.path.isfile(opath):
101 logging.error("Cannot find the origin path for {file_path}")
102 return
104 o_from_head = get_latest_commit_from(opath, "HEAD")
105 t_from_head = get_latest_commit_from(file_path, "HEAD")
107 if o_from_head is None or t_from_head is None:
108 logging.error("Cannot find the latest commit for %s", file_path)
109 return
111 o_from_t = get_origin_from_trans(opath, t_from_head)
113 if o_from_t is None:
114 logging.error("Error: Cannot find the latest origin commit for %s", file_path)
115 return
117 if o_from_head["hash"] == o_from_t["hash"]:
118 logging.debug("No update needed for %s", file_path)
119 else:
120 logging.info(file_path)
121 commits = get_commits_count_between(
122 opath, o_from_t["hash"], o_from_head["hash"]
124 count = 0
125 for commit in commits:
126 if valid_commit(commit):
127 logging.info("commit %s", pretty_output(commit))
128 count += 1
129 logging.info("%d commits needs resolving in total\n", count)
132 def valid_locales(locale):
133 """Check if the locale is valid or not"""
134 script_path = os.path.dirname(os.path.abspath(__file__))
135 linux_path = os.path.join(script_path, "..")
136 if not os.path.isdir(f"{linux_path}/Documentation/translations/{locale}"):
137 raise ArgumentTypeError("Invalid locale: {locale}")
138 return locale
141 def list_files_with_excluding_folders(folder, exclude_folders, include_suffix):
142 """List all files with the specified suffix in the folder and its subfolders"""
143 files = []
144 stack = [folder]
146 while stack:
147 pwd = stack.pop()
148 # filter out the exclude folders
149 if os.path.basename(pwd) in exclude_folders:
150 continue
151 # list all files and folders
152 for item in os.listdir(pwd):
153 ab_item = os.path.join(pwd, item)
154 if os.path.isdir(ab_item):
155 stack.append(ab_item)
156 else:
157 if ab_item.endswith(include_suffix):
158 files.append(ab_item)
160 return files
163 class DmesgFormatter(logging.Formatter):
164 """Custom dmesg logging formatter"""
165 def format(self, record):
166 timestamp = time.time()
167 formatted_time = f"[{timestamp:>10.6f}]"
168 log_message = f"{formatted_time} {record.getMessage()}"
169 return log_message
172 def config_logging(log_level, log_file="checktransupdate.log"):
173 """configure logging based on the log level"""
174 # set up the root logger
175 logger = logging.getLogger()
176 logger.setLevel(log_level)
178 # Create console handler
179 console_handler = logging.StreamHandler()
180 console_handler.setLevel(log_level)
182 # Create file handler
183 file_handler = logging.FileHandler(log_file)
184 file_handler.setLevel(log_level)
186 # Create formatter and add it to the handlers
187 formatter = DmesgFormatter()
188 console_handler.setFormatter(formatter)
189 file_handler.setFormatter(formatter)
191 # Add the handler to the logger
192 logger.addHandler(console_handler)
193 logger.addHandler(file_handler)
196 def main():
197 """Main function of the script"""
198 script_path = os.path.dirname(os.path.abspath(__file__))
199 linux_path = os.path.join(script_path, "..")
201 parser = ArgumentParser(description="Check the translation update")
202 parser.add_argument(
203 "-l",
204 "--locale",
205 default="zh_CN",
206 type=valid_locales,
207 help="Locale to check when files are not specified",
210 parser.add_argument(
211 "--print-missing-translations",
212 action=BooleanOptionalAction,
213 default=True,
214 help="Print files that do not have translations",
217 parser.add_argument(
218 '--log',
219 default='INFO',
220 choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
221 help='Set the logging level')
223 parser.add_argument(
224 '--logfile',
225 default='checktransupdate.log',
226 help='Set the logging file (default: checktransupdate.log)')
228 parser.add_argument(
229 "files", nargs="*", help="Files to check, if not specified, check all files"
231 args = parser.parse_args()
233 # Configure logging based on the --log argument
234 log_level = getattr(logging, args.log.upper(), logging.INFO)
235 config_logging(log_level)
237 # Get files related to linux path
238 files = args.files
239 if len(files) == 0:
240 offical_files = list_files_with_excluding_folders(
241 os.path.join(linux_path, "Documentation"), ["translations", "output"], "rst"
244 for file in offical_files:
245 # split the path into parts
246 path_parts = file.split(os.sep)
247 # find the index of the "Documentation" directory
248 kindex = path_parts.index("Documentation")
249 # insert the translations and locale after the Documentation directory
250 new_path_parts = path_parts[:kindex + 1] + ["translations", args.locale] \
251 + path_parts[kindex + 1 :]
252 # join the path parts back together
253 new_file = os.sep.join(new_path_parts)
254 if os.path.isfile(new_file):
255 files.append(new_file)
256 else:
257 if args.print_missing_translations:
258 logging.info(os.path.relpath(os.path.abspath(file), linux_path))
259 logging.info("No translation in the locale of %s\n", args.locale)
261 files = list(map(lambda x: os.path.relpath(os.path.abspath(x), linux_path), files))
263 # cd to linux root directory
264 os.chdir(linux_path)
266 for file in files:
267 check_per_file(file)
270 if __name__ == "__main__":
271 main()