如果先前的系统不是 Subversion 或 Perforce 之一,先上网找一下有没有与之对应的导入脚本——导入 CVS,Clear Case,Visual Source Safe,甚至存档目录的导入脚本已经存在。假如这些工具都不适用,或者使用的工具很少见,抑或你需要导入过程具有更多可制定性,则应该使用 git fast-import。该命令从标准输入读取简单的指令来写入具体的 Git 数据。
这样创建 Git 对象比运行纯 Git 命令或者手动写对象要简单的多(更多相关内容见第九章)。通过它,你可以编写一个导入脚本来从导入源读取必要的信息,同时在标准输出直接输出相关指示。你可以运行该脚本并把它的输出管道连接到 git fast-import。
下面演示一下如何编写一个简单的导入脚本。假设你在进行一项工作,并且按时通过把工作目录复制为以时间戳 back_YY_MM_DD 命名的目录来进行备份,现在你需要把它们导入 Git 。目录结构如下:
$ ls /opt/import_from
back_2009_01_02
back_2009_01_04
back_2009_01_14
back_2009_02_03
current
为了导入到一个 Git 目录,我们首先回顾一下 Git 储存数据的方式。你可能还记得,Git 本质上是一个 commit 对象的链表,每一个对象指向一个内容的快照。而这里需要做的工作就是告诉 fast-import 内容快照的位置,什么样的 commit 数据指向它们,以及它们的顺序。我们采取一次处理一个快照的策略,为每一个内容目录建立对应的 commit ,每一个 commit 与之前的建立链接。
正如在第七章 “Git 执行策略一例” 一节中一样,我们将使用 Ruby 来编写这个脚本,因为它是我日常使用的语言而且阅读起来简单一些。你可以用任何其他熟悉的语言来重写这个例子——它仅需要把必要的信息打印到标准输出而已。同时,如果你在使用 Windows,这意味着你要特别留意不要在换行的时候引入回车符(译注:carriage returns,Windows 换行时加入的符号,通常说的 \r )—— git fast-import 对仅使用换行符(LF)而非 Windows 的回车符(CRLF)要求非常严格。
首先,进入目标目录并且找到所有子目录,每一个子目录将作为一个快照被导入为一个 commit。我们将依次进入每一个子目录并打印所需的命令来导出它们。脚本的主循环大致是这样:
last_mark = nil
循环遍历所有目录
[code]Dir.chdir(ARGV[0]) do
Dir.glob(“*”).each do |dir|
next if File.file?(dir)
# 进入目标目录
Dir.chdir(dir) do
last_mark = print_export(dir, last_mark)
end
end
end[/code]
我们在每一个目录里运行 print_export ,它会取出上一个快照的索引和标记并返回本次快照的索引和标记;由此我们就可以正确的把二者连接起来。“标记(mark)” 是 fast-import 中对 commit 标识符的叫法;在创建 commit 的同时,我们逐一赋予一个标记以便以后在把它连接到其他 commit 时使用。
因此,在print_export 方法中要做的第一件事就是根据目录名生成一个标记:
mark = convert_dir_to_mark(dir)
实现该函数的方法是建立一个目录的数组序列并使用数组的索引值作为标记,因为标记必须是一个整数。这个方法大致是这样的:
$marks = []
def convert_dir_to_mark(dir)
if !$marks.include?(dir)
$marks << dir
end
($marks.index(dir) + 1).to_s
end
有了整数来代表每个 commit,我们现在需要提交附加信息中的日期。由于日期是用目录名表示的,我们就从中解析出来。print_export 文件的下一行将是:
date = convert_dir_to_date(dir)
而 convert_dir_to_date 则定义为
def convert_dir_to_date(dir)
if dir == 'current'
return Time.now().to_i
else
dir = dir.gsub('back_', '')
(year, month, day) = dir.split('_')
return Time.local(year, month, day).to_i
end
end
它为每个目录返回一个整型值。提交附加信息里最后一项所需的是提交者数据,我们在一个全局变量中直接定义之:
$author = 'Scott Chacon <[email protected]>'
我们差不多可以开始为导入脚本输出提交数据了。第一项信息指明我们定义的是一个 commit 对象以及它所在的分支,随后是我们生成的标记,提交者信息以及提交备注,然后是前一个 commit 的索引,如果有的话。代码大致这样:
打印导入所需的信息
puts 'commit refs/heads/master'
puts 'mark :' + mark
puts "committer #{$author} #{date} -0700"
export_data('imported from ' + dir)
puts 'from :' + last_mark if last_mark
时区(-0700)处于简化目的使用硬编码。如果是从其他版本控制系统导入,则必须以变量的形式指明时区。 提交备注必须以特定格式给出:
data (size)\n(contents)
该格式包含了单词 data,所读取数据的大小,一个换行符,最后是数据本身。由于随后指明文件内容的时候要用到相同的格式,我们写一个辅助方法,export_data:
def export_data(string)
print "data #{string.size}\n#{string}"
end
唯一剩下的就是每一个快照的内容了。这简单的很,因为它们分别处于一个目录——你可以输出 deleeall命令,随后是目录中每个文件的内容。Git 会正确的记录每一个快照:
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
end
注意:由于很多系统把每次修订看作一个 commit 到另一个 commit 的变化量,fast-import 也可以依据每次提交获取一个命令来指出哪些文件被添加,删除或者修改过,以及修改的内容。我们将需要计算快照之间的差别并且仅仅给出这项数据,不过该做法要复杂很多——还如不直接把所有数据丢给 Git 然它自己搞清楚。假如前面这个方法更适用于你的数据,参考 fast-import 的 man 帮助页面来了解如何以这种方式提供数据。
列举新文件内容或者指明带有新内容的已修改文件的格式如下:
M 644 inline path/to/file
data (size)
(file contents)
这里,644 是权限模式(加入有可执行文件,则需要探测之并设定为 755),而 inline 说明我们在本行结束之后立即列出文件的内容。我们的 inline_data 方法大致是:
def inline_data(file, code = 'M', mode = '644')
content = File.read(file)
puts "#{code} #{mode} inline #{file}"
export_data(content)
end
我们重用了前面定义过的 export_data,因为这里和指明提交注释的格式如出一辙。
最后一项工作是返回当前的标记以便下次循环的使用。
return mark
注意:如果你在用 Windows,一定记得添加一项额外的步骤。前面提过,Windows 使用 CRLF 作为换行字符而 git fast-import 只接受 LF。为了绕开这个问题来满足 git fast-import,你需要让 ruby 用 LF 取代 CRLF:
$stdout.binmode
搞定了。现在运行该脚本,你将得到如下内容:
$ ruby import.rb /opt/import_from
commit refs/heads/master
mark :1
committer Scott Chacon <[email protected]> 1230883200 -0700
data 29
imported from back_2009_01_02deleteall
M 644 inline file.rb
data 12
version two
commit refs/heads/master
mark :2
committer Scott Chacon <[email protected]> 1231056000 -0700
data 29
imported from back_2009_01_04from :1
deleteall
M 644 inline file.rb
data 14
version three
M 644 inline new.rb
data 16
new version one
(...)
要运行导入脚本,在需要导入的目录把该内容用管道定向到 git fast-import。你可以建立一个空目录然后运行 git init 作为开头,然后运行该脚本:
[code] git init
Initialized empty Git repository in /opt/import_to/.git/
ruby import.rb /opt/import_from | git fast-import
git-fast-import statistics:
Alloc’d objects: 5000
Total objects: 18 ( 1 duplicates )
blobs : 7 ( 1 duplicates 0 deltas)
trees : 6 ( 0 duplicates 1 deltas)
commits: 5 ( 0 duplicates 0 deltas)
tags : 0 ( 0 duplicates 0 deltas)
Total branches: 1 ( 1 loads )
marks: 1024 ( 5 unique )
atoms: 3
Memory total: 2255 KiB
pools: 2098 KiB
objects: 156 KiB
pack_report: getpagesize() = 4096
pack_report: core.packedGitWindowSize = 33554432
pack_report: core.packedGitLimit = 268435456
pack_report: pack_used_ctr = 9
pack_report: pack_mmap_calls = 5
pack_report: pack_open_windows = 1 / 1
pack_report: pack_mapped = 1356 / 1356
---------------------------------------------------------------------[/code]
你会发现,在它成功执行完毕以后,会给出一堆有关已完成工作的数据。上例在一个分支导入了5次提交数据,包含了18个对象。现在可以运行 git log 来检视新的历史:
[code]$ git log -2
commit 10bfe7d22ce15ee25b60a824c8982157ca593d41
Author: Scott Chacon [email protected]
Date: Sun May 3 12:57:39 2009 -0700
imported from current
commit 7e519590de754d079dd73b44d695a42c9d2df452
Author: Scott Chacon [email protected]
Date: Tue Feb 3 01:00:00 2009 -0700
imported from back_2009_02_03[/code]
就它了——一个干净整洁的 Git 仓库。需要注意的是此时没有任何内容被检出——刚开始当前目录里没有任何文件。要获取它们,你得转到 master 分支的所在:
$ ls
$ git reset --hard master
HEAD is now at 10bfe7d imported from current
$ ls
file.rb lib
fast-import 还可以做更多——处理不同的文件模式,二进制文件,多重分支与合并,标签,进展标识等等。
一些更加复杂的实例可以在 Git 源码的 contib/fast-import 目录里找到;其中较为出众的是前面提过的 git-p4 脚本。
REF:http://cwiki.ossez.com/pages/viewpage.action?pageId=7045946