mirror of
https://github.com/corda/corda.git
synced 2025-02-05 10:39:13 +00:00
40e5e65365
* RELEASE - Release script to extract which issues are in a release In addition, format the output for inclusion in the release notes. Not 100% sure if this should be in the tree or out in a separate repo but we have *a lot* of small repo#s now and this is fine to live here unless anyone vehemently disagrees Add additional modes, make keyring optional
248 lines
8.1 KiB
Python
Executable File
248 lines
8.1 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
#-------------------------------------------------------------------------------
|
|
#
|
|
# Usage
|
|
# =======
|
|
#
|
|
# ./jiraReleaseChecker.py <oldTag> <jiraTag> <jiraUser> [-m mode]
|
|
# ./jiraReleaseChecker.py release-V3.1 "Corda 3.3" some.user@r3.com [-m not-in-jira]
|
|
#
|
|
# <oldTag> is the point prior to the current branches head in history from
|
|
# which to inspect commits. Normally this will be the tag of the previous
|
|
# release. e.g.
|
|
#
|
|
# master ----------------------------------------------
|
|
# \
|
|
# release/4 -----------+--------------+------------
|
|
# / /
|
|
# release/4.0 release/4.1
|
|
#
|
|
# The current release in the above example will be 4.2 and those commits
|
|
# extend from 4.1 having been backported from master. Thus <oldTag> is
|
|
# release/4.1
|
|
#
|
|
# <jiraTag> should refer to the version string used within
|
|
# the R3 Corda Jira to track the release. For example, for 3.3 this would be
|
|
# "Corda 3.3"
|
|
#
|
|
# <jiraUser> should be a registered user able to authenticate with the
|
|
# R3 Jira system. Authentication and password management is handled through
|
|
# the native OS keyring implementation.
|
|
#
|
|
# The script should be run on the relevant release branch within the git
|
|
# repository.
|
|
#
|
|
# Modes
|
|
# -------
|
|
#
|
|
# The tool can operate in 3 modes
|
|
#
|
|
# * rst - The default when omitted. Will take the combined lists
|
|
# of issues fixed from both Jira and commit summaries and
|
|
# format that list in such a way it can be included within
|
|
# the release notes for the next release. Will include hyper
|
|
# links to the R3 Jira for each ticket.
|
|
# * not-in-jira - Print a list of tickets that are included in commit
|
|
# summaries but are not tagged in Jira as fixed in the release
|
|
# * not-in-commit - Print a list of tickets that are tagged in Jira but that
|
|
# are not mentioned in any commit summary,
|
|
#
|
|
# Pre Requisites
|
|
# ================
|
|
#
|
|
# pip
|
|
# pyjira
|
|
# gitpython
|
|
# keyring (optional)
|
|
#
|
|
# Installation
|
|
# --------------
|
|
# Should be a simple matter of ``pip install <package>
|
|
#
|
|
# Issues
|
|
# ========
|
|
#
|
|
# Doesn't really handle many errors all that well, also gives no mechanism
|
|
# to enter a correct password into the keyring if a wrong one is added which
|
|
# isn't great but for now this should do
|
|
#
|
|
#-------------------------------------------------------------------------------
|
|
|
|
import re
|
|
import sys
|
|
import getpass
|
|
import argparse
|
|
|
|
try :
|
|
import keyring
|
|
except ImportError :
|
|
disableKeyring = True
|
|
else :
|
|
disableKeyring = False
|
|
|
|
from jira import JIRA
|
|
from git import Repo
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
R3_JIRA_ADDR = "https://r3-cev.atlassian.net"
|
|
JIRA_MAX_RESULTS = 50
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
#
|
|
# For a given user (provide via the command line) authenticate with Jira and
|
|
# return an interface object instance
|
|
#
|
|
def jiraLogin(user) :
|
|
password = keyring.get_password ('jira', user) if not disableKeyring else None
|
|
|
|
if not password:
|
|
password = getpass.getpass("Please enter your JIRA password, " +
|
|
"it will be stored in your OS Keyring: ")
|
|
if not disableKeyring :
|
|
keyring.set_password ('jira', user, password)
|
|
|
|
return JIRA(R3_JIRA_ADDR, auth=(user, password))
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
#
|
|
# Cope with Jira REST API paginating query results
|
|
#
|
|
def jiraQuery (jira, query) :
|
|
offset = 0
|
|
results = JIRA_MAX_RESULTS
|
|
rtn = []
|
|
while (results == JIRA_MAX_RESULTS) :
|
|
issues = jira.search_issues(query, maxResults=JIRA_MAX_RESULTS, startAt=offset)
|
|
results = len(issues)
|
|
if results > 0 :
|
|
offset += JIRA_MAX_RESULTS
|
|
rtn += issues
|
|
|
|
return rtn
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
#
|
|
# Take a Jira issue and format it in such a way we can include it as a line
|
|
# item in the release notes formatted with a hyperlink to the issue in Jira
|
|
#
|
|
def issueToRST(issue) :
|
|
return "* %s [`%s <%s/browse/%s>`_]" % (
|
|
issue.fields.summary,
|
|
issue.key,
|
|
R3_JIRA_ADDR,
|
|
issue.key)
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
#
|
|
# Get a list of jiras from Jira where those jiras are marked as fixed
|
|
# in some specific version (set on the command line).
|
|
#
|
|
# Optionally, an already authenticated Jira connection instance can be
|
|
# provided to avoid re-authenticating. The authenticated object
|
|
# is returned for reuse.
|
|
#
|
|
def getJirasFromJira(args_, jira_ = None) :
|
|
jira = jiraLogin(args_.jiraUser) if jira_ == None else jira_
|
|
|
|
return jiraQuery(jira, \
|
|
'project in (Corda, Ent) And fixVersion in ("%s") and status in (Done)' % (args_.jiraTag)) \
|
|
, jira
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
def getJiraIdsFromJira(args_, jira_ = None) :
|
|
jira = jiraLogin(args_.jiraUser) if jira_ == None else jira_
|
|
|
|
jirasFromJira, _ = jiraQuery(jira, \
|
|
'project in (Corda, Ent) And fixVersion in ("%s") and status in (Done)' % (args_.jiraTag)) \
|
|
, jira
|
|
|
|
return [ j.key for j in jirasFromJira ], jira
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
def getJiraIdsFromCommits(args_) :
|
|
jiraMatch = re.compile("(CORDA-\d+|ENT-\d+)")
|
|
repo = Repo(".", search_parent_directories = True)
|
|
|
|
jirasFromCommits = []
|
|
for commit in list (repo.iter_commits ("%s.." % (args_.oldTag))) :
|
|
jirasFromCommits += jiraMatch.findall(commit.summary)
|
|
|
|
return jirasFromCommits
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
#
|
|
# Take the set of all tickets completed in a release (the union of those
|
|
# tagged in Jira and those marked in commit summaries) and format them
|
|
# for inclusion in the release notes (rst format).
|
|
#
|
|
def rst (args_) :
|
|
jiraIdsFromCommits = getJiraIdsFromCommits(args_)
|
|
jirasFromJira, jiraObj = getJirasFromJira(args_)
|
|
|
|
jiraIdsFromJira = [ jira.key for jira in jirasFromJira ]
|
|
|
|
#
|
|
# Grab the set of JIRA's that aren't tagged as fixed in the release but are mentioned
|
|
# in a commit and pull down the JIRA information for those so as to get access
|
|
# to their summary
|
|
#
|
|
extraJiras = set(jiraIdsFromCommits).difference(jiraIdsFromJira)
|
|
jirasFromJira += jiraQuery(jiraObj, "key in (%s)" % (", ".join(extraJiras)))
|
|
|
|
for jira in jirasFromJira :
|
|
print issueToRST(jira)
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
def notInJira(args_) :
|
|
jiraIdsFromCommits = getJiraIdsFromCommits(args_)
|
|
jiraIdsFromJira, _ = getJiraIdsFromJira(args_)
|
|
|
|
print 'Issues mentioned in commits but not set as "fixed in" in Jira'
|
|
|
|
for jiraId in set(jiraIdsFromJira).difference(jiraIdsFromCommits) :
|
|
print jiraId
|
|
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
def notInCommit(args_) :
|
|
jiraIdsFromCommits = getJiraIdsFromCommits(args_)
|
|
jiraIdsFromJira, _ = getJiraIdsFromJira(args_)
|
|
|
|
print 'Issues tagged in Jira as fixed but not mentioned in any commit summary'
|
|
|
|
for jiraId in set(jiraIdsFromCommits).difference(jiraIdsFromJira) :
|
|
print jiraId
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
if __name__ == "__main__" :
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("-m", "--mode", help="display a square of a given number",
|
|
choices = [ "rst", "not-in-jira", "not-in-commit"])
|
|
parser.add_argument("oldTag", help="The previous release tag")
|
|
parser.add_argument("jiraTag", help="The current Jira release")
|
|
parser.add_argument("jiraUser", help="Who to authenticate with Jira as")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not args.mode : args.mode = "rst"
|
|
|
|
if args.mode == "rst" : rst(args)
|
|
elif args.mode == "not-in-jira" : notInJira(args)
|
|
elif args.mode == "not-in-commit" : notInCommit(args)
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
|