fetch-changelogs.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. #!/usr/bin/env python3
  2. from datetime import datetime
  3. import enum
  4. from pathlib import Path
  5. import sys
  6. import requests
  7. GITLAB = "https://gitlab.torproject.org"
  8. API_URL = f"{GITLAB}/api/v4"
  9. PROJECT_ID = 473
  10. class Platform(enum.IntFlag):
  11. WINDOWS = 8
  12. MACOS = 4
  13. LINUX = 2
  14. ANDROID = 1
  15. DESKTOP = 8 | 4 | 2
  16. ALL_PLATFORMS = 8 | 4 | 2 | 1
  17. class Issue:
  18. def __init__(self, j):
  19. self.title = j["title"]
  20. self.project, self.number = (
  21. j["references"]["full"].rsplit("/", 2)[-1].split("#")
  22. )
  23. self.platform = 0
  24. self.num_platforms = 0
  25. if "Desktop" in j["labels"]:
  26. self.platform = Platform.DESKTOP
  27. self.num_platforms += 3
  28. else:
  29. if "Windows" in j["labels"]:
  30. self.platform |= Platform.WINDOWS
  31. self.num_platforms += 1
  32. if "MacOS" in j["labels"]:
  33. self.platform |= Platform.MACOS
  34. self.num_platforms += 1
  35. if "Linux" in j["labels"]:
  36. self.platform |= Platform.LINUX
  37. self.num_platforms += 1
  38. if "Android" in j["labels"]:
  39. self.platform |= Platform.ANDROID
  40. self.num_platforms += 1
  41. if not self.platform:
  42. self.platform = Platform.ALL_PLATFORMS
  43. self.num_platforms = 4
  44. self.is_build = "Build System" in j["labels"]
  45. def get_platforms(self):
  46. if self.platform == Platform.ALL_PLATFORMS:
  47. return "All Platforms"
  48. platforms = []
  49. if self.platform & Platform.WINDOWS:
  50. platforms.append("Windows")
  51. if self.platform & Platform.MACOS:
  52. platforms.append("macOS")
  53. if self.platform & Platform.LINUX:
  54. platforms.append("Linux")
  55. if self.platform & Platform.ANDROID:
  56. platforms.append("Android")
  57. return " + ".join(platforms)
  58. def __str__(self):
  59. return f"Bug {self.number}: {self.title} [{self.project}]"
  60. def __lt__(self, other):
  61. return self.number < other.number
  62. def sorted_issues(issues):
  63. issues = [sorted(v) for v in issues.values()]
  64. return sorted(
  65. issues,
  66. key=lambda group: (group[0].num_platforms << 8) | group[0].platform,
  67. reverse=True,
  68. )
  69. if len(sys.argv) < 2:
  70. print(f"Usage: {sys.argv[0]} version-to-release or #issue-id")
  71. sys.exit(1)
  72. token_file = Path(__file__).parent / ".changelogs_token"
  73. if not token_file.exists():
  74. print(
  75. f"Please add your personal GitLab token (with 'read_api' scope) to {token_file}"
  76. )
  77. print(
  78. f"Please go to {GITLAB}/-/profile/personal_access_tokens and generate it."
  79. )
  80. token = input("Please enter the new token: ").strip()
  81. if not token:
  82. print("Invalid token!")
  83. sys.exit(2)
  84. with token_file.open("w") as f:
  85. f.write(token)
  86. with token_file.open() as f:
  87. token = f.read().strip()
  88. headers = {"PRIVATE-TOKEN": token}
  89. version = sys.argv[1]
  90. r = requests.get(
  91. f"{API_URL}/projects/{PROJECT_ID}/issues?labels=Release Prep",
  92. headers=headers,
  93. )
  94. if r.status_code == 401:
  95. print("Unauthorized! Has your token expired?")
  96. sys.exit(3)
  97. issue = None
  98. for i in r.json():
  99. if i["title"].find(sys.argv[1]) != -1:
  100. if issue is None:
  101. issue = i
  102. else:
  103. print("More than one matching issue found!")
  104. print("Please use the issue id.")
  105. sys.exit(4)
  106. if not issue:
  107. iid = version
  108. version = None
  109. if iid[0] == "#":
  110. iid = iid[1:]
  111. try:
  112. int(iid)
  113. r = requests.get(
  114. f"{API_URL}/projects/{PROJECT_ID}/issues?iids={iid}",
  115. headers=headers,
  116. )
  117. if r.ok and r.json():
  118. issue = r.json()[0]
  119. except ValueError:
  120. pass
  121. if not issue:
  122. print(
  123. "Release preparation issue not found. Please make sure it has ~Release Prep."
  124. )
  125. sys.exit(5)
  126. iid = issue["iid"]
  127. linked = {}
  128. linked_build = {}
  129. r = requests.get(
  130. f"{API_URL}/projects/{PROJECT_ID}/issues/{iid}/links", headers=headers
  131. )
  132. for i in r.json():
  133. i = Issue(i)
  134. target = linked_build if i.is_build else linked
  135. if i.platform not in target:
  136. target[i.platform] = []
  137. target[i.platform].append(i)
  138. linked = sorted_issues(linked)
  139. linked_build = sorted_issues(linked_build)
  140. date = datetime.now().strftime("%B %d %Y")
  141. print(f"Tor Browser {version} - {date}")
  142. for issues in linked:
  143. print(f" * {issues[0].get_platforms()}")
  144. for i in issues:
  145. print(f" * {i}")
  146. print(" * Build System")
  147. for issues in linked_build:
  148. print(f" * {issues[0].get_platforms()}")
  149. for i in issues:
  150. print(f" * {i}")