| # Copyright 2018 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import collections |
| |
| # PatchInput describes the input to a `jiri patch` command. These are decoded from the |
| # list of JSON objects in a patchfile (see below). |
| # |
| # Properties: |
| # ref (str): The gerrit change ref (e.g refs/changes/aa/aabbcc/n) |
| # host (str): The code review host (e.g. fuchsia-review.googlesource.com) |
| # project (str): The patch project (e.g. fuchsia) |
| # |
| # Usage: |
| # These inputs are typically specified within a patchfile at the root of a project's |
| # directory. When checking out from a gerrit patchset (i.e. when running on CQ), |
| # CheckoutApi will patch any changes listed in this file. |
| PatchInput = collections.namedtuple("PatchInput", "ref host project") |
| |
| |
| class PatchFile(object): |
| """A file used to patch one or more changes from unrelated projects into a |
| workspace. |
| |
| The PatchFile should contain a list of JSON objects with the following structure: |
| |
| [ |
| { |
| "ref": "refs/changes/56/123456/3", |
| "host": "fuchsia-review.googlesource.com", |
| "project": "project", |
| } |
| ] |
| """ |
| |
| @staticmethod |
| def from_json(js): |
| """Unmarshals a PatchFile from JSON.""" |
| patch_inputs = [] |
| for js_object in js: |
| patch_inputs.append(PatchInput(**js_object)) |
| |
| return PatchFile(patch_inputs) |
| |
| def __init__(self, patch_inputs): |
| self.patch_inputs = patch_inputs |
| |
| @property |
| def inputs(self): |
| return self.patch_inputs |
| |
| def validate(self, gerrit_change): |
| """Verifies the following about this PatchFile: |
| |
| 1. No input overwrites the Gerrit change that is currently being tested. |
| 2. No two inputs patch over one another. |
| |
| Returns: |
| A ValueError that should be raised as a StepFailure, if validation fails. Else |
| None. |
| """ |
| |
| def create_key(project, host): |
| """Produces a unique ID for a project + host combination.""" |
| return "%s/%s" % (project, host) |
| |
| # Maps validated PatchInput keys to their PatchInputs. |
| validated = {} |
| |
| # The key for the original Gerrit change that is being tested. |
| gerrit_patch_key = create_key(gerrit_change.project, gerrit_change.host) |
| |
| for patch_input in self.patch_inputs: |
| patch_key = create_key(patch_input.project, patch_input.host) |
| |
| # User cannot use patches.json to overwrite the original gerrit change. |
| if patch_key == gerrit_patch_key: |
| return ValueError( |
| ( |
| "This patch overwrites the original gerrit change: %s\n" |
| "Inline this patch into the change instead of specifying in patches.json" |
| ) |
| % str(patch_input) |
| ) |
| |
| # User cannot patch multiple changes to the same project. Those changes should |
| # be tested locally. |
| if validated.get(patch_key, None): |
| return ValueError( |
| ( |
| "Found patch that ovewrites a previous patch. These changes should be" |
| "tested together locally instead of through patches.json:\n" |
| "Original: %(original)s\nDuplicate: %(duplicate)s." |
| ) |
| % dict( |
| original=str(validated[patch_key]), |
| duplicate=str(patch_input), |
| ) |
| ) |
| |
| validated[patch_key] = patch_input |