Git正解 脱水版 【9. 其他VCS系统】

9.1 Git寄生

这个世界还不够完美,用户无法在Git中,查看所有的开发项目,但是使用其他VCS系统的开发项目,可以迁移到Git,这里有两种方法,其一,将Git作为客户端,添加到其他的VCS系统中,其二,将开发项目整体迁移到Git。以下将介绍Git在其他VCS系统中的寄生用法,

Git与Subversion(SVN)

大量的开源项目和一些高质量的企业项目,都选择了SVN,在过去的十多年时间里,SVN也一直是开源项目的主流选择,同时它与之前的王者工具CVS很相似。因此Git构建一个双向桥接器git svn,用于SVN的协作,如果用户只使用本地SVN,则可基于Git的本地功能,实现SVN仓库的提交,这不会影响到SVN系统的协作者,直到用户将项目的框架逐渐改成Git风格,协作者才可了解高效的含义。

git svn

该命令的选项不多,它最重要的使命,则是完成与SVN的交互,虽然用户可以使用本地分支,合并,衍合,但是有些功能应当避免使用,比如与Git远程仓库的同步交互,以及在修改提交历史后,不要重新提交,另外,同一时刻下,Git用户不能再推送并行Git仓库(与SVN项目并行的Git项目),由于SVN只有单一的线性提交历史,很容易产生混乱,同时在开发组中,一部分成员使用SVN,另一部分成员使用Git,必须通知所有人,只能在SVN服务器上进行协作,这样更易于管理。

配置

首先用户需要配置一个带写入权限的SVN仓库,为了简化描述,这里将使用svnsync工具,生成一个可写入的SVN仓库的副本,因此需要新建一个SVN本地仓库的保存路径,

$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn

为了确保所有用户都可修改revprop(提交日志),最简单的方法,让pre-revprop-change hook脚本只返回0,这时所有用户都可获得提交日志的修改权限,

$ cat /tmp/test-svn/hooks/pre-revprop-change
#!/bin/sh
exit 0;

$ chmod +x /tmp/test-svn/hooks/pre-revprop-change

调用svnsync init,将SVN远程仓库,关联到本地副本的存储路径,

$ svnsync init file:///tmp/test-svn http://your-svn-server.example.org/svn/

克隆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个,因为SVN只能实现基于版本的克隆,每个版本需要完整导出后,将可进行数据传输,从上述的输出信息可知,因而出现了无法忍受的低效,同时这又是最简单的方法。

应用基础

此时用户已拥有了一个带写入权限的SVN仓库,可运行git svn clone命令,将整个SVN仓库导出到Git本地仓库,应当注意,这不是一个真实的导出操作,只是将SVN仓库的URL修改为本地路径file:///tmp/test-svn,

$ 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
...
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的组合,从输出信息可知,示例项目只有75个提交,代码库也不大,Git无需检查SVN示例项目的每个版本,因为SVN的提交都是相互独立的,如果示例项目包含了上百甚至上千个提交,上述操作可能需要数小时甚至数天。

-T trunk -b branches -t tags,表明SVN仓库中使用了trunk(主分支),branch(分支),tag(标签)标识,如果用户需要修改这些默认标识,可在上述命令中,附带其他的选项,而上述命令附带的选项,都是常见选项,因此可替换成-s选项,以下命令等同于之前的命令,

$ git svn clone file:///tmp/test-svn -s

这时SVN仓库的分支和标签,都已导入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

使用show-ref命令,从输出信息可知,SVN标签已被视为Git仓库的引用,

$ 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

推送提交

这时用户可在SVN项目副本中进行开发,并完成推送,Git仓库将视为SVN的客户端,如果用户修改了项目文件并提交,应当注意,该提交只存在于Git本地仓库,并不存在于SVN服务器.

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

之后用户需要推送提交,可以选择多个提交,一次性推送到SVN服务器,这时可运行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

从输出信息可知,提交已推送到SVN服务器,每个Git提交都将转换成一个SVN提交,应当注意一个重要的细节,提交的SHA-1校验码在提交过程中会被修改,因此建立一个与SVN项目并行的Git仓库,并不是明智的选择,如果需要查看SVN项目的最新提交,从输出信息中可见,已添加了新的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

注意,提交的原始校验码为4af61fd,提交之后,则变为95e0222,如果用户需要同时推送Git仓库和SVN仓库,建议首先推送SVN仓库,因为dcommit推送操作,将会修改提交数据.

获取变更

如果存在开发协作者,不可避免会因为同步问题,而产生推送冲突,除非完成合并操作,如果出现以下冲突,

$ 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.

将衍合结果,提交到SVN仓库,

$ 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 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中,根本不存在这类问题,在提交之前,用户就可检查当前仓库的状态,而在SVN中,提交前后都无法立即确认仓库的当前状态.

用户可使用git svn fetch命令,从SVN仓库中获取最新数据,但使用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工作流,才可体会轻松和愉快,这时用户可创建特性分支,并工作在特性分支上,在适当的时候合并特性分支,而在SVN中,用户只能将开发结果,衍合到单一分支,而不是合并分支,因为SVN只有一个线性提交历史,无需处理分支合并的问题,所以git svn通常需要将提交,转换成项目快照,再传递给SVN.

假定用户的提交示例如下,首先创建了一个experimnet分支,并传入了两个提交,之后该分支将合并到master分支,如果此时使用dcommit,将Git仓库的提交,传递给SVN仓库,

$ 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

这时master分支的合并提交,已传递给SVN,可见experiment分支的两个提交,并未传递给SVN,但是master分支的两个合并提交,传入SVN后,SHA-1校验值都发生了变化,如果此时有协作者同步了SVN仓库,将会看到两个合并提交所包含的变更,被重组在一起,已经无法分辨这些变更来自哪个提交.

SVN分支

SVN分支不同于Git分支,在大多数情况下,用户应当避免使用SVN分支,这可能是最好的选择,当然用户可使用git svn,生成SVN分支,并实现SVN分支的提交.

新建SVN分支
$ 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)

在SVN中,上述命令等同于svn copy trunk branches/opera,从等价命令可知,新建分支只是将主分支trunk,复制到opera,后续的提交依旧会传递给主分支trunk.

切换SVN分支

如果用户需要了解dcommit命令,对应的SVN分支,可查找SVN提交历史的最新提交,即git-svn-id所提供的信息,注意,SVN只有一条主分支,如果用户想实现多条分支的并行,可新建一个本地分支,并关联到特定的SVN分支,比如需要独立运行之前的opera分支,这时Git就可提交到本地分支,

$ git branch opera remotes/origin/opera

如果用户需要将opera分支,合并到trunk分支,可执行git merge,但用户必须使用-m选项,附带一个提交描述,比如Merge branch opera.

应当注意,虽然用户可使用git merge完成合并,似乎和纯Git系统一样简单,因为Git可自动检测最佳的合并点,而实际的合并,并不会生成一个合并提交,如果将合并提交,直接传递给SVN仓库,事实上SVN仓库无法处理,因为这些提交包含了多个父提交,因此需要额外的操作,一旦合并提交(已处理)传递给SVN仓库后,在SVN仓库中,只能看到一个提交,该提交将重组来自不同分支的变更,虽然实现了分支合并,但无法切换分支,dcommit会清除掉分支合并的所有信息,因此后续基于合并的命令操作,只能得到错误结果,所以dcommit命令更类似于git merge --squash,目前无法避免这类情况的发生,SVN无法记录合并信息,用户应当牢记这一点,为了避免出现问题,当本地分支opera合并到trunk后,应将opera分支删除.

SVN命令

git svn提供一组子命令,以方便Git和SVN之间的互连,如下,

提交历史

如果用户需要查看SVN风格的提交历史,可执行git svn log,

$ 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

有两点需要注意,其一,SVN仓库处于离线状态,上述输出并不等同svn log命令,svn log会从SVN服务器读取数据,其二,只显示当前用户的提交,也是已保存在SVN仓库的提交,未传递的Git本地提交将不会显示,

标注

git svn log可视为svn log的离线版,同样git svn blame [文件名]也等同于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 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)

和blame,log一样,info也是离线运行,其中包含了连接SVN服务器的最后时间.

可忽略属性

如果克隆的SVN仓库包含了svn:ignore属性,该属性类似于.gitignore文件,这时git svn提供了两个子命令,其一,git svn create-ignore可自动创建对应的.gitignore文件,在用户的下一次提交中,将应用.gitignore文件的设置,其二,git svn show-ignore可将.gitignore文件,输出到stdout,因此用户可基于重定向,将stdout接收的数据,转存到文件中,

$ git svn show-ignore > .git/info/exclude

Git和Mercurial

在DVCS(分布式版本控制系统)类别中,除了Git之外,还存在大量的其他工具,每个工具的侧重点都不同,最流行的工具,即为Mercurial,在许多功能上,与Git很相似.如果用户喜欢Git的客户端管理功能,同时开发项目又使用了Mercurial,这时可在Mercurial仓库中,使用Git客户端,基于网络链接,使用git-remote-hg工具,可实现Git与Mercurial服务器仓库的交互,该工具的获取地址为https://github.com/felipec/git-remote-hg.

git-remote-hg

首先需要下载和安装git-remote-hg,

$ curl -o ~/bin/git-remote-hg https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg

假定用户的$PATH为~/bin,git-remote-hg依赖于mercurial的python库,如果用户已安装python,可直接下载该支持库,

$ pip install mercurial

同时用户还需要mercurial客户端,从https://www.mercurial-scm.org/页面可下载客户端,当然用户还需要一个供测试使用的mercurial,这里配置了一个测试仓库,如下,

$ hg clone http://selenic.com/repo/hello /tmp/hello

应用基础

以下将基于典型工作流,实现两个系统的交互,用户将看到,两个系统的相似之处,通常不会产生太大的冲突,首先克隆mercurial仓库,

$ 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为Git引用文件(校验码为ac7955c),即master所指向的提交,同时refs/hg可等同于 refs/remotes/origin,但是bookmarks和branches之间存在差异.

git-remote-hg可使用notes/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可输出该文件树中所有对象的模式,类型,哈希值和文件名,之后用户可选择一个对象,比如ac9117f(master所指向的提交),该对象包含了一串哈希值0a04b98,即Mercurial变更集的ID号,并处于default分支的最新位置.

大多数情况下,上述操作不会引发错误,相比于Git远程仓库的处理,典型工作流并没有太大的变动.另一个应当注意的问题,文件忽略的处理,虽然Git和Mercurial使用了类似的机制,但用户无法向Mercurial仓库,提交一个.gitignore文件,同时Git提供一种方法,直接在本地仓库中实现文件忽略,并且Git也兼容Mercurial格式,可直接使用Mercurial文件忽略的配置文件,

$ cp .hgignore .git/info/exclude

.git/info/exclude可等同于.gitignore,但无需提交.

工作流

假定用户完成了一部分开发,并提交到master分支,准备推送到远程仓库,而远程仓库的当前状态如下,

$ git log --oneline --graph --decorate
* ba04a2a (HEAD, master) Update makefile
* d25d16f Goodbye
* ac7955c (origin/master, origin/branches/default, origin/HEAD,
refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Create a makefile
* 65bb417 Create a standard "hello, world" program

Git用户的master分支,即为origin/master,出现了两个提交,同时这些提交都是本地提交,为了获得更多细节,可运行

$ git fetch
From hg::/tmp/hello
   ac7955c..df85e87 master -> origin/master
   ac7955c..df85e87 branches/default -> origin/branches/default
   
$ git log --oneline --graph --decorate --all
* 7b07969 (refs/notes/hg) Notes for default
* d4c1038 Notes for master
* df85e87 (origin/master, origin/branches/default, origin/HEAD,
refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some
documentation
| * ba04a2a (HEAD, master) Update makefile
| * d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program

附带–all选项,可查看git-remote-hg在内部使用的notes引用,其实可以忽略它,测试结果符合预期,origin/master只有一个提交,并在这个提交之后,提交历史出现分支,虽然Mercurial可处理分支合并,但是用户也不必过分期待,

$ git merge origin/master
Auto-merging hello.c
Merge made by the 'recursive' strategy.
hello.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

$ git log --oneline --graph --decorate
* 0c64627 (HEAD, master) Merge remote-tracking branch 'origin/master'
|\
| * df85e87 (origin/master, origin/branches/default, origin/HEAD,
refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some
documentation
* | ba04a2a Update makefile
* | d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program

合并成功,当测试通过后,就可分享给其他协作者,

$ git push
To hg::/tmp/hello
   df85e87..0c64627 master -> master

查看Mercurial远程仓库,推送结果符合用户的期望,

$ hg
 log -G --style compact
o   5[tip]:4,2 dc8fa4f932b8 2014-08-14 19:33 -0700  ben
|\    Merge remote-tracking branch 'origin/master'
| |
| o 4  64f27bcefc35 2014-08-14 19:27 -0700  ben
| |   Update makefile
| |
| o 3:1 4256fc29598f 2014-08-14 19:27 -0700  ben
| |   Goodbye
| |
@ | 2 7db0b4848b3c 2014-08-14 19:30 -0700  ben
|/    Add some documentation
|
o 1   82e55d328c8c 2005-08-26 01:21 -0700  mpm
|   Create a makefile
|
o 0   0a04b987be5a 2005-08-26 01:20 -0700  mpm
    Create a standard "hello, world" program

在变更集中,#2为Mercurial的提交,#3和#4则为git-remote-hg的提交,并使用Git完成推送.

分支与书签

Git只有一类分支,当生成新提交时,该分支引用将会移动,但在Mercurial中,这类引用被称为书签(bookmark),功能与Git分支相似,而Mercurial分支则可视为变更集的记录,这意味着Mercurial分支将一直保存在提交历史中,如下例,

$ hg log -l 1
changeset: 6:8f65e5e02793
branch:    develop
tag:       tip
user:      Ben Straub <[email protected]>
date:      Thu Aug 14 20:06:38 2014 -0700
summary:   More documentation

注意branch文本行,Git无法模拟也无需模拟这类分支功能,因为这两种Mercurial分支,只需替换成Git引用,但是git-remote-hg必须理解两者的差异.

创建Mercurial书签与创建Git分支一样简单,如下,

$ git checkout -b featureA
Switched to a new branch 'featureA'

$ git push origin featureA
To hg::/tmp/hello
 * [new branch] featureA -> featureA

在Mercurial系统中,查看创建结果,

$ hg bookmarks
   featureA     5:bd5ac26f11f9
   
$ hg log --style compact -G
@   6[tip] 8f65e5e02793 2014-08-14 20:06 -0700 ben
|     More documentation
|
o   5[featureA]:4,2 bd5ac26f11f9 2014-08-14 20:02 -0700 ben
|\    Merge remote-tracking branch 'origin/master'
| |
| o 4   0434aaa6b91f 2014-08-14 20:01 -0700 ben
| |   update makefile
| |
| o 3:1 318914536c86 2014-08-14 20:00 -0700 ben
| |   goodbye
| |
o | 2   f098c7f45c4f 2014-08-14 20:01 -0700 ben
|/    Add some documentation
|
o  1  82e55d328c8c 2005-08-26 01:21 -0700 mpm
|    Create a makefile
|
o  0  0a04b987be5a 2005-08-26 01:20 -0700 mpm
     Create a standard "hello, world" program

在变更集#5中,出现了[featureA]标记,它与Git分支很相似,但有一点应当注意,在Git系统中,无法删除书签,至于创建Mercurial分支,实际上就是将分支,放置于branches名字空间下,

$ git checkout -b branches/permanent
Switched to a new branch 'branches/permanent'

$ vi Makefile
$ git commit -am 'A permanent change'
$ git push origin branches/permanent
To hg::/tmp/hello
 * [new branch] branches/permanent -> branches/permanent

在Mercurial系统中,查看创建结果,

$ hg branches
permanent   7:a4529d07aad4
develop     6:8f65e5e02793
default     5:bd5ac26f11f9 (inactive)

$ hg log -G
o changeset:    7:a4529d07aad4
| branch:       permanent
| tag:          tip
| parent:       5:bd5ac26f11f9
| user:         Ben Straub <[email protected]>
| date:         Thu Aug 14 20:21:09 2014 -0700
| summary:      A permanent change
|
| @ changeset:  6:8f65e5e02793
|/ branch:      develop
| user:         Ben Straub <[email protected]>
| date:         Thu Aug 14 20:06:38 2014 -0700
| summary:      More documentation
|
o changeset:    5:bd5ac26f11f9
|\ bookmark:    featureA
| | parent:     4:0434aaa6b91f
| | parent:     2:f098c7f45c4f
| | user:       Ben Straub <[email protected]>
| | date:       Thu Aug 14 20:02:21 2014 -0700
| | summary:    Merge remote-tracking branch 'origin/master'
[...]

permanent分支记录了#7变更集,对于Git来说,上述两条分支的操作是一样的,但是用户必须记住,Mercurial不支持提交历史的修改,只能进行添加,因此上述的Mercurial仓库很像是,交互式衍合以及强制推送之后的结果.

Git和Bazaar

在DVCS工具中,还有另一种主流工具Bazaar,它是一个免费的开源工具,并且是GNU项目的一部分,但是它的工作流与Git完全不同,同时它给出的命令关键字,让人难以理解,尤其是分支的管理,很容易引发冲突,特别是具备Git使用经验的人.

很多开源工具都可实现Bazaar的Git客户端,这里使用Felipe Contreras的项目,可从https://github.com/felipec/git-remote-bzr-bzr页面中找到,首先下载和安装该工具,

$ wget https://raw.github.com/felipec/git-remote-bzr/master/git-remote-bzr -O ~/bin/git-remote-bzr
$ chmod +x ~/bin/git-remote-bzr

新建Git仓库(基于Bazaar仓库)

使用bzr::前缀,可克隆Bazaar仓库,之后需将Git仓库目录,直接放置到Bazaar仓库中,使用ssh地址bzr+ssh://developer@[bazaar服务器名]:[项目名],克隆Bazaar仓库,

$ git clone bzr::bzr+ssh://developer@mybazaarserver:myproject myProject-Git
$ cd myProject-Git

在克隆过程中,本地Git仓库也已创建,但是该Git仓库占用了过多的磁盘空间,必须首先清理和压缩新建的Git仓库,尤其在大型项目中,

$ git gc --aggressive

分支

Bazaar仓库可包含多条分支,git-remote-baz允许用户克隆单条或所有分支,比如克隆单条分支,

$ git clone bzr::bzr://bzr.savannah.gnu.org/emacs/trunk emacs-trunk

或克隆整个Bazaar仓库,

$ git clone bzr::bzr://bzr.savannah.gnu.org/emacs emacs

上述可获取远程仓库的所有分支,但用户可通过配置,在仓库克隆中,只克隆指定分支,如下,

$ git config remote-bzr.branches 'trunk, xwindow'

有些远程Bazaar仓库,不允许用户,查询仓库的所有分支,这时用户只能手动设置,

$ git init emacs
$ git remote add origin bzr::bzr://bzr.savannah.gnu.org/emacs
$ git config remote-bzr.branches 'trunk, xwindow'
$ git fetch

可忽略文件

由于当前项目受控于Bazaar,因此无法创建.gitignore文件,因为它对项目控制并无意义,同时还会干扰到其他协作者,此时需创建一个.git/info/exclude文件,它既可以是符号连接,也可以是一个正常文件.

Bazaar中忽略文件的管控机制,与Git相似,但需要注意两个功能,

  1. !!符号之后,可附带一个忽略文件的模板,也可使用!符号
  2. 行首的RE::之后,可指定一个python风格的正则表达式(Git只允许shell风格)

除此之外,还需要考虑两种不同的应用场景,

  1. 如果分支克隆和仓库克隆中,都未包含.bzrignore文件,用户可创建一个符号链接,ln -s .bzrignore .git/info/exclude
  2. 另外用户必须创建.git/info/exclude文件,其中的配置也应当与.bzrignore相同

剩下的问题,保证.git/info/exclude和.bzrignore之间的配置同步,最简单的方法,创建.git/info/exclude符号链接,指向.bzrignore.

获取变更

为获取远程仓库的更新,可使用pull命令,假定远程仓库的master分支出现更新,用户可将远程更新,合并或衍合到本地的origin/master分支,

$ git pull --rebase origin

推送变更

由于Bazaar包含了提交合并的机制,因此推送一个合并提交,并不会引发问题,用户只需在Git中,将特性分支合并到master分支,再进行提交,

$ git push origin master

警告

应当注意Git的桥接工具,通常存在一些应用限制,比如以下命令将无法工作,
- git push origin 分支将被删除,因为Bazaar不接受这种方式的引用删除
- git push origin old:new 只能推送到old,无法限定new
- git push --dry-run origin branch 只能完成推送,不支持其他功能

Git和Perforce

Perforce在企业级用户中相当流行,首个版本发布于1995年,其中包含了日期约束,同时用户必须一直连接单个中心服务器,本地磁盘也只能保留一个版本,所以这个版本控制系统只适合于特定问题的修复,但目前依然有大量项目在使用Perforce.

目前有两种工具,可桥接Git和Perforce,其一为Git Fusion,它可导出Perforce仓库的子树,并视为一个可读写的Git仓库,其二为git-p4,可将Git视为Perforce的客户端,无需重新配置Perforce服务器.

Git Fusion

可从http://www.perforce.com/git-fusion页面中,找到Git Fusion,它可以实现Git仓库与Perforce服务器之间的同步.

创建

首先需要一个虚拟器,这里采用VirtualBox,在虚拟机中,可运行Perforce后台和Git Fusion,从http://www.perforce.com/downloads/Perforce/20-Usert页面,找到虚拟机镜像,将镜像添加到虚拟机,之后启动虚拟机,用户需要配置三个linux账号的密码,分别是root/perforce/git,还需要创建一个新账号,以区别不同的使用者,一切完成后,将看到以下显示,
在这里插入图片描述
记住界面给出的IP地址,之后需使用它,首先需要创建一个Perforce用户账号,点击下部的Login,使用root账号登录系统,使用以下命令,创建一个Perforce用户账号,

$ p4 -p localhost:1666 -u super user -f john
$ p4 -p localhost:1666 -u john passwd
$ exit

可自定义用户的默认属性,之后需输入两次用户密码,最后退出当前会话,完成用户创建.之后需告知Git,无需验证SSL证书,虽然Git Fusion镜像中包含了一个证书,但Perforce后台无法将证书,匹配虚拟机的IP地址,所以Git将拒绝HTTPS连接,因此可选择重新安装证书,或者取消Git的SSL验证,如下,

$ export GIT_SSL_NO_VERIFY=true

测试工作环境是否正常,

$ git clone https://10.0.1.254/Talkhouse
Cloning into 'Talkhouse'...
Username for 'https://10.0.1.254': john
Password for 'https://[email protected]':
remote: Counting objects: 630, done.
remote: Compressing objects: 100% (581/581), done.
remote: Total 630 (delta 172), reused 0 (delta 0)
Receiving objects: 100% (630/630), 1.22 MiB | 0 bytes/s, done.
Resolving deltas: 100% (172/172), done.
Checking connectivity... done.

基于HTTPS连接,克隆示例项目的测试已通过,同时在虚拟机镜像中,创建了john账号,Git询问SSL证书的步骤已被跳过.

配置

Git Fusion已安装成功,配置过程也很简单,也就是将Perforce服务器的//.git-fusion目录,映射到本地工作区,之后可查看对应目录,

$ tree
.
├── objects
│  ├── repos
│  │  └── [...]
│  └── trees
│     └── [...]
│
├── p4gf_config
├── repos
│  └── Talkhouse
│     └── p4gf_config
└── users
   └── p4gf_usermap
   
498 directories, 287 files

Git Fusion的内部目录为objects,它可实现Perforce和Git之间的对象映射,用户需小心对待该目录,根目录下,还包含了全局配置文件p4gf_config,每个Perforce仓库中都会包含该配置文件,其中可设定Git Fusion的操作模式,如下,

[repo-creation]
charset = utf8

[git-to-perforce]
change-owner = author
enable-git-branch-creation = yes
enable-swarm-reviews = yes
enable-git-merge-commits = yes
enable-git-submodules = yes
preflight-commit = none
ignore-author-permissions = no
read-permission-check = none
git-merge-avoidance-after-change-num = 12107

[perforce-to-git]
http-url = none
ssh-url = none

[@features]
imports = False
chunked-push = False
matrix2 = False
parallel-push = False

[authentication]
email-case-sensitivity = no

用户不要随意修改上述配置,同时该文件的格式为ini,全局配置文件p4gf_config可被仓库的p4gf_config所覆盖,比如repos/Talkhouse/p4gf_config,该文件的[@repo]配置段,与全局配置不同,

[Talkhouse-master]
git-branch-name = master
view = //depot/Talkhouse/main-dev/... ...

其中包含了Perforce分支和Git分支的映射,给出的分支名必须是唯一的,git-branch-name可将一个笨重的路径名,转换成一个更友好的名称,view可设定,需映射到Git仓库的Perforce文件,这里使用的标准模式,当然也可使用其他的映射模式,如下,

[multi-project-mapping]
git-branch-name = master
view = //depot/project1/main/... project1/...
       //depot/project2/mainline/... project2/...

最后需要注意的文件是users/p4gf_usermap,它可实现Perforce账号与Git账号的映射,当然用户无需手动使用该功能,当一个Perforce变更集转换成一个Git提交时,Git Fusion将默认查找Perforce账号,并将账号信息,充填Git提交的作者名/提交者名和邮箱地址,当反向转换时,将基于Git提交的作者邮箱地址,查找对应的Perforce账号,在大多数情况下,该操作不会产生问题,映射文件如下,

john [email protected] "John Doe"
john [email protected] "John Doe"
bob [email protected] "Anon X. Mouse"
joe [email protected] "Anon Y. Mouse"

每行文本都可设定单个账号的映射,每行起始的账号名和邮箱地址为Perforce账号,如果Git提交中需要多个邮箱地址,可创建多行文本,映射同一个Perforce账号,注意最后两行文本,即bob和joe,employee是一个无需公开发布的内部项目,因此可使用虚构的信息,但是邮件地址和Git账号必须是唯一的.

工作流

之前已介绍过,Perforce Git Fusion可提供双向的桥接方式,查看Git仓库的工作流,假定需要克隆一个Jam项目(Perforce),

$ git clone https://10.0.1.254/Jam
Cloning into 'Jam'...
Username for 'https://10.0.1.254': john
Password for 'https://[email protected]':
remote: Counting objects: 2070, done.
remote: Compressing objects: 100% (1704/1704), done.
Receiving objects: 100% (2070/2070), 1.21 MiB | 0 bytes/s, done.
remote: Total 2070 (delta 1242), reused 0 (delta 0)
Resolving deltas: 100% (1242/1242), done.
Checking connectivity... done.

$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/rel2.1
  
$ git log --oneline --decorate --graph --all
* 0a38c33 (origin/rel2.1) Create Jam 2.1 release branch.
| * d254865 (HEAD, origin/master, origin/HEAD, master) Upgrade to latest metrowerks on
Beos -- the Intel one.
| * bd2f54a Put in fix for jam's NT handle leak.
| * c0f29e7 Fix URL in a jam doc
| * cc644ac Radstone's lynx port.
[...]

首先Git Fusion会将Perforce提交历史中所有的变更集,转换成Git提交,这些操作发生在服务端,因此速度较快,当然也取决于变更集的规模,后续的数据传输,才取决于Git的传输速度.

此时的Perforce仓库已相当近似于Git仓库,其中包含了三条分支,并且之前已完成了一部分开发,并生成了一组提交,从git log --oneline --decorate --graph --all命令的输出可知,因此Git用户需将这些提交,同步到本地,

$ git fetch
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://10.0.1.254/Jam
   d254865..6afeb15 master -> origin/master
 
$ git log --oneline --decorate --graph --all
* 6afeb15 (origin/master, origin/HEAD) Update copyright
| * cfd46ab (HEAD, master) Add documentation for new feature
| * a730d77 Whitespace
|/
* d254865 Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

实际上6afeb15提交来自于Perforce仓库,但是与Git提交并无差别,接着可查看Perforce服务端如何处理合并提交,

$ git merge origin/master
Auto-merging README
Merge made by the 'recursive' strategy.
README | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

$ git push
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 6), reused 0 (delta 0)
remote: Perforce: 100% (3/3) Loading commit tree into memory...
remote: Perforce: 100% (5/5) Finding child commits...
remote: Perforce: Running git fast-export...
remote: Perforce: 100% (3/3) Checking commits...
remote: Processing will continue even if connection is closed.
remote: Perforce: 100% (3/3) Copying changelists...
remote: Perforce: Submitting new Git commit objects to Perforce: 4
To https://10.0.1.254/Jam
   6afeb15..89cba2b master -> master

从上述输出可知,Git合并提交已推送到Perforce远程仓库,使用Perforce环境的工具,可看到合并提交已放置到匿名目录中,因此Perforce和Git可以协同工作.

Git-p4

Git和Perforce双向桥接的另一个工具为Git-p4,它运行在Git仓库中,因此用户无法配置Perforce服务端,虽然Git-p4还不如Git Fusion灵活和完善,但是它无需侵入到Perforce系统中,另外p4支持工具,可从http://www.perforce.com/downloads/Perforce/20-User页面下载.

创建

首先配置git-p4依赖的p4命令行客户端,所需的环境变量,

$ export P4PORT=10.0.1.254:1666
$ export P4USER=john
配置

运行克隆命令,

$ git p4 clone //depot/www/live www-shallow
Importing from //depot/www/live into www-shallow
Initialized empty Git repository in /private/tmp/www-shallow/.git/
Doing initial import of //depot/www/live/ from revision #head into
refs/remotes/p4/master

上述命令只完成了"浅"克隆,只有最新的Perforce版本,才会导入Git,这是Perforce的设计理念,用户具有权限,并不是每个用户都能获取所有版本,查看完成克隆的Git仓库,

$ cd myproject
$ git log --oneline --all --graph --decorate
* 70eaf78 (HEAD, p4/master, p4/HEAD, master) Initial import of //depot/www/live/ from
the state at revision #head

在上述输出中,p4即为Perforce远程仓库,但事实上,这个远程仓库并不存在,

$ git remote -v

不会显示Perforce远程仓库,Git-p4只是创建一些引用,用于描述远程仓库的状态,这使得git log的输出,看上去很像一个远程仓库,由于Perforce远程仓库不受Git的管理,因此也无法直接推送.

工作流

假定Git用户完成了一部分开发,并生成了两次提交,如下,

$ git log --oneline --all --graph --decorate
* 018467c (HEAD, master) Change page title
* c0fb617 Update link
* 70eaf78 (p4/master, p4/HEAD) Initial import of //depot/www/live/ from the state at revision #head

这时需要将两次提交,推送到Perforce仓库,

$ git p4 sync
git p4 sync
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12142 (100%)

$ git log --oneline --all --graph --decorate
* 75cd059 (p4/master, p4/HEAD) Update copyright
| * 018467c (HEAD, master) Change page title
| * c0fb617 Update link
|/
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

从上述信息可知,master和p4/master之间产生了分离点,Perforce的分支不同于Git,推送合并提交并无意义(分支并不会合并),因此Git-p4推荐Git用户使用衍合,以便得到一个更简洁的提交历史,

$ git p4 rebase
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
No changes to import!
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
Applying: Update link
Applying: Change page title
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

git p4 rebase可获得一个更简明的提交记录,并等同于git rebase p4/master,这是更聪明的方法,尤其在多分支的合并中,基本能实现两个仓库系统之间的完美近似.这时提交历史已成线性,可将提交推送到Perforce远程仓库,git p4 submit将基于p4/master和master之间的Git提交,创建一个Perforce新版本,同时会打开编辑器,并显示以下的描述文件,

# A Perforce Change Specification.
#
# Change:      The change number. 'new' on a new changelist.
# Date:        The date this specification was last modified.
# Client:      The client on which the changelist was created. Read-only.
# User:        The user who created the changelist.
# Status:      Either 'pending' or 'submitted'. Read-only.
# Type:        Either 'public' or 'restricted'. Default is 'public'.
# Description: Comments about the changelist. Required.
# Jobs:        What opened jobs are to be closed by this changelist.
#              You may delete jobs from this list. (New changelists only.)
# Files:       What opened files from the default changelist are to be added
#              to this changelist. You may delete files from this list.
#              (New changelists only.)

Change:      new
Client:      john_bens-mbp_8487
User:        john
Status:      new
Description: Update link
Files:       //depot/www/live/index.html   # edit

######## git author [email protected] does not match your p4 account.
######## Use option --preserve-user to modify authorship.
######## Variable git-p4.skipUserNameCheck hides this message.
######## everything below this line is just the diff #######
--- //depot/www/live/index.html 2014-08-31 18:26:05.000000000 0000
+++ /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/index.html  2014-
08-31 18:26:05.000000000 0000
@@ -60,7 +60,7 @@
 </td>
 <td valign=top>
 Source and documentation for
-<a href="http://www.perforce.com/jam/jam.html">
+<a href="jam.html">
 Jam/MR</a>,
 a software build tool.
 </td>

当运行p4 submit时,可看到大致相同的内容,但在文件末尾,由git p4 submit充填的变更信息,则不相同,当需要为一个提交或变更集,提供一个名称时,Git-p4将会沿用Git和Perforce的设置,但大多数情况下,用户都需要进行修改,比如Git提交的作者并无对应的Perforce账号,则用户需要在转换后的变更集中,修改相关信息.当完成相关信息的修改后,推送才可继续,如下,

$ git p4 submit
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-
mbp_8487/john_bens-mbp_8487/depot/www/live/
Synchronizing p4 checkout...
... - file(s) up-to-date.
Applying dbac45b Update link
//depot/www/live/index.html#4 - opened for edit
Change 12143 created with 1 open file(s).
Submitting change 12143.
Locking 1 files ...
edit //depot/www/live/index.html#5
Change 12143 submitted.
Applying 905ec6a Change page title
//depot/www/live/index.html#5 - opened for edit
Change 12144 created with 1 open file(s).
Submitting change 12144.
Locking 1 files ...
edit //depot/www/live/index.html#6
Change 12144 submitted.
All commits applied!
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12144 (100%)
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...

$ git log --oneline --all --graph --decorate
* 775a46f (HEAD, p4/master, p4/HEAD, master) Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

注意,在Git提交转换成Perforce变更集的过程中,如果用户希望将Git提交,重组到单个变更集中,应在执行git p4 submit之前,进行一次衍合,这时所有Git提交的校验码都将改变,因为git-p4会在每个提交文件的末尾,添加一行帮助信息,如下,

$ git log -1
commit 775a46f630d8b46535fc9983cf3ebe6b9aa53145
Author: John Doe <[email protected]>
Date:   Sun Aug 31 10:31:44 2014 -0800

    Change page title
    
    [git-p4: depot-paths = "//depot/www/live/": change = 12144]

如果推送合并提交,Perforce远程仓库将会如何处理,以下是Git仓库和Perforce仓库的提交历史,

$ git log --oneline --all --graph --decorate
* 3be6fd8 (HEAD, master) Correct email address
* 1dcbf21 Merge remote-tracking branch 'p4/master'
|\
| * c4689fc (p4/master, p4/HEAD) Grammar fix
* | cbacd0a Table borders: yes please
* | b4959b6 Trademark
|/
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

从输出信息可知,在775a46f提交之后,Git提交历史和Perforce提交历史,产生了分离,Git提交历史中,包含了两个提交,其中之一为,已合并到Perforce仓库的合并提交,如果将Perforce仓库中最新的单个变更集,推送到Perforce远程仓库,如下,

$ git p4 submit -n
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-
mbp_8487/john_bens-mbp_8487/depot/www/live/
Would synchronize p4 checkout in /Users/ben/john_bens-mbp_8487/john_bens-
mbp_8487/depot/www/live/
Would apply
  b4959b6 Trademark
  cbacd0a Table borders: yes please
  3be6fd8 Correct email address

-n选项即为–dry-run的缩写,它可在推送成功后,显示命令操作的细节,从输出信息中可知,首先创建了三个Perforce变更集,分别对应三个非合并提交,以下是推送之后的结果,

$ git log --oneline --all --graph --decorate
* dadbd89 (HEAD, p4/master, p4/HEAD, master) Correct email address
* 1b79a80 Table borders: yes please
* 0097235 Trademark
* c4689fc Grammar fix
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Perforce本地仓库变为线性历史,因为在推送之前,进行提交的衍合,所以用户无需担心Git仓库的操作,会产生不兼容的Perforce提交,因为在推送到Perforce远程仓库前,都将被衍合.

分支

如果Perforce仓库包含了多条分支,无需奇怪,只是git-4p进行处理,使得转换结果更类似于Git仓库,假定Perforce仓库如下,

//depot
└── project
  ├── main
  └── dev

Git-p4可自动检测当前状态,并进行必要的转换,

$ git p4 clone --detect-branches //depot/project@all
Importing from //depot/project@all into project
Initialized empty Git repository in /private/tmp/project/.git/
Importing revision 20 (50%)
    Importing new branch project/dev
    Resuming with change 20
Importing revision 22 (100%)
Updated branches: main dev

$ cd project; git log --oneline --all --graph --decorate
* eae77ae (HEAD, p4/master, p4/HEAD, master) main
| * 10d55fb (p4/project/dev) dev
| * a43cfae Populate //depot/project/main/... //depot/project/dev/....
|/
* 2b83451 Project init

@all表示路径下的所有目录,并可告知git-4p,不仅克隆最新的变更集,而且将克隆@all下的所有变更集,–detect-branches选项,可告知git-4p,将Perforce分支映射为Git分支引用,这些映射关系并不存在于Perforce远程仓库,

$ git init project
Initialized empty Git repository in /tmp/project/.git/
$ cd project
$ git config git-p4.branchList main:dev
$ git clone --detect-branches //depot/project@all .

将git-p4.branchList配置变量,设为main:dev,可告知git-4p,Perforce仓库存在两条分支main和dev,同时dev为main的子分支.

使用git checkout -b dev p4/project/dev, 基于p4/project/dev分支,创建一个本地Git分支dev,如果用户生成了提交,git-4p能够区分用户需提交的分支,但是git-4p无法混合使用浅克隆和多分支克隆,如果Perforce仓库保存了一个巨型项目,并包含了多条分支,这时获取每条分支,都必须运行一次git p4 clone.

Git-p4只能同步和推送已存在的分支,每次只能得到一个线性的变更集,如果用户在Git系统中,合并了两条分支,再将合并结果,转换一个新的变更集,并推送到Perforce远程仓库时,只能得到一组文件变更的记录,与分支相关的信息,将全部丢失.

Git和TFS

当Git在Windows开发者中越来越流行,在Windows系统下,用户还可以选择TFS(Microsoft’s Team Foundation Server),这是一个协作套件,其中包含缺陷和工作项(work-item)的跟踪,可使用Scrum(敏捷)和其他类型的软件开发过程,代码浏览,同时TFS还包含了TFVC(Team Foundation Version Control)系统,Git支持TFS(2013版本)的某些新功能,而TFS的范畴远远超出了版本控制的需求,所以Windows开发者在大多数时间里,一直在使用TFVC.

工具集

目前存在两种桥接工具git-tf和git-tfs,git-tfs(https://github.com/git-tfs/git-tfs)是一个.NET项目,只能运行在Windows系统下,为了运行Git仓库,必须使用.NET绑定库libgit2,该库可支持Git的高性能,并保留足够的灵活性,但是无法完全兼容Git,在一些操作中,需要直接调用Git命令,因此不存在人为的限制,同时git-tfs对于TFVC的支持相当完善,可使用Visual Studio组件,完成TFS服务器的操作,这也意味着用户需要安装Visual Studio(2010版本,以及Express 2012版本)或是VS SDK.

Git-tf(https://gittf.codeplex.com)是一个java项目,可使用JGit(Git的JVM接口),访问Git仓库,基本兼容Git命令,但是不支持分支功能.

当然工具始终都存在优点和确定,是否适合,主要取决于应用场景.

git-tf

首先需要克隆TFS仓库,

$ git tf clone https://tfs.codeplex.com:443/tfs/TFS13 $/myproject/Main project_git

第一个参数为TFVC仓库的URL,第二个参数为用户的本地保存路径,第三个参数为本地Git仓库名(可选参数),Git-tf每次只能使用一条分支,如果存在多条TFVC分支,则需要克隆多条分支,之后可查看Git仓库,

$ cd project_git
$ git log --all --oneline --decorate
512e75a (HEAD, tag: TFS_C35190, origin_tfs/tfs, master) Checkin message

同样这也是一次浅克隆,只会下载最新的变更集,TFVC的设计理念同样是,并非所有的客户端,都能获取完整的提交历史,git-tf的默认设置,即获取最新版本,为了克隆特定分支的完整提交历史,可使用 --deep选项,

$ git tf clone https://tfs.codeplex.com:443/tfs/TFS13 $/myproject/Main project_git --deep
Username: domain\user
Password:
Connecting to TFS...
Cloning $/myproject into /tmp/project_git: 100%, done.
Cloned 4 changesets. Cloned last changeset 35190 as d44b17a

$ cd project_git
$ git log --all --oneline --decorate
d44b17a (HEAD, tag: TFS_C35190, origin_tfs/tfs, master) Goodbye
126aa7b (tag: TFS_C35189)
8f77431 (tag: TFS_C35178) FIRST
0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the Team Project Creation Wizard

类似于TFS_C35189的标记,可帮助用户了解,Git提交所对应的TFVC变更集,当然用户可配置git config git-tf.tag false,取消上述映射关系的显示,同时git-tf会在.git/git-tf文件中,保证完整的提交与变更集的映射.

git-tfs

该工具的克隆操作会有点复杂,如下,

PS> git tfs clone --with-branches \
    https://username.visualstudio.com/DefaultCollection \
    $/project/Trunk project_git
Initialized empty Git repository in C:/Users/ben/project_git/.git/
C15 = b75da1aba1ffb359d00e85c52acb261e4586b0c9
C16 = c403405f4989d73a2c3c119e79021cb2104ce44a
Tfs branches found:
- $/tfvc-test/featureA
The name of the local branch will be : featureA
C17 = d202b53f67bde32171d5078968c644e562f1c439
C18 = 44cd729d8df868a8be20438fdeeefb961958b674

–with-branches选项,可生成TVFC分支与Git分支的映射,同时还能将TFVC分支,配置成本地的Git分支,如果用户需要克隆TFS的所有分支,或是需要合并分支,强烈推荐该选项,但是TFS 2010之前的版本,无法支持该功能,之后可查看已生成的Git仓库,

PS> git log --oneline --graph --decorate --all
* 44cd729 (tfs/featureA, featureA) Goodbye
* d202b53 Branched from $/tfvc-test/Trunk
* c403405 (HEAD, tfs/default, master) Hello
* b75da1a New project

PS> git log -1
commit c403405f4989d73a2c3c119e79021cb2104ce44a
Author: Ben Straub <[email protected]>
Date:   Fri Aug 1 03:41:59 2014 +0000
    Hello
    git-tfs-id: [https://username.visualstudio.com/DefaultCollection]$/myproject/Trunk;C16

从上述输出可知,存在两条本地分支master和featureA,对应TFVC的初始分支Trunk和子分支featureA,同时tfs远程服务器也包含了一组引用default和featureA,用于表示TFVC分支,而Git-fs已将tfs/default分支和子分支,克隆到本地.

应当注意,git-tfs-id提供的提交信息,git-fs将使用这些信息,标记Git提交与TFVC变更集的对应关系,同时存在一个隐含意义,即Git提交在推送到TFVC的前后,它的校验码将发生变化.

工作流

无论使用哪个工具,首先必须完成一组Git配置的设定,以免出现问题,

$ git config set --local core.ignorecase=true
$ git config set --local core.autocrlf=false

之后用户可继续项目的开发,但是TFVC和TFS的一些功能,会将更多的复杂性,引入工作流,

  1. TFVC无法描述特性分支,这与Git分支并不一致.
  2. 虽然TFVC允许用户在服务器上查看文件,但是文件已被锁定,无法进行编辑,这当然无法阻止用户对本地仓库的文件修改,因此除非用户将文件变更,推送到TFVC服务器,否则服务器的文件不会被修改.
  3. TFS具备门禁功能,当门禁关闭之后,可保证TFS测试周期不被打扰,TFVC shelve支持该功能,因此用户需要手动处理这类问题,同时git-tfs提供了checkintool命令,用于查看门禁的状态.
git-tf工作流

当用户完成了一部分开发,并在master分支上,生成了一组提交,并准备推送到TFVC服务器,首先可查看本地Git仓库,

$ git log --oneline --graph --decorate --all
* 4178a82 (HEAD, master) update code
* 9df2ae3 update readme
* d44b17a (tag: TFS_C35190, origin_tfs/tfs) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the Team Project Creation Wizard

用户决定将4178a82提交,推送到TFVC服务器,首先需查看TFVC服务器上,是否有未同步的最新数据,

$ git tf fetch
Username: domain\user
Password:
Connecting to TFS...
Fetching $/myproject at latest changeset: 100%, done.
Downloaded changeset 35320 as commit 8ef06a8. Updated FETCH_HEAD.

$ git log --oneline --graph --decorate --all
* 8ef06a8 (tag: TFS_C35320, origin_tfs/tfs) just some text
| * 4178a82 (HEAD, master) update code
| * 9df2ae3 update readme
|/
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the Team Project Creation Wizard

这时已有新提交,同时提交历史已出现分支,可选择两种处理方法,

  1. 创建合并提交,但TFVC无法理解合并提交,如果用户推送合并提交,将会得到一个与Git完全不同的结果,甚至会引发冲突,只能将所有变更,打包放入一个变更集中,这大概是最简单的选择.

  2. 衍合提交,以保证一个线性的提交历史,这意味着每个Git提交,都将转换成一个TFVC变更集,这种方法对于大多数功能,都非常友好,推荐使用它,因此git-tf提供了相应的命令git tf pull --rebase.

$ git rebase FETCH_HEAD
First, rewinding head to replay your work on top of it...
Applying: update readme
Applying: update code

$ git log --oneline --graph --decorate --all
* 5a0e25e (HEAD, master) update code
* 6eb3eb5 update readme
* 8ef06a8 (tag: TFS_C35320, origin_tfs/tfs) just some text
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the Team Project Creation Wizard

git-tf可选择,生成一个变更集,并放入所有的文件变更(使用默认选项 --shallow),或是为每个Git提交,生成一个新的变更集(使用 --deep选项),以下将生成一个包含所有文件变更的变更集,

$ git tf checkin -m 'Updating readme and code'
Username: domain\user
Password:
Connecting to TFS...
Checking in to $/myproject: 100%, done.
Checked commit 5a0e25e in as changeset 35348

$ git log --oneline --graph --decorate --all
* 5a0e25e (HEAD, tag: TFS_C35348, origin_tfs/tfs, master) update code
* 6eb3eb5 update readme
* 8ef06a8 (tag: TFS_C35320) just some text
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

新标记TFS_C35348保存了Git提交5a0e25e,应当注意,并不是所有的Git提交,都需要精准对应到TFVC,比如Git提交6eb3eb5,并未推送到TFVC服务器,以上操作就是典型的工作流,同时用户还需要考虑其他一些问题,
- Git-tf无法兼容Git的分支概念,每条TFVC分支都只能单独创建一个Git仓库.
- 协作开发只能选择TFVC或Git,不能混用两者,相同TFVC仓库的多次克隆,将会导致Git提交出现的不同校验值.
- 如果开发组选择了Git协作,并定期同步到TFVC,那么连接TFVC必须是同一个Git仓库.

git-tfs工作流

在之前的工作场景中,使用git-tfs,首先Git仓库的master分支已生成了新提交,

PS> git log --oneline --graph --all --decorate
* c3bd3ae (HEAD, master) update code
* d85e5a2 update readme
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 (tfs/default) Hello
* b75da1a New project

同时TFVC仓库中,也出现了需同步的新数据,

PS> git tfs fetch
C19 = aea74a0313de0a391940c999e51c5c15c381d91d

PS> git log --all --oneline --graph --decorate
* aea74a0 (tfs/default) update documentation
| * c3bd3ae (HEAD, master) update code
| * d85e5a2 update readme
|/
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

协作者推送的TFVC变更集,已导致tfs/default远程分支,移动到提交aea74a0,为了解决提交历史的分叉,可选择两种方法,

  1. 衍合提交
  2. 合并提交

这里选择衍合提交,

PS> git rebase tfs/default
First, rewinding head to replay your work on top of it...
Applying: update readme
Applying: update code
PS> git log --all --oneline --graph --decorate
* 10a75ac (HEAD, master) update code
* 5cec4ab update readme
* aea74a0 (tfs/default) update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

此时可将衍合提交推送到TFVC服务器,这里使用rcheckin命令,可将每个Git衍合提交,转换成一个TFVC变更集,并推送到TFVC服务器的第一个tfs远程分支,

PS> git tfs rcheckin
Working with tfs remote: default
Fetching changes from TFS to minimize possibility of late conflict...
Starting checkin of 5cec4ab4 'update readme'
 add README.md
C20 = 71a5ddce274c19f8fdc322b4f165d93d89121017
Done with 5cec4ab4b213c354341f66c80cd650ab98dcf1ed, rebasing tail onto new TFS-commit...
Rebase done successfully.
Starting checkin of b1bf0f99 'update code'
 edit .git\tfs\default\workspace\ConsoleApplication1/ConsoleApplication1/Program.cs
C21 = ff04e7c35dfbe6a8f94e782bf5e0031cee8d103b
Done with b1bf0f9977b2d48bad611ed4a03d3738df05ea5d, rebasing tail onto new TFS-commit...
Rebase done successfully.
No more to rcheckin.

PS> git log --all --oneline --graph --decorate
* ff04e7c (HEAD, tfs/default, master) update code
* 71a5ddc update readme
* aea74a0 update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

每次转换,都将在提交文件末尾,添加git-tfs-id行,其中包含了转换信息,同时也将改变Git提交的校验值,用户应当该问题,以免产生失误.

TFS还包含了版本控制的很多集成功能,比如工作项追踪,设计预览,门禁检查等等,如果基于命令行,使用这些功能,将会非常复杂和笨重,因而git-tfs提供了一个图形化工具checkintool,同时Git-tfs也允许,Git对TFVC分支进行管理,如下,

PS> git tfs branch $/tfvc-test/featureBee
The name of the local branch will be : featureBee
C26 = 1d54865c397608c004a2cadce7296f5edc22a7e5

PS> git log --oneline --graph --decorate --all
* 1d54865 (tfs/featureBee) Creation branch $/myproject/featureBee
* ff04e7c (HEAD, tfs/default, master) update code
* 71a5ddc update readme
* aea74a0 update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

在TFVC中,创建一个分支,等同于添加一个变更集,同时也可视为一个Git提交,在上述命令中,创建了tfs/featureBee远程分支,但HEAD一直会指向master分支,如果用户需要在新分支上进行开发,则需要基于提交1d54865,创建新的提交,或是创建一个特性分支.

9.2 项目迁移

SVN

使用之前介绍的git svn clone,克隆一个SVN仓库,再停用SVN服务器,之后再将项目,推送到一个Git服务器,项目迁移就完成了,如果用户需要之前提交的完整历史,则需要实现SVN服务器的快速获取.

当然导入过程并不完美,即使迁移操作都正确,也需要耗费大量的时间,第一个问题,就是作者信息,在SVN中,每个提交者都有一个账号,该账号将保存在提交信息中,如果用户需要保留账号信息,则应当创建Git作者与SVN账号的映射,即创建users.txt文件,如下,

schacon = Scott Chacon <[email protected]>
selse = Someo Nelse <[email protected]>

使用以下命令,可列出SVN仓库的所有作者名称,

$ svn log --xml --quiet | grep author | sort -u | \
  perl -pe 's/.*>(.*?)<.*/$1 = /'

上述命令可生成一个XML格式的日志文件,其中包含了作者信息,并丢弃不必要的信息,以及剔除XML标记(事先必须安装好grep,sort,perl工具),并重定向到users.txt文件,该文件可帮助git svn,正确映射作者信息,如果在clone或init命令中,附带 --no-metadata选项,可使git svn在正常导入操作中,不包含SVN的元数据(metadata),如果用户需要保留同步元数据,可忽略该选项,使用以下命令,克隆SVN仓库,

$ git svn clone http://my-project.googlecode.com/svn/ \
      --authors-file=users.txt --no-metadata --prefix "" -s my_project
$ cd my_project

SVN仓库已导入my_project,而原有的SVN提交描述,与以下内容类似,

commit 37efa680e8473b615de980fa935944215428a35a
Author: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029>
Date:   Sun May 3 00:12:22 2009 +0000
    fixed install - go to trunk
    
    git-svn-id: https://my-project.googlecode.com/svn/trunk@94 4c93b258-373f-11de-be05-5f7a86268029

导入Git仓库后,SVN提交描述将转换成以下格式,

commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2
Author: Scott Chacon <[email protected]>
Date:   Sun May 3 00:12:22 2009 +0000
    fixed install - go to trunk

导入之后,应当进行一些清理操作,需要处理git svn生成的引用标记,首先需要将这些引用标记,修改为真实的引用标记,而不是指向远程分支,之后将分支引用指向本地分支,使用以下命令,将远程仓库的引用标记(refs/remotes/tags),修改为正确的Git标记,

$ for t in $(git for-each-ref --format='%(refname:short)' refs/remotes/tags); do git
tag ${t/tags\//} $t && git branch -D -r $t; done

之后将指向refs/remotes的分支引用,修改为指向本地分支,

$ for b in $(git for-each-ref --format='%(refname:short)' refs/remotes); do git branch
$b refs/remotes/$b && git branch -D -r $b; done

虽然SVN只有一条分支,但是可能会出现一些扩展分支(名称后缀为@xxx,xxx为数字),这些分支是SVN的过期版本(又被称为peg revision),用于保存主线分支所弃用的文件,以便得到一个完整的提交历史,xxx数字即为过期版本的编号,同时Git并未提供与之对应的功能,如果用户不需要这些过期版本,可直接删除,

$ for p in $(git for-each-ref --format='%(refname:short)' | grep @); do git branch -D $p; done

清理操作的最后一步,由于git svn创建了一个扩展分支trunk,并映射了SVN的主线分支,而trunk分支指向的位置,与master分支相同,同时master名称更符合Git系统的习惯,此时需删除trunk分支引用,但保留分支内容,之后则可使用替代名master,

$ git branch -d trunk

最后添加Git远程仓库,

$ git remote add origin git@my-git-server:myrepository.git

这时需要将本地Git仓库,全部推送到远程仓库,

$ git push origin --all
$ git push origin --tags

Mercurial

由于Mercurial和Git使用相似的版本描述模型,同时Git更加灵活,因此将Mercurial仓库转换成Git仓库相当简单,可使用hg-fast-export工具,首先克隆该工具,

$ git clone https://github.com/frej/fast-export.git

转换的第一步,完整克隆Mercurial仓库

$ hg clone <Mercurial仓库URL> /tmp/hg-repo

之后创建提交作者的映射文件,Mercurial并未严格管理,变更集包含的作者信息,需要进行必要的处理,

$ cd /tmp/hg-repo
$ hg log | grep user: | sort | uniq | sed 's/user: *//' > ../authors

此时已生成/tmp/authors文件,如下,

bob
bob@localhost
bob <[email protected]>
bob jones <bob <AT> company <DOT> com>
Bob Jones <[email protected]>
Joe Smith <[email protected]>

创建变更集的同一作者(Bob)使用了五个不同的名字,这时用户需要手工创建,不同名称的映射,如下,

"bob"="Bob Jones <[email protected]>"
"bob@localhost"="Bob Jones <[email protected]>"
"bob <[email protected]>"="Bob Jones <[email protected]>"
"bob jones <bob <AT> company <DOT> com>"="Bob Jones <[email protected]>"

如果Mercurial的分支名和标签名,不兼容Git,则需要修改,完成修改之后,可创建一个新的Git仓库,

$ git init /tmp/converted
$ cd /tmp/converted
$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors

-r选项,可指定Mercurial仓库克隆的存储路径,-A选项,可指定作者映射文件的存储路径,还可使用-B和-T选项,分别指定分支和标签的映射文件的存储路径,

$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors
Loaded 4 authors
master: Exporting full revision 1/22208 with 13/0/0 added/changed/removed files
master: Exporting simple delta revision 2/22208 with 1/1/0 added/changed/removed files
master: Exporting simple delta revision 3/22208 with 0/1/0 added/changed/removed files
[...]
master: Exporting simple delta revision 22206/22208 with 0/4/0 added/changed/removed files
master: Exporting simple delta revision 22207/22208 with 0/2/0 added/changed/removed files
master: Exporting thorough delta revision 22208/22208 with 3/213/0 added/changed/removed files
Exporting tag [0.4c] at [hg r9] [git :10]
Exporting tag [0.4d] at [hg r16] [git :17]
[...]
Exporting tag [3.1-rc] at [hg r21926] [git :21927]
Exporting tag [3.1] at [hg r21973] [git :21974]
Issued 22315 commands
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects: 120000
Total objects :  115032 ( 208171 duplicates )
      blobs   :   40504 ( 205320 duplicates 26117 deltas of 39602 attempts)
      trees   :   52320 (   2851 duplicates 47467 deltas of 47599 attempts)
      commits :   22208 (      0 duplicates     0 deltas of     0 attempts)
      tags    :       0 (      0 duplicates     0 deltas of     0 attempts)
Total branches:     109 (      2 loads )
      marks   : 1048576 (  22208 unique )
      atoms   :    1952
Memory total  :    7860 KiB
      pools   :    2235 KiB
      objects :    5625 KiB
---------------------------------------------------------------------
pack_report: getpagesize()            =       4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit      = 8589934592
pack_report: pack_used_ctr            =      90430
pack_report: pack_mmap_calls          =      46771
pack_report: pack_open_windows        =          1 /         1
pack_report: pack_mapped              =  340852700 / 340852700
---------------------------------------------------------------------

$ git shortlog -sn
369 Bob Jones
365 Joe Smith

转换操作已完成,所有的Mercurial标签,已转换成Git标签,所有的Mercurial分支和书签,都转换成Git分支,之后需将本地Git仓库,推送到Git远程仓库,

$ git remote add origin git@my-git-server:myrepository.git
$ git push origin --all

Bazaar

Bazaar与Git也很相似,因此转换操作也相当简单,可使用bzr-fastimport插件(plugin),但是在Windows和类Unix系统中, fastimport插件的安装稍有不同,

# Debian
$ sudo apt-get install bzr-fastimport
# RHEL
$ sudo yum install bzr-fastimport
# Fedora,从22版本开始,可使用新的包管理器dnf
$ sudo dnf install bzr-fastimport

也可手动安装插件,

$ mkdir --parents ~/.bazaar/plugins  # 创建插件所需的默认目录
$ cd ~/.bazaar/plugins
$ bzr branch lp:bzr-fastimport fastimport  # 导入fastimport插件
$ cd fastimport
$ sudo python setup.py install --record=files.txt  # 安装插件

当然也可在python环境中直接安装,

$ python -c "import fastimport"
Traceback (most recent call last):
File "<string>", line 1, in <module>
ImportError: No module named fastimport

$ pip install fastimport

在Windows系统中,可直接下载安装程序,自动完成插件的安装.

单分支项目

进入Bazaar仓库本地副本的目录,初始化Git仓库,

$ git init

只需导入Bazaar仓库,并转换成Git仓库,

$ bzr fast-export --plain . | git fast-import
多分支项目

如果Bazaar仓库包含了多条分支,假定存在两条分支,一条为主分支(myProject.trunk),另一条为工作分支(myProject.work),

$ ls
myProject.trunk myProject.work

创建Git仓库,

$ git init git-repo
$ cd git-repo

导入Bazaar仓库的主分支,

$ bzr fast-export --export-marks=../marks.bzr ../myProject.trunk | \
  git fast-import --export-marks=../marks.git

导入Bazaar仓库的工作分支,

$ bzr fast-export --marks=../marks.bzr --git-branch=work ../myProject.work | \
  git fast-import --import-marks=../marks.git --export-marks=../marks.git

使用git branch可知,Git仓库的master分支和work分支一模一样,查看日志文件,确认Bazaar仓库包含的文件,已完全导入Git.

同步暂存区

虽然多条Bazaar分支已导入Git,但是暂存区与工作区,并未和HEAD指针同步,使用以下命令,

$ git reset --hard HEAD
可忽略文件

首先将.bzrignore改名为.gitignore,如果.bzrignore包含了Git不支持的匹配模板,比如!!或RE:,用户则需要修改,以保证.bzrignore包含的匹配规则能够完整保留.

再创建一个提交,用于保存迁移记录,

$ git mv .bzrignore .gitignore
$ # modify .gitignore if needed
$ git commit -am 'Migration from Bazaar to Git'

最后将Git本地仓库,推送到远程仓库,

$ git remote add origin git@my-git-server:mygitrepository.git
$ git push origin --all
$ git push origin --tags

Perforce

如前所述,迁移Perforce也有两个工具,git-p4和Perforce Git Fusion.

Perforce Git Fusion

Git Fusion的迁移也相当轻松,设定好项目配置,账号名映射,分支所需的配置文件,再克隆Perforce仓库,即可完成迁移.

git-p4

git-p4也是一种迁移工具,首先用户必须设定P4Port环境变量,将其指向,需迁移的perforce仓库,这里使用了public.perforce.com公共仓库,

$ export P4PORT=public.perforce.com:1666

使用git p4 clone,克隆perforce公共仓库的Jam项目,

$ git-p4 clone //guest/perforce_software/jam@all p4import
Importing from //guest/perforce_software/jam@all into p4import
Initialized empty Git repository in /private/tmp/p4import/.git/
Import destination: refs/remotes/p4/master
Importing revision 9957 (100%)

从上述输出可知,该项目只有一条分支,如果存在多条分支,必须在git p4 clone中,添加–detect-branches选项,可导入所有分支.完成导入后,可进入p4import目录,查看导入结果,

$ git log -2
commit e5da1c909e5db3036475419f6379f2c73710c4e6
Author: giles <giles@[email protected]>
Date:   Wed Feb 8 03:13:27 2012 -0800
    Correction to line 355; change </UL> to </OL>.
    [git-p4: depot-paths = "//public/jam/src/": change = 8068]

commit aa21359a0a135dda85c50a7f7cf249e4f7b8fd98
Author: kwirth <[email protected]>
Date:   Tue Jul 7 01:35:51 2009 -0800
    Fix spelling error on Jam doc page (cummulative -> cumulative).
    [git-p4: depot-paths = "//public/jam/src/": change = 7304]

从上述输出中可知,git-p4在每个提交描述中,都添加了一个标记,用于记录Perforce变更的编号,如果需要删除该标记,可使用以下命令,

$ git filter-branch --msg-filter 'sed -e "/^\[git-p4:/d"'
Rewrite e5da1c909e5db3036475419f6379f2c73710c4e6 (125/125)
Ref 'refs/heads/master' was rewritten

运行git log,可知所有提交的校验码都已改变,
$ git log -2
commit b17341801ed838d97f7800a54a6f9b95750839b7
Author: giles <giles@[email protected]>
Date:   Wed Feb 8 03:13:27 2012 -0800
    Correction to line 355; change </UL> to </OL>.
    
commit 3e68c2e26cd89cb983eb52c024ecdfba1d6b3fff
Author: kwirth <[email protected]>
Date:   Tue Jul 7 01:35:51 2009 -0800
    Fix spelling error on Jam doc page (cummulative -> cumulative).

最后将本地Git仓库,推送到远程仓库.

TFS

为了保证TFVC完整迁移到Git,只能使用git-tfs工具,因为它支持多分支,同时比git-tf更容易实现.

首先需进行用户名映射,TFVC也未严格管理,每个变更集的作者名,而Git仓库需要在每个提交中,添加作者名和邮箱地址,因此使用以下命令,

PS> tf history $/myproject -recursive > AUTHORS_TMP

上述命令可收集TFVC项目中所有变更集包含的作者信息,并放入AUTHORS_TMP文件,之后需过滤出作者名,再放入AUTHORS文件,

PS> cat AUTHORS_TMP | cut -b 11-20 | tail -n+3 | sort | uniq > AUTHORS

手动编辑AUTHORS文件,使用以下格式,创建作者名的映射关系,

DOMAIN\username = User Name <[email protected]>

username即为TFVC变更集的作者名,User Name即为Git提交所需的作者信息,完成作者名映射之后,则需要克隆TFVC项目,

PS> git tfs clone --with-branches --authors=AUTHORS
https://username.visualstudio.com/DefaultCollection $/project/Trunk project_git

清除每个Git提交描述,所包含的git-tfs-id文本行,

PS> git filter-branch -f --msg-filter 'sed "s/^git-tfs-id:.*$//g"' '--' --all

完成之后,添加一个新的Git远程仓库,将本地Git仓库,推送到远程仓库.

自定义迁移工具 (略)

感觉意义不大,如果是高级玩家,后续内容太浅显,如果是入门玩家,不会尝试这类高难度的课题.

在这里插入图片描述

发布了80 篇原创文章 · 获赞 10 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/osoon/article/details/104262602