假设我有一个Git repo,其中包含以下提交给主服务器:A,B,C,D。我想将主服务器回滚到它在以下提交A中的状态;换句话说,放弃B,C和D的变化。我很确定git reset --hard
会这样做。但是,我想有选择地重新应用一些丢弃的补丁(git cherry-pick
是我想要的,对吗?)所以我的具体问题是:
git reset --hard
是否会从提交历史记录中删除任何内容?如果我将主人重置为A,那么B,C和D是否仍会在回购中闲置?
git cherry-pick
允许我做我上面描述的事情,还是我误解了它?
答案 0 :(得分:7)
要正确理解git reset
,您需要所有这些信息:
在某种意义上,提交本身存在于任何分支名称之外。
当你进行提交时,Git会为其分配一个唯一的哈希ID。您在其中存储的新提交是在您创建它时当前提交的任何提交的哈希ID。我们可以使用这些哈希ID将提交链接在一起:
A <-B <-C <-D
我们说每个提交指向之前的提交。 (由于A
之前没有提交,因此它并不指向任何位置。如果 之前的A
,请想象链条会更进一步。最终必须结束,因为没有Git存储库具有无限数量的提交,并且图形受到约束。)
但是,分支名称,如master
,保留提交。如果上面的D
提交有 no 名称,则D
有被Git&#39> 垃圾收集器清理和删除的危险因为它似乎毫无用处。因此,我们添加一个外部名称以指向D
:
A <-B <-C <-D <-- master
现在Git知道D
正在使用中。由于D
指向C
,因此Git知道C
正在使用中,依此类推,直到历史记录为止。
特殊名称HEAD
通常包含分支的名称。分支名称本身(例如master
)通常用于标识某些特定提交(D
),从而使D
保持活动状态。名称HEAD
用于告诉Git哪个分支名称将被视为当前分支。
当您使用git commit
进行新提交时,Git会使用 index 的内容进行新提交。索引,也称为暂存区域,有时也称为缓存,位于&#34;之间。当前(HEAD)提交和工作树。因此,当前提交的每个文件都有(最多)三个版本:HEAD
中的一个,索引中的一个,以及工作树中的一个。
您可以在索引和工作树之间来回复制文件,并且可以将任何提交的文件 out 复制到索引中;但是提交是只读的,因此您无法从索引复制到现有提交中。您只能从索引中进行 new 提交。
当然,工作树以正常的可读/可写方式保存您的文件,而不是某些特殊的Gitty格式(在提交本身和索引中使用)。
git reset
做什么(在正常模式下,--soft
,--mixed
和--hard
)最多可以完成三项工作:
HEAD
更改内容(通常是当前分支的存储哈希ID)。 总是执行此操作,但如果您使用HEAD
作为新值,则新值与旧值相同,因此实际上没有任何更改。 (如果--soft
,请停在此处。)--mixed
和--hard
。 (如果--mixed
,请在此处停止。)重置意味着将所有内容从(现在重新设置)HEAD
复制到索引中。 --hard
。重置意味着将所有内容从(现在重新设置)索引复制到工作树中。 现在,您提到要将事物回滚到提交A
期间的状态。正确定义事物是这里的问题。我们可以将分支名称指向提交A
:
A <-- master (HEAD)
\
B--C--D
这是通过第一个操作完成的,它始终发生:git reset <hash of A>
使得当前分支 - 大概master
- 指向提交A
,即使您使用--soft
。使用--mixed
或--hard
也会重新设置索引,或索引和工作树。
但是,这会立即取消保护B
,C
和D
。因此,您应首先通过添加名称(分支或标记)来保护它们以记住D
,这将保护它。然后D
会保护C
,这将保护B
。
与此同时,你在这里做的是创建一个分支名称&#34;向后移动&#34;。这没有任何内在的错误,但其他人和进程可能不会发生这种情况。通常只有分支名称&#34;向前移动&#34; (我们添加 new 提交并使分支名称指向最新的提交,这样我们就可以继续访问仍受保护的旧提交。所以可能不是正确的方法。 (如果使用这个分支名称的其他人都同意它以这种方式移动,那就没问题。如果没有,那就不行了。)
你提到git cherry-pick
。 git cherry-pick
所做的是将提交转换为更改(提交自身是完整快照,保存运行git commit
时索引中的内容)。然后它会尝试在任何地方应用更改。例如,假设在创建新名称git reset --hard
指向提交save
后,我们完全执行上述D
:
A <-- master (HEAD)
\
B--C--D <-- save
您现在可以运行git cherry-pick <hash-of-C>
或git cherry-pick save~1
(这两个都会识别提交C
)。然后,Git会将commit C
的内容与commit B
的内容进行比较。无论改变什么,Git都会尝试将这些更改改为索引和工作树的内容。如果一切顺利,Git将提交结果:
A--C' <-- master (HEAD)
\
B--C--D <-- save
在这里,我调用新提交C'
,因为它类似于C
:它使{em>更改与C
相同(但是对于不同的基础!),并且具有与C
相同的提交消息(通常使用&#34;从...添加#&#34;添加注释)。
当您完成挑选并且完全没有用于提交B
到D
时,您可以简单地删除保留它们并且易于查找的名称。在这一点上,当git gc
运行时,这三个提交确实有效(好吧,也许 1 )有资格被丢弃。
1 Git尝试非常努力不会丢失提交。因此,有很多方法可以快速收集提交 ,包括&#34; reflogs&#34;和年龄。默认情况下,不会修剪14天以下的提交; reflog条目中的提交也没有被修剪;和reflog条目本身通常至少持续30天。删除save
名称会抛出save
本身的reflog,但HEAD
和master
的reflog可能会保留一段时间的提交。
答案 1 :(得分:2)
快速回答您的问题:
- git reset --hard会从提交历史记录中删除任何内容吗?如果我将主人重置为A,那么B,C和D是否仍然会在回购中停留?
醇>
git reset --hard
不会从本地存储库中删除任何内容。它会移动您的分支指针,为您的下一次提交做准备。未指向的提交最终将被删除,但不会立即删除。您可以在git gc
例如,在git reset --hard A
之后,您可以立即恢复“失去的”#34;使用命令提交:git merge --ff-only D
。
就个人而言,在我做git reset --hard
之前,我喜欢标记当前的&#39;使用标记提交:git tag here
只是为了在完成我的历史记录后,我可以通过执行git diff here..HEAD
- git cherry-pick允许我做我上面描述的事情,还是我误解了它?
醇>
git cherry-pick
确实按照您的描述进行操作(有选择地应用补丁)
答案 2 :(得分:0)
仅供参考,因为这不是您的问题,您最好使用git rebase -i
而不是git reset --hard
。
然后,在rebase期间,您只需要删除不再需要的提交行。
因为在你重置之后,很可能你不会再看到提交了,除非你在sha1的某个地方写过,你将难以点赞它们(除非你看一下reflog)。