【问题标题】:How to fix a local copy messed up with git reset HASH如何修复被 git reset HASH 弄乱的本地副本
【发布时间】:2021-07-02 19:38:22
【问题描述】:

我向本地文件添加了一些更改,然后提交了它们 (A),然后我添加了更多提交 (B&C),然后意识到我不想从提交 (A) 中进行更改,所以我继续做了git reset --hard <HASH>

它从git log 中删除了先前的提交,但保留了我在文件中 (A) 添加的更改,当我现在将此文件更改回其原始状态时,它会将其标记为我不想要的已更改.我希望文件反映origin。我如何从这里完成这项工作?

(我认为这就是 reset --hard 所做的)

【问题讨论】:

  • 如果您希望您的分支 X 反映原始(又名远程)认为的应该是什么,您可能只想在签出 X 后执行 git reset --hard origin/X。请注意,在您进一步试验之前您可能希望确保拥有存储库文件夹的副本,甚至可能要对副本进行试验,以便在搞砸时保持原件完好无损。
  • @LasseV.Karlsen 我想要这个git reset --hard origin/X,但只适用于单个文件并将我的其他本地更改保留在分支中
  • 你不能用 reset 命令来做到这一点,git reset 在存储库级别工作。您将需要类似git checkout <HASH> -- path/to/file

标签: git version-control reset


【解决方案1】:

作为already answered in a comment,你不能用git reset得到你想要的最终效果。然而,这里的细节相当混乱,因为git reset 有点像kitchen-sink 命令。

我在本地文件中添加了一些更改,然后提交了它们 (A) ...

这里要意识到的是,每个 Git 提交都是每个文件的完整快照。当您更改一个文件并进行新的提交时,您会创建一个新的完整快照

这不会占用大量磁盘空间,因为 Git 提交中的文件不是普通文件。它们以特殊的、压缩的、Git 化的和去重复 的形式存储。所以新的提交 A 只是重新使用了之前提交 α 中的所有旧文件,除了一个修改过的文件,它会获得一个新的快照。1

然后我添加了更多提交 (B&C),然后意识到,我不希望提交 (A) 的更改,所以我继续执行 git reset --hard <HASH> ...

这种git reset 是关于进行特定的提交

在 Git 中,提交的布局有点像一串珍珠。

每个提交都有一个唯一但看起来随机的哈希 ID,它只是一个非常大的数字的 hexadecimal 编码。除非您拥有提交的所有数据来自,否则不可能知道某个提交的哈希 ID,或者只是直接给出哈希 ID,因此 Git 处理此问题的方式是每个提交 存储其直接前任的哈希 ID(连同快照和其他元数据)。然后,Git 只需要某种方式将 last 提交的哈希 ID 存储在此链中,因为每个提交都向后指向上一个提交:

δ <-γ <-β <-α <-A <-B <-C   <-- your-branch

这里的字母(来自您自己的问题的 A、B 和 C,加上一些倒序排列的希腊字母,表示链中较早的提交)在这里代表看起来随机的哈希 ID。当您提交 A 时,链来自:

δ <-γ <-β <-α   <-- your-branch

到:

δ <-γ <-β <-α <-A   <-- your-branch

因为 Git 添加了一个新的提交A,持有一个完整的快照,加上链上前一端的哈希 ID。然后,当你提交 B 时,你得到:

δ <-γ <-β <-α <-A <-B   <-- your-branch

等等提交C

git reset 命令告诉 Git:停止使用当前分支名称记住当前的最后提交。相反,让这个分支名称指向我指定的提交。 因为你指定了提交A,所以给了你:

                  B <-C   [no name to find C]
                 /
δ <-γ <-β <-α <-A   <-- your-branch

除了git reset 的“移动分支名称”动作之外,它还会对 Git 的索引和工作树产生影响,除非你告诉它不要这样做。 git reset 命令还有其他模式可以做其他事情,但我们不要误入歧途;我的答案已经太长了。 ?

如果您希望您的 BC 提交回来,它们可恢复的,一段时间。恢复它们的主要问题是找到提交C 的哈希ID。 (您不必找到提交 B 的哈希 ID,因为提交 C 为您保存了该哈希 ID。您只需在链中找到 last 提交:Git 将从那里自动更早地找到所有内容。)

幸运的是,Git 有一个叫做 reflogs 的东西,它会将 存储在某个名称中的哈希 ID 默认保存至少 30 天。您可以查看 HEAD 的 reflog 或分支名称的 reflog,以找到提交 C 的哈希 ID。使用git refloggit reflog <em>branch</em> 来执行此操作。

假设您将这两个放回(与另一个 git reset --hard),以便您的 strand-of-pearls 提交返回到:

δ <-γ <-β <-α <-A <-B <-C   <-- your-branch

您现在可以非常轻松地向您的分支添加一个提交D,使用git revert完全撤消提交A的效果.或者,您可以创建一个新的提交 D,其快照与 C 的快照相同,只是一个特定文件的内容是从任何给定提交中提取的内容。


1这些单独的文件快照后来在 Git 所谓的 pack files 中进一步压缩,超出了它们的初始压缩和 Git 化,这意味着即使是多个版本的大文本文件最终不会占用太多空间。不过,您无需关心这些细节:只需记住每个提交都充当每个文件的完整存档,例如 tar、zip 或 rar 存档。


使用git revert

git revert 命令通过将提交的内容与其父级的内容进行比较来工作。无论在此处更改,该更改将通过或多或少地反向应用更改而在当前 文件集中取消2 因此,如果您在提交 A 中修改了文件 F,但没有对任何其他文件执行任何操作,git revert <em>hash-of-A</em> 将退出该更改。 Git 将从结果文件中进行新的提交。


2从技术上讲,revert 是一种三向合并,当前提交一如既往地是当前提交,但 merge base 是子提交您在 git revert 命令中指定,并且三向合并的 其他提交 是该父/子对的父级。


使用git restoregit checkout

如前所述,每次提交都有所有文件的完整快照。

要获取一个特定提交的特定文件输出,您可以使用新的(从 Git 2.23 开始)git restore 命令:

git restore -SW --source <commit> -- <path/to/file>

-S-W 选项都在这里选择,告诉git restore 将替换文件写入暂存区(这样您就不必在之后git add 文件) 你的工作树。默认情况下只写入您的工作树,需要后续的git add

源提交可以是原始哈希 ID,或者您可以使用任何可以找到正确哈希 ID 的名称。如果origin/main 选择了具有正确文件副本的提交,您可以使用--source origin/main。您可以将--source 缩写为-s(注意小写,而-S 用于暂存区)。

如果您的 Git 早于 2.23,您可以使用:

git checkout <commit> -- <path/to/file>

git restore -SW 命令的效果相同(写入索引/暂存区和工作树)。

无论如何,在使用这些方法之后,您都需要进行一次新的提交。

使用交互式变基

除了添加新的提交,可以用新的和改进的替代提交替换整个系列的提交git rebase 命令用于此目的;当与--interactive(或简称-i)一起使用时,git rebase 是一种有效的方式来停止使用一大堆陈旧的提交。旧的提交并没有消失: 就像git reset --hard 一样,Git 仍然会保留旧的提交一段时间(默认情况下至少 30 天)。但是您的 Git 停止使用它们,转而支持 git rebase 在放弃旧提交之前构建的新的和改进的提交。

因为这确实放弃了旧的提交,rebase 并不总是合适的。特别是,如果 other Git 存储库有旧提交的副本,则很难说服 每个 Git 存储库改用新的和改进的提交。旧的、糟糕的提交可能会继续困扰您。添加还原提交没有这个问题,因为 Git 是为 add 提交而不是 drop 旧的(重置和变基是在 你的 存储库,但不影响其他人的)。

【讨论】:

  • 这是一篇很好的文章,用于描述功能和后果!非常感谢托雷克!很棒的一课!
猜你喜欢
  • 2011-02-02
  • 1970-01-01
  • 1970-01-01
  • 2015-09-05
  • 2016-06-03
  • 2014-11-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-11
相关资源
最近更新 更多