You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
270 lines
9.0 KiB
270 lines
9.0 KiB
#!/usr/bin/env python3
|
|
|
|
#
|
|
# Copyright (C) 2018 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
|
|
"""A command line utility to post review votes to multiple CLs on Gerrit."""
|
|
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
try:
|
|
from urllib.error import HTTPError # PY3
|
|
except ImportError:
|
|
from urllib2 import HTTPError # PY2
|
|
|
|
from gerrit import (
|
|
abandon, add_reviewers, create_url_opener_from_args, delete_reviewer,
|
|
delete_topic, find_gerrit_name, query_change_lists, restore, set_hashtags,
|
|
set_review, set_topic, submit
|
|
)
|
|
|
|
|
|
def _get_labels_from_args(args):
|
|
"""Collect and check labels from args."""
|
|
if not args.label:
|
|
return None
|
|
labels = {}
|
|
for (name, value) in args.label:
|
|
try:
|
|
labels[name] = int(value)
|
|
except ValueError:
|
|
print('error: Label {} takes integer, but {} is specified'
|
|
.format(name, value), file=sys.stderr)
|
|
return labels
|
|
|
|
|
|
# pylint: disable=redefined-builtin
|
|
def _print_change_lists(change_lists, file=sys.stdout):
|
|
"""Print matching change lists for each projects."""
|
|
change_lists = sorted(
|
|
change_lists, key=lambda change: (change['project'], change['_number']))
|
|
|
|
prev_project = None
|
|
print('Change Lists:', file=file)
|
|
for change in change_lists:
|
|
project = change['project']
|
|
if project != prev_project:
|
|
print(' ', project, file=file)
|
|
prev_project = project
|
|
|
|
change_id = change['change_id']
|
|
revision_sha1 = change['current_revision']
|
|
revision = change['revisions'][revision_sha1]
|
|
subject = revision['commit']['subject']
|
|
print(' ', change_id, '--', subject, file=file)
|
|
|
|
|
|
def _confirm(question):
|
|
"""Confirm before proceeding."""
|
|
try:
|
|
if input(question + ' [yn] ').lower() not in {'y', 'yes'}:
|
|
print('Cancelled', file=sys.stderr)
|
|
sys.exit(1)
|
|
except KeyboardInterrupt:
|
|
print('Cancelled', file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def _parse_args():
|
|
"""Parse command line options."""
|
|
parser = argparse.ArgumentParser()
|
|
|
|
parser.add_argument('query', help='Change list query string')
|
|
parser.add_argument('-g', '--gerrit', help='Gerrit review URL')
|
|
|
|
parser.add_argument('--gitcookies',
|
|
default=os.path.expanduser('~/.gitcookies'),
|
|
help='Gerrit cookie file')
|
|
parser.add_argument('--limits', default=1000,
|
|
help='Max number of change lists')
|
|
|
|
parser.add_argument('-l', '--label', nargs=2, action='append',
|
|
help='Labels to be added')
|
|
parser.add_argument('-m', '--message', help='Review message')
|
|
|
|
parser.add_argument('--submit', action='store_true', help='Submit a CL')
|
|
|
|
parser.add_argument('--abandon', help='Abandon a CL with a message')
|
|
parser.add_argument('--restore', action='store_true', help='Restore a CL')
|
|
|
|
parser.add_argument('--add-hashtag', action='append', help='Add hashtag')
|
|
parser.add_argument('--remove-hashtag', action='append',
|
|
help='Remove hashtag')
|
|
parser.add_argument('--delete-hashtag', action='append',
|
|
help='Remove hashtag', dest='remove_hashtag')
|
|
|
|
parser.add_argument('--set-topic', help='Set topic name')
|
|
parser.add_argument('--delete-topic', action='store_true',
|
|
help='Delete topic name')
|
|
parser.add_argument('--remove-topic', action='store_true',
|
|
help='Delete topic name', dest='delete_topic')
|
|
parser.add_argument('--add-reviewer', action='append', default=[],
|
|
help='Add reviewer')
|
|
parser.add_argument('--delete-reviewer', action='append', default=[],
|
|
help='Delete reviewer')
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def _has_task(args):
|
|
"""Determine whether a task has been specified in the arguments."""
|
|
if args.label is not None or args.message is not None:
|
|
return True
|
|
if args.submit:
|
|
return True
|
|
if args.abandon is not None:
|
|
return True
|
|
if args.restore:
|
|
return True
|
|
if args.add_hashtag or args.remove_hashtag:
|
|
return True
|
|
if args.set_topic or args.delete_topic:
|
|
return True
|
|
if args.add_reviewer or args.delete_reviewer:
|
|
return True
|
|
return False
|
|
|
|
|
|
_SEP_SPLIT = '=' * 79
|
|
_SEP = '-' * 79
|
|
|
|
|
|
def _print_error(change, res_code, res_body, res_json):
|
|
"""Print the error message"""
|
|
|
|
change_id = change['change_id']
|
|
project = change['project']
|
|
revision_sha1 = change['current_revision']
|
|
revision = change['revisions'][revision_sha1]
|
|
subject = revision['commit']['subject']
|
|
|
|
print(_SEP_SPLIT, file=sys.stderr)
|
|
print('Project:', project, file=sys.stderr)
|
|
print('Change-Id:', change_id, file=sys.stderr)
|
|
print('Subject:', subject, file=sys.stderr)
|
|
print('HTTP status code:', res_code, file=sys.stderr)
|
|
if res_json:
|
|
print(_SEP, file=sys.stderr)
|
|
json.dump(res_json, sys.stderr, indent=4,
|
|
separators=(', ', ': '))
|
|
print(file=sys.stderr)
|
|
elif res_body:
|
|
print(_SEP, file=sys.stderr)
|
|
print(res_body.decode('utf-8'), file=sys.stderr)
|
|
print(_SEP_SPLIT, file=sys.stderr)
|
|
|
|
|
|
def _do_task(change, func, *args, **kwargs):
|
|
"""Process a task and report errors when necessary."""
|
|
|
|
res_code, res_body, res_json = func(*args)
|
|
|
|
if res_code != kwargs.get('expected_http_code', 200):
|
|
_print_error(change, res_code, res_body, res_json)
|
|
|
|
errors = kwargs.get('errors')
|
|
if errors is not None:
|
|
errors['num_errors'] += 1
|
|
|
|
|
|
def main():
|
|
"""Set review labels to selected change lists"""
|
|
|
|
# Parse and check the command line options
|
|
args = _parse_args()
|
|
|
|
if not args.gerrit:
|
|
try:
|
|
args.gerrit = find_gerrit_name()
|
|
# pylint: disable=bare-except
|
|
except:
|
|
print('gerrit instance not found, use [-g GERRIT]')
|
|
sys.exit(1)
|
|
|
|
if not _has_task(args):
|
|
print('error: Either --label, --message, --submit, --abandon, --restore, '
|
|
'--add-hashtag, --remove-hashtag, --set-topic, --delete-topic, '
|
|
'--add-reviewer or --delete-reviewer must be specified',
|
|
file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Convert label arguments
|
|
labels = _get_labels_from_args(args)
|
|
|
|
# Convert reviewer arguments
|
|
new_reviewers = [{'reviewer': name} for name in args.add_reviewer]
|
|
|
|
# Load authentication credentials
|
|
url_opener = create_url_opener_from_args(args)
|
|
|
|
# Retrieve change lists
|
|
change_lists = query_change_lists(
|
|
url_opener, args.gerrit, args.query, args.limits)
|
|
if not change_lists:
|
|
print('error: No matching change lists.', file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Print matching lists
|
|
_print_change_lists(change_lists, file=sys.stdout)
|
|
|
|
# Confirm
|
|
_confirm('Do you want to continue?')
|
|
|
|
# Post review votes
|
|
errors = {'num_errors': 0}
|
|
for change in change_lists:
|
|
if args.label or args.message:
|
|
_do_task(change, set_review, url_opener, args.gerrit, change['id'],
|
|
labels, args.message, errors=errors)
|
|
if args.add_hashtag or args.remove_hashtag:
|
|
_do_task(change, set_hashtags, url_opener, args.gerrit,
|
|
change['id'], args.add_hashtag, args.remove_hashtag,
|
|
errors=errors)
|
|
if args.set_topic:
|
|
_do_task(change, set_topic, url_opener, args.gerrit, change['id'],
|
|
args.set_topic, errors=errors)
|
|
if args.delete_topic:
|
|
_do_task(change, delete_topic, url_opener, args.gerrit,
|
|
change['id'], expected_http_code=204, errors=errors)
|
|
if args.submit:
|
|
_do_task(change, submit, url_opener, args.gerrit, change['id'],
|
|
errors=errors)
|
|
if args.abandon:
|
|
_do_task(change, abandon, url_opener, args.gerrit, change['id'],
|
|
args.abandon, errors=errors)
|
|
if args.restore:
|
|
_do_task(change, restore, url_opener, args.gerrit, change['id'],
|
|
errors=errors)
|
|
if args.add_reviewer:
|
|
_do_task(change, add_reviewers, url_opener, args.gerrit,
|
|
change['id'], new_reviewers, errors=errors)
|
|
for name in args.delete_reviewer:
|
|
_do_task(change, delete_reviewer, url_opener, args.gerrit,
|
|
change['id'], name, expected_http_code=204, errors=errors)
|
|
|
|
|
|
if errors['num_errors']:
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|