Github之深入解析如何在托管在不同系统的项目上使用Git客户端

Posted Forever_wj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Github之深入解析如何在托管在不同系统的项目上使用Git客户端相关的知识,希望对你有一定的参考价值。

一、前言

  • 通常,在开发工作时,不能立刻就把接触到的每一个项目都切换到 Git,有时候使我们被困在使用其他 VCS 的项目中,却希望使用 Git。在某些时候,可能我们想要将已有项目转换到 Git,那么该怎么处理呢?
  • Git 为开发者提供了很多优秀的体验,许多人已经找到了在他们的工作站上使用 Git 的方法,即使他们团队其余的人使用的是完全不同的 VCS。有许多这种可用的适配器,它们被叫做“桥接”。 那么,实际中用到的桥接有哪些呢?

二、Git 与 Subversion

  • 很大一部分开源项目与相当多的企业项目使用 Subversion 来管理它们的源代码,而且在大多数时间里,它已经是开源项目 VCS 选择的事实标准,它在很多方面都与曾经是源代码管理世界的大人物的 CVS 相似。
  • 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 仓库,如果想要拷贝这些例子,必须获取一份可写入的 SVN 测试仓库的副本。为了轻松地拷贝,可以使用 Subversion 自带的一个名为 svnsync 的工具,为了这些测试,我们在 Google Code 上创建了一个 protobuf 项目部分拷贝的新 Subversion 仓库,protobuf 是一个将结构性数据编码用于网络传输的工具。
  • 接下来,需要先创建一个新的本地 Subversion 仓库:
$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn
  • 然后,允许所有用户改变版本属性,最容易的方式是添加一个返回值为 0 的 pre-revprop-change 脚本:
$ cat /tmp/test-svn/hooks/pre-revprop-change
#!/bin/sh
exit 0;
$ chmod +x /tmp/test-svn/hooks/pre-revprop-change
  • 现在可以调用加入目标与来源仓库参数的 svnsync init 命令同步这个项目到本地的机器:
$ svnsync init file:///tmp/test-svn \\
  http://your-svn-server.example.org/svn/
  • 这样就设置好了同步所使用的属性,可以通过运行下面的命令来克隆代码:
$ 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:
$ 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 来替代整个这部分,这表示标准布局并且指代所有那些选项。下面的命令是相同的:
$ git svn clone file:///tmp/test-svn -s
  • 至此,应该得到了一个已经导入了分支与标签的有效的 Git 仓库:
$ 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 的底层命令 show-ref:
$ 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 服务器克隆时并不这样做,下面是在刚刚克隆完成的有标签的仓库的样子:
$ 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 服务器上并不存在:
$ 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 命令:
$ 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 被添加:
$ 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 中,它看起来是这样的:
$ 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,它会从服务器拉取任何本地还没有的改动,并将所有的工作变基到服务器的内容之上:
$ 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:
$ 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 命令会正常工作:
$ 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 会抓取并更新本地的提交。
$ 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 时,看到输出是这样的:
$ 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 :
$ 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 分支,可以运行:
$ 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 格式的提交历史:
$ 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,输出看起来像这样:
$ 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 相同种类的信息:
$ 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 文件中的每行内容打印到标准输出,这样就可以将输出内容重定向到项目的例外文件中:
$ 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 服务器,那么迁移过去能使团队获得更多好处。

三、Git 与 Mercurial

  • DVCS 的宇宙里不只有 Git,实际上,在这个空间里有许多其他的系统。对于如何正确地进行分布式版本管理,每一个系统都有自己的视角。除了 Git,最流行的就是 Mercurial,并且它们两个在很多方面都很相似。
  • 好消息是,如果更喜欢 Git 的客户端行为但是工作在源代码由 Mercurial 控制的项目中,有一种使用 Git 作为 Mercurial 托管仓库的客户端的方法。由于 Git 与服务器仓库是使用远程交互的,那么由远程助手实现的桥接方法就不会让人很惊讶,这个项目的名字是 git-remote-hg,可以在git-remote-hg找到。

① git-remote-hg

  • 首先,需要安装 git-remote-hg,实际上需要将它的文件放在 PATH 变量的某个目录中,像这样:
$ curl -o ~/bin/git-remote-hg \\
  https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg
  • 假定 ~/bin 在 $PATH 变量中,Git-remote-hg 有一个其他的依赖:mercurial Python 库,如果已经安装了 Python,安装它就像这样简单:
$ pip install mercurial
(如果未安装 Python,访问 https://www.python.org/ 来获取它)
  • 需要做的最后一件事是安装 Mercurial 客户端,如果还没有安装的话请访问 Mercurial 来安装。现在准备完成,所需要的一切就是一个可以推送的 Mercurial 仓库。很幸运,每一个 Mercurial 仓库都可以这样做,所以我们只需要使用大家用来学习 Mercurial 的“hello world”仓库就可以了:
$ hg clone http://selenic.com/repo/hello /tmp/hello

② 开始

  • 既然有一个可用的 “server-side” 仓库,可以通过一个典型的工作流来了解。将会看到,这两种系统非常相似,没有太多的出入。和 Git 一样,首先克隆:
$ git clone hg::/tmp/hello /tmp/hello-git
$ cd /tmp/hello-git
$ git log --oneline --graph --decorate
* ac7955c (HEAD, origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master, master) Create a makefile
* 65bb417 Create a standard "hello, world" program
  • 会注意到与 Mercurial 仓库工作时使用了标准的 git clone 命令,那是因为 git-remote-hg 工作在相当低的底层,使用类似于 Git HTTP/S 协议的机制实现的(远程助手)。由于 Git 与 Mercurial 都设计为每一个客户端有一个仓库的完整历史,所以这个命令做了一次完整的克隆,包括所有的项目历史,并且相当快地完成。
  • log 命令显示了两次提交,最后一次提交指向了一大堆引用,那说明这其中的一部分实际上并没有在那儿,让我们看看 .git 目录中实际上有什么:
$ tree .git/refs
.git/refs
├── heads
│   └── master
├── hg
│   └── origin
│       ├── bookmarks
│       │   └── master
│       └── branches
│           └── default
├── notes
│   └── hg
├── remotes
│   └── origin
│       └── HEAD
└── tags

9 directories, 5 files
  • Git-remote-hg 尝试让结构更有 Git 风格,但是在隐藏在下面的是它管理两个轻微不同系统之间概念的映射,refs/hg 目录中存储了实际的远程引用。 例如,refs/hg/origin/branches/default 是一个包含以 “ac7955c” 开始的 SHA-1 值的 Git 引用文件,是 master 所指向的提交。因此 refs/hg 目录是一种类似 refs/remotes/origin 的替代品,但是它引入了书签与分支的区别。
  • notes/hg 文件是 git-remote-hg 如何在 Git 的提交散列与 Mercurial 变更集 ID 之间建立映射的起点。让我们来探索一下:
$ cat notes/hg
d4c10386...

$ git cat-file -p d4c10386...
tree 1781c96...
author remote-hg <> 1408066400 -0800
committer remote-hg <> 1408066400 -0800

Notes for master

$ git ls-tree 1781c96...
100644 blob ac9117f...	65bb417...
100644 blob 485e178...	ac7955c...

$ git cat-file -p ac9117f
0a04b987be5ae354b710cefeba0e2d9de7ad41a9
  • 所以 refs/notes/hg 指向了一个树,即在 Git 对象数据库中的一个有其他对象名字的列表,git ls-tree 输出 tree 对象中所有项目的模式、类型、对象哈希与文件名。如果深入挖掘 tree 对象中的一个项目,我们会发现在其中是一个名字为 “ac9117f” 的 blob 对象(master 所指向提交的 SHA-1 散列值),包含内容 “0a04b98” (是 default 分支指向的 Mercurial 变更集的 ID)。
  • 在我们继续之前,这里还有一件需要注意的事情:忽略。Mercurial 与 Git 使用非常类似的机制实现这个功能,但是一般来说不会想要把一个 .gitignore 文件提交到 Mercurial 仓库中。幸运的是,Git 有一种方式可以忽略本地磁盘仓库的文件,而且 Mercurial 格式是与 Git 兼容的,所以只需将这个文件拷贝过去:
$ cp .hgi

以上是关于Github之深入解析如何在托管在不同系统的项目上使用Git客户端的主要内容,如果未能解决你的问题,请参考以下文章

GitHub之深入解析如何将项目迁移到Git

Git之深入解析如何运行自己的Git仓库托管服务器

GitHub之深入解析如何创建维护和管理自己的项目

Git之深入解析如何使用Git的分布式工作流程与如何管理多人开发贡献的项目

GitHub之深入解析脚本·自定义与修改GitHub来更好地为特定的工作流程工作

深入解析开源项目之Universal-Image-Loader缓存篇