Project import generated by Copybara.
GitOrigin-RevId: b578e69f18a543889ded9c57a8f0dffacdb103d8
This commit is contained in:
parent
9567a9803b
commit
dfee7b6196
576 changed files with 98856 additions and 0 deletions
12
third_party/copybara/.bazelproject
vendored
Normal file
12
third_party/copybara/.bazelproject
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
directories:
|
||||||
|
copybara/integration
|
||||||
|
java/com/google/copybara
|
||||||
|
javatests/com/google/copybara
|
||||||
|
third_party
|
||||||
|
|
||||||
|
targets:
|
||||||
|
//copybara/integration/...
|
||||||
|
//java/com/google/copybara/...
|
||||||
|
//javatests/com/google/copybara/...
|
||||||
|
//third_party/...
|
||||||
|
|
11
third_party/copybara/.bazelrc
vendored
Normal file
11
third_party/copybara/.bazelrc
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# Options applied to all Bazel invocations in the workspace.
|
||||||
|
# Enforces UTF-8 encoding in bazel tests.
|
||||||
|
test --test_env='LC_ALL=en_US.UTF-8'
|
||||||
|
test --test_env='LANG=en_US.UTF-8'
|
||||||
|
test --jvmopt='-Dsun.jnu.encoding=UTF-8'
|
||||||
|
test --jvmopt='-Dfile.encoding=UTF-8'
|
||||||
|
build --test_env='LC_ALL=en_US.UTF-8'
|
||||||
|
build --jvmopt='-Dsun.jnu.encoding=UTF-8'
|
||||||
|
build --jvmopt='-Dfile.encoding=UTF-8'
|
||||||
|
build --test_env='LANG=en_US.UTF-8'
|
||||||
|
test --test_env=PATH
|
17
third_party/copybara/.docker/entrypoint.sh
vendored
Normal file
17
third_party/copybara/.docker/entrypoint.sh
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Copyright 2018 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
java -jar /opt/copybara/copybara_deploy.jar $COPYBARA_OPTIONS $COPYBARA_SUBCOMMAND $COPYBARA_CONFIG $COPYBARA_WORKFLOW $COPYBARA_SOURCEREF
|
5
third_party/copybara/.gitignore
vendored
Normal file
5
third_party/copybara/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
bazel-*
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
*.pyc
|
||||||
|
.ijwb
|
9
third_party/copybara/AUTHORS
vendored
Normal file
9
third_party/copybara/AUTHORS
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# This the official list of Copybara authors for copyright purposes.
|
||||||
|
# This file is distinct from the CONTRIBUTORS files.
|
||||||
|
# See the latter for an explanation.
|
||||||
|
|
||||||
|
# Names should be added to this file as:
|
||||||
|
# Name or Organization <email address>
|
||||||
|
# The email address is not required for organizations.
|
||||||
|
|
||||||
|
Google Inc.
|
20
third_party/copybara/CONTRIBUTING.md
vendored
Normal file
20
third_party/copybara/CONTRIBUTING.md
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Contributing guidelines
|
||||||
|
|
||||||
|
## How to become a contributor and submit your own code
|
||||||
|
|
||||||
|
### Contributor License Agreements
|
||||||
|
|
||||||
|
We'd love to accept your patches! Before we can take them, we have to jump a couple of legal hurdles.
|
||||||
|
|
||||||
|
Please fill out either the individual or corporate Contributor License Agreement (CLA).
|
||||||
|
|
||||||
|
* If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html).
|
||||||
|
* If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html).
|
||||||
|
|
||||||
|
Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests.
|
||||||
|
|
||||||
|
***NOTE***: Only original source code from you and other people that have signed the CLA can be accepted into the main repository.
|
||||||
|
|
||||||
|
### Contributing code
|
||||||
|
|
||||||
|
If you have improvements to Copybara, send us your pull requests!
|
50
third_party/copybara/Dockerfile
vendored
Normal file
50
third_party/copybara/Dockerfile
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Copyright 2016 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
FROM l.gcr.io/google/bazel:latest AS build
|
||||||
|
|
||||||
|
WORKDIR /usr/src/copybara
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN bazel build //java/com/google/copybara:copybara_deploy.jar \
|
||||||
|
&& mkdir -p /tmp/copybara \
|
||||||
|
&& cp bazel-bin/java/com/google/copybara/copybara_deploy.jar /tmp/copybara/
|
||||||
|
|
||||||
|
# Fails currently
|
||||||
|
# RUN bazel test //...
|
||||||
|
|
||||||
|
FROM golang:latest AS buildtools
|
||||||
|
|
||||||
|
RUN go get github.com/bazelbuild/buildtools/buildozer
|
||||||
|
RUN go get github.com/bazelbuild/buildtools/buildifier
|
||||||
|
|
||||||
|
FROM openjdk:8-jre-slim
|
||||||
|
ENV COPYBARA_CONFIG=copy.bara.sky \
|
||||||
|
COPYBARA_SUBCOMMAND=migrate \
|
||||||
|
COPYBARA_OPTIONS='' \
|
||||||
|
COPYBARA_WORKFLOW=default \
|
||||||
|
COPYBARA_SOURCEREF=''
|
||||||
|
COPY --from=build /tmp/copybara/ /opt/copybara/
|
||||||
|
COPY --from=buildtools /go/bin/buildozer /go/bin/buildifier /usr/bin/
|
||||||
|
COPY .docker/entrypoint.sh /usr/local/bin/copybara
|
||||||
|
|
||||||
|
RUN chmod +x /usr/local/bin/copybara
|
||||||
|
|
||||||
|
# Install git for fun times
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y git \
|
||||||
|
&& apt-get clean
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
202
third_party/copybara/LICENSE
vendored
Normal file
202
third_party/copybara/LICENSE
vendored
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
165
third_party/copybara/README.md
vendored
Normal file
165
third_party/copybara/README.md
vendored
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
# Copybara
|
||||||
|
|
||||||
|
*A tool for transforming and moving code between repositories.*
|
||||||
|
|
||||||
|
Copybara is a tool used internally at Google. It transforms and moves code between repositories.
|
||||||
|
|
||||||
|
Often, source code needs to exist in multiple repositories, and Copybara allows you to transform
|
||||||
|
and move source code between these repositories. A common case is a project that involves
|
||||||
|
maintaining a confidential repository and a public repository in sync.
|
||||||
|
|
||||||
|
Copybara requires you to choose one of the repositories to be the authoritative repository, so that
|
||||||
|
there is always one source of truth. However, the tool allows contributions to any repository, and
|
||||||
|
any repository can be used to cut a release.
|
||||||
|
|
||||||
|
The most common use case involves repetitive movement of code from one repository to another.
|
||||||
|
Copybara can also be used for moving code once to a new repository.
|
||||||
|
|
||||||
|
Examples uses of Copybara include:
|
||||||
|
|
||||||
|
- Importing sections of code from a confidential repository to a public repository.
|
||||||
|
|
||||||
|
- Importing code from a public repository to a confidential repository.
|
||||||
|
|
||||||
|
- Importing a change from a non-authoritative repository into the authoritative repository. When
|
||||||
|
a change is made in the non-authoritative repository (for example, a contributor in the public
|
||||||
|
repository), Copybara transforms and moves that change into the appropriate place in the
|
||||||
|
authoritative repository. Any merge conflicts are dealt with in the same way as an out-of-date
|
||||||
|
change within the authoritative repository.
|
||||||
|
|
||||||
|
Currently, the only supported type of repository is Git. Copybara also supports reading from Mercurial repositories, but the feature is still experimental.
|
||||||
|
Support for other repositories types will be added in the future.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
core.workflow(
|
||||||
|
name = "default",
|
||||||
|
origin = git.github_origin(
|
||||||
|
url = "https://github.com/google/copybara.git",
|
||||||
|
ref = "master",
|
||||||
|
),
|
||||||
|
destination = git.destination(
|
||||||
|
url = "file:///tmp/foo",
|
||||||
|
),
|
||||||
|
|
||||||
|
# Copy everything but don't remove a README_INTERNAL.txt file if it exists.
|
||||||
|
destination_files = glob(["third_party/copybara/**"], exclude = ["README_INTERNAL.txt"]),
|
||||||
|
|
||||||
|
authoring = authoring.pass_thru("Default email <default@default.com>"),
|
||||||
|
transformations = [
|
||||||
|
core.replace(
|
||||||
|
before = "//third_party/bazel/bashunit",
|
||||||
|
after = "//another/path:bashunit",
|
||||||
|
paths = glob(["**/BUILD"])),
|
||||||
|
core.move("", "third_party/copybara")
|
||||||
|
],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ (mkdir /tmp/foo ; cd /tmp/foo ; git init --bare)
|
||||||
|
$ copybara copy.bara.sky
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting Started using Copybara
|
||||||
|
|
||||||
|
Copybara doesn't have a release process yet, so you need to compile from HEAD. In order to do that
|
||||||
|
you need:
|
||||||
|
|
||||||
|
* [Install JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html).
|
||||||
|
* [Install Bazel](https://docs.bazel.build/versions/master/install.html).
|
||||||
|
* Clone the copybara source locally:
|
||||||
|
* `git clone https://github.com/google/copybara.git`
|
||||||
|
* Build:
|
||||||
|
* `bazel build //java/com/google/copybara`.
|
||||||
|
* `bazel build //java/com/google/copybara:copybara_deploy.jar` to create a executable uberjar.
|
||||||
|
* Tests: `bazel test //...` if you want to ensure you are not using a broken version.
|
||||||
|
|
||||||
|
### Using Intellij with Bazel plugin
|
||||||
|
|
||||||
|
If you use Intellij and the Bazel plugin, use this project configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
directories:
|
||||||
|
copybara/integration
|
||||||
|
java/com/google/copybara
|
||||||
|
javatests/com/google/copybara
|
||||||
|
third_party
|
||||||
|
|
||||||
|
targets:
|
||||||
|
//copybara/integration/...
|
||||||
|
//java/com/google/copybara/...
|
||||||
|
//javatests/com/google/copybara/...
|
||||||
|
//third_party/...
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that configuration files can be stored in any place, even in a local folder. We recommend to
|
||||||
|
use a VCS (like git) to store them; treat them as source code.
|
||||||
|
|
||||||
|
### Using Docker to build and run Copybara
|
||||||
|
|
||||||
|
*NOTE: Docker use is currently experimental, and we encourage feedback or contributions.*
|
||||||
|
|
||||||
|
You can build copybara using Docker like so
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build --rm -t copybara .
|
||||||
|
```
|
||||||
|
|
||||||
|
Once this has finished building you can run the image like so from the root of the code you are trying to use Copybara on:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -it -v "$(pwd)":/usr/src/app copybara copybara
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
A few environment variables exist to allow you to change how you run copybara:
|
||||||
|
* `COPYBARA_CONFIG=copy.bara.sky`
|
||||||
|
* allows you to specify a path to a config file, defaults to root `copy.bara.sky`
|
||||||
|
* `COPYBARA_SUBCOMMAND=migrate`
|
||||||
|
* allows you to change the command run, defaults to `migrate`
|
||||||
|
* `COPYBARA_OPTIONS=''`
|
||||||
|
* allows you to specify options for copybara, defaults to none
|
||||||
|
* `COPYBARA_WORKFLOW=default`
|
||||||
|
* allows you to specify the workflow to run, defaults to `default`
|
||||||
|
* `COPYBARA_SOURCEREF=''`
|
||||||
|
* allows you to specify the sourceref, defaults to none
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run
|
||||||
|
-e COPYBARA_CONFIG='other.config.sky'
|
||||||
|
-e COPYBARA_SUBCOMMAND='validate'
|
||||||
|
-v "$(pwd)":/usr/src/app
|
||||||
|
-it copybara copybara
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Git Config and Credentials
|
||||||
|
|
||||||
|
There are a number of ways by which to share your git config and ssh credentials with the docker container, an example with OS X is below:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run
|
||||||
|
-v ~/.ssh:/root/.ssh
|
||||||
|
-v ~/.gitconfig:/root/.gitconfig
|
||||||
|
-v "$(pwd)":/usr/src/app
|
||||||
|
-it copybara copybara
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
We are still working on the documentation. Here are some resources:
|
||||||
|
|
||||||
|
* [Reference documentation](docs/reference.md)
|
||||||
|
* [Examples](docs/examples.md)
|
||||||
|
|
||||||
|
## Contact us
|
||||||
|
|
||||||
|
If you have any questions about how Copybara works please contact us at our [mailing list](https://groups.google.com/forum/#!forum/copybara-discuss)
|
||||||
|
|
||||||
|
## Optional tips
|
||||||
|
|
||||||
|
* If you want to see the test errors in Bazel, instead of having to cat the logs, add this line to your `~/.bazelrc: *test --test_output=streamed*`.
|
||||||
|
|
186
third_party/copybara/WORKSPACE
vendored
Normal file
186
third_party/copybara/WORKSPACE
vendored
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
# Copyright 2016 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
workspace(name = "copybara")
|
||||||
|
|
||||||
|
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||||
|
load("//third_party:bazel.bzl", "bazel_sha256", "bazel_version")
|
||||||
|
|
||||||
|
RULES_JVM_EXTERNAL_TAG = "3.0"
|
||||||
|
|
||||||
|
RULES_JVM_EXTERNAL_SHA = "62133c125bf4109dfd9d2af64830208356ce4ef8b165a6ef15bbff7460b35c3a"
|
||||||
|
|
||||||
|
http_archive(
|
||||||
|
name = "rules_jvm_external",
|
||||||
|
sha256 = RULES_JVM_EXTERNAL_SHA,
|
||||||
|
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
|
||||||
|
url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
|
||||||
|
)
|
||||||
|
|
||||||
|
load("@rules_jvm_external//:defs.bzl", "maven_install")
|
||||||
|
|
||||||
|
maven_install(
|
||||||
|
artifacts = [
|
||||||
|
"com.beust:jcommander:1.48",
|
||||||
|
"com.google.auto.value:auto-value-annotations:1.6.3",
|
||||||
|
"com.google.auto.value:auto-value:1.6.3",
|
||||||
|
"com.google.auto:auto-common:0.10",
|
||||||
|
"com.google.code.findbugs:jsr305:3.0.2",
|
||||||
|
"com.google.code.gson:gson:jar:2.8.5",
|
||||||
|
"com.google.flogger:flogger-system-backend:0.3.1",
|
||||||
|
"com.google.flogger:flogger:0.3.1",
|
||||||
|
"com.google.guava:failureaccess:1.0.1",
|
||||||
|
"com.google.guava:guava-testlib:27.1-jre",
|
||||||
|
"com.google.guava:guava:27.1-jre",
|
||||||
|
"com.google.http-client:google-http-client-gson:jar:1.27.0",
|
||||||
|
"com.google.http-client:google-http-client-test:jar:1.27.0",
|
||||||
|
"com.google.http-client:google-http-client:jar:1.27.0",
|
||||||
|
"com.google.jimfs:jimfs:1.1",
|
||||||
|
"com.google.re2j:re2j:1.2",
|
||||||
|
"com.google.truth:truth:0.45",
|
||||||
|
"com.googlecode.java-diff-utils:diffutils:1.3.0",
|
||||||
|
"commons-codec:commons-codec:jar:1.11",
|
||||||
|
"junit:junit:4.13",
|
||||||
|
"net.bytebuddy:byte-buddy-agent:1.9.10",
|
||||||
|
"net.bytebuddy:byte-buddy:1.9.10",
|
||||||
|
"org.mockito:mockito-core:2.28.2",
|
||||||
|
"org.objenesis:objenesis:1.0",
|
||||||
|
],
|
||||||
|
repositories = [
|
||||||
|
"https://maven.google.com",
|
||||||
|
"https://repo1.maven.org/maven2",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# LICENSE: The Apache Software License, Version 2.0
|
||||||
|
http_archive(
|
||||||
|
name = "io_bazel",
|
||||||
|
sha256 = bazel_sha256,
|
||||||
|
strip_prefix = "bazel-" + bazel_version,
|
||||||
|
# patch required to avoid depending on broken @io_bazel//src/main/java/com/google/devtools/build/lib/syntax:libcpu_profiler.so
|
||||||
|
patches = ["@copybara//third_party:bazel.patch"],
|
||||||
|
url = "https://github.com/bazelbuild/bazel/archive/" + bazel_version + ".zip",
|
||||||
|
)
|
||||||
|
|
||||||
|
# LICENSE: The Apache Software License, Version 2.0
|
||||||
|
# Buildifier and friends:
|
||||||
|
http_archive(
|
||||||
|
name = "buildtools",
|
||||||
|
sha256 = "fc9c2375fc9d50e5dd2f535b55dd25f12839a3043e7bd09a43ef7180b5670502",
|
||||||
|
strip_prefix = "buildtools-90de5e7001fbdfec29d4128bb508e01169f46950",
|
||||||
|
url = "https://github.com/bazelbuild/buildtools/archive/90de5e7001fbdfec29d4128bb508e01169f46950.zip",
|
||||||
|
)
|
||||||
|
|
||||||
|
EXPORT_WORKSPACE_IN_BUILD_FILE = [
|
||||||
|
"test -f BUILD && chmod u+w BUILD || true",
|
||||||
|
"echo >> BUILD",
|
||||||
|
"echo 'exports_files([\"WORKSPACE\"], visibility = [\"//visibility:public\"])' >> BUILD",
|
||||||
|
]
|
||||||
|
|
||||||
|
EXPORT_WORKSPACE_IN_BUILD_FILE_WIN = [
|
||||||
|
"Add-Content -Path BUILD -Value \"`nexports_files([`\"WORKSPACE`\"], visibility = [`\"//visibility:public`\"])`n\" -Force",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Stuff used by Bazel Starlark syntax package transitively:
|
||||||
|
# LICENSE: The Apache Software License, Version 2.0
|
||||||
|
http_archive(
|
||||||
|
name = "com_google_protobuf",
|
||||||
|
patch_args = ["-p1"],
|
||||||
|
patches = ["@io_bazel//third_party/protobuf:3.11.3.patch"],
|
||||||
|
patch_cmds = EXPORT_WORKSPACE_IN_BUILD_FILE,
|
||||||
|
patch_cmds_win = EXPORT_WORKSPACE_IN_BUILD_FILE_WIN,
|
||||||
|
sha256 = "cf754718b0aa945b00550ed7962ddc167167bd922b842199eeb6505e6f344852",
|
||||||
|
strip_prefix = "protobuf-3.11.3",
|
||||||
|
urls = [
|
||||||
|
"https://mirror.bazel.build/github.com/protocolbuffers/protobuf/archive/v3.11.3.tar.gz",
|
||||||
|
"https://github.com/protocolbuffers/protobuf/archive/v3.11.3.tar.gz",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stuff used by Buildifier transitively:
|
||||||
|
# LICENSE: The Apache Software License, Version 2.0
|
||||||
|
http_archive(
|
||||||
|
name = "io_bazel_rules_go",
|
||||||
|
sha256 = "b27e55d2dcc9e6020e17614ae6e0374818a3e3ce6f2024036e688ada24110444",
|
||||||
|
urls = [
|
||||||
|
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.21.0/rules_go-v0.21.0.tar.gz",
|
||||||
|
"https://github.com/bazelbuild/rules_go/releases/download/v0.21.0/rules_go-v0.21.0.tar.gz",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
|
||||||
|
|
||||||
|
go_rules_dependencies()
|
||||||
|
|
||||||
|
go_register_toolchains()
|
||||||
|
|
||||||
|
# LICENSE: The Apache Software License, Version 2.0
|
||||||
|
http_archive(
|
||||||
|
name = "bazel_gazelle",
|
||||||
|
sha256 = "86c6d481b3f7aedc1d60c1c211c6f76da282ae197c3b3160f54bd3a8f847896f",
|
||||||
|
urls = [
|
||||||
|
"https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.19.1/bazel-gazelle-v0.19.1.tar.gz",
|
||||||
|
"https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.19.1/bazel-gazelle-v0.19.1.tar.gz",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
|
||||||
|
|
||||||
|
gazelle_dependencies()
|
||||||
|
|
||||||
|
# LICENSE: The Apache Software License, Version 2.0
|
||||||
|
go_repository(
|
||||||
|
name = "skylark_syntax",
|
||||||
|
importpath = "go.starlark.net",
|
||||||
|
sum = "h1:Qoe+9POtDT51UBQ8XEnS9QKeHDQzEl2QRh3eok9R4aw=",
|
||||||
|
version = "v0.0.0-20200203144150-6677ee5c7211",
|
||||||
|
)
|
||||||
|
|
||||||
|
# LICENSE: The Apache Software License, Version 2.0
|
||||||
|
http_archive(
|
||||||
|
name = "rules_pkg",
|
||||||
|
sha256 = "5bdc04987af79bd27bc5b00fe30f59a858f77ffa0bd2d8143d5b31ad8b1bd71c",
|
||||||
|
url = "https://github.com/bazelbuild/rules_pkg/releases/download/0.2.0/rules_pkg-0.2.0.tar.gz",
|
||||||
|
)
|
||||||
|
|
||||||
|
# LICENSE: The Apache Software License, Version 2.0
|
||||||
|
http_archive(
|
||||||
|
name = "rules_java",
|
||||||
|
sha256 = "52423cb07384572ab60ef1132b0c7ded3a25c421036176c0273873ec82f5d2b2",
|
||||||
|
url = "https://github.com/bazelbuild/rules_java/releases/download/0.1.0/rules_java-0.1.0.tar.gz",
|
||||||
|
)
|
||||||
|
|
||||||
|
# LICENSE: The Apache Software License, Version 2.0
|
||||||
|
http_archive(
|
||||||
|
name = "rules_python",
|
||||||
|
sha256 = "f7402f11691d657161f871e11968a984e5b48b023321935f5a55d7e56cf4758a",
|
||||||
|
strip_prefix = "rules_python-9d68f24659e8ce8b736590ba1e4418af06ec2552",
|
||||||
|
url = "https://github.com/bazelbuild/rules_python/archive/9d68f24659e8ce8b736590ba1e4418af06ec2552.zip",
|
||||||
|
)
|
||||||
|
|
||||||
|
# LICENSE: The Apache Software License, Version 2.0
|
||||||
|
http_archive(
|
||||||
|
name = "rules_cc",
|
||||||
|
sha256 = "faa25a149f46077e7eca2637744f494e53a29fe3814bfe240a2ce37115f6e04d",
|
||||||
|
strip_prefix = "rules_cc-ea5c5422a6b9e79e6432de3b2b29bbd84eb41081",
|
||||||
|
url = "https://github.com/bazelbuild/rules_cc/archive/ea5c5422a6b9e79e6432de3b2b29bbd84eb41081.zip",
|
||||||
|
)
|
||||||
|
|
||||||
|
# LICENSE: The Apache Software License, Version 2.0
|
||||||
|
http_archive(
|
||||||
|
name = "rules_proto",
|
||||||
|
sha256 = "7d05492099a4359a6006d1b89284d34b76390c3b67d08e30840299b045838e2d",
|
||||||
|
strip_prefix = "rules_proto-9cd4f8f1ede19d81c6d48910429fe96776e567b1",
|
||||||
|
url = "https://github.com/bazelbuild/rules_proto/archive/9cd4f8f1ede19d81c6d48910429fe96776e567b1.zip",
|
||||||
|
)
|
16
third_party/copybara/ci/ubuntu/continuous.cfg
vendored
Normal file
16
third_party/copybara/ci/ubuntu/continuous.cfg
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Copyright 2019 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Location of the continuous bash script in Git.
|
||||||
|
build_file: "copybara/ci/ubuntu/continuous.sh"
|
23
third_party/copybara/ci/ubuntu/continuous.sh
vendored
Executable file
23
third_party/copybara/ci/ubuntu/continuous.sh
vendored
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright 2019 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Fail on any error.
|
||||||
|
set -e
|
||||||
|
# Display commands being run.
|
||||||
|
set -x
|
||||||
|
|
||||||
|
cd git/copybara
|
||||||
|
./cibuild.sh
|
44
third_party/copybara/cibuild.sh
vendored
Executable file
44
third_party/copybara/cibuild.sh
vendored
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
# DISCLAIMER: This is not the Google Cloud Build script
|
||||||
|
# that you are looking for.
|
||||||
|
|
||||||
|
|
||||||
|
function log() {
|
||||||
|
d=$(date +'%Y-%m-%d %H:%M:%S')
|
||||||
|
echo $d" "$1
|
||||||
|
}
|
||||||
|
|
||||||
|
log "Running Copybara tests"
|
||||||
|
|
||||||
|
log "Fetching dependencies"
|
||||||
|
log "Running apt-get update --fix-missing"
|
||||||
|
sudo apt-get update --fix-missing
|
||||||
|
# Mercurial does not have an up-to-date .deb package
|
||||||
|
# The official release needs to be installed with pip.
|
||||||
|
sudo apt-get -y install python-pip
|
||||||
|
sudo apt-get install locales
|
||||||
|
sudo pip install --ignore-installed --upgrade mercurial
|
||||||
|
|
||||||
|
log "Extracting Bazel"
|
||||||
|
# Only because first time it extracts the installation
|
||||||
|
bazel version
|
||||||
|
|
||||||
|
echo "-----------------------------------"
|
||||||
|
echo "Versions:"
|
||||||
|
hg --version | grep "(version" | sed 's/.*[(]version \([^ ]*\)[)].*/Mercurial: \1/'
|
||||||
|
git --version | sed 's/git version/Git:/'
|
||||||
|
bazel version | grep "Build label" | sed 's/Build label:/Bazel:/'
|
||||||
|
echo "-----------------------------------"
|
||||||
|
|
||||||
|
log "Setting Locale"
|
||||||
|
export LANGUAGE=en_US.UTF-8
|
||||||
|
export LANG=en_US.UTF-8
|
||||||
|
export LC_ALL=en_US.UTF-8
|
||||||
|
locale-gen en_US.UTF-8
|
||||||
|
sudo update-locale LANG=en_US.UTF-8
|
||||||
|
|
||||||
|
log "Running Bazel"
|
||||||
|
bazel test ... --test_output=errors --sandbox_tmpfs_path=/tmp -j 1000
|
||||||
|
|
||||||
|
log "Done"
|
42
third_party/copybara/cloudbuild.sh
vendored
Executable file
42
third_party/copybara/cloudbuild.sh
vendored
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
# Build script for Google Cloud Build
|
||||||
|
|
||||||
|
function log() {
|
||||||
|
d=$(date +'%Y-%m-%d %H:%M:%S')
|
||||||
|
echo $d" "$1
|
||||||
|
}
|
||||||
|
|
||||||
|
log "Running Copybara tests"
|
||||||
|
|
||||||
|
log "Fetching dependencies"
|
||||||
|
log "Running apt-get update --fix-missing"
|
||||||
|
apt-get update --fix-missing
|
||||||
|
# Mercurial does not have an up-to-date .deb package
|
||||||
|
# The official release needs to be installed with pip.
|
||||||
|
apt-get -y install python-pip
|
||||||
|
apt-get install locales
|
||||||
|
pip install mercurial
|
||||||
|
|
||||||
|
log "Extracting Bazel"
|
||||||
|
# Only because first time it extracts the installation
|
||||||
|
bazel version
|
||||||
|
|
||||||
|
echo "-----------------------------------"
|
||||||
|
echo "Versions:"
|
||||||
|
hg --version | grep "(version" | sed 's/.*[(]version \([^ ]*\)[)].*/Mercurial: \1/'
|
||||||
|
git --version | sed 's/git version/Git:/'
|
||||||
|
bazel version | grep "Build label" | sed 's/Build label:/Bazel:/'
|
||||||
|
echo "-----------------------------------"
|
||||||
|
|
||||||
|
log "Setting Locale"
|
||||||
|
export LANGUAGE=en_US.UTF-8
|
||||||
|
export LANG=en_US.UTF-8
|
||||||
|
export LC_ALL=en_US.UTF-8
|
||||||
|
locale-gen en_US.UTF-8
|
||||||
|
update-locale LANG=en_US.UTF-8
|
||||||
|
|
||||||
|
log "Running Bazel"
|
||||||
|
bazel "$@"
|
||||||
|
|
||||||
|
log "Done"
|
9
third_party/copybara/cloudbuild.yaml
vendored
Normal file
9
third_party/copybara/cloudbuild.yaml
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# GCB configuration file
|
||||||
|
# To learn more about GCB, go to https://cloud.google.com/container-builder/docs/
|
||||||
|
steps:
|
||||||
|
- name: gcr.io/cloud-builders/bazel
|
||||||
|
entrypoint: "bash"
|
||||||
|
args: ["-c", "./cloudbuild.sh test ... --test_output=errors --sandbox_tmpfs_path=/tmp -j 1000"]
|
||||||
|
options:
|
||||||
|
machine_type: N1_HIGHCPU_32
|
||||||
|
timeout: 30m
|
38
third_party/copybara/copybara/integration/BUILD
vendored
Normal file
38
third_party/copybara/copybara/integration/BUILD
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Copyright 2016 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
sh_test(
|
||||||
|
name = "reference_doc_test",
|
||||||
|
srcs = ["reference_doc_test.sh"],
|
||||||
|
data = [
|
||||||
|
"//docs:reference.md",
|
||||||
|
"//java/com/google/copybara:docs.md",
|
||||||
|
"//third_party/bazel/bashunit",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
sh_test(
|
||||||
|
name = "tool_test",
|
||||||
|
srcs = ["tool_test.sh"],
|
||||||
|
data = [
|
||||||
|
"//java/com/google/copybara",
|
||||||
|
"//third_party/bazel/bashunit",
|
||||||
|
],
|
||||||
|
shard_count = 30,
|
||||||
|
tags = ["local"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
16
third_party/copybara/copybara/integration/hello.t
vendored
Normal file
16
third_party/copybara/copybara/integration/hello.t
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Copyright 2016 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
$ echo hello
|
||||||
|
hello
|
37
third_party/copybara/copybara/integration/reference_doc_test.sh
vendored
Executable file
37
third_party/copybara/copybara/integration/reference_doc_test.sh
vendored
Executable file
|
@ -0,0 +1,37 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Copyright 2016 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
source "${TEST_SRCDIR}/copybara/third_party/bazel/bashunit/unittest.bash"
|
||||||
|
|
||||||
|
function test_reference_doc_generated() {
|
||||||
|
doc=${TEST_SRCDIR}/copybara/java/com/google/copybara/docs.md
|
||||||
|
source_doc=${TEST_SRCDIR}/copybara/docs/reference.md
|
||||||
|
|
||||||
|
[[ -f $doc ]] || fail "Documentation not generated"
|
||||||
|
# Check that we have table of contents and some basic modules
|
||||||
|
grep "^# Table of Contents" "$doc" > /dev/null 2>&1 || fail "Table of contents not found"
|
||||||
|
grep "^## core" "$doc" > /dev/null 2>&1 || fail "core doc not found"
|
||||||
|
grep "^### core.replace" "$doc" > /dev/null 2>&1 || fail "core.replace doc not found"
|
||||||
|
grep "before.*The text before the transformation" \
|
||||||
|
"$doc" > /dev/null 2>&1 || fail "core.replace field doc not found"
|
||||||
|
grep "^### git.origin" "$doc" > /dev/null 2>&1 || fail "git.origin doc not found"
|
||||||
|
grep "Finds links to commits in change messages" "$doc" > /dev/null 2>&1 \
|
||||||
|
|| fail "single example not found"
|
||||||
|
|
||||||
|
diff $doc $source_doc || fail "Generate the documentation with scripts/update_docs [-a]"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_suite "Integration tests for reference documentation generation."
|
38
third_party/copybara/copybara/integration/test-help.t
vendored
Normal file
38
third_party/copybara/copybara/integration/test-help.t
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Copyright 2016 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
$ ${TEST_SRCDIR}/java/com/google/copybara/copybara help
|
||||||
|
Usage: copybara [options] CONFIG_PATH [SOURCE_REF]
|
||||||
|
Options:
|
||||||
|
--folder-dir
|
||||||
|
Local directory to put the output of the transformation
|
||||||
|
--gerrit-change-id
|
||||||
|
ChangeId to use in the generated commit message
|
||||||
|
Default: <empty string>
|
||||||
|
--git-previous-ref
|
||||||
|
Previous SHA-1 reference used for the migration.
|
||||||
|
Default: <empty string>
|
||||||
|
--help
|
||||||
|
Shows this help text
|
||||||
|
Default: false
|
||||||
|
--work-dir
|
||||||
|
Directory where all the transformations will be performed. By default a
|
||||||
|
temporary directory.
|
||||||
|
-v
|
||||||
|
Verbose output.
|
||||||
|
Default: false
|
||||||
|
|
||||||
|
Example:
|
||||||
|
copybara myproject.copybara origin/master
|
||||||
|
|
1242
third_party/copybara/copybara/integration/tool_test.sh
vendored
Executable file
1242
third_party/copybara/copybara/integration/tool_test.sh
vendored
Executable file
File diff suppressed because it is too large
Load diff
19
third_party/copybara/docs/BUILD
vendored
Normal file
19
third_party/copybara/docs/BUILD
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Copyright 2018 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
exports_files(["reference.md"])
|
237
third_party/copybara/docs/examples.md
vendored
Normal file
237
third_party/copybara/docs/examples.md
vendored
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
## Contents
|
||||||
|
- [Git-to-Git import](#basic-git-to-git-import)
|
||||||
|
- [Github SSH import](#github-ssh-basic-import)
|
||||||
|
- [Mercurial to Git import](#mercurial-to-git-import)
|
||||||
|
- [Transformations](#transformations)
|
||||||
|
- [Subcommands](#subcommands)
|
||||||
|
|
||||||
|
## Basic git-to-git import
|
||||||
|
|
||||||
|
This example will import Copybara source code to an internal git repository
|
||||||
|
under ``$GIT/third_party/copybara``.
|
||||||
|
|
||||||
|
Assuming you have an existing git repository. For the example in ``/tmp/foo``. But it could be
|
||||||
|
a remote one:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir /tmp/foo
|
||||||
|
cd /tmp/foo
|
||||||
|
git init --bare .
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a ``copy.bara.sky`` config file like:
|
||||||
|
|
||||||
|
```python
|
||||||
|
url = "https://github.com/google/copybara.git"
|
||||||
|
|
||||||
|
core.workflow(
|
||||||
|
name = "default",
|
||||||
|
origin = git.origin(
|
||||||
|
url = url,
|
||||||
|
ref = "master",
|
||||||
|
),
|
||||||
|
destination = git.destination(
|
||||||
|
url = "file:///tmp/foo",
|
||||||
|
fetch = "master",
|
||||||
|
push = "master",
|
||||||
|
),
|
||||||
|
# Copy everything but don't remove a README_INTERNAL.txt file if it exists.
|
||||||
|
destination_files = glob(["third_party/copybara/**"], exclude = ["README_INTERNAL.txt"]),
|
||||||
|
|
||||||
|
authoring = authoring.pass_thru("Default email <default@default.com>"),
|
||||||
|
transformations = [
|
||||||
|
core.move("", "third_party/copybara"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Invoke the tool like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
copybara copy.bara.sky --force
|
||||||
|
```
|
||||||
|
|
||||||
|
``--force`` should only be needed for empty destination repositories or non-existent
|
||||||
|
branches in the destination. After the first import, it should be always invoked as:
|
||||||
|
|
||||||
|
```
|
||||||
|
copybara copy.bara.sky
|
||||||
|
```
|
||||||
|
|
||||||
|
## GitHub SSH basic import
|
||||||
|
|
||||||
|
This example will import private source code to an external GitHub repository, and uses SSH.
|
||||||
|
|
||||||
|
PROTIP: You will need to have an ssh key setup without a password to accomplish this, Copybara doesn't
|
||||||
|
currently support ssh with a password.
|
||||||
|
|
||||||
|
Create a ``copy.bara.sky`` config file like:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Update these references to your orginzations repos
|
||||||
|
sourceUrl = "git@github.com:organization/internal-repo.git"
|
||||||
|
destinationUrl = "git@github.com:organization/external-repo.git"
|
||||||
|
|
||||||
|
core.workflow(
|
||||||
|
name = "default",
|
||||||
|
origin = git.origin(
|
||||||
|
url = sourceUrl,
|
||||||
|
ref = "master",
|
||||||
|
),
|
||||||
|
destination = git.destination(
|
||||||
|
url = destinationUrl,
|
||||||
|
fetch = "master",
|
||||||
|
push = "master",
|
||||||
|
),
|
||||||
|
# Change path to the folder you want to publish publicly
|
||||||
|
origin_files = glob(["path/to/folder/you/want/exported/**"]),
|
||||||
|
|
||||||
|
authoring = authoring.pass_thru("Default email <default@default.com>"),
|
||||||
|
|
||||||
|
# Change the path here to the folder you want to publish publicly
|
||||||
|
transformations = [
|
||||||
|
core.move("path/to/folder/you/want/exported", ""),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Invoke the tool like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
copybara copy.bara.sky --force
|
||||||
|
```
|
||||||
|
|
||||||
|
``--force`` should only be needed for empty destination repositories or non-existent
|
||||||
|
branches in the destination. After the first import, it should be always invoked as:
|
||||||
|
|
||||||
|
```
|
||||||
|
copybara copy.bara.sky
|
||||||
|
```
|
||||||
|
|
||||||
|
After running through this example, you should see all the source from
|
||||||
|
the folder you selected in the external-repo at the root. This can be helpful if you
|
||||||
|
are only trying to move a subdirectory in your git repo out for public use.
|
||||||
|
|
||||||
|
## Mercurial to Git import
|
||||||
|
Let's set up a simple migration from a Mercurial repository to a git repository. Note that Mercurial
|
||||||
|
support is still experimental.
|
||||||
|
|
||||||
|
In this example, we will import source code from the
|
||||||
|
[Mercurial source repository](https://www.mercurial-scm.org/repo/hg/) to a local git repository.
|
||||||
|
We'll get started by setting up a local bare git repository.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ mkdir /tmp/gitdest
|
||||||
|
$ cd /tmp/gitdest
|
||||||
|
$ git init --bare .
|
||||||
|
```
|
||||||
|
Next up is creating and editing a `copy.bara.sky` config file. The config file will contain the
|
||||||
|
details of our workflow. Using your text editor of choice, create and edit the config file:
|
||||||
|
```
|
||||||
|
$ vim /tmp/copy.bara.sky
|
||||||
|
```
|
||||||
|
We'll define in the config to pull changes from the default branch in the origin repository.
|
||||||
|
```
|
||||||
|
core.workflow(
|
||||||
|
name = "default",
|
||||||
|
origin = hg.origin(
|
||||||
|
url = "https://www.mercurial-scm.org/repo/hg",
|
||||||
|
ref = "default",
|
||||||
|
),
|
||||||
|
destination = git.destination(
|
||||||
|
url = "file:///tmp/gitdest",
|
||||||
|
),
|
||||||
|
# Files that you want to import
|
||||||
|
origin_files = glob(['**']),
|
||||||
|
# Files that you want to copy
|
||||||
|
destination_files = glob(['**']),
|
||||||
|
# Set up a default author
|
||||||
|
authoring = authoring.pass_thru("Default email <default@default.com>"),
|
||||||
|
# Import mode
|
||||||
|
mode = "SQUASH",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
Now we can run Copybara with this config to import the changes. However, since the Mercurial
|
||||||
|
repository has many commits, we can just pull default branch revisions from the most recent 15
|
||||||
|
revisions in the repository, using the `--last-rev` flag.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ copybara /tmp/copy.bara.sky --force --last-rev -15
|
||||||
|
```
|
||||||
|
If we wanted to pull all revisions from the default branch, we would omit the `--last-rev` flag.
|
||||||
|
Since we are using `SQUASH` mode, all commits from the origin repository will be "squashed" into a
|
||||||
|
single commit.
|
||||||
|
|
||||||
|
If we navigate to our git destination repository, we can run `git log` and see the commit that
|
||||||
|
was created.
|
||||||
|
```
|
||||||
|
$ cd /tmp/gitdest
|
||||||
|
$ git log
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Transformations
|
||||||
|
|
||||||
|
Let's say that we realized that we need to do some code transformations to the imported code.
|
||||||
|
We could use core.replace to do it. Here we look for ``//third_party/bazel/bashunit`` text
|
||||||
|
and we replace it with the correct destination one just for BUILD files:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
url = "https://github.com/google/copybara.git"
|
||||||
|
|
||||||
|
core.workflow(
|
||||||
|
name = "default",
|
||||||
|
origin = git.origin(
|
||||||
|
url = url,
|
||||||
|
ref = "master",
|
||||||
|
),
|
||||||
|
destination = git.destination(
|
||||||
|
url = "file:///tmp/foo",
|
||||||
|
fetch = "master",
|
||||||
|
push = "master",
|
||||||
|
),
|
||||||
|
|
||||||
|
# Copy everything but don't remove a README_INTERNAL.txt file if it exists.
|
||||||
|
destination_files = glob(["third_party/copybara/**"], exclude = ["README_INTERNAL.txt"]),
|
||||||
|
|
||||||
|
authoring = authoring.pass_thru("Default email <default@default.com>"),
|
||||||
|
|
||||||
|
transformations = [
|
||||||
|
core.replace(
|
||||||
|
before = "//third_party/bazel/bashunit",
|
||||||
|
after = "//another/path:bashunit",
|
||||||
|
paths = glob(["**/BUILD"]),
|
||||||
|
),
|
||||||
|
core.move("", "third_party/copybara"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Subcommands
|
||||||
|
|
||||||
|
The tool accepts different subcommands, _à la_ Bazel. If no
|
||||||
|
command is specified, *migrate* is executed by default. These two commands are
|
||||||
|
equivalent:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ copybara copy.bara.sky
|
||||||
|
$ copybara migrate copy.bara.sky
|
||||||
|
```
|
||||||
|
|
||||||
|
You can validate your configuration running:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ copybara validate copy.bara.sky
|
||||||
|
Copybara source mover
|
||||||
|
INFO: Configuration validated.
|
||||||
|
```
|
||||||
|
|
||||||
|
And you can get information about a migration workflow by running:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ copybara info copy.bara.sky
|
||||||
|
Copybara source mover
|
||||||
|
...
|
||||||
|
INFO: Workflow 'default': last_migrated_ref 4dd20b2...
|
||||||
|
```
|
3853
third_party/copybara/docs/reference.md
vendored
Executable file
3853
third_party/copybara/docs/reference.md
vendored
Executable file
File diff suppressed because it is too large
Load diff
219
third_party/copybara/java/com/google/copybara/BUILD
vendored
Normal file
219
third_party/copybara/java/com/google/copybara/BUILD
vendored
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
# Copyright 2016 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
load(":docs.bzl", "doc_generator")
|
||||||
|
|
||||||
|
exports_files(
|
||||||
|
[
|
||||||
|
"doc_skylark.sh",
|
||||||
|
"docs.bzl",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
JAVACOPTS = [
|
||||||
|
"-Xlint:unchecked",
|
||||||
|
"-source",
|
||||||
|
"1.8",
|
||||||
|
]
|
||||||
|
|
||||||
|
java_binary(
|
||||||
|
name = "copybara",
|
||||||
|
javacopts = JAVACOPTS,
|
||||||
|
main_class = "com.google.copybara.Main",
|
||||||
|
runtime_deps = [
|
||||||
|
":copybara_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
java_library(
|
||||||
|
name = "copybara_main",
|
||||||
|
srcs = ["Main.java"],
|
||||||
|
javacopts = JAVACOPTS,
|
||||||
|
deps = [
|
||||||
|
":base",
|
||||||
|
":copybara_lib",
|
||||||
|
":general_options",
|
||||||
|
"//java/com/google/copybara/config:base",
|
||||||
|
"//java/com/google/copybara/exception",
|
||||||
|
"//java/com/google/copybara/jcommander:converters",
|
||||||
|
"//java/com/google/copybara/profiler",
|
||||||
|
"//java/com/google/copybara/util",
|
||||||
|
"//java/com/google/copybara/util/console",
|
||||||
|
"//third_party:flogger",
|
||||||
|
"//third_party:guava",
|
||||||
|
"//third_party:jcommander",
|
||||||
|
"//third_party:jsr305",
|
||||||
|
"//third_party:skylark-lang",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
doc_generator(
|
||||||
|
name = "docs",
|
||||||
|
deps = [":copybara"],
|
||||||
|
)
|
||||||
|
|
||||||
|
BASE_SRCS = [
|
||||||
|
"BaselinesWithoutLabelVisitor.java",
|
||||||
|
"Change.java",
|
||||||
|
"ChangeMessage.java",
|
||||||
|
"Changes.java",
|
||||||
|
"ChangeVisitable.java",
|
||||||
|
"CheckoutPath.java",
|
||||||
|
"CheckoutPathAttributes.java",
|
||||||
|
"ConfigItemDescription.java",
|
||||||
|
"Destination.java",
|
||||||
|
"DestinationEffect.java",
|
||||||
|
"DestinationReader.java",
|
||||||
|
"DestinationStatusVisitor.java",
|
||||||
|
"Endpoint.java",
|
||||||
|
"EndpointProvider.java",
|
||||||
|
"Info.java",
|
||||||
|
"LazyResourceLoader.java",
|
||||||
|
"LabelFinder.java",
|
||||||
|
"LocalParallelizer.java",
|
||||||
|
"Metadata.java",
|
||||||
|
"MigrationInfo.java",
|
||||||
|
"NonReversibleValidationException.java",
|
||||||
|
"Option.java",
|
||||||
|
"Options.java",
|
||||||
|
"Origin.java",
|
||||||
|
"Revision.java",
|
||||||
|
"SkylarkContext.java",
|
||||||
|
"Transformation.java",
|
||||||
|
"TransformResult.java",
|
||||||
|
"TransformWork.java",
|
||||||
|
"Trigger.java",
|
||||||
|
"treestate/FileSystemTreeState.java",
|
||||||
|
"treestate/MapBasedTreeState.java",
|
||||||
|
"treestate/TreeState.java",
|
||||||
|
"treestate/TreeStateUtil.java",
|
||||||
|
"WorkflowOptions.java",
|
||||||
|
"WriterContext.java",
|
||||||
|
]
|
||||||
|
|
||||||
|
java_library(
|
||||||
|
name = "options",
|
||||||
|
srcs = [
|
||||||
|
"Option.java",
|
||||||
|
"Options.java",
|
||||||
|
],
|
||||||
|
javacopts = JAVACOPTS,
|
||||||
|
deps = [
|
||||||
|
"//third_party:guava",
|
||||||
|
"//third_party:jsr305",
|
||||||
|
"//third_party:re2j",
|
||||||
|
"//third_party:shell",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
java_library(
|
||||||
|
name = "moduleset",
|
||||||
|
srcs = ["ModuleSet.java"],
|
||||||
|
javacopts = JAVACOPTS,
|
||||||
|
deps = [
|
||||||
|
":options",
|
||||||
|
"//third_party:guava",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
java_library(
|
||||||
|
name = "base",
|
||||||
|
srcs = BASE_SRCS,
|
||||||
|
javacopts = JAVACOPTS,
|
||||||
|
deps = [
|
||||||
|
"//java/com/google/copybara/authoring",
|
||||||
|
"//java/com/google/copybara/doc:annotations",
|
||||||
|
"//java/com/google/copybara/exception",
|
||||||
|
"//java/com/google/copybara/jcommander:converters",
|
||||||
|
"//java/com/google/copybara/jcommander:validators",
|
||||||
|
"//java/com/google/copybara/util",
|
||||||
|
"//java/com/google/copybara/util/console",
|
||||||
|
"//third_party:autovalue",
|
||||||
|
"//third_party:flogger",
|
||||||
|
"//third_party:guava",
|
||||||
|
"//third_party:jcommander",
|
||||||
|
"//third_party:jsr305",
|
||||||
|
"//third_party:re2j",
|
||||||
|
"//third_party:skylark-lang",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
java_library(
|
||||||
|
name = "general_options",
|
||||||
|
srcs = ["GeneralOptions.java"],
|
||||||
|
javacopts = JAVACOPTS,
|
||||||
|
deps = [
|
||||||
|
":base",
|
||||||
|
"//java/com/google/copybara/exception",
|
||||||
|
"//java/com/google/copybara/jcommander:converters",
|
||||||
|
"//java/com/google/copybara/monitor",
|
||||||
|
"//java/com/google/copybara/profiler",
|
||||||
|
"//java/com/google/copybara/util",
|
||||||
|
"//java/com/google/copybara/util/console",
|
||||||
|
"//third_party:autovalue",
|
||||||
|
"//third_party:flogger",
|
||||||
|
"//third_party:guava",
|
||||||
|
"//third_party:jcommander",
|
||||||
|
"//third_party:jsr305",
|
||||||
|
"//third_party:re2j",
|
||||||
|
"//third_party:skylark-lang",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
java_library(
|
||||||
|
name = "copybara_lib",
|
||||||
|
srcs = glob(
|
||||||
|
["**/*.java"],
|
||||||
|
exclude = [
|
||||||
|
"Main.java",
|
||||||
|
"GeneralOptions.java",
|
||||||
|
] + BASE_SRCS,
|
||||||
|
),
|
||||||
|
javacopts = JAVACOPTS,
|
||||||
|
deps = [
|
||||||
|
":base",
|
||||||
|
":general_options",
|
||||||
|
"//java/com/google/copybara/authoring",
|
||||||
|
"//java/com/google/copybara/buildozer",
|
||||||
|
"//java/com/google/copybara/buildozer:buildozer_options",
|
||||||
|
"//java/com/google/copybara/config:base",
|
||||||
|
"//java/com/google/copybara/config:global_migrations",
|
||||||
|
"//java/com/google/copybara/config:parser",
|
||||||
|
"//java/com/google/copybara/doc:annotations",
|
||||||
|
"//java/com/google/copybara/exception",
|
||||||
|
"//java/com/google/copybara/format",
|
||||||
|
"//java/com/google/copybara/git",
|
||||||
|
"//java/com/google/copybara/hg",
|
||||||
|
"//java/com/google/copybara/monitor",
|
||||||
|
"//java/com/google/copybara/profiler",
|
||||||
|
"//java/com/google/copybara/remotefile",
|
||||||
|
"//java/com/google/copybara/templatetoken",
|
||||||
|
"//java/com/google/copybara/transform",
|
||||||
|
"//java/com/google/copybara/transform/debug",
|
||||||
|
"//java/com/google/copybara/transform/patch",
|
||||||
|
"//java/com/google/copybara/util",
|
||||||
|
"//java/com/google/copybara/util/console",
|
||||||
|
"//third_party:flogger",
|
||||||
|
"//third_party:guava",
|
||||||
|
"//third_party:jcommander",
|
||||||
|
"//third_party:jsr305",
|
||||||
|
"//third_party:re2j",
|
||||||
|
"//third_party:skylark-lang",
|
||||||
|
],
|
||||||
|
)
|
62
third_party/copybara/java/com/google/copybara/BaselinesWithoutLabelVisitor.java
vendored
Normal file
62
third_party/copybara/java/com/google/copybara/BaselinesWithoutLabelVisitor.java
vendored
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.copybara.ChangeVisitable.ChangesVisitor;
|
||||||
|
import com.google.copybara.ChangeVisitable.VisitResult;
|
||||||
|
import com.google.copybara.util.Glob;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** A visitor that finds all the parents that match the origin glob. */
|
||||||
|
public class BaselinesWithoutLabelVisitor<T> implements ChangesVisitor {
|
||||||
|
|
||||||
|
private final List<T> result = new ArrayList<>();
|
||||||
|
private final int limit;
|
||||||
|
private final Glob originFiles;
|
||||||
|
private boolean skipFirst;
|
||||||
|
|
||||||
|
public BaselinesWithoutLabelVisitor(Glob originFiles, int limit, boolean skipFirst) {
|
||||||
|
this.originFiles = Preconditions.checkNotNull(originFiles);
|
||||||
|
Preconditions.checkArgument(limit > 0);
|
||||||
|
this.limit = limit;
|
||||||
|
this.skipFirst = skipFirst;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableList<T> getResult() {
|
||||||
|
return ImmutableList.copyOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public VisitResult visit(Change<? extends Revision> change) {
|
||||||
|
if (skipFirst) {
|
||||||
|
skipFirst = false;
|
||||||
|
return VisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
ImmutableSet<String> files = change.getChangeFiles();
|
||||||
|
if (Glob.affectsRoots(originFiles.roots(), files)) {
|
||||||
|
result.add((T) change.getRevision());
|
||||||
|
return result.size() < limit ? VisitResult.CONTINUE : VisitResult.TERMINATE;
|
||||||
|
}
|
||||||
|
// This change only contains files that are not exported
|
||||||
|
return VisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
}
|
236
third_party/copybara/java/com/google/copybara/Change.java
vendored
Normal file
236
third_party/copybara/java/com/google/copybara/Change.java
vendored
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableListMultimap;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.copybara.DestinationEffect.OriginRef;
|
||||||
|
import com.google.copybara.authoring.Author;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkMethod;
|
||||||
|
import com.google.devtools.build.lib.syntax.Dict;
|
||||||
|
import com.google.devtools.build.lib.syntax.Sequence;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkList;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/** Represents a change in a Repository */
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "change",
|
||||||
|
category = StarlarkDocumentationCategory.BUILTIN,
|
||||||
|
doc = "A change metadata. Contains information like author, change message or detected labels")
|
||||||
|
public final class Change<R extends Revision> extends OriginRef implements StarlarkValue {
|
||||||
|
|
||||||
|
private final R revision;
|
||||||
|
private final Author author;
|
||||||
|
private final String message;
|
||||||
|
private final ZonedDateTime dateTime;
|
||||||
|
private final ImmutableListMultimap<String, String> labels;
|
||||||
|
private Author mappedAuthor;
|
||||||
|
private final boolean merge;
|
||||||
|
@Nullable
|
||||||
|
private final ImmutableList<R> parents;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final ImmutableSet<String> changeFiles;
|
||||||
|
|
||||||
|
public Change(R revision, Author author, String message, ZonedDateTime dateTime,
|
||||||
|
ImmutableListMultimap<String, String> labels) {
|
||||||
|
this(revision, author, message, dateTime, labels, /*changeFiles=*/null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Change(R revision, Author author, String message, ZonedDateTime dateTime,
|
||||||
|
ImmutableListMultimap<String, String> labels, @Nullable Set<String> changeFiles) {
|
||||||
|
this(revision, author, message, dateTime, labels, changeFiles, /*merge=*/false,
|
||||||
|
/*parents=*/ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Change(R revision, Author author, String message, ZonedDateTime dateTime,
|
||||||
|
ImmutableListMultimap<String, String> labels, @Nullable Set<String> changeFiles,
|
||||||
|
boolean merge, @Nullable ImmutableList<R> parents) {
|
||||||
|
super(revision.asString());
|
||||||
|
this.revision = Preconditions.checkNotNull(revision);
|
||||||
|
this.author = Preconditions.checkNotNull(author);
|
||||||
|
this.message = Preconditions.checkNotNull(message);
|
||||||
|
this.dateTime = dateTime;
|
||||||
|
this.labels = labels;
|
||||||
|
this.changeFiles = changeFiles == null ? null : ImmutableSet.copyOf(changeFiles);
|
||||||
|
this.merge = merge;
|
||||||
|
this.parents = parents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference of the change. For example a SHA-1 reference in git.
|
||||||
|
*/
|
||||||
|
public R getRevision() {
|
||||||
|
return revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the parent revisions if the origin provides that information. Currently only for Git and
|
||||||
|
* Hg. Otherwise null.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public ImmutableList<R> getParents() {
|
||||||
|
return parents;
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "original_author", doc = "The author of the change before any"
|
||||||
|
+ " mapping", structField = true)
|
||||||
|
public Author getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The author of the change. Can already be mapped using metadata.map_author
|
||||||
|
*/
|
||||||
|
@StarlarkMethod(name = "author", doc = "The author of the change", structField = true)
|
||||||
|
public Author getMappedAuthor() {
|
||||||
|
return Preconditions.checkNotNull(mappedAuthor == null ? author : mappedAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMappedAuthor(Author mappedAuthor) {
|
||||||
|
this.mappedAuthor = mappedAuthor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "message", doc = "The message of the change", structField = true)
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "labels",
|
||||||
|
doc =
|
||||||
|
"A dictionary with the labels detected for the change. If the label is present multiple"
|
||||||
|
+ " times it returns the last value. Note that this is a heuristic and it could"
|
||||||
|
+ " include things that are not labels.",
|
||||||
|
structField = true)
|
||||||
|
public Dict<String, String> getLabelsForSkylark() {
|
||||||
|
return Dict.copyOf(
|
||||||
|
/* mu= */ null,
|
||||||
|
ImmutableMap.copyOf(Maps.transformValues(labels.asMap(), Iterables::getLast)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "labels_all_values",
|
||||||
|
doc =
|
||||||
|
"A dictionary with the labels detected for the change. Note that the value is a"
|
||||||
|
+ " collection of the values for each time the label was found. Use 'labels' instead"
|
||||||
|
+ " if you are only interested in the last value. Note that this is a heuristic and"
|
||||||
|
+ " it could include things that are not labels.",
|
||||||
|
structField = true)
|
||||||
|
public Dict<String, Sequence<String>> getLabelsAllForSkylark() {
|
||||||
|
return Dict.copyOf(
|
||||||
|
/* mu= */ null, Maps.transformValues(labels.asMap(), StarlarkList::immutableCopyOf));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If not null, the files that were affected in this change.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public ImmutableSet<String> getChangeFiles() {
|
||||||
|
return changeFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZonedDateTime getDateTime() {
|
||||||
|
return dateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "date_time_iso_offset",
|
||||||
|
doc = "Return a ISO offset date time. Example: 2011-12-03T10:15:30+01:00'",
|
||||||
|
structField = true)
|
||||||
|
public String dateTimeFmt() {
|
||||||
|
return ISO_OFFSET_DATE_TIME.format(getDateTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableListMultimap<String, String> getLabels() {
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first line of the change. Usually a summary.
|
||||||
|
*/
|
||||||
|
@StarlarkMethod(name = "first_line_message", doc = "The message of the change"
|
||||||
|
, structField = true)
|
||||||
|
public String firstLineMessage() {
|
||||||
|
return extractFirstLine(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String extractFirstLine(String message) {
|
||||||
|
int idx = message.indexOf('\n');
|
||||||
|
return idx == -1 ? message : message.substring(0, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the change represents a merge.
|
||||||
|
*/
|
||||||
|
@StarlarkMethod(name = "merge", doc = "Returns true if the change represents a merge"
|
||||||
|
, structField = true)
|
||||||
|
public boolean isMerge() {
|
||||||
|
return merge;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("revision", revision.asString())
|
||||||
|
.add("author", author)
|
||||||
|
.add("dateTime", dateTime)
|
||||||
|
.add("message", message)
|
||||||
|
.add("merge", merge)
|
||||||
|
.add("parents", parents)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Change<R> withLabels(ImmutableListMultimap<String, String> newLabels) {
|
||||||
|
return new Change<>(revision, author, message, dateTime,
|
||||||
|
Revision.addNewLabels(labels, newLabels), changeFiles, merge, parents);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Change<?> change = (Change<?>) o;
|
||||||
|
return Objects.equals(revision, change.revision)
|
||||||
|
&& Objects.equals(author, change.author)
|
||||||
|
&& Objects.equals(message, change.message)
|
||||||
|
&& Objects.equals(dateTime, change.dateTime)
|
||||||
|
&& Objects.equals(labels, change.labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(revision, author, message, dateTime, labels);
|
||||||
|
}
|
||||||
|
}
|
253
third_party/copybara/java/com/google/copybara/ChangeMessage.java
vendored
Normal file
253
third_party/copybara/java/com/google/copybara/ChangeMessage.java
vendored
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.base.CharMatcher;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableListMultimap;
|
||||||
|
import com.google.copybara.doc.annotations.DocSignaturePrefix;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.Param;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkMethod;
|
||||||
|
import com.google.devtools.build.lib.syntax.Sequence;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkList;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import javax.annotation.CheckReturnValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that represents a well formed message: No superfluous new lines, a group of labels,
|
||||||
|
* etc.
|
||||||
|
*
|
||||||
|
* <p>This class is immutable.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "ChangeMessage",
|
||||||
|
category = StarlarkDocumentationCategory.BUILTIN,
|
||||||
|
doc = "Represents a well formed parsed change message with its associated labels.")
|
||||||
|
@DocSignaturePrefix("message")
|
||||||
|
public final class ChangeMessage implements StarlarkValue {
|
||||||
|
|
||||||
|
private static final String DOUBLE_NEWLINE = "\n\n";
|
||||||
|
private static final String DASH_DASH_SEPARATOR = "\n--\n";
|
||||||
|
private static final CharMatcher TRIM = CharMatcher.is('\n');
|
||||||
|
|
||||||
|
private final String text;
|
||||||
|
private final String groupSeparator;
|
||||||
|
private final ImmutableList<LabelFinder> labels;
|
||||||
|
|
||||||
|
private ChangeMessage(String text, String groupSeparator, List<LabelFinder> labels) {
|
||||||
|
this.text = TRIM.trimFrom(text);
|
||||||
|
this.groupSeparator = Preconditions.checkNotNull(groupSeparator);
|
||||||
|
this.labels = ImmutableList.copyOf(Preconditions.checkNotNull(labels));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message object looking for labels in just the last paragraph.
|
||||||
|
*
|
||||||
|
* <p>Use this for Copybara well-formed messages.
|
||||||
|
*/
|
||||||
|
public static ChangeMessage parseMessage(String message) {
|
||||||
|
String trimMsg = TRIM.trimFrom(message);
|
||||||
|
int doubleNewLine = trimMsg.lastIndexOf(DOUBLE_NEWLINE);
|
||||||
|
int dashDash = trimMsg.lastIndexOf(DASH_DASH_SEPARATOR);
|
||||||
|
if (doubleNewLine == -1 && dashDash == -1) {
|
||||||
|
// Empty message like "\n\nfoo: bar" or "\n\nfoo bar baz"
|
||||||
|
if (message.startsWith(DOUBLE_NEWLINE)) {
|
||||||
|
return new ChangeMessage("", DOUBLE_NEWLINE, linesAsLabels(trimMsg));
|
||||||
|
}
|
||||||
|
return new ChangeMessage(trimMsg, DOUBLE_NEWLINE, new ArrayList<>());
|
||||||
|
} else if (doubleNewLine > dashDash) {
|
||||||
|
return new ChangeMessage(trimMsg.substring(0, doubleNewLine), DOUBLE_NEWLINE,
|
||||||
|
linesAsLabels(trimMsg.substring(doubleNewLine + 2)));
|
||||||
|
} else {
|
||||||
|
return new ChangeMessage(trimMsg.substring(0, dashDash), DASH_DASH_SEPARATOR,
|
||||||
|
linesAsLabels(trimMsg.substring(dashDash + 4)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message object treating all the lines as possible labels instead of looking
|
||||||
|
* just in the last paragraph for labels.
|
||||||
|
*/
|
||||||
|
public static ChangeMessage parseAllAsLabels(String message) {
|
||||||
|
Preconditions.checkNotNull(message);
|
||||||
|
return new ChangeMessage("", DOUBLE_NEWLINE, linesAsLabels(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<LabelFinder> linesAsLabels(String message) {
|
||||||
|
Preconditions.checkNotNull(message);
|
||||||
|
return Splitter.on('\n').splitToList(TRIM.trimTrailingFrom(message)).stream()
|
||||||
|
.map(LabelFinder::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "first_line", doc = "First line of this message", structField = true)
|
||||||
|
public String firstLine() {
|
||||||
|
int idx = text.indexOf('\n');
|
||||||
|
return idx == -1 ? text : text.substring(0, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "text",
|
||||||
|
doc = "The text description this message, not including the labels.",
|
||||||
|
structField = true)
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableList<LabelFinder> getLabels() {
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the labels in the message. If a label appears multiple times, it respects the
|
||||||
|
* order of appearance.
|
||||||
|
*/
|
||||||
|
public ImmutableListMultimap<String, String> labelsAsMultimap(){
|
||||||
|
// We overwrite duplicates
|
||||||
|
ImmutableListMultimap.Builder<String, String> result = ImmutableListMultimap.builder();
|
||||||
|
for (LabelFinder label : labels) {
|
||||||
|
if (label.isLabel()) {
|
||||||
|
result.put(label.getName(), label.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "label_values",
|
||||||
|
doc = "Returns a list of values associated with the label name.",
|
||||||
|
parameters = {
|
||||||
|
@Param(name = "label_name", type = String.class, named = true, doc = "The label name."),
|
||||||
|
})
|
||||||
|
public Sequence<String> getLabelValues(String labelName) {
|
||||||
|
ImmutableListMultimap<String, String> localLabels = labelsAsMultimap();
|
||||||
|
if (localLabels.containsKey(labelName)) {
|
||||||
|
return StarlarkList.immutableCopyOf(localLabels.get(labelName));
|
||||||
|
}
|
||||||
|
return StarlarkList.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public ChangeMessage withLabel(String name, String separator, String value) {
|
||||||
|
List<LabelFinder> newLabels = new ArrayList<>(labels);
|
||||||
|
// Add an additional line if none of the previous elements are labels
|
||||||
|
if (!newLabels.isEmpty() && newLabels.stream().noneMatch(LabelFinder::isLabel)) {
|
||||||
|
newLabels.add(new LabelFinder(""));
|
||||||
|
}
|
||||||
|
newLabels.add(new LabelFinder(validateLabelName(name) + Preconditions
|
||||||
|
.checkNotNull(separator) + Preconditions.checkNotNull(value)));
|
||||||
|
return new ChangeMessage(this.text, this.groupSeparator, newLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public ChangeMessage withReplacedLabel(String labelName, String separator , String value) {
|
||||||
|
validateLabelName(labelName);
|
||||||
|
List<LabelFinder> newLabels = labels.stream().map(label -> label.isLabel(labelName)
|
||||||
|
? new LabelFinder(labelName + separator + value)
|
||||||
|
: label)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return new ChangeMessage(this.text, this.groupSeparator, newLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public ChangeMessage withNewOrReplacedLabel(String labelName, String separator, String value) {
|
||||||
|
validateLabelName(labelName);
|
||||||
|
List<LabelFinder> newLabels = new ArrayList<>();
|
||||||
|
boolean wasReplaced = false;
|
||||||
|
|
||||||
|
for (LabelFinder originalLabel : labels) {
|
||||||
|
if (originalLabel.isLabel(labelName)) {
|
||||||
|
newLabels.add(new LabelFinder(labelName + separator + value));
|
||||||
|
wasReplaced = true;
|
||||||
|
} else {
|
||||||
|
newLabels.add(originalLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChangeMessage newChangeMessage = new ChangeMessage(this.text, this.groupSeparator, newLabels);
|
||||||
|
if (!wasReplaced) {
|
||||||
|
return newChangeMessage.withLabel(labelName, separator, value);
|
||||||
|
}
|
||||||
|
return newChangeMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a label by name if it exist.
|
||||||
|
*/
|
||||||
|
@CheckReturnValue
|
||||||
|
public ChangeMessage withRemovedLabelByName(String name) {
|
||||||
|
validateLabelName(name);
|
||||||
|
ImmutableList<LabelFinder> filteredLabels =
|
||||||
|
labels
|
||||||
|
.stream()
|
||||||
|
.filter(label -> !label.isLabel(name))
|
||||||
|
.collect(ImmutableList.toImmutableList());
|
||||||
|
return new ChangeMessage(this.text, this.groupSeparator, filteredLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a label by name and value if it exist.
|
||||||
|
*/
|
||||||
|
@CheckReturnValue
|
||||||
|
public ChangeMessage withRemovedLabelByNameAndValue(String name, String value) {
|
||||||
|
validateLabelName(name);
|
||||||
|
ImmutableList<LabelFinder> filteredLabels =
|
||||||
|
labels
|
||||||
|
.stream()
|
||||||
|
.filter(label -> !label.isLabel(name) || !label.getValue().equals(value))
|
||||||
|
.collect(ImmutableList.toImmutableList());
|
||||||
|
return new ChangeMessage(this.text, this.groupSeparator, filteredLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String validateLabelName(String label) {
|
||||||
|
Preconditions.checkArgument(LabelFinder.VALID_LABEL.matcher(label).matches(),
|
||||||
|
"Label '%s' is not a valid label", label);
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the text part of the message, leaving the labels untouched.L
|
||||||
|
*/
|
||||||
|
@CheckReturnValue
|
||||||
|
public ChangeMessage withText(String text) {
|
||||||
|
return new ChangeMessage(TRIM.trimFrom(text), this.groupSeparator, this.labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if (!text.isEmpty()) {
|
||||||
|
sb.append(text).append(labels.isEmpty() ? "\n" : groupSeparator);
|
||||||
|
}
|
||||||
|
for (LabelFinder label : labels) {
|
||||||
|
sb.append(label.getLine()).append('\n');
|
||||||
|
}
|
||||||
|
// Lets normalize in case parseAllAsLabels was used and all the labels where
|
||||||
|
// removed.
|
||||||
|
return TRIM.trimFrom(sb.toString()) + '\n';
|
||||||
|
}
|
||||||
|
}
|
109
third_party/copybara/java/com/google/copybara/ChangeVisitable.java
vendored
Normal file
109
third_party/copybara/java/com/google/copybara/ChangeVisitable.java
vendored
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableCollection;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface stating that the implementing class accepts child visitors to explore repository
|
||||||
|
* state beyond the changes being migrated.
|
||||||
|
*/
|
||||||
|
public interface ChangeVisitable <R extends Revision> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visit the parents of the {@code start} revision and call the visitor for each
|
||||||
|
* change. The visitor can stop the stream of changes at any moment by returning {@see
|
||||||
|
* VisitResult#TERMINATE}.
|
||||||
|
*
|
||||||
|
* <p>It is up to the Origin how and what changes it provides to the function.
|
||||||
|
*/
|
||||||
|
void visitChanges(@Nullable R start, ChangesVisitor visitor)
|
||||||
|
throws RepoException, ValidationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visit only changes that contain any of the labels in {@code labels}.
|
||||||
|
*/
|
||||||
|
default void visitChangesWithAnyLabel(
|
||||||
|
@Nullable R start, ImmutableCollection<String> labels, ChangesLabelVisitor visitor)
|
||||||
|
throws RepoException, ValidationException {
|
||||||
|
visitChanges(start, input -> {
|
||||||
|
// We could return all the label values, but this is really only used for
|
||||||
|
// RevId like ones and last is good enough for now.
|
||||||
|
Map<String, String> copy = Maps.newHashMap(Maps.transformValues(input.getLabels().asMap(),
|
||||||
|
Iterables::getLast));
|
||||||
|
copy.keySet().retainAll(labels);
|
||||||
|
if (copy.isEmpty()) {
|
||||||
|
return VisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
return visitor.visit(input, ImmutableMap.copyOf(copy));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A visitor of changes. An implementation of this interface is provided to {@see
|
||||||
|
* visitChanges} methods to visit changes in Origin or
|
||||||
|
* Destination history.
|
||||||
|
*/
|
||||||
|
interface ChangesVisitor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked for each change found. The implementation can chose to cancel the visitation by
|
||||||
|
* returning {@link VisitResult#TERMINATE}.
|
||||||
|
*/
|
||||||
|
VisitResult visit(Change<? extends Revision> input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A visitor of changes that only receives changes that match any of the passed labels.
|
||||||
|
*/
|
||||||
|
interface ChangesLabelVisitor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked for each change found that matches the labels.
|
||||||
|
*
|
||||||
|
* <p>Note that the {@code matchedLabels} can be disjoint with the labels in {@code input},
|
||||||
|
* since labels might be stored with a different string format.
|
||||||
|
*/
|
||||||
|
VisitResult visit(Change<? extends Revision> input, ImmutableMap<String, String> matchedLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result type for the function passed to
|
||||||
|
* {@see visitChanges}.
|
||||||
|
*/
|
||||||
|
enum VisitResult {
|
||||||
|
/**
|
||||||
|
* Continue. If more changes are available for visiting, the origin will call again the
|
||||||
|
* function with the next changes.
|
||||||
|
*/
|
||||||
|
CONTINUE,
|
||||||
|
/**
|
||||||
|
* Stop. Origin will not pass more changes to the visitor function. Usually used because the
|
||||||
|
* function found what it was looking for (For example a commit with a label).
|
||||||
|
*/
|
||||||
|
TERMINATE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
65
third_party/copybara/java/com/google/copybara/Changes.java
vendored
Normal file
65
third_party/copybara/java/com/google/copybara/Changes.java
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkMethod;
|
||||||
|
import com.google.devtools.build.lib.syntax.Sequence;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkList;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
|
||||||
|
/** Information about the changes being imported */
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "Changes",
|
||||||
|
category = StarlarkDocumentationCategory.BUILTIN,
|
||||||
|
doc =
|
||||||
|
"Data about the set of changes that are being migrated. "
|
||||||
|
+ "Each change includes information like: original author, change message, "
|
||||||
|
+ "labels, etc. You receive this as a field in TransformWork object for used defined "
|
||||||
|
+ "transformations")
|
||||||
|
public final class Changes implements StarlarkValue {
|
||||||
|
|
||||||
|
public static final Changes EMPTY = new Changes(ImmutableList.of(), ImmutableList.of());
|
||||||
|
|
||||||
|
private final Sequence<? extends Change<?>> current;
|
||||||
|
private final Sequence<? extends Change<?>> migrated;
|
||||||
|
|
||||||
|
public Changes(Iterable<? extends Change<?>> current, Iterable<? extends Change<?>> migrated) {
|
||||||
|
this.current = StarlarkList.immutableCopyOf(current);
|
||||||
|
this.migrated = StarlarkList.immutableCopyOf(migrated);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "current",
|
||||||
|
doc = "List of changes that will be migrated",
|
||||||
|
structField = true)
|
||||||
|
public final Sequence<? extends Change<?>> getCurrent() {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "migrated",
|
||||||
|
doc =
|
||||||
|
"List of changes that where migrated in previous Copybara executions or if using"
|
||||||
|
+ " ITERATIVE mode in previous iterations of this workflow.",
|
||||||
|
structField = true)
|
||||||
|
public Sequence<? extends Change<?>> getMigrated() {
|
||||||
|
return migrated;
|
||||||
|
}
|
||||||
|
}
|
218
third_party/copybara/java/com/google/copybara/CheckoutPath.java
vendored
Normal file
218
third_party/copybara/java/com/google/copybara/CheckoutPath.java
vendored
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
|
import com.google.copybara.doc.annotations.DocSignaturePrefix;
|
||||||
|
import com.google.copybara.util.FileUtil;
|
||||||
|
import com.google.copybara.util.FileUtil.ResolvedSymlink;
|
||||||
|
import com.google.copybara.util.Glob;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.Param;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkMethod;
|
||||||
|
import com.google.devtools.build.lib.syntax.EvalException;
|
||||||
|
import com.google.devtools.build.lib.syntax.Printer;
|
||||||
|
import com.google.devtools.build.lib.syntax.Starlark;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.LinkOption;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a file that is exposed to Skylark.
|
||||||
|
*
|
||||||
|
* <p>Files are always relative to the checkout dir and normalized.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "Path",
|
||||||
|
category = StarlarkDocumentationCategory.BUILTIN,
|
||||||
|
doc = "Represents a path in the checkout directory")
|
||||||
|
@DocSignaturePrefix("path")
|
||||||
|
public class CheckoutPath implements Comparable<CheckoutPath>, StarlarkValue {
|
||||||
|
|
||||||
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
private final Path path;
|
||||||
|
private final Path checkoutDir;
|
||||||
|
|
||||||
|
CheckoutPath(Path path, Path checkoutDir) {
|
||||||
|
this.path = Preconditions.checkNotNull(path);
|
||||||
|
this.checkoutDir = Preconditions.checkNotNull(checkoutDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CheckoutPath create(Path path) throws EvalException {
|
||||||
|
return createWithCheckoutDir(path, checkoutDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CheckoutPath createWithCheckoutDir(Path relative, Path checkoutDir) throws EvalException {
|
||||||
|
if (relative.isAbsolute()) {
|
||||||
|
throw Starlark.errorf("Absolute paths are not allowed: %s", relative);
|
||||||
|
}
|
||||||
|
return new CheckoutPath(relative.normalize(), checkoutDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "path", doc = "Full path relative to the checkout directory",
|
||||||
|
structField = true)
|
||||||
|
public String fullPath() {
|
||||||
|
return path.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "name",
|
||||||
|
doc = "Filename of the path. For foo/bar/baz.txt it would be baz.txt",
|
||||||
|
structField = true)
|
||||||
|
public String name() {
|
||||||
|
return path.getFileName().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "parent",
|
||||||
|
doc = "Get the parent path",
|
||||||
|
structField = true,
|
||||||
|
allowReturnNones = true)
|
||||||
|
public Object parent() throws EvalException {
|
||||||
|
Path parent = path.getParent();
|
||||||
|
if (parent == null) {
|
||||||
|
// nio equivalent of new_path("foo").parent returns null, but we want to be able to do
|
||||||
|
// foo.parent.resolve("bar"). While sibbling could be use for this, sometimes we'll need
|
||||||
|
// to return the parent folder and another function resolve a path based on that.
|
||||||
|
return path.toString().equals("") ? Starlark.NONE : create(path.getFileSystem().getPath(""));
|
||||||
|
}
|
||||||
|
return create(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "relativize",
|
||||||
|
doc =
|
||||||
|
"Constructs a relative path between this path and a given path. For example:<br>"
|
||||||
|
+ " path('a/b').relativize('a/b/c/d')<br>"
|
||||||
|
+ "returns 'c/d'",
|
||||||
|
parameters = {
|
||||||
|
@Param(
|
||||||
|
name = "other",
|
||||||
|
type = CheckoutPath.class,
|
||||||
|
doc = "The path to relativize against this path"),
|
||||||
|
})
|
||||||
|
public CheckoutPath relativize(CheckoutPath other) throws EvalException {
|
||||||
|
return create(path.relativize(other.path));
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "resolve",
|
||||||
|
doc = "Resolve the given path against this path.",
|
||||||
|
parameters = {
|
||||||
|
@Param(
|
||||||
|
name = "child",
|
||||||
|
type = Object.class,
|
||||||
|
doc =
|
||||||
|
"Resolve the given path against this path. The parameter"
|
||||||
|
+ " can be a string or a Path.")
|
||||||
|
})
|
||||||
|
public CheckoutPath resolve(Object child) throws EvalException {
|
||||||
|
if (child instanceof String) {
|
||||||
|
return create(path.resolve((String) child));
|
||||||
|
} else if (child instanceof CheckoutPath) {
|
||||||
|
return create(path.resolve(((CheckoutPath) child).path));
|
||||||
|
}
|
||||||
|
throw Starlark.errorf(
|
||||||
|
"Cannot resolve children for type %s: %s", child.getClass().getSimpleName(), child);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "resolve_sibling",
|
||||||
|
doc = "Resolve the given path against this path.",
|
||||||
|
parameters = {
|
||||||
|
@Param(
|
||||||
|
name = "other",
|
||||||
|
type = Object.class,
|
||||||
|
doc =
|
||||||
|
"Resolve the given path against this path. The parameter can be a string or"
|
||||||
|
+ " a Path."),
|
||||||
|
})
|
||||||
|
public CheckoutPath resolveSibling(Object other) throws EvalException {
|
||||||
|
if (other instanceof String) {
|
||||||
|
return create(path.resolveSibling((String) other));
|
||||||
|
} else if (other instanceof CheckoutPath) {
|
||||||
|
return create(path.resolveSibling(((CheckoutPath) other).path));
|
||||||
|
}
|
||||||
|
throw Starlark.errorf(
|
||||||
|
"Cannot resolve sibling for type %s: %s", other.getClass().getSimpleName(), other);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "attr",
|
||||||
|
doc = "Get the file attributes, for example size.",
|
||||||
|
structField = true)
|
||||||
|
public CheckoutPathAttributes attr() throws EvalException {
|
||||||
|
try {
|
||||||
|
return new CheckoutPathAttributes(path,
|
||||||
|
Files.readAttributes(checkoutDir.resolve(path), BasicFileAttributes.class,
|
||||||
|
LinkOption.NOFOLLOW_LINKS));
|
||||||
|
} catch (IOException e) {
|
||||||
|
String msg = "Error getting attributes for " + path + ":" + e;
|
||||||
|
logger.atSevere().withCause(e).log(msg);
|
||||||
|
throw Starlark.errorf("%s", msg); // or IOException?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "read_symlink", doc = "Read the symlink")
|
||||||
|
public CheckoutPath readSymbolicLink() throws EvalException {
|
||||||
|
try {
|
||||||
|
Path symlinkPath = checkoutDir.resolve(path);
|
||||||
|
if (!Files.isSymbolicLink(symlinkPath)) {
|
||||||
|
throw Starlark.errorf("%s is not a symlink", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResolvedSymlink resolvedSymlink =
|
||||||
|
FileUtil.resolveSymlink(Glob.ALL_FILES.relativeTo(checkoutDir), symlinkPath);
|
||||||
|
if (!resolvedSymlink.isAllUnderRoot()) {
|
||||||
|
throw Starlark.errorf(
|
||||||
|
"Symlink %s points to a file outside the checkout dir: %s",
|
||||||
|
symlinkPath, resolvedSymlink.getRegularFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
return create(checkoutDir.relativize(resolvedSymlink.getRegularFile()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
String msg = String.format("Cannot resolve symlink %s: %s", path, e);
|
||||||
|
logger.atSevere().withCause(e).log(msg);
|
||||||
|
throw Starlark.errorf("%s", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return path.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(CheckoutPath o) {
|
||||||
|
return this.path.compareTo(o.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void repr(Printer printer) {
|
||||||
|
printer.append(path.toString());
|
||||||
|
}
|
||||||
|
}
|
62
third_party/copybara/java/com/google/copybara/CheckoutPathAttributes.java
vendored
Normal file
62
third_party/copybara/java/com/google/copybara/CheckoutPathAttributes.java
vendored
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkMethod;
|
||||||
|
import com.google.devtools.build.lib.syntax.Starlark;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
|
||||||
|
/** Represents file attributes exposed to Skylark. */
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "PathAttributes",
|
||||||
|
category = StarlarkDocumentationCategory.BUILTIN,
|
||||||
|
doc = "Represents a path attributes like size.")
|
||||||
|
public class CheckoutPathAttributes implements StarlarkValue {
|
||||||
|
|
||||||
|
private final Path path;
|
||||||
|
private final BasicFileAttributes attributes;
|
||||||
|
|
||||||
|
CheckoutPathAttributes(Path path, BasicFileAttributes attributes) {
|
||||||
|
this.path = Preconditions.checkNotNull(path);
|
||||||
|
this.attributes = Preconditions.checkNotNull(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "size",
|
||||||
|
doc = "The size of the file. Throws an error if file size > 2GB.",
|
||||||
|
structField = true)
|
||||||
|
public int size() throws Exception {
|
||||||
|
long size = attributes.size();
|
||||||
|
try {
|
||||||
|
return Math.toIntExact(size);
|
||||||
|
} catch (ArithmeticException e) {
|
||||||
|
throw Starlark.errorf("File %s is too big to compute the size: %d bytes", path, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "symlink",
|
||||||
|
doc = "Returns true if it is a symlink", structField = true)
|
||||||
|
public boolean isSymlink() {
|
||||||
|
return attributes.isSymbolicLink();
|
||||||
|
}
|
||||||
|
}
|
96
third_party/copybara/java/com/google/copybara/CommandEnv.java
vendored
Normal file
96
third_party/copybara/java/com/google/copybara/CommandEnv.java
vendored
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.copybara.exception.CommandLineException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Environment information for command execution: arguments, workdir, etc.
|
||||||
|
*/
|
||||||
|
public class CommandEnv {
|
||||||
|
|
||||||
|
private final Path workdir;
|
||||||
|
private final Options options;
|
||||||
|
private final ImmutableList<String> args;
|
||||||
|
@Nullable
|
||||||
|
private ConfigFileArgs configFileArgs;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public CommandEnv(Path workdir, Options options, ImmutableList<String> args) {
|
||||||
|
this.workdir = Preconditions.checkNotNull(workdir);
|
||||||
|
this.options = Preconditions.checkNotNull(options);
|
||||||
|
this.args = Preconditions.checkNotNull(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the arguments parsed as config [migration [source_ref]...] if the command uses that format.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public ConfigFileArgs getConfigFileArgs() {
|
||||||
|
return configFileArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the CLI arguments as config [workflow [source_ref]...]
|
||||||
|
*/
|
||||||
|
public ConfigFileArgs parseConfigFileArgs(CopybaraCmd cmd, boolean usesSourceRef)
|
||||||
|
throws CommandLineException {
|
||||||
|
Preconditions.checkState(this.configFileArgs == null, "parseConfigFileArgs was already"
|
||||||
|
+ " called. Only one invocation allowed.");
|
||||||
|
if (args.isEmpty()) {
|
||||||
|
throw new CommandLineException(
|
||||||
|
String.format("Configuration file missing for '%s' subcommand.", cmd.name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
String configPath = args.get(0);
|
||||||
|
|
||||||
|
if (args.size() < 2) {
|
||||||
|
configFileArgs = new ConfigFileArgs(configPath, /*workflowName=*/null);
|
||||||
|
return configFileArgs;
|
||||||
|
}
|
||||||
|
String workflowName = args.get(1);
|
||||||
|
if (args.size() < 3) {
|
||||||
|
configFileArgs = new ConfigFileArgs(configPath, workflowName);
|
||||||
|
return configFileArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!usesSourceRef) {
|
||||||
|
throw new CommandLineException(
|
||||||
|
String.format("Too many arguments for subcommand '%s'", cmd.name()));
|
||||||
|
}
|
||||||
|
configFileArgs = new ConfigFileArgs(configPath, workflowName, args.subList(2, args.size()));
|
||||||
|
return configFileArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Path getWorkdir() {
|
||||||
|
return workdir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Options getOptions() {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableList<String> getArgs() {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
}
|
72
third_party/copybara/java/com/google/copybara/ConfigFileArgs.java
vendored
Normal file
72
third_party/copybara/java/com/google/copybara/ConfigFileArgs.java
vendored
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arguments for a command that expects the CLI arguments be like: <code>config_file [workflow
|
||||||
|
* [source_ref]]</code>
|
||||||
|
*/
|
||||||
|
public final class ConfigFileArgs {
|
||||||
|
|
||||||
|
private final String configPath;
|
||||||
|
@Nullable
|
||||||
|
private final String workflowName;
|
||||||
|
private final ImmutableList<String> sourceRefs;
|
||||||
|
|
||||||
|
ConfigFileArgs(String configPath, @Nullable String workflowName) {
|
||||||
|
this(configPath, workflowName, ImmutableList.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigFileArgs(String configPath, @Nullable String workflowName, List<String> sourceRefs) {
|
||||||
|
this.configPath = Preconditions.checkNotNull(configPath);
|
||||||
|
this.workflowName = workflowName;
|
||||||
|
this.sourceRefs = ImmutableList.copyOf(sourceRefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConfigPath() {
|
||||||
|
return configPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWorkflowName() {
|
||||||
|
return workflowName == null ? "default" : workflowName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasWorkflowName() {
|
||||||
|
return workflowName != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first sourceRef from the command arguments, or null if no source ref was provided.
|
||||||
|
*
|
||||||
|
* <p>This method is provided for convenience, for subocmmands that only care about the first
|
||||||
|
* source_ref.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getSourceRef() {
|
||||||
|
return Iterables.getFirst(sourceRefs, /*default*/ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableList<String> getSourceRefs() {
|
||||||
|
return sourceRefs;
|
||||||
|
}
|
||||||
|
}
|
40
third_party/copybara/java/com/google/copybara/ConfigItemDescription.java
vendored
Normal file
40
third_party/copybara/java/com/google/copybara/ConfigItemDescription.java
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
|
import com.google.copybara.util.Glob;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for self-description. The information returned should be sufficient to create a new
|
||||||
|
* instance with identical migration behavior (but potentially different side effects). This is
|
||||||
|
* intended for discovering changes in a config.
|
||||||
|
*/
|
||||||
|
public interface ConfigItemDescription {
|
||||||
|
|
||||||
|
default String getType() {
|
||||||
|
return getClass().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a key-value ist of the options the endpoint was instantiated with. */
|
||||||
|
default ImmutableSetMultimap<String, String> describe(Glob originFiles) {
|
||||||
|
ImmutableSetMultimap.Builder<String, String> builder =
|
||||||
|
new ImmutableSetMultimap.Builder<String, String>()
|
||||||
|
.put("type", getType());
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
102
third_party/copybara/java/com/google/copybara/ConfigLoader.java
vendored
Normal file
102
third_party/copybara/java/com/google/copybara/ConfigLoader.java
vendored
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.copybara.config.Config;
|
||||||
|
import com.google.copybara.config.ConfigFile;
|
||||||
|
import com.google.copybara.config.SkylarkParser;
|
||||||
|
import com.google.copybara.config.SkylarkParser.ConfigWithDependencies;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.profiler.Profiler.ProfilerTask;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import com.google.copybara.util.console.StarlarkMode;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the configuration from a given config file.
|
||||||
|
*/
|
||||||
|
public class ConfigLoader {
|
||||||
|
|
||||||
|
private final SkylarkParser skylarkParser;
|
||||||
|
private final ConfigFile configFile;
|
||||||
|
private final ModuleSet moduleSet;
|
||||||
|
|
||||||
|
public ConfigLoader(ModuleSet moduleSet, ConfigFile configFile, StarlarkMode validateStarlark) {
|
||||||
|
this.moduleSet = moduleSet;
|
||||||
|
this.skylarkParser = new SkylarkParser(this.moduleSet.getStaticModules(), validateStarlark);
|
||||||
|
this.configFile = Preconditions.checkNotNull(configFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of the location of this configuration.
|
||||||
|
*/
|
||||||
|
public String location() {
|
||||||
|
return configFile.path();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the configuration using this loader.
|
||||||
|
* @param console the console to use for reporting progress/errors
|
||||||
|
*/
|
||||||
|
public Config load(Console console) throws ValidationException, IOException {
|
||||||
|
return loadForConfigFile(console, configFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the configuration using this loader.
|
||||||
|
* @param console the console to use for reporting progress/errors
|
||||||
|
*/
|
||||||
|
public ConfigWithDependencies loadWithDependencies(Console console)
|
||||||
|
throws ValidationException, IOException {
|
||||||
|
console.progressFmt("Loading config and dependencies %s", configFile.getIdentifier());
|
||||||
|
|
||||||
|
try (ProfilerTask ignore = moduleSet.getOptions().get(GeneralOptions.class).profiler()
|
||||||
|
.start("loading_config_with_deps")) {
|
||||||
|
return skylarkParser.getConfigWithTransitiveImports(configFile, moduleSet, console);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Config loadForConfigFile(Console console, ConfigFile configFile)
|
||||||
|
throws IOException, ValidationException {
|
||||||
|
console.progressFmt("Loading config %s", configFile.getIdentifier());
|
||||||
|
|
||||||
|
try (ProfilerTask ignore = moduleSet.getOptions().get(GeneralOptions.class).profiler()
|
||||||
|
.start("loading_config")) {
|
||||||
|
return skylarkParser.loadConfig(configFile, moduleSet, console);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Config doLoadForRevision(Console console, Revision revision)
|
||||||
|
throws ValidationException, RepoException {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"This origin/configuration doesn't allow loading configs from specific revisions");
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Config loadForRevision(Console console, Revision revision)
|
||||||
|
throws ValidationException, RepoException {
|
||||||
|
try (ProfilerTask ignore = moduleSet.getOptions().get(GeneralOptions.class).profiler()
|
||||||
|
.start("loading_config_for_revision")) {
|
||||||
|
return doLoadForRevision(console, revision);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsLoadForRevision() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
30
third_party/copybara/java/com/google/copybara/ConfigLoaderProvider.java
vendored
Normal file
30
third_party/copybara/java/com/google/copybara/ConfigLoaderProvider.java
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/** A class that given a main config path (copy.bara.sky file) returns a ConfigLoader. */
|
||||||
|
public interface ConfigLoaderProvider {
|
||||||
|
|
||||||
|
/** Create a new loader for {@code configPath} */
|
||||||
|
ConfigLoader newLoader(String configPath, @Nullable String sourceRef)
|
||||||
|
throws ValidationException, IOException;
|
||||||
|
|
||||||
|
}
|
31
third_party/copybara/java/com/google/copybara/ContextProvider.java
vendored
Normal file
31
third_party/copybara/java/com/google/copybara/ContextProvider.java
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.copybara.config.Config;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/** A class providing additional context for CMD*/
|
||||||
|
public interface ContextProvider {
|
||||||
|
/** get context for CMD */
|
||||||
|
ImmutableMap<String, String> getContext(Config config, ConfigFileArgs configFileArgs,
|
||||||
|
ConfigLoaderProvider configLoaderProvider, Console console)
|
||||||
|
throws ValidationException, IOException;
|
||||||
|
}
|
41
third_party/copybara/java/com/google/copybara/CopybaraCmd.java
vendored
Normal file
41
third_party/copybara/java/com/google/copybara/CopybaraCmd.java
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.util.ExitCode;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Copybara command like 'info' 'migrate', etc.
|
||||||
|
*/
|
||||||
|
public interface CopybaraCmd {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the command
|
||||||
|
* @param commandEnv Command environment: Params, workdir, etc.
|
||||||
|
* @return Result exit code
|
||||||
|
*/
|
||||||
|
ExitCode run(CommandEnv commandEnv) throws ValidationException, IOException, RepoException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command name
|
||||||
|
*/
|
||||||
|
String name();
|
||||||
|
|
||||||
|
}
|
1632
third_party/copybara/java/com/google/copybara/Core.java
vendored
Normal file
1632
third_party/copybara/java/com/google/copybara/Core.java
vendored
Normal file
File diff suppressed because it is too large
Load diff
156
third_party/copybara/java/com/google/copybara/CoreGlobal.java
vendored
Normal file
156
third_party/copybara/java/com/google/copybara/CoreGlobal.java
vendored
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.copybara.authoring.Author;
|
||||||
|
import com.google.copybara.config.SkylarkUtil;
|
||||||
|
import com.google.copybara.doc.annotations.Example;
|
||||||
|
import com.google.copybara.util.Glob;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.Param;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkGlobalLibrary;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkMethod;
|
||||||
|
import com.google.devtools.build.lib.syntax.EvalException;
|
||||||
|
import com.google.devtools.build.lib.syntax.Sequence;
|
||||||
|
import com.google.devtools.build.lib.syntax.Starlark;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A module to expose Skylark glob(), parse_message(), etc functions.
|
||||||
|
*
|
||||||
|
* <p>Don't add functions here and prefer "core" namespace unless it is something really general
|
||||||
|
*/
|
||||||
|
@StarlarkGlobalLibrary
|
||||||
|
public class CoreGlobal implements StarlarkValue {
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "glob",
|
||||||
|
doc =
|
||||||
|
"Glob returns a list of every file in the workdir that matches at least one"
|
||||||
|
+ " pattern in include and does not match any of the patterns in exclude.",
|
||||||
|
parameters = {
|
||||||
|
@Param(
|
||||||
|
name = "include",
|
||||||
|
type = Sequence.class,
|
||||||
|
named = true,
|
||||||
|
generic1 = String.class,
|
||||||
|
doc = "The list of glob patterns to include"),
|
||||||
|
@Param(
|
||||||
|
name = "exclude",
|
||||||
|
type = Sequence.class,
|
||||||
|
generic1 = String.class,
|
||||||
|
doc = "The list of glob patterns to exclude",
|
||||||
|
defaultValue = "[]",
|
||||||
|
named = true,
|
||||||
|
positional = false),
|
||||||
|
})
|
||||||
|
@Example(
|
||||||
|
title = "Simple usage",
|
||||||
|
before = "Include all the files under a folder except for `internal` folder files:",
|
||||||
|
code = "glob([\"foo/**\"], exclude = [\"foo/internal/**\"])")
|
||||||
|
@Example(
|
||||||
|
title = "Multiple folders",
|
||||||
|
before = "Globs can have multiple inclusive rules:",
|
||||||
|
code = "glob([\"foo/**\", \"bar/**\", \"baz/**.java\"])",
|
||||||
|
after =
|
||||||
|
"This will include all files inside `foo` and `bar` folders and Java files"
|
||||||
|
+ " inside `baz` folder.")
|
||||||
|
@Example(
|
||||||
|
title = "Multiple excludes",
|
||||||
|
before = "Globs can have multiple exclusive rules:",
|
||||||
|
code = "glob([\"foo/**\"], exclude = [\"foo/internal/**\", \"foo/confidential/**\" ])",
|
||||||
|
after =
|
||||||
|
"Include all the files of `foo` except the ones in `internal` and `confidential`"
|
||||||
|
+ " folders")
|
||||||
|
@Example(
|
||||||
|
title = "All BUILD files recursively",
|
||||||
|
before =
|
||||||
|
"Copybara uses Java globbing. The globbing is very similar to Bash one. This"
|
||||||
|
+ " means that recursive globbing for a filename is a bit more tricky:",
|
||||||
|
code = "glob([\"BUILD\", \"**/BUILD\"])",
|
||||||
|
after =
|
||||||
|
"This is the correct way of matching all `BUILD` files recursively, including the"
|
||||||
|
+ " one in the root. `**/BUILD` would only match `BUILD` files in subdirectories.")
|
||||||
|
@Example(
|
||||||
|
title = "Matching multiple strings with one expression",
|
||||||
|
before =
|
||||||
|
"While two globs can be used for matching two directories, there is a more"
|
||||||
|
+ " compact approach:",
|
||||||
|
code = "glob([\"{java,javatests}/**\"])",
|
||||||
|
after = "This matches any file in `java` and `javatests` folders.")
|
||||||
|
@Example(
|
||||||
|
title = "Glob union",
|
||||||
|
before =
|
||||||
|
"This is useful when you want to exclude a broad subset of files but you want to"
|
||||||
|
+ " still include some of those files.",
|
||||||
|
code =
|
||||||
|
"glob([\"folder/**\"], exclude = [\"folder/**.excluded\"])"
|
||||||
|
+ " + glob([\'folder/includeme.excluded\'])",
|
||||||
|
after =
|
||||||
|
"This matches all the files in `folder`, excludes all files in that folder that"
|
||||||
|
+ " ends with `.excluded` but keeps `folder/includeme.excluded`<br><br>"
|
||||||
|
+ "`+` operator for globs is equivalent to `OR` operation.")
|
||||||
|
public Glob glob(Sequence<?> include, Sequence<?> exclude) throws EvalException {
|
||||||
|
List<String> includeStrings = SkylarkUtil.convertStringList(include, "include");
|
||||||
|
List<String> excludeStrings = SkylarkUtil.convertStringList(exclude, "exclude");
|
||||||
|
try {
|
||||||
|
return Glob.createGlob(includeStrings, excludeStrings);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw Starlark.errorf(
|
||||||
|
"Cannot create a glob from: include='%s' and exclude='%s': %s",
|
||||||
|
includeStrings, excludeStrings, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "parse_message",
|
||||||
|
doc = "Returns a ChangeMessage parsed from a well formed string.",
|
||||||
|
parameters = {
|
||||||
|
@Param(
|
||||||
|
name = "message",
|
||||||
|
named = true,
|
||||||
|
type = String.class,
|
||||||
|
doc = "The contents of the change message"),
|
||||||
|
})
|
||||||
|
public ChangeMessage parseMessage(String changeMessage) throws EvalException {
|
||||||
|
try {
|
||||||
|
return ChangeMessage.parseMessage(changeMessage);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw Starlark.errorf("Cannot parse change message '%s': %s", changeMessage, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "new_author",
|
||||||
|
doc = "Create a new author from a string with the form 'name <foo@bar.com>'",
|
||||||
|
parameters = {
|
||||||
|
@Param(
|
||||||
|
name = "author_string",
|
||||||
|
type = String.class,
|
||||||
|
named = true,
|
||||||
|
doc = "A string representation of the author with the form 'name <foo@bar.com>'"),
|
||||||
|
})
|
||||||
|
@Example(
|
||||||
|
title = "Create a new author",
|
||||||
|
before = "",
|
||||||
|
code = "new_author('Foo Bar <foobar@myorg.com>')")
|
||||||
|
public Author newAuthor(String authorString) throws EvalException {
|
||||||
|
return Author.parse(authorString);
|
||||||
|
}
|
||||||
|
}
|
185
third_party/copybara/java/com/google/copybara/Destination.java
vendored
Normal file
185
third_party/copybara/java/com/google/copybara/Destination.java
vendored
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.util.Glob;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/** A repository which a source of truth can be copied to. */
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "destination",
|
||||||
|
doc = "A repository which a source of truth can be copied to",
|
||||||
|
category = StarlarkDocumentationCategory.TOP_LEVEL_TYPE,
|
||||||
|
documented = false)
|
||||||
|
public interface Destination<R extends Revision> extends ConfigItemDescription, StarlarkValue {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object which is capable of writing multiple revisions to the destination. This object is
|
||||||
|
* allowed to maintain state between the writing of revisions if applicable (for instance, to
|
||||||
|
* create multiple changes which are dependent on one another that require review before
|
||||||
|
* submission).
|
||||||
|
*
|
||||||
|
* <p>A single instance of this class is used to import either a single change, or a sequence of
|
||||||
|
* changes where each change is the following change's parent.
|
||||||
|
*/
|
||||||
|
interface Writer<R extends Revision> extends ChangeVisitable<R> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the status of the import at the destination.
|
||||||
|
*
|
||||||
|
* <p>This method may have undefined behavior if called after {@link #write(TransformResult,
|
||||||
|
* Glob, Console)}.
|
||||||
|
*
|
||||||
|
* @param labelName the label used in the destination for storing the last migrated ref
|
||||||
|
* @param destinationFiles the glob to use for filtering changes (optional)
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
DestinationStatus getDestinationStatus(Glob destinationFiles, String labelName)
|
||||||
|
throws RepoException, ValidationException;
|
||||||
|
/**
|
||||||
|
* Returns true if this destination stores revisions in the repository so that
|
||||||
|
* {@link #getDestinationStatus(Glob, String)} can be used for discovering the state of the
|
||||||
|
* destination and we can use the methods in {@link ChangeVisitable}.
|
||||||
|
*/
|
||||||
|
boolean supportsHistory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the fully-transformed repository stored at {@code workdir} to this destination.
|
||||||
|
* @param transformResult what to write to the destination
|
||||||
|
* @param destinationFiles the glob to use for write. This glob might be different from the
|
||||||
|
* one received in {@code {@link #getDestinationStatus(Glob, String)}} due to read config from
|
||||||
|
* change configuration.
|
||||||
|
* @param console console to be used for printing messages
|
||||||
|
* @return one or more destination effects
|
||||||
|
*
|
||||||
|
* @throws ValidationException if an user attributable error happens during the write
|
||||||
|
* @throws RepoException if there was an issue with the destination repository
|
||||||
|
* @throws IOException if a file access error happens during the write
|
||||||
|
*/
|
||||||
|
ImmutableList<DestinationEffect> write(TransformResult transformResult, Glob destinationFiles,
|
||||||
|
Console console) throws ValidationException, RepoException, IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility endpoint for accessing and adding feedback data.
|
||||||
|
* @param console console to use for reporting information to the user
|
||||||
|
*/
|
||||||
|
default Endpoint getFeedbackEndPoint(Console console) throws ValidationException {
|
||||||
|
return Endpoint.NOOP_ENDPOINT;
|
||||||
|
}
|
||||||
|
|
||||||
|
default DestinationReader getDestinationReader(
|
||||||
|
Console console, @Nullable Origin.Baseline<?> baseline, Path workdir)
|
||||||
|
throws ValidationException, RepoException {
|
||||||
|
return DestinationReader.NOT_IMPLEMENTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a writer which is capable of writing to this destination. This writer may maintain
|
||||||
|
* state between writing of revisions.
|
||||||
|
*
|
||||||
|
* <p>This method should only do trivial initialization of the writer, since it does not have
|
||||||
|
* access to a {@link Console}.
|
||||||
|
*
|
||||||
|
* @param writerContext Contains all the information for writing to destination, including
|
||||||
|
* workflowName, destinationFiles, * dryRun, revision, and oldWriter
|
||||||
|
* @throws ValidationException if the writer could not be created because of a user error. For
|
||||||
|
* instance, the destination cannot be used with the given {@code destinationFiles}.
|
||||||
|
*/
|
||||||
|
Writer<R> newWriter(WriterContext writerContext) throws ValidationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a reverse workflow with an {@code Origin} than is of the same type as this destination,
|
||||||
|
* the label that that {@link Origin#getLabelName()} would return.
|
||||||
|
*
|
||||||
|
* <p>This label name is used by the origin in the reverse workflow to stamp it's original
|
||||||
|
* revision id. Destinations return the origin label so that a baseline label can be found when
|
||||||
|
* using {@link WorkflowMode#CHANGE_REQUEST}.
|
||||||
|
*/
|
||||||
|
String getLabelNameWhenOrigin() throws ValidationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents the status of the destination. It includes the baseline revision
|
||||||
|
* and if it is a code review destination, the list of pending changes that have been already
|
||||||
|
* migrated. In order: First change is the oldest one.
|
||||||
|
*/
|
||||||
|
final class DestinationStatus {
|
||||||
|
|
||||||
|
private final String baseline;
|
||||||
|
private final ImmutableList<String> pendingChanges;
|
||||||
|
|
||||||
|
public DestinationStatus(String baseline, ImmutableList<String> pendingChanges) {
|
||||||
|
this.baseline = Preconditions.checkNotNull(baseline);
|
||||||
|
this.pendingChanges = Preconditions.checkNotNull(pendingChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String representation of the latest migrated revision in the baseline.
|
||||||
|
*/
|
||||||
|
public String getBaseline() {
|
||||||
|
return Preconditions.checkNotNull(baseline, "Trying to get baseline for NO_STATUS");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String representation of the migrated revisions that are in pending state in the destination.
|
||||||
|
* First element is the oldest one. Last element the newest one.
|
||||||
|
*/
|
||||||
|
public ImmutableList<String> getPendingChanges() {
|
||||||
|
return Preconditions.checkNotNull(pendingChanges,
|
||||||
|
"Trying to get pendingChanges for NO_STATUS");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DestinationStatus that = (DestinationStatus) o;
|
||||||
|
return Objects.equals(baseline, that.baseline)
|
||||||
|
&& Objects.equals(pendingChanges, that.pendingChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(baseline, pendingChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("baseline", baseline)
|
||||||
|
.add("pendingChanges", pendingChanges)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
343
third_party/copybara/java/com/google/copybara/DestinationEffect.java
vendored
Normal file
343
third_party/copybara/java/com/google/copybara/DestinationEffect.java
vendored
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkMethod;
|
||||||
|
import com.google.devtools.build.lib.syntax.Printer;
|
||||||
|
import com.google.devtools.build.lib.syntax.Sequence;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkList;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.util.Objects;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/** An effect happening in the destination as a consequence of the migration */
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "destination_effect",
|
||||||
|
category = StarlarkDocumentationCategory.BUILTIN,
|
||||||
|
doc = "Represents an effect that happened in the destination due to a single migration")
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class DestinationEffect implements StarlarkValue {
|
||||||
|
private final Type type;
|
||||||
|
private final String summary;
|
||||||
|
private final ImmutableList<OriginRef> originRefs;
|
||||||
|
@Nullable private final DestinationRef destinationRef;
|
||||||
|
private final ImmutableList<String> errors;
|
||||||
|
|
||||||
|
public DestinationEffect(
|
||||||
|
Type type,
|
||||||
|
String summary,
|
||||||
|
Iterable<? extends OriginRef> originRefs,
|
||||||
|
@Nullable DestinationRef destinationRef) {
|
||||||
|
this(type, summary, originRefs, destinationRef, ImmutableList.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DestinationEffect(
|
||||||
|
Type type,
|
||||||
|
String summary,
|
||||||
|
Iterable<? extends OriginRef> originRefs,
|
||||||
|
@Nullable DestinationRef destinationRef,
|
||||||
|
Iterable<String> errors) {
|
||||||
|
this.type = Preconditions.checkNotNull(type);
|
||||||
|
this.summary = Preconditions.checkNotNull(summary);
|
||||||
|
this.originRefs = ImmutableList.copyOf(Preconditions.checkNotNull(originRefs));
|
||||||
|
this.destinationRef = destinationRef;
|
||||||
|
this.errors = ImmutableList.copyOf(Preconditions.checkNotNull(errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the origin references included in this effect. */
|
||||||
|
public ImmutableList<OriginRef> getOriginRefs() {
|
||||||
|
return originRefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "origin_refs",
|
||||||
|
doc = "List of origin changes that were included in" + " this migration",
|
||||||
|
structField = true)
|
||||||
|
public final Sequence<? extends OriginRef> getOriginRefsSkylark() {
|
||||||
|
return StarlarkList.immutableCopyOf(originRefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the type of effect that happened: Create, updated, noop or error */
|
||||||
|
public Type getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "type",
|
||||||
|
doc =
|
||||||
|
"Return the type of effect that happened: CREATED, UPDATED, NOOP, INSUFFICIENT_APPROVALS"
|
||||||
|
+ " or ERROR",
|
||||||
|
structField = true
|
||||||
|
)
|
||||||
|
public String getTypeSkylark() {
|
||||||
|
return type.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Textual summary of what happened. Users of this class should not try to parse this field. */
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "summary",
|
||||||
|
doc =
|
||||||
|
"Textual summary of what happened. Users of this class should not try to parse this"
|
||||||
|
+ " field.",
|
||||||
|
structField = true
|
||||||
|
)
|
||||||
|
public String getSummary() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destination reference updated/created. Might be null if there was no effect. Might be set even
|
||||||
|
* if the type is error (For example a synchronous presubmit test failed but a review was
|
||||||
|
* created).
|
||||||
|
*/
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "destination_ref",
|
||||||
|
doc =
|
||||||
|
"Destination reference updated/created. Might be null if there was no effect. Might be"
|
||||||
|
+ " set even if the type is error (For example a synchronous presubmit test failed but"
|
||||||
|
+ " a review was created).",
|
||||||
|
structField = true,
|
||||||
|
allowReturnNones = true
|
||||||
|
)
|
||||||
|
@Nullable
|
||||||
|
public DestinationRef getDestinationRef() {
|
||||||
|
return destinationRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of errors that happened during the write to the destination. This can be used for example
|
||||||
|
* for synchronous presubmit failures.
|
||||||
|
*/
|
||||||
|
public ImmutableList<String> getErrors() {
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "errors",
|
||||||
|
doc = "List of errors that happened during the migration",
|
||||||
|
structField = true)
|
||||||
|
public final Sequence<String> getErrorsSkylark() {
|
||||||
|
return StarlarkList.immutableCopyOf(errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DestinationEffect that = (DestinationEffect) o;
|
||||||
|
return type == that.type
|
||||||
|
&& Objects.equals(summary, that.summary)
|
||||||
|
&& Objects.equals(originRefs, that.originRefs)
|
||||||
|
&& Objects.equals(destinationRef, that.destinationRef)
|
||||||
|
&& Objects.equals(errors, that.errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void repr(Printer printer) {
|
||||||
|
printer.append(toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("type", type)
|
||||||
|
.add("summary", summary)
|
||||||
|
.add("originRefs", originRefs)
|
||||||
|
.add("destinationRef", destinationRef)
|
||||||
|
.add("errors", errors)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(type, summary, originRefs, destinationRef, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Type of effect on the destination */
|
||||||
|
public enum Type {
|
||||||
|
/** A new review or change was created */
|
||||||
|
CREATED,
|
||||||
|
/** An existing review or change was updated */
|
||||||
|
UPDATED,
|
||||||
|
/**
|
||||||
|
* The change was a noop. {@code destinationRef} might still be populated if the noop was
|
||||||
|
* detected against an existing review or pending change.
|
||||||
|
*/
|
||||||
|
NOOP,
|
||||||
|
/** The effect couldn't happen because the change doesn't have enough approvals */
|
||||||
|
INSUFFICIENT_APPROVALS,
|
||||||
|
/**
|
||||||
|
* A user attributable error happened that prevented the destination from creating/updating the
|
||||||
|
* change.
|
||||||
|
*/
|
||||||
|
ERROR,
|
||||||
|
/**
|
||||||
|
* An error not attributable to the user that could be retried (RepoException, IOException...)
|
||||||
|
*/
|
||||||
|
TEMPORARY_ERROR,
|
||||||
|
/**
|
||||||
|
* A starting effect of a migration that is eventually expected to trigger another migration
|
||||||
|
* asynchronously. This allows to have 'dependant' migrations defined by users.
|
||||||
|
* An example of this: a workflow migrates code from a Gerrit review to a GitHub PR, and a
|
||||||
|
* feedback migration migrates the test results from a CI in GitHub back to the Gerrit change.
|
||||||
|
* This effect would be created on the former one.
|
||||||
|
*/
|
||||||
|
STARTED,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reference to the change/review read from the origin. */
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "origin_ref",
|
||||||
|
category = StarlarkDocumentationCategory.BUILTIN,
|
||||||
|
doc = "Reference to the change/review in the origin.")
|
||||||
|
public static class OriginRef implements StarlarkValue {
|
||||||
|
private final String ref;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public OriginRef(String id) {
|
||||||
|
this.ref = Preconditions.checkNotNull(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Origin reference*/
|
||||||
|
@StarlarkMethod(name = "ref", doc = "Origin reference ref", structField = true)
|
||||||
|
public String getRef() {
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
OriginRef originRef = (OriginRef) o;
|
||||||
|
return Objects.equals(ref, originRef.ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void repr(Printer printer) {
|
||||||
|
printer.append(toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("ref", ref)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reference to the change/review created/updated on the destination. */
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "destination_ref",
|
||||||
|
category = StarlarkDocumentationCategory.BUILTIN,
|
||||||
|
doc = "Reference to the change/review created/updated on the destination.")
|
||||||
|
public static class DestinationRef implements StarlarkValue {
|
||||||
|
@Nullable private final String url;
|
||||||
|
private final String id;
|
||||||
|
private final String type;
|
||||||
|
|
||||||
|
public DestinationRef(String id, String type, @Nullable String url) {
|
||||||
|
this.id = Preconditions.checkNotNull(id);
|
||||||
|
this.type = Preconditions.checkNotNull(type);
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Destination reference id */
|
||||||
|
@StarlarkMethod(name = "id", doc = "Destination reference id", structField = true)
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of reference created. Each destination defines its own and guarantees to be more stable
|
||||||
|
* than urls/ids.
|
||||||
|
*/
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "type",
|
||||||
|
doc =
|
||||||
|
"Type of reference created. Each destination defines its own and guarantees to be more"
|
||||||
|
+ " stable than urls/ids",
|
||||||
|
structField = true
|
||||||
|
)
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Url, if any, of the destination change */
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "url",
|
||||||
|
doc = "Url, if any, of the destination change",
|
||||||
|
structField = true,
|
||||||
|
allowReturnNones = true
|
||||||
|
)
|
||||||
|
@Nullable
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DestinationRef that = (DestinationRef) o;
|
||||||
|
return Objects.equals(url, that.url)
|
||||||
|
&& Objects.equals(id, that.id)
|
||||||
|
&& Objects.equals(type, that.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(url, id, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void repr(Printer printer) {
|
||||||
|
printer.append(toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("url", url)
|
||||||
|
.add("id", id)
|
||||||
|
.add("type", type)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
third_party/copybara/java/com/google/copybara/DestinationReader.java
vendored
Normal file
103
third_party/copybara/java/com/google/copybara/DestinationReader.java
vendored
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.copybara.doc.annotations.Example;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.util.Glob;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.Param;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkMethod;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
|
||||||
|
/** An api handle to read files from the destination, rather than just the origin. */
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "destination_reader",
|
||||||
|
doc = "Handle to read from the destination",
|
||||||
|
category = StarlarkDocumentationCategory.TOP_LEVEL_TYPE,
|
||||||
|
documented = true)
|
||||||
|
public abstract class DestinationReader implements StarlarkValue {
|
||||||
|
|
||||||
|
public static final DestinationReader NOT_IMPLEMENTED = new DestinationReader() {
|
||||||
|
@Override
|
||||||
|
public String readFile(String path) throws RepoException {
|
||||||
|
throw new RepoException("Reading files is not implemented by this destination");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void copyDestinationFiles(Glob path) throws RepoException {
|
||||||
|
throw new RepoException("Reading files is not implemented by this destination");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final DestinationReader NOOP_DESTINATION_READER = new DestinationReader() {
|
||||||
|
@Override
|
||||||
|
public String readFile(String path) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void copyDestinationFiles(Glob path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "read_file",
|
||||||
|
doc = "Read a file from the destination.",
|
||||||
|
parameters = {
|
||||||
|
@Param(name = "path", type = String.class, named = true, doc = "Path to the file."),
|
||||||
|
})
|
||||||
|
@Example(
|
||||||
|
title = "Read a file from the destination's baseline",
|
||||||
|
before = "This can be added to the transformations of your core.workflow:",
|
||||||
|
code =
|
||||||
|
"def _read_destination_file(ctx):\n"
|
||||||
|
+ " content = ctx.destination_reader().read_file(path = path/to/my_file.txt')\n"
|
||||||
|
+ " ctx.console.info(content)\n\n"
|
||||||
|
+ " transforms = [core.dynamic_transform(_read_destination_file)]\n",
|
||||||
|
after =
|
||||||
|
"Would print out the content of path/to/my_file.txt in the destination. The file does not"
|
||||||
|
+ " have to be covered by origin_files nor destination_files.")
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public abstract String readFile(String path) throws RepoException;
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "copy_destination_files",
|
||||||
|
doc = "Copy files from the destination into the workdir.",
|
||||||
|
parameters = {
|
||||||
|
@Param(name = "glob", type = Glob.class, named = true, doc = "Files to copy to the "
|
||||||
|
+ "workdir, potentially overwriting files checked out from the origin."),
|
||||||
|
})
|
||||||
|
@Example(
|
||||||
|
title = "Copy files from the destination's baseline",
|
||||||
|
before = "This can be added to the transformations of your core.workflow:",
|
||||||
|
code =
|
||||||
|
"def _copy_destination_file(ctx):\n"
|
||||||
|
+ " content = ctx.destination_reader().copy_destination_files(path = 'path/to/**')"
|
||||||
|
+ "\n\n"
|
||||||
|
+ " transforms = [core.dynamic_transform(_copy_destination_file)]\n",
|
||||||
|
after =
|
||||||
|
"Would copy all files in path/to/ from the destination baseline to the copybara workdir."
|
||||||
|
+ " The files do not have to be covered by origin_files nor destination_files, but "
|
||||||
|
+ "will cause errors if they are not covered by destination_files and not moved or "
|
||||||
|
+ "deleted.")
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public abstract void copyDestinationFiles(Glob glob) throws RepoException, ValidationException;
|
||||||
|
}
|
66
third_party/copybara/java/com/google/copybara/DestinationStatusVisitor.java
vendored
Normal file
66
third_party/copybara/java/com/google/copybara/DestinationStatusVisitor.java
vendored
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.copybara.ChangeVisitable.ChangesVisitor;
|
||||||
|
import com.google.copybara.ChangeVisitable.VisitResult;
|
||||||
|
import com.google.copybara.Destination.DestinationStatus;
|
||||||
|
import java.nio.file.PathMatcher;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A visitor that computes the {@link DestinationStatus} matching the actual files affected by
|
||||||
|
* the changes with the destination files glob.
|
||||||
|
*/
|
||||||
|
public class DestinationStatusVisitor implements ChangesVisitor {
|
||||||
|
|
||||||
|
private final PathMatcher pathMatcher;
|
||||||
|
private final String labelName;
|
||||||
|
|
||||||
|
private DestinationStatus destinationStatus = null;
|
||||||
|
|
||||||
|
public DestinationStatusVisitor(PathMatcher pathMatcher, String labelName) {
|
||||||
|
this.pathMatcher = pathMatcher;
|
||||||
|
this.labelName = labelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VisitResult visit(Change<? extends Revision> change) {
|
||||||
|
ImmutableSet<String> changeFiles = change.getChangeFiles();
|
||||||
|
if (changeFiles != null) {
|
||||||
|
if (change.getLabels().containsKey(labelName)) {
|
||||||
|
for (String file : changeFiles) {
|
||||||
|
if (pathMatcher.matches(Paths.get('/' + file))) {
|
||||||
|
String lastRev = Iterables.getLast(change.getLabels().get(labelName));
|
||||||
|
destinationStatus = new DestinationStatus(lastRev, ImmutableList.of());
|
||||||
|
return VisitResult.TERMINATE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return VisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public DestinationStatus getDestinationStatus() {
|
||||||
|
return destinationStatus;
|
||||||
|
}
|
||||||
|
}
|
118
third_party/copybara/java/com/google/copybara/Endpoint.java
vendored
Normal file
118
third_party/copybara/java/com/google/copybara/Endpoint.java
vendored
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
|
import com.google.copybara.DestinationEffect.DestinationRef;
|
||||||
|
import com.google.copybara.DestinationEffect.OriginRef;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.Param;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkMethod;
|
||||||
|
import com.google.devtools.build.lib.syntax.EvalUtils;
|
||||||
|
import com.google.devtools.build.lib.syntax.Printer;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An origin or destination API in a feedback migration.
|
||||||
|
*
|
||||||
|
* <p>Endpoints are symmetric, that is, they need to be able to act both as an origin and
|
||||||
|
* destination of a feedback migration, which means that they need to support both read and write
|
||||||
|
* operations on the API.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "endpoint",
|
||||||
|
doc = "An origin or destination API in a feedback migration.",
|
||||||
|
category = StarlarkDocumentationCategory.TOP_LEVEL_TYPE)
|
||||||
|
public interface Endpoint extends StarlarkValue {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be used for core.workflow origin/destinations that don't want to provide an api for giving
|
||||||
|
* feedback.
|
||||||
|
*/
|
||||||
|
Endpoint NOOP_ENDPOINT =
|
||||||
|
new Endpoint() {
|
||||||
|
@Override
|
||||||
|
public ImmutableSetMultimap<String, String> describe() {
|
||||||
|
throw new IllegalStateException("Instance shouldn't be used for core.feedback");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void repr(Printer printer) {
|
||||||
|
printer.append("noop_endpoint");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void repr(Printer printer) {
|
||||||
|
printer.append(toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a key-value ist of the options the endpoint was instantiated with. */
|
||||||
|
ImmutableSetMultimap<String, String> describe();
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "new_origin_ref",
|
||||||
|
doc = "Creates a new origin reference out of this endpoint.",
|
||||||
|
parameters = {
|
||||||
|
@Param(name = "ref", type = String.class, named = true, doc = "The reference."),
|
||||||
|
})
|
||||||
|
default OriginRef newOriginRef(String ref) {
|
||||||
|
return new OriginRef(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "new_destination_ref",
|
||||||
|
doc = "Creates a new destination reference out of this endpoint.",
|
||||||
|
parameters = {
|
||||||
|
@Param(name = "ref", type = String.class, named = true, doc = "The reference."),
|
||||||
|
@Param(
|
||||||
|
name = "type",
|
||||||
|
type = String.class,
|
||||||
|
named = true,
|
||||||
|
doc = "The type of this reference."),
|
||||||
|
@Param(
|
||||||
|
name = "url",
|
||||||
|
type = String.class,
|
||||||
|
named = true,
|
||||||
|
noneable = true,
|
||||||
|
doc = "The url associated with this reference, if any.",
|
||||||
|
defaultValue = "None"),
|
||||||
|
})
|
||||||
|
default DestinationRef newDestinationRef(String ref, String type, Object urlObj) {
|
||||||
|
String url = EvalUtils.isNullOrNone(urlObj) ? null : (String) urlObj;
|
||||||
|
return new DestinationRef(ref, type, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "url",
|
||||||
|
doc = "Return the URL of this endpoint.",
|
||||||
|
structField = true,
|
||||||
|
allowReturnNones = true)
|
||||||
|
default String getUrl() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance of this endpoint with the given console.
|
||||||
|
*/
|
||||||
|
default Endpoint withConsole(Console console) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
63
third_party/copybara/java/com/google/copybara/EndpointProvider.java
vendored
Normal file
63
third_party/copybara/java/com/google/copybara/EndpointProvider.java
vendored
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkMethod;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
|
||||||
|
/** Wrapper class to prevent arbitrary instantiation of endpoints in starlark. */
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "endpoint_provider",
|
||||||
|
doc = "An handle for an origin or destination API in a feedback migration.",
|
||||||
|
category = StarlarkDocumentationCategory.TOP_LEVEL_TYPE)
|
||||||
|
public class EndpointProvider<T extends Endpoint> implements StarlarkValue, Endpoint {
|
||||||
|
final T endpoint;
|
||||||
|
|
||||||
|
EndpointProvider(T endpoint) {
|
||||||
|
this.endpoint = endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getEndpoint() {
|
||||||
|
return endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableSetMultimap<String, String> describe() {
|
||||||
|
return endpoint.describe();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap an Endpoint
|
||||||
|
*/
|
||||||
|
public static <T extends Endpoint> EndpointProvider<T> wrap(T e) {
|
||||||
|
return new EndpointProvider<>(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "url",
|
||||||
|
doc = "Return the URL of this endpoint, if any.",
|
||||||
|
structField = true,
|
||||||
|
allowReturnNones = true)
|
||||||
|
public String getUrl() {
|
||||||
|
return endpoint.getUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
23
third_party/copybara/java/com/google/copybara/FeedbackOptions.java
vendored
Normal file
23
third_party/copybara/java/com/google/copybara/FeedbackOptions.java
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.beust.jcommander.Parameters;
|
||||||
|
|
||||||
|
/** Options for {@link com.google.copybara.feedback.Feedback} migrations. */
|
||||||
|
@Parameters(separators = "=")
|
||||||
|
public class FeedbackOptions implements Option {}
|
430
third_party/copybara/java/com/google/copybara/GeneralOptions.java
vendored
Normal file
430
third_party/copybara/java/com/google/copybara/GeneralOptions.java
vendored
Normal file
|
@ -0,0 +1,430 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.copybara.exception.ValidationException.checkCondition;
|
||||||
|
|
||||||
|
import com.beust.jcommander.Parameter;
|
||||||
|
import com.beust.jcommander.Parameters;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Ascii;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.base.Ticker;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
|
import com.google.common.flogger.StackSize;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.jcommander.DurationConverter;
|
||||||
|
import com.google.copybara.jcommander.MapConverter;
|
||||||
|
import com.google.copybara.monitor.ConsoleEventMonitor;
|
||||||
|
import com.google.copybara.monitor.EventMonitor;
|
||||||
|
import com.google.copybara.profiler.Profiler;
|
||||||
|
import com.google.copybara.profiler.Profiler.ProfilerTask;
|
||||||
|
import com.google.copybara.util.CommandRunner;
|
||||||
|
import com.google.copybara.util.DirFactory;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import com.google.copybara.util.console.StarlarkMode;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileSystem;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General options available for all the program classes.
|
||||||
|
*/
|
||||||
|
@Parameters(separators = "=")
|
||||||
|
public final class GeneralOptions implements Option {
|
||||||
|
|
||||||
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
public static final String NOANSI = "--noansi";
|
||||||
|
public static final String FORCE = "--force";
|
||||||
|
public static final String CONFIG_ROOT_FLAG = "--config-root";
|
||||||
|
public static final String OUTPUT_ROOT_FLAG = "--output-root";
|
||||||
|
public static final String OUTPUT_LIMIT_FLAG = "--output-limit";
|
||||||
|
public static final String DRY_RUN_FLAG = "--dry-run";
|
||||||
|
public static final String SQUASH_FLAG = "--squash";
|
||||||
|
static final Duration DEFAULT_CONSOLE_FILE_FLUSH_INTERVAL = Duration.ofSeconds(30);
|
||||||
|
|
||||||
|
private Map<String, String> environment;
|
||||||
|
private FileSystem fileSystem;
|
||||||
|
private Console console;
|
||||||
|
private EventMonitor eventMonitor;
|
||||||
|
private Path configRootPath;
|
||||||
|
private Path outputRootPath;
|
||||||
|
|
||||||
|
private Profiler profiler = new Profiler(Ticker.systemTicker());
|
||||||
|
|
||||||
|
public GeneralOptions(Map<String, String> environment, FileSystem fileSystem, Console console) {
|
||||||
|
this.environment = environment;
|
||||||
|
this.fileSystem = Preconditions.checkNotNull(fileSystem);
|
||||||
|
this.console = Preconditions.checkNotNull(console);
|
||||||
|
this.eventMonitor = new ConsoleEventMonitor(console, EventMonitor.EMPTY_MONITOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public GeneralOptions(Map<String, String> environment, FileSystem fileSystem, boolean verbose,
|
||||||
|
Console console, @Nullable Path configRoot, @Nullable Path outputRoot,
|
||||||
|
boolean noCleanup, boolean disableReversibleCheck, boolean force, int outputLimit) {
|
||||||
|
this.environment = ImmutableMap.copyOf(Preconditions.checkNotNull(environment));
|
||||||
|
this.console = Preconditions.checkNotNull(console);
|
||||||
|
this.eventMonitor = new ConsoleEventMonitor(console, EventMonitor.EMPTY_MONITOR);
|
||||||
|
this.fileSystem = Preconditions.checkNotNull(fileSystem);
|
||||||
|
this.verbose = verbose;
|
||||||
|
this.configRootPath = configRoot;
|
||||||
|
this.outputRootPath = outputRoot;
|
||||||
|
this.noCleanup = noCleanup;
|
||||||
|
this.disableReversibleCheck = disableReversibleCheck;
|
||||||
|
this.force = force;
|
||||||
|
this.outputLimit = outputLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeneralOptions withForce(boolean force) throws ValidationException {
|
||||||
|
return new GeneralOptions(environment, fileSystem, verbose, console, getConfigRoot(),
|
||||||
|
getOutputRoot(), noCleanup, disableReversibleCheck, force, outputLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeneralOptions withConsole(Console console) throws ValidationException {
|
||||||
|
return new GeneralOptions(environment, fileSystem, verbose, console, getConfigRoot(),
|
||||||
|
getOutputRoot(), noCleanup, disableReversibleCheck, force, outputLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getEnvironment() {
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVerbose() {
|
||||||
|
return verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Console console() {
|
||||||
|
return console;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileSystem getFileSystem() {
|
||||||
|
return fileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNoCleanup() {
|
||||||
|
return noCleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDisableReversibleCheck() {
|
||||||
|
return disableReversibleCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isForced() {
|
||||||
|
return force;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns current working directory
|
||||||
|
*/
|
||||||
|
public Path getCwd() {
|
||||||
|
return fileSystem.getPath(environment.get("PWD"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the root absolute path to use for config.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Path getConfigRoot() throws ValidationException {
|
||||||
|
if (configRootPath == null && this.configRoot != null) {
|
||||||
|
configRootPath = fileSystem.getPath(this.configRoot).toAbsolutePath();
|
||||||
|
checkCondition(Files.exists(configRootPath), "%s doesn't exist", configRoot);
|
||||||
|
checkCondition(Files.isDirectory(configRootPath), "%s isn't a directory", configRoot);
|
||||||
|
}
|
||||||
|
return configRootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the output root directory, or null if not set.
|
||||||
|
*
|
||||||
|
* <p>This method is exposed mainly for tests and it's probably not what you're looking for. Try
|
||||||
|
* {@link #getDirFactory()} instead.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
@Nullable
|
||||||
|
public Path getOutputRoot() {
|
||||||
|
if (outputRootPath == null && this.outputRoot != null) {
|
||||||
|
outputRootPath = fileSystem.getPath(this.outputRoot);
|
||||||
|
}
|
||||||
|
return outputRootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the output limit.
|
||||||
|
*
|
||||||
|
* <p>Each subcommand can use this value differently.
|
||||||
|
*/
|
||||||
|
public int getOutputLimit() {
|
||||||
|
return outputLimit > 0 ? outputLimit : Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Profiler profiler() {
|
||||||
|
return profiler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventMonitor eventMonitor() {
|
||||||
|
return eventMonitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a repository task with profiling
|
||||||
|
*/
|
||||||
|
public <T> T repoTask(String description, Callable<T> callable)
|
||||||
|
throws RepoException, ValidationException {
|
||||||
|
try (ProfilerTask ignored = profiler().start(description)) {
|
||||||
|
return callable.call();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Throwables.propagateIfPossible(e, RepoException.class, ValidationException.class);
|
||||||
|
throw new RuntimeException("Unexpected exception", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a repository task that can throw IOException with profiling
|
||||||
|
*/
|
||||||
|
public <T> T ioRepoTask(String description, Callable<T> callable)
|
||||||
|
throws RepoException, ValidationException, IOException{
|
||||||
|
try (ProfilerTask ignored = profiler().start(description)) {
|
||||||
|
return callable.call();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Throwables.propagateIfPossible(e, RepoException.class, ValidationException.class);
|
||||||
|
Throwables.propagateIfPossible(e, IOException.class);
|
||||||
|
throw new RuntimeException("Unexpected exception", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link DirFactory} capable of creating directories in a self contained location in
|
||||||
|
* the filesystem.
|
||||||
|
*
|
||||||
|
* <p>By default, the directories are created under {@code $HOME/copybara}, but it can be
|
||||||
|
* overridden with the flag --output-root.
|
||||||
|
*/
|
||||||
|
public DirFactory getDirFactory() {
|
||||||
|
if (getOutputRoot() != null) {
|
||||||
|
return new DirFactory(getOutputRoot());
|
||||||
|
} else {
|
||||||
|
String home = checkNotNull(environment.get("HOME"), "$HOME environment var is not set");
|
||||||
|
return new DirFactory(fileSystem.getPath(home).resolve("copybara"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setEnvironmentForTest(Map<String, String> environment) {
|
||||||
|
this.environment = environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setOutputRootPathForTest(Path outputRootPath) {
|
||||||
|
this.outputRootPath = outputRootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setConsoleForTest(Console console) {
|
||||||
|
this.console = console;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setForceForTest(boolean force) {
|
||||||
|
this.force = force;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setFileSystemForTest(FileSystem fileSystem) {
|
||||||
|
this.fileSystem = fileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeneralOptions withProfiler(Profiler profiler) {
|
||||||
|
this.profiler = Preconditions.checkNotNull(profiler);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeneralOptions withEventMonitor(EventMonitor eventMonitor) {
|
||||||
|
this.eventMonitor = new ConsoleEventMonitor(console(), eventMonitor);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = {"-v", "--verbose"},
|
||||||
|
description = "Verbose output.")
|
||||||
|
boolean verbose;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = {"--fetch-timeout"},
|
||||||
|
description = "Fetch timeout",
|
||||||
|
converter = DurationConverter.class)
|
||||||
|
public Duration fetchTimeout = CommandRunner.DEFAULT_TIMEOUT;
|
||||||
|
|
||||||
|
// We don't use JCommander for parsing this flag but we do it manually since
|
||||||
|
// the parsing could fail and we need to report errors using one console
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Parameter(names = NOANSI, description = "Don't use ANSI output for messages")
|
||||||
|
boolean noansi = false;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = FORCE,
|
||||||
|
description =
|
||||||
|
"Force the migration even if Copybara cannot find in the destination a change that is an"
|
||||||
|
+ " ancestor of the one(s) being migrated. This should be used with care, as it"
|
||||||
|
+ " could lose changes when migrating a previous/conflicting change.")
|
||||||
|
boolean force = false;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = CONFIG_ROOT_FLAG,
|
||||||
|
description =
|
||||||
|
"Configuration root path to be used for resolving absolute config labels"
|
||||||
|
+ " like '//foo/bar'")
|
||||||
|
String configRoot;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = "--disable-reversible-check",
|
||||||
|
description =
|
||||||
|
"If set, all workflows will be executed without reversible_check, overriding"
|
||||||
|
+ " the workflow config and the normal behavior for CHANGE_REQUEST mode.")
|
||||||
|
boolean disableReversibleCheck = false;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = OUTPUT_ROOT_FLAG,
|
||||||
|
description =
|
||||||
|
"The root directory where to generate output files. If not set, ~/copybara/out is used "
|
||||||
|
+ "by default. Use with care, Copybara might remove files inside this root if "
|
||||||
|
+ "necessary.")
|
||||||
|
String outputRoot = null;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = OUTPUT_LIMIT_FLAG,
|
||||||
|
description =
|
||||||
|
"Limit the output in the console to a number of records. Each subcommand might use this "
|
||||||
|
+ "flag differently. Defaults to 0, which shows all the output.")
|
||||||
|
int outputLimit = 0;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = "--nocleanup",
|
||||||
|
description =
|
||||||
|
"Cleanup the output directories. This includes the workdir, scratch clones of Git"
|
||||||
|
+ " repos, etc. By default is set to false and directories will be cleaned prior to"
|
||||||
|
+ " the execution. If set to true, the previous run output will not be cleaned up."
|
||||||
|
+ " Keep in mind that running in this mode will lead to an ever increasing disk"
|
||||||
|
+ " usage.")
|
||||||
|
boolean noCleanup = false;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = "--nologging",
|
||||||
|
description =
|
||||||
|
"Disable logging of this binary. Note that commands executed by Copybara "
|
||||||
|
+ "might still log to their own file.", hidden = true)
|
||||||
|
boolean noLogging = false;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = "--temporary-features",
|
||||||
|
description = "Change guarded features. If set it means that it will return true.",
|
||||||
|
converter = MapConverter.class,
|
||||||
|
hidden = true)
|
||||||
|
private ImmutableMap<String, String> temporaryFeatures = ImmutableMap.of();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary features is mean to be used by Copybara team for guarding new codepaths. Should
|
||||||
|
* never be used for user facing flags or longer term experiments. Any caller of this function
|
||||||
|
* should have a todo saying when to remove the call.
|
||||||
|
*
|
||||||
|
* <p>If the flag doesn't have a value it will use defaultVal. If the flag is incorrect (different
|
||||||
|
* from true/false) it will use defaultVal (And log at severe).
|
||||||
|
*/
|
||||||
|
public boolean isTemporaryFeature(String name, boolean defaultVal) {
|
||||||
|
Preconditions.checkNotNull(name);
|
||||||
|
String v = temporaryFeatures.get(name);
|
||||||
|
if (v == null) {
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
if (Ascii.equalsIgnoreCase(v, "true")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Ascii.equalsIgnoreCase(v, "false")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logger.atSevere()
|
||||||
|
.withStackTrace(StackSize.SMALL)
|
||||||
|
.log("Invalid boolean value for '%s' in '%s'. Needs to be true or false. Using default: %s",
|
||||||
|
name, temporaryFeatures, defaultVal);
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final String CONSOLE_FILE_PATH = "--console-file-path";
|
||||||
|
|
||||||
|
// This flag is read before we parse the arguments, because of the console lifecycle
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Parameter(
|
||||||
|
names = CONSOLE_FILE_PATH,
|
||||||
|
description = "If set, write the console output also to the given file path.")
|
||||||
|
String consoleFilePath;
|
||||||
|
|
||||||
|
// This flag is read before we parse the arguments, because of the console lifecycle
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Deprecated
|
||||||
|
@Parameter(
|
||||||
|
names = "--console-file-flush-rate",
|
||||||
|
description =
|
||||||
|
"How often in number of lines to flush the console to the output file. "
|
||||||
|
+ "If set to 0, console will be flushed only at the end.", hidden = true)
|
||||||
|
int consoleFileFlushRateDeprecatedDontUse = -1;
|
||||||
|
|
||||||
|
static final String CONSOLE_FILE_FLUSH_INTERVAL = "--console-file-flush-interval";
|
||||||
|
|
||||||
|
// This flag is read before we parse the arguments, because of the console lifecycle
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Parameter(
|
||||||
|
names = CONSOLE_FILE_FLUSH_INTERVAL,
|
||||||
|
converter = DurationConverter.class,
|
||||||
|
description =
|
||||||
|
"How often Copybara should flush the console to the output file. (10s, 1m, etc.)"
|
||||||
|
+ "If set to 0s, console will be flushed only at the end.")
|
||||||
|
Duration consoleFileFlushInterval = DEFAULT_CONSOLE_FILE_FLUSH_INTERVAL;
|
||||||
|
|
||||||
|
@Parameter(names = DRY_RUN_FLAG,
|
||||||
|
description = "Run the migration in dry-run mode. Some destination implementations might"
|
||||||
|
+ " have some side effects (like creating a code review), but never submit to a main"
|
||||||
|
+ " branch.")
|
||||||
|
public boolean dryRunMode = false;
|
||||||
|
|
||||||
|
@Parameter(names = SQUASH_FLAG, description = "Override workflow's mode with 'SQUASH'. This is "
|
||||||
|
+ "useful mainly for workflows that use 'ITERATIVE' mode, when we want to run a single "
|
||||||
|
+ "export with 'SQUASH', maybe to fix an issue. Always use " + DRY_RUN_FLAG + " before, to "
|
||||||
|
+ "test your changes locally.")
|
||||||
|
public boolean squash = false;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = "--validate-starlark",
|
||||||
|
description =
|
||||||
|
"Starlark should be validated prior to execution, but this might break legacy configs."
|
||||||
|
+ " Options are LOOSE, STRICT")
|
||||||
|
public String starlarkMode = StarlarkMode.LOOSE.name();
|
||||||
|
|
||||||
|
public StarlarkMode getStarlarkMode() {
|
||||||
|
return StarlarkMode.valueOf(starlarkMode);
|
||||||
|
}
|
||||||
|
}
|
112
third_party/copybara/java/com/google/copybara/Info.java
vendored
Normal file
112
third_party/copybara/java/com/google/copybara/Info.java
vendored
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.auto.value.AutoValue;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMultimap;
|
||||||
|
import java.util.Optional;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the information about a Migration.
|
||||||
|
*
|
||||||
|
* <p>A migration can have one or more {@link MigrationReference}s.
|
||||||
|
*/
|
||||||
|
@AutoValue
|
||||||
|
public abstract class Info<O extends Revision> {
|
||||||
|
|
||||||
|
public static final Info<? extends Revision> EMPTY = create(ImmutableMultimap.of(),
|
||||||
|
ImmutableMultimap.of(), ImmutableList.of());
|
||||||
|
|
||||||
|
public static <O extends Revision> Info<O> create(
|
||||||
|
ImmutableMultimap<String, String> originDescription,
|
||||||
|
ImmutableMultimap<String, String> destinationDescription,
|
||||||
|
Iterable<MigrationReference<O>> migrationReferences) {
|
||||||
|
return new AutoValue_Info<>(originDescription, destinationDescription,
|
||||||
|
ImmutableList.copyOf(migrationReferences));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns origin description of the migration.
|
||||||
|
*/
|
||||||
|
public abstract ImmutableMultimap<String, String> originDescription();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns destination description of the migration.
|
||||||
|
*/
|
||||||
|
public abstract ImmutableMultimap<String, String> destinationDescription();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns information about a migration for one reference (like 'master')
|
||||||
|
*
|
||||||
|
* <p>Public so that it can be used programmatically.
|
||||||
|
*/
|
||||||
|
public abstract Iterable<MigrationReference<O>> migrationReferences();
|
||||||
|
|
||||||
|
@AutoValue
|
||||||
|
public abstract static class MigrationReference<O extends Revision> {
|
||||||
|
|
||||||
|
public static <O extends Revision> MigrationReference<O> create(
|
||||||
|
String label,
|
||||||
|
@Nullable O lastMigrated,
|
||||||
|
Iterable<Change<O>> availableToMigrate) {
|
||||||
|
return new AutoValue_Info_MigrationReference<>(
|
||||||
|
label, lastMigrated, ImmutableList.copyOf(availableToMigrate));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of this {@link MigrationReference}.
|
||||||
|
*
|
||||||
|
* <p>For a {@code Workflow} migration, the label is the string "workflow_" followed by the
|
||||||
|
* workflow name.
|
||||||
|
*
|
||||||
|
* <p>For a {@code Mirror} migration, the name is the string "mirror_" followed by the refspec.
|
||||||
|
*/
|
||||||
|
abstract String getLabel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last migrated {@link Revision} from the origin, or {@code null} if no change was
|
||||||
|
* ever migrated.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public abstract O getLastMigrated();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last available {@link Revision} to migrate from the origin, or {@code null} if
|
||||||
|
* there are no changes available to migrate.
|
||||||
|
*
|
||||||
|
* <p>There might be more available changes to migrate, but this is the revision of the most
|
||||||
|
* recent change available at this moment.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public O getLastAvailableToMigrate() {
|
||||||
|
Optional<O> lastAvailable =
|
||||||
|
getAvailableToMigrate()
|
||||||
|
.stream()
|
||||||
|
.map(Change::getRevision)
|
||||||
|
.reduce((first, second) -> second);
|
||||||
|
return lastAvailable.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of the next available {@link Change}s to migrate from the origin.
|
||||||
|
*/
|
||||||
|
public abstract ImmutableList<Change<O>> getAvailableToMigrate();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
166
third_party/copybara/java/com/google/copybara/InfoCmd.java
vendored
Normal file
166
third_party/copybara/java/com/google/copybara/InfoCmd.java
vendored
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.beust.jcommander.Parameters;
|
||||||
|
import com.google.common.base.Ascii;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.copybara.Info.MigrationReference;
|
||||||
|
import com.google.copybara.config.Config;
|
||||||
|
import com.google.copybara.config.Migration;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.monitor.EventMonitor.InfoFinishedEvent;
|
||||||
|
import com.google.copybara.util.ExitCode;
|
||||||
|
import com.google.copybara.util.TablePrinter;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the last migrated revision in the origin and destination.
|
||||||
|
*/
|
||||||
|
@Parameters(separators = "=",
|
||||||
|
commandDescription = "Reads the last migrated revision in the origin and destination.")
|
||||||
|
public class InfoCmd implements CopybaraCmd {
|
||||||
|
|
||||||
|
private static final int REVISION_MAX_LENGTH = 15;
|
||||||
|
private static final int DESCRIPTION_MAX_LENGTH = 80;
|
||||||
|
private static final int AUTHOR_MAX_LENGTH = 40;
|
||||||
|
private static final DateTimeFormatter DATE_FORMATTER =
|
||||||
|
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
private final ConfigLoaderProvider configLoaderProvider;
|
||||||
|
private final ContextProvider contextProvider;
|
||||||
|
|
||||||
|
public InfoCmd(ConfigLoaderProvider configLoaderProvider, ContextProvider contextProvider) {
|
||||||
|
this.configLoaderProvider = Preconditions.checkNotNull(configLoaderProvider);
|
||||||
|
this.contextProvider = Preconditions.checkNotNull(contextProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExitCode run(CommandEnv commandEnv)
|
||||||
|
throws ValidationException, IOException, RepoException {
|
||||||
|
ConfigFileArgs configFileArgs = commandEnv.parseConfigFileArgs(this, /*useSourceRef*/false);
|
||||||
|
Console console = commandEnv.getOptions().get(GeneralOptions.class).console();
|
||||||
|
Config config = configLoaderProvider
|
||||||
|
.newLoader(configFileArgs.getConfigPath(), configFileArgs.getSourceRef())
|
||||||
|
.load(console);
|
||||||
|
if (configFileArgs.hasWorkflowName()) {
|
||||||
|
ImmutableMap<String, String> context =
|
||||||
|
contextProvider.getContext(config, configFileArgs, configLoaderProvider, console);
|
||||||
|
info(commandEnv.getOptions(), config, configFileArgs.getWorkflowName(), context);
|
||||||
|
} else {
|
||||||
|
showAllMigrations(commandEnv, config);
|
||||||
|
}
|
||||||
|
return ExitCode.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showAllMigrations(CommandEnv commandEnv, Config config) {
|
||||||
|
TablePrinter table = new TablePrinter("Name", "Origin", "Destination", "Mode", "Description");
|
||||||
|
for (Migration m :
|
||||||
|
config.getMigrations().values().stream()
|
||||||
|
.sorted(Comparator.comparing(Migration::getName))
|
||||||
|
.collect(ImmutableList.toImmutableList())) {
|
||||||
|
table.addRow(
|
||||||
|
m.getName(),
|
||||||
|
prettyOriginDestination(m.getOriginDescription()),
|
||||||
|
prettyOriginDestination(m.getDestinationDescription()),
|
||||||
|
m.getModeString(),
|
||||||
|
Strings.nullToEmpty(m.getDescription()));
|
||||||
|
}
|
||||||
|
Console console = commandEnv.getOptions().get(GeneralOptions.class).console();
|
||||||
|
for (String line : table.build()) {
|
||||||
|
console.info(line);
|
||||||
|
}
|
||||||
|
console.info("To get information about the state of any migration run:\n\n"
|
||||||
|
+ " copybara info " + config.getLocation() + " [workflow_name]"
|
||||||
|
+ "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String prettyOriginDestination(ImmutableSetMultimap<String, String> desc) {
|
||||||
|
return Iterables.getOnlyElement(desc.get("type"))
|
||||||
|
+ (desc.containsKey("url") ? " (" + Iterables.getOnlyElement(desc.get("url")) + ")" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Retrieves the {@link Info} of the {@code migrationName} and prints it to the console. */
|
||||||
|
private static void info(
|
||||||
|
Options options, Config config, String migrationName, ImmutableMap<String, String> context)
|
||||||
|
throws ValidationException, RepoException {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Info<? extends Revision> info = getInfo(migrationName, config);
|
||||||
|
Console console = options.get(GeneralOptions.class).console();
|
||||||
|
int outputSize = 0;
|
||||||
|
for (MigrationReference<? extends Revision> migrationRef : info.migrationReferences()) {
|
||||||
|
console.info(String.format(
|
||||||
|
"'%s': last_migrated %s - last_available %s.",
|
||||||
|
migrationRef.getLabel(),
|
||||||
|
migrationRef.getLastMigrated() != null
|
||||||
|
? migrationRef.getLastMigrated().asString() : "None",
|
||||||
|
migrationRef.getLastAvailableToMigrate() != null
|
||||||
|
? migrationRef.getLastAvailableToMigrate().asString() : "None"));
|
||||||
|
|
||||||
|
ImmutableList<? extends Change<? extends Revision>> availableToMigrate =
|
||||||
|
migrationRef.getAvailableToMigrate();
|
||||||
|
int outputLimit = options.get(GeneralOptions.class).getOutputLimit();
|
||||||
|
if (!availableToMigrate.isEmpty()) {
|
||||||
|
console.infoFmt(
|
||||||
|
"Available changes %s:",
|
||||||
|
availableToMigrate.size() <= outputLimit
|
||||||
|
? String.format("(%d)", availableToMigrate.size())
|
||||||
|
: String.format(
|
||||||
|
"(showing only first %d out of %d)", outputLimit, availableToMigrate.size()));
|
||||||
|
TablePrinter table = new TablePrinter("Date", "Revision", "Description", "Author");
|
||||||
|
for (Change<? extends Revision> change :
|
||||||
|
Iterables.limit(availableToMigrate, outputLimit)) {
|
||||||
|
outputSize++;
|
||||||
|
table.addRow(
|
||||||
|
change.getDateTime().format(DATE_FORMATTER),
|
||||||
|
Ascii.truncate(change.getRevision().asString(), REVISION_MAX_LENGTH, ""),
|
||||||
|
Ascii.truncate(change.firstLineMessage(), DESCRIPTION_MAX_LENGTH, "..."),
|
||||||
|
Ascii.truncate(change.getAuthor().toString(), AUTHOR_MAX_LENGTH, "..."));
|
||||||
|
}
|
||||||
|
for (String line : table.build()) {
|
||||||
|
console.info(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (outputSize > 100) {
|
||||||
|
console.infoFmt(
|
||||||
|
"Use %s to limit the output of the command.", GeneralOptions.OUTPUT_LIMIT_FLAG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options.get(GeneralOptions.class).eventMonitor().onInfoFinished(
|
||||||
|
new InfoFinishedEvent(info, context));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the {@link Info} of the {@code migrationName}. */
|
||||||
|
private static Info<? extends Revision> getInfo(String migrationName, Config config)
|
||||||
|
throws ValidationException, RepoException {
|
||||||
|
return config.getMigration(migrationName).getInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "info";
|
||||||
|
}
|
||||||
|
}
|
102
third_party/copybara/java/com/google/copybara/LabelFinder.java
vendored
Normal file
102
third_party/copybara/java/com/google/copybara/LabelFinder.java
vendored
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
|
import com.google.re2j.Matcher;
|
||||||
|
import com.google.re2j.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple line finder/parser for labels like:
|
||||||
|
* <ul>
|
||||||
|
* <li>foo = bar</li>
|
||||||
|
* <li>baz : foo</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>In general this class should only be used in {@code Origin}s to create a labels map.
|
||||||
|
* During transformations/destination, it can be used to check if a line is a line but
|
||||||
|
* never to find labels. Use {@link TransformWork#getLabel(String)} instead, since it looks
|
||||||
|
* in more places for labels.
|
||||||
|
*
|
||||||
|
* TODO(malcon): Rename to MaybeLabel
|
||||||
|
*/
|
||||||
|
public class LabelFinder {
|
||||||
|
|
||||||
|
private static final String VALID_LABEL_EXPR = "([\\w-]+)";
|
||||||
|
|
||||||
|
public static final Pattern VALID_LABEL = Pattern.compile(VALID_LABEL_EXPR);
|
||||||
|
|
||||||
|
private static final Pattern URL = Pattern.compile(VALID_LABEL + "://.*");
|
||||||
|
|
||||||
|
private static final Pattern LABEL_PATTERN = Pattern.compile(
|
||||||
|
"^" + VALID_LABEL_EXPR + "( *[:=] ?)(.*)");
|
||||||
|
private final Matcher matcher;
|
||||||
|
private final String line;
|
||||||
|
|
||||||
|
public LabelFinder(String line) {
|
||||||
|
matcher = LABEL_PATTERN.matcher(line);
|
||||||
|
this.line = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLabel() {
|
||||||
|
// It is a line if it looks like a line but it doesn't look like a url (foo://bar)
|
||||||
|
return matcher.matches() && !URL.matcher(line).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLabel(String labelName) {
|
||||||
|
return isLabel() && getName().equals(labelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the label.
|
||||||
|
*
|
||||||
|
* <p>Use isLabel() method before calling this method.
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
checkIsLabel();
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the separator of the label.
|
||||||
|
*
|
||||||
|
* <p>Use isLabel() method before calling this method.
|
||||||
|
*/
|
||||||
|
public String getSeparator() {
|
||||||
|
checkIsLabel();
|
||||||
|
return matcher.group(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of the label.
|
||||||
|
*
|
||||||
|
* <p>Use isLabel() method before calling this method.
|
||||||
|
*/
|
||||||
|
public String getValue() {
|
||||||
|
checkIsLabel();
|
||||||
|
return matcher.group(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkIsLabel() {
|
||||||
|
checkState(isLabel(), "Not a label: '" + line + "'. Please call isLabel() first");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLine() {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
56
third_party/copybara/java/com/google/copybara/LazyResourceLoader.java
vendored
Normal file
56
third_party/copybara/java/com/google/copybara/LazyResourceLoader.java
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a resource (repository, API client...) lazily to avoid side effects.
|
||||||
|
*/
|
||||||
|
public interface LazyResourceLoader<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the resource.
|
||||||
|
*/
|
||||||
|
T load(@Nullable Console console) throws RepoException, ValidationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@link LazyResourceLoader} object that defers the loading of the resource
|
||||||
|
* until {@link #load(Console)} is called and after that always returns the same instance.
|
||||||
|
*/
|
||||||
|
static <T> LazyResourceLoader<T> memoized(LazyResourceLoader<T> delegate) {
|
||||||
|
|
||||||
|
return new LazyResourceLoader<T>() {
|
||||||
|
T resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see LazyResourceLoader#load(Console)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T load(Console console) throws RepoException, ValidationException {
|
||||||
|
if (resource == null) {
|
||||||
|
resource = Preconditions.checkNotNull(delegate.load(console));
|
||||||
|
}
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
88
third_party/copybara/java/com/google/copybara/LocalParallelizer.java
vendored
Normal file
88
third_party/copybara/java/com/google/copybara/LocalParallelizer.java
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that allows to run a list of things in parallel batches.
|
||||||
|
*/
|
||||||
|
public class LocalParallelizer {
|
||||||
|
|
||||||
|
private final int threads;
|
||||||
|
private final int minSize;
|
||||||
|
private final ListeningExecutorService executor;
|
||||||
|
|
||||||
|
public LocalParallelizer(int threads, int minSize) {
|
||||||
|
this.threads = threads;
|
||||||
|
this.minSize = minSize;
|
||||||
|
Preconditions.checkState(threads >= 1, "Threads need to be positive");
|
||||||
|
Preconditions.checkState(threads < 1000, "Too many threads (max: 1000)");
|
||||||
|
executor = threads == 1
|
||||||
|
? null
|
||||||
|
: MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(threads));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a list of things in batches, calling {@code func} for each batch.
|
||||||
|
*/
|
||||||
|
public <K, V> List<V> run(Iterable<K> list, TransformFunc<K, V> func)
|
||||||
|
throws IOException, ValidationException {
|
||||||
|
if (threads == 1 || Iterables.size(list) < minSize) {
|
||||||
|
return ImmutableList.of(func.run(list));
|
||||||
|
}
|
||||||
|
List<ListenableFuture<V>> results = new ArrayList<>(threads);
|
||||||
|
List<K> newList = Lists.newArrayList(list);
|
||||||
|
for (List<K> batch : Lists.partition(newList, Math.max(1, newList.size() / threads))) {
|
||||||
|
results.add(executor.submit(() -> func.run(batch)));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Futures.allAsList(results).get();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
//TODO We cannot do much here. We might expose InterruptedException all the way up to Main...
|
||||||
|
throw new RuntimeException("Interrupted", e);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
Throwables.propagateIfPossible(e.getCause(), IOException.class, ValidationException.class);
|
||||||
|
throw new RuntimeException("Unhandled error", e.getCause());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Transforms a collection of K elements into T. */
|
||||||
|
public interface TransformFunc<K, T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute oen batch. The number of elements is undefined.
|
||||||
|
*/
|
||||||
|
T run(Iterable<K> elements) throws IOException, ValidationException;
|
||||||
|
}
|
||||||
|
}
|
588
third_party/copybara/java/com/google/copybara/Main.java
vendored
Normal file
588
third_party/copybara/java/com/google/copybara/Main.java
vendored
Normal file
|
@ -0,0 +1,588 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import static com.google.copybara.MainArguments.COPYBARA_SKYLARK_CONFIG_FILENAME;
|
||||||
|
import static com.google.copybara.exception.ValidationException.checkCondition;
|
||||||
|
|
||||||
|
import com.beust.jcommander.JCommander;
|
||||||
|
import com.beust.jcommander.ParameterException;
|
||||||
|
import com.beust.jcommander.Parameters;
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.StandardSystemProperty;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
|
import com.google.copybara.MainArguments.CommandWithArgs;
|
||||||
|
import com.google.copybara.config.ConfigValidator;
|
||||||
|
import com.google.copybara.config.Migration;
|
||||||
|
import com.google.copybara.config.PathBasedConfigFile;
|
||||||
|
import com.google.copybara.exception.CommandLineException;
|
||||||
|
import com.google.copybara.exception.EmptyChangeException;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.jcommander.DurationConverter;
|
||||||
|
import com.google.copybara.profiler.ConsoleProfilerListener;
|
||||||
|
import com.google.copybara.profiler.Listener;
|
||||||
|
import com.google.copybara.profiler.LogProfilerListener;
|
||||||
|
import com.google.copybara.profiler.Profiler;
|
||||||
|
import com.google.copybara.util.ExitCode;
|
||||||
|
import com.google.copybara.util.console.AnsiConsole;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import com.google.copybara.util.console.FileConsole;
|
||||||
|
import com.google.copybara.util.console.LogConsole;
|
||||||
|
import com.google.devtools.build.lib.syntax.EvalException;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.FileSystem;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.LogManager;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main class that invokes Copybara from command-line.
|
||||||
|
*
|
||||||
|
* <p>This class should only know about how to validate and parse command-line arguments in order to
|
||||||
|
* invoke Copybara.
|
||||||
|
*/
|
||||||
|
public class Main {
|
||||||
|
|
||||||
|
private static final String COPYBARA_NAMESPACE = "com.google.copybara";
|
||||||
|
|
||||||
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
/**
|
||||||
|
* Represents the environment, typically {@code System.getEnv()}. Injected to make easier tests.
|
||||||
|
*
|
||||||
|
* <p>Should not be mutated.
|
||||||
|
*/
|
||||||
|
protected final ImmutableMap<String, String> environment;
|
||||||
|
protected Profiler profiler;
|
||||||
|
protected JCommander jCommander;
|
||||||
|
|
||||||
|
private Console console;
|
||||||
|
|
||||||
|
public Main() {
|
||||||
|
this(System.getenv());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Main(Map<String, String> environment) {
|
||||||
|
this.environment = Preconditions.checkNotNull(ImmutableMap.copyOf(environment));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.exit(new Main(System.getenv()).run(args).getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final ExitCode run(String[] args) {
|
||||||
|
// We need a console before parsing the args because it could fail with wrong
|
||||||
|
// arguments and we need to show the error.
|
||||||
|
this.console = getConsole(args);
|
||||||
|
// Configure logs location correctly before anything else. We want to write to the
|
||||||
|
// correct location in case of any error.
|
||||||
|
FileSystem fs = FileSystems.getDefault();
|
||||||
|
try {
|
||||||
|
configureLog(fs, args);
|
||||||
|
} catch (IOException e) {
|
||||||
|
handleUnexpectedError(console, e.getMessage(), args, e);
|
||||||
|
return ExitCode.ENVIRONMENT_ERROR;
|
||||||
|
}
|
||||||
|
// This is useful when debugging user issues
|
||||||
|
logger.atInfo().log("Running: %s", Joiner.on(' ').join(args));
|
||||||
|
|
||||||
|
console.startupMessage(getVersion());
|
||||||
|
|
||||||
|
CommandResult result = runInternal(args, console, fs);
|
||||||
|
try {
|
||||||
|
shutdown(result);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
handleUnexpectedError(console, "Execution was interrupted.", args, e);
|
||||||
|
}
|
||||||
|
return result.exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper to find out about verbose output before JCommander has been initialized .*/
|
||||||
|
protected static boolean isVerbose(String[] args) {
|
||||||
|
return Arrays.stream(args).anyMatch(s -> s.equals("-v") || s.equals("--verbose"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper to find out if logging is enabled before JCommander has been initialized . */
|
||||||
|
protected static boolean isEnableLogging(String[] args) {
|
||||||
|
return !Arrays.asList(args).contains("--nologging");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a flag value before JCommander is initialized. Returns {@code Optional.empty()} if the
|
||||||
|
* flag is not present.
|
||||||
|
*/
|
||||||
|
protected static Optional<String> findFlagValue(String[] args, String flagName) {
|
||||||
|
for (int index = 0; index < args.length - 1; index++) {
|
||||||
|
if (args[index].equals(flagName)) {
|
||||||
|
if (!args[index + 1].startsWith("-")) {
|
||||||
|
return Optional.of(args[index + 1]);
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A wrapper of the exit code and the command executed */
|
||||||
|
protected static class CommandResult {
|
||||||
|
|
||||||
|
private final ExitCode exitCode;
|
||||||
|
@Nullable private final CommandEnv commandEnv;
|
||||||
|
@Nullable private final CopybaraCmd command;
|
||||||
|
|
||||||
|
CommandResult(
|
||||||
|
ExitCode exitCode, @Nullable CopybaraCmd command, @Nullable CommandEnv commandEnv) {
|
||||||
|
this.exitCode = Preconditions.checkNotNull(exitCode);
|
||||||
|
this.command = command;
|
||||||
|
this.commandEnv = commandEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExitCode getExitCode() {
|
||||||
|
return exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command environment passed to the command. Can be null for executions that failed before
|
||||||
|
* executing the command, like bad options.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public CommandEnv getCommandEnv() {
|
||||||
|
return commandEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command that was executed. Can be null for executions that failed before executing the
|
||||||
|
* command, like bad options.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public CopybaraCmd getCommand() {
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Runs the command and returns the {@link ExitCode}.
|
||||||
|
*
|
||||||
|
* <p>This method is also responsible for the exception handling/logging.
|
||||||
|
*/
|
||||||
|
private CommandResult runInternal(String[] args, Console console, FileSystem fs) {
|
||||||
|
CommandEnv commandEnv = null;
|
||||||
|
CopybaraCmd subcommand = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
ModuleSet moduleSet = newModuleSet(environment, fs, console);
|
||||||
|
|
||||||
|
final MainArguments mainArgs = new MainArguments();
|
||||||
|
Options options = moduleSet.getOptions();
|
||||||
|
jCommander = new JCommander(ImmutableList.builder()
|
||||||
|
.addAll(options.getAll())
|
||||||
|
.add(mainArgs)
|
||||||
|
.build());
|
||||||
|
jCommander.setProgramName("copybara");
|
||||||
|
|
||||||
|
String version = getVersion();
|
||||||
|
logger.atInfo().log("Copybara version: %s", version);
|
||||||
|
jCommander.parse(args);
|
||||||
|
|
||||||
|
|
||||||
|
ConfigLoaderProvider configLoaderProvider = newConfigLoaderProvider(moduleSet);
|
||||||
|
|
||||||
|
ImmutableMap<String, CopybaraCmd> commands =
|
||||||
|
Maps.uniqueIndex(getCommands(moduleSet, configLoaderProvider, jCommander),
|
||||||
|
CopybaraCmd::name);
|
||||||
|
// Tell jcommander about the commands; we don't actually use the feature, this is solely for
|
||||||
|
// generating the usage info.
|
||||||
|
for (Map.Entry<String, CopybaraCmd> cmd : commands.entrySet()) {
|
||||||
|
jCommander.addCommand(cmd.getKey(), cmd.getValue());
|
||||||
|
}
|
||||||
|
CommandWithArgs cmdToRun = mainArgs.parseCommand(commands, commands.get("migrate"));
|
||||||
|
subcommand = cmdToRun.getSubcommand();
|
||||||
|
|
||||||
|
initEnvironment(options, cmdToRun.getSubcommand(), ImmutableList.copyOf(args));
|
||||||
|
|
||||||
|
GeneralOptions generalOptions = options.get(GeneralOptions.class);
|
||||||
|
Path baseWorkdir = mainArgs.getBaseWorkdir(generalOptions, generalOptions.getFileSystem());
|
||||||
|
|
||||||
|
commandEnv = new CommandEnv(baseWorkdir, options, cmdToRun.getArgs());
|
||||||
|
generalOptions.console().progressFmt("Running %s", subcommand.name());
|
||||||
|
|
||||||
|
// TODO(malcon): Remove this after 2019-09-15, once tested that temp features work.
|
||||||
|
logger.atInfo().log("Temporary features test: %s",
|
||||||
|
options.get(GeneralOptions.class).isTemporaryFeature("TEST_TEMP_FEATURES", true));
|
||||||
|
|
||||||
|
ExitCode exitCode = subcommand.run(commandEnv);
|
||||||
|
return new CommandResult(exitCode, subcommand, commandEnv);
|
||||||
|
|
||||||
|
} catch (CommandLineException | ParameterException e) {
|
||||||
|
printCauseChain(Level.WARNING, console, args, e);
|
||||||
|
console.error("Try 'copybara help'.");
|
||||||
|
return new CommandResult(ExitCode.COMMAND_LINE_ERROR, subcommand, commandEnv);
|
||||||
|
} catch (RepoException e) {
|
||||||
|
printCauseChain(Level.SEVERE, console, args, e);
|
||||||
|
// TODO(malcon): Expose interrupted exception from WorkflowMode to Main so that we don't
|
||||||
|
// have to do this hack.
|
||||||
|
if (e.getCause() instanceof InterruptedException) {
|
||||||
|
return new CommandResult(ExitCode.INTERRUPTED, subcommand, commandEnv);
|
||||||
|
}
|
||||||
|
return new CommandResult(ExitCode.REPOSITORY_ERROR, subcommand, commandEnv);
|
||||||
|
} catch (EmptyChangeException e) {
|
||||||
|
// This is not necessarily an error. Maybe the tool was run previously and there are no new
|
||||||
|
// changes to import.
|
||||||
|
console.warn(e.getMessage());
|
||||||
|
return new CommandResult(ExitCode.NO_OP, subcommand, commandEnv);
|
||||||
|
} catch (ValidationException e) {
|
||||||
|
printCauseChain(Level.WARNING, console, args, e);
|
||||||
|
return new CommandResult(ExitCode.CONFIGURATION_ERROR,
|
||||||
|
subcommand, commandEnv);
|
||||||
|
} catch (IOException e) {
|
||||||
|
handleUnexpectedError(console, e.getMessage(), args, e);
|
||||||
|
return new CommandResult(ExitCode.ENVIRONMENT_ERROR, subcommand, commandEnv);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// This usually indicates a serious programming error that will require Copybara team
|
||||||
|
// intervention. Print stack trace without concern for presentation.
|
||||||
|
e.printStackTrace();
|
||||||
|
handleUnexpectedError(console,
|
||||||
|
"Unexpected error (please file a bug against copybara): " + e.getMessage(), args, e);
|
||||||
|
return new CommandResult(ExitCode.INTERNAL_ERROR, subcommand, commandEnv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableSet<CopybaraCmd> getCommands(ModuleSet moduleSet,
|
||||||
|
ConfigLoaderProvider configLoaderProvider, JCommander jcommander)
|
||||||
|
throws CommandLineException {
|
||||||
|
ConfigValidator validator = getConfigValidator(moduleSet.getOptions());
|
||||||
|
Consumer<Migration> consumer = getMigrationRanConsumer();
|
||||||
|
return ImmutableSet.of(
|
||||||
|
new MigrateCmd(validator, consumer, configLoaderProvider),
|
||||||
|
new InfoCmd(configLoaderProvider, newInfoContextProvider()),
|
||||||
|
new ValidateCmd(validator, consumer, configLoaderProvider),
|
||||||
|
new HelpCmd(jcommander),
|
||||||
|
new VersionCmd());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a short String representing the version of the binary
|
||||||
|
*/
|
||||||
|
protected String getVersion() {
|
||||||
|
return "Unknown version";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a String (can be multiline) representing all the information about who and when the
|
||||||
|
* Copybara was built.
|
||||||
|
*/
|
||||||
|
protected String getBinaryInfo() {
|
||||||
|
return "Unknown version";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Consumer<Migration> getMigrationRanConsumer() {
|
||||||
|
return migration -> {};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ConfigValidator getConfigValidator(Options options) throws CommandLineException {
|
||||||
|
return new ConfigValidator() {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a new module set. */
|
||||||
|
protected ModuleSet newModuleSet(ImmutableMap<String, String> environment,
|
||||||
|
FileSystem fs, Console console) {
|
||||||
|
return new ModuleSupplier(environment, fs, console).create();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ConfigLoaderProvider newConfigLoaderProvider(ModuleSet moduleSet) {
|
||||||
|
GeneralOptions generalOptions = moduleSet.getOptions().get(GeneralOptions.class);
|
||||||
|
return (configPath, sourceRef) -> new ConfigLoader(moduleSet,
|
||||||
|
createConfigFileWithHeuristic(validateLocalConfig(generalOptions, configPath),
|
||||||
|
generalOptions.getConfigRoot()), generalOptions.getStarlarkMode());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ContextProvider newInfoContextProvider() {
|
||||||
|
return (config, configFileArgs, configLoaderProvider, console) -> ImmutableMap.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that the passed config file is correct (exists, follows the correct format, parent
|
||||||
|
* if passed is a real parent, etc.).
|
||||||
|
*
|
||||||
|
* <p>Returns the absolute {@link Path} of the config file.
|
||||||
|
*/
|
||||||
|
protected Path validateLocalConfig(GeneralOptions generalOptions, String configLocation)
|
||||||
|
throws ValidationException {
|
||||||
|
Path configPath = generalOptions.getFileSystem().getPath(configLocation).normalize();
|
||||||
|
String fileName = configPath.getFileName().toString();
|
||||||
|
checkCondition(
|
||||||
|
fileName.contentEquals(COPYBARA_SKYLARK_CONFIG_FILENAME),
|
||||||
|
"Copybara config file filename should be '%s' but it is '%s'.",
|
||||||
|
COPYBARA_SKYLARK_CONFIG_FILENAME, configPath.getFileName());
|
||||||
|
|
||||||
|
// Treat the top level element specially since it is passed thru the command line.
|
||||||
|
if (!Files.exists(configPath)) {
|
||||||
|
throw new CommandLineException("Configuration file not found: " + configPath);
|
||||||
|
}
|
||||||
|
return configPath.toAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the root path for resolving configuration file paths and resources. This method assumes
|
||||||
|
* that the .git containing directory is the root path.
|
||||||
|
*
|
||||||
|
* <p>This could be extended to other kind of source control systems.
|
||||||
|
*/
|
||||||
|
protected PathBasedConfigFile createConfigFileWithHeuristic(
|
||||||
|
Path configPath, @Nullable Path commandLineRoot) {
|
||||||
|
if (commandLineRoot != null) {
|
||||||
|
return new PathBasedConfigFile(configPath, commandLineRoot, /*identifierPrefix=*/ null);
|
||||||
|
}
|
||||||
|
Path parent = configPath.getParent();
|
||||||
|
while (parent != null) {
|
||||||
|
if (Files.isDirectory(parent.resolve(".git"))) {
|
||||||
|
return new PathBasedConfigFile(configPath, parent, /*identifierPrefix=*/ null);
|
||||||
|
}
|
||||||
|
parent = parent.getParent();
|
||||||
|
}
|
||||||
|
return new PathBasedConfigFile(configPath, /*rootPath=*/ null, /*identifierPrefix=*/ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Console getConsole(String[] args) {
|
||||||
|
boolean verbose = isVerbose(args);
|
||||||
|
// If System.console() is not present, we are forced to use LogConsole
|
||||||
|
Console console;
|
||||||
|
if (System.console() == null) {
|
||||||
|
console = LogConsole.writeOnlyConsole(System.err, verbose);
|
||||||
|
} else if (Arrays.asList(args).contains(GeneralOptions.NOANSI)) {
|
||||||
|
// The System.console doesn't detect redirects/pipes, but at least we have
|
||||||
|
// jobs covered.
|
||||||
|
console = LogConsole.readWriteConsole(System.in, System.err, verbose);
|
||||||
|
} else {
|
||||||
|
console = new AnsiConsole(System.in, System.err, verbose);
|
||||||
|
}
|
||||||
|
Optional<String> maybeConsoleFilePath = findFlagValue(args, GeneralOptions.CONSOLE_FILE_PATH);
|
||||||
|
if (!maybeConsoleFilePath.isPresent()) {
|
||||||
|
return console;
|
||||||
|
}
|
||||||
|
Path consoleFilePath = Paths.get(maybeConsoleFilePath.get());
|
||||||
|
try {
|
||||||
|
Files.createDirectories(consoleFilePath.getParent());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.atSevere().withCause(e).log(
|
||||||
|
"Could not create parent directories to file: %s. Redirecting will be disabled.",
|
||||||
|
consoleFilePath);
|
||||||
|
return console;
|
||||||
|
}
|
||||||
|
return new FileConsole(console, consoleFilePath, getConsoleFlushRate(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the console flush rate from the flag, if present and valid, or 0 (no flush) otherwise.
|
||||||
|
*/
|
||||||
|
protected Duration getConsoleFlushRate(String[] args) {
|
||||||
|
return findFlagValue(args, GeneralOptions.CONSOLE_FILE_FLUSH_INTERVAL)
|
||||||
|
.map(e -> new DurationConverter().convert(e))
|
||||||
|
.orElse(GeneralOptions.DEFAULT_CONSOLE_FILE_FLUSH_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void configureLog(FileSystem fs, String[] args) throws IOException {
|
||||||
|
String baseDir = getBaseExecDir();
|
||||||
|
Files.createDirectories(fs.getPath(baseDir));
|
||||||
|
if (System.getProperty("java.util.logging.config.file") == null) {
|
||||||
|
logger.atInfo().log("Setting up LogManager");
|
||||||
|
String level = isEnableLogging(args) ? "INFO" : "OFF";
|
||||||
|
LogManager.getLogManager().readConfiguration(new ByteArrayInputStream((
|
||||||
|
"handlers=java.util.logging.FileHandler\n"
|
||||||
|
+ ".level=INFO\n"
|
||||||
|
+ "java.util.logging.FileHandler.level=" + level +"\n"
|
||||||
|
+ "java.util.logging.FileHandler.pattern="
|
||||||
|
+ baseDir + "/copybara-%g.log\n"
|
||||||
|
+ "java.util.logging.FileHandler.count=10\n"
|
||||||
|
+ "java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter\n"
|
||||||
|
+ "java.util.logging.SimpleFormatter.format="
|
||||||
|
+ "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$-6s %2$s %5$s%6$s%n")
|
||||||
|
.getBytes(StandardCharsets.UTF_8)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to allow setting variables that are not run or validation specific, based on options.
|
||||||
|
* Sample use case are remote logging, test harnesses and others. Called after command line
|
||||||
|
* options are parsed, but before a file is read or a run started.
|
||||||
|
*/
|
||||||
|
protected void initEnvironment(Options options, CopybaraCmd copybaraCmd,
|
||||||
|
ImmutableList<String> rawArgs)
|
||||||
|
throws ValidationException, IOException, RepoException {
|
||||||
|
GeneralOptions generalOptions = options.get(GeneralOptions.class);
|
||||||
|
profiler = generalOptions.profiler();
|
||||||
|
ImmutableList.Builder<Listener> profilerListeners = ImmutableList.builder();
|
||||||
|
profilerListeners.add(
|
||||||
|
new LogProfilerListener(), new ConsoleProfilerListener(generalOptions.console()));
|
||||||
|
profiler.init(profilerListeners.build());
|
||||||
|
cleanupOutputDir(generalOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void cleanupOutputDir(GeneralOptions generalOptions)
|
||||||
|
throws RepoException, IOException, ValidationException {
|
||||||
|
generalOptions
|
||||||
|
.ioRepoTask(
|
||||||
|
"clean_outputdir",
|
||||||
|
() -> {
|
||||||
|
if (generalOptions.isNoCleanup()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
generalOptions.console().progress("Cleaning output directory");
|
||||||
|
generalOptions.getDirFactory().cleanupTempDirs();
|
||||||
|
// Only for profiling purposes, no need to use the console
|
||||||
|
logger.atInfo()
|
||||||
|
.log("Cleaned output directory:" + generalOptions.getDirFactory().getTmpRoot());
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Performs cleanup tasks after executing Copybara.
|
||||||
|
* @param result
|
||||||
|
*/
|
||||||
|
protected void shutdown(CommandResult result) throws InterruptedException {
|
||||||
|
// Before profiler.stop()
|
||||||
|
if (console != null) {
|
||||||
|
console.close();
|
||||||
|
}
|
||||||
|
if (profiler != null) {
|
||||||
|
profiler.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the base directory to be used by Copybara to write execution related files (Like
|
||||||
|
* logs).
|
||||||
|
*/
|
||||||
|
private String getBaseExecDir() {
|
||||||
|
// In this case we are not using GeneralOptions.getEnvironment() because we still haven't built
|
||||||
|
// the options, but it's fine. This is the tool's Main and is also injecting System.getEnv()
|
||||||
|
// to the options, so the value is the same.
|
||||||
|
String userHome = StandardSystemProperty.USER_HOME.value();
|
||||||
|
|
||||||
|
switch (StandardSystemProperty.OS_NAME.value()) {
|
||||||
|
case "Linux":
|
||||||
|
String xdgCacheHome = System.getenv("XDG_CACHE_HOME");
|
||||||
|
return Strings.isNullOrEmpty(xdgCacheHome)
|
||||||
|
? userHome + "/.cache/" + COPYBARA_NAMESPACE
|
||||||
|
: xdgCacheHome + COPYBARA_NAMESPACE;
|
||||||
|
case "Mac OS X":
|
||||||
|
return userHome + "/Library/Logs/" + COPYBARA_NAMESPACE;
|
||||||
|
default:
|
||||||
|
return "/var/tmp/" + COPYBARA_NAMESPACE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printCauseChain(Level level, Console console, String[] args, Throwable e) {
|
||||||
|
StringBuilder error = new StringBuilder(e.getMessage()).append("\n");
|
||||||
|
Throwable cause = e.getCause();
|
||||||
|
while (cause != null) {
|
||||||
|
error.append(" CAUSED BY: ").append(printException(cause)).append("\n");
|
||||||
|
cause = cause.getCause();
|
||||||
|
}
|
||||||
|
console.error(error.toString());
|
||||||
|
logger.at(level).withCause(e).log(formatLogError(e.getMessage(), args));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String printException(Throwable t) {
|
||||||
|
if (t instanceof EvalException) {
|
||||||
|
return (((EvalException) t).print());
|
||||||
|
}
|
||||||
|
return t.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUnexpectedError(Console console, String msg, String[] args, Throwable e) {
|
||||||
|
logger.atSevere().withCause(e).log(formatLogError(msg, args));
|
||||||
|
console.error(msg + " (" + e + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String usage(JCommander jcommander, String version) {
|
||||||
|
StringBuilder fullUsage = new StringBuilder();
|
||||||
|
fullUsage.append("Copybara version: ").append(version).append("\n");
|
||||||
|
jcommander.usage(fullUsage);
|
||||||
|
fullUsage
|
||||||
|
.append("\n")
|
||||||
|
.append("Example:\n")
|
||||||
|
.append(" copybara ").append(COPYBARA_SKYLARK_CONFIG_FILENAME).append(" origin/master\n");
|
||||||
|
return fullUsage.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatLogError(String message, String[] args) {
|
||||||
|
return String.format("%s (command args: %s)", message, Arrays.toString(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Prints the Copybara version */
|
||||||
|
@Parameters(separators = "=", commandDescription = "Shows the version of Copybara.")
|
||||||
|
private class VersionCmd implements CopybaraCmd {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExitCode run(CommandEnv commandEnv)
|
||||||
|
throws ValidationException, IOException, RepoException {
|
||||||
|
commandEnv.getOptions().get(GeneralOptions.class).console().info(getBinaryInfo());
|
||||||
|
return ExitCode.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "version";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints the help message
|
||||||
|
* TODO(malcon): Implement help per command
|
||||||
|
*/
|
||||||
|
@Parameters(separators = "=", commandDescription = "Shows the help.")
|
||||||
|
private class HelpCmd implements CopybaraCmd {
|
||||||
|
|
||||||
|
private final JCommander jCommander;
|
||||||
|
|
||||||
|
HelpCmd(JCommander jCommander) {
|
||||||
|
this.jCommander = Preconditions.checkNotNull(jCommander);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExitCode run(CommandEnv commandEnv)
|
||||||
|
throws ValidationException, IOException, RepoException {
|
||||||
|
String version = getVersion();
|
||||||
|
commandEnv.getOptions().get(GeneralOptions.class).console().info(usage(jCommander, version));
|
||||||
|
return ExitCode.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "help";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
147
third_party/copybara/java/com/google/copybara/MainArguments.java
vendored
Normal file
147
third_party/copybara/java/com/google/copybara/MainArguments.java
vendored
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.beust.jcommander.Parameter;
|
||||||
|
import com.beust.jcommander.Parameters;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.copybara.exception.CommandLineException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.DirectoryStream;
|
||||||
|
import java.nio.file.FileSystem;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import javax.annotation.concurrent.NotThreadSafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arguments which are unnamed (i.e. positional) or must be evaluated inside {@link Main}.
|
||||||
|
*/
|
||||||
|
@NotThreadSafe
|
||||||
|
@Parameters(separators = "=")
|
||||||
|
public final class MainArguments {
|
||||||
|
private final Logger logger = Logger.getLogger(this.getClass().getName());
|
||||||
|
|
||||||
|
public static final String COPYBARA_SKYLARK_CONFIG_FILENAME = "copy.bara.sky";
|
||||||
|
|
||||||
|
// TODO(danielromero): Annotate the subcommands with the documentation and generate this
|
||||||
|
// automatically.
|
||||||
|
@Parameter(description =
|
||||||
|
""
|
||||||
|
+ "[subcommand] [config_path [migration_name [source_ref]...]]\n"
|
||||||
|
+ "\n"
|
||||||
|
+ (""
|
||||||
|
+ "subcommand: Optional, defaults to 'migrate'. The type of task to be performed by "
|
||||||
|
+ "Copybara. Available subcommands:\n"
|
||||||
|
+ " - help: Shows the help.\n"
|
||||||
|
+ " - info: Reads the last migrated revision in the origin and destination.\n"
|
||||||
|
+ " - migrate: Executes the migration for the given config.\n"
|
||||||
|
+ " - validate: Validates that the configuration is correct.\n"
|
||||||
|
+ " - version: Shows the version of Copybara.\n"
|
||||||
|
+ "")
|
||||||
|
+ "\n"
|
||||||
|
+ "config_path: Required. Relative or absolute path to the main Copybara config file.\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "migration_name: Optional, defaults to 'default'. The name of the migration that the "
|
||||||
|
+ "subcommand will be applied to.\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "source_ref: Optional. The reference(s) to be resolved in the origin. Most of the "
|
||||||
|
+ "times this argument is not needed, as Copybara can infer the last migrated reference "
|
||||||
|
+ "in the destination. Different subcommands might require this argument, use only one "
|
||||||
|
+ "source_ref or use all the list.\n"
|
||||||
|
)
|
||||||
|
List<String> unnamed = new ArrayList<>();
|
||||||
|
|
||||||
|
@Parameter(names = "--work-dir", description = "Directory where all the transformations"
|
||||||
|
+ " will be performed. By default a temporary directory.")
|
||||||
|
String baseWorkdir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the base working directory. This method should not be accessed directly by any other
|
||||||
|
* class but Main.
|
||||||
|
*/
|
||||||
|
public Path getBaseWorkdir(GeneralOptions generalOptions, FileSystem fs)
|
||||||
|
throws IOException {
|
||||||
|
Path workdirPath;
|
||||||
|
|
||||||
|
workdirPath = baseWorkdir == null
|
||||||
|
? generalOptions.getDirFactory().newTempDir("workdir")
|
||||||
|
: fs.getPath(baseWorkdir).normalize();
|
||||||
|
logger.log(Level.INFO, String.format("Using workdir: %s", workdirPath.toAbsolutePath()));
|
||||||
|
|
||||||
|
if (Files.exists(workdirPath) && !Files.isDirectory(workdirPath)) {
|
||||||
|
// Better being safe
|
||||||
|
throw new IOException(
|
||||||
|
"'" + workdirPath + "' exists and is not a directory");
|
||||||
|
}
|
||||||
|
if (!isDirEmpty(workdirPath)) {
|
||||||
|
System.err.println("WARNING: " + workdirPath + " is not empty");
|
||||||
|
}
|
||||||
|
return workdirPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDirEmpty(final Path directory) throws IOException {
|
||||||
|
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(directory)) {
|
||||||
|
return !dirStream.iterator().hasNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandWithArgs parseCommand(ImmutableMap<String, ? extends CopybaraCmd> commands,
|
||||||
|
CopybaraCmd defaultCmd) throws CommandLineException {
|
||||||
|
if (unnamed.isEmpty()) {
|
||||||
|
return new CommandWithArgs(defaultCmd, ImmutableList.of());
|
||||||
|
}
|
||||||
|
String firstArg = unnamed.get(0);
|
||||||
|
// Default command might take a config file as param.
|
||||||
|
if (firstArg.endsWith(COPYBARA_SKYLARK_CONFIG_FILENAME)) {
|
||||||
|
return new CommandWithArgs(defaultCmd, ImmutableList.copyOf(unnamed));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!commands.containsKey(firstArg.toLowerCase())) {
|
||||||
|
throw new CommandLineException(
|
||||||
|
String.format("Invalid subcommand '%s'. Available commands: %s", firstArg,
|
||||||
|
new TreeSet<>(commands.keySet())));
|
||||||
|
}
|
||||||
|
return new CommandWithArgs(commands.get(firstArg.toLowerCase()),
|
||||||
|
ImmutableList.copyOf(unnamed.subList(1, unnamed.size())));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CommandWithArgs {
|
||||||
|
|
||||||
|
private final CopybaraCmd subcommand;
|
||||||
|
private final ImmutableList<String> args;
|
||||||
|
|
||||||
|
private CommandWithArgs(CopybaraCmd subcommand, ImmutableList<String> args) {
|
||||||
|
this.subcommand = Preconditions.checkNotNull(subcommand);
|
||||||
|
this.args = Preconditions.checkNotNull(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
CopybaraCmd getSubcommand() {
|
||||||
|
return subcommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmutableList<String> getArgs() {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
third_party/copybara/java/com/google/copybara/MapMapper.java
vendored
Normal file
50
third_party/copybara/java/com/google/copybara/MapMapper.java
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableBiMap;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.copybara.transform.ReversibleFunction;
|
||||||
|
import com.google.devtools.build.lib.syntax.Location;
|
||||||
|
|
||||||
|
public class MapMapper implements ReversibleFunction<String, String> {
|
||||||
|
|
||||||
|
private final ImmutableMap<String, String> map;
|
||||||
|
private final Location location;
|
||||||
|
|
||||||
|
MapMapper(ImmutableMap<String, String> map, Location location) {
|
||||||
|
this.map = Preconditions.checkNotNull(map);
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReversibleFunction<String, String> reverseMapping() throws NonReversibleValidationException {
|
||||||
|
try {
|
||||||
|
return new MapMapper(ImmutableBiMap.copyOf(map).inverse(), location);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new NonReversibleValidationException(location,
|
||||||
|
"Non-reversible map: " + map + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String apply(String s) {
|
||||||
|
String v = map.get(s);
|
||||||
|
return v == null ? s : v;
|
||||||
|
}
|
||||||
|
}
|
91
third_party/copybara/java/com/google/copybara/Metadata.java
vendored
Normal file
91
third_party/copybara/java/com/google/copybara/Metadata.java
vendored
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.collect.ImmutableMultimap;
|
||||||
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
|
import com.google.copybara.authoring.Author;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata associated with a change: Change message, author, etc.
|
||||||
|
*/
|
||||||
|
public final class Metadata {
|
||||||
|
|
||||||
|
private final String message;
|
||||||
|
private final Author author;
|
||||||
|
private final ImmutableSetMultimap<String, String> hiddenLabels;
|
||||||
|
|
||||||
|
public Metadata(String message, Author author,
|
||||||
|
ImmutableSetMultimap<String, String> hiddenLabels) {
|
||||||
|
this.message = checkNotNull(message);
|
||||||
|
this.author = checkNotNull(author);
|
||||||
|
this.hiddenLabels = checkNotNull(hiddenLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Metadata withAuthor(Author author) {
|
||||||
|
return new Metadata(message, checkNotNull(author, "Author cannot be null"), hiddenLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Metadata withMessage(String message) {
|
||||||
|
return new Metadata(checkNotNull(message, "Message cannot be null"), author, hiddenLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We never allow deleting hidden labels. Use a different name if you want to rename one.
|
||||||
|
*/
|
||||||
|
public final Metadata addHiddenLabels(ImmutableMultimap<String, String> hiddenLabels) {
|
||||||
|
checkNotNull(hiddenLabels, "hidden labels cannot be null");
|
||||||
|
return new Metadata(message, author,
|
||||||
|
ImmutableSetMultimap.<String, String>builder()
|
||||||
|
.putAll(this.hiddenLabels)
|
||||||
|
.putAll(hiddenLabels).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description to be used for the change
|
||||||
|
*/
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Author to be used for the change
|
||||||
|
*/
|
||||||
|
public Author getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hidden labels are labels added by transformations during transformations but that they are
|
||||||
|
* not visible in the message.
|
||||||
|
*/
|
||||||
|
public ImmutableSetMultimap<String, String> getHiddenLabels() {
|
||||||
|
return hiddenLabels;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("message", message)
|
||||||
|
.add("author", author)
|
||||||
|
.add("hiddenLabels", hiddenLabels)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
121
third_party/copybara/java/com/google/copybara/MigrateCmd.java
vendored
Normal file
121
third_party/copybara/java/com/google/copybara/MigrateCmd.java
vendored
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import static com.google.copybara.exception.ValidationException.checkCondition;
|
||||||
|
|
||||||
|
import com.beust.jcommander.Parameters;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.copybara.config.Config;
|
||||||
|
import com.google.copybara.config.ConfigValidator;
|
||||||
|
import com.google.copybara.config.Migration;
|
||||||
|
import com.google.copybara.config.ValidationResult;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.util.ExitCode;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the migration for the given config.
|
||||||
|
*/
|
||||||
|
@Parameters(separators = "=", commandDescription = "Executes the migration for the given config.")
|
||||||
|
public class MigrateCmd implements CopybaraCmd {
|
||||||
|
|
||||||
|
private final ConfigValidator configValidator;
|
||||||
|
private final Consumer<Migration> migrationRanConsumer;
|
||||||
|
private final ConfigLoaderProvider configLoaderProvider;
|
||||||
|
|
||||||
|
MigrateCmd(ConfigValidator configValidator, Consumer<Migration> migrationRanConsumer,
|
||||||
|
ConfigLoaderProvider configLoaderProvider) {
|
||||||
|
this.configValidator = Preconditions.checkNotNull(configValidator);
|
||||||
|
this.migrationRanConsumer = Preconditions.checkNotNull(migrationRanConsumer);
|
||||||
|
this.configLoaderProvider = Preconditions.checkNotNull(configLoaderProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExitCode run(CommandEnv commandEnv)
|
||||||
|
throws RepoException, ValidationException, IOException {
|
||||||
|
ConfigFileArgs configFileArgs = commandEnv.parseConfigFileArgs(this,
|
||||||
|
/*useSourceRef*/true);
|
||||||
|
ImmutableList<String> sourceRefs = configFileArgs.getSourceRefs();
|
||||||
|
run(
|
||||||
|
commandEnv.getOptions(),
|
||||||
|
configLoaderProvider.newLoader(
|
||||||
|
configFileArgs.getConfigPath(),
|
||||||
|
sourceRefs.size() == 1 ? Iterables.getOnlyElement(sourceRefs) : null),
|
||||||
|
configFileArgs.getWorkflowName(),
|
||||||
|
commandEnv.getWorkdir(),
|
||||||
|
sourceRefs);
|
||||||
|
return ExitCode.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the migration specified by {@code migrationName}.
|
||||||
|
*/
|
||||||
|
private void run(Options options, ConfigLoader configLoader, String migrationName,
|
||||||
|
Path workdir, ImmutableList<String> sourceRefs)
|
||||||
|
throws RepoException, ValidationException, IOException {
|
||||||
|
Config config = loadConfig(options, configLoader, migrationName);
|
||||||
|
Migration migration = config.getMigration(migrationName);
|
||||||
|
|
||||||
|
if (!options.get(WorkflowOptions.class).isReadConfigFromChange()) {
|
||||||
|
this.migrationRanConsumer.accept(migration);
|
||||||
|
migration.run(workdir, sourceRefs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCondition(configLoader.supportsLoadForRevision(),
|
||||||
|
"%s flag is not supported for the origin/config file path",
|
||||||
|
WorkflowOptions.READ_CONFIG_FROM_CHANGE);
|
||||||
|
|
||||||
|
// A safeguard, mirror workflows are not supported in the service anyway
|
||||||
|
checkCondition(migration instanceof Workflow,
|
||||||
|
"Flag --read-config-from-change is not supported for non-workflow migrations: %s",
|
||||||
|
migrationName);
|
||||||
|
migrationRanConsumer.accept(migration);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Workflow<? extends Revision, ? extends Revision> workflow =
|
||||||
|
(Workflow<? extends Revision, ? extends Revision>) migration;
|
||||||
|
new ReadConfigFromChangeWorkflow<>(workflow, options, configLoader, configValidator)
|
||||||
|
.run(workdir, sourceRefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Config loadConfig(Options options, ConfigLoader configLoader, String migrationName)
|
||||||
|
throws IOException, ValidationException {
|
||||||
|
GeneralOptions generalOptions = options.get(GeneralOptions.class);
|
||||||
|
Console console = generalOptions.console();
|
||||||
|
Config config = configLoader.load(console);
|
||||||
|
console.progress("Validating configuration");
|
||||||
|
ValidationResult result = configValidator.validate(config, migrationName);
|
||||||
|
if (!result.hasErrors()) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
result.getErrors().forEach(console::error);
|
||||||
|
console.error("Configuration is invalid.");
|
||||||
|
throw new ValidationException("Error validating configuration: Configuration is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "migrate";
|
||||||
|
}
|
||||||
|
}
|
44
third_party/copybara/java/com/google/copybara/MigrationInfo.java
vendored
Normal file
44
third_party/copybara/java/com/google/copybara/MigrationInfo.java
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reflective information about the migration in progress.
|
||||||
|
*/
|
||||||
|
public class MigrationInfo {
|
||||||
|
@Nullable private final String originLabel;
|
||||||
|
@Nullable private final ChangeVisitable<?> destinationVisitable;
|
||||||
|
|
||||||
|
public MigrationInfo(String originLabel, ChangeVisitable<?> destinationVisitable) {
|
||||||
|
this.originLabel = originLabel;
|
||||||
|
this.destinationVisitable = destinationVisitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOriginLabel() {
|
||||||
|
return checkNotNull(originLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public ChangeVisitable<?> destinationVisitable() {
|
||||||
|
return destinationVisitable;
|
||||||
|
}
|
||||||
|
}
|
64
third_party/copybara/java/com/google/copybara/ModuleSet.java
vendored
Normal file
64
third_party/copybara/java/com/google/copybara/ModuleSet.java
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of modules and options for evaluating a Skylark config file.
|
||||||
|
*/
|
||||||
|
public class ModuleSet {
|
||||||
|
|
||||||
|
private final Options options;
|
||||||
|
// TODO(malcon): Remove this once all modules are @StarlarkMethod
|
||||||
|
private final ImmutableSet<Class<?>> staticModules;
|
||||||
|
private final ImmutableMap<String, Object> modules;
|
||||||
|
|
||||||
|
ModuleSet(Options options,
|
||||||
|
ImmutableSet<Class<?>> staticModules,
|
||||||
|
ImmutableMap<String, Object> modules) {
|
||||||
|
this.options = Preconditions.checkNotNull(options);
|
||||||
|
this.staticModules = Preconditions.checkNotNull(staticModules);
|
||||||
|
this.modules = Preconditions.checkNotNull(modules);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copybara options
|
||||||
|
*/
|
||||||
|
public Options getOptions() {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static modules. Will be deleted.
|
||||||
|
* TODO(malcon): Delete
|
||||||
|
*/
|
||||||
|
public ImmutableSet<Class<?>> getStaticModules() {
|
||||||
|
return staticModules;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-static Copybara modules.
|
||||||
|
*/
|
||||||
|
public ImmutableMap<String, Object> getModules() {
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
160
third_party/copybara/java/com/google/copybara/ModuleSupplier.java
vendored
Normal file
160
third_party/copybara/java/com/google/copybara/ModuleSupplier.java
vendored
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.copybara.authoring.Authoring;
|
||||||
|
import com.google.copybara.buildozer.BuildozerModule;
|
||||||
|
import com.google.copybara.buildozer.BuildozerOptions;
|
||||||
|
import com.google.copybara.folder.FolderDestinationOptions;
|
||||||
|
import com.google.copybara.folder.FolderModule;
|
||||||
|
import com.google.copybara.folder.FolderOriginOptions;
|
||||||
|
import com.google.copybara.format.BuildifierOptions;
|
||||||
|
import com.google.copybara.format.FormatModule;
|
||||||
|
import com.google.copybara.git.GerritOptions;
|
||||||
|
import com.google.copybara.git.GitDestinationOptions;
|
||||||
|
import com.google.copybara.git.GitHubDestinationOptions;
|
||||||
|
import com.google.copybara.git.GitHubOptions;
|
||||||
|
import com.google.copybara.git.GitHubPrOriginOptions;
|
||||||
|
import com.google.copybara.git.GitMirrorOptions;
|
||||||
|
import com.google.copybara.git.GitModule;
|
||||||
|
import com.google.copybara.git.GitOptions;
|
||||||
|
import com.google.copybara.git.GitOriginOptions;
|
||||||
|
import com.google.copybara.hg.HgModule;
|
||||||
|
import com.google.copybara.hg.HgOptions;
|
||||||
|
import com.google.copybara.hg.HgOriginOptions;
|
||||||
|
import com.google.copybara.remotefile.RemoteFileModule;
|
||||||
|
import com.google.copybara.remotefile.RemoteFileOptions;
|
||||||
|
import com.google.copybara.transform.debug.DebugOptions;
|
||||||
|
import com.google.copybara.transform.metadata.MetadataModule;
|
||||||
|
import com.google.copybara.transform.patch.PatchModule;
|
||||||
|
import com.google.copybara.transform.patch.PatchingOptions;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import java.nio.file.FileSystem;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A supplier of modules and {@link Option}s for Copybara.
|
||||||
|
*/
|
||||||
|
public class ModuleSupplier {
|
||||||
|
|
||||||
|
private static final ImmutableSet<Class<?>> BASIC_MODULES = ImmutableSet.of(
|
||||||
|
CoreGlobal.class);
|
||||||
|
private final Map<String, String> environment;
|
||||||
|
private final FileSystem fileSystem;
|
||||||
|
private final Console console;
|
||||||
|
|
||||||
|
public ModuleSupplier(Map<String, String> environment, FileSystem fileSystem,
|
||||||
|
Console console) {
|
||||||
|
this.environment = Preconditions.checkNotNull(environment);
|
||||||
|
this.fileSystem = Preconditions.checkNotNull(fileSystem);
|
||||||
|
this.console = Preconditions.checkNotNull(console);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@code set} of modules available.
|
||||||
|
* TODO(malcon): Remove once no more static modules exist.
|
||||||
|
*/
|
||||||
|
protected ImmutableSet<Class<?>> getStaticModules() {
|
||||||
|
return BASIC_MODULES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get non-static modules available
|
||||||
|
*/
|
||||||
|
public ImmutableSet<Object> getModules(Options options) {
|
||||||
|
GeneralOptions general = options.get(GeneralOptions.class);
|
||||||
|
return ImmutableSet.of(
|
||||||
|
new Core(general, options.get(WorkflowOptions.class), options.get(DebugOptions.class)),
|
||||||
|
new GitModule(options), new HgModule(options),
|
||||||
|
new FolderModule(
|
||||||
|
options.get(FolderOriginOptions.class),
|
||||||
|
options.get(FolderDestinationOptions.class),
|
||||||
|
general),
|
||||||
|
new FormatModule(
|
||||||
|
options.get(WorkflowOptions.class), options.get(BuildifierOptions.class), general),
|
||||||
|
new BuildozerModule(
|
||||||
|
options.get(WorkflowOptions.class), options.get(BuildozerOptions.class)),
|
||||||
|
new PatchModule(options.get(PatchingOptions.class)),
|
||||||
|
new MetadataModule(),
|
||||||
|
new Authoring.Module(),
|
||||||
|
new RemoteFileModule(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a new list of {@link Option}s. */
|
||||||
|
protected Options newOptions() {
|
||||||
|
GeneralOptions generalOptions = new GeneralOptions(environment, fileSystem, console);
|
||||||
|
GitOptions gitOptions = new GitOptions(generalOptions);
|
||||||
|
GitDestinationOptions gitDestinationOptions =
|
||||||
|
new GitDestinationOptions(generalOptions, gitOptions);
|
||||||
|
BuildifierOptions buildifierOptions = new BuildifierOptions();
|
||||||
|
WorkflowOptions workflowOptions = new WorkflowOptions();
|
||||||
|
return new Options(ImmutableList.of(
|
||||||
|
generalOptions,
|
||||||
|
buildifierOptions,
|
||||||
|
new BuildozerOptions(generalOptions, buildifierOptions, workflowOptions),
|
||||||
|
new FolderDestinationOptions(),
|
||||||
|
new FolderOriginOptions(),
|
||||||
|
gitOptions,
|
||||||
|
new GitOriginOptions(),
|
||||||
|
new GitHubPrOriginOptions(),
|
||||||
|
gitDestinationOptions,
|
||||||
|
new GitHubOptions(generalOptions, gitOptions),
|
||||||
|
new GitHubDestinationOptions(),
|
||||||
|
new GerritOptions(generalOptions, gitOptions),
|
||||||
|
new GitMirrorOptions(generalOptions, gitOptions),
|
||||||
|
new HgOptions(generalOptions),
|
||||||
|
new HgOriginOptions(),
|
||||||
|
new PatchingOptions(generalOptions),
|
||||||
|
workflowOptions,
|
||||||
|
new RemoteFileOptions(),
|
||||||
|
new DebugOptions(generalOptions)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ModuleSet contains the collection of modules and flags for one Skylark copy.bara.sky
|
||||||
|
* evaluation/execution.
|
||||||
|
*/
|
||||||
|
public final ModuleSet create() {
|
||||||
|
Options options = newOptions();
|
||||||
|
return new ModuleSet(options, getStaticModules(), modulesToVariableMap(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImmutableMap<String, Object> modulesToVariableMap(Options options) {
|
||||||
|
return getModules(options).stream()
|
||||||
|
.collect(ImmutableMap.toImmutableMap(
|
||||||
|
this::findClosestStarlarkBuiltinName,
|
||||||
|
Function.identity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String findClosestStarlarkBuiltinName(Object o) {
|
||||||
|
Class<?> cls = o.getClass();
|
||||||
|
while (cls != null && cls != Object.class) {
|
||||||
|
StarlarkBuiltin annotation = cls.getAnnotation(StarlarkBuiltin.class);
|
||||||
|
if (annotation != null) {
|
||||||
|
return annotation.name();
|
||||||
|
}
|
||||||
|
cls = cls.getSuperclass();
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("Cannot find @StarlarkBuiltin for " + o.getClass());
|
||||||
|
}
|
||||||
|
}
|
37
third_party/copybara/java/com/google/copybara/NonReversibleValidationException.java
vendored
Normal file
37
third_party/copybara/java/com/google/copybara/NonReversibleValidationException.java
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.devtools.build.lib.syntax.EvalException;
|
||||||
|
import com.google.devtools.build.lib.syntax.Location;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a {@link Transformation} is not reversible but the configuration asked for
|
||||||
|
* the reverse.
|
||||||
|
*
|
||||||
|
* TODO(malcon): Move to the exception package
|
||||||
|
*/
|
||||||
|
public class NonReversibleValidationException extends EvalException {
|
||||||
|
|
||||||
|
public NonReversibleValidationException(Location location, String message) {
|
||||||
|
super(location, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NonReversibleValidationException(Location location, String message, Throwable cause) {
|
||||||
|
super(location, message, cause);
|
||||||
|
}
|
||||||
|
}
|
23
third_party/copybara/java/com/google/copybara/Option.java
vendored
Normal file
23
third_party/copybara/java/com/google/copybara/Option.java
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@code Option} indicates a class has options usable by copybara's config system.
|
||||||
|
*/
|
||||||
|
public interface Option {
|
||||||
|
}
|
61
third_party/copybara/java/com/google/copybara/Options.java
vendored
Normal file
61
third_party/copybara/java/com/google/copybara/Options.java
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableCollection;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that groups all the options used in the program
|
||||||
|
*/
|
||||||
|
public class Options {
|
||||||
|
|
||||||
|
private final ImmutableMap<Class<? extends Option>, Option> config;
|
||||||
|
|
||||||
|
public Options(Iterable<? extends Option> options) {
|
||||||
|
ImmutableMap.Builder<Class<? extends Option>, Option> builder = ImmutableMap.builder();
|
||||||
|
for (Option option : options) {
|
||||||
|
builder.put(option.getClass(), option);
|
||||||
|
}
|
||||||
|
config = builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an option for a given class.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the configuration cannot be found
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends Option> T get(Class<? extends T> optionClass) {
|
||||||
|
Option option = config.get(optionClass);
|
||||||
|
if (option == null) {
|
||||||
|
// If we didn't find the exact class, look for a subclass.
|
||||||
|
for (Entry<Class<? extends Option>, Option> entry : config.entrySet()) {
|
||||||
|
if (optionClass.isAssignableFrom(entry.getKey())) {
|
||||||
|
return (T) entry.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("No option type found for " + optionClass);
|
||||||
|
}
|
||||||
|
return (T) option;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableCollection<Option> getAll() {
|
||||||
|
return config.values();
|
||||||
|
}
|
||||||
|
}
|
400
third_party/copybara/java/com/google/copybara/Origin.java
vendored
Normal file
400
third_party/copybara/java/com/google/copybara/Origin.java
vendored
Normal file
|
@ -0,0 +1,400 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import static com.google.common.collect.Queues.newArrayDeque;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.copybara.authoring.Authoring;
|
||||||
|
import com.google.copybara.exception.EmptyChangeException;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.util.Glob;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code Origin} represents a source control repository from which source is copied.
|
||||||
|
*
|
||||||
|
* @param <R> the origin type of the references/revisions this origin handles
|
||||||
|
*/
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "origin",
|
||||||
|
doc = "A Origin represents a source control repository from which source is copied.",
|
||||||
|
category = StarlarkDocumentationCategory.TOP_LEVEL_TYPE,
|
||||||
|
documented = false)
|
||||||
|
public interface Origin<R extends Revision> extends ConfigItemDescription, StarlarkValue {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a migration reference into a revision. For example for git it would resolve 'master'
|
||||||
|
* to the SHA-1.
|
||||||
|
*
|
||||||
|
* @throws RepoException if any error happens during the resolve.
|
||||||
|
*/
|
||||||
|
R resolve(String reference) throws RepoException, ValidationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show different changes between two references. Returns null if the origin doesn't
|
||||||
|
* support generating differences.
|
||||||
|
*
|
||||||
|
* @throws RepoException
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
default String showDiff(R revisionFrom, R revisionTo) throws RepoException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object which is capable of checking out code from the origin at particular paths. This can
|
||||||
|
* also enumerate changes in the history and transform authorship information.
|
||||||
|
*/
|
||||||
|
interface Reader<R extends Revision> extends ChangeVisitable<R> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks out the revision {@code ref} from the repository into {@code workdir} directory. This
|
||||||
|
* method is not on {@link Revision} in order to prevent {@link Destination} implementations
|
||||||
|
* from getting access to the code pre-transformation.
|
||||||
|
*
|
||||||
|
* @throws RepoException if any error happens during the checkout or workdir preparation.
|
||||||
|
*/
|
||||||
|
void checkout(R ref, Path workdir) throws RepoException, ValidationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the changes that happen in the interval (fromRef, toRef].
|
||||||
|
*
|
||||||
|
* <p>If {@code fromRef} is null, returns all the changes from the first commit of the parent
|
||||||
|
* branch to {@code toRef}, both included.
|
||||||
|
*
|
||||||
|
* @param fromRef the revision used in the latest invocation. If null it means that no previous
|
||||||
|
* ref could be found or that the destination didn't store the ref.
|
||||||
|
* @param toRef current revision to transform.
|
||||||
|
* @throws RepoException if any error happens during the computation of the diff.
|
||||||
|
*/
|
||||||
|
ChangesResponse<R> changes(@Nullable R fromRef, R toRef)
|
||||||
|
throws RepoException, ValidationException;
|
||||||
|
|
||||||
|
class ChangesResponse<R extends Revision> {
|
||||||
|
|
||||||
|
private final ImmutableList<Change<R>> changes;
|
||||||
|
@Nullable private final EmptyReason emptyReason;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes in key will only be included if the value is included. The usage is for non-linear
|
||||||
|
* histories like git where including a change depends if we end up including the merge
|
||||||
|
* commit.
|
||||||
|
*/
|
||||||
|
private final ImmutableMap<Change<R>, Change<R>> conditionalChanges;
|
||||||
|
|
||||||
|
private ChangesResponse(ImmutableList<Change<R>> changes,
|
||||||
|
ImmutableMap<Change<R>, Change<R>> conditionalChanges,
|
||||||
|
@Nullable EmptyReason emptyReason) {
|
||||||
|
this.changes = Preconditions.checkNotNull(changes);
|
||||||
|
this.conditionalChanges = Preconditions.checkNotNull(conditionalChanges);
|
||||||
|
this.emptyReason = emptyReason;
|
||||||
|
Preconditions.checkArgument(changes.isEmpty() ^ emptyReason == null, "Either we have"
|
||||||
|
+ " changes or we have an empty reason");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends Revision> ChangesResponse<T> forChanges(
|
||||||
|
Iterable<Change<T>> changes) {
|
||||||
|
Preconditions.checkArgument(!Iterables.isEmpty(changes), "Empty changes not allowed");
|
||||||
|
return new ChangesResponse<>(ImmutableList.copyOf(changes),
|
||||||
|
ImmutableMap.copyOf(ImmutableMap.of()),
|
||||||
|
/*emptyReason=*/ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a ChangeResponse object with changes where some of them are conditional to their
|
||||||
|
* closest first-parent root being included (merge commit).
|
||||||
|
*/
|
||||||
|
public static <R extends Revision> ChangesResponse<R> forChangesWithMerges(
|
||||||
|
Iterable<Change<R>> changes) {
|
||||||
|
Preconditions.checkArgument(!Iterables.isEmpty(changes),
|
||||||
|
"Shouldn't be called for empty changes");
|
||||||
|
|
||||||
|
Map<R, Change<R>> byRevision = new HashMap<>();
|
||||||
|
List<Change<R>> all = new ArrayList<>();
|
||||||
|
for (Change<R> e : changes) {
|
||||||
|
all.add(e);
|
||||||
|
byRevision.put(e.getRevision(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Change<R>> firstParents = new ArrayList<>();
|
||||||
|
Set<R> toSkip = new HashSet<>();
|
||||||
|
Change<R> latest = Iterables.getLast(changes);
|
||||||
|
|
||||||
|
// Compute first parents and add them to toSkip so that they are not counted as conditional
|
||||||
|
// changes later.
|
||||||
|
while (true) {
|
||||||
|
firstParents.add(latest);
|
||||||
|
// We don't want to add first parents as conditional changes
|
||||||
|
toSkip.add(latest.getRevision());
|
||||||
|
if (parents(latest).isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
R firstParent = parents(latest).get(0);
|
||||||
|
Change<R> firstParentChange = byRevision.get(firstParent);
|
||||||
|
if (firstParentChange == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
latest = firstParentChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Change<R>, Change<R>> conditionalChanges = new HashMap<>();
|
||||||
|
|
||||||
|
// Traverse from old to new so that we use oldest first-parent as the conditional change.
|
||||||
|
for (Change<R> firstParent : Lists.reverse(firstParents)) {
|
||||||
|
// Skip non-merges
|
||||||
|
if (parents(firstParent).size() < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Deque<R> toVisit = newArrayDeque(Iterables.skip(parents(firstParent), 1));
|
||||||
|
while (!toVisit.isEmpty()) {
|
||||||
|
R revision = toVisit.poll();
|
||||||
|
// Don't traverse again non-first parents already visited: This is for performance and
|
||||||
|
// correctness, the conditional changes is the oldest merge first-parent.
|
||||||
|
if (!toSkip.add(revision)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Change<R> change = byRevision.get(revision);
|
||||||
|
if (change == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
conditionalChanges.put(change, firstParent);
|
||||||
|
toVisit.addAll(parents(change));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ChangesResponse<>(ImmutableList.copyOf((Iterable<Change<R>>) all),
|
||||||
|
ImmutableMap.copyOf(conditionalChanges),
|
||||||
|
/*emptyReason=*/ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <R extends Revision> ImmutableList<R> parents(Change<R> change) {
|
||||||
|
return Preconditions.checkNotNull(change.getParents(),
|
||||||
|
"Don't use forChangesWithParents for changes that don't support parents: ",
|
||||||
|
change);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@code ChangeResponse} that doesn't contain any change
|
||||||
|
*/
|
||||||
|
public static <T extends Revision> ChangesResponse<T> noChanges(EmptyReason emptyReason) {
|
||||||
|
Preconditions.checkNotNull(emptyReason);
|
||||||
|
return new ChangesResponse<>(ImmutableList.of(), ImmutableMap.of(), emptyReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there are no changes.
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return changes.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmptyReason getEmptyReason() {
|
||||||
|
Preconditions.checkNotNull(emptyReason, "Use isEmpty() first");
|
||||||
|
return emptyReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The changes that happen in the interval (fromRef, toRef].
|
||||||
|
*
|
||||||
|
* <p>The list might include changes that shouldn't be included in the final list of changes.
|
||||||
|
* Check conditionalChanges for changes that might not be included.
|
||||||
|
*/
|
||||||
|
public ImmutableList<Change<R>> getChanges() {
|
||||||
|
Preconditions.checkNotNull(changes, "Use isEmpty() first");
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes that should only be included if the change in the value is also included.
|
||||||
|
*/
|
||||||
|
ImmutableMap<Change<R>, Change<R>> getConditionalChanges() {
|
||||||
|
return conditionalChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reason why {@link Origin.Reader#changes(Revision, Revision)} didn't return any change */
|
||||||
|
public enum EmptyReason {
|
||||||
|
/** 'from' is ancestor of 'to' but all changes are for irrelevant files */
|
||||||
|
NO_CHANGES,
|
||||||
|
/** There is no parent/child relationship between 'from' and 'to' */
|
||||||
|
UNRELATED_REVISIONS,
|
||||||
|
/* 'to' is equal or ancestor of 'from' */
|
||||||
|
TO_IS_ANCESTOR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns true if the origin repository supports maintaining a history of changes. Generally
|
||||||
|
* this should be true
|
||||||
|
*/
|
||||||
|
default boolean supportsHistory() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a change identified by {@code ref}.
|
||||||
|
*
|
||||||
|
* @param ref current revision to transform.
|
||||||
|
* @throws RepoException if any error happens during the computation of the diff.
|
||||||
|
*/
|
||||||
|
Change<R> change(R ref) throws RepoException, EmptyChangeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the baseline of startRevision. Most of the implementations will use the label to
|
||||||
|
* look for the closest parent with that label, but there might be other kind of implementations
|
||||||
|
* that ignore it.
|
||||||
|
*
|
||||||
|
* <p>If the the label is present in a change multiple times it generally uses the last
|
||||||
|
* appearance.
|
||||||
|
*/
|
||||||
|
default Optional<Baseline<R>> findBaseline(R startRevision, String label)
|
||||||
|
throws RepoException, ValidationException {
|
||||||
|
FindLatestWithLabel<R> visitor = new FindLatestWithLabel<>(startRevision, label);
|
||||||
|
visitChanges(startRevision, visitor);
|
||||||
|
return visitor.getBaseline();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the baseline of the change without using a label. That means that it will use the
|
||||||
|
* specific system information to compute the parent. For example for GH PR, it will return the
|
||||||
|
* baseline submitted SHA-1.
|
||||||
|
*
|
||||||
|
* <p>The order is chronologically reversed. First element is the most recent one. In other
|
||||||
|
* words, the best suitable baseline should be element 0, then 1, etc.
|
||||||
|
*/
|
||||||
|
default ImmutableList<R> findBaselinesWithoutLabel(R startRevision, int limit)
|
||||||
|
throws RepoException, ValidationException {
|
||||||
|
throw new ValidationException("Origin does't support this workflow mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
class FindLatestWithLabel<R extends Revision> implements ChangesVisitor {
|
||||||
|
|
||||||
|
private final R startRevision;
|
||||||
|
private final String label;
|
||||||
|
@Nullable
|
||||||
|
private Baseline<R> baseline;
|
||||||
|
|
||||||
|
public FindLatestWithLabel(R startRevision, String label) {
|
||||||
|
this.startRevision = Preconditions.checkNotNull(startRevision);
|
||||||
|
this.label = Preconditions.checkNotNull(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Baseline<R>> getBaseline() {
|
||||||
|
return Optional.ofNullable(baseline);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public VisitResult visit(Change<? extends Revision> input) {
|
||||||
|
if (input.getRevision().asString().equals(startRevision.asString())) {
|
||||||
|
return VisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
ImmutableMap<String, Collection<String>> labels = input.getLabels().asMap();
|
||||||
|
if (!labels.containsKey(label)) {
|
||||||
|
return VisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
baseline = new Baseline<>(Iterables.getLast(labels.get(label)),
|
||||||
|
(R) input.getRevision());
|
||||||
|
return VisitResult.TERMINATE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility endpoint for accessing and adding feedback data.
|
||||||
|
* @param console
|
||||||
|
*/
|
||||||
|
default Endpoint getFeedbackEndPoint(Console console) throws ValidationException {
|
||||||
|
return Endpoint.NOOP_ENDPOINT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new reader of this origin.
|
||||||
|
*
|
||||||
|
* @param originFiles indicates which files in the origin repository need to be read. Note that
|
||||||
|
* the reader does not necessarily need to remove files after checking them out according to
|
||||||
|
* the glob - that is actually done automatically by the {@link Workflow}. However, some
|
||||||
|
* {@link Origin} implementations may choose to optimize operations on the repo based on the
|
||||||
|
* glob.
|
||||||
|
* @param authoring the authoring object used for constructing the Author objects.
|
||||||
|
* @throws ValidationException if the reader could not be created because of a user error. For
|
||||||
|
* instance, the origin cannot be used with the given {@code originFiles}.
|
||||||
|
*/
|
||||||
|
Reader<R> newReader(Glob originFiles, Authoring authoring) throws ValidationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label name to be used in when creating a commit message in the destination to refer to a
|
||||||
|
* revision. For example "Git-RevId".
|
||||||
|
*/
|
||||||
|
String getLabelName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a baseline pointer in the origin
|
||||||
|
* @param <R>
|
||||||
|
*/
|
||||||
|
class Baseline<R extends Revision> {
|
||||||
|
|
||||||
|
private final String baseline;
|
||||||
|
private final R originRevision;
|
||||||
|
|
||||||
|
public Baseline(String baseline, @Nullable R originRevision) {
|
||||||
|
this.baseline = Preconditions.checkNotNull(baseline);
|
||||||
|
this.originRevision = originRevision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The baseline reference that will be used in the destination.
|
||||||
|
*/
|
||||||
|
public String getBaseline() {
|
||||||
|
return baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to the origin revision where the baseline was found.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public R getOriginRevision() {
|
||||||
|
return originRevision;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("baseline", baseline)
|
||||||
|
.add("originRevision", originRevision)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
223
third_party/copybara/java/com/google/copybara/ReadConfigFromChangeWorkflow.java
vendored
Normal file
223
third_party/copybara/java/com/google/copybara/ReadConfigFromChangeWorkflow.java
vendored
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import static com.google.common.base.Joiner.on;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.copybara.exception.ValidationException.checkCondition;
|
||||||
|
import static java.lang.String.format;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.copybara.Destination.Writer;
|
||||||
|
import com.google.copybara.Origin.Reader;
|
||||||
|
import com.google.copybara.WorkflowRunHelper.ChangeMigrator;
|
||||||
|
import com.google.copybara.config.Config;
|
||||||
|
import com.google.copybara.config.ConfigValidator;
|
||||||
|
import com.google.copybara.config.Migration;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.monitor.EventMonitor.ChangeMigrationFinishedEvent;
|
||||||
|
import com.google.copybara.util.Glob;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An extension of {@link Workflow} that is capable of reloading itself, reading the configuration
|
||||||
|
* from the origin location provided.
|
||||||
|
*
|
||||||
|
* <p>How it works is with the method
|
||||||
|
* {@link Workflow#newRunHelper(Path, Revision, String, Consumer<ChangeMigrationFinishedEvent>)} and
|
||||||
|
* {@link WorkflowRunHelper}. The core implementation returns a regular run helper that always
|
||||||
|
* returns {@code this} for any changes, which means that no config is read and the workflow remains
|
||||||
|
* immutable.
|
||||||
|
*
|
||||||
|
* <p>The service uses this implementation to provide a {@link ReloadingRunHelper} that is capable
|
||||||
|
* of reading the configuration for the current change being migrated, perform some security and
|
||||||
|
* validation checks, and provide a new run helper instance.
|
||||||
|
*/
|
||||||
|
public class ReadConfigFromChangeWorkflow<O extends Revision, D extends Revision>
|
||||||
|
extends Workflow<O, D> {
|
||||||
|
private final Logger logger = Logger.getLogger(this.getClass().getName());
|
||||||
|
|
||||||
|
private final ConfigLoader configLoader;
|
||||||
|
private final ConfigValidator configValidator;
|
||||||
|
|
||||||
|
ReadConfigFromChangeWorkflow(Workflow<O, D> workflow, Options options,
|
||||||
|
ConfigLoader configLoader, ConfigValidator configValidator) {
|
||||||
|
super(
|
||||||
|
workflow.getName(),
|
||||||
|
workflow.getDescription(),
|
||||||
|
workflow.getOrigin(),
|
||||||
|
workflow.getDestination(),
|
||||||
|
workflow.getAuthoring(),
|
||||||
|
workflow.getTransformation(),
|
||||||
|
workflow.getLastRevisionFlag(),
|
||||||
|
workflow.isInitHistory(),
|
||||||
|
options.get(GeneralOptions.class),
|
||||||
|
workflow.getOriginFiles(),
|
||||||
|
workflow.getDestinationFiles(),
|
||||||
|
workflow.getMode(),
|
||||||
|
workflow.getWorkflowOptions(),
|
||||||
|
workflow.getReverseTransformForCheck(),
|
||||||
|
workflow.isAskForConfirmation(),
|
||||||
|
workflow.getMainConfigFile(),
|
||||||
|
workflow.getAllConfigFiles(),
|
||||||
|
workflow.isDryRunMode(),
|
||||||
|
workflow.isCheckLastRevState(),
|
||||||
|
workflow.getAfterMigrationActions(),
|
||||||
|
workflow.getAfterAllMigrationActions(),
|
||||||
|
workflow.getChangeIdentity(),
|
||||||
|
workflow.isSetRevId(),
|
||||||
|
workflow.isSmartPrune(),
|
||||||
|
workflow.isMigrateNoopChanges(),
|
||||||
|
workflow.customRevId(),
|
||||||
|
workflow.isCheckout());
|
||||||
|
this.configLoader = checkNotNull(configLoader, "configLoaderProvider");
|
||||||
|
this.configValidator = checkNotNull(configValidator, "configValidator");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WorkflowRunHelper<O, D> newRunHelper(Path workdir, O resolvedRef,
|
||||||
|
String rawSourceRef, Consumer<ChangeMigrationFinishedEvent> migrationFinishedMonitor)
|
||||||
|
throws ValidationException {
|
||||||
|
Reader<O> reader = this.getOrigin().newReader(this.getOriginFiles(), this.getAuthoring());
|
||||||
|
return new ReloadingRunHelper(
|
||||||
|
this,
|
||||||
|
getName(),
|
||||||
|
workdir,
|
||||||
|
resolvedRef,
|
||||||
|
createWriter(resolvedRef),
|
||||||
|
reader,
|
||||||
|
rawSourceRef,
|
||||||
|
migrationFinishedMonitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link WorkflowRunHelper} that reloads itself based on the change being imported, loading
|
||||||
|
* the configuration from the origin, after performing security and validation checks.
|
||||||
|
*/
|
||||||
|
private class ReloadingRunHelper extends WorkflowRunHelper<O, D> {
|
||||||
|
private final Workflow<O, D> workflow;
|
||||||
|
private final String workflowName;
|
||||||
|
|
||||||
|
private ReloadingRunHelper(
|
||||||
|
Workflow<O, D> workflow,
|
||||||
|
String workflowName,
|
||||||
|
Path workdir,
|
||||||
|
O resolvedRef,
|
||||||
|
Writer<D> writer,
|
||||||
|
Reader<O> originReader,
|
||||||
|
@Nullable String rawSourceRef,
|
||||||
|
Consumer<ChangeMigrationFinishedEvent> migrationFinishedMonitor) {
|
||||||
|
|
||||||
|
super(workflow, workdir, resolvedRef, originReader, writer, rawSourceRef,
|
||||||
|
migrationFinishedMonitor);
|
||||||
|
this.workflow = workflow;
|
||||||
|
this.workflowName = checkNotNull(workflowName, "workflowName");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ChangeMigrator<O, D> getMigratorForChangeAndWriter(Change<?> change, Writer<D> writer)
|
||||||
|
throws ValidationException, RepoException {
|
||||||
|
checkNotNull(change);
|
||||||
|
|
||||||
|
logger.info(format("Loading configuration for change '%s %s'",
|
||||||
|
change.getRef(), change.firstLineMessage()));
|
||||||
|
|
||||||
|
Config config = ReadConfigFromChangeWorkflow.this.configLoader.
|
||||||
|
loadForRevision(getConsole(), change.getRevision());
|
||||||
|
// The service config validator already checks that the configuration matches the registry,
|
||||||
|
// checking that the origin and destination haven't changed.
|
||||||
|
List<String> errors =
|
||||||
|
configValidator
|
||||||
|
.validate(config, workflowName)
|
||||||
|
.getErrors();
|
||||||
|
checkCondition(errors.isEmpty(), "Invalid configuration [ref '%s': %s ]: '%s': \n%s",
|
||||||
|
change.getRef(), configLoader.location(), workflowName, on('\n').join(errors));
|
||||||
|
|
||||||
|
Migration migration = config.getMigration(workflowName);
|
||||||
|
checkCondition(migration instanceof Workflow,
|
||||||
|
"Invalid configuration [ref '%s': %s ]: '%s' is not a workflow", change.getRef(),
|
||||||
|
configLoader.location(), workflowName);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Workflow<O, D> workflowForChange = (Workflow<O, D>) migration;
|
||||||
|
Reader<O> newReader = workflowForChange
|
||||||
|
.getOrigin()
|
||||||
|
.newReader(workflowForChange.getOriginFiles(), workflowForChange.getAuthoring());
|
||||||
|
return new ReloadingChangeMigrator<>(
|
||||||
|
workflow,
|
||||||
|
workflowForChange,
|
||||||
|
getWorkdir(),
|
||||||
|
newReader,
|
||||||
|
writer,
|
||||||
|
getResolvedRef(),
|
||||||
|
rawSourceRef,
|
||||||
|
getMigrationFinishedMonitor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class
|
||||||
|
ReloadingChangeMigrator<O extends Revision, D extends Revision> extends ChangeMigrator<O, D> {
|
||||||
|
|
||||||
|
private final Workflow<O, D> changeWorkflow;
|
||||||
|
|
||||||
|
ReloadingChangeMigrator(Workflow<O, D> headWorkflow, Workflow<O, D> changeWorkflow,
|
||||||
|
Path workdir, Reader<O> reader,
|
||||||
|
Writer<D> writer, O resolvedRef, @Nullable String rawSourceRef,
|
||||||
|
Consumer<ChangeMigrationFinishedEvent> migrationFinishedMonitor) {
|
||||||
|
super(headWorkflow, workdir, reader, writer, resolvedRef, rawSourceRef,
|
||||||
|
migrationFinishedMonitor);
|
||||||
|
this.changeWorkflow = Preconditions.checkNotNull(changeWorkflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Set<String> getConfigFiles() {
|
||||||
|
return changeWorkflow.configPaths();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Glob getOriginFiles() {
|
||||||
|
return changeWorkflow.getOriginFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Glob getDestinationFiles() {
|
||||||
|
return changeWorkflow.getDestinationFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Transformation getTransformation() {
|
||||||
|
return changeWorkflow.getTransformation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected Transformation getReverseTransformForCheck() {
|
||||||
|
return changeWorkflow.getReverseTransformForCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
third_party/copybara/java/com/google/copybara/Revision.java
vendored
Normal file
114
third_party/copybara/java/com/google/copybara/Revision.java
vendored
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableListMultimap;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A revision of {@link Origin}.
|
||||||
|
*
|
||||||
|
* <p>For example, in Git it would be a commit SHA-1.
|
||||||
|
*/
|
||||||
|
public interface Revision {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the timestamp of this revision from the repository, or {@code null} if this repo type
|
||||||
|
* does not support it. This is the {@link Instant} from the UNIX epoch when the revision was
|
||||||
|
* submitted to the source repository.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
ZonedDateTime readTimestamp() throws RepoException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String representation of the revision that can be parsed by {@link Origin#resolve(String)}.
|
||||||
|
*
|
||||||
|
* <p> Unlike {@link #toString()} method, this method is guaranteed to be stable.
|
||||||
|
*/
|
||||||
|
String asString();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If not null, returns a stable name representing the reference from where this {@code Revision}
|
||||||
|
* was created.
|
||||||
|
*
|
||||||
|
* <p>For example if the user passed 'master' in the command line, the {@link #asString()} would
|
||||||
|
* return the SHA-1 and this method would return 'master'. Note that it is a valid response
|
||||||
|
* to return {@link #asString()} here if the implementation chooses to.
|
||||||
|
*/
|
||||||
|
default @Nullable String contextReference() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return any associated label with the revision. Keys are the label name and values are the
|
||||||
|
* content of the label.
|
||||||
|
*
|
||||||
|
* <p>Labels should only be set when the origin knows for sure that the reference is in the
|
||||||
|
* context of the current migration. For example if origin resolves 'master' internally, it
|
||||||
|
* should not put the labels of the commit at HEAD master, since the workflows might choose to
|
||||||
|
* just migrate the affected changes (using origin_files) and not include that reference. This
|
||||||
|
* could potentially make labels from HEAD master for unrelated commits to be used in a
|
||||||
|
* migration.
|
||||||
|
*
|
||||||
|
* <p>On the other hand, a good usage of associated labels would be for a code review system
|
||||||
|
* like Gerrit. The returned reference could have associated labels like the gerrit url for
|
||||||
|
* the change.
|
||||||
|
*/
|
||||||
|
default ImmutableListMultimap<String, String> associatedLabels() {
|
||||||
|
return ImmutableListMultimap.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a specific label
|
||||||
|
*/
|
||||||
|
default ImmutableList<String> associatedLabel(String label) {
|
||||||
|
return associatedLabels().get(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The url of the revision repository .
|
||||||
|
*/
|
||||||
|
@Nullable default String getUrl() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a list of labels it adds new labels without repeating them
|
||||||
|
*/
|
||||||
|
static ImmutableListMultimap<String, String> addNewLabels(
|
||||||
|
ImmutableListMultimap<String, String> existingLabels,
|
||||||
|
ImmutableListMultimap<String, String> newLabels) {
|
||||||
|
ImmutableListMultimap.Builder<String, String> builder = ImmutableListMultimap.builder();
|
||||||
|
builder.putAll(existingLabels);
|
||||||
|
for (Entry<String, Collection<String>> k : newLabels.asMap().entrySet()) {
|
||||||
|
for (String v : k.getValue()) {
|
||||||
|
if (existingLabels.containsEntry(k.getKey(), v)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
builder.put(k.getKey(), v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
32
third_party/copybara/java/com/google/copybara/SkylarkContext.java
vendored
Normal file
32
third_party/copybara/java/com/google/copybara/SkylarkContext.java
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.devtools.build.lib.syntax.Dict;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context object that can be enhanced with Skylark information.
|
||||||
|
*/
|
||||||
|
public interface SkylarkContext<T> {
|
||||||
|
|
||||||
|
/** Create a copy instance with Skylark function parameters. */
|
||||||
|
T withParams(Dict<?, ?> params);
|
||||||
|
|
||||||
|
/** Performs tasks after an {@link com.google.copybara.feedback.Action} finishes. */
|
||||||
|
void onFinish(Object result, SkylarkContext<?> actionContext) throws ValidationException;
|
||||||
|
}
|
491
third_party/copybara/java/com/google/copybara/TransformResult.java
vendored
Normal file
491
third_party/copybara/java/com/google/copybara/TransformResult.java
vendored
Normal file
|
@ -0,0 +1,491 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.copybara.authoring.Author;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.util.DiffUtil.DiffFile;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import javax.annotation.CheckReturnValue;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the final result of a transformation, including metadata and actual code to be
|
||||||
|
* migrated.
|
||||||
|
*/
|
||||||
|
public final class TransformResult {
|
||||||
|
private final Path path;
|
||||||
|
private final Author author;
|
||||||
|
private final ZonedDateTime timestamp;
|
||||||
|
private final String summary;
|
||||||
|
@Nullable
|
||||||
|
private final String baseline;
|
||||||
|
private final boolean askForConfirmation;
|
||||||
|
private final Revision currentRevision;
|
||||||
|
private final Revision requestedRevision;
|
||||||
|
@Nullable
|
||||||
|
private final String changeIdentity;
|
||||||
|
private final String workflowName;
|
||||||
|
@Nullable private final String rawSourceRef;
|
||||||
|
private final Changes changes;
|
||||||
|
private final boolean setRevId;
|
||||||
|
@Nullable private final ImmutableList<DiffFile> affectedFilesForSmartPrune;
|
||||||
|
private final Function<String, Collection<String>> labelFinder;
|
||||||
|
private final String revIdLabel;
|
||||||
|
private final boolean confirmedInOrigin;
|
||||||
|
|
||||||
|
private static ZonedDateTime readTimestampOrCurrentTime(Revision originRef) throws RepoException {
|
||||||
|
ZonedDateTime refTimestamp = originRef.readTimestamp();
|
||||||
|
return (refTimestamp != null) ? refTimestamp : ZonedDateTime.now(ZoneId.systemDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransformResult(
|
||||||
|
Path path,
|
||||||
|
Revision currentRevision,
|
||||||
|
Author author,
|
||||||
|
String summary,
|
||||||
|
Revision requestedRevision,
|
||||||
|
String workflowName,
|
||||||
|
Changes changes,
|
||||||
|
@Nullable String rawSourceRef,
|
||||||
|
boolean setRevId,
|
||||||
|
Function<String, Collection<String>> labelFinder,
|
||||||
|
String revIdLabel)
|
||||||
|
throws RepoException {
|
||||||
|
this(
|
||||||
|
path,
|
||||||
|
currentRevision,
|
||||||
|
author,
|
||||||
|
readTimestampOrCurrentTime(currentRevision),
|
||||||
|
summary,
|
||||||
|
/*baseline=*/ null,
|
||||||
|
/*askForConfirmation=*/ false,
|
||||||
|
requestedRevision,
|
||||||
|
/*changeIdentity=*/ null,
|
||||||
|
workflowName,
|
||||||
|
changes,
|
||||||
|
rawSourceRef,
|
||||||
|
setRevId,
|
||||||
|
/*affectedFilesForSmartPrune=*/ null,
|
||||||
|
labelFinder,
|
||||||
|
revIdLabel,
|
||||||
|
/*confirmedInOrigin=*/ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TransformResult(
|
||||||
|
Path path,
|
||||||
|
Revision currentRevision,
|
||||||
|
Author author,
|
||||||
|
ZonedDateTime timestamp,
|
||||||
|
String summary,
|
||||||
|
@Nullable String baseline,
|
||||||
|
boolean askForConfirmation,
|
||||||
|
Revision requestedRevision,
|
||||||
|
@Nullable String changeIdentity,
|
||||||
|
String workflowName,
|
||||||
|
Changes changes,
|
||||||
|
@Nullable String rawSourceRef,
|
||||||
|
boolean setRevId,
|
||||||
|
@Nullable ImmutableList<DiffFile> affectedFilesForSmartPrune,
|
||||||
|
Function<String, Collection<String>> labelFinder,
|
||||||
|
String revIdLabel,
|
||||||
|
boolean confirmedInOrigin) {
|
||||||
|
this.path = Preconditions.checkNotNull(path);
|
||||||
|
this.currentRevision = Preconditions.checkNotNull(currentRevision);
|
||||||
|
this.author = Preconditions.checkNotNull(author);
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.summary = Preconditions.checkNotNull(summary);
|
||||||
|
this.baseline = baseline;
|
||||||
|
this.askForConfirmation = askForConfirmation;
|
||||||
|
this.requestedRevision = Preconditions.checkNotNull(requestedRevision);
|
||||||
|
this.changeIdentity = changeIdentity;
|
||||||
|
this.workflowName = Preconditions.checkNotNull(workflowName);
|
||||||
|
this.changes = Preconditions.checkNotNull(changes);
|
||||||
|
this.rawSourceRef = rawSourceRef;
|
||||||
|
this.setRevId = setRevId;
|
||||||
|
this.affectedFilesForSmartPrune = affectedFilesForSmartPrune;
|
||||||
|
this.labelFinder = Preconditions.checkNotNull(labelFinder);
|
||||||
|
this.revIdLabel = Preconditions.checkNotNull(revIdLabel);
|
||||||
|
this.confirmedInOrigin = confirmedInOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public TransformResult withBaseline(String newBaseline) {
|
||||||
|
Preconditions.checkNotNull(newBaseline);
|
||||||
|
return new TransformResult(
|
||||||
|
this.path,
|
||||||
|
this.currentRevision,
|
||||||
|
this.author,
|
||||||
|
this.timestamp,
|
||||||
|
this.summary,
|
||||||
|
newBaseline,
|
||||||
|
this.askForConfirmation,
|
||||||
|
this.requestedRevision,
|
||||||
|
this.changeIdentity,
|
||||||
|
this.workflowName,
|
||||||
|
this.changes,
|
||||||
|
this.rawSourceRef,
|
||||||
|
this.setRevId,
|
||||||
|
this.affectedFilesForSmartPrune,
|
||||||
|
this.labelFinder,
|
||||||
|
this.revIdLabel,
|
||||||
|
this.confirmedInOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used internally
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@CheckReturnValue
|
||||||
|
public TransformResult withSummary(String summary) {
|
||||||
|
return new TransformResult(
|
||||||
|
this.path,
|
||||||
|
this.currentRevision,
|
||||||
|
this.author,
|
||||||
|
this.timestamp,
|
||||||
|
summary,
|
||||||
|
this.baseline,
|
||||||
|
this.askForConfirmation,
|
||||||
|
this.requestedRevision,
|
||||||
|
this.changeIdentity,
|
||||||
|
this.workflowName,
|
||||||
|
this.changes,
|
||||||
|
this.rawSourceRef,
|
||||||
|
this.setRevId,
|
||||||
|
this.affectedFilesForSmartPrune,
|
||||||
|
this.labelFinder,
|
||||||
|
this.revIdLabel,
|
||||||
|
this.confirmedInOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public TransformResult withIdentity(String changeIdentity) {
|
||||||
|
return new TransformResult(
|
||||||
|
this.path,
|
||||||
|
this.currentRevision,
|
||||||
|
this.author,
|
||||||
|
this.timestamp,
|
||||||
|
this.summary,
|
||||||
|
this.baseline,
|
||||||
|
this.askForConfirmation,
|
||||||
|
this.requestedRevision,
|
||||||
|
changeIdentity,
|
||||||
|
this.workflowName,
|
||||||
|
this.changes,
|
||||||
|
this.rawSourceRef,
|
||||||
|
setRevId,
|
||||||
|
this.affectedFilesForSmartPrune,
|
||||||
|
this.labelFinder,
|
||||||
|
this.revIdLabel,
|
||||||
|
this.confirmedInOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public TransformResult withAskForConfirmation(boolean askForConfirmation) {
|
||||||
|
return new TransformResult(
|
||||||
|
this.path,
|
||||||
|
this.currentRevision,
|
||||||
|
this.author,
|
||||||
|
this.timestamp,
|
||||||
|
this.summary,
|
||||||
|
this.baseline,
|
||||||
|
askForConfirmation,
|
||||||
|
this.requestedRevision,
|
||||||
|
this.changeIdentity,
|
||||||
|
this.workflowName,
|
||||||
|
this.changes,
|
||||||
|
this.rawSourceRef,
|
||||||
|
this.setRevId,
|
||||||
|
this.affectedFilesForSmartPrune,
|
||||||
|
this.labelFinder,
|
||||||
|
this.revIdLabel,
|
||||||
|
this.confirmedInOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public TransformResult withChanges(Changes changes) {
|
||||||
|
return new TransformResult(
|
||||||
|
this.path,
|
||||||
|
this.currentRevision,
|
||||||
|
this.author,
|
||||||
|
this.timestamp,
|
||||||
|
this.summary,
|
||||||
|
this.baseline,
|
||||||
|
this.askForConfirmation,
|
||||||
|
this.requestedRevision,
|
||||||
|
this.changeIdentity,
|
||||||
|
this.workflowName,
|
||||||
|
changes,
|
||||||
|
this.rawSourceRef,
|
||||||
|
this.setRevId,
|
||||||
|
this.affectedFilesForSmartPrune,
|
||||||
|
this.labelFinder,
|
||||||
|
this.revIdLabel,
|
||||||
|
this.confirmedInOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public TransformResult withSetRevId(boolean setRevId) {
|
||||||
|
return new TransformResult(
|
||||||
|
this.path,
|
||||||
|
this.currentRevision,
|
||||||
|
this.author,
|
||||||
|
this.timestamp,
|
||||||
|
this.summary,
|
||||||
|
this.baseline,
|
||||||
|
this.askForConfirmation,
|
||||||
|
this.requestedRevision,
|
||||||
|
this.changeIdentity,
|
||||||
|
this.workflowName,
|
||||||
|
this.changes,
|
||||||
|
this.rawSourceRef,
|
||||||
|
setRevId,
|
||||||
|
this.affectedFilesForSmartPrune,
|
||||||
|
this.labelFinder,
|
||||||
|
this.revIdLabel,
|
||||||
|
this.confirmedInOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public TransformResult withAffectedFilesForSmartPrune(
|
||||||
|
ImmutableList<DiffFile> affectedFilesForSmartPrune) {
|
||||||
|
Preconditions.checkNotNull(affectedFilesForSmartPrune);
|
||||||
|
return new TransformResult(
|
||||||
|
this.path,
|
||||||
|
this.currentRevision,
|
||||||
|
this.author,
|
||||||
|
this.timestamp,
|
||||||
|
this.summary,
|
||||||
|
this.baseline,
|
||||||
|
this.askForConfirmation,
|
||||||
|
this.requestedRevision,
|
||||||
|
this.changeIdentity,
|
||||||
|
this.workflowName,
|
||||||
|
this.changes,
|
||||||
|
this.rawSourceRef,
|
||||||
|
this.setRevId,
|
||||||
|
affectedFilesForSmartPrune,
|
||||||
|
this.labelFinder,
|
||||||
|
this.revIdLabel,
|
||||||
|
this.confirmedInOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public TransformResult withLabelFinder(Function<String, Collection<String>> labelMapper) {
|
||||||
|
return new TransformResult(
|
||||||
|
this.path,
|
||||||
|
this.currentRevision,
|
||||||
|
this.author,
|
||||||
|
this.timestamp,
|
||||||
|
this.summary,
|
||||||
|
this.baseline,
|
||||||
|
this.askForConfirmation,
|
||||||
|
this.requestedRevision,
|
||||||
|
this.changeIdentity,
|
||||||
|
this.workflowName,
|
||||||
|
this.changes,
|
||||||
|
this.rawSourceRef,
|
||||||
|
this.setRevId,
|
||||||
|
this.affectedFilesForSmartPrune,
|
||||||
|
Preconditions.checkNotNull(labelMapper),
|
||||||
|
this.revIdLabel,
|
||||||
|
this.confirmedInOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public TransformResult withDiffInOrigin(boolean diffInOrigin) {
|
||||||
|
return new TransformResult(
|
||||||
|
this.path,
|
||||||
|
this.currentRevision,
|
||||||
|
this.author,
|
||||||
|
this.timestamp,
|
||||||
|
this.summary,
|
||||||
|
this.baseline,
|
||||||
|
this.askForConfirmation,
|
||||||
|
this.requestedRevision,
|
||||||
|
this.changeIdentity,
|
||||||
|
this.workflowName,
|
||||||
|
this.changes,
|
||||||
|
this.rawSourceRef,
|
||||||
|
this.setRevId,
|
||||||
|
this.affectedFilesForSmartPrune,
|
||||||
|
this.labelFinder,
|
||||||
|
this.revIdLabel,
|
||||||
|
diffInOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directory containing the tree of files to put in destination.
|
||||||
|
*/
|
||||||
|
public Path getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current revision being migrated. In ITERATIVE mode this would change per
|
||||||
|
* migration.
|
||||||
|
*/
|
||||||
|
public Revision getCurrentRevision() {
|
||||||
|
return currentRevision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The revision that the user asked to migrate to. For example in ITERATIVE mode this would be
|
||||||
|
* always the same revision for one Copybara invocation.
|
||||||
|
*
|
||||||
|
* <p>This revision might contain {@link Revision#contextReference()}, labels or other metadata
|
||||||
|
* associated with it.
|
||||||
|
*/
|
||||||
|
public Revision getRequestedRevision() {
|
||||||
|
return requestedRevision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An stable identifier that represents an entity (code review for example) in the origin for
|
||||||
|
* this particular change. For example a workflow location + Gerrit change number.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getChangeIdentity() {
|
||||||
|
return changeIdentity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destination author to be used.
|
||||||
|
*/
|
||||||
|
public Author getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ZonedDateTime} when the code was submitted to the origin repository.
|
||||||
|
*/
|
||||||
|
public ZonedDateTime getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A description of the migrated changes to include in the destination's change description. The
|
||||||
|
* destination may add more boilerplate text or metadata.
|
||||||
|
*/
|
||||||
|
public String getSummary() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destination baseline to be used for updating the code in the destination. If null, the
|
||||||
|
* destination can assume head baseline.
|
||||||
|
*
|
||||||
|
* <p>Destinations supporting non-null baselines are expected to do the equivalent of:
|
||||||
|
* <ul>
|
||||||
|
* <li>Sync to that baseline</li>
|
||||||
|
* <li>Apply/patch the changes on that revision</li>
|
||||||
|
* <li>Sync to head and auto-merge conflicts if possible</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getBaseline() {
|
||||||
|
return baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the destination should ask for confirmation. Some destinations might chose to ignore this
|
||||||
|
* flag either because it doesn't apply to them or because the always ask for confirmation in
|
||||||
|
* certain circumstances.
|
||||||
|
*
|
||||||
|
* <p>But in general, any destination that could do accidental damage to a repository should
|
||||||
|
* not ignore when the value is true.
|
||||||
|
*/
|
||||||
|
public boolean isAskForConfirmation() {
|
||||||
|
return askForConfirmation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the destination will not ask for confirmation. Instead, it will show the diff.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public boolean isConfirmedInOrigin() {
|
||||||
|
return confirmedInOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The workflow name for the migration. Together with the config path it uniquely identifies a
|
||||||
|
* workflow.
|
||||||
|
*/
|
||||||
|
public String getWorkflowName() {
|
||||||
|
return workflowName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the labels from the message.
|
||||||
|
*/
|
||||||
|
public ImmutableList<LabelFinder> findAllLabels() {
|
||||||
|
return ChangeMessage.parseMessage(summary).getLabels();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data about the set of changes that are being migrated.
|
||||||
|
*/
|
||||||
|
public Changes getChanges() {
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference as requested in the CLI if any
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getRawSourceRef() {
|
||||||
|
return rawSourceRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If RevId should be recorded in the destination
|
||||||
|
*/
|
||||||
|
public boolean isSetRevId() {
|
||||||
|
return setRevId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label to use for storing the current migration state
|
||||||
|
*/
|
||||||
|
public String getRevIdLabel() {
|
||||||
|
return revIdLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It not null, the subset of files that Workflow smart_prune detected as really changed.
|
||||||
|
*
|
||||||
|
* <p>Destinations might ignore this field if they don't want to prune the list of affected
|
||||||
|
* files.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public ImmutableList<DiffFile> getAffectedFilesForSmartPrune() {
|
||||||
|
return affectedFilesForSmartPrune;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that returns all the label values that match a name.
|
||||||
|
*/
|
||||||
|
public Function<String, Collection<String>> getLabelFinder() {
|
||||||
|
return labelFinder;
|
||||||
|
}
|
||||||
|
}
|
702
third_party/copybara/java/com/google/copybara/TransformWork.java
vendored
Normal file
702
third_party/copybara/java/com/google/copybara/TransformWork.java
vendored
Normal file
|
@ -0,0 +1,702 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import static com.google.copybara.exception.ValidationException.checkCondition;
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Verify;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableListMultimap;
|
||||||
|
import com.google.common.collect.ImmutableMultimap;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
|
import com.google.copybara.authoring.Author;
|
||||||
|
import com.google.copybara.doc.annotations.DocSignaturePrefix;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.treestate.FileSystemTreeState;
|
||||||
|
import com.google.copybara.treestate.TreeState;
|
||||||
|
import com.google.copybara.util.Glob;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.Param;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkMethod;
|
||||||
|
import com.google.devtools.build.lib.syntax.Dict;
|
||||||
|
import com.google.devtools.build.lib.syntax.EvalException;
|
||||||
|
import com.google.devtools.build.lib.syntax.Sequence;
|
||||||
|
import com.google.devtools.build.lib.syntax.Starlark;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkList;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.PathMatcher;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains information related to an on-going process of repository transformation.
|
||||||
|
*
|
||||||
|
* <p>This object is passed to the user defined functions in Skylark so that they can personalize
|
||||||
|
* the commit message, change the author or in the future run custom transformations.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "TransformWork",
|
||||||
|
category = StarlarkDocumentationCategory.BUILTIN,
|
||||||
|
doc =
|
||||||
|
"Data about the set of changes that are being migrated. "
|
||||||
|
+ "It includes information about changes like: the author to be used for commit, "
|
||||||
|
+ "change message, etc. You receive a TransformWork object as an argument to the <code>"
|
||||||
|
+ "transformations</code> functions used in <code>core.workflow</code>")
|
||||||
|
@DocSignaturePrefix("ctx")
|
||||||
|
public final class TransformWork implements SkylarkContext<TransformWork>, StarlarkValue {
|
||||||
|
|
||||||
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
static final String COPYBARA_CONTEXT_REFERENCE_LABEL = "COPYBARA_CONTEXT_REFERENCE";
|
||||||
|
static final String COPYBARA_LAST_REV = "COPYBARA_LAST_REV";
|
||||||
|
static final String COPYBARA_CURRENT_REV = "COPYBARA_CURRENT_REV";
|
||||||
|
static final String COPYBARA_CURRENT_MESSAGE = "COPYBARA_CURRENT_MESSAGE";
|
||||||
|
static final String COPYBARA_AUTHOR = "COPYBARA_AUTHOR";
|
||||||
|
static final String COPYBARA_CURRENT_MESSAGE_TITLE = "COPYBARA_CURRENT_MESSAGE_TITLE";
|
||||||
|
|
||||||
|
private final Path checkoutDir;
|
||||||
|
private Metadata metadata;
|
||||||
|
private final Changes changes;
|
||||||
|
private final Console console;
|
||||||
|
private final MigrationInfo migrationInfo;
|
||||||
|
private final Revision resolvedReference;
|
||||||
|
private final TreeState treeState;
|
||||||
|
private final boolean insideExplicitTransform;
|
||||||
|
private final boolean ignoreNoop;
|
||||||
|
@Nullable private final Revision lastRev;
|
||||||
|
@Nullable private final Revision currentRev;
|
||||||
|
private TransformWork skylarkTransformWork;
|
||||||
|
private final Dict<?, ?> skylarkTransformParams;
|
||||||
|
private final LazyResourceLoader<Endpoint> originApi;
|
||||||
|
private final LazyResourceLoader<Endpoint> destinationApi;
|
||||||
|
private final ResourceSupplier<DestinationReader> destinationReader;
|
||||||
|
|
||||||
|
|
||||||
|
public TransformWork(Path checkoutDir, Metadata metadata, Changes changes, Console console,
|
||||||
|
MigrationInfo migrationInfo, Revision resolvedReference, boolean ignoreNoop,
|
||||||
|
LazyResourceLoader<Endpoint> originApi, LazyResourceLoader<Endpoint> destinationApi,
|
||||||
|
ResourceSupplier<DestinationReader> destinationReader) {
|
||||||
|
this(
|
||||||
|
checkoutDir,
|
||||||
|
metadata,
|
||||||
|
changes,
|
||||||
|
console,
|
||||||
|
migrationInfo,
|
||||||
|
resolvedReference,
|
||||||
|
new FileSystemTreeState(checkoutDir), /*insideExplicitTransform*/
|
||||||
|
false,
|
||||||
|
/*lastRev=*/ null,
|
||||||
|
/*currentRev=*/ null,
|
||||||
|
Dict.empty(),
|
||||||
|
ignoreNoop,
|
||||||
|
originApi,
|
||||||
|
destinationApi,
|
||||||
|
destinationReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TransformWork(
|
||||||
|
Path checkoutDir,
|
||||||
|
Metadata metadata,
|
||||||
|
Changes changes,
|
||||||
|
Console console,
|
||||||
|
MigrationInfo migrationInfo,
|
||||||
|
Revision resolvedReference,
|
||||||
|
TreeState treeState,
|
||||||
|
boolean insideExplicitTransform,
|
||||||
|
@Nullable Revision lastRev,
|
||||||
|
@Nullable Revision currentRev,
|
||||||
|
Dict<?, ?> skylarkTransformParams,
|
||||||
|
boolean ignoreNoop,
|
||||||
|
LazyResourceLoader<Endpoint> originApi,
|
||||||
|
LazyResourceLoader<Endpoint> destinationApi,
|
||||||
|
ResourceSupplier<DestinationReader> destinationReader) {
|
||||||
|
this.checkoutDir = Preconditions.checkNotNull(checkoutDir);
|
||||||
|
this.metadata = Preconditions.checkNotNull(metadata);
|
||||||
|
this.changes = changes;
|
||||||
|
this.console = console;
|
||||||
|
this.migrationInfo = migrationInfo;
|
||||||
|
this.resolvedReference = Preconditions.checkNotNull(resolvedReference);
|
||||||
|
this.treeState = treeState;
|
||||||
|
this.insideExplicitTransform = insideExplicitTransform;
|
||||||
|
this.lastRev = lastRev;
|
||||||
|
this.currentRev = currentRev;
|
||||||
|
this.skylarkTransformWork = this;
|
||||||
|
this.skylarkTransformParams = skylarkTransformParams;
|
||||||
|
this.ignoreNoop = ignoreNoop;
|
||||||
|
this.originApi = Preconditions.checkNotNull(originApi);
|
||||||
|
this.destinationApi = Preconditions.checkNotNull(destinationApi);
|
||||||
|
this.destinationReader = Preconditions.checkNotNull(destinationReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path containing the repository state to transform. Transformation should be done in-place.
|
||||||
|
*/
|
||||||
|
public Path getCheckoutDir() {
|
||||||
|
return checkoutDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A description of the migrated changes to include in the destination's change description. The
|
||||||
|
* destination may add more boilerplate text or metadata.
|
||||||
|
*/
|
||||||
|
@StarlarkMethod(name = "message", doc = "Message to be used in the change", structField = true)
|
||||||
|
public String getMessage() {
|
||||||
|
return metadata.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "author", doc = "Author to be used in the change", structField = true)
|
||||||
|
public Author getAuthor() {
|
||||||
|
return metadata.getAuthor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "params",
|
||||||
|
doc = "Parameters for the function if created with" + " core.dynamic_transform",
|
||||||
|
structField = true)
|
||||||
|
public Dict<?, ?> getParams() {
|
||||||
|
return skylarkTransformParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "set_message", doc = "Update the message to be used in the change",
|
||||||
|
parameters = { @Param(name = "message", type = String.class)}
|
||||||
|
)
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.metadata = this.metadata.withMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Metadata getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addHiddenLabels(ImmutableMultimap<String, String> hiddenLabels) {
|
||||||
|
this.metadata = metadata.addHiddenLabels(hiddenLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "run",
|
||||||
|
doc =
|
||||||
|
"Run a glob or a transform. For example:<br>"
|
||||||
|
+ "<code>files = ctx.run(glob(['**.java']))</code><br>or<br>"
|
||||||
|
+ "<code>ctx.run(core.move(\"foo\", \"bar\"))</code><br>or<br>",
|
||||||
|
parameters = {
|
||||||
|
@Param(
|
||||||
|
name = "runnable",
|
||||||
|
type = Object.class,
|
||||||
|
doc = "A glob or a transform (Transforms still not implemented)"),
|
||||||
|
})
|
||||||
|
public Object run(Object runnable) throws EvalException, IOException, ValidationException {
|
||||||
|
if (runnable instanceof Glob) {
|
||||||
|
PathMatcher pathMatcher = ((Glob) runnable).relativeTo(checkoutDir);
|
||||||
|
|
||||||
|
try (Stream<Path> stream = Files.walk(checkoutDir)) {
|
||||||
|
return StarlarkList.immutableCopyOf(
|
||||||
|
stream
|
||||||
|
.filter(Files::isRegularFile)
|
||||||
|
.filter(pathMatcher::matches)
|
||||||
|
.map(p -> new CheckoutPath(checkoutDir.relativize(p), checkoutDir))
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
} else if (runnable instanceof Transformation) {
|
||||||
|
// Works like Sequence. We keep always the latest transform work to allow
|
||||||
|
// catching for two sequential replaces.
|
||||||
|
skylarkTransformWork = skylarkTransformWork.withUpdatedTreeState();
|
||||||
|
((Transformation) runnable).transform(skylarkTransformWork);
|
||||||
|
this.updateFrom(skylarkTransformWork);
|
||||||
|
return Starlark.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Starlark.errorf(
|
||||||
|
"Only globs or transforms can be run, but '%s' is of type %s",
|
||||||
|
runnable, runnable.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "new_path",
|
||||||
|
doc = "Create a new path",
|
||||||
|
parameters = {
|
||||||
|
@Param(name = "path", type = String.class, doc = "The string representing the path"),
|
||||||
|
})
|
||||||
|
public CheckoutPath newPath(String path) throws EvalException {
|
||||||
|
return CheckoutPath.createWithCheckoutDir(
|
||||||
|
checkoutDir.getFileSystem().getPath(path), checkoutDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "create_symlink",
|
||||||
|
doc = "Create a symlink",
|
||||||
|
parameters = {
|
||||||
|
@Param(name = "link", type = CheckoutPath.class, doc = "The link path"),
|
||||||
|
@Param(name = "target", type = CheckoutPath.class, doc = "The target path"),
|
||||||
|
})
|
||||||
|
public void createSymlink(CheckoutPath link, CheckoutPath target) throws EvalException {
|
||||||
|
try {
|
||||||
|
Path linkFullPath = asCheckoutPath(link);
|
||||||
|
// Verify target is inside checkout dir
|
||||||
|
asCheckoutPath(target);
|
||||||
|
|
||||||
|
if (Files.exists(linkFullPath)) {
|
||||||
|
throw Starlark.errorf(
|
||||||
|
"'%s' already exist%s",
|
||||||
|
link.getPath(),
|
||||||
|
Files.isDirectory(linkFullPath)
|
||||||
|
? " and is a directory"
|
||||||
|
: Files.isSymbolicLink(linkFullPath)
|
||||||
|
? " and is a symlink"
|
||||||
|
: Files.isRegularFile(linkFullPath)
|
||||||
|
? " and is a regular file"
|
||||||
|
// Shouldn't happen:
|
||||||
|
: " and we don't know what kind of file is");
|
||||||
|
}
|
||||||
|
|
||||||
|
Path relativized = link.getPath().getParent() == null
|
||||||
|
? target.getPath()
|
||||||
|
: link.getPath().getParent().relativize(target.getPath());
|
||||||
|
Files.createDirectories(linkFullPath.getParent());
|
||||||
|
|
||||||
|
// Shouldn't happen.
|
||||||
|
Verify.verify(
|
||||||
|
linkFullPath.getParent().resolve(relativized).normalize().startsWith(checkoutDir),
|
||||||
|
"%s path escapes the checkout dir", relativized);
|
||||||
|
Files.createSymbolicLink(linkFullPath, relativized);
|
||||||
|
} catch (IOException e) {
|
||||||
|
String msg = "Cannot create symlink: " + e.getMessage();
|
||||||
|
logger.atSevere().withCause(e).log(msg);
|
||||||
|
throw Starlark.errorf("%s", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "write_path",
|
||||||
|
doc = "Write an arbitrary string to a path (UTF-8 will be used)",
|
||||||
|
parameters = {
|
||||||
|
@Param(name = "path", type = CheckoutPath.class, doc = "The string representing the path"),
|
||||||
|
@Param(name = "content", type = String.class, doc = "The content of the file"),
|
||||||
|
})
|
||||||
|
public void writePath(CheckoutPath path, String content) throws IOException, EvalException {
|
||||||
|
Path fullPath = asCheckoutPath(path);
|
||||||
|
if (fullPath.getParent() != null) {
|
||||||
|
Files.createDirectories(fullPath.getParent());
|
||||||
|
}
|
||||||
|
Files.write(fullPath, content.getBytes(UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "read_path",
|
||||||
|
doc = "Read the content of path as UTF-8",
|
||||||
|
parameters = {
|
||||||
|
@Param(name = "path", type = CheckoutPath.class, doc = "The string representing the path"),
|
||||||
|
})
|
||||||
|
public String readPath(CheckoutPath path) throws IOException, EvalException {
|
||||||
|
return new String(Files.readAllBytes(asCheckoutPath(path)), UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path asCheckoutPath(CheckoutPath path) throws EvalException {
|
||||||
|
Path normalized = checkoutDir.resolve(path.getPath()).normalize();
|
||||||
|
if (!normalized.startsWith(checkoutDir)) {
|
||||||
|
throw Starlark.errorf("%s is not inside the checkout directory", path);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "add_label",
|
||||||
|
doc = "Add a label to the end of the description",
|
||||||
|
parameters = {
|
||||||
|
@Param(name = "label", type = String.class, doc = "The label to replace"),
|
||||||
|
@Param(name = "value", type = String.class, doc = "The new value for the label"),
|
||||||
|
@Param(name = "separator", type = String.class,
|
||||||
|
doc = "The separator to use for the label", defaultValue = "\"=\""),
|
||||||
|
@Param(name = "hidden", type = Boolean.class,
|
||||||
|
doc = "Don't show the label in the message but only keep it internally",
|
||||||
|
named = true, positional = false, defaultValue = "False"),
|
||||||
|
})
|
||||||
|
public void addLabel(String label, String value, String separator, Boolean hidden) {
|
||||||
|
if (hidden) {
|
||||||
|
addHiddenLabels(ImmutableListMultimap.of(label, value));
|
||||||
|
} else {
|
||||||
|
setMessage(ChangeMessage.parseMessage(getMessage())
|
||||||
|
.withLabel(label, separator, value)
|
||||||
|
.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "add_or_replace_label",
|
||||||
|
doc = "Replace an existing label or add it to the end of the description",
|
||||||
|
parameters = {
|
||||||
|
@Param(name = "label", type = String.class, doc = "The label to replace"),
|
||||||
|
@Param(name = "value", type = String.class, doc = "The new value for the label"),
|
||||||
|
@Param(name = "separator", type = String.class,
|
||||||
|
doc = "The separator to use for the label", defaultValue = "\"=\""),
|
||||||
|
})
|
||||||
|
public void addOrReplaceLabel(String label, String value, String separator) {
|
||||||
|
setMessage(ChangeMessage.parseMessage(getMessage())
|
||||||
|
.withNewOrReplacedLabel(label, separator, value)
|
||||||
|
.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "add_text_before_labels",
|
||||||
|
doc = "Add a text to the description before the labels paragraph",
|
||||||
|
parameters = { @Param(name = "text", type = String.class) })
|
||||||
|
public void addTextBeforeLabels(String text) {
|
||||||
|
ChangeMessage message = ChangeMessage.parseMessage(getMessage());
|
||||||
|
message = message.withText(message.getText() + '\n' + text);
|
||||||
|
setMessage(message.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "replace_label", doc = "Replace a label if it exist in the message",
|
||||||
|
parameters = {
|
||||||
|
@Param(name = "label", type = String.class, doc = "The label to replace"),
|
||||||
|
@Param(name = "value", type = String.class, doc = "The new value for the label"),
|
||||||
|
@Param(name = "separator", type = String.class,
|
||||||
|
doc = "The separator to use for the label", defaultValue = "\"=\""),
|
||||||
|
@Param(name = "whole_message", type = Boolean.class,
|
||||||
|
doc = "By default Copybara only looks in the last paragraph for labels. This flag"
|
||||||
|
+ "make it replace labels in the whole message.", defaultValue = "False"),
|
||||||
|
})
|
||||||
|
public void replaceLabel(String labelName, String value, String separator, Boolean wholeMessage) {
|
||||||
|
setMessage(parseMessage(wholeMessage)
|
||||||
|
.withReplacedLabel(labelName, separator, value)
|
||||||
|
.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "remove_label", doc = "Remove a label from the message if present",
|
||||||
|
parameters = {
|
||||||
|
@Param(name = "label", type = String.class, doc = "The label to delete"),
|
||||||
|
@Param(name = "whole_message", type = Boolean.class,
|
||||||
|
doc = "By default Copybara only looks in the last paragraph for labels. This flag"
|
||||||
|
+ "make it replace labels in the whole message.", defaultValue = "False"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public void removeLabel(String label, Boolean wholeMessage) {
|
||||||
|
setMessage(parseMessage(wholeMessage).withRemovedLabelByName(label).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeLabelWithValue(String label, String value, Boolean wholeMessage) {
|
||||||
|
setMessage(parseMessage(wholeMessage).withRemovedLabelByNameAndValue(label, value).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "now_as_string", doc = "Get current date as a string",
|
||||||
|
parameters = {
|
||||||
|
@Param(name = "format", type = String.class, doc = "The format to use. See:"
|
||||||
|
+ " https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html"
|
||||||
|
+ " for details.",
|
||||||
|
defaultValue = "\"yyyy-MM-dd\""),
|
||||||
|
@Param(name = "zone", doc = "The timezone id to use. "
|
||||||
|
+ "See https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html. By default"
|
||||||
|
+ " UTC ", defaultValue = "\"UTC\"")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public String formatDate(String format, String zone) {
|
||||||
|
return DateTimeFormatter.ofPattern(format).format(ZonedDateTime.now(ZoneId.of(zone)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChangeMessage parseMessage(Boolean wholeMessage) {
|
||||||
|
return wholeMessage
|
||||||
|
? ChangeMessage.parseAllAsLabels(getMessage())
|
||||||
|
: ChangeMessage.parseMessage(getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "find_label", doc = ""
|
||||||
|
+ "Tries to find a label. First it looks at the generated message (IOW labels that might"
|
||||||
|
+ " have been added by previous steps), then looks in all the commit messages being imported"
|
||||||
|
+ " and finally in the resolved reference passed in the CLI.",
|
||||||
|
parameters = { @Param(name = "label", type = String.class) },
|
||||||
|
allowReturnNones = true)
|
||||||
|
@Nullable
|
||||||
|
public String getLabel(String label) {
|
||||||
|
Collection<String> labelValues = findLabelValues(label, /*all=*/false);
|
||||||
|
return labelValues.isEmpty() ? null : Iterables.getLast(labelValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "find_all_labels",
|
||||||
|
doc =
|
||||||
|
"Tries to find all the values for a label. First it looks at the generated message (IOW"
|
||||||
|
+ " labels that might have been added by previous steps), then looks in all the"
|
||||||
|
+ " commit messages being imported and finally in the resolved reference passed in"
|
||||||
|
+ " the CLI.",
|
||||||
|
parameters = {@Param(name = "message", type = String.class)},
|
||||||
|
allowReturnNones = true)
|
||||||
|
public Sequence<String> getAllLabels(String label) {
|
||||||
|
return findLabelValues(label, /*all=*/true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "origin_api",
|
||||||
|
doc =
|
||||||
|
"Returns an api handle for the origin repository. Methods available depend on the origin "
|
||||||
|
+ "type. Use with extreme caution, as external calls can make workflow "
|
||||||
|
+ "non-deterministic and possibly irreversible. Can have side effects in dry-run"
|
||||||
|
+ "mode.")
|
||||||
|
public Endpoint getOriginApi() throws ValidationException, RepoException {
|
||||||
|
return originApi.load(console);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "destination_api",
|
||||||
|
doc =
|
||||||
|
"Returns an api handle for the destination repository. Methods available depend on the "
|
||||||
|
+ "destination type. Use with extreme caution, as external calls can make workflow "
|
||||||
|
+ "non-deterministic and possibly irreversible. Can have side effects in dry-run"
|
||||||
|
+ "mode.")
|
||||||
|
public Endpoint getDestinationApi() throws ValidationException, RepoException {
|
||||||
|
return destinationApi.load(console);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "destination_reader",
|
||||||
|
doc =
|
||||||
|
"Returns a handle to read files from the destination, if supported by the destination.")
|
||||||
|
public DestinationReader getDestinationReader() throws ValidationException, RepoException {
|
||||||
|
return destinationReader.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Sequence<String> findLabelValues(String label, boolean all) {
|
||||||
|
Map<String, ImmutableList<String>> coreLabels = getCoreLabels();
|
||||||
|
if (coreLabels.containsKey(label)) {
|
||||||
|
return StarlarkList.immutableCopyOf(coreLabels.get(label));
|
||||||
|
}
|
||||||
|
ArrayList<String> result = new ArrayList<>();
|
||||||
|
ImmutableList<LabelFinder> msgLabel = getLabelInMessage(label);
|
||||||
|
if (!msgLabel.isEmpty()) {
|
||||||
|
result.addAll(Lists.transform(msgLabel, LabelFinder::getValue));
|
||||||
|
if (!all) {
|
||||||
|
return StarlarkList.immutableCopyOf(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImmutableSet<String> values = metadata.getHiddenLabels().get(label);
|
||||||
|
if (!values.isEmpty()) {
|
||||||
|
if (!all) {
|
||||||
|
return StarlarkList.immutableCopyOf(ImmutableList.of(Iterables.getLast(values)));
|
||||||
|
} else {
|
||||||
|
result.addAll(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find the label in the current changes migrated. We prioritize current
|
||||||
|
// changes over resolvedReference. Since in iterative mode this would be more
|
||||||
|
// specific to the current migration.
|
||||||
|
for (Change<?> change : changes.getCurrent()) {
|
||||||
|
Sequence<String> val = change.getLabelsAllForSkylark().get(label);
|
||||||
|
if (val != null) {
|
||||||
|
result.addAll(val);
|
||||||
|
if (!all) {
|
||||||
|
return StarlarkList.immutableCopyOf(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImmutableList<String> revVal = change.getRevision().associatedLabel(label);
|
||||||
|
if (!revVal.isEmpty()) {
|
||||||
|
result.addAll(revVal);
|
||||||
|
if (!all) {
|
||||||
|
return StarlarkList.immutableCopyOf(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find the label in the resolved reference
|
||||||
|
ImmutableList<String> resolvedRefLabel = resolvedReference.associatedLabels().get(label);
|
||||||
|
if (result.addAll(resolvedRefLabel) && !all) {
|
||||||
|
return StarlarkList.immutableCopyOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return StarlarkList.immutableCopyOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for a label in the current message. We are less strict and look in the whole message.
|
||||||
|
*/
|
||||||
|
private ImmutableList<LabelFinder> getLabelInMessage(String name) {
|
||||||
|
return parseMessage(/*wholeMessage= */true).getLabels().stream()
|
||||||
|
.filter(label -> label.isLabel(name)).collect(ImmutableList.toImmutableList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "set_author", doc = "Update the author to be used in the change",
|
||||||
|
parameters = { @Param(name = "author", type = Author.class) })
|
||||||
|
public void setAuthor(Author author) {
|
||||||
|
this.metadata = this.metadata.withAuthor(author);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "changes", doc = "List of changes that will be migrated",
|
||||||
|
structField = true)
|
||||||
|
public Changes getChanges() {
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(name = "console", doc = "Get an instance of the console to report errors or"
|
||||||
|
+ " warnings", structField = true)
|
||||||
|
public Console getConsole() {
|
||||||
|
return console;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MigrationInfo getMigrationInfo() {
|
||||||
|
return migrationInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Revision getResolvedReference() {
|
||||||
|
return resolvedReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInsideExplicitTransform() {
|
||||||
|
return insideExplicitTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a clone of the transform work but use a different console.
|
||||||
|
*/
|
||||||
|
public TransformWork withConsole(Console newConsole) {
|
||||||
|
return new TransformWork(checkoutDir, metadata, changes, Preconditions.checkNotNull(newConsole),
|
||||||
|
migrationInfo, resolvedReference, treeState, insideExplicitTransform, lastRev,
|
||||||
|
currentRev, skylarkTransformParams, ignoreNoop, originApi, destinationApi,
|
||||||
|
destinationReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link TransformWork} object that contains a new {@link TreeState}
|
||||||
|
* ready to be used by a transform.
|
||||||
|
*/
|
||||||
|
public TransformWork withUpdatedTreeState() {
|
||||||
|
return new TransformWork(checkoutDir, metadata, changes, console,
|
||||||
|
migrationInfo, resolvedReference, treeState.newTreeState(), insideExplicitTransform,
|
||||||
|
lastRev, currentRev, skylarkTransformParams, ignoreNoop, originApi, destinationApi,
|
||||||
|
destinationReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransformWork withParams(Dict<?, ?> params) {
|
||||||
|
Preconditions.checkNotNull(params);
|
||||||
|
return new TransformWork(checkoutDir, metadata, changes, console, migrationInfo,
|
||||||
|
resolvedReference, treeState, insideExplicitTransform, lastRev, currentRev, params,
|
||||||
|
ignoreNoop, originApi, destinationApi, destinationReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public TransformWork withChanges(Changes changes) {
|
||||||
|
Preconditions.checkNotNull(changes);
|
||||||
|
return new TransformWork(checkoutDir, metadata, changes, console, migrationInfo,
|
||||||
|
resolvedReference, treeState, insideExplicitTransform, lastRev, currentRev,
|
||||||
|
skylarkTransformParams, ignoreNoop, originApi, destinationApi, destinationReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public TransformWork withLastRev(@Nullable Revision previousRef) {
|
||||||
|
return new TransformWork(checkoutDir, metadata, changes, console, migrationInfo,
|
||||||
|
resolvedReference, treeState, insideExplicitTransform, previousRef, currentRev,
|
||||||
|
skylarkTransformParams, ignoreNoop, originApi, destinationApi, destinationReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public TransformWork withResolvedReference(Revision resolvedReference) {
|
||||||
|
Preconditions.checkNotNull(resolvedReference);
|
||||||
|
return new TransformWork(checkoutDir, metadata, changes, console, migrationInfo,
|
||||||
|
resolvedReference, treeState, insideExplicitTransform, lastRev, currentRev,
|
||||||
|
skylarkTransformParams, ignoreNoop, originApi, destinationApi, destinationReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransformWork insideExplicitTransform(boolean ignoreNoop) {
|
||||||
|
Preconditions.checkNotNull(resolvedReference);
|
||||||
|
return new TransformWork(checkoutDir, metadata, changes, console, migrationInfo,
|
||||||
|
resolvedReference, treeState, /*insideExplicitTransform=*/true, lastRev, currentRev,
|
||||||
|
skylarkTransformParams, ignoreNoop, originApi, destinationApi, destinationReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <O extends Revision> TransformWork withCurrentRev(Revision currentRev) {
|
||||||
|
Preconditions.checkNotNull(currentRev);
|
||||||
|
return new TransformWork(checkoutDir, metadata, changes, console, migrationInfo,
|
||||||
|
resolvedReference, treeState, insideExplicitTransform, lastRev, currentRev,
|
||||||
|
skylarkTransformParams, ignoreNoop, originApi, destinationApi, destinationReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update mutable state from another worker data.
|
||||||
|
*/
|
||||||
|
public void updateFrom(TransformWork skylarkWork) {
|
||||||
|
metadata = skylarkWork.metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeState getTreeState() {
|
||||||
|
return treeState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getIgnoreNoop() {
|
||||||
|
return ignoreNoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the map of internal core labels.
|
||||||
|
*
|
||||||
|
* <p/> We use a Map instead of Multimap so that we can differentiate the label exists but
|
||||||
|
* we don't have a value.
|
||||||
|
*/
|
||||||
|
private Map<String, ImmutableList<String>> getCoreLabels() {
|
||||||
|
Map<String, ImmutableList<String>> labels = new HashMap<>();
|
||||||
|
String ctxRef = resolvedReference.contextReference();
|
||||||
|
labels.put(COPYBARA_CONTEXT_REFERENCE_LABEL, ctxRef == null
|
||||||
|
? ImmutableList.of()
|
||||||
|
: ImmutableList.of(ctxRef));
|
||||||
|
|
||||||
|
labels.put(COPYBARA_LAST_REV, lastRev == null
|
||||||
|
? ImmutableList.of()
|
||||||
|
// Skip anything after space in the revision, since we might include metadata like
|
||||||
|
// snapshot number, etc. that is subject to change.
|
||||||
|
: ImmutableList.of(lastRev.asString().replaceAll(" .*", "")));
|
||||||
|
|
||||||
|
labels.put(COPYBARA_CURRENT_REV, currentRev == null
|
||||||
|
? ImmutableList.of()
|
||||||
|
// Skip anything after space in the revision, since we might include metadata like
|
||||||
|
// snapshot number, etc. that is subject to change.
|
||||||
|
: ImmutableList.of(
|
||||||
|
currentRev.asString().replaceAll(" .*", "")));
|
||||||
|
|
||||||
|
labels.put(COPYBARA_CURRENT_MESSAGE, ImmutableList.of(getMessage()));
|
||||||
|
labels.put(COPYBARA_AUTHOR, ImmutableList.of(getMessage()));
|
||||||
|
labels.put(COPYBARA_CURRENT_MESSAGE_TITLE,
|
||||||
|
ImmutableList.of(Change.extractFirstLine(metadata.getMessage())));
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish(Object result, SkylarkContext<?> actionContext) throws ValidationException {
|
||||||
|
checkCondition(
|
||||||
|
result == null || result.equals(Starlark.NONE),
|
||||||
|
"Transform work cannot return any result but returned: %s",
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ResourceSupplier<T> {
|
||||||
|
T get() throws ValidationException, RepoException;
|
||||||
|
}
|
||||||
|
}
|
70
third_party/copybara/java/com/google/copybara/Transformation.java
vendored
Normal file
70
third_party/copybara/java/com/google/copybara/Transformation.java
vendored
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.syntax.Location;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/** Interface implemented by all source code transformations. */
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "transformation",
|
||||||
|
doc = "A transformation to the workdir",
|
||||||
|
category = StarlarkDocumentationCategory.TOP_LEVEL_TYPE,
|
||||||
|
documented = false)
|
||||||
|
public interface Transformation extends StarlarkValue {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the files inside the checkout dir specified by {@code work}.
|
||||||
|
*
|
||||||
|
* @throws IOException if an error occur during the access to the files
|
||||||
|
* @throws ValidationException if an error attributable to the user happened
|
||||||
|
*/
|
||||||
|
void transform(TransformWork work) throws IOException, ValidationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a transformation which runs this transformation in reverse.
|
||||||
|
*
|
||||||
|
* @throws NonReversibleValidationException if the transform is not reversible
|
||||||
|
*/
|
||||||
|
Transformation reverse() throws NonReversibleValidationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a high level description of what the transform is doing. Note that this should not be
|
||||||
|
* {@link #toString()} method but something more user friendly.
|
||||||
|
*/
|
||||||
|
String describe();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starlark location of the transformation.
|
||||||
|
*/
|
||||||
|
default Location location() {
|
||||||
|
return Location.BUILTIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean canJoin(Transformation transformation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default Transformation join(Transformation next) {
|
||||||
|
throw new IllegalStateException(String.format(
|
||||||
|
"Unexpected join call for %s and %s", this, next));
|
||||||
|
}
|
||||||
|
}
|
41
third_party/copybara/java/com/google/copybara/Trigger.java
vendored
Normal file
41
third_party/copybara/java/com/google/copybara/Trigger.java
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.syntax.Printer;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
|
||||||
|
/** Starter of feedback migration executions. */
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "trigger",
|
||||||
|
doc = "Starter of feedback migration executions.",
|
||||||
|
category = StarlarkDocumentationCategory.TOP_LEVEL_TYPE,
|
||||||
|
documented = false)
|
||||||
|
public interface Trigger extends StarlarkValue {
|
||||||
|
|
||||||
|
Endpoint getEndpoint();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void repr(Printer printer) {
|
||||||
|
printer.append(toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmutableSetMultimap<String, String> describe();
|
||||||
|
}
|
115
third_party/copybara/java/com/google/copybara/ValidateCmd.java
vendored
Normal file
115
third_party/copybara/java/com/google/copybara/ValidateCmd.java
vendored
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import com.beust.jcommander.Parameters;
|
||||||
|
import com.google.copybara.config.Config;
|
||||||
|
import com.google.copybara.config.ConfigValidator;
|
||||||
|
import com.google.copybara.config.Migration;
|
||||||
|
import com.google.copybara.config.ValidationResult;
|
||||||
|
import com.google.copybara.config.ValidationResult.ValidationMessage;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.util.ExitCode;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that the configuration is correct.
|
||||||
|
*/
|
||||||
|
@Parameters(separators = "=", commandDescription = "Validates that the configuration is correct.")
|
||||||
|
public class ValidateCmd implements CopybaraCmd {
|
||||||
|
|
||||||
|
private final ConfigValidator configValidator;
|
||||||
|
private final ConfigLoaderProvider configLoaderProvider;
|
||||||
|
|
||||||
|
ValidateCmd(ConfigValidator configValidator, Consumer<Migration> migrationRanConsumer,
|
||||||
|
ConfigLoaderProvider configLoaderProvider) {
|
||||||
|
this.configValidator = checkNotNull(configValidator);
|
||||||
|
this.configLoaderProvider = checkNotNull(configLoaderProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExitCode run(CommandEnv commandEnv)
|
||||||
|
throws ValidationException, IOException, RepoException {
|
||||||
|
ConfigFileArgs configFileArgs = commandEnv.parseConfigFileArgs(this, /*useSourceRef*/false);
|
||||||
|
ConfigLoader configLoader =
|
||||||
|
configLoaderProvider.newLoader(
|
||||||
|
configFileArgs.getConfigPath(), configFileArgs.getSourceRef());
|
||||||
|
ValidationResult result =
|
||||||
|
validate(
|
||||||
|
commandEnv.getOptions(),
|
||||||
|
configLoader,
|
||||||
|
configFileArgs.getWorkflowName());
|
||||||
|
|
||||||
|
Console console = commandEnv.getOptions().get(GeneralOptions.class).console();
|
||||||
|
for (ValidationMessage message : result.getAllMessages()) {
|
||||||
|
switch (message.getLevel()) {
|
||||||
|
case WARNING:
|
||||||
|
console.warn(message.getMessage());
|
||||||
|
break;
|
||||||
|
case ERROR:
|
||||||
|
console.error(message.getMessage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result.hasErrors()) {
|
||||||
|
console.errorFmt("Configuration '%s' is invalid.", configLoader.location());
|
||||||
|
return ExitCode.CONFIGURATION_ERROR;
|
||||||
|
}
|
||||||
|
console.infoFmt("Configuration '%s' is valid.", configLoader.location());
|
||||||
|
return ExitCode.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that the configuration is correct and that there is a valid migration specified by
|
||||||
|
* {@code migrationName}.
|
||||||
|
*
|
||||||
|
* <p>Note that, besides validating the specific migration, all the configuration will be
|
||||||
|
* validated syntactically.
|
||||||
|
*
|
||||||
|
* Returns true iff this configuration is valid.
|
||||||
|
*/
|
||||||
|
private ValidationResult validate(Options options, ConfigLoader configLoader,
|
||||||
|
String migrationName)
|
||||||
|
throws IOException {
|
||||||
|
Console console = options.get(GeneralOptions.class).console();
|
||||||
|
ValidationResult.Builder resultBuilder = new ValidationResult.Builder();
|
||||||
|
try {
|
||||||
|
Config config = configLoader.load(console);
|
||||||
|
resultBuilder.append(configValidator.validate(config, migrationName));
|
||||||
|
} catch (ValidationException e) {
|
||||||
|
// The validate subcommand should not throw Validation exceptions but log a result
|
||||||
|
StringBuilder error = new StringBuilder(e.getMessage()).append("\n");
|
||||||
|
Throwable cause = e.getCause();
|
||||||
|
while (cause != null) {
|
||||||
|
error.append(" CAUSED BY: ").append(cause.getMessage()).append("\n");
|
||||||
|
cause = cause.getCause();
|
||||||
|
}
|
||||||
|
resultBuilder.error(error.toString());
|
||||||
|
}
|
||||||
|
return resultBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "validate";
|
||||||
|
}
|
||||||
|
}
|
84
third_party/copybara/java/com/google/copybara/ValidateDestinationFilesVisitor.java
vendored
Normal file
84
third_party/copybara/java/com/google/copybara/ValidateDestinationFilesVisitor.java
vendored
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
|
import com.google.copybara.exception.NotADestinationFileException;
|
||||||
|
import com.google.copybara.util.Glob;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileVisitResult;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.PathMatcher;
|
||||||
|
import java.nio.file.SimpleFileVisitor;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visitor that verifies that all files in a checkout dir match the {@code destination_files} glob.
|
||||||
|
*/
|
||||||
|
class ValidateDestinationFilesVisitor extends SimpleFileVisitor<Path> {
|
||||||
|
|
||||||
|
private final PathMatcher destinationFiles;
|
||||||
|
private final Path checkoutDir;
|
||||||
|
private ArrayList<Path> invalidPaths;
|
||||||
|
|
||||||
|
ValidateDestinationFilesVisitor(Glob destinationFiles, Path checkoutDir) {
|
||||||
|
this.destinationFiles = destinationFiles.relativeTo(checkoutDir);
|
||||||
|
this.checkoutDir = checkNotNull(checkoutDir, "checkoutDir");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
|
||||||
|
if (!destinationFiles.matches(file)) {
|
||||||
|
invalidPaths.add(checkoutDir.relativize(file));
|
||||||
|
}
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that all paths in the checkout dir match {@code destinationFiles}. If any one does not,
|
||||||
|
* it throws an exception which indicates that the {@code destination_files} setting in the config
|
||||||
|
* is incorrect.
|
||||||
|
*
|
||||||
|
* <p>This can be used to verify that a file that is being written to the destination is actually
|
||||||
|
* intended to be written by the user. Also note that for some {@link Destination}
|
||||||
|
* implementations, a {@code destination_files} that doesn't match some destination paths will
|
||||||
|
* not be able to delete the path later if a commit removes it. This is because the
|
||||||
|
* {@code destination_files} glob is often used to determine if a path not in the origin should be
|
||||||
|
* preserved.
|
||||||
|
*
|
||||||
|
* @throws NotADestinationFileException if the given path does not match the {@link PathMatcher}
|
||||||
|
*/
|
||||||
|
void verifyFilesToWrite() throws NotADestinationFileException, IOException {
|
||||||
|
checkState(invalidPaths == null,
|
||||||
|
"This method already ran with the following results: %s", invalidPaths);
|
||||||
|
this.invalidPaths = new ArrayList<>();
|
||||||
|
Files.walkFileTree(checkoutDir, this);
|
||||||
|
|
||||||
|
if (!invalidPaths.isEmpty()) {
|
||||||
|
Collections.sort(invalidPaths);
|
||||||
|
throw new NotADestinationFileException(String.format(
|
||||||
|
"Attempted to write these files in the destination, but they are not covered by "
|
||||||
|
+ "destination_files: %s.\nYour destination_files are %s.",
|
||||||
|
invalidPaths, destinationFiles));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
646
third_party/copybara/java/com/google/copybara/Workflow.java
vendored
Normal file
646
third_party/copybara/java/com/google/copybara/Workflow.java
vendored
Normal file
|
@ -0,0 +1,646 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import static com.google.copybara.LazyResourceLoader.memoized;
|
||||||
|
import static com.google.copybara.WorkflowMode.CHANGE_REQUEST;
|
||||||
|
import static com.google.copybara.exception.ValidationException.checkCondition;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
|
import com.google.copybara.Destination.DestinationStatus;
|
||||||
|
import com.google.copybara.Destination.Writer;
|
||||||
|
import com.google.copybara.Info.MigrationReference;
|
||||||
|
import com.google.copybara.Origin.Reader;
|
||||||
|
import com.google.copybara.Origin.Reader.ChangesResponse;
|
||||||
|
import com.google.copybara.authoring.Authoring;
|
||||||
|
import com.google.copybara.config.ConfigFile;
|
||||||
|
import com.google.copybara.config.Migration;
|
||||||
|
import com.google.copybara.exception.CommandLineException;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.feedback.Action;
|
||||||
|
import com.google.copybara.feedback.FinishHookContext;
|
||||||
|
import com.google.copybara.monitor.EventMonitor;
|
||||||
|
import com.google.copybara.monitor.EventMonitor.ChangeMigrationFinishedEvent;
|
||||||
|
import com.google.copybara.profiler.Profiler;
|
||||||
|
import com.google.copybara.profiler.Profiler.ProfilerTask;
|
||||||
|
import com.google.copybara.templatetoken.Token;
|
||||||
|
import com.google.copybara.templatetoken.Token.TokenType;
|
||||||
|
import com.google.copybara.transform.SkylarkConsole;
|
||||||
|
import com.google.copybara.util.Glob;
|
||||||
|
import com.google.copybara.util.Identity;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a particular migration operation that can occur for a project. Each project can have
|
||||||
|
* multiple workflows. Each workflow has a particular origin and destination.
|
||||||
|
* @param <O> Origin revision type.
|
||||||
|
* @param <D> Destination revision type.
|
||||||
|
*/
|
||||||
|
public class Workflow<O extends Revision, D extends Revision> implements Migration {
|
||||||
|
|
||||||
|
private final Logger logger = Logger.getLogger(this.getClass().getName());
|
||||||
|
|
||||||
|
static final String COPYBARA_CONFIG_PATH_IDENTITY_VAR = "copybara_config_path";
|
||||||
|
static final String COPYBARA_WORKFLOW_NAME_IDENTITY_VAR = "copybara_workflow_name";
|
||||||
|
static final String COPYBARA_REFERENCE_IDENTITY_VAR = "copybara_reference";
|
||||||
|
static final String COPYBARA_REFERENCE_LABEL_VAR = "label:";
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
@Nullable private final String description;
|
||||||
|
private final Origin<O> origin;
|
||||||
|
private final Destination<D> destination;
|
||||||
|
private final Authoring authoring;
|
||||||
|
private final Transformation transformation;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final String lastRevisionFlag;
|
||||||
|
private final boolean initHistoryFlag;
|
||||||
|
private final Console console;
|
||||||
|
private final GeneralOptions generalOptions;
|
||||||
|
private final Glob originFiles;
|
||||||
|
private final Glob destinationFiles;
|
||||||
|
private final WorkflowMode mode;
|
||||||
|
private final WorkflowOptions workflowOptions;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final Transformation reverseTransformForCheck;
|
||||||
|
private final boolean verbose;
|
||||||
|
private final boolean askForConfirmation;
|
||||||
|
private final boolean force;
|
||||||
|
private final ConfigFile mainConfigFile;
|
||||||
|
private final Supplier<ImmutableMap<String, ConfigFile>> allConfigFiles;
|
||||||
|
private final boolean effectiveDryRunMode;
|
||||||
|
private final boolean dryRunModeField;
|
||||||
|
private final ImmutableList<Action> afterMigrationActions;
|
||||||
|
private final ImmutableList<Token> changeIdentity;
|
||||||
|
private final boolean setRevId;
|
||||||
|
private final boolean smartPrune;
|
||||||
|
private final boolean migrateNoopChanges;
|
||||||
|
private final boolean checkLastRevState;
|
||||||
|
private final ImmutableList<Action> afterAllMigrationActions;
|
||||||
|
@Nullable private final String customRevId;
|
||||||
|
private final boolean checkout;
|
||||||
|
|
||||||
|
public Workflow(
|
||||||
|
String name,
|
||||||
|
@Nullable String description,
|
||||||
|
Origin<O> origin,
|
||||||
|
Destination<D> destination,
|
||||||
|
Authoring authoring,
|
||||||
|
Transformation transformation,
|
||||||
|
@Nullable String lastRevisionFlag,
|
||||||
|
boolean initHistoryFlag,
|
||||||
|
GeneralOptions generalOptions,
|
||||||
|
Glob originFiles,
|
||||||
|
Glob destinationFiles,
|
||||||
|
WorkflowMode mode,
|
||||||
|
WorkflowOptions workflowOptions,
|
||||||
|
@Nullable Transformation reverseTransformForCheck,
|
||||||
|
boolean askForConfirmation,
|
||||||
|
ConfigFile mainConfigFile,
|
||||||
|
Supplier<ImmutableMap<String, ConfigFile>> allConfigFiles,
|
||||||
|
boolean dryRunModeField,
|
||||||
|
boolean checkLastRevState,
|
||||||
|
ImmutableList<Action> afterMigrationActions,
|
||||||
|
ImmutableList<Action> afterAllMigrationActions,
|
||||||
|
ImmutableList<Token> changeIdentity,
|
||||||
|
boolean setRevId,
|
||||||
|
boolean smartPrune,
|
||||||
|
boolean migrateNoopChanges,
|
||||||
|
@Nullable String customRevId,
|
||||||
|
boolean checkout) {
|
||||||
|
this.name = Preconditions.checkNotNull(name);
|
||||||
|
this.description = description;
|
||||||
|
this.origin = Preconditions.checkNotNull(origin);
|
||||||
|
this.destination = Preconditions.checkNotNull(destination);
|
||||||
|
this.authoring = Preconditions.checkNotNull(authoring);
|
||||||
|
this.transformation = Preconditions.checkNotNull(transformation);
|
||||||
|
this.lastRevisionFlag = lastRevisionFlag;
|
||||||
|
this.initHistoryFlag = initHistoryFlag;
|
||||||
|
this.console = Preconditions.checkNotNull(generalOptions.console());
|
||||||
|
this.generalOptions = generalOptions;
|
||||||
|
this.originFiles = Preconditions.checkNotNull(originFiles);
|
||||||
|
this.destinationFiles = Preconditions.checkNotNull(destinationFiles);
|
||||||
|
this.mode = Preconditions.checkNotNull(mode);
|
||||||
|
this.workflowOptions = Preconditions.checkNotNull(workflowOptions);
|
||||||
|
this.reverseTransformForCheck = reverseTransformForCheck;
|
||||||
|
this.verbose = generalOptions.isVerbose();
|
||||||
|
this.askForConfirmation = askForConfirmation;
|
||||||
|
this.force = generalOptions.isForced();
|
||||||
|
this.mainConfigFile = Preconditions.checkNotNull(mainConfigFile);
|
||||||
|
this.allConfigFiles = allConfigFiles;
|
||||||
|
this.checkLastRevState = checkLastRevState;
|
||||||
|
this.customRevId = customRevId;
|
||||||
|
this.checkout = checkout;
|
||||||
|
this.effectiveDryRunMode = dryRunModeField || generalOptions.dryRunMode;
|
||||||
|
this.dryRunModeField = dryRunModeField;
|
||||||
|
this.afterMigrationActions = Preconditions.checkNotNull(afterMigrationActions);
|
||||||
|
this.afterAllMigrationActions = Preconditions.checkNotNull(afterAllMigrationActions);
|
||||||
|
this.changeIdentity = Preconditions.checkNotNull(changeIdentity);
|
||||||
|
this.setRevId = setRevId;
|
||||||
|
this.smartPrune = smartPrune;
|
||||||
|
this.migrateNoopChanges = migrateNoopChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The repository that represents the source of truth
|
||||||
|
*/
|
||||||
|
public Origin<O> getOrigin() {
|
||||||
|
return origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The destination repository to copy to.
|
||||||
|
*/
|
||||||
|
public Destination<D> getDestination() {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The author mapping between an origin and a destination
|
||||||
|
*/
|
||||||
|
public Authoring getAuthoring() {
|
||||||
|
return authoring;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformation to run before writing them to the destination.
|
||||||
|
*/
|
||||||
|
public Transformation getTransformation() {
|
||||||
|
return transformation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAskForConfirmation() {
|
||||||
|
return askForConfirmation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes only the fields that are part of the configuration: Console is not part of the config,
|
||||||
|
* configName is in the parent, and lastRevisionFlag is a command-line flag.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("name", name)
|
||||||
|
.add("origin", origin)
|
||||||
|
.add("destination", destination)
|
||||||
|
.add("authoring", authoring)
|
||||||
|
.add("transformation", transformation)
|
||||||
|
.add("originFiles", originFiles)
|
||||||
|
.add("destinationFiles", destinationFiles)
|
||||||
|
.add("mode", mode)
|
||||||
|
.add("reverseTransformForCheck", reverseTransformForCheck)
|
||||||
|
.add("askForConfirmation", askForConfirmation)
|
||||||
|
.add("checkLastRevState", checkLastRevState)
|
||||||
|
.add("afterMigrationActions", afterMigrationActions)
|
||||||
|
.add("changeIdentity", changeIdentity)
|
||||||
|
.add("setRevId", setRevId)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(Path workdir, ImmutableList<String> sourceRefs)
|
||||||
|
throws RepoException, IOException, ValidationException {
|
||||||
|
if (sourceRefs.size() > 1) {
|
||||||
|
throw new CommandLineException(
|
||||||
|
String.format(
|
||||||
|
"Workflow does not support multiple source_ref arguments yet: %s",
|
||||||
|
ImmutableList.copyOf(sourceRefs)));
|
||||||
|
}
|
||||||
|
@Nullable
|
||||||
|
String sourceRef = sourceRefs.size() == 1 ? sourceRefs.get(0) : null;
|
||||||
|
|
||||||
|
validateFlags();
|
||||||
|
try (ProfilerTask ignore = profiler().start("run/" + name)) {
|
||||||
|
console.progress("Getting last revision: "
|
||||||
|
+ "Resolving " + ((sourceRef == null) ? "origin reference" : sourceRef));
|
||||||
|
O resolvedRef = generalOptions.repoTask("origin.resolve_source_ref",
|
||||||
|
() -> origin.resolve(sourceRef));
|
||||||
|
|
||||||
|
logger.log(Level.INFO, String.format(
|
||||||
|
"Running Copybara for workflow '%s' and ref '%s': %s",
|
||||||
|
name, resolvedRef.asString(),
|
||||||
|
this.toString()));
|
||||||
|
logger.log(Level.INFO, String.format("Using working directory : %s", workdir));
|
||||||
|
ImmutableList.Builder<DestinationEffect> allEffects = ImmutableList.builder();
|
||||||
|
WorkflowRunHelper<O, D> helper = newRunHelper(workdir, resolvedRef, sourceRef,
|
||||||
|
event -> {
|
||||||
|
allEffects.addAll(event.getDestinationEffects());
|
||||||
|
eventMonitor().onChangeMigrationFinished(event);
|
||||||
|
});
|
||||||
|
try (ProfilerTask ignored = profiler().start(mode.toString().toLowerCase())) {
|
||||||
|
mode.run(helper);
|
||||||
|
} finally {
|
||||||
|
if (!getGeneralOptions().dryRunMode) {
|
||||||
|
try (ProfilerTask ignored = profiler().start("after_all_migration")) {
|
||||||
|
ImmutableList<DestinationEffect> effects = allEffects.build();
|
||||||
|
ImmutableList<DestinationEffect> resultEffects = runHooks(
|
||||||
|
effects,
|
||||||
|
getAfterAllMigrationActions(),
|
||||||
|
// Only do this once for all the actions
|
||||||
|
memoized(c -> helper.getOriginReader().getFeedbackEndPoint(c)),
|
||||||
|
// Only do this once for all the actions
|
||||||
|
memoized(c -> helper.getDestinationWriter().getFeedbackEndPoint(c)),
|
||||||
|
resolvedRef);
|
||||||
|
if (effects.size() != resultEffects.size()) {
|
||||||
|
console.warn("Effects where created in after_all_migrations, but they are ignored.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates if flags are compatible with this workflow.
|
||||||
|
*
|
||||||
|
* @throws ValidationException if flags are invalid for this workflow
|
||||||
|
*/
|
||||||
|
private void validateFlags() throws ValidationException {
|
||||||
|
checkCondition(!isInitHistory() || mode != CHANGE_REQUEST,
|
||||||
|
"%s is not compatible with %s",
|
||||||
|
WorkflowOptions.INIT_HISTORY_FLAG, CHANGE_REQUEST);
|
||||||
|
checkCondition(!isCheckLastRevState() || mode != CHANGE_REQUEST,
|
||||||
|
"%s is not compatible with %s",
|
||||||
|
WorkflowOptions.CHECK_LAST_REV_STATE, CHANGE_REQUEST);
|
||||||
|
checkCondition(
|
||||||
|
!isSmartPrune() || mode == CHANGE_REQUEST,
|
||||||
|
"'smart_prune = True' is only supported for CHANGE_REQUEST mode.");
|
||||||
|
if (isSetRevId()) {
|
||||||
|
checkCondition(mode != CHANGE_REQUEST || customRevId == null,
|
||||||
|
"experimental_custom_rev_id is not allowed to be used in CHANGE_REQUEST mode if"
|
||||||
|
+ " set_rev_id is set to true. experimental_custom_rev_id is used for looking"
|
||||||
|
+ " for the baseline in the origin. No revId is stored in the destination.");
|
||||||
|
} else {
|
||||||
|
checkCondition(mode == CHANGE_REQUEST, "'set_rev_id = False' is only supported"
|
||||||
|
+ " for CHANGE_REQUEST mode.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected WorkflowRunHelper<O, D> newRunHelper(Path workdir, O resolvedRef, String rawSourceRef,
|
||||||
|
Consumer<ChangeMigrationFinishedEvent> migrationFinishedMonitor)
|
||||||
|
throws ValidationException {
|
||||||
|
|
||||||
|
Reader<O> reader = getOrigin()
|
||||||
|
.newReader(getOriginFiles(), getAuthoring());
|
||||||
|
return new WorkflowRunHelper<>(
|
||||||
|
this, workdir, resolvedRef, reader, createWriter(resolvedRef), rawSourceRef,
|
||||||
|
migrationFinishedMonitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the config files relative to their roots. For example a config file like 'admin/foo/bar'
|
||||||
|
* with a root 'admin' would return 'foo/bar'.
|
||||||
|
*/
|
||||||
|
Set<String> configPaths() {
|
||||||
|
return allConfigFiles.get().values().stream()
|
||||||
|
.map(ConfigFile::getIdentifier)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Info<? extends Revision> getInfo() throws RepoException, ValidationException {
|
||||||
|
return generalOptions.repoTask(
|
||||||
|
"info",
|
||||||
|
(Callable<Info<? extends Revision>>)
|
||||||
|
() -> {
|
||||||
|
O lastResolved =
|
||||||
|
generalOptions.repoTask(
|
||||||
|
"origin.last_resolved", () -> origin.resolve(/* reference= */ null));
|
||||||
|
|
||||||
|
Reader<O> oReader = origin.newReader(originFiles, authoring);
|
||||||
|
DestinationStatus destinationStatus =
|
||||||
|
generalOptions.repoTask("destination.previous_ref", () -> getDestinationStatus(lastResolved));
|
||||||
|
|
||||||
|
O lastMigrated =
|
||||||
|
generalOptions.repoTask(
|
||||||
|
"origin.last_migrated",
|
||||||
|
() ->
|
||||||
|
(destinationStatus == null)
|
||||||
|
? null
|
||||||
|
: origin.resolve(destinationStatus.getBaseline()));
|
||||||
|
|
||||||
|
ImmutableList<Change<O>> allChanges =
|
||||||
|
generalOptions.repoTask(
|
||||||
|
"origin.changes",
|
||||||
|
() -> {
|
||||||
|
ChangesResponse<O> changes = oReader.changes(lastMigrated, lastResolved);
|
||||||
|
return changes.isEmpty()
|
||||||
|
? ImmutableList.of()
|
||||||
|
: ImmutableList.copyOf(changes.getChanges());
|
||||||
|
});
|
||||||
|
WorkflowRunHelper<O, D> helper = newRunHelper(
|
||||||
|
// We shouldn't use this path for info
|
||||||
|
Paths.get("shouldnt_be_used"),
|
||||||
|
lastResolved, /*rawSourceRef=*/null,
|
||||||
|
// We don't create effects on info
|
||||||
|
changeMigrationFinishedEvent -> {});
|
||||||
|
|
||||||
|
List<Change<O>> affectedChanges = new ArrayList<>();
|
||||||
|
for (Change<O> change : allChanges) {
|
||||||
|
if (helper.getMigratorForChange(change).shouldSkipChange(change)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
affectedChanges.add(change);
|
||||||
|
}
|
||||||
|
MigrationReference<O> migrationRef = MigrationReference.create(
|
||||||
|
String.format("workflow_%s", name),
|
||||||
|
lastMigrated,
|
||||||
|
affectedChanges);
|
||||||
|
|
||||||
|
return Info.create(
|
||||||
|
getOriginDescription(),
|
||||||
|
getDestinationDescription(),
|
||||||
|
ImmutableList.of(migrationRef));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private DestinationStatus getDestinationStatus(O revision)
|
||||||
|
throws RepoException, ValidationException {
|
||||||
|
if (getLastRevisionFlag() != null) {
|
||||||
|
return new DestinationStatus(getLastRevisionFlag(), ImmutableList.of());
|
||||||
|
}
|
||||||
|
return createDryRunWriter(revision)
|
||||||
|
.getDestinationStatus(getDestinationFiles(), getRevIdLabel());
|
||||||
|
}
|
||||||
|
|
||||||
|
String getRevIdLabel() {
|
||||||
|
return customRevId != null ? customRevId : origin.getLabelName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a writer that respects the effectiveDryRunMode value */
|
||||||
|
Writer<D> createWriter(O revision) throws ValidationException {
|
||||||
|
return destination.newWriter(new WriterContext(
|
||||||
|
name,
|
||||||
|
workflowOptions.workflowIdentityUser,
|
||||||
|
effectiveDryRunMode,
|
||||||
|
revision,
|
||||||
|
destinationFiles.roots()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a writer in dry-run mode */
|
||||||
|
Writer<D> createDryRunWriter(O revision) throws ValidationException {
|
||||||
|
return destination.newWriter(new WriterContext(
|
||||||
|
name, workflowOptions.workflowIdentityUser,
|
||||||
|
/*dryRun=*/true,
|
||||||
|
revision,
|
||||||
|
destinationFiles.roots()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableSetMultimap<String, String> getOriginDescription() {
|
||||||
|
return origin.describe(originFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableSetMultimap<String, String> getDestinationDescription() {
|
||||||
|
return destination.describe(destinationFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Glob getOriginFiles() {
|
||||||
|
return originFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Glob getDestinationFiles() {
|
||||||
|
return destinationFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Console getConsole() {
|
||||||
|
return console;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorkflowOptions getWorkflowOptions() {
|
||||||
|
return workflowOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isForce() {
|
||||||
|
return force;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@VisibleForTesting
|
||||||
|
public Transformation getReverseTransformForCheck() {
|
||||||
|
return reverseTransformForCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVerbose() {
|
||||||
|
return verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getLastRevisionFlag() {
|
||||||
|
return lastRevisionFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isInitHistory() {
|
||||||
|
return initHistoryFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorkflowMode getMode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getModeString() {
|
||||||
|
return mode.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isCheckLastRevState() {
|
||||||
|
return checkLastRevState;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isDryRunMode() {
|
||||||
|
return effectiveDryRunMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDryRunModeField() {
|
||||||
|
return dryRunModeField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCheckout() {
|
||||||
|
return checkout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration identity tries to create a stable identifier for the migration that is stable between
|
||||||
|
* Copybara invocations for the same reference. For example it will contain the copy.bara.sky
|
||||||
|
* config file location relative to the root, the workflow name or the context reference used in
|
||||||
|
* the request.
|
||||||
|
*
|
||||||
|
* <p>This identifier can be used by destinations to reuse code reviews, etc.
|
||||||
|
*/
|
||||||
|
String getMigrationIdentity(Revision requestedRevision, TransformWork transformWork) {
|
||||||
|
boolean contextRefDefined = requestedRevision.contextReference() != null;
|
||||||
|
// In iterative mode we want to use the revision, since we could have an export from
|
||||||
|
// git.origin(master) -> git.gerrit_destination. In that case we want to create one change
|
||||||
|
// per origin commit. We are loosing some destination change reuse on cases like rebase (for
|
||||||
|
// example git.github_pr_origin -> gerrit. But we can fix this kind of issues in the future
|
||||||
|
// if we want to support it (for example with a custom identity using labels).
|
||||||
|
String ctxRef = contextRefDefined && mode != WorkflowMode.ITERATIVE
|
||||||
|
? requestedRevision.contextReference()
|
||||||
|
: requestedRevision.asString();
|
||||||
|
if (changeIdentity.isEmpty()) {
|
||||||
|
return Identity.computeIdentity(
|
||||||
|
"ChangeIdentity",
|
||||||
|
ctxRef,
|
||||||
|
this.name,
|
||||||
|
mainConfigFile.getIdentifier(),
|
||||||
|
workflowOptions.workflowIdentityUser);
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Token token : changeIdentity) {
|
||||||
|
if (token.getType().equals(TokenType.LITERAL)) {
|
||||||
|
sb.append(token.getValue());
|
||||||
|
} else if (token.getValue().equals(COPYBARA_CONFIG_PATH_IDENTITY_VAR)) {
|
||||||
|
sb.append(mainConfigFile.getIdentifier());
|
||||||
|
} else if (token.getValue().equals(COPYBARA_WORKFLOW_NAME_IDENTITY_VAR)) {
|
||||||
|
sb.append(this.name);
|
||||||
|
} else if (token.getValue().equals(COPYBARA_REFERENCE_IDENTITY_VAR)) {
|
||||||
|
sb.append(ctxRef);
|
||||||
|
} else if (token.getValue().startsWith(COPYBARA_REFERENCE_LABEL_VAR)) {
|
||||||
|
String label = token.getValue().substring(COPYBARA_REFERENCE_LABEL_VAR.length());
|
||||||
|
String labelValue = transformWork.getLabel(label);
|
||||||
|
if (labelValue == null) {
|
||||||
|
console.warn(String.format(
|
||||||
|
"Couldn't find label '%s'. Using the default identity algorithm", label));
|
||||||
|
return Identity.computeIdentity(
|
||||||
|
"ChangeIdentity",
|
||||||
|
ctxRef,
|
||||||
|
this.name,
|
||||||
|
mainConfigFile.getIdentifier(),
|
||||||
|
workflowOptions.workflowIdentityUser);
|
||||||
|
}
|
||||||
|
sb.append(labelValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Identity.hashIdentity(
|
||||||
|
MoreObjects.toStringHelper("custom_identity").add("text", sb.toString()),
|
||||||
|
workflowOptions.workflowIdentityUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFile getMainConfigFile() {
|
||||||
|
return mainConfigFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Profiler profiler() {
|
||||||
|
return generalOptions.profiler();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventMonitor eventMonitor() {
|
||||||
|
return generalOptions.eventMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
|
Supplier<ImmutableMap<String, ConfigFile>> getAllConfigFiles() {
|
||||||
|
return allConfigFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeneralOptions getGeneralOptions() {
|
||||||
|
return generalOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableList<Action> getAfterMigrationActions() {
|
||||||
|
return afterMigrationActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableList<Action> getAfterAllMigrationActions() {
|
||||||
|
return afterAllMigrationActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmutableList<DestinationEffect> runHooks(
|
||||||
|
ImmutableList<DestinationEffect> effects,
|
||||||
|
ImmutableList<Action> actions,
|
||||||
|
LazyResourceLoader<Endpoint> originEndpoint,
|
||||||
|
LazyResourceLoader<Endpoint> destinationEndpoint,
|
||||||
|
Revision resolvedRef)
|
||||||
|
throws ValidationException, RepoException {
|
||||||
|
SkylarkConsole console = new SkylarkConsole(getConsole());
|
||||||
|
|
||||||
|
List<DestinationEffect> hookDestinationEffects = new ArrayList<>();
|
||||||
|
for (Action action : actions) {
|
||||||
|
try (ProfilerTask ignored2 = profiler().start(action.getName())) {
|
||||||
|
logger.log(Level.INFO, "Running after migration hook: " + action.getName());
|
||||||
|
FinishHookContext context =
|
||||||
|
new FinishHookContext(
|
||||||
|
action,
|
||||||
|
originEndpoint,
|
||||||
|
destinationEndpoint,
|
||||||
|
ImmutableList.copyOf(effects),
|
||||||
|
resolvedRef,
|
||||||
|
console);
|
||||||
|
action.run(context);
|
||||||
|
hookDestinationEffects.addAll(context.getNewDestinationEffects());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ImmutableList.<DestinationEffect>builder()
|
||||||
|
.addAll(effects)
|
||||||
|
.addAll(hookDestinationEffects)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmutableList<Token> getChangeIdentity() {
|
||||||
|
return changeIdentity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSetRevId() {
|
||||||
|
return setRevId;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isSmartPrune() {
|
||||||
|
return smartPrune;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isMigrateNoopChanges() {
|
||||||
|
return migrateNoopChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String customRevId() {
|
||||||
|
return customRevId;
|
||||||
|
}
|
||||||
|
}
|
507
third_party/copybara/java/com/google/copybara/WorkflowMode.java
vendored
Normal file
507
third_party/copybara/java/com/google/copybara/WorkflowMode.java
vendored
Normal file
|
@ -0,0 +1,507 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
|
||||||
|
import static com.google.copybara.GeneralOptions.FORCE;
|
||||||
|
import static com.google.copybara.Origin.Reader.ChangesResponse.EmptyReason.NO_CHANGES;
|
||||||
|
import static com.google.copybara.WorkflowOptions.CHANGE_REQUEST_FROM_SOT_LIMIT_FLAG;
|
||||||
|
import static com.google.copybara.WorkflowOptions.CHANGE_REQUEST_PARENT_FLAG;
|
||||||
|
import static com.google.copybara.exception.ValidationException.checkCondition;
|
||||||
|
import static com.google.copybara.exception.ValidationException.retriableException;
|
||||||
|
import static java.lang.String.format;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
|
import com.google.copybara.ChangeVisitable.VisitResult;
|
||||||
|
import com.google.copybara.DestinationEffect.Type;
|
||||||
|
import com.google.copybara.Origin.Baseline;
|
||||||
|
import com.google.copybara.Origin.Reader.ChangesResponse;
|
||||||
|
import com.google.copybara.Origin.Reader.ChangesResponse.EmptyReason;
|
||||||
|
import com.google.copybara.WorkflowRunHelper.ChangeMigrator;
|
||||||
|
import com.google.copybara.doc.annotations.DocField;
|
||||||
|
import com.google.copybara.exception.CannotResolveRevisionException;
|
||||||
|
import com.google.copybara.exception.ChangeRejectedException;
|
||||||
|
import com.google.copybara.exception.EmptyChangeException;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.profiler.Profiler.ProfilerTask;
|
||||||
|
import com.google.copybara.util.console.PrefixConsole;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Workflow type to run between origin an destination
|
||||||
|
*/
|
||||||
|
public enum WorkflowMode {
|
||||||
|
/**
|
||||||
|
* Create a single commit in the destination with new tree state.
|
||||||
|
*/
|
||||||
|
@DocField(description = "Create a single commit in the destination with new tree state.")
|
||||||
|
SQUASH {
|
||||||
|
@Override
|
||||||
|
<O extends Revision, D extends Revision> void run(WorkflowRunHelper<O, D> runHelper)
|
||||||
|
throws RepoException, IOException, ValidationException {
|
||||||
|
ImmutableList<Change<O>> detectedChanges = ImmutableList.of();
|
||||||
|
ImmutableMap<Change<O>, Change<O>> conditionalChanges = ImmutableMap.of();
|
||||||
|
O current = runHelper.getResolvedRef();
|
||||||
|
O lastRev = null;
|
||||||
|
if (isHistorySupported(runHelper)) {
|
||||||
|
lastRev = maybeGetLastRev(runHelper);
|
||||||
|
ChangesResponse<O> response = runHelper.getChanges(lastRev, current);
|
||||||
|
if (response.isEmpty()) {
|
||||||
|
manageNoChangesDetectedForSquash(runHelper, current, lastRev, response.getEmptyReason());
|
||||||
|
} else {
|
||||||
|
detectedChanges = response.getChanges();
|
||||||
|
conditionalChanges = response.getConditionalChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Metadata metadata = new Metadata(
|
||||||
|
runHelper.getChangeMessage("Project import generated by Copybara.\n"),
|
||||||
|
// SQUASH workflows always use the default author if it was not forced.
|
||||||
|
runHelper.getFinalAuthor(runHelper.getAuthoring().getDefaultAuthor()),
|
||||||
|
ImmutableSetMultimap.of());
|
||||||
|
|
||||||
|
runHelper.maybeValidateRepoInLastRevState(metadata);
|
||||||
|
|
||||||
|
// Don't replace helperForChanges with runHelper since origin_files could
|
||||||
|
// be potentially different in the helper for the current change.
|
||||||
|
ChangeMigrator<O, D> helperForChanges = detectedChanges.isEmpty()
|
||||||
|
? runHelper.getDefaultMigrator()
|
||||||
|
: runHelper.getMigratorForChange(Iterables.getLast(detectedChanges));
|
||||||
|
|
||||||
|
// Remove changes that don't affect origin_files
|
||||||
|
ImmutableList<Change<O>> changes = filterChanges(
|
||||||
|
detectedChanges, conditionalChanges, helperForChanges);
|
||||||
|
if (changes.isEmpty() && isHistorySupported(runHelper)) {
|
||||||
|
manageNoChangesDetectedForSquash(runHelper, current, lastRev, NO_CHANGES);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to use the latest change that affected the origin_files roots instead of the
|
||||||
|
// current revision, that could be an unrelated change.
|
||||||
|
current = changes.isEmpty()
|
||||||
|
? current
|
||||||
|
: Iterables.getLast(changes).getRevision();
|
||||||
|
|
||||||
|
if (runHelper.isSquashWithoutHistory()) {
|
||||||
|
changes = ImmutableList.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
helperForChanges.migrate(
|
||||||
|
current,
|
||||||
|
lastRev,
|
||||||
|
runHelper.getConsole(),
|
||||||
|
metadata,
|
||||||
|
// Squash notes an Skylark API expect last commit to be the first one.
|
||||||
|
new Changes(changes.reverse(), ImmutableList.of()),
|
||||||
|
/*destinationBaseline=*/null,
|
||||||
|
runHelper.getResolvedRef());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Import each origin change individually. */
|
||||||
|
@DocField(description = "Import each origin change individually.")
|
||||||
|
ITERATIVE {
|
||||||
|
@Override
|
||||||
|
<O extends Revision, D extends Revision> void run(WorkflowRunHelper<O, D> runHelper)
|
||||||
|
throws RepoException, IOException, ValidationException {
|
||||||
|
O lastRev = runHelper.getLastRev();
|
||||||
|
ChangesResponse<O> changesResponse =
|
||||||
|
runHelper.getChanges(lastRev, runHelper.getResolvedRef());
|
||||||
|
if (changesResponse.isEmpty()) {
|
||||||
|
ValidationException.checkCondition(
|
||||||
|
!changesResponse.getEmptyReason().equals(EmptyReason.UNRELATED_REVISIONS),
|
||||||
|
"last imported revision %s is not ancestor of requested revision %s",
|
||||||
|
lastRev, runHelper.getResolvedRef());
|
||||||
|
throw new EmptyChangeException(
|
||||||
|
"No new changes to import for resolved ref: " + runHelper.getResolvedRef().asString());
|
||||||
|
}
|
||||||
|
int changeNumber = 1;
|
||||||
|
|
||||||
|
ImmutableList<Change<O>> changes = ImmutableList.copyOf(changesResponse.getChanges());
|
||||||
|
Iterator<Change<O>> changesIterator = changes.iterator();
|
||||||
|
int limit = changes.size();
|
||||||
|
if (runHelper.workflowOptions().iterativeLimitChanges < changes.size()) {
|
||||||
|
runHelper.getConsole().info(String.format("Importing first %d change(s) out of %d",
|
||||||
|
limit, changes.size()));
|
||||||
|
limit = runHelper.workflowOptions().iterativeLimitChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
runHelper.maybeValidateRepoInLastRevState(/*metadata=*/null);
|
||||||
|
|
||||||
|
Deque<Change<O>> migrated = new ArrayDeque<>();
|
||||||
|
int migratedChanges = 0;
|
||||||
|
while (changesIterator.hasNext() && migratedChanges < limit) {
|
||||||
|
Change<O> change = changesIterator.next();
|
||||||
|
String prefix = String.format(
|
||||||
|
"Change %d of %d (%s): ",
|
||||||
|
changeNumber, Math.min(changes.size(), limit), change.getRevision().asString());
|
||||||
|
ImmutableList<DestinationEffect> result;
|
||||||
|
|
||||||
|
boolean errors = false;
|
||||||
|
try (ProfilerTask ignored = runHelper.profiler().start(change.getRef())) {
|
||||||
|
ImmutableList<Change<O>> current = ImmutableList.of(change);
|
||||||
|
ChangeMigrator<O, D> migrator = runHelper.getMigratorForChange(change);
|
||||||
|
if (migrator.skipChange(change)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result =
|
||||||
|
migrator.migrate(
|
||||||
|
change.getRevision(),
|
||||||
|
lastRev,
|
||||||
|
new PrefixConsole(prefix, runHelper.getConsole()),
|
||||||
|
new Metadata(
|
||||||
|
runHelper.getChangeMessage(change.getMessage()),
|
||||||
|
runHelper.getFinalAuthor(change.getAuthor()),
|
||||||
|
ImmutableSetMultimap.of()),
|
||||||
|
new Changes(current, migrated),
|
||||||
|
/*destinationBaseline=*/ null,
|
||||||
|
// Use the current change since we might want to create different
|
||||||
|
// reviews in the destination. Will not work if we want to group
|
||||||
|
// all the changes in the same Github PR
|
||||||
|
change.getRevision());
|
||||||
|
migratedChanges++;
|
||||||
|
for (DestinationEffect effect : result) {
|
||||||
|
if (effect.getType() != Type.NOOP) {
|
||||||
|
errors |= !effect.getErrors().isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (EmptyChangeException e) {
|
||||||
|
runHelper.getConsole().warnFmt("Migration of origin revision '%s' resulted in an empty"
|
||||||
|
+ " change in the destination: %s", change.getRevision().asString(), e.getMessage());
|
||||||
|
} catch (ValidationException | RepoException e) {
|
||||||
|
runHelper.getConsole().errorFmt("Migration of origin revision '%s' failed with error: %s",
|
||||||
|
change.getRevision().asString(), e.getMessage());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
migrated.addFirst(change);
|
||||||
|
|
||||||
|
if (errors && changesIterator.hasNext()) {
|
||||||
|
// Use the regular console to log prompt and final message, it will be easier to spot
|
||||||
|
if (!runHelper.getConsole()
|
||||||
|
.promptConfirmation("Continue importing next change?")) {
|
||||||
|
String message = String.format("Iterative workflow aborted by user after: %s", prefix);
|
||||||
|
runHelper.getConsole().warn(message);
|
||||||
|
throw new ChangeRejectedException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changeNumber++;
|
||||||
|
}
|
||||||
|
if (migratedChanges == 0) {
|
||||||
|
throw new EmptyChangeException(
|
||||||
|
String.format(
|
||||||
|
"Iterative workflow produced no changes in the destination for resolved ref: %s",
|
||||||
|
runHelper.getResolvedRef().asString()));
|
||||||
|
}
|
||||||
|
logger.atInfo().log("Imported %d change(s) out of %d", migratedChanges, changes.size());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
@DocField(description = "Import an origin tree state diffed by a common parent"
|
||||||
|
+ " in destination. This could be a GH Pull Request, a Gerrit Change, etc.")
|
||||||
|
CHANGE_REQUEST {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
<O extends Revision, D extends Revision> void run(WorkflowRunHelper<O, D> runHelper)
|
||||||
|
throws RepoException, IOException, ValidationException {
|
||||||
|
|
||||||
|
checkCondition(runHelper.destinationSupportsPreviousRef(),
|
||||||
|
"'%s' is incompatible with destinations that don't support history"
|
||||||
|
+ " (For example folder.destination)", CHANGE_REQUEST);
|
||||||
|
String originLabelName = runHelper.getLabelNameWhenOrigin();
|
||||||
|
Optional<Baseline<O>> baseline;
|
||||||
|
/*originRevision=*/
|
||||||
|
baseline = Strings.isNullOrEmpty(runHelper.workflowOptions().changeBaseline)
|
||||||
|
? runHelper.getOriginReader().findBaseline(runHelper.getResolvedRef(), originLabelName)
|
||||||
|
: Optional.of(
|
||||||
|
new Baseline<O>(runHelper.workflowOptions().changeBaseline, /*originRevision=*/null));
|
||||||
|
|
||||||
|
runChangeRequest(runHelper, baseline);
|
||||||
|
}},
|
||||||
|
@DocField(
|
||||||
|
description = "Import **from** the Source-of-Truth. This mode is useful when, despite the"
|
||||||
|
+ " pending change being already in the SoT, the users want to review the code on a"
|
||||||
|
+ " different system."
|
||||||
|
)
|
||||||
|
CHANGE_REQUEST_FROM_SOT {
|
||||||
|
@Override
|
||||||
|
<O extends Revision, D extends Revision> void run(WorkflowRunHelper<O, D> runHelper)
|
||||||
|
throws RepoException, IOException, ValidationException {
|
||||||
|
|
||||||
|
ImmutableList<O> originBaselines;
|
||||||
|
if (Strings.isNullOrEmpty(runHelper.workflowOptions().changeBaseline)) {
|
||||||
|
originBaselines = runHelper
|
||||||
|
.getOriginReader()
|
||||||
|
.findBaselinesWithoutLabel(runHelper.getResolvedRef(),
|
||||||
|
runHelper.workflowOptions().changeRequestFromSotLimit);
|
||||||
|
} else {
|
||||||
|
originBaselines = ImmutableList.of(
|
||||||
|
runHelper.originResolve(runHelper.workflowOptions().changeBaseline));
|
||||||
|
}
|
||||||
|
|
||||||
|
Baseline<O> destinationBaseline = getDestinationBaseline(runHelper, originBaselines);
|
||||||
|
|
||||||
|
if (destinationBaseline == null) {
|
||||||
|
checkCondition(!originBaselines.isEmpty(),
|
||||||
|
"Couldn't find any parent change for %s and origin_files = %s",
|
||||||
|
runHelper.getResolvedRef().asString(), runHelper.getOriginFiles());
|
||||||
|
|
||||||
|
throw retriableException(format(
|
||||||
|
"Couldn't find a change in the destination with %s label that matches a change from"
|
||||||
|
+ " the origin. Make sure"
|
||||||
|
+ " to sync the submitted changes from the origin -> destination first or use"
|
||||||
|
+ " SQUASH mode or use %s",
|
||||||
|
runHelper.getOriginLabelName(),
|
||||||
|
CHANGE_REQUEST_FROM_SOT_LIMIT_FLAG));
|
||||||
|
}
|
||||||
|
runChangeRequest(runHelper, Optional.of(destinationBaseline));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private <O extends Revision, D extends Revision> Baseline<O>
|
||||||
|
getDestinationBaseline(WorkflowRunHelper<O, D> runHelper, ImmutableList<O> originRevision)
|
||||||
|
throws RepoException, ValidationException {
|
||||||
|
|
||||||
|
Baseline<O> result =
|
||||||
|
getDestinationBaselineOneAttempt(runHelper, originRevision);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Integer delay : runHelper.workflowOptions().changeRequestFromSotRetry) {
|
||||||
|
runHelper.getConsole().warnFmt(
|
||||||
|
"Couldn't find a change in the destination with %s label and %s value."
|
||||||
|
+ " Retrying in %s seconds...",
|
||||||
|
runHelper.getOriginLabelName(), originRevision, delay);
|
||||||
|
try {
|
||||||
|
TimeUnit.SECONDS.sleep(delay);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RepoException("Interrupted while waiting for CHANGE_REQUEST_FROM_SOT"
|
||||||
|
+ " destination baseline to be available", e);
|
||||||
|
}
|
||||||
|
result = getDestinationBaselineOneAttempt(runHelper, originRevision);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private <O extends Revision, D extends Revision> Baseline<O>
|
||||||
|
getDestinationBaselineOneAttempt(
|
||||||
|
WorkflowRunHelper<O, D> runHelper, ImmutableList<O> originRevisions)
|
||||||
|
throws RepoException, ValidationException {
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
|
Baseline<O>[] result = new Baseline[] {null};
|
||||||
|
runHelper
|
||||||
|
.getDestinationWriter()
|
||||||
|
.visitChangesWithAnyLabel(
|
||||||
|
/*start=*/ null,
|
||||||
|
ImmutableList.of(runHelper.getOriginLabelName()),
|
||||||
|
(change, matchedLabels) -> {
|
||||||
|
for (String value : matchedLabels.values()) {
|
||||||
|
for (O originRevision : originRevisions) {
|
||||||
|
if (revisionWithoutReviewInfo(originRevision.asString())
|
||||||
|
.equals(revisionWithoutReviewInfo(value))) {
|
||||||
|
result[0] = new Baseline<>(change.getRevision().asString(), originRevision);
|
||||||
|
return VisitResult.TERMINATE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return VisitResult.CONTINUE;
|
||||||
|
});
|
||||||
|
return result[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Technically revisions can contain additional metadata in the String. For example:
|
||||||
|
* 'aaaabbbbccccddddeeeeffff1111222233334444 PatchSet-1'. This method return the identification
|
||||||
|
* part.
|
||||||
|
*/
|
||||||
|
private static String revisionWithoutReviewInfo(String r) {
|
||||||
|
return r.replaceFirst(" .*", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <O extends Revision, D extends Revision> void runChangeRequest(
|
||||||
|
WorkflowRunHelper<O, D> runHelper, Optional<Baseline<O>> baseline)
|
||||||
|
throws ValidationException, RepoException, IOException {
|
||||||
|
checkCondition(baseline.isPresent(),
|
||||||
|
"Cannot find matching parent commit in in the destination. Use '%s' flag to force a"
|
||||||
|
+ " parent commit to use as baseline in the destination.",
|
||||||
|
CHANGE_REQUEST_PARENT_FLAG);
|
||||||
|
logger.atInfo().log("Found baseline %s", baseline.get().getBaseline());
|
||||||
|
|
||||||
|
ChangeMigrator<O, D> migrator = runHelper.getDefaultMigrator();
|
||||||
|
// If --change_request_parent was used, we don't have information about the origin changes
|
||||||
|
// included in the CHANGE_REQUEST so we assume the last change is the only change
|
||||||
|
ImmutableList<Change<O>> changes;
|
||||||
|
if (baseline.get().getOriginRevision() == null) {
|
||||||
|
changes = ImmutableList.of(runHelper.getOriginReader().change(runHelper.getResolvedRef()));
|
||||||
|
} else {
|
||||||
|
ChangesResponse<O> changesResponse = runHelper.getOriginReader()
|
||||||
|
.changes(baseline.get().getOriginRevision(),
|
||||||
|
runHelper.getResolvedRef());
|
||||||
|
if (changesResponse.isEmpty()) {
|
||||||
|
throw new EmptyChangeException(
|
||||||
|
format("Change '%s' doesn't include any change for origin_files = %s",
|
||||||
|
runHelper.getResolvedRef(), runHelper.getOriginFiles()));
|
||||||
|
}
|
||||||
|
changes = filterChanges(
|
||||||
|
changesResponse.getChanges(), changesResponse.getConditionalChanges(), migrator);
|
||||||
|
if (changes.isEmpty()) {
|
||||||
|
throw new EmptyChangeException(
|
||||||
|
format("Change '%s' doesn't include any change for origin_files = %s",
|
||||||
|
runHelper.getResolvedRef(), runHelper.getOriginFiles()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --read-config-from-change is not implemented in CHANGE_REQUEST mode
|
||||||
|
migrator.migrate(
|
||||||
|
runHelper.getResolvedRef(),
|
||||||
|
/*lastRev=*/ null,
|
||||||
|
runHelper.getConsole(),
|
||||||
|
// Use latest change as the message/author. If it contains multiple changes the user
|
||||||
|
// can always use metadata.squash_notes or similar.
|
||||||
|
new Metadata(
|
||||||
|
runHelper.getChangeMessage(Iterables.getLast(changes).getMessage()),
|
||||||
|
runHelper.getFinalAuthor(Iterables.getLast(changes).getAuthor()),
|
||||||
|
ImmutableSetMultimap.of()),
|
||||||
|
// Squash notes an Skylark API expect last commit to be the first one.
|
||||||
|
new Changes(changes.reverse(), ImmutableList.of()),
|
||||||
|
baseline.get(),
|
||||||
|
runHelper.getResolvedRef());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <O extends Revision, D extends Revision> void manageNoChangesDetectedForSquash(
|
||||||
|
WorkflowRunHelper<O, D> runHelper, O current, O lastRev, EmptyReason emptyReason)
|
||||||
|
throws ValidationException {
|
||||||
|
switch (emptyReason) {
|
||||||
|
case NO_CHANGES:
|
||||||
|
String noChangesMsg =
|
||||||
|
String.format(
|
||||||
|
"No changes%s up to %s match any origin_files",
|
||||||
|
lastRev == null ? "" : " from " + lastRev.asString(), current.asString());
|
||||||
|
if (!runHelper.isForce()) {
|
||||||
|
throw new EmptyChangeException(
|
||||||
|
String.format(
|
||||||
|
"%s. Use %s if you really want to run the migration anyway.",
|
||||||
|
noChangesMsg, GeneralOptions.FORCE));
|
||||||
|
}
|
||||||
|
runHelper
|
||||||
|
.getConsole()
|
||||||
|
.warnFmt("%s. Migrating anyway because of %s", noChangesMsg, GeneralOptions.FORCE);
|
||||||
|
break;
|
||||||
|
case TO_IS_ANCESTOR:
|
||||||
|
if (!runHelper.isForce()) {
|
||||||
|
throw new EmptyChangeException(
|
||||||
|
String.format(
|
||||||
|
"'%s' has been already migrated. Use %s if you really want to run the migration"
|
||||||
|
+ " again (For example if the copy.bara.sky file has changed).",
|
||||||
|
current.asString(), GeneralOptions.FORCE));
|
||||||
|
}
|
||||||
|
runHelper
|
||||||
|
.getConsole()
|
||||||
|
.warnFmt(
|
||||||
|
"'%s' has been already migrated. Migrating anyway" + " because of %s",
|
||||||
|
lastRev.asString(), GeneralOptions.FORCE);
|
||||||
|
break;
|
||||||
|
case UNRELATED_REVISIONS:
|
||||||
|
checkCondition(
|
||||||
|
runHelper.isForce(),
|
||||||
|
String.format(
|
||||||
|
"Last imported revision '%s' is not an ancestor of the revision currently being"
|
||||||
|
+ " migrated ('%s'). Use %s if you really want to migrate the reference.",
|
||||||
|
lastRev, current.asString(), GeneralOptions.FORCE));
|
||||||
|
runHelper
|
||||||
|
.getConsole()
|
||||||
|
.warnFmt(
|
||||||
|
"Last imported revision '%s' is not an ancestor of the revision currently being"
|
||||||
|
+ " migrated ('%s')",
|
||||||
|
lastRev, current.asString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isHistorySupported(WorkflowRunHelper<?, ?> helper) {
|
||||||
|
return helper.destinationSupportsPreviousRef() && helper.getOriginReader().supportsHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
static <O extends Revision, D extends Revision> ImmutableList<Change<O>> filterChanges(
|
||||||
|
ImmutableList<Change<O>> detectedChanges,
|
||||||
|
ImmutableMap<Change<O>, Change<O>> conditionalChanges,
|
||||||
|
ChangeMigrator<O, D> changeMigrator) {
|
||||||
|
|
||||||
|
List<Change<O>> includedChanges = detectedChanges.stream()
|
||||||
|
.filter(e -> !changeMigrator.skipChange(e))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// For all the changes that should be included based on skipChange, find the ones that
|
||||||
|
// should be added unconditionally
|
||||||
|
List<Change<O>> unconditionalChanges = includedChanges.stream()
|
||||||
|
.filter(e -> !conditionalChanges.keySet().contains(e))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// Only include unconditional changes or conditional changes that its dependant change is
|
||||||
|
// included
|
||||||
|
return includedChanges.stream()
|
||||||
|
.filter(e -> unconditionalChanges.contains(e)
|
||||||
|
|| (conditionalChanges.containsKey(e)
|
||||||
|
&& unconditionalChanges.contains(conditionalChanges.get(e))))
|
||||||
|
.collect(ImmutableList.toImmutableList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last rev if possible. If --force is not enabled it will fail if not found.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static <O extends Revision, D extends Revision> O maybeGetLastRev(
|
||||||
|
WorkflowRunHelper<O, D> runHelper) throws RepoException, ValidationException {
|
||||||
|
try {
|
||||||
|
return runHelper.getLastRev();
|
||||||
|
} catch (CannotResolveRevisionException e) {
|
||||||
|
if (runHelper.isForce()) {
|
||||||
|
runHelper.getConsole().warnFmt(
|
||||||
|
"Cannot find last imported revision, but proceeding because of %s flag",
|
||||||
|
GeneralOptions.FORCE);
|
||||||
|
} else {
|
||||||
|
throw new ValidationException(
|
||||||
|
String.format("Cannot find last imported revision. Use %s if you really want to proceed"
|
||||||
|
+ " with the migration", FORCE), e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
abstract <O extends Revision, D extends Revision> void run(
|
||||||
|
WorkflowRunHelper<O, D> runHelper) throws RepoException, IOException, ValidationException;
|
||||||
|
}
|
266
third_party/copybara/java/com/google/copybara/WorkflowOptions.java
vendored
Normal file
266
third_party/copybara/java/com/google/copybara/WorkflowOptions.java
vendored
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.beust.jcommander.Parameter;
|
||||||
|
import com.beust.jcommander.Parameters;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
|
import com.google.copybara.authoring.Author;
|
||||||
|
import com.google.copybara.exception.VoidOperationException;
|
||||||
|
import com.google.copybara.jcommander.AuthorConverter;
|
||||||
|
import com.google.copybara.jcommander.GreaterThanZeroListValidator;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import com.google.devtools.build.lib.syntax.EvalException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arguments for {@link Workflow} components.
|
||||||
|
*/
|
||||||
|
@Parameters(separators = "=")
|
||||||
|
public class WorkflowOptions implements Option {
|
||||||
|
|
||||||
|
static final String CHANGE_REQUEST_PARENT_FLAG = "--change-request-parent";
|
||||||
|
static final String CHANGE_REQUEST_PARENT_FLAG_ALT = "--change_request_parent";
|
||||||
|
|
||||||
|
static final String READ_CONFIG_FROM_CHANGE = "--read-config-from-change";
|
||||||
|
static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
protected static final String CHANGE_REQUEST_FROM_SOT_LIMIT_FLAG =
|
||||||
|
"--change-request-from-sot-limit";
|
||||||
|
|
||||||
|
@Parameter(names = {CHANGE_REQUEST_PARENT_FLAG, CHANGE_REQUEST_PARENT_FLAG_ALT},
|
||||||
|
description = "Commit revision to be used as parent when importing a commit using"
|
||||||
|
+ " CHANGE_REQUEST workflow mode. this shouldn't be needed in general as Copybara is able"
|
||||||
|
+ " to detect the parent commit message.")
|
||||||
|
public String changeBaseline = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public so that it can be used programmatically.
|
||||||
|
*/
|
||||||
|
@Parameter(names = "--last-rev",
|
||||||
|
description = "Last revision that was migrated to the destination")
|
||||||
|
public String lastRevision;
|
||||||
|
|
||||||
|
static final String INIT_HISTORY_FLAG = "--init-history";
|
||||||
|
|
||||||
|
@Parameter(names = INIT_HISTORY_FLAG,
|
||||||
|
description = "Import all the changes from the beginning of the history up to the resolved"
|
||||||
|
+ " ref. For 'ITERATIVE' workflows this will import individual changes since the first "
|
||||||
|
+ "one. For 'SQUASH' it will import the squashed change up to the resolved ref. "
|
||||||
|
+ "WARNING: Use with care, this flag should be used only for the very first run of "
|
||||||
|
+ "Copybara for a workflow.")
|
||||||
|
public boolean initHistory = false;
|
||||||
|
|
||||||
|
@Parameter(names = "--iterative-limit-changes",
|
||||||
|
description = "Import just a number of changes instead of all the pending ones")
|
||||||
|
public int iterativeLimitChanges = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
@Parameter(names = "--ignore-noop",
|
||||||
|
description = "Only warn about operations/transforms that didn't have any effect."
|
||||||
|
+ " For example: A transform that didn't modify any file, non-existent origin"
|
||||||
|
+ " directories, etc.")
|
||||||
|
public boolean ignoreNoop = false;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = "--squash-skip-history",
|
||||||
|
description =
|
||||||
|
"Avoid exposing the history of changes that are being migrated. This is"
|
||||||
|
+ " useful when we want to migrate a new repository but we don't want to expose all"
|
||||||
|
+ " the change history to metadata.squash_notes."
|
||||||
|
)
|
||||||
|
public boolean squashSkipHistory = false;
|
||||||
|
|
||||||
|
@Parameter(names = {"--import-noop-changes"},
|
||||||
|
description = "By default Copybara will only try to migrate changes that could affect the"
|
||||||
|
+ " destination. Ignoring changes that only affect excluded files in origin_files. This"
|
||||||
|
+ " flag disables that behavior and runs for all the changes.")
|
||||||
|
public boolean migrateNoopChanges = false;
|
||||||
|
|
||||||
|
@Parameter(names = {"--workflow-identity-user"},
|
||||||
|
description = "Use a custom string as a user for computing change identity")
|
||||||
|
@Nullable
|
||||||
|
public String workflowIdentityUser = null;
|
||||||
|
|
||||||
|
public static final String CHECK_LAST_REV_STATE = "--check-last-rev-state";
|
||||||
|
|
||||||
|
@Parameter(names = CHECK_LAST_REV_STATE,
|
||||||
|
description = "If enabled, Copybara will validate that the destination didn't change"
|
||||||
|
+ " since last-rev import for destination_files. Note that this"
|
||||||
|
+ " flag doesn't work for CHANGE_REQUEST mode.")
|
||||||
|
public boolean checkLastRevState = false;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = "--threads",
|
||||||
|
description =
|
||||||
|
"Number of threads to use when running transformations that change lot of files")
|
||||||
|
public int threads = Runtime.getRuntime().availableProcessors();
|
||||||
|
|
||||||
|
@Parameter(names = CHANGE_REQUEST_FROM_SOT_LIMIT_FLAG,
|
||||||
|
description = "Number of origin baseline changes to use for trying to match one in the"
|
||||||
|
+ " destination. It can be used if the are many parent changes in the origin that are a"
|
||||||
|
+ " no-op in the destination")
|
||||||
|
public int changeRequestFromSotLimit = 500;
|
||||||
|
|
||||||
|
@Parameter(names = "--threads-min-size",
|
||||||
|
description = "Minimum size of the lists to process to run them in parallel")
|
||||||
|
public int threadsMinSize = 100;
|
||||||
|
|
||||||
|
@Parameter(names = "--notransformation-join",
|
||||||
|
description = "By default Copybara tries to join certain transformations in one so that it"
|
||||||
|
+ " is more efficient. This disables the feature.")
|
||||||
|
public boolean noTransformationJoin = false;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = READ_CONFIG_FROM_CHANGE,
|
||||||
|
description = "For each imported origin change, load the workflow's origin_files, "
|
||||||
|
+ "destination_files and transformations from the config version of that change. The "
|
||||||
|
+ "rest of the fields (more importantly, "
|
||||||
|
+ "origin and destination) cannot change and the version from the first config will be "
|
||||||
|
+ "used.")
|
||||||
|
boolean readConfigFromChange = false;
|
||||||
|
|
||||||
|
@Parameter(names = "--nosmart-prune",
|
||||||
|
description = "Disable smart prunning")
|
||||||
|
boolean noSmartPrune = false;
|
||||||
|
|
||||||
|
public boolean canUseSmartPrune() {
|
||||||
|
return !noSmartPrune;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parameter(names = "--change-request-from-sot-retry",
|
||||||
|
description = "Number of retries and delay between retries when we cannot find the baseline"
|
||||||
|
+ " in the destination for CHANGE_REQUEST_FROM_SOT. For example '10,30,60' will retry"
|
||||||
|
+ " three times. The first retry will be delayed 10s, the second one 30s and the third"
|
||||||
|
+ " one 60s", validateWith = GreaterThanZeroListValidator.class)
|
||||||
|
public List<Integer> changeRequestFromSotRetry= Lists.newArrayList();
|
||||||
|
|
||||||
|
|
||||||
|
@Parameter(names = "--default-author",
|
||||||
|
description = "Use this author as default instead of the one in the config file."
|
||||||
|
+ "Format should be 'Foo Bar <foobar@example.com>'")
|
||||||
|
String defaultAuthor = null;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = "--force-message",
|
||||||
|
description = "Force the change description to this. Note that this only changes the message"
|
||||||
|
+ " before the transformations happen, you can still use the transformations"
|
||||||
|
+ " to alter it.")
|
||||||
|
String forcedChangeMessage = null;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = "--force-author",
|
||||||
|
description = "Force the author to this. Note that this only changes the author"
|
||||||
|
+ " before the transformations happen, you can still use the transformations"
|
||||||
|
+ " to alter it.", converter = AuthorConverter.class)
|
||||||
|
Author forcedAuthor = null;
|
||||||
|
|
||||||
|
@Parameter(names = "--diff-in-origin",
|
||||||
|
description = "When this flag is enabled, copybara will show different changes between last"
|
||||||
|
+ " Revision and current revision in origin instead of in destination. NOTE: it Only"
|
||||||
|
+ " works for SQUASH and ITERATIVE")
|
||||||
|
public boolean diffInOrigin = false;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Author getDefaultAuthorFlag() throws EvalException {
|
||||||
|
if (defaultAuthor == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Author.parse(defaultAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isReadConfigFromChange() {
|
||||||
|
return readConfigFromChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Supplier<LocalParallelizer> parallelizerSupplier =
|
||||||
|
Suppliers.memoize(() -> new LocalParallelizer(getThreads(), threadsMinSize));
|
||||||
|
|
||||||
|
private int getThreads() {
|
||||||
|
logger.atInfo().log("Using %d thread(s) for transformations", threads);
|
||||||
|
return threads;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalParallelizer parallelizer() {
|
||||||
|
return parallelizerSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean joinTransformations() {
|
||||||
|
return !noTransformationJoin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports that some operation is a no-op. This will either throw an exception or report the
|
||||||
|
* incident to the console, depending on the options.
|
||||||
|
*/
|
||||||
|
public void reportNoop(Console console, String message, boolean currentIgnoreNoop)
|
||||||
|
throws VoidOperationException {
|
||||||
|
if (ignoreNoop) {
|
||||||
|
console.warn("NOOP: " + message);
|
||||||
|
} else if(currentIgnoreNoop){
|
||||||
|
if(console.isVerbose()){
|
||||||
|
console.warn("NOOP: " + message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new VoidOperationException(
|
||||||
|
String.format("%s. Use --ignore-noop if you want to ignore this error", message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorkflowOptions() {}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public WorkflowOptions(String changeBaseline, String lastRevision, boolean checkLastRevState) {
|
||||||
|
this.changeBaseline = changeBaseline;
|
||||||
|
this.lastRevision = lastRevision;
|
||||||
|
this.checkLastRevState = checkLastRevState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastRevision() {
|
||||||
|
return lastRevision;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInitHistory() {
|
||||||
|
return initHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getChangeBaseline() {
|
||||||
|
return changeBaseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof WorkflowOptions)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
WorkflowOptions that = (WorkflowOptions) o;
|
||||||
|
return Objects.equals(changeBaseline, that.changeBaseline)
|
||||||
|
&& Objects.equals(lastRevision, that.lastRevision);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(changeBaseline, lastRevision);
|
||||||
|
}
|
||||||
|
}
|
781
third_party/copybara/java/com/google/copybara/WorkflowRunHelper.java
vendored
Normal file
781
third_party/copybara/java/com/google/copybara/WorkflowRunHelper.java
vendored
Normal file
|
@ -0,0 +1,781 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.copybara.GeneralOptions.OUTPUT_ROOT_FLAG;
|
||||||
|
import static com.google.copybara.util.FileUtil.CopySymlinkStrategy.FAIL_OUTSIDE_SYMLINKS;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.base.Verify;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
|
import com.google.copybara.Destination.DestinationStatus;
|
||||||
|
import com.google.copybara.Destination.Writer;
|
||||||
|
import com.google.copybara.DestinationEffect.Type;
|
||||||
|
import com.google.copybara.Origin.Baseline;
|
||||||
|
import com.google.copybara.Origin.Reader;
|
||||||
|
import com.google.copybara.Origin.Reader.ChangesResponse;
|
||||||
|
import com.google.copybara.TransformWork.ResourceSupplier;
|
||||||
|
import com.google.copybara.authoring.Author;
|
||||||
|
import com.google.copybara.authoring.Authoring;
|
||||||
|
import com.google.copybara.exception.CannotResolveRevisionException;
|
||||||
|
import com.google.copybara.exception.ChangeRejectedException;
|
||||||
|
import com.google.copybara.exception.EmptyChangeException;
|
||||||
|
import com.google.copybara.exception.RepoException;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.monitor.EventMonitor;
|
||||||
|
import com.google.copybara.monitor.EventMonitor.ChangeMigrationFinishedEvent;
|
||||||
|
import com.google.copybara.monitor.EventMonitor.ChangeMigrationStartedEvent;
|
||||||
|
import com.google.copybara.profiler.Profiler;
|
||||||
|
import com.google.copybara.profiler.Profiler.ProfilerTask;
|
||||||
|
import com.google.copybara.util.DiffUtil;
|
||||||
|
import com.google.copybara.util.DiffUtil.DiffFile;
|
||||||
|
import com.google.copybara.util.FileUtil;
|
||||||
|
import com.google.copybara.util.Glob;
|
||||||
|
import com.google.copybara.util.InsideGitDirException;
|
||||||
|
import com.google.copybara.util.console.AnsiColor;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import com.google.copybara.util.console.PrefixConsole;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.NoSuchFileException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.PathMatcher;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a single migration step for a {@link Workflow}, using its configuration.
|
||||||
|
*/
|
||||||
|
public class WorkflowRunHelper<O extends Revision, D extends Revision> {
|
||||||
|
|
||||||
|
private final Workflow<O, D> workflow;
|
||||||
|
private final Path workdir;
|
||||||
|
private final O resolvedRef;
|
||||||
|
private final Origin.Reader<O> originReader;
|
||||||
|
protected final Destination.Writer<D> writer;
|
||||||
|
@Nullable final String rawSourceRef;
|
||||||
|
private final Consumer<ChangeMigrationFinishedEvent> migrationFinishedMonitor;
|
||||||
|
|
||||||
|
WorkflowRunHelper(
|
||||||
|
Workflow<O, D> workflow,
|
||||||
|
Path workdir,
|
||||||
|
O resolvedRef,
|
||||||
|
Reader<O> originReader,
|
||||||
|
Writer<D> destinationWriter,
|
||||||
|
@Nullable String rawSourceRef,
|
||||||
|
Consumer<ChangeMigrationFinishedEvent> migrationFinishedMonitor) {
|
||||||
|
this.workflow = checkNotNull(workflow);
|
||||||
|
this.workdir = checkNotNull(workdir);
|
||||||
|
this.resolvedRef = checkNotNull(resolvedRef);
|
||||||
|
this.originReader = checkNotNull(originReader);
|
||||||
|
this.writer = checkNotNull(destinationWriter);
|
||||||
|
this.rawSourceRef = rawSourceRef;
|
||||||
|
this.migrationFinishedMonitor = checkNotNull(migrationFinishedMonitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Consumer<ChangeMigrationFinishedEvent> getMigrationFinishedMonitor() {
|
||||||
|
return migrationFinishedMonitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* origin_files used for this workflow
|
||||||
|
*/
|
||||||
|
protected Glob getOriginFiles(){
|
||||||
|
return workflow.getOriginFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
ChangeMigrator<O, D> getMigratorForChange(Change<?> change)
|
||||||
|
throws RepoException, ValidationException {
|
||||||
|
return getMigratorForChangeAndWriter(change, writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChangeMigrator<O, D> getMigratorForChangeAndWriter(Change<?> change, Writer<D> writer)
|
||||||
|
throws ValidationException, RepoException {
|
||||||
|
return new ChangeMigrator<>(workflow, workdir, originReader, writer, resolvedRef, rawSourceRef,
|
||||||
|
migrationFinishedMonitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a default migrator for the current writer
|
||||||
|
*/
|
||||||
|
ChangeMigrator<O, D> getDefaultMigrator() {
|
||||||
|
return new ChangeMigrator<>(workflow, workdir, originReader, writer, resolvedRef, rawSourceRef,
|
||||||
|
migrationFinishedMonitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Profiler profiler() {
|
||||||
|
return workflow.profiler();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Path getWorkdir() {
|
||||||
|
return workdir;
|
||||||
|
}
|
||||||
|
|
||||||
|
O getResolvedRef() {
|
||||||
|
return resolvedRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authoring configuration.
|
||||||
|
*/
|
||||||
|
Authoring getAuthoring() {
|
||||||
|
return workflow.getAuthoring();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getChangeMessage(String message) {
|
||||||
|
return workflow.getWorkflowOptions().forcedChangeMessage == null
|
||||||
|
? message
|
||||||
|
: workflow.getWorkflowOptions().forcedChangeMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Author getFinalAuthor(Author author) {
|
||||||
|
return workflowOptions().forcedAuthor == null ? author : workflowOptions().forcedAuthor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Console to use for printing messages.
|
||||||
|
*/
|
||||||
|
Console getConsole() {
|
||||||
|
return workflow.getConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options that change how workflows behave.
|
||||||
|
*/
|
||||||
|
WorkflowOptions workflowOptions() {
|
||||||
|
return workflow.getWorkflowOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isForce() {
|
||||||
|
return workflow.isForce();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInitHistory() {
|
||||||
|
return workflow.isInitHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isSquashWithoutHistory() {
|
||||||
|
return workflow.getWorkflowOptions().squashSkipHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
Destination<?> getDestination() {
|
||||||
|
return workflow.getDestination();
|
||||||
|
}
|
||||||
|
|
||||||
|
Origin.Reader<O> getOriginReader() {
|
||||||
|
return originReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
Destination.Writer<D> getDestinationWriter() {
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean destinationSupportsPreviousRef() {
|
||||||
|
return writer.supportsHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
void maybeValidateRepoInLastRevState(@Nullable Metadata metadata) throws RepoException,
|
||||||
|
ValidationException, IOException {
|
||||||
|
if (!workflow.isCheckLastRevState() || isForce()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
workflow
|
||||||
|
.getGeneralOptions()
|
||||||
|
.ioRepoTask(
|
||||||
|
"validate_last_rev",
|
||||||
|
() -> {
|
||||||
|
O lastRev =
|
||||||
|
workflow.getGeneralOptions().repoTask("get_last_rev", this::maybeGetLastRev);
|
||||||
|
|
||||||
|
if (lastRev == null) {
|
||||||
|
// Not the job of this function to check for lastrev status.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Change<O> change = originReader.change(lastRev);
|
||||||
|
Changes changes = new Changes(ImmutableList.of(change), ImmutableList.of());
|
||||||
|
// Create a new writer so that state is not shared with the regular writer.
|
||||||
|
// The current writer might have state from previous migrations, etc.
|
||||||
|
ChangeMigrator<O, D> migrator =
|
||||||
|
getMigratorForChangeAndWriter(change, workflow.createDryRunWriter(resolvedRef));
|
||||||
|
|
||||||
|
try {
|
||||||
|
workflow
|
||||||
|
.getGeneralOptions()
|
||||||
|
.ioRepoTask(
|
||||||
|
"migrate",
|
||||||
|
() ->
|
||||||
|
// We pass lastRev as the lastRev. This is not correct but we cannot
|
||||||
|
// know the previous rev of the last rev. Furthermore, this should only
|
||||||
|
// be used for generating messages, so users shouldn't care about the
|
||||||
|
// value (but they might care about its presence, so it cannot be null.
|
||||||
|
migrator.doMigrate(
|
||||||
|
lastRev,
|
||||||
|
lastRev,
|
||||||
|
new PrefixConsole(
|
||||||
|
"Validating last migration: ", workflow.getConsole()),
|
||||||
|
metadata == null
|
||||||
|
? new Metadata(
|
||||||
|
change.getMessage(), change.getAuthor(),
|
||||||
|
ImmutableSetMultimap.of())
|
||||||
|
: metadata,
|
||||||
|
changes,
|
||||||
|
/*destinationBaseline=*/ null,
|
||||||
|
lastRev));
|
||||||
|
throw new ValidationException(
|
||||||
|
"Migration of last-rev '"
|
||||||
|
+ lastRev.asString()
|
||||||
|
+ "' didn't"
|
||||||
|
+ " result in an empty change. This means that the result change of that"
|
||||||
|
+ " migration was modified ouside of Copybara or that new changes happened"
|
||||||
|
+ " later in the destination without using Copybara. Use --force if you"
|
||||||
|
+ " really want to do the migration.");
|
||||||
|
} catch (EmptyChangeException ignored) {
|
||||||
|
// EmptyChangeException ignored
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ChangesResponse<O> getChanges(@Nullable O from, O to) throws RepoException, ValidationException {
|
||||||
|
try (ProfilerTask ignore = workflow.profiler().start("get_changes")) {
|
||||||
|
return originReader.changes(from, to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate a change for a workflow. Can overwrite the reader, writer, transformations, etc.
|
||||||
|
*/
|
||||||
|
public static class ChangeMigrator<O extends Revision, D extends Revision> {
|
||||||
|
|
||||||
|
private final Workflow<O, D> workflow;
|
||||||
|
private final Path workdir;
|
||||||
|
private final O resolvedRef;
|
||||||
|
private final Reader<O> reader;
|
||||||
|
private final Writer<D> writer;
|
||||||
|
@Nullable
|
||||||
|
private final String rawSourceRef;
|
||||||
|
private final Consumer<ChangeMigrationFinishedEvent> migrationFinishedMonitor;
|
||||||
|
|
||||||
|
ChangeMigrator(Workflow<O, D> workflow, Path workdir, Reader<O> reader,
|
||||||
|
Writer<D> writer, O resolvedRef, @Nullable String rawSourceRef,
|
||||||
|
Consumer<ChangeMigrationFinishedEvent> migrationFinishedMonitor) {
|
||||||
|
this.workflow = checkNotNull(workflow);
|
||||||
|
this.workdir = checkNotNull(workdir);
|
||||||
|
this.resolvedRef = checkNotNull(resolvedRef);
|
||||||
|
this.reader = checkNotNull(reader);
|
||||||
|
this.writer = checkNotNull(writer);
|
||||||
|
this.rawSourceRef = rawSourceRef;
|
||||||
|
this.migrationFinishedMonitor = checkNotNull(migrationFinishedMonitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if this change can be skipped because it would generate a noop in the
|
||||||
|
* destination.
|
||||||
|
*
|
||||||
|
* <p>First we check if the change contains the files of the change and if they match
|
||||||
|
* origin_files. Then we also check for potential changes in the config for configs that are
|
||||||
|
* stored in the origin.
|
||||||
|
*/
|
||||||
|
final boolean skipChange(Change<?> currentChange) {
|
||||||
|
boolean skipChange = shouldSkipChange(currentChange);
|
||||||
|
if (skipChange) {
|
||||||
|
workflow.getConsole().verboseFmt("Skipped change %s as it would create an empty result.",
|
||||||
|
currentChange.toString());
|
||||||
|
}
|
||||||
|
return skipChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true iff the given change should be skipped based on the origin globs and flags
|
||||||
|
* provided.
|
||||||
|
*/
|
||||||
|
final boolean shouldSkipChange(Change<?> currentChange) {
|
||||||
|
if (workflow.isMigrateNoopChanges()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// We cannot know the files included. Try to migrate then.
|
||||||
|
if (currentChange.getChangeFiles() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PathMatcher pathMatcher = getOriginFiles().relativeTo(Paths.get("/"));
|
||||||
|
for (String changedFile : currentChange.getChangeFiles()) {
|
||||||
|
if (pathMatcher.matches(Paths.get("/" + changedFile))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This is an heuristic for cases where the Copybara configuration is stored in the same
|
||||||
|
// folder as the origin code but excluded.
|
||||||
|
//
|
||||||
|
// The config root can be a subfolder of the files as seen by the origin. For example:
|
||||||
|
// admin/copy.bara.sky could be present in the origin as root/admin/copy.bara.sky.
|
||||||
|
// This might give us some false positives but they would be noop migrations.
|
||||||
|
for (String changesFile : currentChange.getChangeFiles()) {
|
||||||
|
for (String configPath : getConfigFiles()) {
|
||||||
|
if (changesFile.endsWith(configPath)) {
|
||||||
|
workflow.getConsole()
|
||||||
|
.infoFmt("Migrating %s because %s config file changed at that revision",
|
||||||
|
currentChange.getRevision().asString(), changesFile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set<String> getConfigFiles() {
|
||||||
|
return workflow.configPaths();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Glob getOriginFiles() {
|
||||||
|
return workflow.getOriginFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Glob getDestinationFiles() {
|
||||||
|
return workflow.getDestinationFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Transformation getTransformation() {
|
||||||
|
return workflow.getTransformation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected Transformation getReverseTransformForCheck() {
|
||||||
|
return workflow.getReverseTransformForCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Profiler profiler() {
|
||||||
|
return workflow.profiler();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a full migration, including checking out files from the origin, deleting excluded
|
||||||
|
* files, transforming the code, and writing to the destination. This writes to the destination
|
||||||
|
* exactly once.
|
||||||
|
*
|
||||||
|
* @param rev revision to the version which will be written to the destination
|
||||||
|
* @param lastRev last revision that was migrated
|
||||||
|
* @param processConsole console to use to print progress messages
|
||||||
|
* @param metadata metadata of the change to be migrated
|
||||||
|
* @param changes changes included in this migration
|
||||||
|
* @param destinationBaseline it not null, use this baseline in the destination
|
||||||
|
* @param changeIdentityRevision the revision to be used for computing the change identity
|
||||||
|
*/
|
||||||
|
final ImmutableList<DestinationEffect> migrate(
|
||||||
|
O rev,
|
||||||
|
@Nullable O lastRev,
|
||||||
|
Console processConsole,
|
||||||
|
Metadata metadata,
|
||||||
|
Changes changes,
|
||||||
|
@Nullable Baseline<O> destinationBaseline,
|
||||||
|
@Nullable O changeIdentityRevision)
|
||||||
|
throws IOException, RepoException, ValidationException {
|
||||||
|
ImmutableList<DestinationEffect> effects = ImmutableList.of();
|
||||||
|
try {
|
||||||
|
workflow.eventMonitor().onChangeMigrationStarted(new ChangeMigrationStartedEvent());
|
||||||
|
effects =
|
||||||
|
doMigrate(
|
||||||
|
rev, lastRev, processConsole, metadata, changes, destinationBaseline,
|
||||||
|
changeIdentityRevision);
|
||||||
|
} catch (EmptyChangeException empty) {
|
||||||
|
effects =
|
||||||
|
ImmutableList.of(
|
||||||
|
new DestinationEffect(
|
||||||
|
Type.NOOP,
|
||||||
|
String.format("Cannot migrate revisions [%s]: %s",
|
||||||
|
changes.getCurrent().isEmpty()
|
||||||
|
? "Unknown"
|
||||||
|
: Joiner.on(", ").join(changes.getCurrent().stream()
|
||||||
|
.map(c -> c.getRevision().asString())
|
||||||
|
.iterator()),
|
||||||
|
empty.getMessage()),
|
||||||
|
changes.getCurrent(),
|
||||||
|
/*destinationRef=*/ null));
|
||||||
|
throw empty;
|
||||||
|
} catch (ValidationException | IOException | RepoException | RuntimeException e) {
|
||||||
|
boolean userError = e instanceof ValidationException;
|
||||||
|
effects =
|
||||||
|
ImmutableList.of(
|
||||||
|
new DestinationEffect(
|
||||||
|
userError ? Type.ERROR : Type.TEMPORARY_ERROR,
|
||||||
|
"Errors happened during the migration",
|
||||||
|
changes.getCurrent(),
|
||||||
|
/*destinationRef=*/ null,
|
||||||
|
ImmutableList.of(e.getMessage() != null ? e.getMessage() : e.toString())));
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!workflow.getGeneralOptions().dryRunMode) {
|
||||||
|
try (ProfilerTask ignored = profiler().start("after_migration")) {
|
||||||
|
effects = workflow.runHooks(effects, workflow.getAfterMigrationActions(),
|
||||||
|
// Only do this once for all the actions
|
||||||
|
LazyResourceLoader.memoized(reader::getFeedbackEndPoint),
|
||||||
|
// Only do this once for all the actions
|
||||||
|
LazyResourceLoader.memoized(writer::getFeedbackEndPoint),
|
||||||
|
resolvedRef);
|
||||||
|
}
|
||||||
|
} else if (!workflow.getAfterMigrationActions().isEmpty()) {
|
||||||
|
workflow.getConsole()
|
||||||
|
.infoFmt(
|
||||||
|
"Not calling 'after_migration' actions because of %s mode",
|
||||||
|
GeneralOptions.DRY_RUN_FLAG);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
migrationFinishedMonitor.accept(new ChangeMigrationFinishedEvent(effects));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return effects;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean showDiffInOrigin(O rev, @Nullable O lastRev, Console processConsole)
|
||||||
|
throws RepoException, ChangeRejectedException {
|
||||||
|
if (!workflow.getWorkflowOptions().diffInOrigin
|
||||||
|
|| workflow.getMode() == WorkflowMode.CHANGE_REQUEST
|
||||||
|
|| workflow.getMode() == WorkflowMode.CHANGE_REQUEST_FROM_SOT
|
||||||
|
|| lastRev == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String diff = workflow.getOrigin().showDiff(lastRev, rev);
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (String line : Splitter.on('\n').split(diff)) {
|
||||||
|
sb.append("\n");
|
||||||
|
if (line.startsWith("+")) {
|
||||||
|
sb.append(processConsole.colorize(AnsiColor.GREEN, line));
|
||||||
|
} else if (line.startsWith("-")) {
|
||||||
|
sb.append(processConsole.colorize(AnsiColor.RED, line));
|
||||||
|
} else {
|
||||||
|
sb.append(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processConsole.info(sb.toString());
|
||||||
|
if (!processConsole.promptConfirmation(String.format("Continue to migrate with '%s' to "
|
||||||
|
+ "%s?", workflow.getMode(), workflow.getDestination().getType()))){
|
||||||
|
processConsole.warn("Migration aborted by user.");
|
||||||
|
throw new ChangeRejectedException(
|
||||||
|
"User aborted execution: did not confirm diff in origin changes.");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImmutableList<DestinationEffect> doMigrate(
|
||||||
|
O rev,
|
||||||
|
@Nullable O lastRev,
|
||||||
|
Console processConsole,
|
||||||
|
Metadata metadata,
|
||||||
|
Changes changes,
|
||||||
|
@Nullable Baseline<O> destinationBaseline,
|
||||||
|
@Nullable O changeIdentityRevision)
|
||||||
|
throws IOException, RepoException, ValidationException {
|
||||||
|
Path checkoutDir = workdir.resolve("checkout");
|
||||||
|
try (ProfilerTask ignored = profiler().start("prepare_workdir")) {
|
||||||
|
processConsole.progress("Cleaning working directory");
|
||||||
|
if (Files.exists(workdir)) {
|
||||||
|
FileUtil.deleteRecursively(workdir);
|
||||||
|
}
|
||||||
|
Files.createDirectories(checkoutDir);
|
||||||
|
}
|
||||||
|
processConsole.progress("Checking out the change");
|
||||||
|
boolean isShowDiffInOrigin = showDiffInOrigin(rev, lastRev, processConsole);
|
||||||
|
|
||||||
|
checkout(rev, processConsole, checkoutDir, "origin.checkout");
|
||||||
|
|
||||||
|
Path originCopy = null;
|
||||||
|
if (getReverseTransformForCheck() != null) {
|
||||||
|
try (ProfilerTask ignored = profiler().start("reverse_copy")) {
|
||||||
|
workflow.getConsole().progress("Making a copy or the workdir for reverse checking");
|
||||||
|
originCopy = Files.createDirectories(workdir.resolve("origin"));
|
||||||
|
try {
|
||||||
|
FileUtil.copyFilesRecursively(checkoutDir, originCopy, FAIL_OUTSIDE_SYMLINKS);
|
||||||
|
} catch (NoSuchFileException e) {
|
||||||
|
throw new ValidationException(String.format(""
|
||||||
|
+ "Failed to perform reversible check of transformations due to symlink '%s' "
|
||||||
|
+ "that points outside the checkout dir. Consider removing this symlink from "
|
||||||
|
+ "your origin_files or, alternatively, set reversible_check = False in your "
|
||||||
|
+ "workflow.", e.getFile()), e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Lazy loading to avoid running afoul of checks unless the instance is actually used.
|
||||||
|
LazyResourceLoader<Endpoint> originApi = c -> reader.getFeedbackEndPoint(c);
|
||||||
|
LazyResourceLoader<Endpoint> destinationApi = c-> writer.getFeedbackEndPoint(c);
|
||||||
|
ResourceSupplier<DestinationReader> destinationReader = () ->
|
||||||
|
writer.getDestinationReader(workflow.getConsole(), destinationBaseline, workdir);
|
||||||
|
|
||||||
|
TransformWork transformWork =
|
||||||
|
new TransformWork(
|
||||||
|
checkoutDir,
|
||||||
|
metadata,
|
||||||
|
changes,
|
||||||
|
workflow.getConsole(),
|
||||||
|
new MigrationInfo(workflow.getRevIdLabel(), writer),
|
||||||
|
resolvedRef,
|
||||||
|
/*ignoreNoop=*/ false,
|
||||||
|
originApi,
|
||||||
|
destinationApi,
|
||||||
|
destinationReader)
|
||||||
|
.withLastRev(lastRev)
|
||||||
|
.withCurrentRev(rev);
|
||||||
|
try (ProfilerTask ignored = profiler().start("transforms")) {
|
||||||
|
getTransformation().transform(transformWork);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getReverseTransformForCheck() != null) {
|
||||||
|
workflow.getConsole().progress("Checking that the transformations can be reverted");
|
||||||
|
Path reverse;
|
||||||
|
try (ProfilerTask ignored = profiler().start("reverse_copy")) {
|
||||||
|
reverse = Files.createDirectories(workdir.resolve("reverse"));
|
||||||
|
try {
|
||||||
|
FileUtil.copyFilesRecursively(checkoutDir, reverse, FAIL_OUTSIDE_SYMLINKS);
|
||||||
|
} catch (NoSuchFileException e) {
|
||||||
|
throw new ValidationException(""
|
||||||
|
+ "Failed to perform reversible check of transformations due to a symlink that "
|
||||||
|
+ "points outside the checkout dir. Consider removing this symlink from your "
|
||||||
|
+ "origin_files or, alternatively, set reversible_check = False in your "
|
||||||
|
+ "workflow.", e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try (ProfilerTask ignored = profiler().start("reverse_transform")) {
|
||||||
|
getReverseTransformForCheck()
|
||||||
|
.transform(
|
||||||
|
new TransformWork(
|
||||||
|
reverse,
|
||||||
|
transformWork.getMetadata(),
|
||||||
|
changes,
|
||||||
|
workflow.getConsole(),
|
||||||
|
new MigrationInfo(/*originLabel=*/ null, null),
|
||||||
|
resolvedRef,
|
||||||
|
/*ignoreNoop=*/ false,
|
||||||
|
destinationApi,
|
||||||
|
originApi,
|
||||||
|
() -> DestinationReader.NOT_IMPLEMENTED));
|
||||||
|
}
|
||||||
|
String diff;
|
||||||
|
try {
|
||||||
|
diff = new String(DiffUtil.diff(originCopy, reverse, workflow.isVerbose(),
|
||||||
|
workflow.getGeneralOptions().getEnvironment()),
|
||||||
|
StandardCharsets.UTF_8);
|
||||||
|
} catch (InsideGitDirException e) {
|
||||||
|
throw new ValidationException(String.format(
|
||||||
|
"Cannot use 'reversible_check = True' because Copybara temporary directory (%s) is"
|
||||||
|
+ " inside a git directory (%s). Please remove the git repository or use %s"
|
||||||
|
+ " flag.", e.getPath(), e.getGitDirPath(), OUTPUT_ROOT_FLAG));
|
||||||
|
}
|
||||||
|
if (!diff.trim().isEmpty()) {
|
||||||
|
workflow.getConsole().error("Non reversible transformations:\n"
|
||||||
|
+ DiffUtil.colorize(workflow.getConsole(), diff));
|
||||||
|
throw new ValidationException(
|
||||||
|
String.format("Workflow '%s' is not reversible", workflow.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
workflow.getConsole()
|
||||||
|
.progress("Checking that destination_files covers all files in transform result");
|
||||||
|
new ValidateDestinationFilesVisitor(getDestinationFiles(), checkoutDir)
|
||||||
|
.verifyFilesToWrite();
|
||||||
|
|
||||||
|
// TODO(malcon): Pass metadata object instead
|
||||||
|
TransformResult transformResult =
|
||||||
|
new TransformResult(
|
||||||
|
checkoutDir,
|
||||||
|
rev,
|
||||||
|
transformWork.getAuthor(),
|
||||||
|
transformWork.getMessage(),
|
||||||
|
resolvedRef,
|
||||||
|
workflow.getName(),
|
||||||
|
changes,
|
||||||
|
rawSourceRef,
|
||||||
|
workflow.isSetRevId(),
|
||||||
|
transformWork::getAllLabels,
|
||||||
|
workflow.getRevIdLabel());
|
||||||
|
|
||||||
|
if (destinationBaseline != null) {
|
||||||
|
transformResult = transformResult.withBaseline(destinationBaseline.getBaseline());
|
||||||
|
if (workflow.isSmartPrune() && workflow.getWorkflowOptions().canUseSmartPrune()) {
|
||||||
|
ValidationException.checkCondition(destinationBaseline.getOriginRevision() != null,
|
||||||
|
"smart_prune is not compatible with %s flag for now",
|
||||||
|
WorkflowOptions.CHANGE_REQUEST_PARENT_FLAG);
|
||||||
|
Path baselineWorkdir = Files.createDirectories(workdir.resolve("baseline"));
|
||||||
|
|
||||||
|
PrefixConsole baselineConsole = new PrefixConsole("Migrating baseline for diff: ",
|
||||||
|
workflow.getConsole());
|
||||||
|
checkout(destinationBaseline.getOriginRevision(), baselineConsole, baselineWorkdir,
|
||||||
|
"origin.baseline.checkout");
|
||||||
|
|
||||||
|
TransformWork baselineTransformWork =
|
||||||
|
new TransformWork(
|
||||||
|
baselineWorkdir,
|
||||||
|
// We don't care about the message or author and this guarantees that it will
|
||||||
|
// work with the transformations
|
||||||
|
metadata,
|
||||||
|
// We don't care about the changes that are imported.
|
||||||
|
changes,
|
||||||
|
baselineConsole,
|
||||||
|
new MigrationInfo(workflow.getRevIdLabel(), writer),
|
||||||
|
resolvedRef,
|
||||||
|
// Doesn't guarantee that we will not run a ignore_noop = False core.transform but
|
||||||
|
// reduces the chances.
|
||||||
|
/*ignoreNoop=*/true,
|
||||||
|
originApi,
|
||||||
|
destinationApi,
|
||||||
|
destinationReader)
|
||||||
|
// Again, we don't care about this
|
||||||
|
.withLastRev(lastRev)
|
||||||
|
.withCurrentRev(destinationBaseline.getOriginRevision());
|
||||||
|
try (ProfilerTask ignored = profiler().start("baseline_transforms")) {
|
||||||
|
getTransformation().transform(baselineTransformWork);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ImmutableList<DiffFile> affectedFiles = DiffUtil
|
||||||
|
.diffFiles(baselineWorkdir, checkoutDir, workflow.getGeneralOptions().isVerbose(),
|
||||||
|
workflow.getGeneralOptions().getEnvironment());
|
||||||
|
transformResult = transformResult.withAffectedFilesForSmartPrune(affectedFiles);
|
||||||
|
} catch (InsideGitDirException e) {
|
||||||
|
throw new ValidationException("Error computing diff for smart_prune: " + e.getMessage(),
|
||||||
|
e.getCause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transformResult = transformResult
|
||||||
|
.withAskForConfirmation(workflow.isAskForConfirmation())
|
||||||
|
.withDiffInOrigin(isShowDiffInOrigin)
|
||||||
|
.withIdentity(workflow.getMigrationIdentity(changeIdentityRevision, transformWork));
|
||||||
|
|
||||||
|
ImmutableList<DestinationEffect> result;
|
||||||
|
try (ProfilerTask ignored = profiler().start(
|
||||||
|
"destination.write", profiler().taskType(workflow.getDestination().getType()))) {
|
||||||
|
result = writer.write(transformResult, getDestinationFiles(), processConsole);
|
||||||
|
}
|
||||||
|
Verify.verifyNotNull(result, "Destination returned a null result.");
|
||||||
|
Verify
|
||||||
|
.verify(!result.isEmpty(), "Destination " + writer + " returned an empty set of effects");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkout(
|
||||||
|
O rev, Console processConsole, Path checkoutDir, String profileDescription)
|
||||||
|
throws RepoException, ValidationException, IOException {
|
||||||
|
if (workflow.isCheckout() ) {
|
||||||
|
try (ProfilerTask ignored = profiler().start(
|
||||||
|
profileDescription, profiler().taskType(workflow.getOrigin().getType()))) {
|
||||||
|
reader.checkout(rev, checkoutDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove excluded origin files.
|
||||||
|
PathMatcher originFiles = getOriginFiles().relativeTo(checkoutDir);
|
||||||
|
processConsole.progress("Removing excluded origin files");
|
||||||
|
|
||||||
|
int deleted = FileUtil.deleteFilesRecursively(
|
||||||
|
checkoutDir, FileUtil.notPathMatcher(originFiles));
|
||||||
|
if (deleted != 0) {
|
||||||
|
processConsole.infoFmt(
|
||||||
|
"Removed %d files from workdir that do not match origin_files", deleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get last imported revision or fail if it cannot be found.
|
||||||
|
*
|
||||||
|
* @throws RepoException if a last revision couldn't be found
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
O getLastRev() throws RepoException, ValidationException {
|
||||||
|
O lastRev = maybeGetLastRev();
|
||||||
|
if (lastRev == null && !isInitHistory()) {
|
||||||
|
throw new CannotResolveRevisionException(String.format(
|
||||||
|
"Previous revision label %s could not be found in %s and --last-rev or --init-history "
|
||||||
|
+ "flags were not passed",
|
||||||
|
getOriginLabelName(), workflow.getDestination()));
|
||||||
|
}
|
||||||
|
return lastRev;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getOriginLabelName() {
|
||||||
|
return workflow.getRevIdLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getLabelNameWhenOrigin() throws ValidationException {
|
||||||
|
return workflow.customRevId() == null
|
||||||
|
? workflow.getDestination().getLabelNameWhenOrigin()
|
||||||
|
: workflow.customRevId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last revision that was imported from this origin to the destination. Returns
|
||||||
|
* {@code null} if it cannot be determined.
|
||||||
|
*
|
||||||
|
* <p>If {@code --last-rev} is specified, that revision will be used. Otherwise, the previous
|
||||||
|
* revision will be resolved in the destination with the origin label.
|
||||||
|
*
|
||||||
|
* <p>If {@code --init-history} it will return null, if a last revision cannot be resolved in the
|
||||||
|
* destination. The reason is to avoid users importing accidentally all the history again by using
|
||||||
|
* the flag when they shouldn't.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private O maybeGetLastRev() throws RepoException, ValidationException {
|
||||||
|
if (workflow.getLastRevisionFlag() != null) {
|
||||||
|
try {
|
||||||
|
return originResolve(workflow.getLastRevisionFlag());
|
||||||
|
} catch (RepoException e) {
|
||||||
|
throw new CannotResolveRevisionException(
|
||||||
|
"Could not resolve --last-rev flag. Please make sure it exists in the origin: "
|
||||||
|
+ workflow.getLastRevisionFlag(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DestinationStatus status = writer.getDestinationStatus(
|
||||||
|
workflow.getDestinationFiles(),
|
||||||
|
getOriginLabelName());
|
||||||
|
try {
|
||||||
|
O lastRev = (status == null) ? null : originResolve(status.getBaseline());
|
||||||
|
if (lastRev != null && workflow.isInitHistory()) {
|
||||||
|
getConsole().warnFmt(
|
||||||
|
"Ignoring %s because a previous imported revision '%s' was found in the destination.",
|
||||||
|
WorkflowOptions.INIT_HISTORY_FLAG, lastRev.asString());
|
||||||
|
}
|
||||||
|
return lastRev;
|
||||||
|
} catch (CannotResolveRevisionException e) {
|
||||||
|
if (workflow.isInitHistory()) {
|
||||||
|
// Expected to not find a revision if --init-history is provided
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a string representation of a revision using the origin
|
||||||
|
*/
|
||||||
|
O originResolve(String revStr) throws RepoException, ValidationException {
|
||||||
|
return workflow.getOrigin().resolve(revStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public EventMonitor eventMonitor() {
|
||||||
|
return workflow.eventMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
67
third_party/copybara/java/com/google/copybara/WriterContext.java
vendored
Normal file
67
third_party/copybara/java/com/google/copybara/WriterContext.java
vendored
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writer context which includes all the information for creating a writer
|
||||||
|
*/
|
||||||
|
public class WriterContext {
|
||||||
|
|
||||||
|
private final String workflowName;
|
||||||
|
private final String workflowIdentityUser;
|
||||||
|
private final boolean dryRun;
|
||||||
|
private final Revision originalRevision;
|
||||||
|
private final ImmutableSet<String> roots;
|
||||||
|
|
||||||
|
public WriterContext(String workflowName,
|
||||||
|
@Nullable String workflowIdentityUser,
|
||||||
|
boolean dryRun,
|
||||||
|
Revision originalRevision,
|
||||||
|
ImmutableSet<String> roots) {
|
||||||
|
this.workflowName = Preconditions.checkNotNull(workflowName);
|
||||||
|
this.workflowIdentityUser = workflowIdentityUser != null
|
||||||
|
? workflowIdentityUser
|
||||||
|
: System.getProperty("user.name");
|
||||||
|
this.dryRun = dryRun;
|
||||||
|
this.originalRevision = Preconditions.checkNotNull(originalRevision);
|
||||||
|
this.roots = Preconditions.checkNotNull(roots);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Revision getOriginalRevision() {
|
||||||
|
return originalRevision;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWorkflowIdentityUser() {
|
||||||
|
return workflowIdentityUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWorkflowName() {
|
||||||
|
return workflowName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDryRun() {
|
||||||
|
return dryRun;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableSet<String> getRoots() {
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
}
|
103
third_party/copybara/java/com/google/copybara/authoring/Author.java
vendored
Normal file
103
third_party/copybara/java/com/google/copybara/authoring/Author.java
vendored
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.authoring;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkMethod;
|
||||||
|
import com.google.devtools.build.lib.syntax.EvalException;
|
||||||
|
import com.google.devtools.build.lib.syntax.Starlark;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the contributor of a change in the destination repository. A contributor can be either
|
||||||
|
* an individual or a team.
|
||||||
|
*
|
||||||
|
* <p>Author is lenient in name or email validation.
|
||||||
|
*/
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "author",
|
||||||
|
category = StarlarkDocumentationCategory.BUILTIN,
|
||||||
|
doc = "Represents the author of a change")
|
||||||
|
public final class Author implements StarlarkValue {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final String email;
|
||||||
|
|
||||||
|
public Author(String name, String email) {
|
||||||
|
this.name = Preconditions.checkNotNull(name);
|
||||||
|
this.email = Preconditions.checkNotNull(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the author.
|
||||||
|
*/
|
||||||
|
@StarlarkMethod(name = "name", doc = "The name of the author", structField = true)
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the email address of the author.
|
||||||
|
*/
|
||||||
|
@StarlarkMethod(name = "email", doc = "The email of the author", structField = true)
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the string representation of an author, which is the standard format
|
||||||
|
* {@code Name <email>} used by most version control systems.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s <%s>", name, email);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof Author) {
|
||||||
|
Author that = (Author) o;
|
||||||
|
// Authors with the same non-empty email are the same author
|
||||||
|
return Strings.isNullOrEmpty(this.email) && Strings.isNullOrEmpty(that.email)
|
||||||
|
? Objects.equals(this.name, that.name)
|
||||||
|
: Objects.equals(this.email, that.email);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse author from a String in the format of: "name <foo@bar.com>" */
|
||||||
|
public static Author parse(String authorStr) throws EvalException {
|
||||||
|
try {
|
||||||
|
return AuthorParser.parse(authorStr);
|
||||||
|
} catch (InvalidAuthorException e) {
|
||||||
|
throw Starlark.errorf(
|
||||||
|
"Author '%s' doesn't match the expected format 'name <mail@example.com>: %s",
|
||||||
|
authorStr, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Strings.isNullOrEmpty(this.email)
|
||||||
|
? Objects.hashCode(this.name)
|
||||||
|
: Objects.hashCode(this.email);
|
||||||
|
}
|
||||||
|
}
|
52
third_party/copybara/java/com/google/copybara/authoring/AuthorParser.java
vendored
Normal file
52
third_party/copybara/java/com/google/copybara/authoring/AuthorParser.java
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.authoring;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.re2j.Matcher;
|
||||||
|
import com.google.re2j.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A parser for the standard author format {@code "Name <email>"}.
|
||||||
|
*
|
||||||
|
* <p>This is the format used by most VCS (Git, Mercurial) and also by the Copybara configuration
|
||||||
|
* itself. The parser is lenient: {@code email} can be empty, and it doesn't validate that is an
|
||||||
|
* actual email.
|
||||||
|
*/
|
||||||
|
public class AuthorParser {
|
||||||
|
|
||||||
|
private static final Pattern AUTHOR_PATTERN =
|
||||||
|
Pattern.compile("(?P<name>[^<]+)<(?P<email>[^>]*)>");
|
||||||
|
private static final Pattern IN_QUOTES =
|
||||||
|
Pattern.compile("(\".+\")|(\'.+\')");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a Git author {@code string} into an {@link Author}.
|
||||||
|
*/
|
||||||
|
public static Author parse(String author) throws InvalidAuthorException {
|
||||||
|
Preconditions.checkNotNull(author);
|
||||||
|
if (IN_QUOTES.matcher(author).matches()) {
|
||||||
|
author = author.substring(1, author.length() - 1); //strip quotes
|
||||||
|
}
|
||||||
|
Matcher matcher = AUTHOR_PATTERN.matcher(author);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
return new Author(matcher.group(1).trim(), matcher.group(2).trim());
|
||||||
|
}
|
||||||
|
throw new InvalidAuthorException(
|
||||||
|
String.format("Invalid author '%s'. Must be in the form of 'Name <email>'", author));
|
||||||
|
}
|
||||||
|
}
|
271
third_party/copybara/java/com/google/copybara/authoring/Authoring.java
vendored
Normal file
271
third_party/copybara/java/com/google/copybara/authoring/Authoring.java
vendored
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.authoring;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.copybara.doc.annotations.Example;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.Param;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkMethod;
|
||||||
|
import com.google.devtools.build.lib.syntax.EvalException;
|
||||||
|
import com.google.devtools.build.lib.syntax.Location;
|
||||||
|
import com.google.devtools.build.lib.syntax.Sequence;
|
||||||
|
import com.google.devtools.build.lib.syntax.Starlark;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the authors mapping between an origin and a destination.
|
||||||
|
*
|
||||||
|
* <p>For a given author in the origin, always provides an author in the destination.
|
||||||
|
*/
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "authoring_class",
|
||||||
|
namespace = true,
|
||||||
|
doc = "The authors mapping between an origin and a destination",
|
||||||
|
category = StarlarkDocumentationCategory.BUILTIN)
|
||||||
|
public final class Authoring implements StarlarkValue {
|
||||||
|
|
||||||
|
private final Author defaultAuthor;
|
||||||
|
private final AuthoringMappingMode mode;
|
||||||
|
private final ImmutableSet<String> whitelist;
|
||||||
|
|
||||||
|
public Authoring(
|
||||||
|
Author defaultAuthor, AuthoringMappingMode mode, ImmutableSet<String> whitelist) {
|
||||||
|
this.defaultAuthor = Preconditions.checkNotNull(defaultAuthor);
|
||||||
|
this.mode = Preconditions.checkNotNull(mode);
|
||||||
|
this.whitelist = Preconditions.checkNotNull(whitelist);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mapping mode.
|
||||||
|
*/
|
||||||
|
public AuthoringMappingMode getMode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default author, used for squash workflows,
|
||||||
|
* {@link AuthoringMappingMode#OVERWRITE} mode and for non-whitelisted authors.
|
||||||
|
*/
|
||||||
|
public Author getDefaultAuthor() {
|
||||||
|
return defaultAuthor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@code Set} of whitelisted author identifiers.
|
||||||
|
*
|
||||||
|
* <p>An identifier is typically an email but might have different representations depending on
|
||||||
|
* the origin.
|
||||||
|
*/
|
||||||
|
public ImmutableSet<String> getWhitelist() {
|
||||||
|
return whitelist;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the user can be safely used.
|
||||||
|
*/
|
||||||
|
public boolean useAuthor(String userId) {
|
||||||
|
switch (mode) {
|
||||||
|
case PASS_THRU:
|
||||||
|
return true;
|
||||||
|
case OVERWRITE:
|
||||||
|
return false;
|
||||||
|
case WHITELISTED:
|
||||||
|
return whitelist.contains(userId);
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException(String.format("Mode '%s' not implemented.", mode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "authoring",
|
||||||
|
namespace = true,
|
||||||
|
doc = "The authors mapping between an origin and a destination",
|
||||||
|
category = StarlarkDocumentationCategory.BUILTIN)
|
||||||
|
public static final class Module implements StarlarkValue {
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "overwrite",
|
||||||
|
doc =
|
||||||
|
"Use the default author for all the submits in the destination. Note that some"
|
||||||
|
+ " destinations might choose to ignore this author and use the current user"
|
||||||
|
+ " running the tool (In other words they don't allow impersonation).",
|
||||||
|
parameters = {
|
||||||
|
@Param(
|
||||||
|
name = "default",
|
||||||
|
type = String.class,
|
||||||
|
named = true,
|
||||||
|
doc = "The default author for commits in the destination"),
|
||||||
|
})
|
||||||
|
@Example(
|
||||||
|
title = "Overwrite usage example",
|
||||||
|
before =
|
||||||
|
"Create an authoring object that will overwrite any origin author with"
|
||||||
|
+ " noreply@foobar.com mail.",
|
||||||
|
code = "authoring.overwrite(\"Foo Bar <noreply@foobar.com>\")")
|
||||||
|
public Authoring overwrite(String defaultAuthor) throws EvalException {
|
||||||
|
return new Authoring(
|
||||||
|
Author.parse(defaultAuthor), AuthoringMappingMode.OVERWRITE, ImmutableSet.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Example(
|
||||||
|
title = "Pass thru usage example",
|
||||||
|
before = "",
|
||||||
|
code = "authoring.pass_thru(default = \"Foo Bar <noreply@foobar.com>\")")
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "pass_thru",
|
||||||
|
doc = "Use the origin author as the author in the destination, no whitelisting.",
|
||||||
|
parameters = {
|
||||||
|
@Param(
|
||||||
|
name = "default",
|
||||||
|
type = String.class,
|
||||||
|
named = true,
|
||||||
|
doc =
|
||||||
|
"The default author for commits in the destination. This is used"
|
||||||
|
+ " in squash mode workflows or if author cannot be determined."),
|
||||||
|
})
|
||||||
|
public Authoring passThru(String defaultAuthor) throws EvalException {
|
||||||
|
return new Authoring(
|
||||||
|
Author.parse(defaultAuthor), AuthoringMappingMode.PASS_THRU, ImmutableSet.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "whitelisted",
|
||||||
|
doc = "Create an individual or team that contributes code.",
|
||||||
|
parameters = {
|
||||||
|
@Param(
|
||||||
|
name = "default",
|
||||||
|
type = String.class,
|
||||||
|
named = true,
|
||||||
|
doc =
|
||||||
|
"The default author for commits in the destination. This is used"
|
||||||
|
+ " in squash mode workflows or when users are not whitelisted."),
|
||||||
|
@Param(
|
||||||
|
name = "whitelist",
|
||||||
|
type = Sequence.class,
|
||||||
|
generic1 = String.class,
|
||||||
|
named = true,
|
||||||
|
doc = "List of white listed authors in the origin. The authors must be unique"),
|
||||||
|
})
|
||||||
|
@Example(
|
||||||
|
title = "Only pass thru whitelisted users",
|
||||||
|
before = "",
|
||||||
|
code =
|
||||||
|
"authoring.whitelisted(\n"
|
||||||
|
+ " default = \"Foo Bar <noreply@foobar.com>\",\n"
|
||||||
|
+ " whitelist = [\n"
|
||||||
|
+ " \"someuser@myorg.com\",\n"
|
||||||
|
+ " \"other@myorg.com\",\n"
|
||||||
|
+ " \"another@myorg.com\",\n"
|
||||||
|
+ " ],\n"
|
||||||
|
+ ")")
|
||||||
|
@Example(
|
||||||
|
title = "Only pass thru whitelisted LDAPs/usernames",
|
||||||
|
before =
|
||||||
|
"Some repositories are not based on email but use LDAPs/usernames. This is also"
|
||||||
|
+ " supported since it is up to the origin how to check whether two authors are"
|
||||||
|
+ " the same.",
|
||||||
|
code =
|
||||||
|
"authoring.whitelisted(\n"
|
||||||
|
+ " default = \"Foo Bar <noreply@foobar.com>\",\n"
|
||||||
|
+ " whitelist = [\n"
|
||||||
|
+ " \"someuser\",\n"
|
||||||
|
+ " \"other\",\n"
|
||||||
|
+ " \"another\",\n"
|
||||||
|
+ " ],\n"
|
||||||
|
+ ")")
|
||||||
|
public Authoring whitelisted(String defaultAuthor, Sequence<?> whitelist // <String>
|
||||||
|
) throws EvalException {
|
||||||
|
return new Authoring(
|
||||||
|
Author.parse(defaultAuthor),
|
||||||
|
AuthoringMappingMode.WHITELISTED,
|
||||||
|
createWhitelist(Sequence.cast(whitelist, String.class, "whitelist")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ImmutableSet<String> createWhitelist(List<String> whitelist)
|
||||||
|
throws EvalException {
|
||||||
|
if (whitelist.isEmpty()) {
|
||||||
|
throw Starlark.errorf(
|
||||||
|
"'whitelisted' function requires a non-empty 'whitelist' field. For default mapping,"
|
||||||
|
+ " use 'overwrite(...)' mode instead.");
|
||||||
|
}
|
||||||
|
Set<String> uniqueAuthors = new HashSet<>();
|
||||||
|
for (String author : whitelist) {
|
||||||
|
if (!uniqueAuthors.add(author)) {
|
||||||
|
throw Starlark.errorf("Duplicated whitelist entry '%s'", author);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ImmutableSet.copyOf(whitelist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mode used for author mapping from origin to destination.
|
||||||
|
*
|
||||||
|
* <p>This enum is our internal representation for the different Skylark built-in functions.
|
||||||
|
*/
|
||||||
|
public enum AuthoringMappingMode {
|
||||||
|
/**
|
||||||
|
* Corresponds with {@link Authoring.Module#overwrite(String, Location)} built-in function.
|
||||||
|
*/
|
||||||
|
OVERWRITE,
|
||||||
|
/**
|
||||||
|
* Corresponds with {@link Authoring.Module#passThru(String, Location)} built-in function.
|
||||||
|
*/
|
||||||
|
PASS_THRU,
|
||||||
|
/**
|
||||||
|
* Corresponds with {@link Authoring.Module#whitelisted(String, Sequence, Location)} built-in
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
WHITELISTED
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Authoring authoring = (Authoring) o;
|
||||||
|
return Objects.equals(defaultAuthor, authoring.defaultAuthor) &&
|
||||||
|
mode == authoring.mode &&
|
||||||
|
Objects.equals(whitelist, authoring.whitelist);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(defaultAuthor, mode, whitelist);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("defaultAuthor", defaultAuthor)
|
||||||
|
.add("mode", mode)
|
||||||
|
.add("whitelist", whitelist)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
34
third_party/copybara/java/com/google/copybara/authoring/BUILD
vendored
Normal file
34
third_party/copybara/java/com/google/copybara/authoring/BUILD
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# Copyright 2016 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
java_library(
|
||||||
|
name = "authoring",
|
||||||
|
srcs = glob(["**/*.java"]),
|
||||||
|
javacopts = [
|
||||||
|
"-Xlint:unchecked",
|
||||||
|
"-source",
|
||||||
|
"1.8",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//java/com/google/copybara/doc:annotations",
|
||||||
|
"//third_party:guava",
|
||||||
|
"//third_party:jsr305",
|
||||||
|
"//third_party:re2j",
|
||||||
|
"//third_party:skylark-lang",
|
||||||
|
],
|
||||||
|
)
|
31
third_party/copybara/java/com/google/copybara/authoring/InvalidAuthorException.java
vendored
Normal file
31
third_party/copybara/java/com/google/copybara/authoring/InvalidAuthorException.java
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.authoring;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the author does not conform to the expected format.
|
||||||
|
*
|
||||||
|
* <p>This exception does not extend other exceptions {@code ValidationException} or
|
||||||
|
* {@code RepoException} because a bad parsed author could come from a configuration or a repo, and
|
||||||
|
* it's wrapped into the proper one by the caller.
|
||||||
|
*/
|
||||||
|
public class InvalidAuthorException extends Exception {
|
||||||
|
|
||||||
|
InvalidAuthorException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
61
third_party/copybara/java/com/google/copybara/buildozer/BUILD
vendored
Normal file
61
third_party/copybara/java/com/google/copybara/buildozer/BUILD
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
# Copyright 2016 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Support for expressing Buildozer operations as Copybara transformations
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
java_library(
|
||||||
|
name = "buildozer",
|
||||||
|
srcs = glob(
|
||||||
|
["*.java"],
|
||||||
|
exclude = [
|
||||||
|
"BuildozerOptions.java",
|
||||||
|
"TargetNotFoundException.java",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
deps = [
|
||||||
|
":buildozer_options",
|
||||||
|
"//java/com/google/copybara:base",
|
||||||
|
"//java/com/google/copybara/config:base",
|
||||||
|
"//java/com/google/copybara/doc:annotations",
|
||||||
|
"//java/com/google/copybara/exception",
|
||||||
|
"//java/com/google/copybara/util/console",
|
||||||
|
"//third_party:guava",
|
||||||
|
"//third_party:jsr305",
|
||||||
|
"//third_party:skylark-lang",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
java_library(
|
||||||
|
name = "buildozer_options",
|
||||||
|
srcs = [
|
||||||
|
"BuildozerOptions.java",
|
||||||
|
"TargetNotFoundException.java",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//java/com/google/copybara:base",
|
||||||
|
"//java/com/google/copybara:general_options",
|
||||||
|
"//java/com/google/copybara/exception",
|
||||||
|
"//java/com/google/copybara/format:buildifier_options",
|
||||||
|
"//java/com/google/copybara/util",
|
||||||
|
"//java/com/google/copybara/util/console",
|
||||||
|
"//third_party:flogger",
|
||||||
|
"//third_party:guava",
|
||||||
|
"//third_party:jcommander",
|
||||||
|
"//third_party:shell",
|
||||||
|
],
|
||||||
|
)
|
115
third_party/copybara/java/com/google/copybara/buildozer/BuildozerBatch.java
vendored
Normal file
115
third_party/copybara/java/com/google/copybara/buildozer/BuildozerBatch.java
vendored
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.buildozer;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.copybara.NonReversibleValidationException;
|
||||||
|
import com.google.copybara.TransformWork;
|
||||||
|
import com.google.copybara.Transformation;
|
||||||
|
import com.google.copybara.WorkflowOptions;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.buildozer.BuildozerOptions.BuildozerCommand;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A transformation that runs many buildozer transformation in batch
|
||||||
|
*/
|
||||||
|
public class BuildozerBatch implements BuildozerTransformation {
|
||||||
|
|
||||||
|
private final BuildozerOptions options;
|
||||||
|
private final WorkflowOptions workflowOptions;
|
||||||
|
private final Iterable<BuildozerTransformation> transformations;
|
||||||
|
|
||||||
|
private BuildozerBatch(BuildozerOptions options, WorkflowOptions workflowOptions,
|
||||||
|
Iterable<BuildozerTransformation> transformations) {
|
||||||
|
this.options = Preconditions.checkNotNull(options);
|
||||||
|
this.workflowOptions = Preconditions.checkNotNull(workflowOptions);
|
||||||
|
this.transformations = Preconditions.checkNotNull(transformations);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transform(TransformWork work) throws IOException, ValidationException {
|
||||||
|
Iterable<BuildozerCommand> commands = ImmutableList.of();
|
||||||
|
for (BuildozerTransformation transformation : transformations) {
|
||||||
|
transformation.beforeRun(work);
|
||||||
|
commands = Iterables.concat(commands, transformation.getCommands());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
options.run(work.getConsole(), work.getCheckoutDir(), commands, work.getIgnoreNoop());
|
||||||
|
} catch (TargetNotFoundException e) {
|
||||||
|
workflowOptions.reportNoop(work.getConsole(), e.getMessage(), work.getIgnoreNoop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transformation reverse() throws NonReversibleValidationException {
|
||||||
|
throw new IllegalStateException("Reverse should never be called for join transformations");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String describe() {
|
||||||
|
return "buildozer batch of " + Iterables.size(transformations) + " buildozer transformations";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canJoin(Transformation transformation) {
|
||||||
|
return isBuildozer(transformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isBuildozer(Transformation transformation) {
|
||||||
|
return transformation instanceof BuildozerTransformation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transformation join(Transformation next) {
|
||||||
|
return join(options, workflowOptions, this, (BuildozerTransformation) next);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BuildozerBatch join(BuildozerOptions buildozerOptions,
|
||||||
|
WorkflowOptions workflowOptions, BuildozerTransformation current,
|
||||||
|
BuildozerTransformation next) {
|
||||||
|
|
||||||
|
ImmutableList.Builder<BuildozerTransformation> transformationBuilder = ImmutableList.builder();
|
||||||
|
if (current instanceof BuildozerBatch) {
|
||||||
|
transformationBuilder.addAll(((BuildozerBatch) current).transformations);
|
||||||
|
} else {
|
||||||
|
transformationBuilder.add(current);
|
||||||
|
}
|
||||||
|
if (next instanceof BuildozerBatch) {
|
||||||
|
transformationBuilder.addAll(((BuildozerBatch) next).transformations);
|
||||||
|
} else {
|
||||||
|
transformationBuilder.add(next);
|
||||||
|
}
|
||||||
|
return new BuildozerBatch(buildozerOptions, workflowOptions, transformationBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeRun(TransformWork transformWork) throws ValidationException, IOException {
|
||||||
|
for (BuildozerTransformation transformation : transformations) {
|
||||||
|
transformation.beforeRun(transformWork);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<BuildozerCommand> getCommands() {
|
||||||
|
return Iterables.concat(
|
||||||
|
Iterables.transform(transformations, BuildozerTransformation::getCommands));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
182
third_party/copybara/java/com/google/copybara/buildozer/BuildozerCreate.java
vendored
Normal file
182
third_party/copybara/java/com/google/copybara/buildozer/BuildozerCreate.java
vendored
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.buildozer;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.copybara.TransformWork;
|
||||||
|
import com.google.copybara.Transformation;
|
||||||
|
import com.google.copybara.WorkflowOptions;
|
||||||
|
import com.google.copybara.buildozer.BuildozerOptions.BuildozerCommand;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.devtools.build.lib.syntax.EvalException;
|
||||||
|
import com.google.devtools.build.lib.syntax.Location;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A transformation which creates a new build target and reverses to delete the same target.
|
||||||
|
*/
|
||||||
|
public final class BuildozerCreate implements BuildozerTransformation {
|
||||||
|
|
||||||
|
private final Location location;
|
||||||
|
private final BuildozerOptions options;
|
||||||
|
private final WorkflowOptions workflowOptions;
|
||||||
|
private final Target target;
|
||||||
|
private final String ruleType;
|
||||||
|
private final RelativeTo relativeTo;
|
||||||
|
private final ImmutableList<String> commands;
|
||||||
|
|
||||||
|
static final class RelativeTo {
|
||||||
|
final String args;
|
||||||
|
|
||||||
|
// TODO(team): skylark. remove after migration.
|
||||||
|
RelativeTo(String before, String after) {
|
||||||
|
if (before != null) {
|
||||||
|
this.args = "before " + before;
|
||||||
|
} else if (after != null) {
|
||||||
|
this.args = "after " + after;
|
||||||
|
} else {
|
||||||
|
this.args = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateTargetName(Location location, String targetName)
|
||||||
|
throws EvalException {
|
||||||
|
if (targetName.contains(":")) {
|
||||||
|
throw new EvalException(location, String.format(
|
||||||
|
"unexpected : in target name (did you include the package by mistake?) - '%s'",
|
||||||
|
targetName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RelativeTo(Location location, String before, String after) throws EvalException {
|
||||||
|
if (!before.isEmpty() && !after.isEmpty()) {
|
||||||
|
throw new EvalException(
|
||||||
|
location, "cannot specify both 'before' and 'after' in the target create arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!before.isEmpty()) {
|
||||||
|
validateTargetName(location, before);
|
||||||
|
this.args = "before " + before;
|
||||||
|
} else if (!after.isEmpty()) {
|
||||||
|
validateTargetName(location, after);
|
||||||
|
this.args = "after " + after;
|
||||||
|
} else {
|
||||||
|
this.args = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildozerCreate(
|
||||||
|
Location location, BuildozerOptions options,
|
||||||
|
WorkflowOptions workflowOptions,
|
||||||
|
Target target,
|
||||||
|
String ruleType,
|
||||||
|
RelativeTo relativeTo,
|
||||||
|
// TODO(matvore): Accept Iterable<Command> once YAML support is removed.
|
||||||
|
Iterable<String> commands) {
|
||||||
|
this.location = location;
|
||||||
|
this.options = checkNotNull(options, "options");
|
||||||
|
this.workflowOptions = checkNotNull(workflowOptions, "workflowOptions");
|
||||||
|
this.target = checkNotNull(target, "target");
|
||||||
|
this.ruleType = checkNotNull(ruleType, "ruleType");
|
||||||
|
this.relativeTo = checkNotNull(relativeTo, "relativeTo");
|
||||||
|
this.commands = ImmutableList.copyOf(commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transform(TransformWork work) throws IOException, ValidationException {
|
||||||
|
beforeRun(work);
|
||||||
|
try {
|
||||||
|
options.run(work.getConsole(), work.getCheckoutDir(), getCommands(), work.getIgnoreNoop());
|
||||||
|
} catch (TargetNotFoundException e) {
|
||||||
|
// This should not happen for creation. If it happens, it is due to a file error.
|
||||||
|
throw new ValidationException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeRun(TransformWork work) throws ValidationException, IOException {
|
||||||
|
Path buildFilePath = work.getCheckoutDir().resolve(getTargetBuildFile());
|
||||||
|
if (!Files.exists(buildFilePath)) {
|
||||||
|
// Alert the user that the package to contain this target doesn't have a BUILD file, since
|
||||||
|
// this may be a configuration error.
|
||||||
|
work.getConsole().info(
|
||||||
|
String.format("BUILD file to contain %s doesn't exist. Creating now.", target));
|
||||||
|
Files.createDirectories(buildFilePath.getParent());
|
||||||
|
Files.write(buildFilePath, new byte[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTargetBuildFile() {
|
||||||
|
String pkg = target.getPackage();
|
||||||
|
// pkg can be empty (e.g. ":foo"), which should create targets in the workdir root, i.e. ./BUILD
|
||||||
|
return pkg + (pkg.isEmpty() ? "." : "") + "/BUILD";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<BuildozerCommand> getCommands() {
|
||||||
|
List<BuildozerCommand> result = new ArrayList<>();
|
||||||
|
result.add(new BuildozerCommand(ImmutableList.of(getTargetBuildFile()),
|
||||||
|
String.format("new %s %s %s", ruleType, target.getName(), relativeTo.args)));
|
||||||
|
for (String command : this.commands) {
|
||||||
|
result.add(new BuildozerCommand(ImmutableList.of(target.toString()), command));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canJoin(Transformation transformation) {
|
||||||
|
return BuildozerBatch.isBuildozer(transformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transformation join(Transformation next) {
|
||||||
|
return BuildozerBatch.join(options, workflowOptions, this, (BuildozerTransformation) next);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String describe() {
|
||||||
|
return "buildozer.create " + target;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BuildozerDelete reverse() {
|
||||||
|
return new BuildozerDelete(location, options, workflowOptions, target, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("target", target)
|
||||||
|
.add("ruleType", ruleType)
|
||||||
|
.add("relativeTo", relativeTo)
|
||||||
|
.add("commands", commands)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
103
third_party/copybara/java/com/google/copybara/buildozer/BuildozerDelete.java
vendored
Normal file
103
third_party/copybara/java/com/google/copybara/buildozer/BuildozerDelete.java
vendored
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.buildozer;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.copybara.NonReversibleValidationException;
|
||||||
|
import com.google.copybara.TransformWork;
|
||||||
|
import com.google.copybara.Transformation;
|
||||||
|
import com.google.copybara.WorkflowOptions;
|
||||||
|
import com.google.copybara.buildozer.BuildozerOptions.BuildozerCommand;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.devtools.build.lib.syntax.Location;
|
||||||
|
import java.io.IOException;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A transformation which deletes build target and reverses to create the same target.
|
||||||
|
*/
|
||||||
|
public final class BuildozerDelete implements BuildozerTransformation {
|
||||||
|
|
||||||
|
private final Location location;
|
||||||
|
private final BuildozerOptions options;
|
||||||
|
private final WorkflowOptions workflowOptions;
|
||||||
|
private final Target target;
|
||||||
|
@Nullable private final BuildozerCreate recreateAs;
|
||||||
|
|
||||||
|
BuildozerDelete(
|
||||||
|
Location location, BuildozerOptions options,
|
||||||
|
WorkflowOptions workflowOptions,
|
||||||
|
Target target,
|
||||||
|
@Nullable BuildozerCreate recreateAs) {
|
||||||
|
this.location = location;
|
||||||
|
this.options = checkNotNull(options, "options");
|
||||||
|
this.workflowOptions = checkNotNull(workflowOptions, "workflowOptions");
|
||||||
|
this.target = checkNotNull(target, "target");
|
||||||
|
this.recreateAs = recreateAs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transform(TransformWork work)
|
||||||
|
throws IOException, ValidationException {
|
||||||
|
try {
|
||||||
|
options.run(work.getConsole(), work.getCheckoutDir(), getCommands(), work.getIgnoreNoop());
|
||||||
|
} catch (TargetNotFoundException e) {
|
||||||
|
workflowOptions.reportNoop(work.getConsole(), e.getMessage(), work.getIgnoreNoop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String describe() {
|
||||||
|
return "buildozer.delete " + target;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canJoin(Transformation transformation) {
|
||||||
|
return BuildozerBatch.isBuildozer(transformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transformation join(Transformation next) {
|
||||||
|
return BuildozerBatch.join(options, workflowOptions, this, (BuildozerTransformation) next);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<BuildozerCommand> getCommands() {
|
||||||
|
return ImmutableList.of(new BuildozerCommand(target.toString(), "delete"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BuildozerCreate reverse() throws NonReversibleValidationException {
|
||||||
|
if (recreateAs == null) {
|
||||||
|
throw new NonReversibleValidationException(location,
|
||||||
|
"This buildozer.delete is not reversible. Please specify at least rule_type to make it"
|
||||||
|
+ " reversible.");
|
||||||
|
}
|
||||||
|
return recreateAs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("target", target)
|
||||||
|
.add("recreateAs", recreateAs)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
105
third_party/copybara/java/com/google/copybara/buildozer/BuildozerModify.java
vendored
Normal file
105
third_party/copybara/java/com/google/copybara/buildozer/BuildozerModify.java
vendored
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.buildozer;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.copybara.NonReversibleValidationException;
|
||||||
|
import com.google.copybara.TransformWork;
|
||||||
|
import com.google.copybara.Transformation;
|
||||||
|
import com.google.copybara.WorkflowOptions;
|
||||||
|
import com.google.copybara.buildozer.BuildozerOptions.BuildozerCommand;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A transformation which runs one or more commands against a single target.
|
||||||
|
*/
|
||||||
|
public final class BuildozerModify implements BuildozerTransformation {
|
||||||
|
|
||||||
|
private final BuildozerOptions options;
|
||||||
|
private final WorkflowOptions workflowOptions;
|
||||||
|
private final List<Target> targets;
|
||||||
|
private final ImmutableList<Command> commands;
|
||||||
|
|
||||||
|
BuildozerModify(
|
||||||
|
BuildozerOptions options,
|
||||||
|
WorkflowOptions workflowOptions,
|
||||||
|
List<Target> targets,
|
||||||
|
Iterable<Command> commands) {
|
||||||
|
this.options = checkNotNull(options, "options");
|
||||||
|
this.workflowOptions = checkNotNull(workflowOptions, "workflowOptions");
|
||||||
|
this.targets = checkNotNull(targets, "target");
|
||||||
|
this.commands = ImmutableList.copyOf(commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transform(TransformWork work) throws IOException, ValidationException {
|
||||||
|
Console console = work.getConsole();
|
||||||
|
try {
|
||||||
|
options.run(console, work.getCheckoutDir(), getCommands(), work.getIgnoreNoop());
|
||||||
|
} catch (TargetNotFoundException e) {
|
||||||
|
workflowOptions.reportNoop(console, e.getMessage(), work.getIgnoreNoop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BuildozerModify reverse() throws NonReversibleValidationException {
|
||||||
|
List<Command> reverseCommands = new ArrayList<>();
|
||||||
|
for (Command command : commands.reverse()) {
|
||||||
|
reverseCommands.add(command.reverse());
|
||||||
|
}
|
||||||
|
return new BuildozerModify(options, workflowOptions, targets, reverseCommands);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<BuildozerCommand> getCommands() {
|
||||||
|
List<BuildozerCommand> result = new ArrayList<>();
|
||||||
|
for (Command command : commands) {
|
||||||
|
result.add(new BuildozerCommand(Target.asStringList(targets), command.toString()));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canJoin(Transformation transformation) {
|
||||||
|
return BuildozerBatch.isBuildozer(transformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transformation join(Transformation next) {
|
||||||
|
return BuildozerBatch.join(options, workflowOptions, this, (BuildozerTransformation) next);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String describe() {
|
||||||
|
return "buildozer.modify " + targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("target", targets)
|
||||||
|
.add("commands", commands)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
327
third_party/copybara/java/com/google/copybara/buildozer/BuildozerModule.java
vendored
Normal file
327
third_party/copybara/java/com/google/copybara/buildozer/BuildozerModule.java
vendored
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.buildozer;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.copybara.WorkflowOptions;
|
||||||
|
import com.google.copybara.config.SkylarkUtil;
|
||||||
|
import com.google.copybara.doc.annotations.Example;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.Param;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.ParamType;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkMethod;
|
||||||
|
import com.google.devtools.build.lib.syntax.EvalException;
|
||||||
|
import com.google.devtools.build.lib.syntax.Location;
|
||||||
|
import com.google.devtools.build.lib.syntax.Sequence;
|
||||||
|
import com.google.devtools.build.lib.syntax.Starlark;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkThread;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Skylark module for Buildozer-related functionality. */
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "buildozer",
|
||||||
|
doc =
|
||||||
|
"Module for Buildozer-related functionality such as creating and modifying BUILD targets.",
|
||||||
|
category = StarlarkDocumentationCategory.BUILTIN)
|
||||||
|
public final class BuildozerModule implements StarlarkValue {
|
||||||
|
|
||||||
|
private final BuildozerOptions buildozerOptions;
|
||||||
|
private final WorkflowOptions workflowOptions;
|
||||||
|
|
||||||
|
public BuildozerModule(WorkflowOptions workflowOptions, BuildozerOptions buildozerOptions) {
|
||||||
|
this.workflowOptions = Preconditions.checkNotNull(workflowOptions);
|
||||||
|
this.buildozerOptions = Preconditions.checkNotNull(buildozerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ImmutableList<Target> getTargetList(Location location, Object arg)
|
||||||
|
throws EvalException {
|
||||||
|
if (arg instanceof String) {
|
||||||
|
return ImmutableList.of(Target.fromConfig(location, (String) arg));
|
||||||
|
} else {
|
||||||
|
ImmutableList.Builder<Target> builder = ImmutableList.builder();
|
||||||
|
for (String target : SkylarkUtil.convertStringList(arg, "target")) {
|
||||||
|
builder.add(Target.fromConfig(location, target));
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ImmutableList<Command> coerceCommandList(Location location, Iterable<?> commands)
|
||||||
|
throws EvalException {
|
||||||
|
ImmutableList.Builder<Command> wrappedCommands = new ImmutableList.Builder<>();
|
||||||
|
for (Object command : commands) {
|
||||||
|
if (command instanceof String) {
|
||||||
|
wrappedCommands.add(Command.fromConfig(location, (String) command, /*reverse*/ null));
|
||||||
|
} else if (command instanceof Command) {
|
||||||
|
wrappedCommands.add((Command) command);
|
||||||
|
} else {
|
||||||
|
throw Starlark.errorf("Expected a string or buildozer.cmd, but got: %s", command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wrappedCommands.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "create",
|
||||||
|
doc =
|
||||||
|
"A transformation which creates a new build target and populates its "
|
||||||
|
+ "attributes. This transform can reverse automatically to delete the target.",
|
||||||
|
parameters = {
|
||||||
|
@Param(
|
||||||
|
name = "target",
|
||||||
|
type = String.class,
|
||||||
|
doc =
|
||||||
|
"Target to create, including the package, e.g. 'foo:bar'. The package can be "
|
||||||
|
+ "'.' for the root BUILD file.",
|
||||||
|
named = true),
|
||||||
|
@Param(
|
||||||
|
name = "rule_type",
|
||||||
|
type = String.class,
|
||||||
|
doc = "Type of this rule, for instance, java_library.",
|
||||||
|
named = true),
|
||||||
|
@Param(
|
||||||
|
name = "commands",
|
||||||
|
type = Sequence.class,
|
||||||
|
doc =
|
||||||
|
"Commands to populate attributes of the target after creating it. Elements can"
|
||||||
|
+ " be strings such as 'add deps :foo' or objects returned by buildozer.cmd.",
|
||||||
|
defaultValue = "[]",
|
||||||
|
named = true),
|
||||||
|
@Param(
|
||||||
|
name = "before",
|
||||||
|
type = String.class,
|
||||||
|
doc =
|
||||||
|
"When supplied, causes this target to be created *before* the target named by"
|
||||||
|
+ " 'before'",
|
||||||
|
positional = false,
|
||||||
|
defaultValue = "''",
|
||||||
|
named = true),
|
||||||
|
@Param(
|
||||||
|
name = "after",
|
||||||
|
type = String.class,
|
||||||
|
doc =
|
||||||
|
"When supplied, causes this target to be created *after* the target named by"
|
||||||
|
+ " 'after'",
|
||||||
|
positional = false,
|
||||||
|
defaultValue = "''",
|
||||||
|
named = true),
|
||||||
|
},
|
||||||
|
useStarlarkThread = true)
|
||||||
|
public BuildozerCreate create(
|
||||||
|
String target,
|
||||||
|
String ruleType,
|
||||||
|
Sequence<?> commands,
|
||||||
|
String before,
|
||||||
|
String after,
|
||||||
|
StarlarkThread thread)
|
||||||
|
throws EvalException {
|
||||||
|
Location location = thread.getCallerLocation();
|
||||||
|
List<String> commandStrings = new ArrayList<>();
|
||||||
|
for (Object command : coerceCommandList(location, commands)) {
|
||||||
|
commandStrings.add(command.toString());
|
||||||
|
}
|
||||||
|
return new BuildozerCreate(
|
||||||
|
location,
|
||||||
|
buildozerOptions,
|
||||||
|
workflowOptions,
|
||||||
|
Target.fromConfig(location, target),
|
||||||
|
ruleType,
|
||||||
|
new BuildozerCreate.RelativeTo(location, before, after),
|
||||||
|
commandStrings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mustOmitRecreateParam(Object expected, Object actual, String paramName)
|
||||||
|
throws EvalException {
|
||||||
|
if (!expected.equals(actual)) {
|
||||||
|
throw Starlark.errorf(
|
||||||
|
"Parameter '%s' is only used for reversible buildozer.delete transforms, but this"
|
||||||
|
+ " buildozer.delete is not reversible. Specify 'rule_type' argument to make it"
|
||||||
|
+ " reversible.",
|
||||||
|
paramName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "delete",
|
||||||
|
doc =
|
||||||
|
"A transformation which is the opposite of creating a build target. When run normally,"
|
||||||
|
+ " it deletes a build target. When reversed, it creates and prepares one.",
|
||||||
|
parameters = {
|
||||||
|
@Param(
|
||||||
|
name = "target",
|
||||||
|
type = String.class,
|
||||||
|
doc = "Target to create, including the package, e.g. 'foo:bar'",
|
||||||
|
named = true),
|
||||||
|
@Param(
|
||||||
|
name = "rule_type",
|
||||||
|
type = String.class,
|
||||||
|
doc =
|
||||||
|
"Type of this rule, for instance, java_library. Supplying this will cause this"
|
||||||
|
+ " transformation to be reversible.",
|
||||||
|
defaultValue = "''",
|
||||||
|
named = true),
|
||||||
|
@Param(
|
||||||
|
name = "recreate_commands",
|
||||||
|
type = Sequence.class,
|
||||||
|
doc =
|
||||||
|
"Commands to populate attributes of the target after creating it. Elements can"
|
||||||
|
+ " be strings such as 'add deps :foo' or objects returned by buildozer.cmd.",
|
||||||
|
positional = false,
|
||||||
|
defaultValue = "[]",
|
||||||
|
named = true),
|
||||||
|
@Param(
|
||||||
|
name = "before",
|
||||||
|
type = String.class,
|
||||||
|
doc =
|
||||||
|
"When supplied with rule_type and the transformation is reversed, causes this"
|
||||||
|
+ " target to be created *before* the target named by 'before'",
|
||||||
|
positional = false,
|
||||||
|
defaultValue = "''",
|
||||||
|
named = true),
|
||||||
|
@Param(
|
||||||
|
name = "after",
|
||||||
|
type = String.class,
|
||||||
|
doc =
|
||||||
|
"When supplied with rule_type and the transformation is reversed, causes this"
|
||||||
|
+ " target to be created *after* the target named by 'after'",
|
||||||
|
positional = false,
|
||||||
|
defaultValue = "''",
|
||||||
|
named = true),
|
||||||
|
},
|
||||||
|
useStarlarkThread = true)
|
||||||
|
public BuildozerDelete delete(
|
||||||
|
String targetString,
|
||||||
|
String ruleType,
|
||||||
|
Sequence<?> recreateCommands,
|
||||||
|
String before,
|
||||||
|
String after,
|
||||||
|
StarlarkThread thread)
|
||||||
|
throws EvalException {
|
||||||
|
Location location = thread.getCallerLocation();
|
||||||
|
List<String> commandStrings = new ArrayList<>();
|
||||||
|
for (Object command : coerceCommandList(location, recreateCommands)) {
|
||||||
|
commandStrings.add(command.toString());
|
||||||
|
}
|
||||||
|
BuildozerCreate recreateAs;
|
||||||
|
Target target = Target.fromConfig(location, targetString);
|
||||||
|
if (ruleType.isEmpty()) {
|
||||||
|
recreateAs = null;
|
||||||
|
mustOmitRecreateParam(ImmutableList.of(), recreateCommands, "recreate_commands");
|
||||||
|
mustOmitRecreateParam("", before, "before");
|
||||||
|
mustOmitRecreateParam("", after, "after");
|
||||||
|
} else {
|
||||||
|
recreateAs = new BuildozerCreate(
|
||||||
|
location, buildozerOptions,
|
||||||
|
workflowOptions,
|
||||||
|
target,
|
||||||
|
ruleType,
|
||||||
|
new BuildozerCreate.RelativeTo(location, before, after),
|
||||||
|
commandStrings);
|
||||||
|
}
|
||||||
|
return new BuildozerDelete(location, buildozerOptions, workflowOptions, target,
|
||||||
|
recreateAs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "modify",
|
||||||
|
doc =
|
||||||
|
"A transformation which runs one or more Buildozer commands against a single"
|
||||||
|
+ " target expression. See http://go/buildozer for details on supported commands and"
|
||||||
|
+ " target expression formats.",
|
||||||
|
parameters = {
|
||||||
|
@Param(
|
||||||
|
name = "target",
|
||||||
|
allowedTypes = {
|
||||||
|
@ParamType(type = String.class),
|
||||||
|
@ParamType(type = Sequence.class, generic1 = String.class)
|
||||||
|
},
|
||||||
|
doc = "Specifies the target(s) against which to apply the commands. Can be a list.",
|
||||||
|
named = true),
|
||||||
|
@Param(
|
||||||
|
name = "commands",
|
||||||
|
type = Sequence.class,
|
||||||
|
doc =
|
||||||
|
"Commands to apply to the target(s) specified. Elements can"
|
||||||
|
+ " be strings such as 'add deps :foo' or objects returned by buildozer.cmd.",
|
||||||
|
named = true),
|
||||||
|
},
|
||||||
|
useStarlarkThread = true)
|
||||||
|
@Example(
|
||||||
|
title = "Add a setting to one target",
|
||||||
|
before = "Add \"config = ':foo'\" to foo/bar:baz:",
|
||||||
|
code =
|
||||||
|
"buildozer.modify(\n"
|
||||||
|
+ " target = 'foo/bar:baz',\n"
|
||||||
|
+ " commands = [\n"
|
||||||
|
+ " buildozer.cmd('set config \":foo\"'),\n"
|
||||||
|
+ " ],\n"
|
||||||
|
+ ")")
|
||||||
|
@Example(
|
||||||
|
title = "Add a setting to several targets",
|
||||||
|
before = "Add \"config = ':foo'\" to foo/bar:baz and foo/bar:fooz:",
|
||||||
|
code =
|
||||||
|
"buildozer.modify(\n"
|
||||||
|
+ " target = ['foo/bar:baz', 'foo/bar:fooz'],\n"
|
||||||
|
+ " commands = [\n"
|
||||||
|
+ " buildozer.cmd('set config \":foo\"'),\n"
|
||||||
|
+ " ],\n"
|
||||||
|
+ ")")
|
||||||
|
public BuildozerModify modify(Object target, Sequence<?> commands, StarlarkThread thread)
|
||||||
|
throws EvalException {
|
||||||
|
if (commands.isEmpty()) {
|
||||||
|
throw Starlark.errorf("at least one element required in 'commands' argument");
|
||||||
|
}
|
||||||
|
Location location = thread.getCallerLocation();
|
||||||
|
return new BuildozerModify(
|
||||||
|
buildozerOptions, workflowOptions, getTargetList(location, target),
|
||||||
|
coerceCommandList(location, commands));
|
||||||
|
}
|
||||||
|
|
||||||
|
@StarlarkMethod(
|
||||||
|
name = "cmd",
|
||||||
|
doc =
|
||||||
|
"Creates a Buildozer command. You can specify the reversal with the 'reverse' "
|
||||||
|
+ "argument.",
|
||||||
|
parameters = {
|
||||||
|
@Param(
|
||||||
|
name = "forward",
|
||||||
|
type = String.class,
|
||||||
|
doc = "Specifies the Buildozer command, e.g. 'replace deps :foo :bar'",
|
||||||
|
named = true),
|
||||||
|
@Param(
|
||||||
|
name = "reverse",
|
||||||
|
type = String.class,
|
||||||
|
doc =
|
||||||
|
"The reverse of the command. This is only required if the given command cannot be"
|
||||||
|
+ " reversed automatically and the reversal of this command is required by"
|
||||||
|
+ " some workflow or Copybara check. The following commands are automatically"
|
||||||
|
+ " reversible:<br><ul><li>add</li><li>remove (when used to remove element"
|
||||||
|
+ " from list i.e. 'remove srcs foo.cc'</li><li>replace</li></ul>",
|
||||||
|
noneable = true,
|
||||||
|
defaultValue = "None",
|
||||||
|
named = true),
|
||||||
|
},
|
||||||
|
useStarlarkThread = true)
|
||||||
|
public Command cmd(String forward, Object reverse, StarlarkThread thread) throws EvalException {
|
||||||
|
return Command.fromConfig(
|
||||||
|
thread.getCallerLocation(), forward, SkylarkUtil.convertOptionalString(reverse));
|
||||||
|
}
|
||||||
|
}
|
181
third_party/copybara/java/com/google/copybara/buildozer/BuildozerOptions.java
vendored
Normal file
181
third_party/copybara/java/com/google/copybara/buildozer/BuildozerOptions.java
vendored
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.buildozer;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
import com.beust.jcommander.Parameter;
|
||||||
|
import com.beust.jcommander.Parameters;
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
|
import com.google.copybara.GeneralOptions;
|
||||||
|
import com.google.copybara.Option;
|
||||||
|
import com.google.copybara.WorkflowOptions;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.format.BuildifierOptions;
|
||||||
|
import com.google.copybara.util.BadExitStatusWithOutputException;
|
||||||
|
import com.google.copybara.util.CommandOutput;
|
||||||
|
import com.google.copybara.util.CommandOutputWithStatus;
|
||||||
|
import com.google.copybara.util.CommandRunner;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import com.google.copybara.util.console.Consoles;
|
||||||
|
import com.google.copybara.shell.Command;
|
||||||
|
import com.google.copybara.shell.CommandException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/** Specifies how Buildozer is executed. */
|
||||||
|
@Parameters(separators = "=")
|
||||||
|
public final class BuildozerOptions implements Option {
|
||||||
|
|
||||||
|
private final GeneralOptions generalOptions;
|
||||||
|
private final BuildifierOptions buildifierOptions;
|
||||||
|
private final WorkflowOptions workflowOptions;
|
||||||
|
|
||||||
|
public BuildozerOptions(GeneralOptions generalOptions,
|
||||||
|
BuildifierOptions buildifierOptions, WorkflowOptions workflowOptions) {
|
||||||
|
this.generalOptions = Preconditions.checkNotNull(generalOptions);
|
||||||
|
this.buildifierOptions = Preconditions.checkNotNull(buildifierOptions);
|
||||||
|
this.workflowOptions = workflowOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
private static final Pattern targetNotFound =
|
||||||
|
Pattern.compile(".*error while executing commands \\[.+\\] on target (?<error>.* not found)");
|
||||||
|
|
||||||
|
@Parameter(names = "--buildozer-bin",
|
||||||
|
description = "Binary to use for buildozer (Default is /usr/bin/buildozer)",
|
||||||
|
hidden = true)
|
||||||
|
public String buildozerBin = "/usr/bin/buildozer";
|
||||||
|
|
||||||
|
private void logError(Console console, CommandOutput output) {
|
||||||
|
Consoles.errorLogLines(console, "buildozer stdout: ", output.getStdout());
|
||||||
|
Consoles.errorLogLines(console, "buildozer stderr: ", output.getStderr());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class BuildozerCommand {
|
||||||
|
|
||||||
|
private final List<String> targets;
|
||||||
|
private final String cmd;
|
||||||
|
|
||||||
|
BuildozerCommand(List<String> targets, String cmd) {
|
||||||
|
this.targets = Preconditions.checkNotNull(targets);
|
||||||
|
this.cmd = Preconditions.checkNotNull(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildozerCommand(String targets, String cmd) {
|
||||||
|
this.targets = ImmutableList.of(Preconditions.checkNotNull(targets));
|
||||||
|
this.cmd = Preconditions.checkNotNull(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return cmd + "|" + Joiner.on('|').join(targets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs buildozer with the given commands.
|
||||||
|
*/
|
||||||
|
void run(Console console, Path checkoutDir, Iterable<BuildozerCommand> commands,
|
||||||
|
boolean ignoreNoop)
|
||||||
|
throws ValidationException, TargetNotFoundException {
|
||||||
|
List<String> args = Lists.newArrayList(
|
||||||
|
buildozerBin, "-buildifier=" + buildifierOptions.buildifierBin);
|
||||||
|
|
||||||
|
// We only use -k in keep going mode because it shows less errors (http://b/69386431)
|
||||||
|
if (workflowOptions.ignoreNoop || ignoreNoop) {
|
||||||
|
args.add("-k");
|
||||||
|
}
|
||||||
|
args.add("-f");
|
||||||
|
args.add("-");
|
||||||
|
try {
|
||||||
|
Command cmd =
|
||||||
|
new Command(
|
||||||
|
args.toArray(new String[0]), /*environmentVariables*/ null, checkoutDir.toFile());
|
||||||
|
CommandOutputWithStatus output = new CommandRunner(cmd)
|
||||||
|
.withVerbose(generalOptions.isVerbose())
|
||||||
|
.withInput(Joiner.on('\n').join(commands).getBytes(UTF_8))
|
||||||
|
.execute();
|
||||||
|
if (!output.getStdout().isEmpty()) {
|
||||||
|
logger.atInfo().log("buildozer stdout: " + output.getStdout());
|
||||||
|
}
|
||||||
|
if (!output.getStderr().isEmpty()) {
|
||||||
|
logger.atInfo().log("buildozer stderr: " + output.getStderr());
|
||||||
|
}
|
||||||
|
} catch (BadExitStatusWithOutputException e) {
|
||||||
|
// Don't print the output for common/known errors.
|
||||||
|
if (generalOptions.isVerbose()) {
|
||||||
|
logError(console, e.getOutput());
|
||||||
|
}
|
||||||
|
if (e.getResult().getTerminationStatus().getExitCode() == 3) {
|
||||||
|
// Buildozer exits with code == 3 when the build file was not modified and no output
|
||||||
|
// was generated. This happens with expressions that match multiple targets, like
|
||||||
|
// :%java_library
|
||||||
|
throw new TargetNotFoundException(
|
||||||
|
commandsMessage("Buildozer could not find a target for", commands));
|
||||||
|
}
|
||||||
|
if (e.getResult().getTerminationStatus().getExitCode() == 2) {
|
||||||
|
ImmutableList<String> errors =
|
||||||
|
Splitter.on('\n').splitToList(e.getOutput().getStderr()).stream()
|
||||||
|
.filter(s -> !(s.isEmpty() || s.startsWith("fixed ")))
|
||||||
|
.collect(ImmutableList.toImmutableList());
|
||||||
|
ImmutableList.Builder<String> notFoundMsg = ImmutableList.builder();
|
||||||
|
boolean allNotFound = true;
|
||||||
|
for (String error : errors) {
|
||||||
|
Matcher matcher = targetNotFound.matcher(error);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
notFoundMsg.add(
|
||||||
|
String.format("Buildozer could not find a target for %s", matcher.group(1)));
|
||||||
|
} else if (error.contains("no such file or directory")
|
||||||
|
|| error.contains("not a directory")) {
|
||||||
|
notFoundMsg.add("Buildozer could not find build file: " + error);
|
||||||
|
} else {
|
||||||
|
allNotFound = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allNotFound) {
|
||||||
|
throw new TargetNotFoundException(Joiner.on("\n").join(notFoundMsg.build()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Otherwise we have already printed above.
|
||||||
|
if (!generalOptions.isVerbose()) {
|
||||||
|
logError(console, e.getOutput());
|
||||||
|
}
|
||||||
|
throw new ValidationException(String.format(
|
||||||
|
"%s\nCommand stderr:%s",
|
||||||
|
commandsMessage("Failed to execute buildozer with args", commands),
|
||||||
|
e.getOutput().getStderr()),
|
||||||
|
e);
|
||||||
|
} catch (CommandException e) {
|
||||||
|
String message = String.format("Error '%s' running buildozer command: %s",
|
||||||
|
e.getMessage(), e.getCommand().toDebugString());
|
||||||
|
console.error(message);
|
||||||
|
throw new ValidationException(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String commandsMessage(final String prefix, Iterable<BuildozerCommand> commands) {
|
||||||
|
return prefix + ":\n " + Joiner.on("\n ").join(commands);
|
||||||
|
}
|
||||||
|
}
|
41
third_party/copybara/java/com/google/copybara/buildozer/BuildozerTransformation.java
vendored
Normal file
41
third_party/copybara/java/com/google/copybara/buildozer/BuildozerTransformation.java
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.buildozer;
|
||||||
|
|
||||||
|
import com.google.copybara.TransformWork;
|
||||||
|
import com.google.copybara.Transformation;
|
||||||
|
import com.google.copybara.exception.ValidationException;
|
||||||
|
import com.google.copybara.buildozer.BuildozerOptions.BuildozerCommand;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common interface implemented by all buildozer transformations.
|
||||||
|
*
|
||||||
|
* Used by {@link BuildozerBatch} to batch multiple invocations in one buildozer cli call.
|
||||||
|
*/
|
||||||
|
public interface BuildozerTransformation extends Transformation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions to run before calling buildozer. For example creating files.
|
||||||
|
*/
|
||||||
|
default void beforeRun(TransformWork work) throws ValidationException, IOException {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of commands to execute
|
||||||
|
*/
|
||||||
|
Iterable<BuildozerCommand> getCommands();
|
||||||
|
}
|
195
third_party/copybara/java/com/google/copybara/buildozer/Command.java
vendored
Normal file
195
third_party/copybara/java/com/google/copybara/buildozer/Command.java
vendored
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.buildozer;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.copybara.NonReversibleValidationException;
|
||||||
|
import com.google.devtools.build.lib.syntax.EvalException;
|
||||||
|
import com.google.devtools.build.lib.syntax.Location;
|
||||||
|
import com.google.devtools.build.lib.syntax.Printer;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/** Represents a possibly-reversible Buildozer command. */
|
||||||
|
public final class Command implements StarlarkValue {
|
||||||
|
|
||||||
|
private Location location;
|
||||||
|
private final String command;
|
||||||
|
@Nullable
|
||||||
|
private final String reverse;
|
||||||
|
|
||||||
|
private Command(Location location, String command, @Nullable String reverse) {
|
||||||
|
this.location = location;
|
||||||
|
this.command = checkNotNull(command, "command");
|
||||||
|
Preconditions.checkArgument(!command.trim().isEmpty(), "Found empty command");
|
||||||
|
Preconditions.checkArgument(reverse == null || !reverse.trim().isEmpty(),
|
||||||
|
"Found empty reversal command. Command was: %s", command);
|
||||||
|
|
||||||
|
this.reverse = reverse;
|
||||||
|
new ArgValidator(command).validate();
|
||||||
|
if (reverse != null) {
|
||||||
|
new ArgValidator(reverse).validate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Command fromConfig(Location location, String command, @Nullable String reverse)
|
||||||
|
throws EvalException {
|
||||||
|
if (reverse == null) {
|
||||||
|
List<String> components = Splitter.on(' ').limit(2).omitEmptyStrings().splitToList(command);
|
||||||
|
if (components.size() == 2) {
|
||||||
|
reverse = Command.reverse(components.get(0), components.get(1), location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new Command(location, command, reverse);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new EvalException(location, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ArgValidator {
|
||||||
|
final List<String> argv;
|
||||||
|
|
||||||
|
ArgValidator(String command) {
|
||||||
|
this.argv = splitArgv(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
void validateCount(boolean valid, String requirement) {
|
||||||
|
Preconditions.checkArgument(valid,
|
||||||
|
"'%s' requires %s, but got: %s", argv.get(0), requirement, argCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
int argCount() {
|
||||||
|
return argv.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void validate() {
|
||||||
|
Preconditions.checkArgument(!argv.isEmpty(), "Expected an operation, but got empty string.");
|
||||||
|
switch (argv.get(0)) {
|
||||||
|
case "del_subinclude":
|
||||||
|
case "rename":
|
||||||
|
case "copy":
|
||||||
|
case "copy_no_overwrite":
|
||||||
|
validateCount(argCount() == 2, "exactly 2 arguments");
|
||||||
|
break;
|
||||||
|
case "fix":
|
||||||
|
case "print":
|
||||||
|
case "remove_comment":
|
||||||
|
break; // can take 0+
|
||||||
|
case "replace_subinclude":
|
||||||
|
case "move":
|
||||||
|
validateCount(argCount() >= 3, "at least 3 arguments");
|
||||||
|
break;
|
||||||
|
case "delete":
|
||||||
|
validateCount(argCount() == 0, "exactly 0 arguments");
|
||||||
|
break;
|
||||||
|
case "replace":
|
||||||
|
validateCount(argCount() == 3, "exactly 3 arguments");
|
||||||
|
break;
|
||||||
|
case "comment":
|
||||||
|
case "remove":
|
||||||
|
validateCount(argCount() >= 1, "at least 1 argument");
|
||||||
|
break;
|
||||||
|
case "add":
|
||||||
|
case "new_load":
|
||||||
|
case "new":
|
||||||
|
case "set":
|
||||||
|
case "set_if_absent":
|
||||||
|
validateCount(argCount() >= 2, "at least 2 arguments");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// We assume that all unary operations are covered.
|
||||||
|
Preconditions.checkArgument(argCount() > 1,
|
||||||
|
"Expected an operation, but got '%s'.", argv.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isImmutable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void repr(Printer printer) {
|
||||||
|
printer.format("buildozer.cmd(%s, reverse = %s)", command, reverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the command and arguments concatenated, which can be passed directly to Buildozer.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the reverse version of this command.
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException if this instance is not reversible
|
||||||
|
*/
|
||||||
|
Command reverse() throws NonReversibleValidationException {
|
||||||
|
if (reverse == null) {
|
||||||
|
throw new NonReversibleValidationException(location,
|
||||||
|
"The current command is not auto-reversible and a reverse was not provided: " + command);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Command(location, reverse, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the reversal a command whose reversal has not been manually-specified.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static String reverse(String commandName, String args, Location location)
|
||||||
|
throws EvalException {
|
||||||
|
switch (commandName) {
|
||||||
|
case "add":
|
||||||
|
return "remove " + args;
|
||||||
|
case "remove":
|
||||||
|
if (args.contains(" ")) {
|
||||||
|
// Do not reverse 'remove attr' operation. Only 'remove attr value'
|
||||||
|
return "add " + args;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
case "replace":
|
||||||
|
List<String> reverseArgs = new ArrayList<>(splitArgv(args));
|
||||||
|
if (reverseArgs.size() != 3) {
|
||||||
|
throw new EvalException(location,
|
||||||
|
String.format("Cannot reverse '%s %s', expected three arguments, but found %d.",
|
||||||
|
commandName, args, reverseArgs.size()));
|
||||||
|
}
|
||||||
|
Collections.swap(reverseArgs, 1, 2);
|
||||||
|
return "replace " + Joiner.on(' ').join(reverseArgs);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> splitArgv(String argv) {
|
||||||
|
return Splitter.on(' ')
|
||||||
|
.omitEmptyStrings()
|
||||||
|
.splitToList(argv);
|
||||||
|
}
|
||||||
|
}
|
80
third_party/copybara/java/com/google/copybara/buildozer/Target.java
vendored
Normal file
80
third_party/copybara/java/com/google/copybara/buildozer/Target.java
vendored
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.buildozer;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.devtools.build.lib.syntax.EvalException;
|
||||||
|
import com.google.devtools.build.lib.syntax.Location;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies a target, including the package and name of target.
|
||||||
|
*/
|
||||||
|
final class Target {
|
||||||
|
|
||||||
|
private static final Pattern TARGET_NAME_PATTERN = Pattern.compile("[^:]*:[^:]+");
|
||||||
|
|
||||||
|
private final String pkg;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private Target(String[] components) {
|
||||||
|
checkArgument(components.length == 2, "%s", Arrays.asList(components));
|
||||||
|
|
||||||
|
this.pkg = checkNotNull(components[0], "pkg");
|
||||||
|
this.name = checkNotNull(components[1], "name");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPackage() {
|
||||||
|
return pkg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return pkg + ":" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a target specified in configuration.
|
||||||
|
*
|
||||||
|
* @param location where the target string appears in the configuration.
|
||||||
|
* @param configString target specified in the form {@code PKG:TARGET_NAME}
|
||||||
|
* @throws EvalException if {@code configString} is not formatted correctly
|
||||||
|
*/
|
||||||
|
static Target fromConfig(Location location, String configString) throws EvalException {
|
||||||
|
if (configString.startsWith("/")) {
|
||||||
|
throw new EvalException(location, "target must be relative and not start with '/' or '//'");
|
||||||
|
}
|
||||||
|
if (!TARGET_NAME_PATTERN.matcher(configString).matches()) {
|
||||||
|
throw new EvalException(
|
||||||
|
location, "target must be in the form of {PKG}:{TARGET_NAME}, e.g. foo/bar:baz");
|
||||||
|
}
|
||||||
|
return new Target(configString.split(":", 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> asStringList(List<Target> targets) {
|
||||||
|
return targets.stream().map(t -> t.toString()).collect(ImmutableList.toImmutableList());
|
||||||
|
}
|
||||||
|
}
|
25
third_party/copybara/java/com/google/copybara/buildozer/TargetNotFoundException.java
vendored
Normal file
25
third_party/copybara/java/com/google/copybara/buildozer/TargetNotFoundException.java
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.buildozer;
|
||||||
|
|
||||||
|
/** Error thrown when a target couldn't be found during a Buildozer transformation */
|
||||||
|
public class TargetNotFoundException extends Exception {
|
||||||
|
|
||||||
|
TargetNotFoundException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
46
third_party/copybara/java/com/google/copybara/buildozer/testing/BUILD
vendored
Normal file
46
third_party/copybara/java/com/google/copybara/buildozer/testing/BUILD
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Copyright 2016 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
genrule(
|
||||||
|
name = "normalised_buildozer",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = ["@buildtools//buildozer"],
|
||||||
|
outs = ["buildozer"],
|
||||||
|
cmd = "cp $(SRCS) $@",
|
||||||
|
)
|
||||||
|
|
||||||
|
genrule(
|
||||||
|
name = "normalised_buildifier",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = ["@buildtools//buildifier"],
|
||||||
|
outs = ["buildifier"],
|
||||||
|
cmd = "cp $(SRCS) $@",
|
||||||
|
)
|
||||||
|
|
||||||
|
java_library(
|
||||||
|
name = "testing",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = glob(["*.java"]),
|
||||||
|
data = [
|
||||||
|
":normalised_buildifier",
|
||||||
|
":normalised_buildozer",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//java/com/google/copybara/testing",
|
||||||
|
],
|
||||||
|
)
|
45
third_party/copybara/java/com/google/copybara/buildozer/testing/BuildozerTesting.java
vendored
Normal file
45
third_party/copybara/java/com/google/copybara/buildozer/testing/BuildozerTesting.java
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.buildozer.testing;
|
||||||
|
|
||||||
|
import com.google.copybara.testing.OptionsBuilder;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for testing Buildozer.
|
||||||
|
*/
|
||||||
|
public final class BuildozerTesting {
|
||||||
|
|
||||||
|
private BuildozerTesting() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows Buildozer to be executed for transformation instances constructed with the given {@code
|
||||||
|
* options}.
|
||||||
|
*/
|
||||||
|
public static void enable(OptionsBuilder options) throws IOException {
|
||||||
|
Path runtime = Paths.get(System.getenv("TEST_SRCDIR")).resolve(
|
||||||
|
"copybara/java/com/google/copybara/buildozer/testing");
|
||||||
|
// Use data dependencies on the buildozer/buildifier binaries.
|
||||||
|
File buildozer = runtime.resolve("buildozer").toFile();
|
||||||
|
File buildifier = runtime.resolve("buildifier").toFile();
|
||||||
|
options.buildozerBin = buildozer.getAbsolutePath();
|
||||||
|
options.buildifier.buildifierBin = buildifier.getAbsolutePath();
|
||||||
|
}
|
||||||
|
}
|
65
third_party/copybara/java/com/google/copybara/checks/ApiChecker.java
vendored
Normal file
65
third_party/copybara/java/com/google/copybara/checks/ApiChecker.java
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.checks;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A checker for API clients that delegates on a {@link Checker} and provides convenience methods
|
||||||
|
* for checking one or more pairs of field names and values, plus error handling.
|
||||||
|
*/
|
||||||
|
public class ApiChecker {
|
||||||
|
|
||||||
|
private final Checker checker;
|
||||||
|
private final Console console;
|
||||||
|
|
||||||
|
public ApiChecker(Checker checker, Console console) {
|
||||||
|
this.checker = Preconditions.checkNotNull(checker);
|
||||||
|
this.console = Preconditions.checkNotNull(console);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Performs a check on the given request field. */
|
||||||
|
public void check(String field, Object value) throws CheckerException {
|
||||||
|
doCheck(ImmutableMap.of(field, value.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Performs a check on the given request fields. */
|
||||||
|
public void check(String field1, Object value1, String field2, Object value2)
|
||||||
|
throws CheckerException {
|
||||||
|
doCheck(ImmutableMap.of(field1, value1.toString(), field2, value2.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Performs a check on the given request fields. */
|
||||||
|
public void check(
|
||||||
|
String field1, Object value1, String field2, Object value2, String field3, Object value3)
|
||||||
|
throws CheckerException {
|
||||||
|
doCheck(
|
||||||
|
ImmutableMap.of(
|
||||||
|
field1, value1.toString(), field2, value2.toString(), field3, value3.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doCheck(ImmutableMap<String, String> data) throws CheckerException {
|
||||||
|
try {
|
||||||
|
checker.doCheck(data, console);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Error running checker", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
third_party/copybara/java/com/google/copybara/checks/BUILD
vendored
Normal file
35
third_party/copybara/java/com/google/copybara/checks/BUILD
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# Copyright 2018 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
java_library(
|
||||||
|
name = "checks",
|
||||||
|
srcs = glob(["**/*.java"]),
|
||||||
|
javacopts = [
|
||||||
|
"-Xlint:unchecked",
|
||||||
|
"-source",
|
||||||
|
"1.8",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//java/com/google/copybara/exception",
|
||||||
|
"//java/com/google/copybara/util/console",
|
||||||
|
"//third_party:guava",
|
||||||
|
"//third_party:jsr305",
|
||||||
|
"//third_party:re2j",
|
||||||
|
"//third_party:skylark-lang",
|
||||||
|
],
|
||||||
|
)
|
51
third_party/copybara/java/com/google/copybara/checks/Checker.java
vendored
Normal file
51
third_party/copybara/java/com/google/copybara/checks/Checker.java
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.copybara.checks;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.copybara.util.console.Console;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkBuiltin;
|
||||||
|
import com.google.devtools.build.lib.skylarkinterface.StarlarkDocumentationCategory;
|
||||||
|
import com.google.devtools.build.lib.syntax.StarlarkValue;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/** A generic interface for performing checks on string contents and files. */
|
||||||
|
@StarlarkBuiltin(
|
||||||
|
name = "checker",
|
||||||
|
doc = "A checker to be run on arbitrary data and files",
|
||||||
|
category = StarlarkDocumentationCategory.TOP_LEVEL_TYPE,
|
||||||
|
documented = false)
|
||||||
|
public interface Checker extends StarlarkValue {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a check on the given contents.
|
||||||
|
*
|
||||||
|
* @throws CheckerException if the check produced errors
|
||||||
|
* @throws IOException if the checker could not be run
|
||||||
|
*/
|
||||||
|
void doCheck(ImmutableMap<String, String> fields, Console console)
|
||||||
|
throws CheckerException, IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a check on the files inside a given path.
|
||||||
|
*
|
||||||
|
* @throws CheckerException if the check produced errors
|
||||||
|
* @throws IOException if the checker could not be run
|
||||||
|
*/
|
||||||
|
void doCheck(Path target, Console console) throws CheckerException, IOException;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue