gerrit: Add wip/ready to control the Work-In-Progress state

Add wip and ready commands to respectively set and clear the
Work-In-Progress bits of the given CLs, as per:

https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-work-in-pogress
https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-ready-for-review

BUG=none
TEST=gerrit wip 2599501; gerrit ready 2599501

Change-Id: Iafa76a4a9ee74c6fde444aa1a829812a5fd8a627
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/2639118
Tested-by: Tomasz Figa <tfiga@chromium.org>
Auto-Submit: Tomasz Figa <tfiga@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Commit-Queue: Mike Frysinger <vapier@chromium.org>
diff --git a/lib/gerrit.py b/lib/gerrit.py
index 4a57e64..aa1e418 100644
--- a/lib/gerrit.py
+++ b/lib/gerrit.py
@@ -133,6 +133,26 @@
       else:
         gob_util.RemoveReviewers(self.host, change, remove, notify=notify)
 
+  def SetWorkInProgress(self, change, wip, msg='', dryrun=False):
+    """Sets the work in progress bit on the given CL.
+
+    Args:
+      change: CL number.
+      wip: bool to indicate what value to set for the work in progress bit.
+      msg: Message to post to the CL.
+      dryrun: If True, only print what would have been done.
+    """
+    if wip:
+      if dryrun:
+        logging.info('Would have made "%s" work in progress', change)
+      else:
+        gob_util.MarkWorkInProgress(self.host, change, msg)
+    else:
+      if dryrun:
+        logging.info('Would have made "%s" ready for review', change)
+      else:
+        gob_util.MarkReadyForReview(self.host, change, msg)
+
   def GetChangeDetail(self, change_num, verbose=False):
     """Return detailed information about a gerrit change.
 
diff --git a/lib/gob_util.py b/lib/gob_util.py
index 3354ab2..ab64daf 100644
--- a/lib/gob_util.py
+++ b/lib/gob_util.py
@@ -657,6 +657,32 @@
     )
 
 
+def MarkWorkInProgress(host, change, msg=''):
+  """Marks the given CL as Work-In-Progress.
+
+  Args:
+    host: The gob host to interact with.
+    change: CL number on the given host.
+    msg: Message to post together with the action.
+  """
+  path = '%s/wip' % _GetChangePath(change)
+  body = {'message': msg}
+  return FetchUrlJson(host, path, reqtype='POST', body=body, ignore_404=False)
+
+
+def MarkReadyForReview(host, change, msg=''):
+  """Marks the given CL as Ready-For-Review.
+
+  Args:
+    host: The gob host to interact with.
+    change: CL number on the given host.
+    msg: Message to post together with the action.
+  """
+  path = '%s/ready' % _GetChangePath(change)
+  body = {'message': msg}
+  return FetchUrlJson(host, path, reqtype='POST', body=body, ignore_404=False)
+
+
 def GetReviewers(host, change):
   """Get information about all reviewers attached to a change.
 
diff --git a/scripts/gerrit.py b/scripts/gerrit.py
index 9bf7907..bc86825 100644
--- a/scripts/gerrit.py
+++ b/scripts/gerrit.py
@@ -580,6 +580,28 @@
     helper.RestoreChange(cl, dryrun=opts.dryrun)
 
 
+class ActionWorkInProgress(_ActionSimpleParallelCLs):
+  """Mark CLs as work in progress"""
+
+  COMMAND = 'wip'
+
+  @staticmethod
+  def _process_one(helper, cl, opts):
+    """Use |helper| to process the single |cl|."""
+    helper.SetWorkInProgress(cl, True, dryrun=opts.dryrun)
+
+
+class ActionReadyForReview(_ActionSimpleParallelCLs):
+  """Mark CLs as ready for review"""
+
+  COMMAND = 'ready'
+
+  @staticmethod
+  def _process_one(helper, cl, opts):
+    """Use |helper| to process the single |cl|."""
+    helper.SetWorkInProgress(cl, False, dryrun=opts.dryrun)
+
+
 class ActionReviewers(UserAction):
   """Add/remove reviewers' emails for a CL (prepend with '~' to remove)"""