# 镜像仓库[](#镜像仓库 "Permalink")

存储库镜像允许将存储库镜像到外部源或从外部源镜像. 它可用于镜像存储库之间的分支，标签和提交.

GitLab 上的存储库镜像将自动更新. 您也可以最多每 5 分钟手动触发一次更新.

## Overview[](#overview "Permalink")

当您想在 GitLab 之外使用存储库时，存储库镜像很有用.

GitLab 支持两种存储库镜像：

*   推送：用于将 GitLab 存储库镜像到另一个位置.
*   Pull: for mirroring a repository from another location to GitLab.

镜像存储库更新后，所有新的分支，标记和提交将在项目的活动提要中可见.

至少具有[开发人员访问](../../permissions.html)项目[权限](../../permissions.html)的用户也可以强制立即更新，除非：

*   镜像已被更新.
*   自上次更新以来，还没有过去 5 分钟.

出于安全原因，在[GitLab 12.10 和更高版本中](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27166) ，原始存储库的 URL 仅显示给对镜像项目具有维护者或所有者权限的用户.

## Use cases[](#use-cases "Permalink")

以下是存储库镜像的一些可能的用例：

*   您迁移到了 GitLab，但仍需要将项目保留在另一个源中. 在这种情况下，您只需将其设置为镜像到 GitLab（拉），所有提交，标签和分支的基本历史记录都将在您的 GitLab 实例中提供.
*   您在另一个源中有旧项目，这些旧项目不再使用，但又不想出于归档目的而删除它们. 在这种情况下，您可以创建一个推送镜像，以便您的活动 GitLab 存储库可以将其更改推送到旧位置.

## Pushing to a remote repository[](#pushing-to-a-remote-repository-core "Permalink")

版本历史

*   在 GitLab 企业版 8.7 中[引入](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/249) .
*   在 10.8 版中[移至 GitLab Core](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/18715) .

对于现有项目，可以按如下所示设置推送镜像：

1.  导航到项目的**"设置">"存储库"，**然后展开" **镜像存储库"**部分.
2.  输入存储库 URL.
3.  从**镜像方向**下拉菜单中选择**推送** .
4.  如有必要，请从" **身份验证方法"**下拉列表中选择一种身份**验证方法** .
5.  如有必要，请选中**仅镜像保护的分支**框.
6.  如果需要， **请**选中" **保持差异引用"**框.
7.  单击**镜像存储库**按钮以保存配置.

[![Repository mirroring push settings screen](img/d67d5681f30c4769c6d92232eada26e4.png)](img/repository_mirroring_push_settings.png)

启用推送镜像后，只有推送会直接提交到镜像存储库，以防止镜像发散. 任何时候，所有更改都将最终存储在镜像存储库中：

*   提交被推送到 GitLab.
*   [强制更新](#forcing-an-update-core)已启动.

推送到存储库中文件的更改至少会自动推送到远程镜像：

*   收到后五分钟内.
*   如果启用了**仅受镜像保护的分支，**则在一分钟之内.

如果分支分支不同，您将在" **镜像存储库"**部分看到指示的错误.

### Configuring push mirrors through the API[](#configuring-push-mirrors-through-the-api "Permalink")

您还可以通过[远程镜像 API](../../../api/remote_mirrors.html)创建和修改项目推送镜像.

### Push only protected branches[](#push-only-protected-branches-core "Permalink")

版本历史

*   在[GitLab Starter](https://about.gitlab.com/pricing/) 10.3 中[引入](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/3350) .
*   在 10.8 版中[移至 GitLab Core](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/18715) .

您可以选择仅将受保护的分支从 GitLab 推送到远程存储库.

要使用此选项，请在创建存储库镜像时选中" **仅镜像保护的分支"**框.

### Keep divergent refs[](#keep-divergent-refs-core "Permalink")

在 GitLab 13.0 中[引入](https://gitlab.com/gitlab-org/gitlab/-/issues/208828) .

默认情况下，如果远程镜像上的任何引用都与本地存储库不同，则*整个推送*将失败，并且不会更新任何内容.

例如，如果存储库具有已镜像到远程的`master` ， `develop`和`stable`分支，然后添加新提交以在镜像上进行`develop` ，则下一次推送尝试将失败，从而使`master`和`stable`不在可用范围内约会尽管没有分歧. 在解决分歧之前，无法反映任何分支上的更改.

启用" **保持差异引用"**选项后，将跳过`develop`分支，从而可以更新`master`和`stable` . 镜像状态将反映`develop`已偏离并被跳过，并标记为更新失败.

**注意：**创建镜像后，当前只能通过[API](../../../api/remote_mirrors.html)修改此选项.

## Setting up a push mirror from GitLab to GitHub[](#setting-up-a-push-mirror-from-gitlab-to-github-core "Permalink")

要设置从 GitLab 到 GitHub 的镜像，您需要执行以下步骤：

1.  创建一个[GitHub 个人访问令牌](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) ，并选中`public_repo`框.
2.  使用以下格式填写**Git 存储库 URL**字段： `https://<your_github_username>@github.com/<your_github_group>/<your_github_project>.git` .
3.  使用您的 GitHub 个人访问令牌填写**密码**字段.
4.  单击**镜像存储库**按钮.

镜像的存储库将被列出. 例如， `https://*****:*****@github.com/<your_github_group>/<your_github_project>.git` .

该存储库将很快推送. 要强制按下，请单击适当的按钮.

## Setting up a push mirror to another GitLab instance with 2FA activated[](#setting-up-a-push-mirror-to-another-gitlab-instance-with-2fa-activated "Permalink")

1.  在目标 GitLab 实例上，创建一个具有`write_repository`范围的[个人访问令牌](../../profile/personal_access_tokens.html) .
2.  在源 GitLab 实例上：
    1.  使用以下格式填写**Git 存储库 URL**字段： `https://oauth2@<destination host>/<your_gitlab_group_or_name>/<your_gitlab_project>.git` .
    2.  在目标 GitLab 实例上创建的 GitLab 个人访问令牌中填写**密码**字段.
    3.  单击**镜像存储库**按钮.

## Pulling from a remote repository[](#pulling-from-a-remote-repository-starter "Permalink")

版本历史

*   在 GitLab 企业版 8.2 中[引入](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51) .
*   在[GitLab Starter](https://about.gitlab.com/pricing/) 11.11 中[添加了 Git LFS 支持](https://gitlab.com/gitlab-org/gitlab/-/issues/10871) .

您可以将存储库设置为自动从上游存储库更新其分支，标记和提交.

当您感兴趣的存储库位于其他服务器上并且您希望能够使用熟悉的 GitLab 界面浏览其内容和活动时，此功能很有用.

要为现有项目配置镜像拉出：

1.  Navigate to your project’s **设置>存储库** and expand the **镜像存储库** section.
2.  输入存储库 URL.
3.  从**镜像方向**下拉菜单中选择**拉** .
4.  如有必要，请从" **身份验证方法"**下拉列表中选择一种身份**验证方法** .
5.  如有必要，请选中以下框：
    *   **覆盖分散的分支** .
    *   **触发管道以更新镜像** .
    *   **仅镜像保护的分支** .
6.  单击**镜像存储库**按钮以保存配置.

[![Repository mirroring pull settings screen - upper part](img/5881da97228cb042e5934f836edfd06c.png)](img/repository_mirroring_pull_settings_upper.png)

* * *

[![Repository mirroring pull settings screen - lower part](img/d5df9e77d389b58908ef235600ccc61c.png)](img/repository_mirroring_pull_settings_lower.png)

Because GitLab is now set to pull changes from the upstream repository, you should not push commits directly to the repository on GitLab. Instead, any commits should be pushed to the upstream repository. Changes pushed to the upstream repository will be pulled into the GitLab repository, either:

*   在一定时间内自动.
*   启动[强制更新时](#forcing-an-update-core) .

**警告：**如果您确实在 GitLab 存储库中更新了分支，则该分支将与上游分叉，并且 GitLab 将不再自动更新该分支以防止丢失任何更改. 另请注意，上游存储库中已删除的分支和标签将不会反映在 GitLab 存储库中.

### How it works[](#how-it-works "Permalink")

为存储库启用拉镜像功能后，会将存储库添加到队列中.

每分钟一次，Sidekiq cron 作业基于以下内容计划存储库镜像的更新：

*   可用容量. 这由 Sidekiq 设置确定. 对于 GitLab.com，请参阅[GitLab.com Sidekiq 设置](../../gitlab_com/index.html#sidekiq) .
*   队列中已经要更新的存储库镜像数. 到期时间取决于存储库镜像的最后更新时间以及重试次数.

随着 Sidekiq 可以处理存储库镜像，它们也会更新. 如果更新存储库镜像的过程为：

*   成功后，至少需要等待 30 分钟，更新才会重新加入队列.
*   失败（例如，分支从上游分支），稍后将再次尝试. 镜像最多可以发生 14 次故障，然后才不会再次进入队列进行更新.

### SSH authentication[](#ssh-authentication "Permalink")

> *   在[GitLab Starter](https://about.gitlab.com/pricing/) 9.5 中[引入](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/2551)了拉镜像.
> *   在[GitLab Core](https://about.gitlab.com/pricing/) 11.6 中为推送镜像[引入](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/22982)

SSH 身份验证是相互的：

*   您必须向服务器证明您有权访问存储库.
*   服务器还必须向*您*证明它是谁.

您提供凭据作为密码或公共密钥. 另一个存储库所在的服务器提供其凭据作为"主机密钥"，其指纹需要手动验证.

如果您通过 SSH 镜像（即使用`ssh://` URL），则可以使用以下方法进行身份验证：

*   基于密码的身份验证，就像通过 HTTPS 一样.
*   公钥认证. 这通常比密码身份验证更安全，尤其是当其他存储库支持[Deploy Keys 时](../../../ssh/README.html#deploy-keys) .

开始：

1.  导航到项目的**"设置">"存储库"，**然后展开" **镜像存储库"**部分.
2.  输入`ssh://` URL 进行镜像.

**注意：**目前不支持 SCP 样式的 URL（即`git@example.com:group/project.git` ）.

输入 URL 在页面上添加两个按钮：

*   **检测主机密钥**.
*   **手动输入主机密钥**.

如果单击：

*   **检测主机密钥**按钮，GitLab 将从服务器获取主机密钥并显示指纹.
*   **手动输入主机密钥**按钮，将显示一个字段，您可以在其中粘贴主机密钥.

假设您使用了前者，那么现在需要验证指纹是否符合您的期望. GitLab.com 和其他代码托管站点公开公开其指纹，供您检查：

*   [AWS CodeCommit](https://docs.aws.amazon.com/codecommit/latest/userguide/regions.html#regions-fingerprints)
*   [Bitbucket](https://support.atlassian.com/bitbucket-cloud/docs/configure-ssh-and-two-step-verification/)
*   [GitHub](https://help.github.com/en/github/authenticating-to-github/githubs-ssh-key-fingerprints)
*   [GitLab.com](../../gitlab_com/index.html#ssh-host-keys-fingerprints)
*   [Launchpad](https://help.launchpad.net/SSHFingerprints)
*   [Savannah](http://savannah.gnu.org/maintenance/SshAccess/)
*   [SourceForge](https://sourceforge.net/p/forge/documentation/SSH%20Key%20Fingerprints/)

其他提供者将有所不同. 如果您正在运行自我管理的 GitLab，或者可以访问其他存储库的服务器，则可以安全地收集密钥指纹：

```
$ cat /etc/ssh/ssh_host*pub | ssh-keygen -E md5 -l -f -
256 MD5:f4:28:9f:23:99:15:21:1b:bf:ed:1f:8e:a0:76:b2:9d root@example.com (ECDSA)
256 MD5:e6:eb:45:8a:3c:59:35:5f:e9:5b:80:12:be:7e:22:73 root@example.com (ED25519)
2048 MD5:3f:72:be:3d:62:03:5c:62:83:e8:6e:14:34:3a:85:1d root@example.com (RSA) 
```

**注意：**对于某些旧版本的 SSH，可能需要排除`-E md5` .

在镜像存储库时，GitLab 现在将在连接之前检查至少一个存储的主机密钥是否匹配. 这样可以防止将恶意代码注入到您的镜像中，或者防止您的密码被盗.

### SSH public key authentication[](#ssh-public-key-authentication "Permalink")

要使用 SSH 公钥身份验证，您还需要从**Authentication method**下拉菜单中选择该选项. 创建镜像后，GitLab 会生成一个 4096 位 RSA 密钥，可以通过单击" **复制 SSH"公共密钥**按钮进行复制.

[![Repository mirroring copy SSH public key to clipboard button](img/c1e086b58ff99dd5c5684a4571e568e3.png)](img/repository_mirroring_copy_ssh_public_key_button.png)

然后，您需要将公共 SSH 密钥添加到另一个存储库的配置中：

*   如果其他存储库托管在 GitLab 上，则应将公共 SSH 密钥添加为[Deploy Key](../../../ssh/README.html#deploy-keys) .
*   如果其他存储库位于其他位置，则可能需要将密钥添加到用户的`authorized_keys`文件中. 将整个公共 SSH 密钥单独粘贴到文件中并保存.

如果您需要随时更改密钥，则可以删除并重新添加镜像以生成新密钥. 您必须使用新密钥更新另一个存储库，以保持镜像运行.

**注意：**生成的密钥存储在 GitLab 数据库中，而不存储在文件系统中. 因此，无法在预接收挂钩中使用针对镜像的 SSH 公钥身份验证.

### Overwrite diverged branches[](#overwrite-diverged-branches-starter "Permalink")

[Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4559) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.6.

您可以选择始终使用远程版本更新本地分支，即使它们与远程分支不同.

**警告：**对于镜像分支，启用此选项会导致丢失本地更改.

要使用此选项，请在创建存储库镜像时选中" **覆盖分支分支"**框.

### Only mirror protected branches[](#only-mirror-protected-branches-starter "Permalink")

[Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/3326) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.3.

您可以选择仅将受保护的分支从远程存储库拉到 GitLab. 未受保护的分支不会被镜像，并且可能会分开.

要使用此选项，请在创建存储库镜像时选中" **仅镜像保护的分支"**框.

### Hard failure[](#hard-failure-starter "Permalink")

[Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/3117) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.2.

如果镜像过程连续 14 次失败重试，它将被标记为硬盘失败. 这将在以下任一位置中可见：

*   项目的主要仪表板.
*   拉镜设置页面.

当一个项目很难失败时，它将不再需要进行镜像. 用户可以通过[强制更新来](#forcing-an-update-core)再次恢复项目镜像.

### Trigger update using API[](#trigger-update-using-api-starter "Permalink")

[Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/3453) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.3.

拉镜像使用轮询来检测新添加的分支和提交，通常是在几分钟后. 如果您通过[API](../../../api/projects.html#start-the-pull-mirroring-process-for-a-project-starter)通知 GitLab，更新将立即被拉出.

有关更多信息，请参阅[启动项目的拉镜像过程](../../../api/projects.html#start-the-pull-mirroring-process-for-a-project-starter) .

## Forcing an update[](#forcing-an-update-core "Permalink")

当计划将镜像自动更新时，您始终可以使用" **存储** **库设置"**页面的" **镜像存储库"**部分上的"更新"按钮强制进行更新.

[![Repository mirroring force update user interface](img/a6e9b62f94c53cd2f37f7300362137a9.png)](img/repository_mirroring_force_update.png)

## Bidirectional mirroring[](#bidirectional-mirroring-starter "Permalink")

**注意：**双向镜像可能会导致冲突.

如果您将 GitLab 存储库配置为从同一远程源中拉出和推送到同一远程源，则不能保证这两个存储库都将正确更新. 如果为双向镜像设置存储库，则应通过确定谁将解决它们以及如何解决它们来为可能的冲突做准备.

重写任一远程上的任何镜像提交将导致冲突，并且镜像失败. 可以通过以下方法防止此情况：

*   [Pulling only protected branches](#only-mirror-protected-branches-starter).
*   [Pushing only protected branches](#push-only-protected-branches-core).

您应该[保护](../protected_branches.html)希望在两个远程控制器上镜像[的分支](../protected_branches.html) ，以防止由于重写历史记录而引起的冲突.

双向镜像还会创建竞态条件，在这种竞态条件中，靠近同一分支的提交会引起冲突. 通过使用[Push 事件 Webhook](../integrations/webhooks.html#push-events)触发立即拉到 GitLab，可以通过减少镜像延迟来缓解竞争状况. 当仅推送镜像受保护的分支时，来自 GitLab 的推送镜像的速率限制为每分钟一次.

### Configure a webhook to trigger an immediate pull to GitLab[](#configure-a-webhook-to-trigger-an-immediate-pull-to-gitlab "Permalink")

假设您已经在上游 GitLab 实例中配置了[推](#setting-up-a-push-mirror-to-another-gitlab-instance-with-2fa-activated)和[拉](#pulling-from-a-remote-repository-starter)镜像，以触发如上所述的立即提拉，则需要在下游实例中配置一个[推事件 Web 挂钩](../integrations/webhooks.html#push-events) .

去做这个：

*   创建具有`API`范围的[个人访问令牌](../../profile/personal_access_tokens.html) .
*   导航到**设置> Webhooks**
*   添加 webhook URL，在这种情况下，它将在存储库更新后使用[Pull Mirror API](../../../api/projects.html#start-the-pull-mirroring-process-for-a-project-starter)请求触发即时提取.

    ```
    https://gitlab.example.com/api/v4/projects/:id/mirror/pull?private_token=<your_access_token> 
    ```

*   确保已选中" **推送事件"**复选框.
*   单击**添加 Webhook**按钮以保存**Webhook** .
*   要测试集成，请单击" **测试"**按钮，并确认 GitLab 不返回任何错误.

### Preventing conflicts using a `pre-receive` hook[](#preventing-conflicts-using-a-pre-receive-hook "Permalink")

**警告：**提议的解决方案将对 Git 推送操作的性能产生负面影响，因为它们将被代理到上游 Git 存储库.

服务器端`pre-receive`挂钩可通过仅在将提交首先推送到上游 Git 存储库之后才接受推送来防止上述竞争情况. 在此配置中，一个 Git 存储库充当权威的上游，另一个充当下游. `pre-receive`挂钩将安装在下游存储库中.

阅读有关在 GitLab 服务器上[配置服务器挂钩](../../../administration/server_hooks.html)的信息.

下面提供了一个样品`pre-receive`挂钩.

```
#!/usr/bin/env bash

# --- Assume only one push mirror target
# Push mirroring remotes are named `remote_mirror_<id>`, this finds the first remote and uses that.
TARGET_REPO=$(git remote | grep -m 1 remote_mirror)

proxy_push()
{
  # --- Arguments
  OLDREV=$(git rev-parse $1)
  NEWREV=$(git rev-parse $2)
  REFNAME="$3"

  # --- Pattern of branches to proxy pushes
  allowlist=$(expr "$branch" : "\(master\)")

  case "$refname" in refs/heads/*)
      branch=$(expr "$refname" : "refs/heads/\(.*\)")

      if [ "$allowlist" = "$branch" ]; then unset GIT_QUARANTINE_PATH # handle https://git-scm.com/docs/git-receive-pack#_quarantine_environment
        error="$(git push --quiet $TARGET_REPO $NEWREV:$REFNAME 2>&1)"
        fail=$?

        if [ "$fail" != "0" ]; then echo >&2 ""
          echo >&2 " Error: updates were rejected by upstream server"
          echo >&2 "   This is usually caused by another repository pushing changes"
          echo >&2 "   to the same ref. You may want to first integrate remote changes"
          echo >&2 ""
          return
        fi
      fi
      ;;
  esac
}

# Allow dual mode: run from the command line just like the update hook, or
# if no arguments are given then run as a hook script
if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
  # Output to the terminal in command line mode - if someone wanted to
  # resend an email; they could redirect the output to sendmail
  # themselves
  PAGER= proxy_push $2 $3 $1
else
  # Push is proxied upstream one ref at a time. Because of this it is possible
  # for some refs to succeed, and others to fail. This will result in a failed
  # push.
  while read oldrev newrev refname
  do proxy_push $oldrev $newrev $refname
  done
fi 
```

请注意，此示例有一些限制：

*   此示例可能无法完全满足您的用例，并且可能需要进行修改.
    *   它不考虑镜像的不同类型的身份验证机制.
    *   它不适用于强制更新（重写历史记录）.
    *   仅匹配`allowlist`模式的分支将被代理推送.
*   该脚本规避了 Git 钩子隔离环境，因为`$TARGET_REPO`的更新被视为参考更新，Git 会对此进行抱怨.

### Mirroring with Perforce Helix via Git Fusion[](#mirroring-with-perforce-helix-via-git-fusion-starter "Permalink")

**警告：**双向镜像不应用作永久配置. 有关替代的迁移方法，请参阅[从 Perforce Helix](../import/perforce.html)迁移.

[Git Fusion](https://www.perforce.com/manuals/git-fusion/#Git-Fusion/section_avy_hyc_gl.html)为[Perforce Helix](https://www.perforce.com/products)提供了一个 Git 接口，GitLab 可以使用它来双向镜像 GitLab 项目. 从 Perforce Helix 迁移到 GitLab 的某些情况下，这可能是有用的，因为重叠的 Perforce Helix 工作区无法同时迁移到 GitLab.

如果将镜像与 Perforce Helix 一起使用，则应仅镜像受保护的分支. Perforce Helix 将拒绝任何重写历史记录的推送. 由于 Git Fusion 的性能限制，只应镜像最少数量的分支.

通过 Git Fusion 使用 Perforce Helix 配置镜像时，建议使用以下 Git Fusion 设置：

*   `change-pusher`应禁用. 否则，每个提交都将被重写为由镜像帐户提交，而不是映射到现有的 Perforce Helix 用户或`unknown_git`用户.
*   如果 Perforce Helix 中不存在 GitLab 用户，则`unknown_git`用户将用作提交作者.

[在 Perforce.com 上](https://www.perforce.com/manuals/git-fusion/Content/Git-Fusion/section_vss_bdw_w3.html#section_zdp_zz1_3l)了解有关[Git Fusion 设置的信息](https://www.perforce.com/manuals/git-fusion/Content/Git-Fusion/section_vss_bdw_w3.html#section_zdp_zz1_3l) .

## Troubleshooting[](#troubleshooting "Permalink")

如果在推送过程中发生错误，则 GitLab 将在该存储库中显示"错误"突出显示. 然后，将鼠标悬停在突出显示的文本上，即可查看有关错误的详细信息.

### 13:Received RST_STREAM with error code 2 with GitHub[](#13received-rst_stream-with-error-code-2-with-github "Permalink")

如果在镜像到 GitHub 存储库时收到"错误代码为 2 的 13：Received RST_STREAM"，则您的 GitHub 设置可能被设置为阻止推送，以暴露用于提交的电子邮件地址. 可以将您在 GitHub 上的电子邮件地址设置为公开，或者禁用[公开我的电子邮件](https://github.com/settings/emails)设置的" [阻止"命令行推送](https://github.com/settings/emails) .