配色: 字号:
Git详解之八Git与其他系统
2017-03-31 | 阅:  转:  |  分享 
  
Git详解之八Git与其他系统http://www.open-open.com/lib/view/open1328070454218.ht
mlgitsvnhttp://www.open-open.com/lib/view/open1328070454218.html
初始设定http://www.open-open.com/lib/view/open1328070454218.html入门htt
p://www.open-open.com/lib/view/open1328070454218.html提交到Subversi
onhttp://www.open-open.com/lib/view/open1328070454218.html拉取最新进展h
ttp://www.open-open.com/lib/view/open1328070454218.htmlGit分支问题ht
tp://www.open-open.com/lib/view/open1328070454218.htmlSubversion
分支http://www.open-open.com/lib/view/open1328070454218.html切换当前分支h
ttp://www.open-open.com/lib/view/open1328070454218.html对应Subvers
ion的命令http://www.open-open.com/lib/view/open1328070454218.htmlGi
t-Svn总结http://www.open-open.com/lib/view/open1328070454218.html导
入http://www.open-open.com/lib/view/open1328070454218.htmlSubversi
onhttp://www.open-open.com/lib/view/open1328070454218.htmlPerforc
ehttp://www.open-open.com/lib/view/open1328070454218.html自定导入脚本Gi
t与其他系统世界不是完美的。大多数时候,将所有接触到的项目全部转向Git是不可能的。有时我们不得不为某个项目使用其他的版本控
制系统(VCS,VersionControlSystem),其中比较常见的是Subversion。你将在本章的第一部分
学习使用gitsvn,Git为Subversion附带的双向桥接工具。或许现在你已经在考虑将先前的项目转向Git。本
章的第二部分将介绍如何将项目迁移到Git:先介绍从Subversion的迁移,然后是Perforce,最后介绍如何使用自定
义的脚本进行非标准的导入。?8.1?Git与Subversion当前,大多数开发中的开源项目以及大量的商业项目都使用Sub
version来管理源码。作为最流行的开源版本控制系统,Subversion已经存在了接近十年的时间。它在许多方面与CVS
十分类似,后者是前者出现之前代码控制世界的霸主。Git最为重要的特性之一是名为gitsvn的Subversion双向桥
接工具。该工具把Git变成了Subversion服务的客户端,从而让你在本地享受到Git所有的功能,而后直接向Sub
version服务器推送内容,仿佛在本地使用了Subversion客户端。也就是说,在其他人忍受古董的同时,你可以在本地享受
分支合并,使暂存区域,衍合以及单项挑拣等等。这是个让Git偷偷潜入合作开发环境的好东西,在帮助你的开发同伴们提高效率的同时,
它还能帮你劝说团队让整个项目框架转向对Git的支持。这个Subversion之桥是通向分布式版本控制系统(DVCS,Di
stributedVCS)世界的神奇隧道。gitsvnGit中所有Subversion桥接命令的基础是gitsvn
。所有的命令都从它开始。相关的命令数目不少,你将通过几个简单的工作流程了解到其中常见的一些。值得警戒的是,在使用gitsvn
的时候,你实际是在与Subversion交互,Git比它要高级复杂的多。尽管可以在本地随意的进行分支和合并,最好还是通过衍
合保持线性的提交历史,尽量避免类似与远程Git仓库动态交互这样的操作。避免修改历史再重新推送的做法,也不要同时推送到并行的G
it仓库来试图与其他Git用户合作。Subersion只能保存单一的线性提交历史,一不小心就会被搞糊涂。合作团队中同时有人
用SVN和Git,一定要确保所有人都使用SVN服务来协作——这会让生活轻松很多。初始设定为了展示功能,先要一个具有写权限
的SVN仓库。如果想尝试这个范例,你必须复制一份其中的测试仓库。比较简单的做法是使用一个名为svnsync的工具。较新的
Subversion版本中都带有该工具,它将数据编码为用于网络传输的格式。要尝试本例,先在本地新建一个Subversion仓
库:$mkdir/tmp/test-svn$svnadmincreate/tmp/test-svn然后,允许所有用户修
改revprop——简单的做法是添加一个总是以0作为返回值的pre-revprop-change脚本:$cat/
tmp/test-svn/hooks/pre-revprop-change#!/bin/shexit0;$chmod+
x/tmp/test-svn/hooks/pre-revprop-change现在可以调用svnsyncinit加目标仓库
,再加源仓库的格式来把该项目同步到本地了:$svnsyncinitfile:///tmp/test-svnhttp://p
rogit-example.googlecode.com/svn/这将建立进行同步所需的属性。可以通过运行以下命令来克隆代码:$
svnsyncsyncfile:///tmp/test-svnCommittedrevision1.Copiedp
ropertiesforrevision1.Committedrevision2.Copiedproperties
forrevision2.Committedrevision3....别看这个操作只花掉几分钟,要是你想把源仓库复制
到另一个远程仓库,而不是本地仓库,那将花掉接近一个小时,尽管项目中只有不到100次的提交。Subversion每次只复制一
次修改,把它推送到另一个仓库里,然后周而复始——惊人的低效,但是我们别无选择。入门有了可以写入的Subversion仓库以后,
就可以尝试一下典型的工作流程了。我们从gitsvnclone命令开始,它会把整个Subversion仓库导入到一个本地
的Git仓库中。提醒一下,这里导入的是一个货真价实的Subversion仓库,所以应该把下面的file:///tmp/te
st-svn换成你所用的Subversion仓库的URL:$gitsvnclonefile:///tmp/test
-svn-Ttrunk-bbranches-ttagsInitializedemptyGitrepositor
yin/Users/schacon/projects/testsvnsync/svn/.git/r1=b4e387bc6
8740b5af56c2a5faf4003ae42bd135c(trunk)Am4/acx_pthread.m4Am4/
stl_hash.m4...r75=d1957f3b307922124eec6314e15bcda59e3d9610(t
runk)Foundpossiblebranchpoint:file:///tmp/test-svn/trunk=>
\file:///tmp/test-svn/branches/my-calc-branch,75Foundbranch
parent:(my-calc-branch)d1957f3b307922124eec6314e15bcda59e3d9610
Followingparentwithdo_switchSuccessfullyfollowedparentr76
=8624824ecc0badd73f40ea2f01fce51894189b01(my-calc-branch)Chec
kedoutHEAD:file:///tmp/test-svn/branches/my-calc-branchr76这相当
于针对所提供的URL运行了两条命令——gitsvninit加上gitsvnfetch。可能会花上一段时间。我们所
用的测试项目仅仅包含75次提交并且它的代码量不算大,所以只有几分钟而已。不过,Git仍然需要提取每一个版本,每次一个,再逐个
提交。对于一个包含成百上千次提交的项目,花掉的时间则可能是几小时甚至数天。-Ttrunk-bbranches-ttags
告诉Git该Subversion仓库遵循了基本的分支和标签命名法则。如果你的主干(译注:trunk,相当于非分布式版本控
制里的master分支,代表开发的主线),分支或者标签以不同的方式命名,则应做出相应改变。由于该法则的常见性,可以使用-s来代替
整条命令,它意味着标准布局(s是Standardlayout的首字母),也就是前面选项的内容。下面的命令有相同的效果:$
gitsvnclonefile:///tmp/test-svn-s现在,你有了一个有效的Git仓库,包含着导入的分支和
标签:$gitbranch-amastermy-calc-branchtags/2.0.2tags/releas
e-2.0.1tags/release-2.0.2tags/release-2.0.2rc1trunk值得注意的是,该工具分
配命名空间时和远程引用的方式不尽相同。克隆普通的Git仓库时,可以以origin/[branch]的形式获取远程服务器上所
有可用的分支——分配到远程服务的名称下。然而gitsvn假定不存在多个远程服务器,所以把所有指向远程服务的引用不加区分的保存下
来。可以用Git探测命令show-ref来查看所有引用的全名。$gitshow-ref1cbd4904d9982f3
86d87f88fce1c24ad7c0f0471refs/heads/masteraee1ecc26318164f355a8
83f5d99cff0c852d3c4refs/remotes/my-calc-branch03d09b0e2aad427e3
4a6d50ff147128e76c0e0f5refs/remotes/tags/2.0.250d02cc0adc9da431
9eeba0900430ba219b9c376refs/remotes/tags/release-2.0.14caaa711a
50c77879a91b8b90380060f672745cbrefs/remotes/tags/release-2.0.21
c4cb508144c513ff1214c3488abe66dcb92916frefs/remotes/tags/release
-2.0.2rc11cbd4904d9982f386d87f88fce1c24ad7c0f0471refs/remotes/t
runk而普通的Git仓库应该是这个模样:$gitshow-ref83e38c7a0af325a9722f2fdc56b
10188806d83a1refs/heads/master3e15e38c198baac84223acfc6224bb8b9
9ff2281refs/remotes/gitserver/master0a30dd3b0c795b80212ae723640
d4e5d48cabdffrefs/remotes/origin/master25812380387fdd55f916652b
e4881c6f11600d6frefs/remotes/origin/testing这里有两个远程服务器:一个名为gitse
rver,具有一个master分支;另一个叫origin,具有master和testing两个分支。注意本例中通过
gitsvn导入的远程引用,(Subversion的)标签是当作远程分支添加的,而不是真正的Git标签。导入的Subv
ersion仓库仿佛是有一个带有不同分支的tags远程服务器。提交到Subversion有了可以开展工作的(本地)仓库以后
,你可以开始对该项目做出贡献并向上游仓库提交内容了,Git这时相当于一个SVN客户端。假如编辑了一个文件并进行提交,那么这次
提交仅存在于本地的Git而非Subversion服务器上。$gitcommit-am''Addinggit-svn
instructionstotheREADME''[master97031e5]Addinggit-svninst
ructionstotheREADME1fileschanged,1insertions(+),1deletio
ns(-)接下来,可以将作出的修改推送到上游。值得注意的是,Subversion的使用流程也因此改变了——你可以在离线状态下进行
多次提交然后一次性的推送到Subversion的服务器上。向Subversion服务器推送的命令是gitsvndcom
mit:$gitsvndcommitCommittingtofile:///tmp/test-svn/trunk..
.MREADME.txtCommittedr79MREADME.txtr79=938b1a547c2cc9203
3b74d32030e86468294a5c8(trunk)NochangesbetweencurrentHEADa
ndrefs/remotes/trunkResettingtothelatestrefs/remotes/trunk所
有在原Subversion数据基础上提交的commit会一一提交到Subversion,然后你本地Git的comm
it将被重写,加入一个特别标识。这一步很重要,因为它意味着所有commit的SHA-1指都会发生变化。这也是同时使用G
it和Subversion两种服务作为远程服务不是个好主意的原因之一。检视以下最后一个commit,你会找到新添加的git
-svn-id(译注:即本段开头所说的特别标识):$gitlog-1commit938b1a547c2cc92033b
74d32030e86468294a5c8Author:schacon-be05-5f7a86268029>Date:SatMay222:06:442009+0000Addinggi
t-svninstructionstotheREADMEgit-svn-id:file:///tmp/test-svn
/trunk@794c93b258-373f-11de-be05-5f7a86268029注意看,原本以97031e5开头的
SHA-1校验值在提交完成以后变成了938b1a5。如果既要向Git远程服务器推送内容,又要推送到Subversio
n远程服务器,则必须先向Subversion推送(dcommit),因为该操作会改变所提交的数据内容。拉取最新进展如果要与其
他开发者协作,总有那么一天你推送完毕之后,其他人发现他们推送自己修改的时候(与你推送的内容)产生冲突。这些修改在你合并之前将一直被
拒绝。在gitsvn里这种情况形似:$gitsvndcommitCommittingtofile:///tmp/
test-svn/trunk...Mergeconflictduringcommit:Yourfileordir
ectory''README.txt''isprobably\out-of-date:resourceoutofda
te;tryupdatingat/Users/schacon/libexec/git-\core/git-svnlin
e482为了解决该问题,可以运行gitsvnrebase,它会拉取服务器上所有最新的改变,再次基础上衍合你的修改:$g
itsvnrebaseMREADME.txtr80=ff829ab914e8775c7c025d741beb3d52
3ee30bc4(trunk)First,rewindingheadtoreplayyourworkontop
ofit...Applying:firstuserchange现在,你做出的修改都发生在服务器内容之后,所以可以顺利的
运行dcommit:$gitsvndcommitCommittingtofile:///tmp/test-svn/
trunk...MREADME.txtCommittedr81MREADME.txtr81=456cbe633
7abe49154db70106d1836bc1332deed(trunk)Nochangesbetweencurren
tHEADandrefs/remotes/trunkResettingtothelatestrefs/remote
s/trunk需要牢记的一点是,Git要求我们在推送之前先合并上游仓库中最新的内容,而gitsvn只要求存在冲突的时候才这
样做。假如有人向一个文件推送了一些修改,这时你要向另一个文件推送一些修改,那么dcommit将正常工作:$gitsvndc
ommitCommittingtofile:///tmp/test-svn/trunk...Mconfigure.ac
Committedr84Mautogen.shr83=8aa54a74d452f82eee10076ab2584c1
fc424853b(trunk)Mconfigure.acr84=cdbac939211ccb18aa744e581e
46563af5d962d0(trunk)W:d2f23b80f67aaaa1f6f5aaef48fce3263ac71a9
2andrefs/remotes/trunkdiffer,\usingrebase::100755100755e
fa5a59965fbbb5b2b0a12890f1b351bb5493c18\015e4c98c482f0fa71e4d54
34338014530b37fa6Mautogen.shFirst,rewindingheadtoreplayyo
urworkontopofit...Nothingtodo.这一点需要牢记,因为它的结果是推送之后项目处于一个不完
整存在与任何主机上的状态。如果做出的修改无法兼容但没有产生冲突,则可能造成一些很难确诊的难题。这和使用Git服务器是不同的——
在Git世界里,发布之前,你可以在客户端系统里完整的测试项目的状态,而在SVN永远都没法确保提交前后项目的状态完全一样。及
时还没打算进行提交,你也应该用这个命令从Subversion服务器拉取最新修改。sitsvnfetch能获取最新的数据,
不过gitsvnrebase才会在获取之后在本地进行更新。$gitsvnrebaseMgenerate_desc
riptor_proto.shr82=bd16df9173e424c6f52c337ab6efa7f7643282f1(t
runk)First,rewindingheadtoreplayyourworkontopofit...F
ast-forwardedmastertorefs/remotes/trunk.不时地运行一下gitsvnrebase
可以确保你的代码没有过时。不过,运行该命令时需要确保工作目录的整洁。如果在本地做了修改,则必须在运行gitsvnrebase
之前或暂存工作,或暂时提交内容——否则,该命令会发现衍合的结果包含着冲突因而终止。Git分支问题习惯了Git的工作流程以后
,你可能会创建一些特性分支,完成相关的开发工作,然后合并他们。如果要用gitsvn向Subversion推送内容,那么最
好是每次用衍合来并入一个单一分支,而不是直接合并。使用衍合的原因是Subversion只有一个线性的历史而不像Git那样处
理合并,所以Gitsvn在把快照转换为Subversion的commit时只能包含第一个祖先。假设分支历史如下:创建
一个experiment分支,进行两次提交,然后合并到master。在dcommit的时候会得到如下输出:$git
svndcommitCommittingtofile:///tmp/test-svn/trunk...MCHANGE
S.txtCommittedr85MCHANGES.txtr85=4bfebeec434d156c36f2bcd18
f4e3d97dc3269a2(trunk)NochangesbetweencurrentHEADandrefs/
remotes/trunkResettingtothelatestrefs/remotes/trunkCOPYING.
txt:locallymodifiedINSTALL.txt:locallymodifiedMCOPYING.txt
MINSTALL.txtCommittedr86MINSTALL.txtMCOPYING.txtr86=26
47f6b86ccfcaad4ec58c520e369ec81f7c283c(trunk)Nochangesbetween
currentHEADandrefs/remotes/trunkResettingtothelatestrefs
/remotes/trunk在一个包含了合并历史的分支上使用dcommit可以成功运行,不过在Git项目的历史中,它没有重
写你在experiment分支中的两个commit——另一方面,这些改变却出现在了SVN版本中同一个合并commit
中。在别人克隆该项目的时候,只能看到这个合并commit包含了所有发生过的修改;他们无法获知修改的作者和时间等提交信息。Su
bversion分支Subversion的分支和Git中的不尽相同;避免过多的使用可能是最好方案。不过,用gitsvn
创建和提交不同的Subversion分支仍是可行的。创建新的SVN分支要在Subversion中建立一个新分支,需要
运行gitsvnbranch[分支名]TocreateanewbranchinSubversion,you
rungitsvnbranch[branchname]:$gitsvnbranchoperaCopyingfi
le:///tmp/test-svn/trunkatr87tofile:///tmp/test-svn/branches/
opera...Foundpossiblebranchpoint:file:///tmp/test-svn/trunk
=>\file:///tmp/test-svn/branches/opera,87Foundbranchparent:
(opera)1f6bfe471083cbca06ac8d4176f7ad4de0d62e5fFollowingparen
twithdo_switchSuccessfullyfollowedparentr89=9b6fe0b90c5c9
adf9165f700897518dbc54a7cbf(opera)相当于在Subversion中的svncopytr
unkbranches/opera命令并且对Subversion服务器进行了相关操作。值得提醒的是它没有检出和转换到那个分
支;如果现在进行提交,将提交到服务器上的trunk,而非opera。切换当前分支Git通过搜寻提交历史中Subversio
n分支的头部来决定dcommit的目的地——而它应该只有一个,那就是当前分支历史中最近一次包含git-svn-id的提交
。如果需要同时在多个分支上提交,可以通过导入Subversion上某个其他分支的commit来建立以该分支为dcommi
t目的地的本地分支。比如你想拥有一个并行维护的opera分支,可以运行$gitbranchoperaremotes/o
pera然后,如果要把opera分支并入trunk(本地的master分支),可以使用普通的gitmerge。不过最
好提供一条描述提交的信息(通过-m),否则这次合并的记录是Mergebranchopera,而不是任何有用的东西。记住,
虽然使用了gitmerge来进行这次操作,并且合并过程可能比使用Subversion简单一些(因为Git会自动找到适
合的合并基础),这并不是一次普通的Git合并提交。最终它将被推送回commit无法包含多个祖先的Subversion服
务器上;因而在推送之后,它将变成一个包含了所有在其他分支上做出的改变的单一commit。把一个分支合并到另一个分支以后,你没法像
在Git中那样轻易的回到那个分支上继续工作。提交时运行的dcommit命令擦除了全部有关哪个分支被并入的信息,因而以后的合并
基础计算将是不正确的——dcommit让gitmerge的结果变得类似于gitmerge--squash。不幸的是,
我们没有什么好办法来避免该情况——Subversion无法储存这个信息,所以在使用它作为服务器的时候你将永远为这个缺陷所困。为
了不出现这种问题,在把本地分支(本例中的opera)并入trunk以后应该立即将其删除。对应Subversion的命令gi
tsvn工具集合了若干个与Subversion类似的功能,对应的命令可以简化向Git的转化过程。下面这些命令能实现S
ubversion的这些功能。SVN风格的历史习惯了Subversion的人可能想以SVN的风格显示历史,运行git
svnlog可以让提交历史显示为SVN格式:$gitsvnlog------------------------
------------------------------------------------r87|schacon|
2009-05-0216:07:37-0700(Sat,02May2009)|2linesautogench
ange------------------------------------------------------------
------------r86|schacon|2009-05-0216:00:21-0700(Sat,02M
ay2009)|2linesMergebranch''experiment''--------------------
----------------------------------------------------r85|schaco
n|2009-05-0216:00:09-0700(Sat,02May2009)|2linesupdate
dthechangelog关于gitsvnlog,有两点需要注意。首先,它可以离线工作,不像svnlog命令,需
要向Subversion服务器索取数据。其次,它仅仅显示已经提交到Subversion服务器上的commit。在本地尚未
dcommit的Git数据不会出现在这里;其他人向Subversion服务器新提交的数据也不会显示。等于说是显示了最近
已知Subversion服务器上的状态。SVN日志类似gitsvnlog对gitlog的模拟,svnanno
tate的等效命令是gitsvnblame[文件名]。其输出如下:$gitsvnblameREADME.txt2
temporalProtocolBuffers-Google''sdatainterchangeformat2t
emporalCopyright2008GoogleInc.2temporalhttp://code.google.
com/apis/protocolbuffers/2temporal22temporalC++Installation
-Unix22temporal=======================2temporal79schacon
Committingingit-svn.78schacon2temporalTobuildandinstal
ltheC++ProtocolBufferruntimeandtheProtocol2temporalBuf
fercompiler(protoc)executethefollowing:2temporal同样,它不显示本地
的Git提交以及Subversion上后来更新的内容。SVN服务器信息还可以使用gitsvninfo来获取与运行
svninfo类似的信息:$gitsvninfoPath:.URL:https://schacon-test.
googlecode.com/svn/trunkRepositoryRoot:https://schacon-test.go
oglecode.com/svnRepositoryUUID:4c93b258-373f-11de-be05-5f7a862
68029Revision:87NodeKind:directorySchedule:normalLastCha
ngedAuthor:schaconLastChangedRev:87LastChangedDate:2009
-05-0216:07:37-0700(Sat,02May2009)它与blame和log的相同点在于离线运行
以及只更新到最后一次与Subversion服务器通信的状态。略Subversion之所略假如克隆了一个包含了svn:ig
nore属性的Subversion仓库,就有必要建立对应的.gitignore文件来防止意外提交一些不应该提交的文件。g
itsvn有两个有益于改善该问题的命令。第一个是gitsvncreate-ignore,它自动建立对应的.gitigno
re文件,以便下次提交的时候可以包含它。第二个命令是gitsvnshow-ignore,它把需要放进.gitignore
文件中的内容打印到标准输出,方便我们把输出重定向到项目的黑名单文件:$gitsvnshow-ignore>.git/i
nfo/exclude这样一来,避免了.gitignore对项目的干扰。如果你是一个Subversion团队里唯一的Gi
t用户,而其他队友不喜欢项目包含.gitignore,该方法是你的不二之选。Git-Svn总结gitsvn工具集在当前不得
不使用Subversion服务器或者开发环境要求使用Subversion服务器的时候格外有用。不妨把它看成一个跛脚的Gi
t,然而,你还是有可能在转换过程中碰到一些困惑你和合作者们的迷题。为了避免麻烦,试着遵守如下守则:保持一个不包含由gitmer
ge生成的commit的线性提交历史。将在主线分支外进行的开发通通衍合回主线;避免直接合并。不要单独建立和使用一个Git
服务来搞合作。可以为了加速新开发者的克隆进程建立一个,但是不要向它提供任何不包含git-svn-id条目的内容。甚至可以添加一
个pre-receive挂钩来在每一个提交信息中查找git-svn-id并拒绝提交那些不包含它的commit。如果遵循这些
守则,在Subversion上工作还可以接受。然而,如果能迁徙到真正的Git服务器,则能为团队带来更多好处。?8.2?迁
移到Git如果在其他版本控制系统中保存了某项目的代码而后决定转而使用Git,那么该项目必须经历某种形式的迁移。本节将介绍Gi
t中包含的一些针对常见系统的导入脚本,并将展示编写自定义的导入脚本的方法。导入你将学习到如何从专业重量级的版本控制系统中导入数据
——Subversion和Perforce——因为据我所知这二者的用户是(向Git)转换的主要群体,而且Git为此
二者附带了高质量的转换工具。Subversion读过前一节有关gitsvn的内容以后,你应该能轻而易举的根据其中的指导来g
itsvnclone一个仓库了;然后,停止Subversion的使用,向一个新Gitserver推送,并开始使用它
。想保留历史记录,所花的时间应该不过就是从Subversion服务器拉取数据的时间(可能要等上好一会就是了)。然而,这样的导入
并不完美;而且还要花那么多时间,不如干脆一次把它做对!首当其冲的任务是作者信息。在Subversion,每个提交者在都在主机上有
一个用户名,记录在提交信息中。上节例子中多处显示了schacon,比如blame的输出以及gitsvnlog。如果想让
这条信息更好的映射到Git作者数据里,则需要从Subversion用户名到Git作者的一个映射关系。建立一个叫做us
er.txt的文件,用如下格式表示映射关系:schacon=ScottChaconm>selse=SomeoNelse通过该命令可以获得SVN作者的列表:$s
vnlog--xml|grepauthor|sort-u|perl-pe''s/.>(.?)<./$1=
/''它将输出XML格式的日志——你可以找到作者,建立一个单独的列表,然后从XML中抽取出需要的信息。(显而易见,本方法要求
主机上安装了grep,sort和perl.)然后把输出重定向到user.txt文件,然后就可以在每一项的后面添加相应的Gi
t用户数据。为gitsvn提供该文件可以然它更精确的映射作者数据。你还可以在clone或者init后面添加--no-
metadata来阻止gitsvn包含那些Subversion的附加信息。这样import命令就变成了:$git
-svnclonehttp://my-project.googlecode.com/svn/\--authors-file
=users.txt--no-metadata-smy_project现在my_project目录下导入的Subver
sion应该比原来整洁多了。原来的commit看上去是这样:commit37efa680e8473b615de980fa9
35944215428a35aAuthor:schacon5f7a86268029>Date:SunMay300:12:222009+0000fixedinstall-
gototrunkgit-svn-id:https://my-project.googlecode.com/svn/tr
unk@944c93b258-373f-11de-be05-5f7a86268029现在是这样:commit03a8785f
44c8ea5cdb0e8834b7c8e6c469be2ff2Author:ScottChaconemail.com>Date:SunMay300:12:222009+0000fixedinstall-go
totrunk不仅作者一项干净了不少,git-svn-id也就此消失了。你还需要一点post-import(导入后)清理
工作。最起码的,应该清理一下gitsvn创建的那些怪异的索引结构。首先要移动标签,把它们从奇怪的远程分支变成实际的标签,然后
把剩下的分支移动到本地。要把标签变成合适的Git标签,运行$cp-Rf.git/refs/remotes/tags/
.git/refs/tags/$rm-Rf.git/refs/remotes/tags该命令将原本以tag/开头的远程
分支的索引变成真正的(轻巧的)标签。接下来,把refs/remotes下面剩下的索引变成本地分支:$cp-Rf.git/
refs/remotes/.git/refs/heads/$rm-Rf.git/refs/remotes现在所有的旧分
支都变成真正的Git分支,所有的旧标签也变成真正的Git标签。最后一项工作就是把新建的Git服务器添加为远程服务器并且
向它推送。下面是新增远程服务器的例子:$gitremoteaddorigingit@my-git-server:myre
pository.git为了让所有的分支和标签都得到上传,我们使用这条命令:$gitpushorigin--all所有的分
支和标签现在都应该整齐干净的躺在新的Git服务器里了。Perforce你将了解到的下一个被导入的系统是Perforce.G
it发行的时候同时也附带了一个Perforce导入脚本,不过它是包含在源码的contrib部分——而不像gitsvn
那样默认可用。运行它之前必须获取Git的源码,可以在git.kernel.org下载:$gitclonegit://
git.kernel.org/pub/scm/git/git.git$cdgit/contrib/fast-import在这
个fast-import目录下,应该有一个叫做git-p4的Python可执行脚本。主机上必须装有Python和p
4工具该导入才能正常进行。例如,你要从Perforce公共代码仓库(译注:PerforcePublicDepot,Pe
rforce官方提供的代码寄存服务)导入Jam工程。为了设定客户端,我们要把P4PORT环境变量export到Pe
rforce仓库:$exportP4PORT=public.perforce.com:1666运行git-p4clone
命令将从Perforce服务器导入Jam项目,我们需要给出仓库和项目的路径以及导入的目标路径:$git-p4clon
e//public/jam/src@all/opt/p4importImportingfrom//public/jam/
src@allinto/opt/p4importReinitializedexistingGitrepository
in/opt/p4import/.git/Importdestination:refs/remotes/p4/master
Importingrevision4409(100%)现在去/opt/p4import目录运行一下gitlog,
就能看到导入的成果:$gitlog-2commit1fd4ec126171790efd2db83548b85b1bbbc
07dc2Author:PerforcestaffDate:ThuAug
1910:18:452004-0800Drop''rc3''monikerofjam-2.5.Foldedrc2
andrc3RELNOTESintothemainpartofthedocument.Builtnewt
ar/zipballs.Only16monthslater.[git-p4:depot-paths="//pub
lic/jam/src/":change=4409]commitca8870db541a23ed867f38847eda
65bf4363371dAuthor:RichardGeigerDate:Tue
Apr2220:51:342003-0800Updatederivedjamgram.c[git-p4:depo
t-paths="//public/jam/src/":change=3108]每一个commit里都有一个git
-p4标识符。这个标识符可以保留,以防以后需要引用Perforce的修改版本号。然而,如果想删除这些标识符,现在正是时候——
在开启新仓库之前。可以通过gitfilter-branch来批量删除这些标识符:$gitfilter-branch--m
sg-filter''sed-e"/^\[git-p4:/d"''Rewrite1fd4ec126171790efd2d
b83548b85b1bbbc07dc2(123/123)Ref''refs/heads/master''wasrewrit
ten现在运行一下gitlog,你会发现这些commit的SHA-1校验值都发生了改变,而那些git-p4字串则从
提交信息里消失了:$gitlog-2commit10a16d60cffca14d454a15c6164378f4082b
c5b0Author:PerforcestaffDate:ThuAug
1910:18:452004-0800Drop''rc3''monikerofjam-2.5.Foldedrc2
andrc3RELNOTESintothemainpartofthedocument.Builtnewta
r/zipballs.Only16monthslater.commit2b6c6db311dd76c34c66ec1
c40a49405e6b527b2Author:RichardGeigerDate:
TueApr2220:51:342003-0800Updatederivedjamgram.c至此导入已经完成,
可以开始向新的Git服务器推送了。自定导入脚本如果先前的系统不是Subversion或Perforce之一,先上网找一
下有没有与之对应的导入脚本——导入CVS,ClearCase,VisualSourceSafe,甚至存档目录的导入脚本已经
存在。假如这些工具都不适用,或者使用的工具很少见,抑或你需要导入过程具有更多可制定性,则应该使用gitfast-import。该
命令从标准输入读取简单的指令来写入具体的Git数据。这样创建Git对象比运行纯Git命令或者手动写对象要简单的多(更多
相关内容见第九章)。通过它,你可以编写一个导入脚本来从导入源读取必要的信息,同时在标准输出直接输出相关指示。你可以运行该脚本并把它
的输出管道连接到gitfast-import。下面演示一下如何编写一个简单的导入脚本。假设你在进行一项工作,并且按时通过把工作目
录复制为以时间戳back_YY_MM_DD命名的目录来进行备份,现在你需要把它们导入Git。目录结构如下:$ls/op
t/import_fromback_2009_01_02back_2009_01_04back_2009_01_14bac
k_2009_02_03current为了导入到一个Git目录,我们首先回顾一下Git储存数据的方式。你可能还记得,Gi
t本质上是一个commit对象的链表,每一个对象指向一个内容的快照。而这里需要做的工作就是告诉fast-import内容快
照的位置,什么样的commit数据指向它们,以及它们的顺序。我们采取一次处理一个快照的策略,为每一个内容目录建立对应的com
mit,每一个commit与之前的建立链接。正如在第七章“Git执行策略一例”一节中一样,我们将使用Ruby来编写
这个脚本,因为它是我日常使用的语言而且阅读起来简单一些。你可以用任何其他熟悉的语言来重写这个例子——它仅需要把必要的信息打印到标准
输出而已。同时,如果你在使用Windows,这意味着你要特别留意不要在换行的时候引入回车符(译注:carriagereturn
s,Windows换行时加入的符号,通常说的\r)——Git的fast-import对仅使用换行符(LF)而非Win
dows的回车符(CRLF)要求非常严格。首先,进入目标目录并且找到所有子目录,每一个子目录将作为一个快照被导入为一个comm
it。我们将依次进入每一个子目录并打印所需的命令来导出它们。脚本的主循环大致是这样:last_mark=nil#循环遍历所
有目录Dir.chdir(ARGV[0])doDir.glob("").eachdo|dir|nextifFil
e.file?(dir)#进入目标目录Dir.chdir(dir)dolast_mark=print_export(
dir,last_mark)endendend我们在每一个目录里运行print_export,它会取出上一个快照的索引
和标记并返回本次快照的索引和标记;由此我们就可以正确的把二者连接起来。”标记(mark)”是fast-import中对com
mit标识符的叫法;在创建commit的同时,我们逐一赋予一个标记以便以后在把它连接到其他commit时使用。因此,在p
rint_export方法中要做的第一件事就是根据目录名生成一个标记:mark=convert_dir_to_mark(di
r)实现该函数的方法是建立一个目录的数组序列并使用数组的索引值作为标记,因为标记必须是一个整数。这个方法大致是这样的:$marks
=[]defconvert_dir_to_mark(dir)if!$marks.include?(dir)$mark
s<在需要提交附加信息中的日期。由于日期是用目录名表示的,我们就从中解析出来。print_export文件的下一行将是:date=
convert_dir_to_date(dir)而convert_dir_to_date则定义为defconvert_di
r_to_date(dir)ifdir==''current''returnTime.now().to_ielsedi
r=dir.gsub(''back_'','''')(year,month,day)=dir.split(''_'')ret
urnTime.local(year,month,day).to_iendend它为每个目录返回一个整型值。提交附加信息
里最后一项所需的是提交者数据,我们在一个全局变量中直接定义之:$author=''ScottChaconxample.com>''我们差不多可以开始为导入脚本输出提交数据了。第一项信息指明我们定义的是一个commit对象以及它所在的
分支,随后是我们生成的标记,提交者信息以及提交备注,然后是前一个commit的索引,如果有的话。代码大致这样:#打印导入所需
的信息puts''commitrefs/heads/master''puts''mark:''+markputs"co
mmitter#{$author}#{date}-0700"export_data(''importedfrom''+
dir)puts''from:''+last_markiflast_mark时区(-0700)处于简化目的使用硬编码。如
果是从其他版本控制系统导入,则必须以变量的形式指明时区。提交备注必须以特定格式给出:data(size)\n(contents
)该格式包含了单词data,所读取数据的大小,一个换行符,最后是数据本身。由于随后指明文件内容的时候要用到相同的格式,我们写一个
辅助方法,export_data:defexport_data(string)print"data#{string.siz
e}\n#{string}"end唯一剩下的就是每一个快照的内容了。这简单的很,因为它们分别处于一个目录——你可以输出dele
eall命令,随后是目录中每个文件的内容。Git会正确的记录每一个快照:puts''deleteall''Dir.glob("
/").eachdo|file|nextif!File.file?(file)inline_data(file)
end注意:由于很多系统把每次修订看作一个commit到另一个commit的变化量,fast-import也可以依据每
次提交获取一个命令来指出哪些文件被添加,删除或者修改过,以及修改的内容。我们将需要计算快照之间的差别并且仅仅给出这项数据,不过该做
法要复杂很多——还如不直接把所有数据丢给Git然它自己搞清楚。假如前面这个方法更适用于你的数据,参考fast-import的
man帮助页面来了解如何以这种方式提供数据。列举新文件内容或者指明带有新内容的已修改文件的格式如下:M644inline
path/to/filedata(size)(filecontents)这里,644是权限模式(加入有可执行文件,则需要
探测之并设定为755),而inline说明我们在本行结束之后立即列出文件的内容。我们的inline_data方法大致是:
definline_data(file,code=''M'',mode=''644'')content=File.re
ad(file)puts"#{code}#{mode}inline#{file}"export_data(conten
t)end我们重用了前面定义过的export_data,因为这里和指明提交注释的格式如出一辙。最后一项工作是返回当前的标记以便
下次循环的使用。returnmark注意:如果你在用Windows,一定记得添加一项额外的步骤。前面提过,Windows使用
CRLF作为换行字符而Gitfast-import只接受LF。为了绕开这个问题来满足gitfast-import,
你需要让ruby用LF取代CRLF:$stdout.binmode搞定了。现在运行该脚本,你将得到如下内容:$ruby
import.rb/opt/import_fromcommitrefs/heads/mastermark:1comm
itterScottChacon1230883200-0700data29
importedfromback_2009_01_02deleteallM644inlinefile.rbdata
12versiontwocommitrefs/heads/mastermark:2committerScott
Chacon1231056000-0700data29importedfr
omback_2009_01_04from:1deleteallM644inlinefile.rbdata14
versionthreeM644inlinenew.rbdata16newversionone(...)要运
行导入脚本,在需要导入的目录把该内容用管道定向到gitfast-import。你可以建立一个空目录然后运行gitinit
作为开头,然后运行该脚本:$gitinitInitializedemptyGitrepositoryin/opt/
import_to/.git/$rubyimport.rb/opt/import_from|gitfast-importgit-fast-importstatistics:---------------------------------------------------------------------Alloc''dobjects:5000Totalobjects:18(1duplicates)blobs:7(1duplicates0deltas)trees:6(0duplicates1deltas)commits:5(0duplicates0deltas)tags:0(0duplicates0deltas)Totalbranches:1(1loads)marks:1024(5unique)atoms:3Memorytotal:2255KiBpools:2098KiBobjects:156KiB---------------------------------------------------------------------pack_report:getpagesize()=4096pack_report:core.packedGitWindowSize=33554432pack_report:core.packedGitLimit=268435456pack_report:pack_used_ctr=9pack_report:pack_mmap_calls=5pack_report:pack_open_windows=1/1pack_report:pack_mapped=1356/1356--------------------------------------------------------------------你会发现,在它成功执行完毕以后,会给出一堆有关已完成工作的数据。上例在一个分支导入了5次提交数据,包含了18个对象。现在可以运行gitlog来检视新的历史:$gitlog-2commit10bfe7d22ce15ee25b60a824c8982157ca593d41Author:ScottChaconDate:SunMay312:57:392009-0700importedfromcurrentcommit7e519590de754d079dd73b44d695a42c9d2df452Author:ScottChaconDate:TueFeb301:00:002009-0700importedfromback_2009_02_03就它了——一个干净整洁的Git仓库。需要注意的是此时没有任何内容被检出——刚开始当前目录里没有任何文件。要获取它们,你得转到master分支的所在:$ls$gitreset--hardmasterHEADisnowat10bfe7dimportedfromcurrent$lsfile.rblibfast-import还可以做更多——处理不同的文件模式,二进制文件,多重分支与合并,标签,进展标识等等。一些更加复杂的实例可以在Git源码的contib/fast-import目录里找到;其中较为出众的是前面提过的git-p4脚本。8.3?总结现在的你应该掌握了在Subversion上使用Git以及把几乎任何先存仓库无损失的导入为Git仓库。下一章将介绍Git内部的原始数据格式,从而是使你能亲手锻造其中的每一个字节,如果必要的话。
献花(0)
+1
(本文系关平藏书首藏)