blob: 1e5804b6be85bbaed4387aff82f4acdf029c88af [file] [log] [blame]
# Copyright 2011 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Module containing helper class and methods for interacting with Gerrit."""
import logging
import operator
import re
from typing import Tuple
from chromite.lib import config_lib
from chromite.lib import constants
from chromite.lib import cros_build_lib
from chromite.lib import git
from chromite.lib import gob_util
from chromite.lib import parallel
from chromite.lib import patch as cros_patch
from chromite.lib import retry_util
class GerritException(Exception):
"""Base exception, thrown for gerrit failures"""
class QueryHasNoResults(GerritException):
"""Exception thrown when a query returns no results."""
class QueryNotSpecific(GerritException):
"""Thrown when a query needs to identify one CL, but matched multiple."""
class GerritHelper:
"""Helper class to manage interaction with the gerrit-on-borg service."""
# Maximum number of results to return per query.
_GERRIT_MAX_QUERY_RETURN = 500
# Number of processes to run in parallel when fetching from Gerrit. The
# Gerrit team recommended keeping this small to avoid putting too much
# load on the server.
_NUM_PROCESSES = 10
# Fields that appear in gerrit change query results.
MORE_CHANGES = "_more_changes"
def __init__(self, host, remote, print_cmd=True):
"""Initialize.
Args:
host: Hostname (without protocol prefix) of the gerrit server.
remote: The symbolic name of a known remote git host, taken from
cbuildbot.constants.
print_cmd: Determines whether all run invocations will be echoed.
Set to False for quiet operation.
"""
self.host = host
self.remote = remote
self.print_cmd = bool(print_cmd)
self._version = None
@classmethod
def FromRemote(cls, remote, **kwargs):
site_params = config_lib.GetSiteParams()
if remote == site_params.INTERNAL_REMOTE:
host = site_params.INTERNAL_GERRIT_HOST
elif remote == site_params.EXTERNAL_REMOTE:
host = site_params.EXTERNAL_GERRIT_HOST
else:
raise ValueError("Remote %s not supported." % remote)
return cls(host, remote, **kwargs)
@classmethod
def FromGob(cls, gob, **kwargs):
"""Return a helper for a GoB instance."""
site_params = config_lib.GetSiteParams()
host = constants.GOB_HOST % ("%s-review" % gob)
# TODO(phobbs) this will be wrong when "gob" isn't in GOB_REMOTES.
# We should get rid of remotes altogether and just use the host.
return cls(host, site_params.GOB_REMOTES.get(gob, gob), **kwargs)
def SetPrivate(self, change, private, dryrun=False):
"""Sets the private bit on the given CL.
Args:
change: CL number.
private: bool to indicate what value to set for the private bit.
dryrun: If True, only print what would have been done.
"""
if private:
if dryrun:
logging.info('Would have made "%s" private', change)
else:
gob_util.MarkPrivate(self.host, change)
else:
if dryrun:
logging.info('Would have made "%s" public', change)
else:
gob_util.MarkNotPrivate(self.host, change)
def SetAttentionSet(
self,
change: str,
add: Tuple[str, ...] = (),
remove: Tuple[str, ...] = (),
dryrun: bool = False,
notify: str = "ALL",
message: str = "gerrit CLI",
):
"""Modify the attention set of a gerrit change.
Args:
change: ChangeId or change number for a gerrit review.
add: Sequence of email addresses to add to attention set.
remove: Sequence of email addresses to remove from attention set.
dryrun: If True, only print what would have been done.
notify: A string, parameter controlling gerrit's email generation.
message: A string, setting the reason for changing the attention
set.
"""
if add:
if dryrun:
logging.info(
'Would have added %s to "%s" attention set', add, change
)
else:
gob_util.AddAttentionSet(
self.host, change, add, notify=notify, reason=message
)
if remove:
if dryrun:
logging.info(
'Would have removed %s from "%s" attention set',
remove,
change,
)
else:
gob_util.RemoveAttentionSet(
self.host, change, remove, notify=notify, reason=message
)
def SetReviewers(
self, change, add=(), remove=(), dryrun=False, notify="ALL"
):
"""Modify the list of reviewers on a gerrit change.
Args:
change: ChangeId or change number for a gerrit review.
add: Sequence of email addresses of reviewers to add.
remove: Sequence of email addresses of reviewers to remove.
dryrun: If True, only print what would have been done.
notify: A string, parameter controlling gerrit's email generation.
"""
if add:
if dryrun:
logging.info('Would have added %s to "%s"', add, change)
else:
gob_util.AddReviewers(self.host, change, add, notify=notify)
if remove:
if dryrun:
logging.info('Would have removed %s to "%s"', remove, change)
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.
Args:
change_num: A gerrit change number.
verbose: Whether to print more properties of the change
"""
if verbose:
o_params = (
"ALL_REVISIONS",
"ALL_FILES",
"ALL_COMMITS",
"DETAILED_LABELS",
"MESSAGES",
"DOWNLOAD_COMMANDS",
"CHECK",
)
else:
o_params = ("CURRENT_REVISION", "CURRENT_COMMIT")
return gob_util.GetChangeDetail(
self.host, change_num, o_params=o_params
)
def GetRelatedChangesInfo(self, change_num):
"""Returns dict that represents a gerrit API RelatedChangesInfo entity.
Args:
change_num: A gerrit change number.
Returns:
A dict representing a RelatedChangesInfo entity.
"""
return gob_util.GetRelatedChanges(self.host, change_num)
def GrabPatchFromGerrit(self, project, change, commit, must_match=True):
"""Return a cros_patch.GerritPatch representing a gerrit change.
Args:
project: The name of the gerrit project for the change.
change: A ChangeId or gerrit number for the change.
commit: The git commit hash for a patch associated with the change.
must_match: Raise an exception if the change is not found.
"""
query = {"project": project, "commit": commit, "must_match": must_match}
return self.QuerySingleRecord(change, **query)
def IsChangeCommitted(self, change, must_match=False):
"""Check whether a gerrit change has been merged.
Args:
change: A gerrit change number.
must_match: Raise an exception if the change is not found. If this
is False, then a missing change will return None.
"""
change = gob_util.GetChange(self.host, change)
if not change:
if must_match:
raise QueryHasNoResults(
"Could not query for change %s" % change
)
return
return change.get("status") == "MERGED"
def GetLatestSHA1ForBranch(self, project, branch):
"""Return the git hash at the tip of a branch."""
url = "%s://%s/%s" % (gob_util.GIT_PROTOCOL, self.host, project)
cmd = ["ls-remote", url, "refs/heads/%s" % branch]
try:
result = git.RunGit(".", cmd, print_cmd=self.print_cmd)
if result:
return result.stdout.split()[0]
except cros_build_lib.RunCommandError:
logging.error(
'Command "%s" failed.',
cros_build_lib.CmdToStr(cmd),
exc_info=True,
)
def QuerySingleRecord(self, change=None, **kwargs):
"""Free-form query of a gerrit change that expects a single result.
Args:
change: A gerrit change number.
**kwargs:
dryrun: Don't query the gerrit server; just return None.
must_match: Raise an exception if the query comes back empty. If
this is False, an unsatisfied query will return None. Refer
to Query() docstring for remaining arguments.
Returns:
If kwargs['raw'] == True, return a python dict representing the
change; otherwise, return a cros_patch.GerritPatch object.
"""
query_kwds = kwargs
dryrun = query_kwds.get("dryrun")
must_match = query_kwds.pop("must_match", True)
results = self.Query(change, **query_kwds)
if dryrun:
return None
elif not results:
if must_match:
raise QueryHasNoResults("Query %s had no results" % (change,))
return None
elif len(results) != 1:
raise QueryNotSpecific(
"Query %s returned too many results: %s" % (change, results)
)
return results[0]
def Query(
self,
change=None,
sort=None,
current_patch=True,
options=(),
dryrun=False,
raw=False,
start=None,
bypass_cache=True,
verbose=False,
convert_results=True,
**kwargs,
):
"""Free-form query for gerrit changes.
Args:
change: ChangeId, git commit hash, or gerrit number for a change.
sort: A functor to extract a sort key from a cros_patch.GerritChange
object, for sorting results. If this is None, results will not
be sorted.
current_patch: If True, ask the gerrit server for extra information
about the latest uploaded patch.
options: Deprecated.
dryrun: If True, don't query the gerrit server; return an empty
list.
raw: If True, return a list of python dict's representing the query
results. Otherwise, return a list of cros_patch.GerritPatch.
start: Offset in the result set to start at.
bypass_cache: Query each change to make sure data is up to date.
verbose: Whether to get all revisions and details about a change.
convert_results: Whether to convert the results from the new json
schema to the old SQL schema.
**kwargs: A dict of query parameters, as described here:
https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
Returns:
A list of python dicts or cros_patch.GerritChange.
"""
query_kwds = kwargs
if options:
raise GerritException(
'"options" argument unsupported on gerrit-on-borg.'
)
url_prefix = gob_util.GetGerritFetchUrl(self.host)
# All possible params are documented at
# pylint: disable=C0301
# https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
o_params = ["DETAILED_ACCOUNTS", "ALL_REVISIONS", "DETAILED_LABELS"]
if current_patch:
o_params.extend(["CURRENT_COMMIT", "CURRENT_REVISION"])
elif verbose and not convert_results:
o_params = [
"ALL_REVISIONS",
"ALL_FILES",
"ALL_COMMITS",
"DETAILED_LABELS",
"MESSAGES",
"DOWNLOAD_COMMANDS",
"CHECK",
]
if change and cros_patch.ParseGerritNumber(change) and not query_kwds:
if dryrun:
logging.info(
"Would have run gob_util.GetChangeDetail(%s, %s)",
self.host,
change,
)
return []
change = self.GetChangeDetail(change, verbose=verbose)
if change is None:
return []
patch_dict = cros_patch.GerritPatch.ConvertQueryResults(
change, self.host
)
if raw:
return [patch_dict]
return [cros_patch.GerritPatch(patch_dict, self.remote, url_prefix)]
# TODO: We should allow querying using a cros_patch.PatchQuery
# object directly.
if change and cros_patch.ParseSHA1(change):
# Use commit:sha1 for accurate query results (crbug.com/358381).
kwargs["commit"] = change
change = None
elif change and cros_patch.ParseChangeID(change):
# Use change:change-id for accurate query results
# (crbug.com/357876).
kwargs["change"] = change
change = None
elif change and cros_patch.ParseFullChangeID(change):
change = cros_patch.ParseFullChangeID(change)
assert change # Help the type checker.
kwargs["change"] = change.change_id
kwargs["project"] = change.project
kwargs["branch"] = change.branch
change = None
if change and query_kwds.get("change"):
raise GerritException(
"Bad query params: provided a change-id-like query,"
' and a "change" search parameter'
)
if dryrun:
logging.info(
"Would have run gob_util.QueryChanges(%s, %s, "
"first_param=%s, limit=%d)",
self.host,
repr(query_kwds),
change,
self._GERRIT_MAX_QUERY_RETURN,
)
return []
start = 0
moar = gob_util.QueryChanges(
self.host,
query_kwds,
first_param=change,
start=start,
limit=self._GERRIT_MAX_QUERY_RETURN,
o_params=o_params,
)
result = list(moar)
while moar and self.MORE_CHANGES in moar[-1]:
start += len(moar)
moar = gob_util.QueryChanges(
self.host,
query_kwds,
first_param=change,
start=start,
limit=self._GERRIT_MAX_QUERY_RETURN,
o_params=o_params,
)
result.extend(moar)
# NOTE: Query results are served from the gerrit cache, which may be
# stale. To make sure the patch information is accurate, re-request each
# query result directly, circumventing the cache. For reference:
# https://code.google.com/p/chromium/issues/detail?id=302072
if bypass_cache:
result = self.GetMultipleChangeDetail(
[x["_number"] for x in result], verbose=verbose
)
if convert_results:
result = [
cros_patch.GerritPatch.ConvertQueryResults(x, self.host)
for x in result
]
if sort:
result = sorted(result, key=operator.itemgetter(sort))
if raw:
return result
return [
cros_patch.GerritPatch(x, self.remote, url_prefix) for x in result
]
def GetMultipleChangeDetail(self, changes, verbose=False):
"""Query the gerrit server for multiple changes using GetChangeDetail.
Args:
changes: A sequence of gerrit change numbers.
verbose: Whether to return more properties of the change.
Returns:
A list of the raw output of GetChangeDetail.
"""
inputs = [[change] for change in changes]
return parallel.RunTasksInProcessPool(
lambda c: self.GetChangeDetail(c, verbose=verbose),
inputs,
processes=self._NUM_PROCESSES,
)
def QueryMultipleCurrentPatchset(self, changes):
"""Query the gerrit server for multiple changes.
Args:
changes: A sequence of gerrit change numbers.
Returns:
A list of cros_patch.GerritPatch.
"""
if not changes:
return
url_prefix = gob_util.GetGerritFetchUrl(self.host)
results = self.GetMultipleChangeDetail(changes)
for change, change_detail in zip(changes, results):
if not change_detail:
raise GerritException(
"Change %s not found on server %s." % (change, self.host)
)
patch_dict = cros_patch.GerritPatch.ConvertQueryResults(
change_detail, self.host
)
yield change, cros_patch.GerritPatch(
patch_dict, self.remote, url_prefix
)
@staticmethod
def _to_changenum(change):
"""Unequivocally return a gerrit change number.
The argument may either be an number, which will be returned unchanged;
or an instance of GerritPatch, in which case its gerrit number will be
returned.
"""
# TODO(davidjames): Deprecate the ability to pass in strings to these
# functions -- API users should just pass in a GerritPatch instead or
# use the gob_util APIs directly.
if isinstance(change, cros_patch.GerritPatch):
return change.gerrit_number
return change
def CreateChange(
self, project: str, branch: str, message: str, publish: bool
) -> cros_patch.GerritPatch:
"""Creates an empty change.
The change will be empty of any file modifications. Use ChangeEdit below
to add file modifications to the change.
Args:
project: The name of the gerrit project for the change.
branch: Branch for the change.
message: Initial commit message for the change.
publish: If True, will publish the CL after uploading. Stays in WIP
mode otherwise.
Returns:
A cros_patch.GerritChange for the created change.
"""
resp = gob_util.CreateChange(
self.host, project, branch, message, publish
)
patch_dict = cros_patch.GerritPatch.ConvertQueryResults(resp, self.host)
return cros_patch.GerritPatch(patch_dict, self.remote, "")
def ChangeEdit(self, change: str, path: str, contents: str) -> None:
"""Attaches file modifications to an open change.
Args:
change: A gerrit change number.
path: Path of the file in the repo to modify.
contents: New contents of the file.
"""
gob_util.ChangeEdit(self.host, change, path, contents)
gob_util.PublishChangeEdit(self.host, change)
def SetReview(
self,
change,
msg=None,
labels=None,
notify="ALL",
reviewers=None,
cc=None,
remove_reviewers=None,
ready=None,
wip=None,
dryrun=False,
):
"""Update the review labels on a gerrit change.
Args:
change: A gerrit change number.
msg: A text comment to post to the review.
labels: A dict of label/value to set on the review.
notify: A string, parameter controlling gerrit's email generation.
reviewers: List of people to add as reviewers.
cc: List of people to add to CC.
remove_reviewers: List of people to remove (reviewers or CC).
NB: This is one option due to Gerrit limitations.
ready: Mark CL as ready.
wip: Mark CL as work-in-progress.
dryrun: If True, don't actually update the review.
"""
if dryrun:
if msg:
logging.info(
'Would have added message "%s" to change "%s".', msg, change
)
if labels:
for key, val in labels.items():
logging.info(
'Would have set label "%s" to "%s" for change "%s".',
key,
val,
change,
)
if reviewers:
logging.info("Would have add %s as reviewers", reviewers)
if cc:
logging.info("Would have add %s to CC", cc)
if remove_reviewers:
logging.info(
"Would have removed %s as reviewer & from CC",
remove_reviewers,
)
if ready:
logging.info("Would mark it as ready")
elif wip:
logging.info("Would mark it as WIP")
return
gob_util.SetReview(
self.host,
self._to_changenum(change),
msg=msg,
labels=labels,
notify=notify,
reviewers=reviewers,
cc=cc,
remove_reviewers=remove_reviewers,
ready=ready,
wip=wip,
)
def SetTopic(self, change, topic, dryrun=False):
"""Update the topic on a gerrit change.
Args:
change: A gerrit change number.
topic: The topic to set the review to.
dryrun: If True, don't actually set the topic.
"""
if dryrun:
logging.info(
'Would have set topic "%s" for change "%s".', topic, change
)
return
gob_util.SetTopic(self.host, self._to_changenum(change), topic=topic)
def SetHashtags(self, change, add, remove, dryrun=False):
"""Add/Remove hashtags for a gerrit change.
Args:
change: A gerrit change number.
add: a list of hashtags to add.
remove: a list of hashtags to remove.
dryrun: If True, don't actually set the hashtag.
"""
if dryrun:
logging.info(
"Would add %r and remove %r for change %s", add, remove, change
)
return
gob_util.SetHashtags(
self.host, self._to_changenum(change), add=add, remove=remove
)
def RemoveReady(self, change, dryrun=False):
"""Set the 'Commit-Queue' label on a |change| to '0'."""
if dryrun:
logging.info("Would have reset Commit-Queue label for %s", change)
return
gob_util.ResetReviewLabels(
self.host,
self._to_changenum(change),
label="Commit-Queue",
notify="OWNER",
)
def SubmitChange(self, change, dryrun=False, notify=None):
"""Land (merge) a gerrit change using the JSON API."""
if dryrun:
logging.info("Would have submitted change %s", change)
return
if isinstance(change, cros_patch.GerritPatch):
rev = change.sha1
else:
rev = None
gob_util.SubmitChange(
self.host, self._to_changenum(change), revision=rev, notify=notify
)
def ReviewedChange(self, change, dryrun=False):
"""Mark a gerrit change as reviewed."""
if dryrun:
logging.info("Would have reviewed change %s", change)
return
gob_util.ReviewedChange(self.host, self._to_changenum(change))
def UnreviewedChange(self, change, dryrun=False):
"""Unmark a gerrit change as reviewed."""
if dryrun:
logging.info("Would have unreviewed change %s", change)
return
gob_util.UnreviewedChange(self.host, self._to_changenum(change))
def IgnoreChange(self, change, dryrun=False):
"""Ignore a gerrit change."""
if dryrun:
logging.info("Would have ignored change %s", change)
return
gob_util.IgnoreChange(self.host, self._to_changenum(change))
def UnignoreChange(self, change, dryrun=False):
"""Unignore a gerrit change."""
if dryrun:
logging.info("Would have unignored change %s", change)
return
gob_util.UnignoreChange(self.host, self._to_changenum(change))
def AbandonChange(self, change, msg="", dryrun=False, notify=None):
"""Mark a gerrit change as 'Abandoned'."""
if dryrun:
logging.info("Would have abandoned change %s", change)
return
gob_util.AbandonChange(
self.host, self._to_changenum(change), msg=msg, notify=notify
)
def RestoreChange(self, change, dryrun=False):
"""Re-activate a previously abandoned gerrit change."""
if dryrun:
logging.info("Would have restored change %s", change)
return
gob_util.RestoreChange(self.host, self._to_changenum(change))
def Delete(self, change, dryrun=False):
"""Delete a gerrit change."""
if dryrun:
logging.info("Would have deleted change %s", change)
return
gob_util.Delete(self.host, self._to_changenum(change))
def CherryPick(
self,
change,
branch,
rev: str = "current",
msg: str = "",
allow_conflicts: bool = False,
dryrun: bool = False,
notify=None,
):
"""Cherry pick a CL to a branch.
Args:
change: A gerrit change number.
branch: The destination branch.
rev: The specific revision to cherry pick back.
msg: An additional message to include.
allow_conflicts: Allow cherry-picks to contain conflicts.
dryrun: If True, don't actually set the hashtag.
notify: Who to send notifications to.
"""
if dryrun:
logging.info(
"Would cherry-pick change %s (revision %s) to branch %s",
change,
rev,
branch,
)
return
return gob_util.CherryPick(
self.host,
self._to_changenum(change),
branch,
rev=rev,
msg=msg,
allow_conflicts=allow_conflicts,
notify=notify,
)
def GetAccount(self, account="self"):
"""Get information about the user account."""
return gob_util.GetAccount(self.host, account=account)
def _get_changenumber_from_stdout(self, stdout):
"""Retrieve the change number written in the URL of the git stdout."""
url = git.GetUrlFromRemoteOutput(stdout)
if not url:
return None
match = re.search(r"(?P<changenum>[0-9]+)$", url)
if match:
return match["changenum"]
return None
def CreateGerritPatch(
self, cwd, remote, ref, dryrun=False, notify="ALL", **kwargs
):
"""Upload a change and retrieve a GerritPatch describing it.
This requires a copy of the project checked out locally. To create a
GerritPatch without a local checkout, use CreateChange() below.
Args:
cwd: The repository that we are working on.
remote: The remote to upload changes to.
ref: The ref where changes will be uploaded to.
dryrun: If True, then return None.
notify: A string, parameter controlling gerrit's email generation.
**kwargs: Keyword arguments to be passed to QuerySingleRecord.
Returns:
A GerritPatch object describing the change for the HEAD commit.
"""
# If dryrun is true then skip all network calls and return None.
if dryrun:
logging.info(
"Would have returned a GerritPatch object describing the"
"local changes."
)
return None
# Upload the local changes to remote.
ret = git.RunGit(
cwd, ["push", remote, f"HEAD:refs/for/{ref}%notify={notify}"]
)
change_number = self._get_changenumber_from_stdout(ret.stdout)
# If we fail to grab a change number from the stdout then fall back to
# the ChangeID.
change_number = change_number or git.GetChangeId(cwd)
def PatchQuery():
"""Retrieve the GerritPatch describing the change."""
return self.QuerySingleRecord(change=change_number, **kwargs)
return retry_util.RetryException(
QueryHasNoResults, 5, PatchQuery, sleep=1
)
def GetGerritPatchInfo(patches):
"""Query Gerrit server for patch information using string queries.
Args:
patches: A list of patch IDs to query. Internal patches start with a
'*'.
Returns:
A list of GerritPatch objects describing each patch. Only the first
instance of a requested patch is returned.
Raises:
PatchException: if a patch can't be found.
ValueError: if a query string cannot be converted to a PatchQuery
object.
"""
return GetGerritPatchInfoWithPatchQueries(
[cros_patch.ParsePatchDep(p) for p in patches]
)
def GetGerritPatchInfoWithPatchQueries(patches):
"""Query Gerrit server for patch information using PatchQuery objects.
Args:
patches: A list of PatchQuery objects to query.
Returns:
A list of GerritPatch objects describing each patch. Only the first
instance of a requested patch is returned.
Raises:
PatchException if a patch can't be found.
"""
site_params = config_lib.GetSiteParams()
seen = set()
results = []
order = {k.ToGerritQueryText(): idx for (idx, k) in enumerate(patches)}
for remote in site_params.CHANGE_PREFIX.keys():
helper = GetGerritHelper(remote)
raw_ids = [x.ToGerritQueryText() for x in patches if x.remote == remote]
for k, change in helper.QueryMultipleCurrentPatchset(raw_ids):
# return a unique list, while maintaining the ordering of the first
# seen instance of each patch. Do this to ensure whatever ordering
# the user is trying to enforce, we honor; lest it break on
# cherry-picking.
if change.id not in seen:
results.append((order[k], change))
seen.add(change.id)
return [change for _idx, change in sorted(results)]
def GetGerritHelper(remote=None, gob=None, **kwargs):
"""Return a GerritHelper instance for interacting with the given remote."""
if gob:
return GerritHelper.FromGob(gob, **kwargs)
else:
return GerritHelper.FromRemote(remote, **kwargs)
def GetGerritHelperForChange(change):
"""Return a usable GerritHelper instance for this change.
If you need a GerritHelper for a specific change, get it via this
function.
"""
return GetGerritHelper(change.remote)
def GetCrosInternal(**kwargs):
"""Convenience method for accessing private ChromeOS gerrit."""
site_params = config_lib.GetSiteParams()
return GetGerritHelper(site_params.INTERNAL_REMOTE, **kwargs)
def GetCrosExternal(**kwargs):
"""Convenience method for accessing public ChromiumOS gerrit."""
site_params = config_lib.GetSiteParams()
return GetGerritHelper(site_params.EXTERNAL_REMOTE, **kwargs)
def GetChangeRef(change_number, patchset=None):
"""Given a change number, return the refs/changes/* space for it.
Args:
change_number: The gerrit change number you want a refspec for.
patchset: If given it must either be an integer or '*'. When given, the
returned refspec is for that exact patchset. If '*' is given, it's
used for pulling down all patchsets for that change.
Returns:
A git refspec.
"""
change_number = int(change_number)
s = "refs/changes/%02i/%i" % (change_number % 100, change_number)
if patchset is not None:
s += "/%s" % ("*" if patchset == "*" else int(patchset))
return s