Formalise the merge script

This commit is contained in:
Raoul Snyman 2017-03-11 22:48:23 -07:00
parent e5abad85a7
commit c256477919

View File

@ -53,99 +53,172 @@ import subprocess
import re import re
import sys import sys
import os import os
import urllib.request from urllib.request import urlopen
from urllib.error import HTTPError
from argparse import ArgumentParser
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
# Check that the argument count is correct
if len(sys.argv) != 2:
print('\n\tUsage: ' + sys.argv[0] + ' <url to approved merge request>\n')
exit()
url = sys.argv[1] def parse_args():
pattern = re.compile('.+?/\+merge/\d+') """
match = pattern.match(url) Parse command line arguments and return them
"""
parser = ArgumentParser(prog='lp-merge', description='A helper script to merge proposals on Launchpad')
parser.add_argument('url', help='URL to the merge proposal')
parser.add_argument('-y', '--yes-to-all', action='store_true', help='Presume yes for all queries')
parser.add_argument('-q', '--use-qcommit', action='store_true', help='Use qcommit for committing')
return parser.parse_args()
# Check that the given argument is an url in the right format
if not url.startswith('https://code.launchpad.net/~') or match is None:
print('The url is not valid! It should look like this:\n '
'https://code.launchpad.net/~tomasgroth/openlp/doc22update4/+merge/271874')
page = urllib.request.urlopen(url) def is_merge_url_valid(url):
soup = BeautifulSoup(page.read(), 'lxml') """
Determine if the merge URL is valid
"""
match = re.match(r'.+?/\+merge/\d+', url)
if not url.startswith('https://code.launchpad.net/~') or match is None:
print('The url is not valid! It should look like this:\n '
'https://code.launchpad.net/~myusername/openlp/mybranch/+merge/271874')
return False
return True
# Find this span tag that contains the branch url
# <span class="branch-url">
span_branch_url = soup.find('span', class_='branch-url')
branch_url = span_branch_url.contents[0]
# Find this tag that describes the branch. We'll use that for commit message def get_merge_info(url):
# <meta name="description" content="..."> """
meta = soup.find('meta', attrs={"name": "description"}) Get all the merge information and return it as a dictionary
"""
merge_info = {}
print('Fetching merge information...')
# Try to load the page
try:
page = urlopen(url)
except HTTPError:
print('Unable to load merge URL: {}'.format(url))
return None
soup = BeautifulSoup(page.read(), 'lxml')
# Find this span tag that contains the branch url
# <span class="branch-url">
span_branch_url = soup.find('span', class_='branch-url')
if not span_branch_url:
print('Unable to find merge details on URL: {}'.format(url))
return None
merge_info['branch_url'] = span_branch_url.contents[0]
# Find this tag that describes the branch. We'll use that for commit message
# <meta name="description" content="...">
meta = soup.find('meta', attrs={"name": "description"})
merge_info['commit_message'] = meta.get('content')
# Find all tr-tags with this class. Makes it possible to get bug numbers.
# <tr class="bug-branch-summary"
bug_rows = soup.find_all('tr', class_='bug-branch-summary')
merge_info['bugs'] = []
for row in bug_rows:
id_attr = row.get('id')
merge_info['bugs'].append(id_attr[8:])
# Find target branch name using the tag below
# <div class="context-publication"><h1>Merge ... into...
div_branches = soup.find('div', class_='context-publication')
branches = div_branches.h1.contents[0]
merge_info['target_branch'] = '+branch/' + branches[(branches.find(' into lp:') + 9):]
# Find the authors email address. It is hidden in a javascript line like this:
# conf = {"status_value": "Needs review", "source_revid": "tomasgroth@yahoo.dk-20160921204550-gxduegmcmty9rljf",
# "user_can_edit_status": false, ...
script_tag = soup.find('script', attrs={"id": "codereview-script"})
content = script_tag.contents[0]
start_pos = content.find('source_revid') + 16
pattern = re.compile('.*\w-\d\d\d\d\d+')
match = pattern.match(content[start_pos:])
merge_info['author_email'] = match.group()[:-15]
# Launchpad doesn't supply the author's true name, so we'll just grab whatever they use for display on LP
a_person = soup.find('div', id='registration').find('a', 'person')
merge_info['author_name'] = a_person.contents[0]
return merge_info
commit_message = meta.get('content')
# Find all tr-tags with this class. Makes it possible to get bug numbers. def do_merge(merge_info, yes_to_all=False):
# <tr class="bug-branch-summary" """
bug_rows = soup.find_all('tr', class_='bug-branch-summary') Do the merge
bugs = [] """
for row in bug_rows: # Check that we are in the right branch
id_attr = row.get('id') bzr_info_output = subprocess.check_output(['bzr', 'info'])
bugs.append(id_attr[8:]) if merge_info['target_branch'] not in bzr_info_output.decode():
print('ERROR: It seems you are not in the right folder...')
return False
# Merge the branch
if yes_to_all:
can_merge = True
else:
user_choice = input('Merge ' + merge_info['branch_url'] + ' into local branch? (y/N/q): ').lower()
if user_choice == 'q':
return False
can_merge = user_choice == 'y'
if can_merge:
print('Merging...')
subprocess.call(['bzr', 'merge', merge_info['branch_url']])
return True
# Find target branch name using the tag below
# <div class="context-publication"><h1>Merge ... into...
div_branches = soup.find('div', class_='context-publication')
branches = div_branches.h1.contents[0]
target_branch = '+branch/' + branches[(branches.find(' into lp:') + 9):]
# Check that we are in the right branch def qcommit(commit_message, author_name, author_email, bugs=[]):
bzr_info_output = subprocess.check_output(['bzr', 'info']) """
if target_branch not in bzr_info_output.decode(): Use qcommit to make the commit
print('ERROR: It seems you are not in the right folder...') """
exit()
# Find the authors email address. It is hidden in a javascript line like this:
# conf = {"status_value": "Needs review", "source_revid": "tomasgroth@yahoo.dk-20160921204550-gxduegmcmty9rljf",
# "user_can_edit_status": false, ...
script_tag = soup.find('script', attrs={"id": "codereview-script"})
content = script_tag.contents[0]
start_pos = content.find('source_revid') + 16
pattern = re.compile('.*\w-\d\d\d\d\d+')
match = pattern.match(content[start_pos:])
author_email = match.group()[:-15]
# Merge the branch
do_merge = input('Merge ' + branch_url + ' into local branch? (y/N/q): ').lower()
if do_merge == 'y':
subprocess.call(['bzr', 'merge', branch_url])
elif do_merge == 'q':
exit()
# Create commit command
commit_command = ['bzr', 'commit']
for bug in bugs:
commit_command.append('--fixes')
commit_command.append('lp:' + bug)
commit_command.append('-m')
commit_command.append(commit_message)
commit_command.append('--author')
commit_command.append('"' + author_email + '"')
print('About to run the bzr command below:\n')
print(' '.join(commit_command))
do_commit = input('Run the command (y), use qcommit (qcommit) or cancel (C): ').lower()
if do_commit == 'y':
subprocess.call(commit_command)
elif do_commit == 'qcommit':
# Setup QT workaround to make qbzr look right on my box # Setup QT workaround to make qbzr look right on my box
my_env = os.environ.copy() my_env = os.environ.copy()
my_env['QT_GRAPHICSSYSTEM'] = 'native' my_env['QT_GRAPHICSSYSTEM'] = 'native'
# Print stuff that kan be copy/pasted into qbzr GUI # Print stuff that kan be copy/pasted into qbzr GUI
print('These bugs can be copy/pasted in: lp:' + ' lp:'.join(bugs)) if bugs:
print('The authors email is: ' + author_email) print('These bugs can be copy/pasted in: ' + ' '.join(['lp:{}'.format(bug) for bug in bugs]))
print('The authors email is: {} <{}>'.format(author_name, author_email))
# Run qcommit # Run qcommit
subprocess.call(['bzr', 'qcommit', '-m', commit_message], env=my_env) subprocess.call(['bzr', 'qcommit', '-m', commit_message], env=my_env)
def do_commit(merge_info, yes_to_all=False, use_qcommit=False):
"""
Actually do the commit
"""
if use_qcommit:
qcommit(merge_info['commit_message'], merge_info['author_name'], merge_info['author_email'],
merge_info.get('bugs'))
return True
# Create commit command
commit_command = ['bzr', 'commit']
if 'bugs' in merge_info:
commit_command.extend(['--fixes=lp:{}'.format(bug) for bug in merge_info['bugs']])
commit_command.extend(['-m', merge_info['commit_message'],
'--author', '{author_name} <{author_email}>'.format(**merge_info)])
if yes_to_all:
can_commit = True
else:
print('About to run the bzr command below:\n')
print(' '.join(commit_command))
user_choice = input('Run the command (y), use qcommit (q) or cancel (C): ').lower()
if user_choice == 'q':
qcommit(merge_info['commit_message'], merge_info['author_name'], merge_info['author_email'],
merge_info.get('bugs'))
return True
can_commit = user_choice == 'y'
if can_commit:
print('Committing...')
subprocess.call(commit_command)
return True
else:
return False
def main():
"""
Run the script
"""
args = parse_args()
if not is_merge_url_valid(args.url):
exit(1)
merge_info = get_merge_info(args.url)
if not merge_info:
exit(2)
if not do_merge(merge_info, args.yes_to_all):
exit(3)
if not do_commit(merge_info, args.yes_to_all, args.use_qcommit):
exit(4)
if __name__ == '__main__':
main()