Skip to content

Commit

Permalink
Merge pull request ipython#4172 from minrk/backport-pr
Browse files Browse the repository at this point in the history
add ability to check what PRs should be backported in backport_pr
  • Loading branch information
minrk committed Sep 4, 2013
2 parents fff3e64 + 7193597 commit f44195d
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 24 deletions.
59 changes: 55 additions & 4 deletions tools/backport_pr.py
Expand Up @@ -2,24 +2,37 @@
"""
Backport pull requests to a particular branch.
Usage: backport_pr.py branch PR
Usage: backport_pr.py branch [PR]
e.g.:
backport_pr.py 0.13.1 123
python tools/backport_pr.py 0.13.1 123
to backport PR #123 onto branch 0.13.1
or
python tools/backport_pr.py 1.x
to see what PRs are marked for backport that have yet to be applied.
"""

from __future__ import print_function

import os
import re
import sys

from subprocess import Popen, PIPE, check_call, check_output
from urllib import urlopen

from gh_api import get_pull_request, get_pull_request_files
from gh_api import (
get_issues_list,
get_pull_request,
get_pull_request_files,
is_pull_request,
)

def find_rejects(root='.'):
for dirname, dirs, files in os.walk(root):
Expand Down Expand Up @@ -84,9 +97,47 @@ def backport_pr(branch, num, project='ipython/ipython'):

return 0

backport_re = re.compile(r"[Bb]ackport.*?(\d+)")

def already_backported(branch, since_tag=None):
"""return set of PRs that have been backported already"""
if since_tag is None:
since_tag = check_output(['git','describe', branch, '--abbrev=0']).decode('utf8').strip()
cmd = ['git', 'log', '%s..%s' % (since_tag, branch), '--oneline']
lines = check_output(cmd).decode('utf8')
return set(int(num) for num in backport_re.findall(lines))

def should_backport(labels):
"""return set of PRs marked for backport"""
issues = get_issues_list("ipython/ipython",
labels=labels,
state='closed',
auth=True,
)
should_backport = set()
for issue in issues:
if not is_pull_request(issue):
continue
pr = get_pull_request("ipython/ipython", issue['number'], auth=True)
if not pr['merged']:
print ("Marked PR closed without merge: %i" % pr['number'])
continue
should_backport.add(pr['number'])
return should_backport

if __name__ == '__main__':
if len(sys.argv) < 3:

if len(sys.argv) < 2:
print(__doc__)
sys.exit(1)

if len(sys.argv) < 3:
branch = sys.argv[1]
already = already_backported(branch)
should = should_backport("backport-1.1")
print ("The following PRs should be backported:")
for pr in should.difference(already):
print (pr)
sys.exit(0)

sys.exit(backport_pr(sys.argv[1], int(sys.argv[2])))
31 changes: 18 additions & 13 deletions tools/gh_api.py
Expand Up @@ -119,12 +119,13 @@ def get_pull_request_files(project, num, auth=False):
element_pat = re.compile(r'<(.+?)>')
rel_pat = re.compile(r'rel=[\'"](\w+)[\'"]')

def get_paged_request(url, headers=None):
def get_paged_request(url, headers=None, **params):
"""get a full list, handling APIv3's paging"""
results = []
params.setdefault("per_page", 100)
while True:
print("fetching %s" % url, file=sys.stderr)
response = requests.get(url, headers=headers)
print("fetching %s with %s" % (url, params), file=sys.stderr)
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
results.extend(response.json())
if 'next' in response.links:
Expand All @@ -133,28 +134,32 @@ def get_paged_request(url, headers=None):
break
return results

def get_pulls_list(project, state="closed", auth=False):
"""get pull request list
"""
url = "https://api.github.com/repos/{project}/pulls?state={state}&per_page=100".format(project=project, state=state)
def get_pulls_list(project, auth=False, **params):
"""get pull request list"""
params.setdefault("state", "closed")
url = "https://api.github.com/repos/{project}/pulls".format(project=project)
if auth:
headers = make_auth_header()
else:
headers = None
pages = get_paged_request(url, headers=headers)
pages = get_paged_request(url, headers=headers, params=params)
return pages

def get_issues_list(project, state="closed", auth=False):
"""get pull request list
"""
url = "https://api.github.com/repos/{project}/pulls?state={state}&per_page=100".format(project=project, state=state)
def get_issues_list(project, auth=False, **params):
"""get issues list"""
params.setdefault("state", "closed")
url = "https://api.github.com/repos/{project}/issues".format(project=project)
if auth:
headers = make_auth_header()
else:
headers = None
pages = get_paged_request(url, headers=headers)
pages = get_paged_request(url, headers=headers, **params)
return pages

def is_pull_request(issue):
"""Return True if the given issue is a pull request."""
return bool(issue.get('pull_request', {}).get('html_url', None))

# encode_multipart_formdata is from urllib3.filepost
# The only change is to iter_fields, to enforce S3's required key ordering

Expand Down
8 changes: 1 addition & 7 deletions tools/github_stats.py
Expand Up @@ -13,7 +13,7 @@

from datetime import datetime, timedelta
from subprocess import check_output
from gh_api import get_paged_request, make_auth_header, get_pull_request
from gh_api import get_paged_request, make_auth_header, get_pull_request, is_pull_request

#-----------------------------------------------------------------------------
# Globals
Expand Down Expand Up @@ -50,12 +50,6 @@ def issues2dict(issues):
idict[i['number']] = i
return idict


def is_pull_request(issue):
"""Return True if the given issue is a pull request."""
return bool(issue.get('pull_request', {}).get('html_url', None))


def split_pulls(all_issues, project="ipython/ipython"):
"""split a list of closed issues into non-PR Issues and Pull Requests"""
pulls = []
Expand Down

0 comments on commit f44195d

Please sign in to comment.