##// END OF EJS Templates
Merge pull request #1759 from Carreau/mpr...
Fernando Perez -
r7531:029bbad1 merge
parent child Browse files
Show More
@@ -0,0 +1,127 b''
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """
4 Usage:
5 python git-mpr.py -m 1657
6 """
7 from __future__ import print_function
8
9 import argparse
10 from subprocess import check_call, CalledProcessError
11
12 import gh_api
13
14 ipy_repository = 'git://github.com/ipython/ipython.git'
15 gh_project = "ipython/ipython"
16 not_merged = {}
17
18 def merge_branch(repo, branch ):
19 """try to merge the givent branch into the current one
20
21 If something does not goes smoothly, merge is aborted
22
23 Returns True if merge sucessfull, False otherwise
24 """
25 # Delete the branch first
26 try :
27 check_call(['git', 'pull', '--no-edit', repo, branch])
28 except CalledProcessError :
29 check_call(['git', 'merge', '--abort'])
30 return False
31 return True
32
33
34 def merge_pr(num, github_api=3):
35 """ try to merge the branch of PR `num` into current branch
36
37 github_api : use github api v2 (to bypass https and issues with proxy) to find the
38 remote branch that should be merged by it's number
39 """
40 # Get Github authorisation first, so that the user is prompted straight away
41 # if their login is needed.
42
43 pr = gh_api.get_pull_request(gh_project, num, github_api)
44 if github_api == 2:
45 repo = pr['head']['repository']['url']
46 elif github_api == 3 :
47 repo = pr['head']['repo']['clone_url']
48
49
50 branch = pr['head']['ref']
51 mergeable = merge_branch(repo=repo,
52 branch=branch,
53 )
54 if not mergeable :
55 cmd = "git pull "+repo+" "+branch
56 not_merged[str(num)] = cmd
57 print("==============================================================================")
58 print("Something went wrong merging this branch, you can try it manually by runngin :")
59 print(cmd)
60 print("==============================================================================")
61
62
63 def main(*args):
64 parser = argparse.ArgumentParser(
65 description="""
66 Merge (one|many) github pull request by their number.\
67
68 If pull request can't be merge as is, cancel merge,
69 and continue to the next if any.
70 """
71 )
72 parser.add_argument('-v2', '--githubapiv2', action='store_const', const=2)
73
74 grp = parser.add_mutually_exclusive_group()
75 grp.add_argument(
76 '-l',
77 '--list',
78 action='store_const',
79 const=True,
80 help='list PR, their number and their mergeability')
81 grp.add_argument('-a',
82 '--merge-all',
83 action='store_const',
84 const=True ,
85 help='try to merge as many PR as possible, one by one')
86 grp.add_argument('-m',
87 '--merge',
88 type=int,
89 help="The pull request numbers",
90 nargs='*',
91 metavar='pr-number')
92 args = parser.parse_args()
93 if args.githubapiv2 == 2 :
94 github_api = 2
95 else :
96 github_api = 3
97
98 if(args.list):
99 pr_list = gh_api.get_pulls_list(gh_project, github_api)
100 for pr in pr_list :
101 mergeable = gh_api.get_pull_request(gh_project, pr['number'], github_api=github_api)['mergeable']
102
103 ismgb = u"√" if mergeable else " "
104 print(u"* #{number} [{ismgb}]: {title}".format(
105 number=pr['number'],
106 title=pr['title'],
107 ismgb=ismgb))
108
109 if(args.merge_all):
110 pr_list = gh_api.get_pulls_list(gh_project)
111 for pr in pr_list :
112 merge_pr(pr['number'])
113
114
115 elif args.merge:
116 for num in args.merge :
117 merge_pr(num, github_api=github_api)
118
119 if not_merged :
120 print('*************************************************************************************')
121 print('the following branch have not been merged automatically, considere doing it by hand :')
122 for num, cmd in not_merged.items() :
123 print( "PR {num}: {cmd}".format(num=num, cmd=cmd))
124 print('*************************************************************************************')
125
126 if __name__ == '__main__':
127 main()
@@ -1,80 +1,104 b''
1 """Functions for Github authorisation."""
1 """Functions for Github authorisation."""
2 from __future__ import print_function
2 from __future__ import print_function
3
3
4 try:
4 try:
5 input = raw_input
5 input = raw_input
6 except NameError:
6 except NameError:
7 pass
7 pass
8
8
9 import requests
9 import requests
10 import getpass
10 import getpass
11 import json
11 import json
12
12
13 # Keyring stores passwords by a 'username', but we're not storing a username and
13 # Keyring stores passwords by a 'username', but we're not storing a username and
14 # password
14 # password
15 fake_username = 'ipython_tools'
15 fake_username = 'ipython_tools'
16
16
17 token = None
17 token = None
18 def get_auth_token():
18 def get_auth_token():
19 global token
19 global token
20
20
21 if token is not None:
21 if token is not None:
22 return token
22 return token
23
23
24 import keyring
24 import keyring
25 token = keyring.get_password('github', fake_username)
25 token = keyring.get_password('github', fake_username)
26 if token is not None:
26 if token is not None:
27 return token
27 return token
28
28
29 print("Please enter your github username and password. These are not "
29 print("Please enter your github username and password. These are not "
30 "stored, only used to get an oAuth token. You can revoke this at "
30 "stored, only used to get an oAuth token. You can revoke this at "
31 "any time on Github.")
31 "any time on Github.")
32 user = input("Username: ")
32 user = input("Username: ")
33 pw = getpass.getpass("Password: ")
33 pw = getpass.getpass("Password: ")
34
34
35 auth_request = {
35 auth_request = {
36 "scopes": [
36 "scopes": [
37 "public_repo",
37 "public_repo",
38 "gist"
38 "gist"
39 ],
39 ],
40 "note": "IPython tools",
40 "note": "IPython tools",
41 "note_url": "https://github.com/ipython/ipython/tree/master/tools",
41 "note_url": "https://github.com/ipython/ipython/tree/master/tools",
42 }
42 }
43 response = requests.post('https://api.github.com/authorizations',
43 response = requests.post('https://api.github.com/authorizations',
44 auth=(user, pw), data=json.dumps(auth_request))
44 auth=(user, pw), data=json.dumps(auth_request))
45 response.raise_for_status()
45 response.raise_for_status()
46 token = json.loads(response.text)['token']
46 token = json.loads(response.text)['token']
47 keyring.set_password('github', fake_username, token)
47 keyring.set_password('github', fake_username, token)
48 return token
48 return token
49
49
50 def make_auth_header():
50 def make_auth_header():
51 return {'Authorization': 'token ' + get_auth_token()}
51 return {'Authorization': 'token ' + get_auth_token()}
52
52
53 def post_issue_comment(project, num, body):
53 def post_issue_comment(project, num, body):
54 url = 'https://api.github.com/repos/{project}/issues/{num}/comments'.format(project=project, num=num)
54 url = 'https://api.github.com/repos/{project}/issues/{num}/comments'.format(project=project, num=num)
55 payload = json.dumps({'body': body})
55 payload = json.dumps({'body': body})
56 r = requests.post(url, data=payload, headers=make_auth_header())
56 r = requests.post(url, data=payload, headers=make_auth_header())
57
57
58 def post_gist(content, description='', filename='file', auth=False):
58 def post_gist(content, description='', filename='file', auth=False):
59 """Post some text to a Gist, and return the URL."""
59 """Post some text to a Gist, and return the URL."""
60 post_data = json.dumps({
60 post_data = json.dumps({
61 "description": description,
61 "description": description,
62 "public": True,
62 "public": True,
63 "files": {
63 "files": {
64 filename: {
64 filename: {
65 "content": content
65 "content": content
66 }
66 }
67 }
67 }
68 }).encode('utf-8')
68 }).encode('utf-8')
69
69
70 headers = make_auth_header() if auth else {}
70 headers = make_auth_header() if auth else {}
71 response = requests.post("https://api.github.com/gists", data=post_data, headers=headers)
71 response = requests.post("https://api.github.com/gists", data=post_data, headers=headers)
72 response.raise_for_status()
72 response.raise_for_status()
73 response_data = json.loads(response.text)
73 response_data = json.loads(response.text)
74 return response_data['html_url']
74 return response_data['html_url']
75
75
76 def get_pull_request(project, num):
76 def get_pull_request(project, num, github_api=3):
77 url = "https://api.github.com/repos/{project}/pulls/{num}".format(project=project, num=num)
77 """get pull request info by number
78
79 github_api : version of github api to use
80 """
81 if github_api==2 :
82 url = "http://github.com/api/v2/json/pulls/{project}/{num}".format(project=project, num=num)
83 elif github_api == 3:
84 url = "https://api.github.com/repos/{project}/pulls/{num}".format(project=project, num=num)
85 response = requests.get(url)
86 response.raise_for_status()
87 if github_api == 2 :
88 return json.loads(response.text)['pull']
89 return json.loads(response.text)
90
91 def get_pulls_list(project, github_api=3):
92 """get pull request list
93
94 github_api : version of github api to use
95 """
96 if github_api == 3 :
97 url = "https://api.github.com/repos/{project}/pulls".format(project=project)
98 else :
99 url = "http://github.com/api/v2/json/pulls/{project}".format(project=project)
78 response = requests.get(url)
100 response = requests.get(url)
79 response.raise_for_status()
101 response.raise_for_status()
102 if github_api == 2 :
103 return json.loads(response.text)['pulls']
80 return json.loads(response.text)
104 return json.loads(response.text)
General Comments 0
You need to be logged in to leave comments. Login now