理解`git reset --hard`

时间:2017-06-22 22:20:54

标签: git

假设我有一个Git repo,其中包含以下提交给主服务器:A,B,C,D。我想将主服务器回滚到它在以下提交A中的状态;换句话说,放弃B,C和D的变化。我很确定git reset --hard会这样做。但是,我想有选择地重新应用一些丢弃的补丁(git cherry-pick是我想要的,对吗?)所以我的具体问题是:

  1. git reset --hard是否会从提交历史记录中删除任何内容?如果我将主人重置为A,那么B,C和D是否仍会在回购中闲置?

  2. git cherry-pick允许我做我上面描述的事情,还是我误解了它?

3 个答案:

答案 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)最多可以完成三项工作:

  1. 通过HEAD更改内容(通常是当前分支的存储哈希ID)。 总是执行此操作,但如果您使用HEAD作为新值,则新值与旧值相同,因此实际上没有任何更改。 (如果--soft,请停在此处。)
  2. 重新设置索引。此部分是可选的:仅适用于--mixed--hard。 (如果--mixed,请在此处停止。)重置意味着将所有内容从(现在重新设置)HEAD复制到索引中。
  3. 重新设置工作树。这部分是可选的:它仅适用于--hard。重置意味着将所有内容从(现在重新设置)索引复制到工作树中。
  4. 现在,您提到要将事物回滚到提交A期间的状态。正确定义事物是这里的问题。我们可以将分支名称指向提交A

    A   <-- master (HEAD)
     \
      B--C--D
    

    这是通过第一个操作完成的,它始终发生:git reset <hash of A>使得当前分支 - 大概master - 指向提交A,即使您使用--soft 。使用--mixed--hard也会重新设置索引,或索引和工作树。

    但是,这会立即取消保护BCD。因此,您应首先通过添加名称(分支或标记)来保护它们以记住D,这将保护它。然后D会保护C,这将保护B

    与此同时,你在这里做的是创建一个分支名称&#34;向后移动&#34;。这没有任何内在的错误,但其他人和进程可能不会发生这种情况。通常只有分支名称&#34;向前移动&#34; (我们添加 new 提交并使分支名称指向最新的提交,这样我们就可以继续访问仍受保护的旧提交。所以可能不是正确的方法。 (如果使用这个分支名称的其他人都同意它以这种方式移动,那就没问题。如果没有,那就不行了。)

    你提到git cherry-pickgit 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;添加注释)。

    当您完成挑选并且完全没有用于提交BD时,您可以简单地删除保留它们并且易于查找的名称。在这一点上,当git gc运行时,这三个提交确实有效(好吧,也许 1 )有资格被丢弃。

    1 Git尝试非常努力不会丢失提交。因此,有很多方法可以快速收集提交 ,包括&#34; reflogs&#34;和年龄。默认情况下,不会修剪14天以下的提交; reflog条目中的提交也没有被修剪;和reflog条目本身通常至少持续30天。删除save名称会抛出save本身的reflog,但HEADmaster的reflog可能会保留一段时间的提交。

答案 1 :(得分:2)

快速回答您的问题:

  
      
  1. git reset --hard会从提交历史记录中删除任何内容吗?如果我将主人重置为A,那么B,C和D是否仍然会在回购中停留?
  2.   

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

轻松确定我是否已经产生了所需的副作用
  
      
  1. git cherry-pick允许我做我上面描述的事情,还是我误解了它?
  2.   

git cherry-pick确实按照您的描述进行操作(有选择地应用补丁)

答案 2 :(得分:0)

仅供参考,因为这不是您的问题,您最好使用git rebase -i而不是git reset --hard

然后,在rebase期间,您只需要删除不再需要的提交行。

因为在你重置之后,很可能你不会再看到提交了,除非你在sha1的某个地方写过,你将难以点赞它们(除非你看一下reflog)。