理解`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
也会重新设置索引,或索引和工作树。
但是,这立即解除了B
, C
和D
的保护。 所以你应该首先通过添加一个名称(分支或标签)来保护它们,以记住D
,这将保护它。 D
将保护C
,这将保护B
同时,你在这里做的是让分支名称“向后移动”。 这并没有什么本质上的错误,但是其他人和流程可能不会期望发生。 通常分支名称只是“向前移动”(我们添加新的提交并使分支名称指向最新的提交,这使我们可以继续访问仍然受保护的旧提交)。 所以这可能不是正确的做法。 (如果其他所有使用这个分支名称的人都同意这种移动方式,那就没问题,如果不是这样的话)。
你提到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会将提交C
的内容与提交B
的内容进行比较。 无论改变什么,Git都会尝试将这些更改转换为索引和工作树的内容。 如果一切顺利,Git将会提交结果:
A--C' <-- master (HEAD)
B--C--D <-- save
在这里,我调用新的提交C'
因为它类似于C
:它与C
(但是不同的基础!)做了相同的改变,并且具有与C
相同的提交信息(通常用“樱桃采摘。 ..“注释添加)。
当您完成樱桃采摘并且完全没有使用B
到D
提交时,您可以简单地删除保留它们并容易找到的名称。 在这一点上,当git gc
运行时,这三个提交确实会(当然,也许1)有资格被带出垃圾箱。
1Git非常努力不失去承诺。 因此,提交不会很快被收集的方式很多,包括“reflogs”和年龄。 未满14天的提交不会被默认修剪; 在reflog条目中的提交也不会被修剪; 并且reflog条目通常会保持至少30天。 删除save
名称会抛出reflog以save
自身,但HEAD
和master
的reflog可能会保留一段时间的提交。
要快速回答您的问题:
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 cherry-pick
确实做你所描述的(有选择地应用补丁)
仅供参考,因为这不是你的问题,你最好使用git rebase -i
不是git reset --hard
。
然后,在rebase期间,您只需删除不再需要的提交行。
因为在你重新设置之后,你有可能再也看不到提交了,除非你写了一个sha1,否则你将难以挑选它们(除非你看看reflog)。
链接地址: http://www.djcxy.com/p/18843.html下一篇: How to view the git history of a project and revert to an older commit?