-
Notifications
You must be signed in to change notification settings - Fork 287
/
changelog.py
172 lines (144 loc) · 5.82 KB
/
changelog.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# INSTALLATION:
# Install Python https://www.python.org/downloads/windows/
# Install git https://git-scm.com/download/win
# Open a terminal and verify that you can run python and git
# Go to the directory where you want to install this to and type
# python -m venv venv
# venv\Scripts\activate.bat
# To install the required libraries, type:
# python -m pip install --upgrade pip
# pip install requests
# pip install lxml
# Running the script
# open a terminal and run activate.bat again (like above)
# python changelog.py <from_version> <to_version>
# For example: python changelog.py 2.0.8.8 2.0.9.0
# If you leave <to_version> out, it defaults to the latest git commit
import os
import re
import sys
import time
from collections import defaultdict
from subprocess import check_output
import requests
from lxml import html
# REPO_NAME = "GT-New-Horizons-Modpack"
REPO_NAME = "GT5-Unofficial"
GITHUB_REPO_URL = "https://github.com/GTNewHorizons/" + REPO_NAME + ".git"
GITHUB_ISSUE_URL = "https://github.com/GTNewHorizons/" + REPO_NAME + "/issues/"
GITHUB_ISSUE_REGEX = re.compile("#\d+")
def update_git_repo():
"""Updates the GT:NH repo from GITHUB_REPO_URL"""
if os.path.exists(REPO_NAME):
os.chdir(REPO_NAME)
check_output("git fetch")
os.chdir("..")
else:
check_output("git clone " + GITHUB_REPO_URL)
def get_git_log(from_tag, to_tag="HEAD"):
"""Gets the git log between two tags. If to_tag=None, defaults to HEAD"""
os.chdir(REPO_NAME)
git_command = "git log --oneline " + from_tag + ".." + to_tag
return check_output(git_command).decode("utf-8").strip("\n")
def format_raw_changelog(raw_log):
"""Takes the raw changelog and formats it as follows:
Find all github issues (by #<digits>)
Fetch titles for github issues
Output:
- <Github issue title> <github issue #>
-- commit 1
-- commit 2
- Commits without titles
"""
changelog_dict = make_changelog_dict(raw_log)
changelog_with_github_titles = add_github_titles(changelog_dict)
output_changelog = ""
for issue_title, commits in changelog_with_github_titles.items():
if not issue_title:
# No known GitHub title, just list these with one dash
for commit_message in commits:
output_changelog += "- " + commit_message + "\n"
else:
output_changelog += "- " + issue_title + "\n"
for commit_message in commits:
if commit_message == issue_title:
continue
if commit_message.startswith("Merge pull request "):
continue
if commit_message.startswith(" Merge pull request "):
continue
output_changelog += "-- " + commit_message + "\n"
return output_changelog
def make_changelog_dict(raw_log):
"""Takes a raw git changelog and returns a dict from github issue # -> list of commits.
If there is no github # mentioned, the key is the empty string
This also strips out the commit hashes coming from git log --oneline
"""
changelog_dict = defaultdict(list)
for line in raw_log.split('\n'):
issue = get_github_issue_number(line)
changelog_dict[issue].append(clean_commit_line(line))
return changelog_dict
def get_github_issue_number(line):
"""Returns the github issue number if it exists, otherwise returns the empty string"""
matches = re.search(GITHUB_ISSUE_REGEX, line)
if matches:
return matches.group()[1:]
else:
return ""
def clean_commit_line(line):
"""Removes the commit hash from the front of the line (from the git log --oneline output)"""
return line[9:]
def add_github_titles(changelog_dict):
"""Turns a defaultdict of <github issue #> -> list of commit messages into
github issue title -> list of commit messages
"""
extended_dict = defaultdict(list)
for issue, list_of_commits in changelog_dict.items():
if issue:
github_title = get_github_issue_title(issue)
if github_title:
title = get_github_issue_title(issue) + " #" + issue
else:
# issue not found on Github
title = ""
else:
title = ""
existing_commits = extended_dict[title]
if existing_commits:
existing_commits.extend(list_of_commits)
else:
extended_dict[title] = list_of_commits
return extended_dict
def get_github_issue_title(issue):
"""Fetches the issue title from GitHub"""
response = requests.get(GITHUB_ISSUE_URL + issue)
if response.status_code == 429:
# We got rate-limited. Wait as long as they are asking for
wait_time = int(response.headers["Retry-After"]) + 5
print("GitHub rate limiting. Sleeping for " + str(wait_time) + " seconds.")
time.sleep(wait_time)
response = requests.get(GITHUB_ISSUE_URL + issue)
html_tree = html.fromstring(response.content)
title_attribute = html_tree.find(".//title")
if title_attribute is None:
# Apparently someone referenced a non-existing Github issue
return ""
title = title_attribute.text
separator = " · "
separator_location = title.find(separator)
return title[0:separator_location]
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Please provide at least a starting version or commit hash.")
from_commit = sys.argv[1]
to_commit = "HEAD"
if len(sys.argv) == 3:
to_commit = sys.argv[2]
print("Changelog generator started. Fetching latest git repository...")
update_git_repo()
print("Generating raw changelog from git...")
raw_log = get_git_log(from_commit, to_commit)
print("Getting issue titles from Github (this takes a while for large change logs)...")
formatted_log = format_raw_changelog(raw_log)
print(formatted_log)