mirror of
https://github.com/LBRYFoundation/herald.go.git
synced 2025-10-01 07:30:33 +00:00
updates versioning script
This commit is contained in:
parent
e6cd5bc220
commit
6b45d7470e
1 changed files with 318 additions and 279 deletions
|
@ -1,279 +1,318 @@
|
||||||
import os
|
import argparse
|
||||||
import io
|
import io
|
||||||
import sys
|
import json
|
||||||
import json
|
import os
|
||||||
import argparse
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import github3
|
import github3
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print('To run release tool you need to install github3.py:')
|
print('To run release tool you need to install github3.py:')
|
||||||
print('')
|
print('')
|
||||||
print(' $ pip install github3.py')
|
print(' $ pip install github3.py')
|
||||||
print('')
|
print('')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
AREA_RENAME = {
|
AREA_RENAME = {
|
||||||
'api': 'API',
|
'api': 'API',
|
||||||
'dht': 'DHT'
|
'dht': 'DHT'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_github():
|
def get_github():
|
||||||
config_path = os.path.expanduser('~/.lbry-release-tool.json')
|
config_path = os.path.expanduser('~/.lbry-release-tool.json')
|
||||||
if os.path.exists(config_path):
|
if os.path.exists(config_path):
|
||||||
with open(config_path, 'r') as config_file:
|
with open(config_path, 'r') as config_file:
|
||||||
config = json.load(config_file)
|
config = json.load(config_file)
|
||||||
return github3.github.GitHub(token=config['token'])
|
return github3.github.GitHub(token=config['token'])
|
||||||
|
|
||||||
token = os.environ.get("GH_TOKEN")
|
token = os.environ.get("GH_TOKEN")
|
||||||
if not token:
|
if not token:
|
||||||
print('GitHub Credentials')
|
print('GitHub Credentials')
|
||||||
username = input('username: ')
|
username = input('username: ')
|
||||||
password = getpass('password: ')
|
password = getpass('password: ')
|
||||||
gh = github3.github.GitHub(username, password)
|
gh = github3.github.GitHub(username, password)
|
||||||
token = input('Enter 2FA: ')
|
token = input('Enter 2FA: ')
|
||||||
with open(config_path, 'w') as config_file:
|
with open(config_path, 'w') as config_file:
|
||||||
json.dump({'token': token}, config_file)
|
json.dump({'token': token}, config_file)
|
||||||
gh.login(token=token)
|
gh.login(token=token)
|
||||||
|
|
||||||
return gh
|
return gh
|
||||||
|
|
||||||
|
|
||||||
def get_labels(pr, prefix):
|
def get_labels(pr, prefix):
|
||||||
for label in pr.labels:
|
for label in pr.labels:
|
||||||
label_name = label['name']
|
label_name = label['name']
|
||||||
if label_name.startswith(f'{prefix}: '):
|
if label_name.startswith(f'{prefix}: '):
|
||||||
yield label_name[len(f'{prefix}: '):]
|
yield label_name[len(f'{prefix}: '):]
|
||||||
|
|
||||||
|
|
||||||
def get_label(pr, prefix):
|
def get_label(pr, prefix):
|
||||||
for label in get_labels(pr, prefix):
|
for label in get_labels(pr, prefix):
|
||||||
return label
|
return label
|
||||||
|
|
||||||
|
|
||||||
BACKWARDS_INCOMPATIBLE = 'backwards-incompatible:'
|
BACKWARDS_INCOMPATIBLE = 'backwards-incompatible:'
|
||||||
RELEASE_TEXT = 'release-text:'
|
RELEASE_TEXT = 'release-text:'
|
||||||
RELEASE_TEXT_LINES = 'release-text-lines:'
|
RELEASE_TEXT_LINES = 'release-text-lines:'
|
||||||
|
|
||||||
|
|
||||||
def get_backwards_incompatible(desc: str):
|
def get_backwards_incompatible(desc: str):
|
||||||
for line in desc.splitlines():
|
for line in desc.splitlines():
|
||||||
if line.startswith(BACKWARDS_INCOMPATIBLE):
|
if line.startswith(BACKWARDS_INCOMPATIBLE):
|
||||||
yield line[len(BACKWARDS_INCOMPATIBLE):]
|
yield line[len(BACKWARDS_INCOMPATIBLE):]
|
||||||
|
|
||||||
|
|
||||||
def get_release_text(desc: str):
|
def get_release_text(desc: str):
|
||||||
in_release_lines = False
|
in_release_lines = False
|
||||||
for line in desc.splitlines():
|
for line in desc.splitlines():
|
||||||
if in_release_lines:
|
if in_release_lines:
|
||||||
yield line.rstrip()
|
yield line.rstrip()
|
||||||
elif line.startswith(RELEASE_TEXT_LINES):
|
elif line.startswith(RELEASE_TEXT_LINES):
|
||||||
in_release_lines = True
|
in_release_lines = True
|
||||||
elif line.startswith(RELEASE_TEXT):
|
elif line.startswith(RELEASE_TEXT):
|
||||||
yield line[len(RELEASE_TEXT):].strip()
|
yield line[len(RELEASE_TEXT):].strip()
|
||||||
yield ''
|
yield ''
|
||||||
|
|
||||||
|
|
||||||
class Version:
|
class Version:
|
||||||
|
|
||||||
def __init__(self, major=0, date=datetime.now(), micro=0, alphabeta=""):
|
def __init__(self, major=0, date=datetime.now(), micro=0, alphabeta=""):
|
||||||
self.major = int(major)
|
self.major = int(major)
|
||||||
self.date = date
|
self.date = date
|
||||||
self.micro = int(micro)
|
self.micro = int(micro)
|
||||||
self.alphabeta = alphabeta
|
self.alphabeta = alphabeta
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_string(cls, version_string):
|
def from_string(cls, version_string):
|
||||||
(major, datemicroalphabeta) = version_string.split('.', 1)
|
(major, datemicroalphabeta) = version_string.split('.', 1)
|
||||||
parts = datemicroalphabeta.split("-")
|
parts = datemicroalphabeta.split("-")
|
||||||
if len(parts) > 1:
|
if len(parts) > 1:
|
||||||
datemicro, alphabeta = parts[0], parts[1]
|
datemicro, alphabeta = parts[0], parts[1]
|
||||||
else:
|
else:
|
||||||
datemicro, alphabeta = parts[0], ""
|
datemicro, alphabeta = parts[0], ""
|
||||||
|
|
||||||
date, micro = datemicro.rsplit('.', 1) if datemicro.count('.') > 2 else (datemicro, "0")
|
date, micro = datemicro.rsplit('.', 1) if datemicro.count('.') > 2 else (datemicro, "0")
|
||||||
return cls(major.replace("v", ""), datetime.strptime(date, "%Y.%m.%d"), int(micro), alphabeta)
|
return cls(major.replace("v", ""), datetime.strptime(date, "%Y.%m.%d"), int(micro), alphabeta)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_content(cls, content):
|
def from_content(cls, content):
|
||||||
src = content.decoded.decode('utf-8')
|
src = content.decoded.decode('utf-8')
|
||||||
return cls.from_string(src)
|
return cls.from_string(src)
|
||||||
|
|
||||||
def increment(self, action):
|
def increment(self, action):
|
||||||
cls = self.__class__
|
cls = self.__class__
|
||||||
|
|
||||||
if action == 'major':
|
if action == 'major':
|
||||||
return cls(self.major+1, datetime.now(), self.micro, self.alphabeta)
|
return cls(self.major+1, datetime.now(), self.micro, self.alphabeta)
|
||||||
elif action == 'date':
|
elif action == 'date':
|
||||||
return cls(self.major, datetime.now(), self.micro, self.alphabeta)
|
return cls(self.major, datetime.now(), self.micro, self.alphabeta)
|
||||||
elif action == 'micro':
|
elif action == 'micro':
|
||||||
return cls(self.major, datetime.now(), self.micro+1, self.alphabeta)
|
return cls(self.major, datetime.now(), self.micro+1, self.alphabeta)
|
||||||
|
|
||||||
raise ValueError(f'unknown action: {action}')
|
raise ValueError(f'unknown action: {action}')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tag(self):
|
def tag(self):
|
||||||
return f'{self}'
|
return f'{self}'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
arr = ['v', self.major, '.', self.date.strftime("%Y.%m.%d")]
|
arr = ['v', self.major, '.', self.date.strftime("%Y.%m.%d")]
|
||||||
if self.micro > 0:
|
if self.micro > 0:
|
||||||
arr += [".", self.micro]
|
arr += [".", self.micro]
|
||||||
if self.alphabeta != "":
|
if self.alphabeta != "":
|
||||||
arr += ["-", self.alphabeta]
|
arr += ["-", self.alphabeta]
|
||||||
return ''.join(str(p) for p in arr)
|
return ''.join(str(p) for p in arr)
|
||||||
|
|
||||||
|
|
||||||
def release(args):
|
def release(args):
|
||||||
gh = get_github()
|
gh = get_github()
|
||||||
repo = gh.repository('lbryio', 'hub')
|
repo = gh.repository('lbryio', 'hub')
|
||||||
try:
|
try:
|
||||||
version_file = repo.file_contents('version.txt')
|
version_file = repo.file_contents('version.txt')
|
||||||
current_version = Version.from_content(version_file)
|
current_version = Version.from_content(version_file)
|
||||||
print(f'Current Version: {current_version}')
|
print(f'Current Version: {current_version}')
|
||||||
except:
|
except:
|
||||||
current_version = Version(0)
|
current_version = Version()
|
||||||
version_file = repo.create_file("version.txt", message="add version file", content=str(current_version).encode('utf-8'))
|
version_file = repo.create_file("version.txt", message="add version file", content=str(current_version).encode('utf-8'))
|
||||||
|
|
||||||
|
|
||||||
if not args.confirm:
|
if not args.confirm:
|
||||||
print("\nDRY RUN ONLY. RUN WITH --confirm TO DO A REAL RELEASE.\n")
|
print("\nDRY RUN ONLY. RUN WITH --confirm TO DO A REAL RELEASE.\n")
|
||||||
|
|
||||||
|
|
||||||
if args.action == 'current':
|
if args.action == 'current':
|
||||||
new_version = current_version
|
new_version = current_version
|
||||||
else:
|
else:
|
||||||
new_version = current_version.increment(args.action)
|
new_version = current_version.increment(args.action)
|
||||||
print(f' New Version: {new_version}')
|
print(f' New Version: {new_version}')
|
||||||
|
|
||||||
previous_release = repo.release_from_tag(args.start_tag or current_version.tag)
|
tag = args.start_tag if args.start_tag else current_version.tag
|
||||||
|
|
||||||
print(f' Changelog From: {previous_release.tag_name} ({previous_release.created_at})')
|
try:
|
||||||
print()
|
previous_release = repo.release_from_tag(tag)
|
||||||
|
except github3.exceptions.NotFoundError:
|
||||||
incompats = []
|
previous_release = list(repo.releases())[-1]
|
||||||
release_texts = []
|
print(f' Changelog From: {previous_release.tag_name} ({previous_release.created_at})')
|
||||||
unlabeled = []
|
print()
|
||||||
fixups = []
|
|
||||||
areas = {}
|
incompats = []
|
||||||
for pr in gh.search_issues(f"merged:>={previous_release._json_data['created_at']} repo:lbryio/hub"):
|
release_texts = []
|
||||||
area_labels = list(get_labels(pr, 'area'))
|
unlabeled = []
|
||||||
type_label = get_label(pr, 'type')
|
fixups = []
|
||||||
if area_labels and type_label:
|
areas = {}
|
||||||
for area_name in area_labels:
|
for pr in gh.search_issues(f"merged:>={previous_release._json_data['created_at']} repo:lbryio/hub"):
|
||||||
for incompat in get_backwards_incompatible(pr.body or ""):
|
area_labels = list(get_labels(pr, 'area'))
|
||||||
incompats.append(f' * [{area_name}] {incompat.strip()} ({pr.html_url})')
|
type_label = get_label(pr, 'type')
|
||||||
for release_text in get_release_text(pr.body or ""):
|
if area_labels and type_label:
|
||||||
release_texts.append(release_text)
|
for area_name in area_labels:
|
||||||
if type_label == 'fixup':
|
for incompat in get_backwards_incompatible(pr.body or ""):
|
||||||
fixups.append(f' * {pr.title} ({pr.html_url}) by {pr.user["login"]}')
|
incompats.append(f' * [{area_name}] {incompat.strip()} ({pr.html_url})')
|
||||||
else:
|
for release_text in get_release_text(pr.body or ""):
|
||||||
area = areas.setdefault(area_name, [])
|
release_texts.append(release_text)
|
||||||
area.append(f' * [{type_label}] {pr.title} ({pr.html_url}) by {pr.user["login"]}')
|
if type_label == 'fixup':
|
||||||
else:
|
fixups.append(f' * {pr.title} ({pr.html_url}) by {pr.user["login"]}')
|
||||||
unlabeled.append(f' * {pr.title} ({pr.html_url}) by {pr.user["login"]}')
|
else:
|
||||||
|
area = areas.setdefault(area_name, [])
|
||||||
area_names = list(areas.keys())
|
area.append(f' * [{type_label}] {pr.title} ({pr.html_url}) by {pr.user["login"]}')
|
||||||
area_names.sort()
|
else:
|
||||||
|
unlabeled.append(f' * {pr.title} ({pr.html_url}) by {pr.user["login"]}')
|
||||||
body = io.StringIO()
|
|
||||||
w = lambda s: body.write(s+'\n')
|
area_names = list(areas.keys())
|
||||||
|
area_names.sort()
|
||||||
w(f'## [{new_version}] - {date.today().isoformat()}')
|
|
||||||
if release_texts:
|
body = io.StringIO()
|
||||||
w('')
|
w = lambda s: body.write(s+'\n')
|
||||||
for release_text in release_texts:
|
|
||||||
w(release_text)
|
w(f'## [{new_version}] - {date.today().isoformat()}')
|
||||||
if incompats:
|
if release_texts:
|
||||||
w('')
|
w('')
|
||||||
w(f'### Backwards Incompatible Changes')
|
for release_text in release_texts:
|
||||||
for incompat in incompats:
|
w(release_text)
|
||||||
w(incompat)
|
if incompats:
|
||||||
for area in area_names:
|
w('')
|
||||||
prs = areas[area]
|
w(f'### Backwards Incompatible Changes')
|
||||||
area = AREA_RENAME.get(area.lower(), area.capitalize())
|
for incompat in incompats:
|
||||||
w('')
|
w(incompat)
|
||||||
w(f'### {area}')
|
for area in area_names:
|
||||||
for pr in prs:
|
prs = areas[area]
|
||||||
w(pr)
|
area = AREA_RENAME.get(area.lower(), area.capitalize())
|
||||||
|
w('')
|
||||||
print(body.getvalue())
|
w(f'### {area}')
|
||||||
|
for pr in prs:
|
||||||
if unlabeled:
|
w(pr)
|
||||||
print('The following PRs were skipped and not included in changelog:')
|
|
||||||
for skipped in unlabeled:
|
print(body.getvalue())
|
||||||
print(skipped)
|
|
||||||
|
if unlabeled:
|
||||||
if fixups:
|
print('The following PRs were skipped and not included in changelog:')
|
||||||
print('The following PRs were marked as fixups and not included in changelog:')
|
for skipped in unlabeled:
|
||||||
for skipped in fixups:
|
print(skipped)
|
||||||
print(skipped)
|
|
||||||
|
if fixups:
|
||||||
if args.confirm:
|
print('The following PRs were marked as fixups and not included in changelog:')
|
||||||
|
for skipped in fixups:
|
||||||
commit = version_file.update(
|
print(skipped)
|
||||||
new_version.tag,
|
|
||||||
version_file.decoded.decode('utf-8').replace(str(current_version), str(new_version)).encode()
|
if args.confirm:
|
||||||
)['commit']
|
|
||||||
|
commit = version_file.update(
|
||||||
repo.create_tag(
|
new_version.tag,
|
||||||
tag=new_version.tag,
|
version_file.decoded.decode('utf-8').replace(str(current_version), str(new_version)).encode()
|
||||||
message=new_version.tag,
|
)['commit']
|
||||||
sha=commit.sha,
|
|
||||||
obj_type='commit',
|
if args.action != "current":
|
||||||
tagger=commit.committer
|
repo.create_tag(
|
||||||
)
|
tag=new_version.tag,
|
||||||
|
message=new_version.tag,
|
||||||
repo.create_release(
|
sha=commit.sha,
|
||||||
new_version.tag,
|
obj_type='commit',
|
||||||
name=new_version.tag,
|
tagger=commit.committer
|
||||||
body=body.getvalue(),
|
)
|
||||||
draft=True,
|
|
||||||
)
|
repo.create_release(
|
||||||
|
new_version.tag,
|
||||||
return 0
|
name=new_version.tag,
|
||||||
|
body=body.getvalue(),
|
||||||
|
draft=True,
|
||||||
class TestReleaseTool(unittest.TestCase):
|
)
|
||||||
|
elif args.action == "current":
|
||||||
def test_version_parsing(self):
|
print("in args.action == current")
|
||||||
self.assertTrue(str(Version.from_string('v1.2020.01.01-beta')), 'v1.2020.01.01-beta')
|
try:
|
||||||
self.assertTrue(str(Version.from_string('v1.2020.01.01.10')), 'v1.2020.01.01-beta')
|
print(new_version.tag)
|
||||||
|
# if we have the tag and release already don't do anything
|
||||||
def test_version_increment(self):
|
release = repo.release_from_tag(new_version.tag)
|
||||||
v = Version.from_string('v1.2020.01.01-beta')
|
if release.prerelease:
|
||||||
self.assertTrue(str(v.increment('major')), 'v2.2020.01.01.beta')
|
release.edit(prerelease=False)
|
||||||
self.assertTrue(str(v.increment('date')), f'v1.{datetime.now().strftime("YYYY.MM.dd")}-beta')
|
return
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
def test():
|
try:
|
||||||
runner = unittest.TextTestRunner(verbosity=2)
|
# We need to do this to get draft and prerelease releases
|
||||||
loader = unittest.TestLoader()
|
release = repo.releases().next()
|
||||||
suite = loader.loadTestsFromTestCase(TestReleaseTool)
|
# Case me have a release and no tag
|
||||||
return 0 if runner.run(suite).wasSuccessful() else 1
|
if release.name == new_version.tag:
|
||||||
|
if release.draft:
|
||||||
|
release.edit(draft=False, prerelease=True)
|
||||||
def main():
|
elif release.prerelease:
|
||||||
parser = argparse.ArgumentParser()
|
release.edit(prerelease=False)
|
||||||
parser.add_argument("--confirm", default=False, action="store_true",
|
return
|
||||||
help="without this flag, it will only print what it will do but will not actually do it")
|
else:
|
||||||
parser.add_argument("--start-tag", help="custom starting tag for changelog generation")
|
raise Exception("asdf")
|
||||||
parser.add_argument("action", choices=['test', 'current', 'major', 'minor', 'micro'])
|
except:
|
||||||
args = parser.parse_args()
|
repo.create_tag(
|
||||||
|
tag=new_version.tag,
|
||||||
if args.action == "test":
|
message=new_version.tag,
|
||||||
code = test()
|
sha=commit.sha,
|
||||||
else:
|
obj_type='commit',
|
||||||
code = release(args)
|
tagger=commit.committer
|
||||||
|
)
|
||||||
print()
|
repo.create_release(
|
||||||
return code
|
new_version.tag,
|
||||||
|
name=new_version.tag,
|
||||||
|
body=body.getvalue(),
|
||||||
if __name__ == "__main__":
|
draft=True,
|
||||||
sys.exit(main())
|
)
|
||||||
|
|
||||||
|
class TestReleaseTool(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_version_parsing(self):
|
||||||
|
self.assertTrue(str(Version.from_string('v1.2020.01.01-beta')), 'v1.2020.01.01-beta')
|
||||||
|
self.assertTrue(str(Version.from_string('v1.2020.01.01.10')), 'v1.2020.01.01-beta')
|
||||||
|
|
||||||
|
def test_version_increment(self):
|
||||||
|
v = Version.from_string('v1.2020.01.01-beta')
|
||||||
|
self.assertTrue(str(v.increment('major')), 'v2.2020.01.01.beta')
|
||||||
|
self.assertTrue(str(v.increment('date')), f'v1.{datetime.now().strftime("YYYY.MM.dd")}-beta')
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
runner = unittest.TextTestRunner(verbosity=2)
|
||||||
|
loader = unittest.TestLoader()
|
||||||
|
suite = loader.loadTestsFromTestCase(TestReleaseTool)
|
||||||
|
return 0 if runner.run(suite).wasSuccessful() else 1
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--confirm", default=False, action="store_true",
|
||||||
|
help="without this flag, it will only print what it will do but will not actually do it")
|
||||||
|
parser.add_argument("--start-tag", help="custom starting tag for changelog generation")
|
||||||
|
parser.add_argument("action", choices=['test', 'current', 'major', 'minor', 'micro'])
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.action == "test":
|
||||||
|
code = test()
|
||||||
|
else:
|
||||||
|
code = release(args)
|
||||||
|
|
||||||
|
print()
|
||||||
|
return code
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
|
|
Loading…
Add table
Reference in a new issue