Browse Source

gerrit: add createchange changeedit publishchangeedit

Add calls to support creating changes, as well as editing and publishing
them in gerrit_client and gerrit_util.

Bug=b:182613582

Change-Id: I0514cf08dce63ab29d99d4485d96fa124006326a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/2800811
Auto-Submit: LaMont Jones <lamontjones@chromium.org>
Reviewed-by: Dirk Pranke <dpranke@google.com>
Reviewed-by: Josip Sokcevic <sokcevic@google.com>
Commit-Queue: LaMont Jones <lamontjones@chromium.org>
LaMont Jones 4 years ago
parent
commit
9eed4238d8
3 changed files with 134 additions and 0 deletions
  1. 58 0
      gerrit_client.py
  2. 42 0
      gerrit_util.py
  3. 34 0
      tests/gerrit_client_test.py

+ 58 - 0
gerrit_client.py

@@ -153,6 +153,64 @@ def CMDchanges(parser, args):
   write_result(result, opt)
 
 
+@subcommand.usage('[args ...]')
+def CMDcreatechange(parser, args):
+  parser.add_option('-s', '--subject', help='subject for change')
+  parser.add_option('-b',
+                    '--branch',
+                    default='main',
+                    help='target branch for change')
+  parser.add_option(
+      '-p',
+      '--param',
+      dest='params',
+      action='append',
+      help='repeatable field value parameter, format: -p key=value')
+
+  (opt, args) = parser.parse_args(args)
+  for p in opt.params:
+    assert '=' in p, '--param is key=value, not "%s"' % p
+
+  result = gerrit_util.CreateChange(
+      urlparse.urlparse(opt.host).netloc,
+      opt.project,
+      branch=opt.branch,
+      subject=opt.subject,
+      params=list(tuple(p.split('=', 1)) for p in opt.params),
+  )
+  logging.info(result)
+  write_result(result, opt)
+
+
+@subcommand.usage('[args ...]')
+def CMDchangeedit(parser, args):
+  parser.add_option('-c', '--change', type=int, help='change number')
+  parser.add_option('--path', help='path for file')
+  parser.add_option('--file', help='file to place at |path|')
+
+  (opt, args) = parser.parse_args(args)
+
+  with open(opt.file) as f:
+    data = f.read()
+  result = gerrit_util.ChangeEdit(
+      urlparse.urlparse(opt.host).netloc, opt.change, opt.path, data)
+  logging.info(result)
+  write_result(result, opt)
+
+
+@subcommand.usage('[args ...]')
+def CMDpublishchangeedit(parser, args):
+  parser.add_option('-c', '--change', type=int, help='change number')
+  parser.add_option('--notify', help='whether to notify')
+
+  (opt, args) = parser.parse_args(args)
+
+  result = gerrit_util.PublishChangeEdit(
+      urlparse.urlparse(opt.host).netloc, opt.change, opt.notify)
+  logging.info(result)
+  write_result(result, opt)
+
+
 @subcommand.usage('')
 def CMDabandon(parser, args):
   parser.add_option('-c', '--change', type=int, help='change number')

+ 42 - 0
gerrit_util.py

@@ -737,6 +737,25 @@ def SubmitChange(host, change, wait_for_merge=True):
   return ReadHttpJsonResponse(conn)
 
 
+def PublishChangeEdit(host, change, notify=True):
+  """Publish a Gerrit change edit."""
+  path = 'changes/%s/edit:publish' % change
+  body = {'notify': 'ALL' if notify else 'NONE'}
+  conn = CreateHttpConn(host, path, reqtype='POST', body=body)
+  return ReadHttpJsonResponse(conn, accept_statuses=(204, ))
+
+
+def ChangeEdit(host, change, path, data):
+  """Puts content of a file into a change edit."""
+  path = 'changes/%s/edit/%s' % (change, urllib.parse.quote(path, ''))
+  body = {
+      'binary_content':
+      'data:text/plain;base64,%s' % base64.b64encode(data.encode('utf-8'))
+  }
+  conn = CreateHttpConn(host, path, reqtype='PUT', body=body)
+  return ReadHttpJsonResponse(conn, accept_statuses=(204, 409))
+
+
 def HasPendingChangeEdit(host, change):
   conn = CreateHttpConn(host, 'changes/%s/edit' % change)
   try:
@@ -930,6 +949,29 @@ def ResetReviewLabels(host, change, label, value='0', message=None,
                    'a new patchset was uploaded.' % change)
 
 
+def CreateChange(host, project, branch='main', subject='', params=()):
+  """
+  Creates a new change.
+
+  Args:
+    params: A list of additional ChangeInput specifiers, as documented here:
+        (e.g. ('is_private', 'true') to mark the change private.
+        https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-input
+
+  Returns:
+    ChangeInfo for the new change.
+  """
+  path = 'changes/'
+  body = {'project': project, 'branch': branch, 'subject': subject}
+  body.update({k: v for k, v in params})
+  for key in 'project', 'branch', 'subject':
+    if not body[key]:
+      raise GerritError(200, '%s is required' % key.title())
+
+  conn = CreateHttpConn(host, path, reqtype='POST', body=body)
+  return ReadHttpJsonResponse(conn, accept_statuses=[201])
+
+
 def CreateGerritBranch(host, project, branch, commit):
   """Creates a new branch from given project and commit
 

+ 34 - 0
tests/gerrit_client_test.py

@@ -13,9 +13,11 @@ import unittest
 if sys.version_info.major == 2:
   from StringIO import StringIO
   import mock
+  BUILTIN_OPEN = '__builtin__.open'
 else:
   from io import StringIO
   from unittest import mock
+  BUILTIN_OPEN = 'builtins.open'
 
 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 
@@ -54,6 +56,38 @@ class TestGerritClient(unittest.TestCase):
         start=20,
         o_params=['op1', 'op2'])
 
+  @mock.patch('gerrit_util.CreateChange', return_value={})
+  def test_createchange(self, util_mock):
+    gerrit_client.main([
+        'createchange', '--host', 'https://example.org/foo', '--project',
+        'project', '--branch', 'main', '--subject', 'subject', '-p',
+        'work_in_progress=true'
+    ])
+    util_mock.assert_called_once_with('example.org',
+                                      'project',
+                                      branch='main',
+                                      subject='subject',
+                                      params=[('work_in_progress', 'true')])
+
+  @mock.patch(BUILTIN_OPEN, mock.mock_open())
+  @mock.patch('gerrit_util.ChangeEdit', return_value='')
+  def test_changeedit(self, util_mock):
+    open().read.return_value = 'test_data'
+    gerrit_client.main([
+        'changeedit', '--host', 'https://example.org/foo', '--change', '1',
+        '--path', 'path/to/file', '--file', '/my/foo'
+    ])
+    util_mock.assert_called_once_with('example.org', 1, 'path/to/file',
+                                      'test_data')
+
+  @mock.patch('gerrit_util.PublishChangeEdit', return_value='')
+  def test_publishchangeedit(self, util_mock):
+    gerrit_client.main([
+        'publishchangeedit', '--host', 'https://example.org/foo', '--change',
+        '1', '--notify', 'yes'
+    ])
+    util_mock.assert_called_once_with('example.org', 1, 'yes')
+
   @mock.patch('gerrit_util.AbandonChange', return_value='')
   def test_abandon(self, util_mock):
     gerrit_client.main([