理解`git reset

假设我有一个Git仓库,对主人提供以下提交,顺序如下:A,B,C,D。我想将主人回滚到提交A后面的状态; 换句话说,抛弃B,C和D的变化。我很确定git reset --hard会做到这一点。 但是,我想选择性地重新应用一些丢弃的补丁( git cherry-pick是我想要的,对吧?)所以我的具体问题是:

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

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


  • 为了正确理解git reset ,你需要所有这些信息:

  • 在某种意义上,提交本身存在于任何分支名称之外。

    当你提交时,Git为它分配一个唯一的哈希ID。 您创建的新提交在其中存储了当前提交的任何提交的哈希ID。 我们可以使用这些散列ID将提交链接在一起:

    A <-B <-C <-D
    

    我们说每个提交都指向前一个提交。 (因为在A之前没有提交,所以它没有指向任何地方,如果在A之前有A ,那么想象链又回到最后,最终必须结束,因为没有Git仓库有无限次的提交,并且图表受到限制。)

  • 但是,分支名称(如master )会保留提交。 如果没有名称提交像D以上, D是在被清理和Git的垃圾收集器删除的危险,因为它似乎是无用的。 所以我们添加一个外部名称来指向D

    A <-B <-C <-D   <-- master
    

    现在Git知道D正在使用中。 由于D指向C ,所以Git知道C正在使用中,以此类推。

  • 特殊名称HEAD通常包含分支的名称。 分支名称本身(如master )具有标识某些特定提交( D )的通常角色,从而使D保持活跃状态​​。 HEAD这个名字用来告诉Git哪个分支名称被视为当前分支。

  • 当你使用git commit进行新的提交时,Git使用索引的内容来进行新的提交。 索引,也称为暂存区域,有时是缓存,位于当前(HEAD)提交和工作树之间。 因此,当前提交的每个文件都有(最多)三个版本: HEAD中的一个,索引中的一个和工作树中的一个。

    您可以在索引和工作树之间来回复制文件,并且可以将任何提交中的文件复制到索引中; 但提交是只读的,所以你不能从索引复制到现有的提交。 您只能从索引进行新的提交。

  • 当然,工作树会以正常的可读/可写方式保存文件,而不是某种特殊的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> --soft git reset <hash of A>使得当前分支 - 大概是master - --soft提交A ,即使你使用了--soft 。 使用--mixed--hard也会重新设置索引,或索引和工作树。

    但是,这立即解除了BCD的保护。 所以你应该首先通过添加一个名称(分支或标签)来保护它们,以记住D ,这将保护它。 D将保护C ,这将保护B

    同时,你在这里做的是让分支名称“向后移动”。 这并没有什么本质上的错误,但是其他人和流程可能不会期望发生。 通常分支名称只是“向前移动”(我们添加新的提交并使分支名称指向最新的提交,这使我们可以继续访问仍然受保护的旧提交)。 所以这可能不是正确的做法。 (如果其他所有使用这个分支名称的人都同意这种移动方式,那就没问题,如果不是这样的话)。

    你提到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会将提交C的内容与提交B的内容进行比较。 无论改变什么,Git都会尝试将这些更改转换为索引和工作树的内容。 如果一切顺利,Git将会提交结果:

    A--C'   <-- master (HEAD)
     
      B--C--D   <-- save
    

    在这里,我调用新的提交C'因为它类似于C :它与C (但是不同的基础!)做了相同的改变,并且具有与C相同的提交信息(通常用“樱桃采摘。 ..“注释添加)。

    当您完成樱桃采摘并且完全没有使用BD提交时,您可以简单地删除保留它们并容易找到的名称。 在这一点上,当git gc运行时,这三个提交确实会(当然,也许1)有资格被带出垃圾箱。


    1Git非常努力不失去承诺。 因此,提交不会很快被收集的方式很多,包括“reflogs”和年龄。 未满14天的提交不会被默认修剪; 在reflog条目中的提交也不会被修剪; 并且reflog条目通常会保持至少30天。 删除save名称会抛出reflog以save自身,但HEADmaster的reflog可能会保留一段时间的提交。


    要快速回答您的问题:

  • git reset --hard会从提交历史记录中删除任何内容吗? 如果我将主人重置为A,那么B,C和D仍然会在回购中闲逛吗?
  • git reset --hard不会从本地存储库中删除任何东西。 它会移动分支指针,为下一次提交做准备。 未指向的提交最终将被删除,但不会立即删除。 你可以在git gc的文档中阅读关于该主题的更多信息

    例如,在git reset --hard A ,您可以立即使用以下命令恢复您的“lost”提交: git merge --ff-only D

    就个人而言,在我进行git reset --hard ,我喜欢在这里标记'current'提交标记: git tag here以便在完成我的历史记录之后,我可以轻松地确定是否已完成所需的通过执行git diff here..HEAD产生副作用

  • git樱桃选择允许我做我上面描述的,还是我误解了它?
  • git cherry-pick确实做你所描述的(有选择地应用补丁)


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

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

    因为在你重新设置之后,你有可能再也看不到提交了,除非你写了一个sha1,否则你将难以挑选它们(除非你看看reflog)。

    链接地址: http://www.djcxy.com/p/18843.html

    上一篇: Understanding `git reset

    下一篇: How to view the git history of a project and revert to an older commit?