StackOverflow 上投票数第二高的问题是……
如果您访问:https://stackoverflow.com/questions?sort= votes,您将看到 StackOverflow 上投票最多的问题。
不难发现,StackOverflow 上投票最多的 4 个问题中,有 3 个是关于 git 的!
这可能意味着两件事之一(或两者兼而有之):git 很难/不直观,或者 git 是最常用的技术之一。其实是两者兼而有之。
当你尝试理解 Git 时,你会注意到,为了从你(或你)那里得到它想要的东西,你需要了解底层的基础架构。这主要是因为你需要熟悉它的 API,而这些 API 与它的底层构建块紧密耦合。这正是造成大部分混乱的原因,有人说 API(在我们这里是 CLI)是其底层模型之上的一个漏洞百出的抽象。
废话不多说,让我们来分析一下 git 中投票最多的问题。
问题是如何撤消 git 中最近的提交?。
或者在图片中:
用户说他已经提交但尚未推送。
为什么您认为这在 git 中令人困惑?
- 您可能希望将 API
revert/undo/checkout
放入不同的提交中。 - 它已提交,您是否通过新的提交撤消?您可以回到过去吗?
- Git 说所有内容都会永久存储,那么如何真正撤消呢?
- 修复最近的提交(有一个命令可以做到这一点)。
- 回到历史的命令是什么?
- 总是有关于更改和撤消已推送内容的警告,我们有风险吗?
即使上述某些 API 存在,但选项太多,该选择哪一个才是最佳选择?
让我们回顾一下第一个答案
第一个答案建议他使用git reset
命令。理解 git reset 的最佳方式,就好像你可以让 HEAD 指针在 git 历史记录图中移动一样。
术语太多了,HEAD、重置、指针、图形
在 Git 中,我们进行提交。
每次提交都会添加到历史记录中,这算是一次及时的提交吧?
历史记录可以用一张图来描述,这意味着每次提交都指向另一个提交(如果是非合并提交,则指向父提交),因此我们得到了一个有向无环图,这又是一个术语。
所以答案是要求用户回到过去,这不正是他想要的吗?答案告诉他这样做:
git reset HEAD~
我们已经回到过去了!
HEAD 指向当前分支位置,~
指向减一指针,即 head 指向的当前提交的父级。
因此,通过使用 reset 命令,我们告诉 git:
Git 中你现在指向的任何位置(HEAD)都指向减 1 个位置,换句话说,指向前一个位置
git 很乐意为我们完成这件事。
请注意,由于我们没有为重置命令指定任何标志(如--hard),它不会改变我们工作目录中的任何内容,因此只有头部移动到那里。
因为我们想要恢复当前工作目录中的某些内容,并且我们只是在更改之前将 HEAD 向后移动,这意味着我们当前的目录现在充斥着我们想要恢复的更改。
因此,只需在工作目录中手动恢复本地更改并进行**另一次提交”
关于 HEAD 的几句话
我们说过,HEAD 指向的是当前检出分支的最后一个提交。正如gitglossary告诉我们的:
HEAD
当前分支。更详细地说:你的工作树通常源自 HEAD 所引用的树的状态。HEAD 是对仓库中某个 head 的引用,除非使用分离式 HEAD,在这种情况下,它直接引用任意提交。
我明白了,所以 HEAD 通常指向当前分支,除非不是!当不是时,它直接指向提交
但是这个提交的父级是什么?
由于我们将HEAD
一个提交移到了过去,这意味着该提交点之后的任何其他提交都不再被该图所指向 - 假设我们对 HEAD~ 父提交进行了新的提交。这意味着我们不仅在向 git 历史记录中追加内容,还在更改历史记录,获取父提交(在我们的例子中是 HEAD~,对于新的提交,我们为其赋予了与之前不同的子提交)。例如,如果我们将一个 HEAD~ 提交移到了过去并从那里开始提交,那么来自原始 HEAD(即 HEAD~ 的子提交)的提交将不再存在于标准历史记录中。因此,如果其他人拥有该子 HEAD 提交并使用它来创建新的提交(此 HEAD 提交的新子提交,您可以称之为 HEAD + 1),则会产生问题(当然,假设我们与他共享了我们的历史记录重写)。
因此,在我们执行 git reset 并在本地仓库中回退一个提交之后,我们通常会在工作目录中进行本地修复,然后提交。这个提交是一个新的提交,但它与我们正在修复的提交具有相同的父提交,因此对于我们和其他在更改之前已经拥有相同仓库快照的人来说,子提交可能有所不同。
因此,如果其他用户已经拥有我们刚刚从 repo 中“分离”出来的旧提交,并且我们将其强制放入远程 repo,这将导致他们得到“从上游 rebase 中恢复”和其他令人讨厌的东西。
我们可以将其推送到主存储库,但要小心,因为这会给其他人带来麻烦,当他们尝试拉取它并发现他们的父母与你的不同时会发生什么?
答案以“你本来可以这样做的git commit --amend
——对此我表示赞同,但这是另一个答案,我们会在另一篇文章中探讨。”
并非一切都失去了
最后需要注意的是,如果你想要恢复 git reset 的操作,可以使用 ,它reflog
像一个日志一样存储了你所有操作的日志。就像 git reset 一样,你也可以使用reflog
来回溯到之前的操作。具体方法如下:
git reset 'HEAD@{1}'
这告诉 git,嘿 git 记得我想要重置,好吧现在我希望你重置到我重置之前的一个时间点。
概括
我们已经学会了回顾过去,git reset
显然这是stackoverflow 中投票数第二多的问题。
附录 A - 一些练习
你想“证明”一下上面的一些说法吗?毕竟我们已经讨论了很多关于 HEAD、分支、提交的内容,让我们来看一下。我们有一个本地 git 仓库,让我们打印一下 head,它就在那里等着你打印。
Step 1: Let's see what is HEAD, let's print the HEAD file
$ cat .git/HEAD # => HEAD is a file in .git directory - yeah on the base dir, let's print it.
ref: refs/heads/master # => So head is simply this line of text, this looks like a branch, let's print it.
# Step 2: HEAD --> master => OK so let's see what is master file
$ cat .git/refs/heads/master # => Now we are printing what head points to, it should be the commit of the branch..
a15d580cc90d47a88f7f971914d45ff5a0e30eef # => So this is the commit which master points to. But how do we know this number is a commit?
# Step 3 : Print the commit content, after all it was pointed indirectly by HEAD
$ git cat-file -t a15d580cc90d47a88f7f971914d45ff5a0e30eef
commit # => Yes git is saying this SHA-1 is a commit. Was not persuaded yet? How about this:
$ git cat-file -p a15d580cc90d47a88f7f971914d45ff5a0e30eef
tree 7d80e5c527e9a1ec7f79f68386ce9710f1e048ce # It makes shadow like a commit.
parent ddf47ffcb19e2aee4839cae40e79fd7579fc637f # It has parents like a commit.
author Tom <tomer@email.com> 1537007909 +0300 # It has an author like a commit
committer Tom <tomer@email.com> 1537007909 +0300 # It has a committer.. like a commit
my commit message # => It talks like a commit.
# So its a commit! :)
# Step 4 : Did HEAD point to the tip of branch?
$ git log --oneline # => is the commit a15d580... really the head?
* a15d580 - (HEAD -> master) test (2 days ago) # => Yes a15d580 is indeed our latest commit where head points to!
* ddf47ff - (develop) added file to folder (9 days ago)
* 9d0e101 - hi (10 days ago)
基本上图片是这样的:
我们在上图中看到的是上面的 bash 命令,我们看到:
- HEAD 指向当前分支的尖端。
- 我们的当前分支引用指向我们分支中的提交,在我们的例子中它是最新的。
- 提交指向一棵树。
- 一棵树指向一个 blob 和树的列表(而树又指向一个树和 blob 的列表,树是目录)。
因此,当我们要求 git 移动到上一个提交时,git reset --soft HEAD~
我们已经要求 git 由 HEAD 指向的当前分支应该指向一个先前的提交,仅此而已。
让我们重置为之前的提交
# Step 5 : Do the reset and see it's effect
$ git reset --soft HEAD~ # Git please move HEAD to point to one previous commit.
# Step 6: Now what is the reset effect on HEAD
$ cat .git/HEAD
ref: refs/heads/master # => Didn't move! it points to the same place to the master.
$ cat refs/heads/master
ddf47ff (HEAD -> master, develop) added file to folder # => Aha so master branch pointer did move and HEAD simply points to our branch as the diagram shows.
9d0e101 hi
概括
通过附录 A,我们已经看到,我们可以深入研究 git,了解 HEAD 的含义,而不仅仅是通过定义、分支以及 git reset 的效果。阅读文档是一回事,而真正幸运的是,我们能够在 .git 目录中看到它,否则我们只需要相信文档,而这实际上并没有多大帮助。
文章来源:https://dev.to/tomerbendavid/the-second-most-voted-question-on-stackoverflow-is-60b