还原先前暂存的更改(或:撤消对 .git/index 的更改)
Revert previously staged changes (or: undo changes to .git/index)
在尝试了解撤消各种 git 操作的方法时,我想到了一个我不确定如何处理它的场景。免责声明:我在实际使用 git 'in production' 时没有遇到这种情况,但我仍然认为这不仅仅是一个学术问题。
让我们看看下面的场景
- 处理之前提交的文件:
echo "some content" >> example.txt
- 进行更改:
git add example.txt
- 签出自上次提交以来的更改:
git checkout @ -- example.txt
- 意识到您选择了错误的文件,并且您想撤消上一个命令以恢复您的更改 (
"some content"
)
我认为幕后发生了什么
每次使用 git add
更改暂存时,都会在 .git/objects/ 和索引文件 ( 下创建一个 blob 对象。 git/index) 得到更新。如果我多次更改和添加内容,将会出现多个斑点。旧的不会立即被垃圾收集。
当 运行 索引上方的签出命令立即更新时(我还假设内容只在我的工作目录中但未暂存)。这样引用就消失了,我不能使用 git checkout-index
之类的东西来恢复它们。
除非垃圾收集启动,内容在技术上仍然存在。但我不知道如何取回它,然后手动尝试以某种方式找到散列并使用 git cat-file
读取内容。同样的,例如对于 运行 git add
多次是正确的,尽管这里想要返回先前上演的更改可能不是一个真正的用例。 (或者也许当从存储中弹出更改时?...)
所以所有这些都归结为这些问题:
- 是否有类似
git reflog
的索引?
git checkout @ -- file
是否被认为是像 git reset --hard
这样的危险命令,您可能会失去工作?
如果答案是 "No" / "Yes"(到目前为止我的假设):
- 是否有手动 change/rewrite 索引的管道命令? (请参阅上面的情况,其中对象仍然存在)
奖励:是否有另一种方法可以在不立即暂存文件的情况下签出单个文件?
您的幕后描述大部分是正确的。唯一不是 100% 的事情与这部分有关:
Every time when staging changes with git add
a blob object is created under .git/objects/
在内部,git add
对工作树文件中的数据内容进行哈希处理,a la git hash-object -w -t blob
。这并不必然创建新对象:如果散列内容已经在存储库中,它只是重新使用现有对象。现有对象可能 打包,即 .git/objects/pack
,而不是 松散 作为单独的 blob。
此外,由于清洁过滤器。由于行结束设置,它通常与工作树中的内容不同。清理过滤器和行尾设置部分(或大部分,取决于您对 Git 的使用)通过您的 .gitattributes
文件控制,部分(或大部分)通过您的配置中的设置控制。
无论如何,重要的是您获得了 blob 对象的哈希 ID。 blob 对象肯定存在于某个地方——作为松散对象在 .git/objects
目录中,或者在包文件中。现在 git add
可以写入 .git/index
(或 GIT_INDEX_FILE
指示的任何其他文件):它将在暂存槽零处的索引中存储给定 [ 的条目=21=],使用计算的 blob-hash 和模式 100644
或 100755
,具体取决于工作树文件是否应在以后标记为可执行文件。
如果你丢了,多半是你运气不好
[场景被剪掉了,但它以 git checkout HEAD 结束——<em>path</em>
破坏了索引条目,它的$path
表示 $blobhash
和模式 $mode
信息, 和 破坏文件的工作树副本在 path
.)
Unless garbage collection kicks in the content is still there technically. But I don't know how I would get it back other then manually trying to find the hash somehow and reading the content with git cat-file
.
事实上,您不能:哈希 ID 计算是 trapdoor function, and only if you do have the hash can you have Git spill out the content, but you need to have the content if you don't have the hash. That's your Catch-22 situation.
如果——这是一个非常重要的"if"——内容是独一无二的,所以git add
确实确实创建了一个 new blob 对象,and 你刚刚覆盖了索引中的 blob 引用,那个 blob 对象确实不再存在在任何地方引用。另一方面,如果 git hash-object -w
最终重用了一些现有的 blob,则 blob 对象仍然被之前引用它的任何对象引用。所以现在有两个有趣的情况:blob 是 唯一的,现在可以进行垃圾收集,或者,blob 不是 是唯一的,并且不是.
使用git fsck --lost-found
或git fsck --unreachable
或git fsck --dangling
(默认),你可以让Git遍历整个对象数据库,确定哪些对象是可达,哪些不可达,并告诉您一些或所有无法到达的 and/or 将来自或关于它们的信息复制到 .git/lost-found
。如果 blob 对象 是 不可访问的,它 将 列为这些不可访问或悬空的 blob 之一,或者将其内容恢复到 .git/lost-found
.
这里的缺点是可能有几十个甚至上百个悬挂的 blob 对象。您的任务现在已从 "guess the hash"(几乎不可能)切换到 "find the needle in the haystack"(不那么困难,但很乏味,您可能会发现 错误的 针——它不是真的是大海捞针,毕竟是一堆针)。当然,这仅适用于 "blob was unique" 情况。
具体问题的答案
(顺便说一下,这个问题 并不是 真正与 Can git undo a checkout of unstaged files 重复的问题。但是那个问题仍然有用,所以也看看.)
Is there something like git reflog
for the index?
没有。您 可以 制作自己的备份副本:只需 cp .git/index
某处即可。但是 Git 本身并不会这样做。您可能会在 git checkout HEAD -- <em>path</em>
操作之前创建一个,通过一些别名或 shell-function你用来做这种危险的操作。
请注意 Git 不知道这些备份副本,因此 git gc
不会将引用的对象视为受保护的。要将备份与 git ls-files
等管道命令一起使用,请在该命令执行期间将路径名放入 GIT_INDEX_FILE
。
Is git checkout @ --
file considered to be a dangerous command like git reset --hard
where you could potentially lose your work?
这个问题的答案取决于谁在考虑。我建议我自己考虑它是危险的,因为你根本就在问这个问题。 :-)
Are there plumbing commands to manually change/rewrite the index? (see the case above where the objects are still there)
是:git update-index
是一次一个条目的更新程序(使用 --cacheinfo
或 --stdin
提供原始索引条目数据而不是让它复制一个很多 git add
工作)。许多其他命令也会部分或整体更新索引。
如果您有一个在 git checkout HEAD -- ...
操作之前备份索引的过程,您可以从备份索引中读取条目(例如使用 GIT_INDEX_FILE=... git ls-files
),然后使用 git update-index
, without having GIT_INDEX_FILE
set, to put the information into the regular index.当然,这是一个索引覆盖操作,您可能希望先对索引进行另一个备份。
Is there an alternative way to checkout a single file without instantaneously staging it?
没有,只是因为这里的动词checkout。要查看索引或任何提交中文件的内容——以便内容具有git rev-parse
可以理解的名称——使用git show
:
git show :file # file in index at stage zero
git show :3:file # file in index at stage three, during merge conflict
git show HEAD:file # file in current commit
git show master~7:file # file in commit 7 first-parent hops back from master
另请注意,git reset
可以在不触及工作树中的文件的情况下覆盖索引中的一个或多个文件:
git reset HEAD -- file # copy HEAD:file to :file leaving work-tree file undisturbed
如果您给 git reset
一个目录的路径,它会重置所有已经在索引中并驻留在该目录中的文件。
在尝试了解撤消各种 git 操作的方法时,我想到了一个我不确定如何处理它的场景。免责声明:我在实际使用 git 'in production' 时没有遇到这种情况,但我仍然认为这不仅仅是一个学术问题。
让我们看看下面的场景
- 处理之前提交的文件:
echo "some content" >> example.txt
- 进行更改:
git add example.txt
- 签出自上次提交以来的更改:
git checkout @ -- example.txt
- 意识到您选择了错误的文件,并且您想撤消上一个命令以恢复您的更改 (
"some content"
)
我认为幕后发生了什么
每次使用 git add
更改暂存时,都会在 .git/objects/ 和索引文件 ( 下创建一个 blob 对象。 git/index) 得到更新。如果我多次更改和添加内容,将会出现多个斑点。旧的不会立即被垃圾收集。
当 运行 索引上方的签出命令立即更新时(我还假设内容只在我的工作目录中但未暂存)。这样引用就消失了,我不能使用 git checkout-index
之类的东西来恢复它们。
除非垃圾收集启动,内容在技术上仍然存在。但我不知道如何取回它,然后手动尝试以某种方式找到散列并使用 git cat-file
读取内容。同样的,例如对于 运行 git add
多次是正确的,尽管这里想要返回先前上演的更改可能不是一个真正的用例。 (或者也许当从存储中弹出更改时?...)
所以所有这些都归结为这些问题:
- 是否有类似
git reflog
的索引? git checkout @ -- file
是否被认为是像 gitreset --hard
这样的危险命令,您可能会失去工作?
如果答案是 "No" / "Yes"(到目前为止我的假设):
- 是否有手动 change/rewrite 索引的管道命令? (请参阅上面的情况,其中对象仍然存在)
奖励:是否有另一种方法可以在不立即暂存文件的情况下签出单个文件?
您的幕后描述大部分是正确的。唯一不是 100% 的事情与这部分有关:
Every time when staging changes with
git add
a blob object is created under .git/objects/
在内部,git add
对工作树文件中的数据内容进行哈希处理,a la git hash-object -w -t blob
。这并不必然创建新对象:如果散列内容已经在存储库中,它只是重新使用现有对象。现有对象可能 打包,即 .git/objects/pack
,而不是 松散 作为单独的 blob。
此外,由于清洁过滤器。由于行结束设置,它通常与工作树中的内容不同。清理过滤器和行尾设置部分(或大部分,取决于您对 Git 的使用)通过您的 .gitattributes
文件控制,部分(或大部分)通过您的配置中的设置控制。
无论如何,重要的是您获得了 blob 对象的哈希 ID。 blob 对象肯定存在于某个地方——作为松散对象在 .git/objects
目录中,或者在包文件中。现在 git add
可以写入 .git/index
(或 GIT_INDEX_FILE
指示的任何其他文件):它将在暂存槽零处的索引中存储给定 [ 的条目=21=],使用计算的 blob-hash 和模式 100644
或 100755
,具体取决于工作树文件是否应在以后标记为可执行文件。
如果你丢了,多半是你运气不好
[场景被剪掉了,但它以 git checkout HEAD 结束——<em>path</em>
破坏了索引条目,它的$path
表示 $blobhash
和模式 $mode
信息, 和 破坏文件的工作树副本在 path
.)
Unless garbage collection kicks in the content is still there technically. But I don't know how I would get it back other then manually trying to find the hash somehow and reading the content with
git cat-file
.
事实上,您不能:哈希 ID 计算是 trapdoor function, and only if you do have the hash can you have Git spill out the content, but you need to have the content if you don't have the hash. That's your Catch-22 situation.
如果——这是一个非常重要的"if"——内容是独一无二的,所以git add
确实确实创建了一个 new blob 对象,and 你刚刚覆盖了索引中的 blob 引用,那个 blob 对象确实不再存在在任何地方引用。另一方面,如果 git hash-object -w
最终重用了一些现有的 blob,则 blob 对象仍然被之前引用它的任何对象引用。所以现在有两个有趣的情况:blob 是 唯一的,现在可以进行垃圾收集,或者,blob 不是 是唯一的,并且不是.
使用git fsck --lost-found
或git fsck --unreachable
或git fsck --dangling
(默认),你可以让Git遍历整个对象数据库,确定哪些对象是可达,哪些不可达,并告诉您一些或所有无法到达的 and/or 将来自或关于它们的信息复制到 .git/lost-found
。如果 blob 对象 是 不可访问的,它 将 列为这些不可访问或悬空的 blob 之一,或者将其内容恢复到 .git/lost-found
.
这里的缺点是可能有几十个甚至上百个悬挂的 blob 对象。您的任务现在已从 "guess the hash"(几乎不可能)切换到 "find the needle in the haystack"(不那么困难,但很乏味,您可能会发现 错误的 针——它不是真的是大海捞针,毕竟是一堆针)。当然,这仅适用于 "blob was unique" 情况。
具体问题的答案
(顺便说一下,这个问题 并不是 真正与 Can git undo a checkout of unstaged files 重复的问题。但是那个问题仍然有用,所以也看看.)
Is there something like
git reflog
for the index?
没有。您 可以 制作自己的备份副本:只需 cp .git/index
某处即可。但是 Git 本身并不会这样做。您可能会在 git checkout HEAD -- <em>path</em>
操作之前创建一个,通过一些别名或 shell-function你用来做这种危险的操作。
请注意 Git 不知道这些备份副本,因此 git gc
不会将引用的对象视为受保护的。要将备份与 git ls-files
等管道命令一起使用,请在该命令执行期间将路径名放入 GIT_INDEX_FILE
。
Is
git checkout @ --
file considered to be a dangerous command likegit reset --hard
where you could potentially lose your work?
这个问题的答案取决于谁在考虑。我建议我自己考虑它是危险的,因为你根本就在问这个问题。 :-)
Are there plumbing commands to manually change/rewrite the index? (see the case above where the objects are still there)
是:git update-index
是一次一个条目的更新程序(使用 --cacheinfo
或 --stdin
提供原始索引条目数据而不是让它复制一个很多 git add
工作)。许多其他命令也会部分或整体更新索引。
如果您有一个在 git checkout HEAD -- ...
操作之前备份索引的过程,您可以从备份索引中读取条目(例如使用 GIT_INDEX_FILE=... git ls-files
),然后使用 git update-index
, without having GIT_INDEX_FILE
set, to put the information into the regular index.当然,这是一个索引覆盖操作,您可能希望先对索引进行另一个备份。
Is there an alternative way to checkout a single file without instantaneously staging it?
没有,只是因为这里的动词checkout。要查看索引或任何提交中文件的内容——以便内容具有git rev-parse
可以理解的名称——使用git show
:
git show :file # file in index at stage zero
git show :3:file # file in index at stage three, during merge conflict
git show HEAD:file # file in current commit
git show master~7:file # file in commit 7 first-parent hops back from master
另请注意,git reset
可以在不触及工作树中的文件的情况下覆盖索引中的一个或多个文件:
git reset HEAD -- file # copy HEAD:file to :file leaving work-tree file undisturbed
如果您给 git reset
一个目录的路径,它会重置所有已经在索引中并驻留在该目录中的文件。