mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge pull request #14415 from Atlante45/feat/rc-branch-script
RC branch script
This commit is contained in:
commit
cc38a96bff
3 changed files with 232 additions and 0 deletions
13
tools/scripts/Readme.md
Normal file
13
tools/scripts/Readme.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
## Setup
|
||||
|
||||
Run the following command to install all the dependencies:
|
||||
|
||||
```pip install -r requirements.txt```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
./rc-branches.py check v0.76.1
|
||||
./rc-branches.py create v0.77.0
|
||||
```
|
218
tools/scripts/rc-branches.py
Executable file
218
tools/scripts/rc-branches.py
Executable file
|
@ -0,0 +1,218 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import argparse
|
||||
|
||||
from git import Repo
|
||||
|
||||
FORMAT = '[%(levelname)s] %(message)s'
|
||||
logging.basicConfig(format=FORMAT, level=logging.DEBUG)
|
||||
|
||||
remote_name = "upstream"
|
||||
remote_master_branch = "{}/master".format(remote_name)
|
||||
|
||||
|
||||
class VersionParser:
|
||||
"""A parser for version numbers"""
|
||||
def __init__(self, versionString):
|
||||
# Validate that user passed a valid version
|
||||
VERSION_RE = re.compile(r"^v?(\d+)\.(\d+)\.(\d+)$")
|
||||
match = VERSION_RE.match(versionString)
|
||||
if not match:
|
||||
raise ValueError("Invalid version (should be X.Y.Z)")
|
||||
|
||||
# Parse the version component and build proper version strings
|
||||
self.major = int(match.group(1))
|
||||
self.minor = int(match.group(2))
|
||||
self.patch = int(match.group(3))
|
||||
self.version = "v{}.{}.{}".format(self.major, self.minor, self.patch) # clean up version
|
||||
|
||||
self.is_major_release = False
|
||||
self.is_minor_release = False
|
||||
self.is_patch_release = False
|
||||
if self.patch != 0:
|
||||
self.is_patch_release = True
|
||||
self.previous_version = "v{}.{}.{}".format(self.major, self.minor, self.patch - 1)
|
||||
elif self.minor != 0:
|
||||
self.is_minor_release = True
|
||||
self.previous_version = "v{}.{}.{}".format(self.major, self.minor - 1, 0)
|
||||
else:
|
||||
self.is_major_release = True
|
||||
self.previous_version = "v{}.{}.{}".format(self.major - 1, 0, 0)
|
||||
raise ValueError("Major releases not yet supported")
|
||||
|
||||
# Build the branch names
|
||||
self.previous_rc_branch = "{}-rc".format(self.previous_version)
|
||||
self.base_branch = "{}-rc-base".format(self.version)
|
||||
self.rc_branch = "{}-rc".format(self.version)
|
||||
self.remote_previous_rc_branch = "{}/{}".format(remote_name, self.previous_rc_branch)
|
||||
self.remote_base_branch = "{}/{}".format(remote_name, self.base_branch)
|
||||
self.remote_rc_branch = "{}/{}".format(remote_name, self.rc_branch)
|
||||
|
||||
|
||||
def checkVersionBranches(version):
|
||||
"""Check that the branches for a given version were created properly."""
|
||||
|
||||
parser = VersionParser(version)
|
||||
|
||||
repo = Repo(os.getcwd(), search_parent_directories=True)
|
||||
assert not repo.bare
|
||||
|
||||
# Verify the branches' existance
|
||||
if parser.remote_previous_rc_branch not in repo.refs:
|
||||
raise ValueError("Previous RC branch not found: {}".format(parser.remote_previous_rc_branch))
|
||||
|
||||
if parser.remote_base_branch not in repo.refs:
|
||||
raise ValueError("Base branch not found: {}".format(parser.remote_base_branch))
|
||||
|
||||
if parser.remote_rc_branch not in repo.refs:
|
||||
raise ValueError("RC branch not found: {}".format(parser.remote_rc_branch))
|
||||
|
||||
previous_rc = repo.refs[parser.remote_previous_rc_branch]
|
||||
current_rc_base = repo.refs[parser.remote_base_branch]
|
||||
current_rc = repo.refs[parser.remote_rc_branch]
|
||||
master = repo.refs[remote_master_branch]
|
||||
|
||||
# Check the base branch is an ancestor of the rc branch
|
||||
if not repo.is_ancestor(current_rc_base, current_rc):
|
||||
raise ValueError("{} is not an ancesctor of {}".format(current_rc_base, current_rc))
|
||||
|
||||
# Check that the base branch is the merge base of the previous and current RCs
|
||||
merge_base = repo.merge_base(previous_rc, current_rc)
|
||||
if current_rc_base.commit not in merge_base:
|
||||
raise ValueError("Base branch is not the merge base between {} and {}".format(previous_rc, current_rc))
|
||||
|
||||
# For patch releases, warn if the base commit is not the previous RC commit
|
||||
if parser.is_patch_release:
|
||||
if parser.previous_version not in repo.tags:
|
||||
logging.warning("The tag {0} does not exist, which suggests {0} has not been released.".format(parser.previous_version))
|
||||
|
||||
if current_rc_base.commit != previous_rc.commit:
|
||||
logging.warning("Previous version has commits not in this patch");
|
||||
logging.warning("Type \"git diff {}..{}\" to see the commit list".format(current_rc_base, previous_rc));
|
||||
|
||||
# Check base branch is part of the previous RC
|
||||
previous_rc_base_commit = repo.merge_base(previous_rc, master)
|
||||
if repo.is_ancestor(current_rc_base, previous_rc_base_commit):
|
||||
raise ValueError("{} is older than {}".format(current_rc_base, previous_rc))
|
||||
|
||||
print("[SUCCESS] Checked {}".format(parser.version))
|
||||
|
||||
def createVersionBranches(version):
|
||||
"""Create the branches for a given version."""
|
||||
|
||||
parser = VersionParser(version)
|
||||
|
||||
repo = Repo(os.getcwd(), search_parent_directories=True)
|
||||
assert not repo.bare
|
||||
|
||||
# Validate the user is on a local branch that has the right merge base
|
||||
if repo.head.is_detached:
|
||||
raise ValueError("You must not run this script in a detached HEAD state")
|
||||
|
||||
# Validate the user has no pending changes
|
||||
if repo.is_dirty():
|
||||
raise ValueError("Your working tree has pending changes. You must have a clean working tree before proceeding.")
|
||||
|
||||
# Make sure the remote is up to date
|
||||
remote = repo.remotes[remote_name]
|
||||
remote.fetch(prune=True)
|
||||
|
||||
# Verify the previous RC branch exists
|
||||
if parser.remote_previous_rc_branch not in repo.refs:
|
||||
raise ValueError("Previous RC branch not found: {}".format(parser.remote_previous_rc_branch))
|
||||
|
||||
# Verify the branches don't already exist
|
||||
if parser.remote_base_branch in repo.refs:
|
||||
raise ValueError("Base branch already exists: {}".format(parser.remote_base_branch))
|
||||
|
||||
if parser.remote_rc_branch in repo.refs:
|
||||
raise ValueError("RC branch already exists: {}".format(parser.remote_rc_branch))
|
||||
|
||||
if parser.base_branch in repo.refs:
|
||||
raise ValueError("Base branch already exists locally: {}".format(parser.base_branch))
|
||||
|
||||
if parser.rc_branch in repo.refs:
|
||||
raise ValueError("RC branch already exists locally: {}".format(parser.rc_branch))
|
||||
|
||||
# Save current branch name
|
||||
current_branch_name = repo.active_branch
|
||||
|
||||
# Create the RC branches
|
||||
if parser.is_patch_release:
|
||||
# Check tag exists, if it doesn't, print warning and ask for comfirmation
|
||||
if parser.previous_version not in repo.tags:
|
||||
logging.warning("The tag {0} does not exist, which suggests {0} has not yet been released.".format(parser.previous_version))
|
||||
logging.warning("Creating the branches now means that {0} will diverge from {1} if anything is merged into {1}.".format(parser.version, parser.previous_version))
|
||||
logging.warning("This is not recommended unless necessary.")
|
||||
|
||||
validAnswer = False
|
||||
askCount = 0
|
||||
while not validAnswer and askCount < 3:
|
||||
answer = input("Are you sure you want to do this? [y/n] ").strip().lower()
|
||||
askCount += 1
|
||||
validAnswer = answer == "y" or answer == "n"
|
||||
|
||||
if not validAnswer:
|
||||
raise ValueError("Did not understand response")
|
||||
|
||||
if answer == "n":
|
||||
print("Aborting")
|
||||
return
|
||||
else:
|
||||
print("Creating branches")
|
||||
|
||||
previous_rc = repo.refs[parser.remote_previous_rc_branch]
|
||||
|
||||
repo.create_head(parser.base_branch, previous_rc)
|
||||
remote.push("{0}:{0}".format(parser.base_branch))
|
||||
repo.create_head(parser.rc_branch, previous_rc)
|
||||
remote.push("{0}:{0}".format(parser.rc_branch))
|
||||
else:
|
||||
previous_rc = repo.refs[parser.remote_previous_rc_branch]
|
||||
master = repo.refs[remote_master_branch]
|
||||
merge_base = repo.merge_base(previous_rc, master)
|
||||
|
||||
repo.create_head(parser.base_branch, merge_base[0])
|
||||
remote.push("{0}:{0}".format(parser.base_branch))
|
||||
repo.create_head(parser.rc_branch, master)
|
||||
remote.push("{0}:{0}".format(parser.rc_branch))
|
||||
|
||||
print("[SUCCESS] Created {} and {}".format(parser.base_branch, parser.rc_branch))
|
||||
print("[SUCCESS] You can make the PR from the following webpage:")
|
||||
print("[SUCCESS] https://github.com/highfidelity/hifi/compare/{}...{}".format(parser.base_branch, parser.rc_branch))
|
||||
if parser.is_patch_release:
|
||||
print("[SUCCESS] NOTE: You will have to wait for the first fix to be merged into the RC branch to be able to create the PR")
|
||||
|
||||
def main():
|
||||
"""Execute Main entry point."""
|
||||
global remote_name
|
||||
|
||||
parser = argparse.ArgumentParser(description='RC branches tool',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog='''Example commands you can run:\n%(prog)s check 0.75.0\n%(prog)s create 0.75.1''')
|
||||
parser.add_argument("command", help="command to execute", choices=["check", "create"])
|
||||
parser.add_argument("version", help="version of the form \"X.Y.Z\"")
|
||||
parser.add_argument("--remote", dest="remote_name", default=remote_name,
|
||||
help="git remote to use as reference")
|
||||
args = parser.parse_args()
|
||||
|
||||
remote_name = args.remote_name
|
||||
|
||||
try:
|
||||
if args.command == "check":
|
||||
checkVersionBranches(args.version)
|
||||
elif args.command == "create":
|
||||
createVersionBranches(args.version)
|
||||
else:
|
||||
parser.print_help()
|
||||
except ValueError as ex:
|
||||
logging.error(ex)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
1
tools/scripts/requirements.txt
Normal file
1
tools/scripts/requirements.txt
Normal file
|
@ -0,0 +1 @@
|
|||
GitPython
|
Loading…
Reference in a new issue