diff --git a/gerrit_client.py b/gerrit_client.py new file mode 100644 index 000000000..ed81d75d2 --- /dev/null +++ b/gerrit_client.py @@ -0,0 +1,105 @@ +#!/usr/bin/python +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Simple client for the Gerrit REST API. + +Example usage: + ./gerrit_client.py [command] [args]"" +""" + +from __future__ import print_function + +import json +import logging +import optparse +import subcommand +import sys +import urllib +import urlparse + +from third_party import colorama +import fix_encoding +import gerrit_util +import setup_color + +__version__ = '0.1' +# Shortcut since it quickly becomes redundant. +Fore = colorama.Fore + + +def write_result(result, opt): + if opt.json_file: + with open(opt.json_file, 'w') as json_file: + json_file.write(json.dumps(result)) + + +@subcommand.usage('[args ...]') +def CMDbranchinfo(parser, args): + parser.add_option('--branch', dest='branch', help='branch name') + + (opt, args) = parser.parse_args(args) + host = urlparse.urlparse(opt.host).netloc + project = urllib.quote_plus(opt.project) + branch = urllib.quote_plus(opt.branch) + result = gerrit_util.GetGerritBranch(host, project, branch) + logging.info(result) + write_result(result, opt) + +@subcommand.usage('[args ...]') +def CMDbranch(parser, args): + parser.add_option('--branch', dest='branch', help='branch name') + parser.add_option('--commit', dest='commit', help='commit hash') + + (opt, args) = parser.parse_args(args) + + project = urllib.quote_plus(opt.project) + host = urlparse.urlparse(opt.host).netloc + branch = urllib.quote_plus(opt.branch) + commit = urllib.quote_plus(opt.commit) + result = gerrit_util.CreateGerritBranch(host, project, branch, commit) + logging.info(result) + write_result(result, opt) + + +class OptionParser(optparse.OptionParser): + """Creates the option parse and add --verbose support.""" + def __init__(self, *args, **kwargs): + optparse.OptionParser.__init__( + self, *args, prog='git cl', version=__version__, **kwargs) + self.add_option( + '--verbose', action='count', default=0, + help='Use 2 times for more debugging info') + self.add_option('--host', dest='host', help='Url of host.') + self.add_option('--project', dest='project', help='project name') + self.add_option( + '--json_file', dest='json_file', help='output json filepath') + + def parse_args(self, args=None, values=None): + options, args = optparse.OptionParser.parse_args(self, args, values) + levels = [logging.WARNING, logging.INFO, logging.DEBUG] + logging.basicConfig(level=levels[min(options.verbose, len(levels) - 1)]) + return options, args + + +def main(argv): + if sys.hexversion < 0x02060000: + print('\nYour python version %s is unsupported, please upgrade.\n' + %(sys.version.split(' ', 1)[0],), + file=sys.stderr) + return 2 + dispatcher = subcommand.CommandDispatcher(__name__) + return dispatcher.execute(OptionParser(), argv) + + +if __name__ == '__main__': + # These affect sys.stdout so do it outside of main() to simplify mocks in + # unit testing. + fix_encoding.fix_encoding() + setup_color.init() + try: + sys.exit(main(sys.argv[1:])) + except KeyboardInterrupt: + sys.stderr.write('interrupted\n') + sys.exit(1) \ No newline at end of file diff --git a/gerrit_util.py b/gerrit_util.py index 95946468c..ef7edc11d 100755 --- a/gerrit_util.py +++ b/gerrit_util.py @@ -772,6 +772,39 @@ def ResetReviewLabels(host, change, label, value='0', message=None, 'a new patchset was uploaded.' % change) +def CreateGerritBranch(host, project, branch, commit): + """ + Create a new branch from given project and commit + https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#create-branch + + Returns: + A JSON with 'ref' key + """ + path = 'projects/%s/branches/%s' % (project, branch) + body = {'revision': commit} + conn = CreateHttpConn(host, path, reqtype='PUT', body=body) + response = ReadHttpJsonResponse(conn) + if response: + return response + raise GerritError(200, 'Unable to create gerrit branch') + + +def GetGerritBranch(host, project, branch): + """ + Get a branch from given project and commit + https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-branch + + Returns: + A JSON object with 'revision' key + """ + path = 'projects/%s/branches/%s' % (project, branch) + conn = CreateHttpConn(host, path, reqtype='GET') + response = ReadHttpJsonResponse(conn) + if response: + return response + raise GerritError(200, 'Unable to get gerrit branch') + + @contextlib.contextmanager def tempdir(): tdir = None diff --git a/recipe_modules/gerrit/__init__.py b/recipe_modules/gerrit/__init__.py new file mode 100644 index 000000000..12ee27476 --- /dev/null +++ b/recipe_modules/gerrit/__init__.py @@ -0,0 +1,6 @@ +DEPS = [ + 'recipe_engine/json', + 'recipe_engine/path', + 'recipe_engine/python', + 'recipe_engine/raw_io', +] diff --git a/recipe_modules/gerrit/api.py b/recipe_modules/gerrit/api.py new file mode 100644 index 000000000..3c63aed95 --- /dev/null +++ b/recipe_modules/gerrit/api.py @@ -0,0 +1,63 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from recipe_engine import recipe_api + +class GerritApi(recipe_api.RecipeApi): + """Module for interact with gerrit endpoints""" + + def __call__(self, name, cmd, infra_step=True, **kwargs): + """Wrapper for easy calling of gerrit_utils steps.""" + assert isinstance(cmd, (list, tuple)) + prefix = 'gerrit ' + + kwargs.setdefault('env', {}) + kwargs['env'].setdefault('PATH', '%(PATH)s') + kwargs['env']['PATH'] = self.m.path.pathsep.join([ + kwargs['env']['PATH'], str(self._module.PACKAGE_REPO_ROOT)]) + + return self.m.python(prefix + name, + self.package_repo_resource('gerrit_client.py'), + cmd, + infra_step=infra_step, + **kwargs) + + def create_gerrit_branch(self, host, project, branch, commit, **kwargs): + """ + Create a new branch from given project and commit + + Returns: + the ref of the branch created + """ + args = [ + 'branch', + '--host', host, + '--project', project, + '--branch', branch, + '--commit', commit, + '--json_file', self.m.json.output() + ] + step_name = 'create_gerrit_branch' + step_result = self(step_name, args, **kwargs) + ref = step_result.json.output.get('ref') + return ref + + def get_gerrit_branch(self, host, project, branch, **kwargs): + """ + Get a branch from given project and commit + + Returns: + the revision of the branch + """ + args = [ + 'branchinfo', + '--host', host, + '--project', project, + '--branch', branch, + '--json_file', self.m.json.output() + ] + step_name='get_gerrit_branch' + step_result = self(step_name, args, **kwargs) + revision = step_result.json.output.get('revision') + return revision diff --git a/recipe_modules/gerrit/example.expected/basic.json b/recipe_modules/gerrit/example.expected/basic.json new file mode 100644 index 000000000..7b83e5626 --- /dev/null +++ b/recipe_modules/gerrit/example.expected/basic.json @@ -0,0 +1,64 @@ +[ + { + "cmd": [ + "python", + "-u", + "RECIPE_PACKAGE_REPO[depot_tools]/gerrit_client.py", + "branch", + "--host", + "https://chromium-review.googlesource.com/a", + "--project", + "v8/v8", + "--branch", + "test", + "--commit", + "67ebf73496383c6777035e374d2d664009e2aa5c", + "--json_file", + "/path/to/tmp/json" + ], + "env": { + "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]" + }, + "name": "gerrit create_gerrit_branch", + "~followup_annotations": [ + "@@@STEP_LOG_LINE@json.output@{@@@", + "@@@STEP_LOG_LINE@json.output@ \"can_delete\": true, @@@", + "@@@STEP_LOG_LINE@json.output@ \"ref\": \"refs/heads/test\", @@@", + "@@@STEP_LOG_LINE@json.output@ \"revision\": \"76016386a0d8ecc7b6be212424978bb45959d668\"@@@", + "@@@STEP_LOG_LINE@json.output@}@@@", + "@@@STEP_LOG_END@json.output@@@" + ] + }, + { + "cmd": [ + "python", + "-u", + "RECIPE_PACKAGE_REPO[depot_tools]/gerrit_client.py", + "branchinfo", + "--host", + "https://chromium-review.googlesource.com/a", + "--project", + "v8/v8", + "--branch", + "master", + "--json_file", + "/path/to/tmp/json" + ], + "env": { + "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]" + }, + "name": "gerrit get_gerrit_branch", + "~followup_annotations": [ + "@@@STEP_LOG_LINE@json.output@{@@@", + "@@@STEP_LOG_LINE@json.output@ \"ref\": \"refs/heads/master\", @@@", + "@@@STEP_LOG_LINE@json.output@ \"revision\": \"67ebf73496383c6777035e374d2d664009e2aa5c\"@@@", + "@@@STEP_LOG_LINE@json.output@}@@@", + "@@@STEP_LOG_END@json.output@@@" + ] + }, + { + "name": "$result", + "recipe_result": null, + "status_code": 0 + } +] \ No newline at end of file diff --git a/recipe_modules/gerrit/example.py b/recipe_modules/gerrit/example.py new file mode 100644 index 000000000..9ae9adbdc --- /dev/null +++ b/recipe_modules/gerrit/example.py @@ -0,0 +1,35 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +DEPS = [ + 'gerrit' +] + + +def RunSteps(api): + host = 'https://chromium-review.googlesource.com/a' + project = 'v8/v8' + + branch = 'test' + commit = '67ebf73496383c6777035e374d2d664009e2aa5c' + + data = api.gerrit.create_gerrit_branch(host, project, branch, commit) + assert data == 'refs/heads/test' + + data = api.gerrit.get_gerrit_branch(host, project, 'master') + assert data == '67ebf73496383c6777035e374d2d664009e2aa5c' + + +def GenTests(api): + yield ( + api.test('basic') + + api.step_data( + 'gerrit create_gerrit_branch', + api.gerrit.make_gerrit_create_branch_response_data() + ) + + api.step_data( + 'gerrit get_gerrit_branch', + api.gerrit.make_gerrit_get_branch_response_data() + ) + ) diff --git a/recipe_modules/gerrit/test_api.py b/recipe_modules/gerrit/test_api.py new file mode 100644 index 000000000..bff3fc092 --- /dev/null +++ b/recipe_modules/gerrit/test_api.py @@ -0,0 +1,24 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from recipe_engine import recipe_test_api + + +class GerritTestApi(recipe_test_api.RecipeTestApi): + + def _make_gerrit_response_json(self, data): + return self.m.json.output(data) + + def make_gerrit_create_branch_response_data(self): + return self._make_gerrit_response_json({ + "ref": "refs/heads/test", + "revision": "76016386a0d8ecc7b6be212424978bb45959d668", + "can_delete": True + }) + + def make_gerrit_get_branch_response_data(self): + return self._make_gerrit_response_json({ + "ref": "refs/heads/master", + "revision": "67ebf73496383c6777035e374d2d664009e2aa5c" + }) \ No newline at end of file