简单地说,“git重置”是做什么的?

我已经看到有趣的帖子解释了关于git reset的微妙之处。

不幸的是,我读得越多,就越觉得我不完全理解它。我来自SVN背景,Git是一个全新的范式。我很容易变得善变,但Git更具技术性。

我认为git reset接近hg revert,但似乎存在差异。

那么git reset到底是做什么的?请包括以下详细解释:

  • 选项--hard--soft--merge
  • 您在HEAD中使用的奇怪符号,例如HEAD^HEAD~1
  • 具体的用例和工作流程;
  • 对工作副本、HEAD和全局压力水平的影响。
268568 次浏览

一般来说,git reset的功能是获取当前分支并将其重置为指向其他地方,并可能带来索引和工作树。更具体地说,如果你的主分支(当前签出)是这样的:

- A - B - C (HEAD, master)

你意识到你希望master指向B,而不是C,你将使用git reset B将其移动到那里:

- A - B (HEAD, master)      # - C is still here, but there's no branch pointing to it anymore

题外话:这与结帐不同。如果你运行git checkout B,你会得到这个:

- A - B (HEAD) - C (master)

HEAD、工作树、索引都匹配B,但是master分支留在了C。如果你在此时进行新的提交D,你会得到这个,这可能不是你想要的:

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

请记住,重置不会进行提交,它只是更新一个分支(这是一个指向提交的指针)以指向不同的提交。其余的只是索引和工作树发生的细节。

用例

在下一节中,我将介绍git reset的许多主要用例。它确实可以用于各种各样的事情;共同的主线是,所有这些都涉及重置分支、索引和/或工作树以指向/匹配给定的提交。

要小心的事情

  • --hard会导致你真正失去工作。它会修改你的工作树。

  • git reset [options] commit可能会导致你(某种程度上)丢失提交。在上面的玩具示例中,我们丢失了C的提交。它仍然在存储库中,你可以通过查看git reflog show HEADgit reflog show master来找到它,但它实际上不再可以从任何分支访问。

  • Git会在30天后永久删除此类提交,但在此之前,您可以通过再次将分支指向它来恢复C(git checkout C; git branch <new branch name>)。

论点

套用手册页,最常见的用法是git reset [<commit>] [paths...]的形式,它将从给定的提交中将给定的路径重置为它们的状态。如果未提供路径,则重置整个树,如果未提供提交,则将其视为HEAD(当前提交)。这是git命令中的常见模式(例如签出、diff、log,尽管确切的语义学有所不同),所以这不应该太令人惊讶。

例如,git reset other-branch path/to/foo将path/to/foo中的所有内容重置为其在其他分支中的状态,git reset -- .将当前目录重置为其在HEAD中的状态,简单的git reset将所有内容重置为其在HEAD中的状态。

主工作树和索引选项

有四个主要选项来控制重置期间工作树和索引发生的情况。

记住,索引是git的“暂存区”——当你说git add准备提交时,它是事情发生的地方。

  • --hard使所有内容都与你重置的提交相匹配。这可能是最容易理解的。你所有的本地更改都会被破坏。一个主要用途是吹走你的工作,但不切换提交:git reset --hard的意思是git reset --hard HEAD,即不更改分支,但摆脱所有本地更改。另一个简单的方法是将分支从一个地方移动到另一个地方,并保持索引/工作树同步。这是一个真正会让你失去工作的方法,因为它修改了你的工作树。在运行任何reset --hard之前,非常确定你想丢弃本地工作。

  • --mixed是默认值,即git reset意味着git reset --mixed。它重置索引,但不是工作树。这意味着你所有的文件都完好无损,但是原始提交和你重置为的提交之间的任何差异都将显示为具有git状态的本地修改(或未跟踪文件)。当你意识到你做了一些糟糕的提交,但你想保留你所做的所有工作,这样你就可以修复它并重新开始。为了提交,你必须再次将文件添加到索引中(git add ...)。

  • --soft不触及索引工作树。你所有的文件都和--mixed一样完好无损,但是所有的更改都显示为changes to be committed,状态为git(即在准备提交时签入)。当你意识到你做了一些糟糕的提交,但工作都很好的时候使用这个-你需要做的就是以不同的方式重新提交。索引没有被触及,所以如果你愿意,你可以立即提交-生成的提交将具有与你重置前相同的内容。

  • --merge是最近添加的,旨在帮助您中止失败的合并。这是必要的,因为git merge实际上会让您尝试使用脏工作树(本地修改的树)进行合并,只要这些修改位于不受合并影响的文件中。git reset --merge重置索引(就像--mixed一样-所有更改都显示为本地修改),并重置受合并影响的文件,但不影响其他文件。这将有望将所有内容恢复到错误合并之前的状态。您通常会将其用作git reset --merge(表示git reset --merge HEAD),因为您只想重置合并,而不是实际移动分支。(HEAD尚未更新,因为合并失败)

    更具体地说,假设你修改了文件A和B,并且你试图在一个修改了文件C和D的分支中合并。由于某种原因,合并失败了,你决定中止它。你使用git reset --merge。它将C和D带回到它们在HEAD中的样子,但将你的修改单独留给A和B,因为它们不是尝试合并的一部分。

想了解更多?

我确实认为man git reset在这方面非常好——也许你确实需要一点git的工作方式才能真正了解它们。特别是,如果你花时间仔细阅读它们,那些详细说明索引和工作树中所有各种选项和案例的文件状态的表格非常非常有帮助。(但是,它们非常密集——它们以非常简洁的形式传达了大量上述信息。)

奇怪的符号

你提到的“奇怪符号”(HEAD^HEAD~1)只是指定提交的简写,而不必使用像3ebe3f6这样的哈希名称。它在git-dev-parse手册页的“指定修订”部分中有完整的记录,有很多示例和相关语法。插入符号和波浪号实际上意味着不同的事情

  • HEAD~HEAD~1的缩写,表示提交的第一个父级。HEAD~2表示提交的第一个父级的第一个父级。可以将HEAD~n视为“HEAD之前的n次提交”或“HEAD的第n代祖先”。
  • EYZ0(或EYZ1)也表示提交的第一个父级。EYZ2表示提交的第一个父级。请记住,正常的合并提交有两个父级——第一个父级是合并入的提交,第二个父级是被合并的提交。一般来说,合并实际上可以有任意多个父级(章鱼合并)。
  • ^~运算符可以串在一起,例如HEAD~3^2HEAD的第三代祖先的第二个父级,HEAD^^2HEAD的第一个父级的第二个父级,甚至HEAD^^^,相当于HEAD~3

插入符号和波浪号

请记住,在git中,您有:

  • HEAD指针,它告诉你你正在做什么提交
  • 工作树,表示系统上文件的状态
  • 集结区(也称为索引),它“阶段”更改,以便以后可以一起提交

请包括关于以下方面的详细解释:

--hard--soft--merge

按危险性递增的顺序:

  • --soft移动HEAD,但不触及暂存区域或工作树。
  • --mixed移动HEAD并更新暂存区域,但不更新工作树。
  • --merge移动HEAD,重置暂存区域,并尝试将工作树中的所有更改移动到新的工作树中。
  • --hard移动HEAD将您的暂存区域和工作树调整为新的HEAD,丢弃所有内容。

具体的用例和工作流程;

  • 当你想要移动到另一个提交并修补东西而不会“丢失你的位置”时,使用--soft

--

# git reset --soft example
touch foo                            // Add a file, make some changes.
git add foo                          //
git commit -m "bad commit message"   // Commit... D'oh, that was a mistake!
git reset --soft HEAD^               // Go back one commit and fix things.
git commit -m "good commit"          // There, now it's right.

--

  • 当您想查看另一个提交中的内容时,请使用--mixed(默认值),但您不想丢失已有的任何更改。

  • 当您想移动到一个新位置,但将已经进行的更改合并到工作树中时,请使用--merge

  • 使用--hard清除所有内容并在新提交时开始新的石板。

博客progit中的帖子重置揭秘git resetgit checkout给出了非常不用动脑的解释。

在那篇文章的顶部进行了所有有用的讨论之后,作者将规则简化为以下简单的三个步骤:

基本上就是这样。reset命令以特定的顺序覆盖这三个树,当你告诉它停止时。

  1. 移动HEAD指向的任何分支(如果--soft停止)
  2. 然后,使索引看起来像这样(除非--hard,否则停在这里)
  3. 然后,使工作目录看起来像这样

还有--merge--keep选项,但我宁愿现在让事情变得简单-这将是另一篇文章。

当你提交一些东西到git时,你首先必须暂存(添加到索引中)你的更改。这意味着你必须在git将它们视为提交的一部分之前,git添加所有你想包含在此提交中的文件。让我们首先看看git仓库的图像: 在此处输入图片描述

所以,现在很简单。我们必须在工作目录中工作,创建文件、目录等等。这些更改是未跟踪的更改。要跟踪它们,我们需要使用git add命令将它们添加到git索引中。一旦它们被添加到git索引中。如果我们想将其推送到git存储库,我们现在可以提交这些更改。

但是突然我们在提交时知道我们有一个额外的文件,我们在索引中添加的文件不需要推送到git存储库中。这意味着我们不希望该文件在索引中。 现在的问题是如何从git索引中删除该文件,由于我们使用git add将它们放在索引中,因此使用gitrm是合乎逻辑的?错误!gitrm将简单地删除文件并将删除添加到索引中。那么现在该怎么办:

使用:-

git重置

它清除您的索引,保持您的工作目录不变。(简单地取消所有内容)。

它可以与多个选项一起使用。 有三个主要选项可用于git重置:--hard、--soft和--混合。除了重置时的HEAD指针外,这些选项还会影响get的重置。

首先,--硬重置所有内容。您当前的目录将与您一直遵循该分支的目录完全相同。工作目录和索引更改为该提交。这是我最常用的版本。git重置类似于svn恢复

接下来,完全相反的-软不会重置工作树或索引。它只移动HEAD指针。这会让你的当前状态在目录中保留与你要切换到的提交不同的任何更改,并“分阶段”提交。如果你在本地提交但尚未将提交推送到git服务器,你可以重置到上一次提交,并使用好的提交消息重新提交。

最后,--混合重置索引,但不重置工作树。所以更改都还在那里,但是是“未暂存的”,需要git add'ed或执行失败git-a。我们有时会使用这个,如果我们提交的内容超过了我们使用git提交-a的意图,我们可以使用git重置-混合来返回提交,添加我们想要提交的内容并只提交这些内容。

git恢复和git重置的区别:-


简而言之,git重置“修复未犯的错误”的命令,git恢复"修正犯的错误"的命令。

这意味着如果我们在某些更改中犯了一些错误并提交并将其推送到git repo,那么git恢复是解决方案。如果我们在推送/提交之前发现了相同的错误,我们可以使用git重置来解决问题。

我希望它能帮助你摆脱困惑。

请注意,这是一个简化的解释,旨在作为寻求理解此复杂功能的第一步。

对于想要在每个命令之后可视化其项目状态的视觉学习者可能会有所帮助:


对于那些使用颜色打开的终端的人 (git config--globalcolor.ui自动):

git reset --soft A,你会看到B和C的东西是绿色的(分阶段并准备提交)

git reset --mixed A(或git reset A),你会看到B和C的东西是红色的(未暂存并准备暂存(绿色),然后提交)

git reset --hard A,你将不再在任何地方看到B和C的更改(就好像它们从未存在过一样)


或者对于那些使用“Tower”或“SourceTree”等GUI程序的人

git reset --soft A,你会在“分阶段文件”区域看到B和C的东西准备提交

git reset --mixed A(或git reset A),您将在“未暂存文件”区域看到B和C的内容,准备移动到暂存然后提交

git reset --hard A,你将不再在任何地方看到B和C的更改(就好像它们从未存在过一样)

太长别读

git reset将暂存重置为最后一次提交。使用--hard也将工作目录中的文件重置为最后一次提交。

较长版本

但这显然过于简单,因此有许多相当冗长的答案。对我来说,在撤消更改的上下文中阅读git reset更有意义。例如。看到这个:

如果git恢复是撤消更改的“安全”方法,您可以考虑git 重置作为危险的方法。当您使用git重置撤消(以及 提交不再被任何ref或reflg引用),有 没有办法检索原始副本-这是一个永久撤消。护理必须 使用此工具时请注意,因为它是唯一可能会丢失您的工作的Git命令之一。

来自https://www.atlassian.com/git/tutorials/undoing-changes/git-reset

和这个

在提交级别,重置是一种将分支的尖端移动到不同提交的方法。这可用于从当前分支中删除提交。

来自https://www.atlassian.com/git/tutorials/resetting-checking-out-and-reverting/commit-level-operations

签出将头部指向特定的提交。

Reset将分支指向特定的提交。(分支是指向提交的指针。)

顺便说一句,如果你的头没有指向一个也被分支指向的提交,那么你就有一个分离的头。(结果是错误的。见评论…)

我不总是做git重置,但当我这样做时,我看看这个:

* 444668f (HEAD -> main) C
|
* c3739b7 B
|
* 207e8a1 A
|
* 38fab46 Initial commit




git reset --hard 207e8




* 207e8a1 (HEAD -> main) A
|
* 38fab46 Initial commit




To retrieve the changes, use --soft instead

HEAD移动到Amain也是,因为HEAD指向main)。git reset不会“重置”BC。您仍然可以使用A2的A0选项看到BC

git log --graph --oneline --all --reflog

警告

在你做git reset之前,

  • 如果您有非阶段性变更

    • 使用--hard,它将获得丢弃
    • 使用--mixed默认),它将获得混合并进行分阶段更改,并且检索到的提交更改
  • 如果您有阶段性变更

    • 使用--hard,它将获得丢弃
    • 使用--mixed,它将获得混合,其中包含未暂存的更改和检索到的提交更改
    • 使用--soft,它将获得混合以及检索到的提交更改

要摆脱它们,您可以使用git stash,但我更喜欢创建一个新分支并为那里的分阶段和未分阶段更改创建单独的提交。然后在我需要它们时使用git rebase+git reset