我看过一些有趣的帖子,解释git重置的微妙之处。

不幸的是,我读得越多,就越觉得我并没有完全理解它。我有SVN的背景,Git是一个全新的范例。我很容易得到mercurial,但是Git的技术含量更高。

我认为git重置接近hg恢复,但似乎有区别。

git重置到底是做什么的?请详细说明:

选项——硬、软和合并; 你对HEAD使用的奇怪符号,如HEAD^和HEAD~1; 具体的用例和工作流程; 对工作副本、HEAD和整体压力水平的影响。


当前回答

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

重置在特定提交时指向一个分支。(分支是指向提交的指针。)

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

其他回答

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

- A - B - C (HEAD, master)

并且你意识到你想要master指向B,而不是C,你将使用git重置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状态。HEAD,工作树,索引都匹配B,但是主分支被留在了c。如果你在这个时候做了一个新的提交D,你会得到这个,这可能不是你想要的:

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

记住,reset不会进行提交,它只是更新一个分支(一个指向提交的指针)以指向不同的提交。剩下的就是索引和工作树的细节。

用例

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

需要注意的事情

--hard can cause you to really lose work. It modifies your work tree. git reset [options] commit can cause you to (sort of) lose commits. In the toy example above, we lost commit C. It's still in the repo, and you can find it by looking at git reflog show HEAD or git reflog show master, but it's not actually accessible from any branch anymore. Git permanently deletes such commits after 30 days, but until then you can recover C by pointing a branch at it again (git checkout C; git branch <new branch name>).

参数

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

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

主要工作树和索引选项

有四个主要选项来控制重置期间工作树和索引的变化。

请记住,索引是git的“暂存区”——当你说git添加准备提交时,它就是东西所在的地方。

--hard makes everything match the commit you've reset to. This is the easiest to understand, probably. All of your local changes get clobbered. One primary use is blowing away your work but not switching commits: git reset --hard means git reset --hard HEAD, i.e. don't change the branch but get rid of all local changes. The other is simply moving a branch from one place to another, and keeping index/work tree in sync. This is the one that can really make you lose work, because it modifies your work tree. Be very very sure you want to throw away local work before you run any reset --hard. --mixed is the default, i.e. git reset means git reset --mixed. It resets the index, but not the work tree. This means all your files are intact, but any differences between the original commit and the one you reset to will show up as local modifications (or untracked files) with git status. Use this when you realize you made some bad commits, but you want to keep all the work you've done so you can fix it up and recommit. In order to commit, you'll have to add files to the index again (git add ...). --soft doesn't touch the index or work tree. All your files are intact as with --mixed, but all the changes show up as changes to be committed with git status (i.e. checked in in preparation for committing). Use this when you realize you've made some bad commits, but the work's all good - all you need to do is recommit it differently. The index is untouched, so you can commit immediately if you want - the resulting commit will have all the same content as where you were before you reset. --merge was added recently, and is intended to help you abort a failed merge. This is necessary because git merge will actually let you attempt a merge with a dirty work tree (one with local modifications) as long as those modifications are in files unaffected by the merge. git reset --merge resets the index (like --mixed - all changes show up as local modifications), and resets the files affected by the merge, but leaves the others alone. This will hopefully restore everything to how it was before the bad merge. You'll usually use it as git reset --merge (meaning git reset --merge HEAD) because you only want to reset away the merge, not actually move the branch. (HEAD hasn't been updated yet, since the merge failed) To be more concrete, suppose you've modified files A and B, and you attempt to merge in a branch which modified files C and D. The merge fails for some reason, and you decide to abort it. You use git reset --merge. It brings C and D back to how they were in HEAD, but leaves your modifications to A and B alone, since they weren't part of the attempted merge.

想知道更多吗?

我确实认为男人git重置真的很好-也许你确实需要一点git工作方式的感觉,让他们真正沉浸其中。特别是,如果你花时间仔细阅读它们,那些详细描述索引和工作树中所有不同选项和情况的文件状态的表是非常非常有用的。(但是,是的,它们非常密集——它们以非常简洁的形式传达了大量上述信息。)

奇怪的符号

您提到的“奇怪的符号”(HEAD^和HEAD~1)只是指定提交的一种简写,而不必使用像3ebe3f6这样的散列名称。它在git-rev-parse手册页的“指定修订”部分中有完整的文档,其中有许多示例和相关语法。插入号和波浪号实际上有不同的含义:

HEAD~ is short for HEAD~1 and means the commit's first parent. HEAD~2 means the commit's first parent's first parent. Think of HEAD~n as "n commits before HEAD" or "the nth generation ancestor of HEAD". HEAD^ (or HEAD^1) also means the commit's first parent. HEAD^2 means the commit's second parent. Remember, a normal merge commit has two parents - the first parent is the merged-into commit, and the second parent is the commit that was merged. In general, merges can actually have arbitrarily many parents (octopus merges). The ^ and ~ operators can be strung together, as in HEAD~3^2, the second parent of the third-generation ancestor of HEAD, HEAD^^2, the second parent of the first parent of HEAD, or even HEAD^^^, which is equivalent to HEAD~3.

记住在git中你有:

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

请详细说明: ——硬,——软,——融合;

按危险程度递增:

-轻轻移动头部,但不触及集结区或工作树。 mixed移动HEAD并更新暂存区,但不更新工作树。 ——merge移动HEAD,重置暂存区域,并尝试将工作树中的所有更改移动到新的工作树中。 -hard移动HEAD,调整你的集结区和工作树到新的HEAD,扔掉所有东西。

具体的用例和工作流;

当你想要转移到另一个地方,修补事情而不“失去你的位置”时,使用-软。你很少需要这个。

--

# 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(这是默认值)。 使用—合并当你想移动到一个新的点,但合并你已经有到工作树的变化。 使用——在新的提交时很难清除所有内容并重新开始。

我不总是做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移到A (main也是,因为HEAD指向main)。git reset不会“重置”B和C。你仍然可以使用git log的——reflog选项看到B和C:

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

警告

在你做git重置之前,

如果你有非阶段性的变化: 如果用力,就会被丢弃 使用——mixed(默认),它将与阶段性更改混合,检索到的更改将提交更改 如果你有阶段性的变化: 如果用力,就会被丢弃 使用——mixed,它将与非阶段更改和检索到的提交更改混合 使用——soft,它将与检索到的提交更改混合在一起

要摆脱它们,你可以使用git stash,但我更喜欢只创建一个新分支,并为那里的阶段性和非阶段性更改创建一个单独的提交。然后使用git rebase + git重置当我需要他们回来。

博客Pro Git中的Reset Demystified给出了一个关于Git重置和Git checkout的非常简单的解释。

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

基本上就是这样。reset命令以特定顺序覆盖这三棵树,当您命令它停止时停止。 移动HEAD指向的分支(如果—软) 然后,让索引看起来像这样(停止在这里,除非——很难) 然后,让工作目录看起来像这样 还有“合并”和“保留”选项,但我现在宁愿让事情简单一些——这将是另一篇文章。

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

重置在特定提交时指向一个分支。(分支是指向提交的指针。)

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