Copybara是一个在 git 存储库之间自动移动源代码的工具。

你什么时候会使用这样的工具?

  • 当您有一个内部存储库但想要它的开源部分时。

  • 当您有多个存储库并且需要一次将代码更改传播到所有存储库时。

了解 Copybara 如何保持 GIT 存储库同步

Copybara 是一个声明性工具,您可以在其中描述源和目标存储库以及要应用于代码的任何转换。

让我们看一个简单的例子。

假设您有两个存储库:一个monorepo和一个public-repo

monorepo/
├─ internal/
│  ├─ do-not-share.js
├─ external/
│  ├─ library.js
├─ README.md

public-repo/
├─ library.js

此时monorepo中的external文件夹和public-repo是同步的。

它们具有相同内容的相同文件 (library.js)。

虽然monorepo仅在内部可用,但公共存储库可以接收贡献。

让我们假设有一个包含README.md文件和对library.js的改进的拉取请求。

合并后,两个存储库不同步:

monorepo/
├─ internal/
│  ├─ do-not-share.js
├─ external/
│  ├─ library.js
├─ README.md

public-repo/
├─ library.js* <-- modified
├─ README.md   <-- added

这两个存储库不同步。

这就是 Copybara 的用武之地。

您可以定义一种机制来将更改从一个存储库复制到另一个存储库。

我们来看一下。

sourceUrl = "ssh://git@github.com/danielepolencic/public-repo.git"
destinationUrl = "ssh://git@github.com/danielepolencic/monorepo.git"

core.workflow(
    name = "default",
    origin = git.origin(
        url = sourceUrl,
        ref = "master",
    ),
    destination = git.destination(
        url = destinationUrl,
        fetch = "master",
        push = "master",
    ),
    destination_files = glob(["external/**"]),
    authoring = authoring.pass_thru("Copybara <copybara@example.com>"),
    transformations = [
        core.move("", "external"),
    ],
)

您可以将此文件另存为copy.bara.sky

什么是.sky文件?!

Copybara 使用 Starlark 来定义如何移动代码。

Starlark 是 Python 的一个子集,没有副作用(执行相同的 Starlark 两次应该会产生相同的输出)。

该文件使用名为.workflow的方法来定义转换。

  • git.origin包含源存储库的详细信息。在这种情况下,它指向外部存储库。

  • git.destination包含将接收更改的存储库的详细信息。在这种情况下,它是 monorepo。

  • destination_files是应该存储文件的位置。在这种情况下,应该将 public-repo 中的所有文件复制到 monorepo 中的external文件夹。

  • authoring是进行更改的默认作者。

  • [`transformation zwz100032 zwz100030 is a list of transformation. In this case, all files are moved to the external 文件夹。

您可以使用以下命令运行该文件:

$ java -jar bazel-bin/java/com/google/copybara/copybara_deploy.jar copy.bara.sky
INFO: Setting up LogManager
Copybara source mover (Version: Unknown version)
Task: Git Destination: Fetching: ssh://git@github.com/danielepolencic/monorepo.git refs/heads/master
ERROR: Cannot find last imported revision. Use --force if you really want to proceed with the migration use, or use '--last-rev' to override the revision.

失败了!

Copybara 使用您的 GIT 存储库上的GitOrigin-RevId标签来跟踪已迁移的更改。

由于这是您第一次运行该工具并且没有标签,因此 Copybara 失败。

您可以通过附加--force标志来强制 Copybara 重新开始。

$ java -jar bazel-bin/java/com/google/copybara/copybara_deploy.jar copy.bara.sky --force

Copybara 完成后,两个仓库的新结构如下:

monorepo/
├─ internal/
│  ├─ do-not-share.js
├─ external/
│  ├─ library.js* <-- updated
│  ├─ README.md   <-- added
├─ README.md

public-repo/
├─ library.js
├─ README.md

伟大的!

现在这两个存储库是同步的。

但是,如果您不想直接将更改提交给 master 并提出 Pull Request 怎么办?

使用 Copybara 在 GitHub 上提出拉取请求

Copybara 可以使用更改创建一个不同的分支,并在 GitHub 上打开一个拉取请求。

让我们修改前面的示例,在monorepo上创建一个 Pull Request,而不是直接将更改提交给 master。

修改copy.bara.sky文件以获得这个新代码:

sourceUrl = "ssh://git@github.com/danielepolencic/public-repo.git"
destinationUrl = "ssh://git@github.com/danielepolencic/monorepo.git"

core.workflow(
    name = "default",
    origin = git.origin(
        url = sourceUrl,
        ref = "master",
    ),
    destination = git.github_pr_destination(
        url = destinationUrl,
        destination_ref = "master",
        pr_branch = "from_public_repo",
        title = "pr from external public repo",
        body = "this is a sample pull request",
        integrates = [],
    ),
    destination_files = glob(["external/**"]),
    authoring = authoring.pass_thru("Copybara <copybara@example.com>"),
    transformations = [
        core.move("", "external"),
    ],
)

这次,您将git.destination方法替换为git.github_pr_destination

新方法接受更多参数,您可以在其中指定接收更新的目标分支 (destination ref) 以及 PR 的标题 (title) 和分支的名称 (pr_branch)。

在执行迁移之前,让我们对公共 repo 做一个微小的更改;否则,Copybara 将抱怨未检测到任何更改。

monorepo/
├─ internal/
│  ├─ do-not-share.js
├─ external/
│  ├─ library.js
│  ├─ README.md
├─ README.md

public-repo/
├─ library.js
├─ README.md   <-- updated

让我们运行 Copybara:

$ java -jar bazel-bin/java/com/google/copybara/copybara_deploy.jar copy.bara.sky
Task: Git Destination: Fetching: ssh://git@github.com/danielepolencic/monorepo.git refs/heads/master
Task: Git Destination: Pushing to ssh://git@github.com/danielepolencic/monorepo.git refs/heads/from_public_repo
INFO: GitHub credentials not found in ~/.git-credentials. Assuming the repository is public.
ERROR: Project not found: GitHub API call failed with code 404 The request was GET repos/danielepolencic/monorepo/pulls?per_page=100&head=danielepolencic:from_public_repo

又失败了!

到目前为止,Copybara 使用您计算机上的配置连接到 GitHub。

换句话说,如果您设置 SSH 私钥来连接到您的 GitHub 公有或私有存储库,Copybara 可以使用它们来创建提交、添加标签等。

但在提出拉取请求和 GitHub 特定功能时,Copybara 必须调用 GitHub API 才能使这些工作正常进行。

默认情况下,它会查找存储在~/.git-credentials中的凭据。

因为,在这种情况下,没有,请求失败。

您可以在此处找到有关如何添加凭据的说明。

如果你重新运行之前的命令,它应该会通过,并且会在 monorepo 上创建一个 Pull Request。

跨存储库推送和拉取更改的端到端工作流程

到目前为止,对存储库的所有更改都是单向的——我们总是将所有更改从public-repo移动到monorepo

但是将变更从 monorepo 推送到 public-repo 呢?

我们可以扩充我们的设计,以便:

  • 来自public-repo的所有更改都作为拉取请求迁移到monorepo

  • 来自monorepo的所有更改都作为另一个拉取请求迁移到public-repo

为此,我们可以在copy.bara.sky文件中创建多个工作流:

sourceUrl = "ssh://git@github.com/danielepolencic/public-repo.git"
destinationUrl = "ssh://git@github.com/danielepolencic/monorepo.git"

core.workflow(
    name = "pull",                                 # <- renamed to pull
    origin = git.origin(
        url = sourceUrl,
        ref = "master",
    ),
    destination = git.github_pr_destination(
        url = destinationUrl,
        destination_ref = "master",
        pr_branch = "from_public_repo",
        title = "pr from external public repo",
        body = "this is a sample pull request",
        integrates = [],
    ),
    destination_files = glob(["external/**"]),
    authoring = authoring.pass_thru("Copybara <copybara@example.com>"),
    transformations = [
        core.move("", "external"),
    ],
)

core.workflow(
    name = "push",                                 # <- created
    origin = git.origin(
        url = destinationUrl,
        ref = "master",
    ),
    destination = git.github_pr_destination(
        url = sourceUrl,
        destination_ref = "master",
        pr_branch = "from_monorepo",
        title = "pr from monorepo",
        body = "this is a sample pull request",
        integrates = [],
    ),
    origin_files = glob(["external/**"]),  # pay attention!
    authoring = authoring.pass_thru("Copybara <copybara@example.com>"),
    transformations = [
        core.move("external", ""),
    ],
)

在这个文件中,我们有两个工作流程:

  • 一个pull的工作流程,和之前的一样。

  • 一个新的push工作流程,将更改从monorepo复制到public-repo

值得注意的是,这两个工作流程非常相似,但有一些值得注意的区别:

  1. 交换源和目标存储库 URL。

  2. pull工作流程中,您使用destination_files将所有文件复制到特定文件夹中,在push工作流程中,您使用origin_files仅导出对该文件夹中文件的更改。

3、core.move方法在pull工作流中添加前缀,在push中去掉前缀。

通过这些更改,您可以使用以下命令将更改拉取和推送到两个存储库:

$ java -jar bazel-bin/java/com/google/copybara/copybara_deploy.jar copy.bara.sky push
$ java -jar bazel-bin/java/com/google/copybara/copybara_deploy.jar copy.bara.sky pull

这种工作流组合与熟悉的git pullgit push命令非常相似,但它可以跨存储库工作。

但是还有另一个方便的功能可以使该过程更加无缝。

使用 Copybara 镜像对 Pull Requests 的更改

您可以配置您的主体存储库(在我们的示例中为monorepo)以镜像另一个(外部)存储库上的拉取请求。

以下是此类工作流程的示例:

  +--------------------+             +--------------------+
  |                    |             |                    |
  |  External Repo     |             |    External PR     +<---+ OSS contributor
  |                    |             |                    |      opens a PR
  |                    |             |                    |
  +--------^-----------+             +--------+-----------+
          |                                  |
    New commits are                  Changes shadowed as an
    pushed via copybara              internal PR via copybara
          |                                  |
  +--------+-----------+             +--------v-----------+
  |                    |             |                    |
  |   Internal Repo    +<------------+  Internal PR       |
  |                    |   CI runs   |                    |
  |                    |   &         +--------------------+
  +--------------------+   Team member reviews and merges

整个过程可以通过 CI/CD 管道自动化,以便您始终拥有最新的更改。

总结

如果你不想使用 GIT 子模块但仍需要在 GIT 中管理依赖项目,你应该考虑给 Copybara 一个机会。

Copybara 是一个可靠的工具,可以在存储库之间自动进行 GIT 更改,并且可以轻松地与 GitHub 集成。

我希望你发现这个使用 Copybara 的笔记集很有用。

如果你喜欢这篇文章,你可能会喜欢我在 Twitter 上发布的线程。

附件:如何安装Copybara

Copybara 没有打包为单个二进制文件。你应该先构建它。

您应该检查存储库并使用以下命令构建 jar:

$ brew install bazelisk
$ git clone https://github.com/google/copybara
$ bazel build java/com/google/copybara:copybara_deploy.jar

您可能会遇到以下错误:

没有为类型@bazel_tools//tools/cpp:toolchain_type 找到匹配的工具链。可能 --incompatible_use_cc_configure_from_rules_cc 已经翻转,WORKSPACE 文件中没有添加默认的 C++ 工具链?有关详细信息和迁移说明,请参阅github.com/bazelbuild/bazel/issues/10134。

问题是 Bazel 和您的 M1 的版本。我再也找不到 GitHub 问题了,但修复在5.0.0中实现,在5.2.0中“丢失”。

要修复它,您可以在项目的根目录下创建一个.bazelversion文件并将5.0.0添加为内容。

如果您遇到以下错误:

获取存储库“JCommander”期间发生错误:

您应该将您的 repo 版本降级到此提交之前的版本。你可以在这里找到更多信息。

您最终可以使用以下命令运行 copybara:

$ java -jar bazel-bin/java/com/google/copybara/copybara_deploy.jar
Jun 24, 2022 10:46:51 AM com.google.copybara.Main configureLog
INFO: Setting up LogManager
Copybara source mover (Version: Unknown version)
Task: Running migrate
ERROR: Configuration file missing for 'migrate' subcommand.

ERROR: Try 'copybara help'.

在Hashnode、Twitter和Linkedin上关注 Kubesimplify。加入我们的Discord服务器与我们一起学习。

Logo

ModelScope旨在打造下一代开源的模型即服务共享平台,为泛AI开发者提供灵活、易用、低成本的一站式模型服务产品,让模型应用更简单!

更多推荐