章节 ▾ 第二版

9.2 Git 与其他系统 - 迁移到 Git

迁移到 Git

如果你的现有代码库位于另一个 VCS 中,但你已决定开始使用 Git,则必须以某种方式迁移你的项目。 本节介绍一些常用系统的导入程序,然后演示如何开发自己的自定义导入程序。 你将学习如何从几个大型专业使用的 SCM 系统导入数据,因为它们构成了大部分正在切换的用户,并且因为它们的高质量工具很容易获得。

Subversion

如果你阅读了上一节关于使用 git svn 的内容,你可以轻松地使用这些说明来 git svn clone 一个仓库;然后,停止使用 Subversion 服务器,推送到新的 Git 服务器,然后开始使用它。 如果你想要历史记录,你可以尽可能快地完成它,你可以从 Subversion 服务器中提取数据(这可能需要一段时间)。

但是,导入并不完美;而且因为它会花费很长时间,你最好把它做好。 第一个问题是作者信息。 在 Subversion 中,每个提交的人都有一个系统用户,该用户记录在提交信息中。 前面的章节中的示例显示了在某些地方,例如 blame 输出和 git svn log 中的 schacon。 如果你想将其映射到更好的 Git 作者数据,你需要从 Subversion 用户到 Git 作者的映射。 创建一个名为 users.txt 的文件,该文件具有以下格式的映射

schacon = Scott Chacon <schacon@geemail.com>
selse = Someo Nelse <selse@geemail.com>

要获取 SVN 使用的作者名称列表,你可以运行此命令

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

这会生成 XML 格式的日志输出,然后仅保留包含作者信息的行,丢弃重复项,删除 XML 标记。 显然,这仅适用于安装了 grepsortperl 的计算机。 然后,将该输出重定向到你的 users.txt 文件中,以便你可以在每个条目旁边添加等效的 Git 用户数据。

注意

如果你是在 Windows 机器上尝试这个操作,那么这里就是你可能遇到问题的地方。微软提供了一些很好的建议和示例,请访问 https://learn.microsoft.com/en-us/azure/devops/repos/git/perform-migration-from-svn-to-git

你可以将此文件提供给 git svn,以帮助它更准确地映射作者数据。你还可以告诉 git svn 不包含 Subversion 通常导入的元数据,方法是将 --no-metadata 传递给 cloneinit 命令。元数据包括 Git 在导入期间生成的每个提交消息中的 git-svn-id。这会膨胀你的 Git 日志,并可能使其有点不清晰。

注意

当你想要将 Git 存储库中所做的提交镜像回原始 SVN 存储库时,你需要保留元数据。如果你不希望提交日志中包含同步信息,请随意省略 --no-metadata 参数。

这会使你的 import 命令看起来像这样:

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

现在,你的 my_project 目录中应该有一个更漂亮的 Subversion 导入。不再是这样的提交:

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

而是这样的提交:

commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2
Author: Scott Chacon <schacon@geemail.com>
Date:   Sun May 3 00:12:22 2009 +0000

    fixed install - go to trunk

不仅作者字段看起来好多了,而且 git-svn-id 也不见了。

你还应该做一些导入后的清理工作。首先,你应该清理掉 git svn 设置的奇怪引用。首先,你将移动标签,使它们成为真正的标签,而不是奇怪的远程分支,然后你将移动其余的分支,使它们成为本地分支。

要将标签移动为正确的 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/tags/ 开头的远程分支的引用,并使它们成为真正的(轻量级)标签。

接下来,将 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

可能会出现这种情况:你会看到一些带有 @xxx 后缀的额外分支(其中 xxx 是一个数字),而在 Subversion 中你只看到一个分支。这实际上是 Subversion 的一个功能,称为“peg-revisions”,Git 根本没有相应的语法对应物。因此,git svn 只是将 SVN 版本号添加到分支名称,就像你在 SVN 中编写它来寻址该分支的 peg-revision 一样。如果你不再关心 peg-revisions,只需删除它们

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

现在,所有旧分支都是真正的 Git 分支,所有旧标签都是真正的 Git 标签。

还有最后一件事需要清理。不幸的是,git svn 创建了一个名为 trunk 的额外分支,它映射到 Subversion 的默认分支,但 trunk 引用指向与 master 相同的位置。由于 master 在 Git 中更常用,以下是删除额外分支的方法:

$ git branch -d trunk

最后要做的是将你的新 Git 服务器添加为远程仓库并推送到它。以下是将你的服务器添加为远程仓库的示例:

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

因为你希望所有分支和标签都上传,所以你现在可以运行此命令:

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

你的所有分支和标签都应该以一个干净的导入形式出现在你的新 Git 服务器上。

Mercurial

由于 Mercurial 和 Git 在表示版本方面具有相当相似的模型,并且由于 Git 更灵活一些,因此使用一个名为 "hg-fast-export" 的工具将存储库从 Mercurial 转换为 Git 相当简单,你需要一份该工具的副本

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

转换的第一步是获取你想要转换的 Mercurial 存储库的完整克隆:

$ hg clone <remote repo URL> /tmp/hg-repo

下一步是创建一个作者映射文件。Mercurial 对 changeset 的作者字段的内容比 Git 更宽松,因此这是一个清理的好时机。在 bash shell 中,生成此文件只需一行命令:

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

这将需要几秒钟,具体取决于你的项目的历史记录有多长,之后 /tmp/authors 文件将如下所示:

bob
bob@localhost
bob <bob@company.com>
bob jones <bob <AT> company <DOT> com>
Bob Jones <bob@company.com>
Joe Smith <joe@company.com>

在此示例中,同一个人 (Bob) 使用四个不同的名称创建了 changeset,其中一个名称实际上看起来是正确的,而另一个名称对于 Git 提交而言是完全无效的。Hg-fast-export 允许我们通过将每一行转换为规则来解决此问题:"<input>"="<output>",将 <input> 映射到 <output>。在 <input><output> 字符串中,支持 Python string_escape 编码理解的所有转义序列。如果作者映射文件不包含匹配的 <input>,则该作者将未经修改地发送到 Git。如果所有用户名看起来都不错,我们根本不需要此文件。在此示例中,我们希望我们的文件如下所示:

"bob"="Bob Jones <bob@company.com>"
"bob@localhost"="Bob Jones <bob@company.com>"
"bob <bob@company.com>"="Bob Jones <bob@company.com>"
"bob jones <bob <AT> company <DOT> com>"="Bob Jones <bob@company.com>"

当 Git 不允许 Mercurial 名称时,可以使用相同类型的映射文件来重命名分支和标签。

下一步是创建我们的新 Git 存储库,并运行导出脚本:

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

-r 标志告诉 hg-fast-export 在哪里找到我们想要转换的 Mercurial 存储库,-A 标志告诉它在哪里找到作者映射文件(分支和标签映射文件分别由 -B-T 标志指定)。该脚本解析 Mercurial changeset 并将其转换为 Git 的 "fast-import" 功能的脚本(我们稍后将详细讨论)。这需要一段时间(虽然它比通过网络快得多),并且输出非常冗长

$ /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 remote add origin git@my-git-server:myrepository.git
$ git push origin --all

Perforce

你将要查看的下一个导入系统是 Perforce。正如我们上面讨论的,有两种方法可以让 Git 和 Perforce 相互通信:git-p4 和 Perforce Git Fusion。

Perforce Git Fusion

Git Fusion 使此过程相当轻松。只需使用配置文件(如 Git Fusion 中所述)配置你的项目设置、用户映射和分支,然后克隆存储库。Git Fusion 会让你得到一个看起来像原生 Git 存储库的东西,如果你愿意,可以将其推送到原生 Git 主机。如果你喜欢,甚至可以使用 Perforce 作为你的 Git 主机。

Git-p4

Git-p4 也可以充当导入工具。例如,我们将从 Perforce Public Depot 导入 Jam 项目。要设置你的客户端,你必须导出 P4PORT 环境变量以指向 Perforce depot

$ export P4PORT=public.perforce.com:1666
注意

为了继续,你需要一个 Perforce depot 来连接。我们将使用 public.perforce.com 上的公共 depot 作为我们的示例,但你可以使用你有权访问的任何 depot。

运行 git p4 clone 命令从 Perforce 服务器导入 Jam 项目,提供 depot 和项目路径以及你想要将项目导入到的路径

$ 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%)

这个特定项目只有一个分支,但是如果你有使用分支视图(或只是一组目录)配置的分支,你可以使用 --detect-branches 标志来 git p4 clone 以导入所有项目的分支。有关此内容的更多详细信息,请参见 Branching

在这一点上,你几乎完成了。如果你转到 p4import 目录并运行 git log,你可以看到你导入的工作

$ git log -2
commit e5da1c909e5db3036475419f6379f2c73710c4e6
Author: giles <giles@giles@perforce.com>
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 <kwirth@perforce.com>
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 大量删除标识符字符串

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

如果你运行 git log,你可以看到提交的所有 SHA-1 校验和都已更改,但 git-p4 字符串不再位于提交消息中

$ git log -2
commit b17341801ed838d97f7800a54a6f9b95750839b7
Author: giles <giles@giles@perforce.com>
Date:   Wed Feb 8 03:13:27 2012 -0800

    Correction to line 355; change </UL> to </OL>.

commit 3e68c2e26cd89cb983eb52c024ecdfba1d6b3fff
Author: kwirth <kwirth@perforce.com>
Date:   Tue Jul 7 01:35:51 2009 -0800

    Fix spelling error on Jam doc page (cummulative -> cumulative).

你的导入已准备好推送到你的新 Git 服务器。

自定义导入器

如果你的系统不是上述系统之一,你应该在线查找导入器 - 许多其他系统都有高质量的导入器,包括 CVS、Clear Case、Visual Source Safe,甚至是一个存档目录。如果这些工具都不适合你,你有一个更晦涩的工具,或者你需要一个更自定义的导入过程,你应该使用 git fast-import。此命令从 stdin 读取简单的指令以写入特定的 Git 数据。以这种方式创建 Git 对象比运行原始 Git 命令或尝试写入原始对象(有关更多信息,请参见 Git 内部原理)容易得多。这样,你可以编写一个导入脚本,该脚本从你正在导入的系统中读取必要的信息,并将简单的指令打印到 stdout。然后,你可以运行此程序并通过 git fast-import 管道其输出。

为了快速演示,你将编写一个简单的导入器。假设你在 current 中工作,你偶尔会将目录复制到带时间戳的 back_YYYY_MM_DD 备份目录中来备份你的项目,并且你想要将其导入到 Git 中。你的目录结构如下所示:

$ ls /opt/import_from
back_2014_01_02
back_2014_01_04
back_2014_01_14
back_2014_02_03
current

为了导入 Git 目录,你需要查看 Git 如何存储其数据。你可能还记得,Git 从根本上讲是一个指向内容快照的提交对象的链接列表。你所要做的就是告诉 fast-import 内容快照是什么,哪些提交数据指向它们,以及它们的顺序。你的策略将是一次一个地浏览快照,并使用每个目录的内容创建提交,将每个提交链接回上一个提交。

正如我们在Git强制策略示例中所做的那样,我们将用Ruby编写此示例,因为我们通常使用它,并且它往往易于阅读。 你可以用任何你熟悉的语言轻松地编写这个例子——它只需要将适当的信息打印到stdout。 并且,如果您在Windows上运行,这意味着您需要特别注意不要在行尾引入回车符——git fast-import 非常在意只想要换行符(LF),而不是Windows使用的回车换行符(CRLF)。

首先,您将进入目标目录并识别每个子目录,每个子目录都是您要作为提交导入的快照。您将进入每个子目录并打印导出它所需的命令。您的基本主循环如下所示:

last_mark = nil

# loop through the directories
Dir.chdir(ARGV[0]) do
  Dir.glob("*").each do |dir|
    next if File.file?(dir)

    # move into the target directory
    Dir.chdir(dir) do
      last_mark = print_export(dir, last_mark)
    end
  end
end

您在每个目录中运行 print_export,它接受前一个快照的清单和标记,并返回此快照的清单和标记;这样,您就可以正确地链接它们。“标记”是您赋予提交的标识符的 fast-import 术语;在创建提交时,您会给每个提交一个标记,您可以使用该标记从其他提交链接到它。因此,在您的 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

现在你已经有了提交的整数表示,你需要提交元数据的日期。 由于日期在目录名称中表示,因此您将解析它。 你的 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 = 'John Doe <john@example.com>'

现在您已准备好开始为您的导入器打印提交数据。 初始信息说明您正在定义一个提交对象以及它所在的哪个分支,然后是您生成的标记,提交者信息和提交消息,以及之前的提交(如果有)。 代码如下所示:

# print the import information
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

剩下的就是指定每个快照的文件内容。 这很容易,因为您在目录中有每一个——您可以打印出 deleteall 命令,然后打印出目录中每个文件的内容。 然后 Git 会适当地记录每个快照:

puts 'deleteall'
Dir.glob("**/*").each do |file|
  next if !File.file?(file)
  inline_data(file)
end

注意:由于许多系统将它们的修订版本视为从一个提交到另一个提交的更改,因此 fast-import 也可以接受每个提交的命令,以指定哪些文件已被添加、删除或修改以及新的内容是什么。 您可以计算快照之间的差异并仅提供此数据,但这样做更复杂——您不妨将所有数据都提供给 Git,让它自己弄清楚。 如果这更适合您的数据,请查看 fast-import 手册页,了解有关如何以这种方式提供数据的详细信息。

列出新文件内容或指定带有新内容的修改文件的格式如下:

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

就这样。 这是完整的脚本:

#!/usr/bin/env ruby

$stdout.binmode
$author = "John Doe <john@example.com>"

$marks = []
def convert_dir_to_mark(dir)
    if !$marks.include?(dir)
        $marks << dir
    end
    ($marks.index(dir)+1).to_s
end

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

def export_data(string)
    print "data #{string.size}\n#{string}"
end

def inline_data(file, code='M', mode='644')
    content = File.read(file)
    puts "#{code} #{mode} inline #{file}"
    export_data(content)
end

def print_export(dir, last_mark)
    date = convert_dir_to_date(dir)
    mark = convert_dir_to_mark(dir)

    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

    puts 'deleteall'
    Dir.glob("**/*").each do |file|
        next if !File.file?(file)
        inline_data(file)
    end
    mark
end

# Loop through the directories
last_mark = nil
Dir.chdir(ARGV[0]) do
    Dir.glob("*").each do |dir|
        next if File.file?(dir)

        # move into the target directory
        Dir.chdir(dir) do
            last_mark = print_export(dir, last_mark)
        end
    end
end

如果你运行这个脚本,你将会得到类似这样的内容:

$ ruby import.rb /opt/import_from
commit refs/heads/master
mark :1
committer John Doe <john@example.com> 1388649600 -0700
data 29
imported from back_2014_01_02deleteall
M 644 inline README.md
data 28
# Hello

This is my readme.
commit refs/heads/master
mark :2
committer John Doe <john@example.com> 1388822400 -0700
data 29
imported from back_2014_01_04from :1
deleteall
M 644 inline main.rb
data 34
#!/bin/env ruby

puts "Hey there"
M 644 inline README.md
(...)

要运行导入器,在您要导入到的 Git 目录中通过管道将此输出传递给 git fast-import。 您可以创建一个新目录,然后在其中运行 git init 作为起点,然后运行您的脚本:

$ 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:           13 (         6 duplicates                  )
      blobs  :            5 (         4 duplicates          3 deltas of          5 attempts)
      trees  :            4 (         1 duplicates          0 deltas of          4 attempts)
      commits:            4 (         1 duplicates          0 deltas of          0 attempts)
      tags   :            0 (         0 duplicates          0 deltas of          0 attempts)
Total branches:           1 (         1 loads     )
      marks:           1024 (         5 unique    )
      atoms:              2
Memory total:          2344 KiB
       pools:          2110 KiB
     objects:           234 KiB
---------------------------------------------------------------------
pack_report: getpagesize()            =       4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit      = 8589934592
pack_report: pack_used_ctr            =         10
pack_report: pack_mmap_calls          =          5
pack_report: pack_open_windows        =          2 /          2
pack_report: pack_mapped              =       1457 /       1457
---------------------------------------------------------------------

正如您所看到的,当它成功完成时,它会为您提供大量关于它完成的任务的统计信息。 在本例中,您为 4 个提交导入了总共 13 个对象到一个分支中。 现在,您可以运行 git log 来查看您的新历史记录:

$ git log -2
commit 3caa046d4aac682a55867132ccdfbe0d3fdee498
Author: John Doe <john@example.com>
Date:   Tue Jul 29 19:39:04 2014 -0700

    imported from current

commit 4afc2b945d0d3c8cd00556fbe2e8224569dc9def
Author: John Doe <john@example.com>
Date:   Mon Feb 3 01:00:00 2014 -0700

    imported from back_2014_02_03

好了——一个干净漂亮的 Git 仓库。 重要的是要注意,没有任何内容被检出——您一开始在工作目录中没有任何文件。 要获取它们,您必须将您的分支重置为 master 现在的位置:

$ ls
$ git reset --hard master
HEAD is now at 3caa046 imported from current
$ ls
README.md main.rb

您可以使用 fast-import 工具做更多的事情——处理不同的模式、二进制数据、多个分支和合并、标签、进度指示器等等。 在 Git 源代码的 contrib/fast-import 目录中提供了许多更复杂场景的示例。

scroll-to-top