使用 Git 恢复一些旧文件的副本,同时保留其最新版本

Use Git to restore a copy of some old files while keeping their latest version

我有几个文件(比方说 file1file2),我想从旧提交中恢复它们的副本。但我也想保留他们的最新版本,因为新旧版本最终做了两件不同且有用的事情。更准确地说,我想从以下情况转移:

project/
  |- file1  (latest version)
  |- file2  (latest version)

以下情况:

project/
  |- new/
    |- file1  (latest version)
    |- file2  (latest version)
  |- old/
    |- file1  (old version)
    |- file2  (old version)

如何使用 Git 实现此目的?

您可以使用 git show 显示任何给定提交的文件内容:

git show deadbeef:path/to/file1 > file1.old

每个 commit 存储每个文件的完整快照——或者更确切地说,是该特定提交中的每个文件。一旦提交,就永远无法更改。因此,您所有旧的、现有的提交都包含所有文件的 all 版本,就像您提交它们时它们中的内容一样。它们一直被冻结——并且,为了像这样保证它们的安全而不占用太多磁盘 space,它们采用特殊的、压缩的、只读的、仅 Git 的形式。

每个提交都有一个 "true name":原始哈希 ID。当您 运行 git log 时,您会看到这些又大又丑的哈希 ID,或者有时是它们的精简版。该原始哈希 ID 将始终用于命名 that 提交。其他名称——例如 master 或其他一些分支或标记名称——也用于命名一个特定的提交,但是 branch 名称会随着时间的推移而变化:分支名称只包含哈希last 提交的 ID,应该被视为该分支的一部分。

因为提交中的文件被冻结并且 Git-only,您需要一种方法来 使用 这些文件的未冻结和重构版本。 Git 允许您使用自己的文件的方式是,它将冻结的、压缩的、仅 Git 的文件——我称之为 freeze-dried——复制到将文件恢复为正常格式的工作区。 Git 将此工作区称为 工作树 ,有时 工作树 工作目录 或这些名称的一些变体。 ("Working directory" 表示只有一层文件夹,所以多年来 Git 的人一直试图让它始终如一地使用短语 "work tree"。我有时喜欢用连字符连接它。)

您可以使用 git checkout 任何特定提交 中获取 所有 文件到您的工作树中。为此,您可以给 git checkout 历史提交的原始哈希 ID。1 当然,问题在于这检查了整个提交,删除来自您正在处理的任何分支中的最后一次提交的那些文件的版本,并将它们替换为旧提交的版本。

因此,就像在 中一样,您可能只想从旧提交中提取 一个 (或几个)文件到您的工作树区域,所以您可以看到它并使用它 on/with。 git show 命令可以显示来自任何提交的一个文件,使用 git show <em>hash</em>:<em>path</em> 。这会将文件的内容显示为 git show 的标准输出,因此您需要将该输出重定向到计算机上某处的新文件(工作树内部或外部的任何地方都可以)。

另一种方法是创建第二个工作树。自 Git 2.5(通过 Git 2.15 在几个版本中修复了一些重要的错误),2 Git 已经有一个 git worktree命令。可以告诉此命令在当前工作树之外的某处创建一个 new 工作树。 Git 以前每个存储库只有一个工作树。现在 Git 有能力拥有多个,我们必须给原来的一个起个名字:我在这里使用 "main" 或 "primary" 这个词。

要使用 git worktree add 获得一个 分离的 HEAD 提交(再次参见脚注 1),运行 git worktree 添加 <em>path</em> <em>commit-hash</em>。例如,如果您 喜欢 使用的所有文件的旧版本都在提交 28014c1084 中,那么 /tmp/foo 是放置的好地方他们,你可以 运行:

$ git worktree add /tmp/foo 28014c1084

这会打印这样的东西(取决于 Git 年份):

Preparing worktree (detached HEAD 28014c1084)
HEAD is now at 28014c1084

此时,/tmp/foo 中存在一个充满文件的新树,这些文件是 所有 从提交 28014c1084 中提取的文件。

当您完成添加的工作树后——已经从中复制了所有您关心的文件——只需删除它:

$ rm -r /tmp/foo

然后使用git worktree prune将其从工作树列表中删除:

$ git worktree list
[path]    d9f6f3b619 [master]
/tmp/foo  28014c1084 (detached HEAD)
$ git worktree prune
$ git worktree list
[path]    d9f6f3b619 [master]

1这会产生 Git 所谓的 分离的 HEAD。这有利于查看历史提交,但不利于开展新工作。要退出此模式,只需 git checkout 任何分支名称。

2如果您的 Git 版本早于 2.15,可以使用 git worktree add,只要您 不使用 将添加的工作树放置太久。在 2.15 中修复的最严重的错误是两周左右后,如果您在主工作树中工作两周或更长时间,同时留下一个添加的工作树,添加的工作树的内部对象可能会被意外破坏在某些情况下。因此,我建议使用 Git 2.5 到 2.14 的任何人的简单经验法则是:尽可能升级;如果没有,最多只使用添加的工作树一周左右。