git reset --soft 是否只更改哈希值?

Does git reset --soft only change hash values?

在这篇post中作者很好的解释了git重置的3个选项(soft, mixed, hard): https://www.atlassian.com/git/tutorials/undoing-changes/git-reset

他使用 "three trees" 作为设备来表示 1) 工作树,2) 暂存区,3) "commit history/commit refs":

--hard 重置 (1),(2),(3);

--mixed 重置 (2) 和 (3);

--soft 仅更改 (3).

(3)到底代表什么我有点不清楚。我可以看到如何使用 git reset --soft 来更改分支指向的提交。但我不知道为什么这里要用历史这个词。除了分支和 HEAD 都引用的提交之外,到底修改了什么?

编辑:特别是 git reset --soft <SHA1> 编辑 i) .git/refs/heads/master 文件中的哈希值和 ii) .git/HEAD 中的哈希值] 没有别的吗?

As writer wrote in topic, for understanding this you need to understand git internals.

我会尽量以最好的方式解释。

在git中,提交是带有树引用的链表,其中包含对 blob(文件)和树的引用。

>    C1<------C2<--------C3
>     |        |          |
>     V        V          V
>     T1       T2         T3
>     |       / \         /\
>     V      /   v       /  \
>     B1 <--     B2 <---    B1'

如上所示

  • 在提交 1 中(即)C1 有一个文件 B1
  • 在C2中,新增了一个文件B2。
  • 在 C3 中,B1 文件被更改 (B1') 并在数据结构中添加了完整文件的新快照。

我上面解释的是git的内部结构。

GIT uses DAG (directed acyclic graph data structure)

现在分支、重置和检出命令仅在提交级别起作用(如您所见,提交形成一个链表)。

所以假设你的分支指向提交 C2,现在你在同一个分支中添加一个新的提交 C3,所以分支指针将从 C2 移动到 C3。

同样,重置与提交相反,因此当您执行重置时,指针会从当前提交移动到后退提交。 假设你在提交 C2 并且你做了重置,你会将当前分支指针移动到上一个提交。

让我们来软、硬和混合。重置有 3 个选项

  1. hard:这里指针被移动到之前的提交并且之前提交的更改从工作目录中完全删除。

  2. mixed:这里指针移动到之前的提交,并且之前提交的更改在没有 staging/adding 的工作目录中维护,即以防你 运行 命令

    git reset --mixed HEAD~1

    git commit

    nothing to commit, working tree clean

    因为您需要使用

  3. add/stage 个文件
git add <filename>
  1. soft:这里指针移动到之前的提交,之前提交的更改在添加它们的情况下维护在工作目录中,即以防你 运行 命令

    git reset --mixed HEAD~1

    git commit

它将创建一个新的提交,因为所有更改都已准备好提交。

如果您有任何问题,请告诉我。 :)

既然你问的是具体的实现——我认为这实际上更容易解释——看看 .git/HEAD 中的实际内容,当你在一个分支上时:

$ cat .git/HEAD
ref: refs/heads/master
$ git checkout -b new
Switched to a new branch 'new'
$ cat .git/HEAD
ref: refs/heads/new
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ cat .git/HEAD
ref: refs/heads/master

所以,只要我 "on a branch" 正如 git status 所说, 分支名称 — 引用的全名,真的——在 .git/HEAD。所以.git/HEAD没有变,也不需要变。

是否存在.git/refs/heads/master更成问题:

$ cat .git/refs/heads/master
cat: .git/refs/heads/master: No such file or directory
$ git rev-parse master
b5101f929789889c2e536d915698f58d5c5c6b7a
$ grep master .git/packed-refs
b5101f929789889c2e536d915698f58d5c5c6b7a refs/heads/master
b5101f929789889c2e536d915698f58d5c5c6b7a refs/remotes/origin/master

这里发生的事情是 Git 已经 打包了 我的参考名称,所以不再有普通文件:refs/heads/master 存储在 .git/packed-refs,作为几行之一(在这种情况下,另一条匹配行是 refs/remotes/origin/master)。

也就是说,reference-to-hash-ID 映射存储在一些数据库中,不一定在一个简单的文件中。 (不过,packed-refs "database" 仍然非常简单。)

不过,要回答您的最终问题,只需 yesgit reset --soft <hash><hash> 写入名称到哈希 ID 的映射。即使我们使用名称而不是哈希 ID,也是如此:

$ git checkout new
$ git reset --soft master~3

名称 new 现在引用与名称 master~3 相同的提交哈希 ID:

$ git rev-parse new
371820d5f1bb3c3e691ad21cee652c02c36ea758
$ git rev-parse master~3
371820d5f1bb3c3e691ad21cee652c02c36ea758

(将新的哈希 ID 写入名称 new 的行为发生在 Git 的当前版本中,以通过写入简单文件 .git/refs/heads/new,但你不应该依赖于此——改用 git rev-parsegit update-ref。)

自从我在上面的 master 中创建了 new 之后,这仅仅起到了将名称 new 移回三个第一父跃点 (master~3) 的作用.这意味着 newmaster 的祖先,所以:

$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ git branch -d new
Deleted branch new (was 371820d5f1).

... 所以 Git 现在只需删除名称 new 即可,因为它已完全合并到 master.

But I don't know why the word history is being used here.

"History" 并不是最好的词。要真正 理解这一点,请通读网站 Think Like (a) Git。这里的关键概念是可达性。更改存储在分支名称下的提交哈希 ID,如 git reset 所做的那样,会更改 可访问提交集 。如果集合增长,则可以访问更多提交;如果缩小,则可以到达的提交更少;如果它保持相同的大小,则可以访问相同的 数量 个提交,但集合本身可能相同也可能不同。

"History" 是,松散和模糊地说,存储库中的提交集,或可从某个名称访问的提交集,或可从某个名称访问的提交的某些子集。使用这些松散定义中的一些但不是全部,移动名称会改变历史。