Commit 333ec9d3 by David Baumgold

Major refactoring based on Cale's branch approach

parent 460ee964
...@@ -17,7 +17,7 @@ import requests ...@@ -17,7 +17,7 @@ import requests
IGNORED_EMAILS = set(("vagrant@precise32.(none)",)) IGNORED_EMAILS = set(("vagrant@precise32.(none)",))
JIRA_RE = re.compile(r"\b[A-Z]{2,}-\d+\b") JIRA_RE = re.compile(r"\b[A-Z]{2,}-\d+\b")
PR_BRANCH_RE = re.compile(r"remotes/origin/pr/(\d+)") PR_BRANCH_RE = re.compile(r"remotes/edx/pr/(\d+)")
PROJECT_ROOT = path(__file__).abspath().dirname() PROJECT_ROOT = path(__file__).abspath().dirname()
repo = Repo(PROJECT_ROOT) repo = Repo(PROJECT_ROOT)
git = repo.git git = repo.git
...@@ -26,11 +26,11 @@ git = repo.git ...@@ -26,11 +26,11 @@ git = repo.git
def make_parser(): def make_parser():
parser = argparse.ArgumentParser(description="release master multitool") parser = argparse.ArgumentParser(description="release master multitool")
parser.add_argument( parser.add_argument(
'--previous', '--prev', '-p', metavar="GITREV", default="origin/release", '--previous', '--prev', '-p', metavar="GITREV", default="edx/release",
help="previous release [origin/release]") help="previous release [%(default)s]")
parser.add_argument( parser.add_argument(
'--current', '--curr', '-c', metavar="GITREV", default="HEAD", '--current', '--curr', '-c', metavar="GITREV", default="HEAD",
help="current release candidate [HEAD]") help="current release candidate [%(default)s]")
parser.add_argument( parser.add_argument(
'--date', '-d', '--date', '-d',
help="expected release date: defaults to " help="expected release date: defaults to "
...@@ -41,19 +41,16 @@ def make_parser(): ...@@ -41,19 +41,16 @@ def make_parser():
parser.add_argument( parser.add_argument(
'--table', '-t', action="store_true", default=False, '--table', '-t', action="store_true", default=False,
help="only print table") help="only print table")
parser.add_argument(
'--commit-table', action="store_true", default=False,
help="Display table by commit, instead of by PR")
return parser return parser
def ensure_pr_fetch(): def ensure_pr_fetch():
# it would be nice to use the git-python API to do this, but it doesn't seem # it would be nice to use the git-python API to do this, but it doesn't seem
# to support configurations with more than one value per key. :( # to support configurations with more than one value per key. :(
origin_fetches = git.config("remote.origin.fetch", get_all=True).splitlines() edx_fetches = git.config("remote.edx.fetch", get_all=True).splitlines()
pr_fetch = '+refs/pull/*/head:refs/remotes/origin/pr/*' pr_fetch = '+refs/pull/*/head:refs/remotes/edx/pr/*'
if pr_fetch not in origin_fetches: if pr_fetch not in edx_fetches:
git.config("remote.origin.fetch", pr_fetch, add=True) git.config("remote.edx.fetch", pr_fetch, add=True)
git.fetch() git.fetch()
...@@ -76,44 +73,6 @@ def parse_ticket_references(text): ...@@ -76,44 +73,6 @@ def parse_ticket_references(text):
return JIRA_RE.findall(text) return JIRA_RE.findall(text)
def emails(commit_range):
"""
Returns a set of all email addresses responsible for the commits between
the two commit references.
"""
# %ae prints the authored_by email for the commit
# %n prints a newline
# %ce prints the committed_by email for the commit
emails = set(git.log(commit_range, format='%ae%n%ce').splitlines())
return emails - IGNORED_EMAILS
def commits_by_email(commit_range, include_merge=False):
"""
Return a ordered dictionary of {email: commit_list}
The dictionary is alphabetically ordered by email address
The commit list is ordered by commit author date
"""
kwargs = {}
if not include_merge:
kwargs["no-merges"] = True
data = OrderedDict()
for email in sorted(emails(commit_range)):
authored_commits = set(repo.iter_commits(
commit_range, author=email, **kwargs
))
committed_commits = set(repo.iter_commits(
commit_range, committer=email, **kwargs
))
commits = authored_commits | committed_commits
data[email] = sorted(commits, key=lambda c: c.authored_date)
return data
class NotFoundError(Exception): pass
def get_pr_for_commit(commit, branch="master"): def get_pr_for_commit(commit, branch="master"):
""" """
http://stackoverflow.com/questions/8475448/find-merge-commit-which-include-a-specific-commit http://stackoverflow.com/questions/8475448/find-merge-commit-which-include-a-specific-commit
...@@ -165,21 +124,43 @@ def get_merge_commit(commit, branch="master"): ...@@ -165,21 +124,43 @@ def get_merge_commit(commit, branch="master"):
commit=commit, branch=branch, commit=commit, branch=branch,
)) ))
def get_prs_for_commit_range(commit_range):
def get_pr_info(num):
""" """
Returns a set of pull requests (integers) that contain all the commits Returns the info from the Github API
in the given commit range.
""" """
pull_requests = set() url = "https://api.github.com/repos/edx/edx-platform/pulls/{num}".format(num=num)
for commit in Commit.iter_items(repo, commit_range): response = requests.get(url)
# ignore merge commits result = response.json()
if len(commit.parents) > 1: if not response.ok:
continue raise requests.exceptions.RequestException(result["message"])
pull_requests.add(get_pr_for_commit(commit)) return result
return pull_requests
def prs_by_email(commit_range): def get_merged_prs(start_ref, end_ref):
"""
Return the set of all pull requests (as integers) that were merged between
the start_ref and end_ref.
"""
ensure_pr_fetch()
start_unmerged_branches = set(
branch.strip() for branch in
git.branch(all=True, no_merged=start_ref).splitlines()
)
end_merged_branches = set(
branch.strip() for branch in
git.branch(all=True, merged=end_ref).splitlines()
)
merged_between_refs = start_unmerged_branches & end_merged_branches
merged_prs = set()
for branch in merged_between_refs:
match = PR_BRANCH_RE.search(branch)
if match:
merged_prs.add(int(match.group(1)))
return merged_prs
def prs_by_email(start_ref, end_ref):
""" """
Returns an ordered dictionary of {email: pr_list} Returns an ordered dictionary of {email: pr_list}
Email is the email address of the person who merged the pull request Email is the email address of the person who merged the pull request
...@@ -187,10 +168,10 @@ def prs_by_email(commit_range): ...@@ -187,10 +168,10 @@ def prs_by_email(commit_range):
The pull request list is ordered by merge date The pull request list is ordered by merge date
""" """
unordered_data = defaultdict(set) unordered_data = defaultdict(set)
for pr_num in get_prs_for_commit_range(commit_range): for pr_num in get_merged_prs(start_ref, end_ref):
ref = "refs/remotes/origin/pr/{num}".format(num=pr_num) ref = "refs/remotes/edx/pr/{num}".format(num=pr_num)
branch = SymbolicReference(repo, ref) branch = SymbolicReference(repo, ref)
merge = get_merge_commit(branch.commit) merge = get_merge_commit(branch.commit, end_ref)
unordered_data[merge.author.email].add((pr_num, merge)) unordered_data[merge.author.email].add((pr_num, merge))
ordered_data = OrderedDict() ordered_data = OrderedDict()
...@@ -200,46 +181,14 @@ def prs_by_email(commit_range): ...@@ -200,46 +181,14 @@ def prs_by_email(commit_range):
return ordered_data return ordered_data
def generate_table_by_commit(commit_range, include_merge=False): def generate_table(start_ref, end_ref):
"""
Return a string corresponding to a commit table to embed in Confluence
"""
header = "||Author||Summary||Commit||JIRA||Verified?||"
commit_link = "[commit|https://github.com/edx/edx-platform/commit/{sha}]"
rows = [header]
cbe = commits_by_email(commit_range, include_merge)
for email, commits in cbe.items():
for i, commit in enumerate(commits):
rows.append("| {author} | {summary} | {commit} | {jira} | {verified} |".format(
author=email if i == 0 else "",
summary=commit.summary.replace("|", "\|"),
commit=commit_link.format(sha=commit.hexsha),
jira=", ".join(parse_ticket_references(commit.message)),
verified="",
))
return "\n".join(rows)
def get_pr_info(num):
"""
Returns the info from the Github API
"""
url = "https://api.github.com/repos/edx/edx-platform/pulls/{num}".format(num=num)
response = requests.get(url)
result = response.json()
if not response.ok:
raise requests.exceptions.RequestException(result["message"])
return result
def generate_table_by_pr(commit_range):
""" """
Return a string corresponding to a commit table to embed in Confluence Return a string corresponding to a commit table to embed in Confluence
""" """
header = "|| Merged By || Title || PR || JIRA || Verified? ||" header = "|| Merged By || Title || PR || JIRA || Verified? ||"
pr_link = "[#{num}|https://github.com/edx/edx-platform/pull/{num}]" pr_link = "[#{num}|https://github.com/edx/edx-platform/pull/{num}]"
rows = [header] rows = [header]
prbe = prs_by_email(commit_range) prbe = prs_by_email(start_ref, end_ref)
for email, pull_requests in prbe.items(): for email, pull_requests in prbe.items():
for i, pull_request in enumerate(pull_requests): for i, pull_request in enumerate(pull_requests):
try: try:
...@@ -265,12 +214,13 @@ def generate_table_by_pr(commit_range): ...@@ -265,12 +214,13 @@ def generate_table_by_pr(commit_range):
return "\n".join(rows) return "\n".join(rows)
def generate_email(commit_range, release_date=None): def generate_email(start_ref, end_ref, release_date=None):
""" """
Returns a string roughly approximating an email. Returns a string roughly approximating an email.
""" """
if release_date is None: if release_date is None:
release_date = default_release_date() release_date = default_release_date()
prbe = prs_by_email(start_ref, end_ref)
email = """ email = """
To: {emails} To: {emails}
...@@ -286,7 +236,7 @@ def generate_email(commit_range, release_date=None): ...@@ -286,7 +236,7 @@ def generate_email(commit_range, release_date=None):
the edX employee who merged in your pull request will manually verify the edX employee who merged in your pull request will manually verify
your change(s), and you may disregard this message. your change(s), and you may disregard this message.
""".format( """.format(
emails=", ".join(sorted(emails(commit_range))), emails=", ".join(prbe.keys()),
date=release_date.isoformat(), date=release_date.isoformat(),
) )
return textwrap.dedent(email).strip() return textwrap.dedent(email).strip()
...@@ -298,17 +248,13 @@ def main(): ...@@ -298,17 +248,13 @@ def main():
if isinstance(args.date, basestring): if isinstance(args.date, basestring):
# user passed in a custom date, so we need to parse it # user passed in a custom date, so we need to parse it
args.date = parse_datestring(args.date).date() args.date = parse_datestring(args.date).date()
commit_range = "{0}..{1}".format(args.previous, args.current)
if args.table: if args.table:
if args.commit_table: print(generate_table(args.previous, args.current))
print(generate_table_by_commit(commit_range, include_merge=args.merge))
else:
print(generate_table_by_pr(commit_range))
return return
print("EMAIL:") print("EMAIL:")
print(generate_email(commit_range, release_date=args.date).encode('UTF-8')) print(generate_email(args.previous, args.current, release_date=args.date).encode('UTF-8'))
print("\n") print("\n")
print("Wiki Table:") print("Wiki Table:")
print( print(
...@@ -316,10 +262,7 @@ def main(): ...@@ -316,10 +262,7 @@ def main():
"in your release wiki page" "in your release wiki page"
) )
print("\n") print("\n")
if args.commit_table: print(generate_table(args.previous, args.current))
print(generate_table_by_commit(commit_range, include_merge=args.merge))
else:
print(generate_table_by_pr(commit_range))
if __name__ == "__main__": if __name__ == "__main__":
main() main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment