轻便快速 Git 导出并获取提交 ID

Light and fast Git Export and fetch commit-id

如何将远程 git 存储库导出到本地 space,只获取给定分支的 head 修订版,然后对于每个导出的文件,获取该文件的提交 ID文件?

到目前为止我尝试了什么

执行这个:

git clone {gitUrl} {repoDir} --branch {branch}

然后对于每个这样导出的文件(忽略 .git 和内容),执行此:

git rev-list -1 HEAD {file}

... 以下位置标记是这样定义的:

  1. {gitUrl} 是存储库的 http url。可以嵌入用户凭据。
  2. {repoDir} 是您本地系统中导出的路径。
  3. {file} 是您要为其提取提交 ID 的导出文件的完整路径。

虽然这可行,但问题是它太慢且效率低下。 git 克隆操作包括该分支的整个回购历史。而我们只对 HEAD 版本及其元数据感兴趣。

或者,我们可以执行导出:

git clone {gitUrl} {repoDir} --branch {branch} --depth 1

这样效率更高,因为它只是拉下 HEAD 版本。但问题在于,随后的 git rev-list -1 HEAD {file} 命令将 return 整个 HEAD 的提交 ID,而不是文件的提交 ID。

我可以把我的蛋糕也吃掉吗?

Can I have my cake and eat it too?

简短的回答是否定的。

从技术上讲,HEAD 提交 中每个文件的提交 ID 是 您使用 git rev-parse HEAD 获得的哈希 ID(或更长但等效的 git rev-list 你正在使用的命令)。那是因为每次提交都包含 每个 文件的完整快照 Git 知道。

当您使用 git rev-listgit log 或在 per-line-in-one-file、git blame 命令中回顾历史时得到的不是提交哈希 ID有问题的文件,因为那是微不足道的。相反,它是一些 较早 提交的提交哈希 ID,其中包含 相同的文件 或者,对于 git blame同一行.

也就是说,假设我们在 Git 存储库中有一个简单的线性历史记录,其中只有五次提交。我们可以这样绘制这五个提交:

A <-B <-C <-D <-E   <--master

其中每个大写字母代表一个实际的提交哈希 ID。分支名称,在本例中为 master,用于让我们找到提交 E 的实际哈希 ID,因为它看起来是随机的,否则很难或有时不可能找到。

提交 E,当然,包含每个文件的完整快照,就像我们(或任何人)提交时的形式一样 E。它还包含早期提交的哈希 ID D。 Git 调用 D 提交 E.

parent

但是提交 D 也有每个文件的完整快照,就像有人创建 D 时的形式一样,并且 link 回到 它的 parent C。这重复 C 等等,回到整个历史(当我们点击 A 时结束,它没有 parent 提交)。

在这种情况下,我们想要的是Git 比较 某个文件的快照—README.md, main.py,或其他什么——出现在提交 E 中的那个和出现在其 parent 提交 D 中的那个。如果这两个快照 相同 ,我们希望 Git 将 DC 进行比较。如果它们相同,Git 应该继续向后计算。它应该这样做,直到它用完 A 处的提交,或者比较显示两个文件不同。1

换句话说,我们在重复执行一个简单的比较操作:

  • 文件 F 在提交 XY 中相同还是不同?

每 parent/child 对提交。一旦答案是“是的,它是不同的”,我们就会 Git 停止通过历史倒退 并打印此时到达的提交的哈希 ID。 (de-duplicates 文件跨提交的内部存储格式使这变得非常简单。使用 git blame,计算相当困难和复杂,但它相当于同一件事,只是在 line-by-line 基础。)

不过,为了做到这一点,Git 必须能够访问 它需要遍历的每个提交,因为它会向后遍历历史。历史,在 Git 中, 存储库中的提交集。 Git必须有历史才能使用历史。


1Git 实际使用的一个简单而权宜的技巧是,当我们点击 parent-less(孤儿?)提交时 A,它可以简单地假装A之前有一个完全空的提交。然后 A 中的每个文件都是新的,因此不同于它的 virtual/fake parent。这就是为什么每个 Git 存储库都包含 empty tree.