| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  | #!/usr/bin/env python3 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import argparse | 
					
						
							|  |  |  | import fileinput | 
					
						
							|  |  |  | import io | 
					
						
							| 
									
										
										
										
											2024-09-12 16:01:35 +08:00
										 |  |  | import os | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  | import shutil | 
					
						
							|  |  |  | import subprocess | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | GIT = shutil.which("git") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def update_file(file, lineno, old, new): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Replace all occurrences of the old substring by the new substring. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :param file: The file to update. | 
					
						
							|  |  |  |     :param lineno: The line number to update. | 
					
						
							|  |  |  |     :param old: The old substring. | 
					
						
							|  |  |  |     :param new: The new substring. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2024-09-12 16:01:35 +08:00
										 |  |  |     print("* Updating file in place") | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  |     with fileinput.FileInput(file, inplace=True) as f: | 
					
						
							|  |  |  |         for line in f: | 
					
						
							|  |  |  |             if f.lineno() == lineno and old in line: | 
					
						
							|  |  |  |                 print(line.replace(old, new), end="") | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 print(line, end="") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def analyze_file(file, lineno, commits_and_tags, dry_run=False): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Analyze the given file. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :param file: The file to analyze. | 
					
						
							|  |  |  |     :param lineno: The line number to analyze. | 
					
						
							|  |  |  |     :param commits_and_tags: The output dictionary mapping commits to release tags. | 
					
						
							|  |  |  |     :param dry_run: Whether or not this is a dry run. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     print(f"Analyzing {file}:{lineno}") | 
					
						
							|  |  |  |     line_sha = ( | 
					
						
							|  |  |  |         subprocess.check_output( | 
					
						
							|  |  |  |             [GIT, "blame", "--porcelain", "-L", f"{lineno},{lineno}", file], text=True | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         .split("\n", 1)[0] | 
					
						
							|  |  |  |         .split(" ", 1)[0] | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2024-09-12 16:01:35 +08:00
										 |  |  |     print(f"* first sha: {line_sha}") | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  |     first_tag = subprocess.check_output( | 
					
						
							|  |  |  |         [GIT, "tag", "--sort=creatordate", "--contains", line_sha, "jenkins-*"], | 
					
						
							|  |  |  |         text=True, | 
					
						
							|  |  |  |     ).split("\n", 1)[0] | 
					
						
							|  |  |  |     if first_tag: | 
					
						
							| 
									
										
										
										
											2024-09-12 16:01:35 +08:00
										 |  |  |         print(f"* first tag was {first_tag}") | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  |         commits_and_tags[line_sha] = first_tag | 
					
						
							|  |  |  |         if not dry_run: | 
					
						
							| 
									
										
										
										
											2022-10-16 02:28:37 +08:00
										 |  |  |             since_version = first_tag.replace("jenkins-", "") | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  |             update_file( | 
					
						
							|  |  |  |                 file, | 
					
						
							|  |  |  |                 int(lineno), | 
					
						
							|  |  |  |                 "@since TODO", | 
					
						
							| 
									
										
										
										
											2022-10-16 02:28:37 +08:00
										 |  |  |                 f"@since {since_version}", | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  |             ) | 
					
						
							| 
									
										
										
										
											2022-10-16 02:28:37 +08:00
										 |  |  |             update_file( | 
					
						
							|  |  |  |                 file, | 
					
						
							|  |  |  |                 int(lineno), | 
					
						
							|  |  |  |                 '@Deprecated(since = "TODO")', | 
					
						
							|  |  |  |                 f'@Deprecated(since = "{since_version}")', | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             update_file( | 
					
						
							|  |  |  |                 file, | 
					
						
							|  |  |  |                 int(lineno), | 
					
						
							|  |  |  |                 '@RestrictedSince("TODO")', | 
					
						
							|  |  |  |                 f'@RestrictedSince("{since_version}")', | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  |     else: | 
					
						
							|  |  |  |         print( | 
					
						
							| 
									
										
										
										
											2024-09-12 16:01:35 +08:00
										 |  |  |             "* Not updating file, no tag found. " | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  |             "Normal if the associated PR/commit is not merged and released yet; " | 
					
						
							|  |  |  |             "otherwise make sure to fetch tags from jenkinsci/jenkins" | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-09-12 16:01:35 +08:00
										 |  |  |     print() # Add a newline for markdown rendering | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def analyze_files(commits_and_tags, dry_run=False): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Analyze all files in the repository. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :param commits_and_tags: The output dictionary mapping commits to release tags. | 
					
						
							|  |  |  |     :param dry_run: Whether or not this is a dry run. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     cmd = [ | 
					
						
							|  |  |  |         GIT, | 
					
						
							|  |  |  |         "grep", | 
					
						
							|  |  |  |         "--line-number", | 
					
						
							| 
									
										
										
										
											2022-10-16 02:28:37 +08:00
										 |  |  |         "-E", | 
					
						
							|  |  |  |         '@since TODO|@Deprecated\\(since = "TODO"\\)|@RestrictedSince\\("TODO"\\)', | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  |         "--", | 
					
						
							|  |  |  |         "*.java", | 
					
						
							|  |  |  |         "*.jelly", | 
					
						
							|  |  |  |         "*.js", | 
					
						
							|  |  |  |     ] | 
					
						
							| 
									
										
										
										
											2024-09-12 16:01:35 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-20 23:23:30 +08:00
										 |  |  |     is_ci = "CI" in os.environ | 
					
						
							|  |  |  |     if is_ci: | 
					
						
							| 
									
										
										
										
											2024-09-12 16:01:35 +08:00
										 |  |  |         print("<details><summary>Detailed output</summary>\n\n") | 
					
						
							| 
									
										
										
										
											2025-10-01 17:27:28 +08:00
										 |  |  |     processed = 0 | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  |     with subprocess.Popen(cmd, stdout=subprocess.PIPE) as proc: | 
					
						
							|  |  |  |         for line in io.TextIOWrapper(proc.stdout): | 
					
						
							|  |  |  |             parts = line.rstrip().split(":", 2) | 
					
						
							|  |  |  |             analyze_file(parts[0], parts[1], commits_and_tags, dry_run=dry_run) | 
					
						
							| 
									
										
										
										
											2025-10-01 17:27:28 +08:00
										 |  |  |             processed += 1 | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  |         retcode = proc.wait() | 
					
						
							| 
									
										
										
										
											2025-10-01 17:27:28 +08:00
										 |  |  |         if retcode not in (0, 1): | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  |             raise subprocess.CalledProcessError(retcode, cmd) | 
					
						
							| 
									
										
										
										
											2025-10-01 17:27:28 +08:00
										 |  |  |         if processed == 0: | 
					
						
							|  |  |  |             print("No '@since TODO', '@Deprecated(since = \"TODO\")', or '@RestrictedSince(\"TODO\")' tags found.") | 
					
						
							| 
									
										
										
										
											2024-09-20 23:23:30 +08:00
										 |  |  |     if is_ci: | 
					
						
							| 
									
										
										
										
											2024-09-12 16:01:35 +08:00
										 |  |  |         print("</details>\n") | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def display_results(commits_and_tags): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Display the results of the analysis. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :param commits_and_tags: The output dictionary mapping commits to release tags. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     print("List of commits introducing new API and the first release they went in:") | 
					
						
							|  |  |  |     releases = {release for release in commits_and_tags.values()} | 
					
						
							|  |  |  |     for release in sorted(releases): | 
					
						
							|  |  |  |         print(f"* https://github.com/jenkinsci/jenkins/releases/tag/{release}") | 
					
						
							|  |  |  |         for commit, first_release in commits_and_tags.items(): | 
					
						
							|  |  |  |             if release == first_release: | 
					
						
							|  |  |  |                 print(f"  - https://github.com/jenkinsci/jenkins/commit/{commit}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def main(): | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2022-10-16 02:28:37 +08:00
										 |  |  |     Update '@since TODO', '@Deprecated(since = "TODO")', and '@RestrictedSince("TODO")' entries | 
					
						
							|  |  |  |     with actual Jenkins release versions. | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     This script is a developer tool, to be used by maintainers. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     parser = argparse.ArgumentParser( | 
					
						
							| 
									
										
										
										
											2022-10-16 02:28:37 +08:00
										 |  |  |         description="Update '@since TODO', '@Deprecated(since = \"TODO\")', and '@RestrictedSince(\"TODO\")' entries " | 
					
						
							|  |  |  |                     "with actual Jenkins release versions. " | 
					
						
							| 
									
										
										
										
											2022-04-13 04:08:12 +08:00
										 |  |  |     ) | 
					
						
							|  |  |  |     parser.add_argument("-n", "--dry-run", help="Dry run", action="store_true") | 
					
						
							|  |  |  |     args = parser.parse_args() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     commits_and_tags = {} | 
					
						
							|  |  |  |     analyze_files(commits_and_tags, dry_run=args.dry_run) | 
					
						
							|  |  |  |     if commits_and_tags: | 
					
						
							|  |  |  |         display_results(commits_and_tags) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     main() |