[[r_git_svn]]
==== Git 与 Subversion

(((Subversion)))(((Interoperation with other VCSs, Subversion)))
很大一部分开源项目与相当多的企业项目使用 Subversion 来管理它们的源代码。
而且在大多数时间里，它已经是开源项目VCS选择的 _事实标准_。
它在很多方面都与曾经是源代码管理世界的大人物的 CVS 相似。

(((git commands, svn)))(((git-svn)))
Git 中最棒的特性就是有一个与 Subversion 的双向桥接，它被称作 `git svn`。
这个工具允许你使用 Git 作为连接到 Subversion 有效的客户端，这样你可以使用 Git 所有本地的功能然后如同正在本地使用 Subversion 一样推送到 Subversion 服务器。
这意味着你可以在本地做新建分支与合并分支、使用暂存区、使用变基与拣选等等的事情，同时协作者还在继续使用他们黑暗又古老的方式。
当你试图游说公司将基础设施修改为完全支持 Git 的过程中，一个好方法是将 Git 偷偷带入到公司环境，并帮助周围的开发者提升效率。
Subversion 桥接就是进入 DVCS 世界的诱饵。

===== `git svn`

在 Git 中所有 Subversion 桥接命令的基础命令是 `git svn`。
它可以跟很多命令，所以我们会通过几个简单的工作流程来为你演示最常用的命令。

需要特别注意的是当你使用 `git svn` 时，就是在与 Subversion 打交道，一个与 Git 完全不同的系统。
尽管 *可以* 在本地新建分支与合并分支，但是你最好还是通过变基你的工作来保证你的历史尽可能是直线，并且避免做类似同时与 Git 远程服务器交互的事情。

不要重写你的历史然后尝试再次推送，同时也不要推送到一个平行的 Git 仓库来与其他使用 Git 的开发者协作。
Subversion 只能有一个线性的历史，弄乱它很容易。
如果你在一个团队中工作，其中有一些人使用 SVN 而另一些人使用 Git，你需要确保每个人都使用 SVN 服务器来协作 - 这样做会省去很多麻烦。

===== 设置

为了演示这个功能，需要一个有写入权限的典型 SVN 仓库。
如果想要拷贝这些例子，你必须获得一份我的测试仓库的可写拷贝。
为了轻松地拷贝，可以使用 Subversion 自带的一个名为 `svnsync` 的工具。
为了这些测试，我们在 Google Code 上创建了一个 `protobuf` 项目部分拷贝的新 Subversion 仓库。`protobuf` 是一个将结构性数据编码用于网络传输的工具。

接下来，你需要先创建一个新的本地 Subversion 仓库：

[source,console]
----
$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn
----

然后，允许所有用户改变版本属性 - 最容易的方式是添加一个返回值为 0 的 `pre-revprop-change` 脚本。

[source,console]
----
$ cat /tmp/test-svn/hooks/pre-revprop-change
#!/bin/sh
exit 0;
$ chmod +x /tmp/test-svn/hooks/pre-revprop-change
----

现在可以调用加入目标与来源仓库参数的 `svnsync init` 命令同步这个项目到本地的机器。

[source,console]
----
$ svnsync init file:///tmp/test-svn \
  http://progit-example.googlecode.com/svn/
----

这样就设置好了同步所使用的属性。
可以通过运行下面的命令来克隆代码：

[source,console]
----
$ svnsync sync file:///tmp/test-svn
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .............................[...]
Committed revision 2.
Copied properties for revision 2.
[…]
----

虽然这个操作可能只会花费几分钟，但如果你尝试拷贝原始的仓库到另一个非本地的远程仓库时，即使只有不到 100 个的提交，这个过程也可能会花费将近一个小时。
Subversion 必须一次复制一个版本然后推送回另一个仓库 - 这低效得可笑，但却是做这件事唯一简单的方式。

===== 开始

既然已经有了一个有写入权限的 Subversion 仓库，那么你可以开始一个典型的工作流程。
可以从 `git svn clone` 命令开始，它会将整个 Subversion 仓库导入到一个本地 Git 仓库。
需要牢记的一点是如果是从一个真正托管的 Subversion 仓库中导入，需要将 `file:///tmp/test-svn` 替换为你的 Subversion 仓库的 URL：

[source,console]
----
$ git svn clone file:///tmp/test-svn -T trunk -b branches -t tags
Initialized empty Git repository in /private/tmp/progit/test-svn/.git/
r1 = dcbfb5891860124cc2e8cc616cded42624897125 (refs/remotes/origin/trunk)
    A	m4/acx_pthread.m4
    A	m4/stl_hash.m4
    A	java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java
    A	java/src/test/java/com/google/protobuf/WireFormatTest.java
…
r75 = 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae (refs/remotes/origin/trunk)
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/my-calc-branch, 75
Found branch parent: (refs/remotes/origin/my-calc-branch) 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae
Following parent with do_switch
Successfully followed parent
r76 = 0fb585761df569eaecd8146c71e58d70147460a2 (refs/remotes/origin/my-calc-branch)
Checked out HEAD:
  file:///tmp/test-svn/trunk r75
----

这相当于运行了两个命令 - `git svn init` 以及紧接着的 `git svn fetch` - 你提供的 URL 。
这会花费一些时间。
测试项目只有 75 个左右的提交并且代码库并不是很大，但是 Git 必须一次一个地检出一个版本同时单独地提交它。
对于有成百上千个提交的项目，这真的可能会花费几小时甚至几天来完成。

`-T trunk -b branches -t tags` 部分告诉 Git Subversion 仓库遵循基本的分支与标签惯例。
如果你命名了不同的主干、分支或标签，可以修改这些参数。
因为这是如此地常见，所以能用 `-s` 来替代整个这部分，这表示标准布局并且指代所有那些选项。
下面的命令是相同的：

[source,console]
----
$ git svn clone file:///tmp/test-svn -s
----

至此，应该得到了一个已经导入了分支与标签的有效的 Git 仓库：

[source,console]
----
$ git branch -a
* master
  remotes/origin/my-calc-branch
  remotes/origin/tags/2.0.2
  remotes/origin/tags/release-2.0.1
  remotes/origin/tags/release-2.0.2
  remotes/origin/tags/release-2.0.2rc1
  remotes/origin/trunk
----

注意这个工具是如何将 Subversion 标签作为远程引用来管理的。
(((git commands, show-ref)))
让我们近距离看一下 Git 的底层命令 `show-ref`：

[source,console]
----
$ git show-ref
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/heads/master
0fb585761df569eaecd8146c71e58d70147460a2 refs/remotes/origin/my-calc-branch
bfd2d79303166789fc73af4046651a4b35c12f0b refs/remotes/origin/tags/2.0.2
285c2b2e36e467dd4d91c8e3c0c0e1750b3fe8ca refs/remotes/origin/tags/release-2.0.1
cbda99cb45d9abcb9793db1d4f70ae562a969f1e refs/remotes/origin/tags/release-2.0.2
a9f074aa89e826d6f9d30808ce5ae3ffe711feda refs/remotes/origin/tags/release-2.0.2rc1
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/remotes/origin/trunk
----

Git 在从 Git 服务器克隆时并不这样做；下面是在刚刚克隆完成的有标签的仓库的样子：

[source,console]
----
$ git show-ref
c3dcbe8488c6240392e8a5d7553bbffcb0f94ef0 refs/remotes/origin/master
32ef1d1c7cc8c603ab78416262cc421b80a8c2df refs/remotes/origin/branch-1
75f703a3580a9b81ead89fe1138e6da858c5ba18 refs/remotes/origin/branch-2
23f8588dde934e8f33c263c6d8359b2ae095f863 refs/tags/v0.1.0
7064938bd5e7ef47bfd79a685a62c1e2649e2ce7 refs/tags/v0.2.0
6dcb09b5b57875f334f61aebed695e2e4193db5e refs/tags/v1.0.0
----

Git 直接将标签抓取至 `refs/tags`，而不是将它们看作分支。

===== 提交回 Subversion

现在你有了一个工作仓库，你可以在项目上做一些改动，然后高效地使用 Git 作为 SVN 客户端将你的提交推送到上游。
一旦编辑了一个文件并提交它，你就有了一个存在于本地 Git 仓库的提交，这提交在 Subversion 服务器上并不存在：

[source,console]
----
$ git commit -am 'Adding git-svn instructions to the README'
[master 4af61fd] Adding git-svn instructions to the README
 1 file changed, 5 insertions(+)
----

接下来，你需要将改动推送到上游。
注意这会怎样改变你使用 Subversion 的方式 - 你可以离线做几次提交然后一次性将它们推送到 Subversion 服务器。
要推送到一个 Subversion 服务器，运行 `git svn dcommit` 命令：

[source,console]
----
$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r77
    M	README.txt
r77 = 95e0222ba6399739834380eb10afcd73e0670bc5 (refs/remotes/origin/trunk)
No changes between 4af61fd05045e07598c553167e0f31c84fd6ffe1 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk
----

这会拿走你在 Subversion 服务器代码之上所做的所有提交，针对每一个做一个 Subversion 提交，然后重写你本地的 Git 提交来包含一个唯一的标识符。
这很重要因为这意味着所有你的提交的 SHA-1 校验和都改变了。
部分由于这个原因，同时使用一个基于 Git 的项目远程版本和一个 Subversion 服务器并不是一个好主意。
如果你查看最后一次提交，有新的 `git-svn-id` 被添加：

[source,console]
----
$ git log -1
commit 95e0222ba6399739834380eb10afcd73e0670bc5
Author: ben <ben@0b684db3-b064-4277-89d1-21af03df0a68>
Date:   Thu Jul 24 03:08:36 2014 +0000

    Adding git-svn instructions to the README

    git-svn-id: file:///tmp/test-svn/trunk@77 0b684db3-b064-4277-89d1-21af03df0a68
----

注意你原来提交的 SHA-1 校验和原来是以 `4af61fd` 开头，而现在是以 `95e0222` 开头。
如果想要既推送到一个 Git 服务器又推送到一个 Subversion 服务器，必须先推送（`dcommit`）到 Subversion 服务器，因为这个操作会改变你的提交数据。

===== 拉取新改动

如果你和其他开发者一起工作，当在某一时刻你们其中之一推送时，另一人尝试推送修改会导致冲突。
那次修改会被拒绝直到你合并他们的工作。
在 `git svn` 中，它看起来是这样的：

[source,console]
----
$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: d5837c4b461b7c0e018b49d12398769d2bfc240a and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 f414c433af0fd6734428cf9d2a9fd8ba00ada145 c80b6127dd04f5fcda218730ddf3a2da4eb39138 M	README.txt
Current branch master is up to date.
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.
----

为了解决这种情况，可以运行 `git svn rebase`，它会从服务器拉取任何你本地还没有的改动，并将你所有的工作变基到服务器的内容之上：

[source,console]
----
$ git svn rebase
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: eaa029d99f87c5c822c5c29039d19111ff32ef46 and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 65536c6e30d263495c17d781962cfff12422693a b34372b25ccf4945fe5658fa381b075045e7702a M	README.txt
First, rewinding head to replay your work on top of it...
Applying: update foo
Using index info to reconstruct a base tree...
M	README.txt
Falling back to patching base and 3-way merge...
Auto-merging README.txt
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.
----

现在，所有你的工作都已经在 Subversion 服务器的内容之上了，你就可以顺利地 `dcommit`：

[source,console]
----
$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r85
    M	README.txt
r85 = 9c29704cc0bbbed7bd58160cfb66cb9191835cd8 (refs/remotes/origin/trunk)
No changes between 5762f56732a958d6cfda681b661d2a239cc53ef5 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk
----

注意，和 Git 需要你在推送前合并本地还没有的上游工作不同的是，`git svn` 只会在修改发生冲突时要求你那样做（更像是 Subversion 工作的行为）。
如果其他人推送一个文件的修改然后你推送了另一个文件的修改，你的 `dcommit` 命令会正常工作：

[source,console]
----
$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	configure.ac
Committed r87
    M	autogen.sh
r86 = d8450bab8a77228a644b7dc0e95977ffc61adff7 (refs/remotes/origin/trunk)
    M	configure.ac
r87 = f3653ea40cb4e26b6281cec102e35dcba1fe17c4 (refs/remotes/origin/trunk)
W: a0253d06732169107aa020390d9fefd2b1d92806 and refs/remotes/origin/trunk differ, using rebase:
:100755 100755 efa5a59965fbbb5b2b0a12890f1b351bb5493c18 e757b59a9439312d80d5d43bb65d4a7d0389ed6d M	autogen.sh
First, rewinding head to replay your work on top of it...
----

记住这一点很重要，因为结果是当你推送后项目的状态并不存在于你的电脑中。
如果修改并未冲突但却是不兼容的，可能会引起一些难以诊断的问题。
这与使用 Git 服务器并不同 - 在 Git 中，可以在发布前完全测试客户端系统的状态，然而在 SVN 中，你甚至不能立即确定在提交前与提交后的状态是相同的。

你也应该运行这个命令从 Subversion 服务器上拉取修改，即使你自己并不准备提交。
可以运行 `git svn fetch` 来抓取新数据，但是 `git svn rebase` 会抓取并更新你本地的提交。

[source,console]
----
$ git svn rebase
    M	autogen.sh
r88 = c9c5f83c64bd755368784b444bc7a0216cc1e17b (refs/remotes/origin/trunk)
First, rewinding head to replay your work on top of it...
Fast-forwarded master to refs/remotes/origin/trunk.
----

每隔一会儿运行 `git svn rebase` 确保你的代码始终是最新的。
虽然需要保证当运行这个命令时工作目录是干净的。
如果有本地的修改，在运行 `git svn rebase` 之前要么储藏你的工作要么做一次临时的提交，不然，当变基会导致合并冲突时，命令会终止。

===== Git 分支问题

当适应了 Git 的工作流程，你大概会想要创建特性分支，在上面做一些工作，然后将它们合并入主分支。
如果你正通过 `git svn` 推送到一个 Subversion 服务器，你可能想要把你的工作变基到一个单独的分支上，而不是将分支合并到一起。
比较喜欢变基的原因是因为 Subversion 有一个线性的历史并且无法像 Git 一样处理合并，所以 `git svn` 在将快照转换成 Subversion 提交时，只会保留第一父提交。

假设你的历史像下面这样：创建了一个 `experiment` 分支，做了两次提交，然后将它们合并回 `master`。
当 `dcommit` 时，你看到输出是这样的：

[source,console]
----
$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	CHANGES.txt
Committed r89
    M	CHANGES.txt
r89 = 89d492c884ea7c834353563d5d913c6adf933981 (refs/remotes/origin/trunk)
    M	COPYING.txt
    M	INSTALL.txt
Committed r90
    M	INSTALL.txt
    M	COPYING.txt
r90 = cb522197870e61467473391799148f6721bcf9a0 (refs/remotes/origin/trunk)
No changes between 71af502c214ba13123992338569f4669877f55fd and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk
----

在一个合并过历史提交的分支上 `dcommit` 命令工作得很好，除了当你查看你的 Git 项目历史时，它并没有重写所有你在 `experiment` 分支上所做的任意提交 - 相反，所有这些修改显示一个单独合并提交的 SVN 版本中。

当其他人克隆那些工作时，他们只会看到一个被塞入了所有改动的合并提交，就像运行了 `git merge --squash`；他们无法看到修改从哪来或何时提交的信息。

===== Subversion 分支

在 Subversion 中新建分支与在 Git 中新建分支并不相同；如果你能不用它，那最好就不要用。
然而，你可以使用 git svn 在 Subversion 中创建分支并在分支上做提交。

===== 创建一个新的 SVN 分支

要在 Subversion 中创建一个新分支，运行 `git svn branch [branchname]`：

[source,console]
----
$ git svn branch opera
Copying file:///tmp/test-svn/trunk at r90 to file:///tmp/test-svn/branches/opera...
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/opera, 90
Found branch parent: (refs/remotes/origin/opera) cb522197870e61467473391799148f6721bcf9a0
Following parent with do_switch
Successfully followed parent
r91 = f1b64a3855d3c8dd84ee0ef10fa89d27f1584302 (refs/remotes/origin/opera)
----

这与 Subversion 中的 `svn copy trunk branches/opera` 命令作用相同并且是在 Subversion 服务器中操作。
需要重点注意的是它并不会检出到那个分支；如果你在这时提交，提交会进入服务器的 `trunk` 分支，而不是 `opera` 分支。

===== 切换活动分支

Git 通过查找在历史中 Subversion 分支的头部来指出你的提交将会到哪一个分支 - 应该只有一个，并且它应该是在当前分支历史中最后一个有 `git-svn-id` 的。

如果想要同时在不止一个分支上工作，可以通过在导入的那个分支的 Subversion 提交开始来设置本地分支 `dcommit` 到特定的 Subversion 分支。
如果想要一个可以单独在上面工作的 `opera` 分支，可以运行

[source,console]
----
$ git branch opera remotes/origin/opera
----

现在，如果想要将你的 `opera` 分支合并入 `trunk`（你的 `master` 分支），可以用一个正常的 `git merge` 来这样做。
但是你需要通过 `-m` 来提供一个描述性的提交信息，否则合并信息会是没有用的 ``Merge branch opera''。

记住尽管使用的是 `git merge` 来做这个操作，而且合并可能会比在 Subversion 中更容易一些（因为 Git 会为你自动地检测合适的合并基础），但这并不是一个普通的 Git 合并提交。
你不得不将这个数据推送回一个 Subversion 服务器，Subversion 服务器不支持那些跟踪多个父结点的提交；所以，当推送完成后，它看起来会是一个将其他分支的所有提交压缩在一起的单独提交。
在合并一个分支到另一个分支后，你并不能像 Git 中那样轻松地回到原来的分支继续工作。
你运行的 `dcommit` 命令会将哪个分支被合并进来的信息抹掉，所以后续的合并基础计算会是错的 - dcommit 会使你的 `git merge` 结果看起来像是运行了 `git merge --squash`。
不幸的是，没有一个好的方式来避免这种情形 - Subversion 无法存储这个信息，所以当使用它做为服务器时你总是会被它的限制打垮。
为了避免这些问题，应该在合并到主干后删除本地分支（本例中是 `opera`）。

===== Subversion 命令

`git svn` 工具集通过提供很多功能与 Subversion 中那些相似的命令来帮助简化转移到 Git 的过程。
下面是一些提供了 Subversion 中常用功能的命令。

====== SVN 风格历史

如果你习惯于使用 Subversion 并且想要看 SVN 输出风格的提交历史，可以运行 `git svn log` 来查看 SVN 格式的提交历史：

[source,console]
----
$ git svn log
------------------------------------------------------------------------
r87 | schacon | 2014-05-02 16:07:37 -0700 (Sat, 02 May 2014) | 2 lines

autogen change

------------------------------------------------------------------------
r86 | schacon | 2014-05-02 16:00:21 -0700 (Sat, 02 May 2014) | 2 lines

Merge branch 'experiment'

------------------------------------------------------------------------
r85 | schacon | 2014-05-02 16:00:09 -0700 (Sat, 02 May 2014) | 2 lines

updated the changelog
----

关于 `git svn log`，有两件重要的事你应该知道。
首先，它是离线工作的，并不像真正的 `svn log` 命令，会向 Subversion 服务器询问数据。
其次，它只会显示已经提交到 Subversion 服务器上的提交。
还未 dcommit 的本地 Git 提交并不会显示；同样也不会显示这段时间中其他人推送到 Subversion 服务器上的提交。
它更像是最后获取到的 Subversion 服务器上的提交状态。

====== SVN 注解

类似 `git svn log` 命令离线模拟了 `svn log` 命令，你可以认为 `git svn blame [FILE]` 离线模拟了 `svn annotate`。
输出看起来像这样：

[source,console]
----
$ git svn blame README.txt
 2   temporal Protocol Buffers - Google's data interchange format
 2   temporal Copyright 2008 Google Inc.
 2   temporal http://code.google.com/apis/protocolbuffers/
 2   temporal
22   temporal C++ Installation - Unix
22   temporal =======================
 2   temporal
79    schacon Committing in git-svn.
78    schacon
 2   temporal To build and install the C++ Protocol Buffer runtime and the Protocol
 2   temporal Buffer compiler (protoc) execute the following:
 2   temporal
----

重复一次，它并不显示你在 Git 中的本地提交，也不显示同一时间被推送到 Subversion 的其他提交。

====== SVN 服务器信息

可以通过运行 `git svn info` 得到与 `svn info` 相同种类的信息。

[source,console]
----
$ git svn info
Path: .
URL: https://schacon-test.googlecode.com/svn/trunk
Repository Root: https://schacon-test.googlecode.com/svn
Repository UUID: 4c93b258-373f-11de-be05-5f7a86268029
Revision: 87
Node Kind: directory
Schedule: normal
Last Changed Author: schacon
Last Changed Rev: 87
Last Changed Date: 2009-05-02 16:07:37 -0700 (Sat, 02 May 2009)
----

这就像是在你上一次和 Subversion 服务器通讯时同步了之后，离线运行的 `blame` 与 `log` 命令。

====== 忽略 Subversion 所忽略的

如果克隆一个在任意一处设置 `svn:ignore` 属性的 Subversion 仓库时，你也许会想要设置对应的 `.gitignore` 文件，这样就不会意外的提交那些不该提交的文件。
`git svn` 有两个命令来帮助解决这个问题。
第一个是 `git svn create-ignore`，它会为你自动地创建对应的 `.gitignore` 文件，这样你的下次提交就能包含它们。

第二个命令是 `git svn show-ignore`，它会将你需要放在 `.gitignore` 文件中的每行内容打印到标准输出，这样就可以将输出内容重定向到项目的例外文件中：

[source,console]
----
$ git svn show-ignore > .git/info/exclude
----

这样，你就不会由于 `.gitignore` 文件而把项目弄乱。
当你是 Subversion 团队中唯一的 Git 用户时这是一个好的选项，并且你的队友并不想要项目内存在 `.gitignore` 文件。

===== Git-Svn 总结

当你不得不使用 Subversion 服务器或者其他必须运行一个 Subversion 服务器的开发环境时，`git svn` 工具很有用。
你应该把它当做一个不完全的 Git，然而，你要是不用它的话，就会在做转换的过程中遇到很多麻烦的问题。
为了不惹麻烦，尽量遵守这些准则：

* 保持一个线性的 Git 历史，其中不能有 `git merge` 生成的合并提交。
  把你在主线分支外开发的全部工作变基到主线分支；而不要合并入主线分支。
* 不要建立一个单独的 Git 服务器，也不要在 Git 服务器上协作。
  可以用一台 Git 服务器来帮助新来的开发者加速克隆，但是不要推送任何不包含 `git-svn-id` 条目的东西。
  你可能会需要增加一个 `pre-receive` 钩子来检查每一个提交信息是否包含 `git-svn-id` 并且拒绝任何未包含的提交。

如果你遵守了那些准则，忍受用一个 Subversion 服务器来工作可以更容易些。
然而，如果有可能迁移到一个真正的 Git 服务器，那么迁移过去能使你的团队获得更多好处。
