我一直认为 git reset
和 git checkout
是相同的,因为它们都将项目带回到特定的提交。但是,我觉得它们不可能完全相同,因为那将是多余的。两者之间的实际区别是什么?我有点困惑,因为 svn 只有 svn co
才能恢复提交。
添加
VonC 和 Charles 很好地解释了 git reset
和 git checkout
之间的区别。我目前的理解是 git reset
将所有更改还原回特定提交,而 git checkout
或多或少为分支做准备。我发现以下两个图表对理解这一点非常有用:
https://i.stack.imgur.com/C4BCo.png
添加 3
从 http://think-like-a-git.net/sections/rebase-from-the-ground-up/using-git-cherry-pick-to-simulate-git-rebase.html 开始,结帐和重置可以模拟变基。
https://i.stack.imgur.com/EYijy.png
git checkout bar
git reset --hard newbar
git branch -d newbar
https://i.stack.imgur.com/6F3ZK.png
-- files
变体可能没问题;我不确定。)该图看起来主要区别在于它们是否影响索引或 WD。请参阅我的回答。第 2 和第 3 图表对于查看真正的差异非常有帮助。第 4 和第 5 图表有助于检查您是否理解这些命令的作用,但不会真正帮助您到达那里。
think-like-a-git.net
文章中提供)以防止数据丢失。
git reset 专门用于更新索引,移动 HEAD。
git checkout 是关于更新工作树(到索引或指定树)。仅当您签出分支时,它才会更新 HEAD(如果没有,您最终会得到一个分离的 HEAD)。 (实际上,使用 Git 2.23 Q3 2019,这将是 git restore,不一定是 git checkout)
相比之下,由于 svn 没有索引,只有工作树,svn checkout
会将给定修订版复制到单独的目录中。
git checkout
更接近的等价物是:
svn update(如果你在同一个分支,意思是同一个SVN URL)
svn 开关(例如,如果您从另一个 SVN 存储库 URL 签出相同的分支)
所有这三个工作树修改(svn checkout
、update
、switch
)在 git 中只有一个命令:git checkout
。
但是由于 git 也有索引的概念(即“暂存区”之间的repo 和工作树),您还有 git reset
。
Thinkeye 提及in the comments文章“Reset Demystified ”。
例如,如果我们有两个分支,“master”和“develop”指向不同的提交,并且我们当前处于“develop”(所以 HEAD 指向它)并且我们运行 git reset master,“develop”本身现在将指向与“master”相同的提交。另一方面,如果我们改为运行 git checkout master,'develop' 不会移动,HEAD 本身会移动。 HEAD 现在将指向“master”。因此,在这两种情况下,我们都将 HEAD 移动到提交 A,但我们这样做的方式非常不同。 reset 将移动 HEAD 指向的分支,checkout 将 HEAD 本身移动到另一个分支。
https://i.stack.imgur.com/M6HAw.png
不过,在这些方面:
LarsH 添加 in the comments:
但是,此答案的第一段具有误导性:“git checkout ...仅在您签出分支时才会更新HEAD(如果没有,您最终会得到一个分离的HEAD)”。不正确:即使您签出不是分支的提交, git checkout 也会更新 HEAD(是的,您最终会得到一个分离的 HEAD,但它仍然得到了更新)。 git checkout a839e8f 更新 HEAD 以指向提交 a839e8f。
@LarsH 是正确的。第二个项目符号对 HEAD 的内容存在误解,只有在您签出分支时才会更新 HEAD。 HEAD 像影子一样随处可见。签出一些非分支引用(例如,标签)或直接提交,将移动 HEAD。分离的头部并不意味着你已经从 HEAD 分离,这意味着头部从分支 ref 分离,你可以从例如 git log --pretty=format:"%d" -1 中看到。附加的头部状态将以 (HEAD -> 开头,分离的仍将显示 (HEAD,但不会有指向分支 ref 的箭头。
在最简单的形式中,reset
在不触及工作树的情况下重置索引,而 checkout
在不触及索引的情况下更改工作树。
重置索引以匹配 HEAD
,单独保留工作树:
git reset
从概念上讲,这会将索引签出到工作树中。要让它真正执行任何操作,您必须使用 -f
强制它覆盖任何本地更改。这是确保“无参数”形式不具有破坏性的安全功能:
git checkout
一旦开始添加参数,确实存在一些重叠。
checkout
通常与分支、标签或提交一起使用。在这种情况下,它将重置 HEAD
和给定提交的索引,并将索引签出到工作树中。
此外,如果您将 --hard
提供给 reset
,您可以要求 reset
覆盖工作树以及重置索引。
如果您当前有一个已签出的分支,那么当您提供替代分支或提交时,reset
和 checkout
之间存在重大差异。 reset
将更改当前分支以指向选定的提交,而 checkout
将不理会当前分支,但将检出提供的分支或提交。
reset
和 commit
的其他形式涉及提供路径。
如果您提供 reset
的路径,则无法提供 --hard
,并且 reset
只会将提供的路径的索引版本更改为提供的提交中的版本(如果您未指定提交,则为 HEAD
)。
如果您向 checkout
提供路径,例如 reset
,它将更新所提供路径的索引版本以匹配所提供的提交(或 HEAD
),但它始终会将所提供路径的索引版本检出到工作树中.
恢复更改时的一个简单用例: 1. 如果要撤消已修改文件的暂存,请使用重置。 2. 如果您想放弃对未暂存文件的更改,请使用 checkout。
简而言之,主要区别在于 reset
移动当前分支引用,而 checkout
没有(它移动 HEAD)。
正如 Pro Git 书籍在 Reset Demystified 下所解释的那样,
reset 要做的第一件事就是移动 HEAD 指向的东西。这与更改 HEAD 本身不同(这是 checkout 所做的); reset 移动 HEAD 指向的分支。这意味着如果 HEAD 设置为 master 分支(即您当前在 master 分支上),运行 git reset 9e5e6a4 将首先使 master 指向 9e5e6a4。 [重点补充]
另请参阅 VonC 对同一篇文章中 very helpful text and diagram excerpt 的回答,我不会在此重复。
当然,还有很多关于 checkout
和 reset
可以对索引和工作树产生什么影响的详细信息,具体取决于使用的参数。这两个命令之间可能有很多相似之处和不同之处。但在我看来,最关键的区别是它们是否移动了当前分支的尖端。
简短的助记符:
git reset HEAD : index = HEAD
git checkout : file_tree = index
git reset --hard HEAD : file_tree = index = HEAD
这两个命令(reset 和 checkout)是完全不同的。
checkout X
不是 reset --hard X
如果 X 是分支名称,checkout X
将更改当前分支,而 reset --hard X
不会。
以下是对歧义的澄清:
git checkout 会将 HEAD 移动到另一个提交(也可以是使用分支名称的更改),但是:在您所在的任何分支上,指向该分支尖端的指针(例如,“main”)将保持不变(因此您可能最终处于分离的头部状态)。此外,暂存区和工作目录将保持不变(处于结帐前的相似状态)。
在任何分支上,指向该分支尖端的指针(例如,“main”)将保持不变(因此您可能最终处于分离的头部状态)。
此外,暂存区和工作目录将保持不变(处于结帐前的相似状态)。
例子:
git checkout 3ad2bcf <--- checkout to another commit
git checkout another-branch <--- checkout to another commit using a branchname
git reset 也移动了 HEAD,但是同样有两个不同之处:它也移动了指向当前分支尖端的提交的指针。例如,假设指向当前分支的指针名为“main”,然后执行 git-reset,现在主指针将指向另一个提交,HEAD 也将指向该提交(基本上, HEAD 通过指向主指针间接指向该提交,它仍然是附加的头(!),但在这里没有任何区别)。 Git-reset 不一定会使暂存区和工作目录保持在执行重置之前的相同状态。如您所知,有三种类型的重置:软、混合(默认)和硬:通过软重置,暂存区和工作目录都保持在重置之前的状态(类似于 checkout in这方面,但不要忘记区别#1)。混合重置是默认类型的重置,除了差异#1,暂存区建议的下一次提交(基本上你已经 git 添加的内容)也将设置为新指向的 HEAD犯罪。但是在工作目录中,所有文件仍然会有你对它们的最新编辑(这就是为什么这种类型的重置是默认的,这样你就不会丢失你的工作)。通过硬重置,除了差异 #1 之外,所有三棵树 HEAD、staging-area 和 ALSO 工作目录都将更改为新指向的 HEAD 提交。
它也会移动指向当前分支尖端的提交的指针。例如,假设指向当前分支的指针名为“main”,然后执行 git-reset,现在主指针将指向另一个提交,HEAD 也将指向该提交(基本上, HEAD 通过指向主指针间接指向该提交,它仍然是附加的头(!),但在这里没有任何区别)。
Git-reset 不一定会使暂存区和工作目录保持在执行重置之前的相同状态。如您所知,有三种类型的重置:软、混合(默认)和硬:通过软重置,暂存区和工作目录都保持在重置之前的状态(类似于 checkout in这方面,但不要忘记区别#1)。混合重置是默认类型的重置,除了差异#1,暂存区建议的下一次提交(基本上你已经 git 添加的内容)也将设置为新指向的 HEAD犯罪。但是在工作目录中,所有文件仍然会有你对它们的最新编辑(这就是为什么这种类型的重置是默认的,这样你就不会丢失你的工作)。通过硬重置,除了差异 #1 之外,所有三棵树 HEAD、staging-area 和 ALSO 工作目录都将更改为新指向的 HEAD 提交。
使用软重置,暂存区和工作目录都保持在重置之前的状态(在这方面类似于 checkout,但不要忘记区别#1)。
混合重置是默认类型的重置,除了差异#1,暂存区建议的下一次提交(基本上你已经 git 添加的内容)也将设置为新指向的 HEAD犯罪。但是在工作目录中,所有文件仍然会有你对它们的最新编辑(这就是为什么这种类型的重置是默认的,这样你就不会丢失你的工作)。
通过硬重置,除了差异 #1 之外,所有三棵树 HEAD、staging-area 和 ALSO 工作目录都将更改为新指向的 HEAD 提交。
例子:
git reset --soft 3ad2bcf
git reset da3b47
git reset
是关于修改分支“标签”并可选地更新索引或工作树作为副作用。git checkout
是关于更新工作树和切换当前“选定”的分支(HEAD
)。git reset
是关于HEAD
的 100%。它甚至可以在分离的 HEAD 模式 (stackoverflow.com/a/3965714/6309) 下工作,这意味着存在 no 分支 (!)。 git checkout 也可以在分离的 HEAD 模式下工作,或者可用于在分离的 HEAD 模式下检出 SHA1:在这种情况下再次不涉及分支。git checkout a839e8f
更新 HEAD 以指向提交a839e8f
。