在尝试 git reset --soft 并撤消它时,提交和 HEAD 在幕后发生了什么?

What is happening behind the scenes, with the commits and HEAD while trying to git reset --soft and undoing it?

我发布了git reset --soft HEAD~1然后我决定不这样做了,我想回到我以前的状态

所以,我在 Stack Overflow 中搜索,然后我得到了这个答案:

Now I just want to go back to the time before I issued the reset

If you only want to cancel the git reset --soft you just did, you can look up the former HEAD commit id in the reflogs

$ git reflog
$ git reset --soft formerCommit

我如前所述发布了git reset --soft formerCommit。然后我使用 git reflog--

检查了 HEAD

如您所见,它又创建了两个日志,但我的头脑正处于所需的提交状态。所以,我想如果我进行新的更改并推送它,它就会正常工作。

但是没有,我得到了这个错误(进行新更改后推送)--

$ git push
To https://github.com/Arpan619Banerjee/Forkify_JS_app.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'https://github.com/Arpan619Banerjee/Forkify_JS_app.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

尽管 git log 不显示 git reflog

中显示的那两个额外日志

所以,现在我的问题是 ---

  1. 恢复某些文件(不是之前提交的所有文件)的更改并再次推送的正确方法是什么?

  2. 如果我必须强制推送,在重置时使用我在问题中发出的命令有什么好处?

  3. 发布 git reset --soft HEAD~1 后,我可以 unstaged 更改> 进行新更改> 提交> 强制推送。

  4. 为什么我总是需要强制更改?我知道 Heads 已经发生分歧,但是没有其他方法可以 优雅地 做到这一点吗?

我如何理解这些概念,我是新手 git 并且正在自学它。

首先,直接(虽然不是很有用)回答你的直接问题:

  1. What is the correct way to revert changes in some files(not all the files previously committed) and push it again?

没有一个。也就是说,没有一个正确的方法来做到这一点。有许多不同的 正确方法可以做到这一点。

  1. What is the advantage of using [git reset --soft] if I have to force the push?

如果这会生成您喜欢的提交图,那就是生成您喜欢的提交图的一种方式。

  1. After issuing git reset --soft HEAD~1, I could have unstaged the changes> made new changes>commited> forced the push.

这不是问题。这是一个声明(也是一个真实的声明)。

  1. Why do I always need to force the [push]?

你并不总是需要;你为这些情况做的是因为你故意要求一些 other Git 存储库 丢弃一些提交.

关键概念

How can I understand the concepts?

这里的关键是 Git 并不是真的关于文件——尽管它存储文件——也不关于 b运行ch 名称,尽管它使用 b运行ch 名称。 Git 实际上是关于 提交 .

每次提交都会存储文件。事实上,每次提交都会存储一个 所有文件的完整快照 。但是,这些快照中的文件采用特殊的 read-only、压缩和 de-duplicated 格式,只有 Git 本身可以理解。 None 您系统上的其他软件可以理解这些文件。 (请注意 de-duplication 意味着每次提交都可以存储 每个 文件的副本,因为大多数提交只是 re-use 来自其他提交的大部分文件, 这样这些多份几乎不占用额外的space.)

重要的是要记住,任何提交都不能在提交后更改。换句话说,所有的提交都是完全的,完全的read-only。 (这实际上是所有内部 Git object 的一般 属性,它与它们的 object 哈希 ID 相关联。)

但是如果提交是 read-only,并且只有 Git 可以读取文件,我们如何 使用 它们?我们需要普通文件。我们需要能够阅读它们!我们需要能够给他们!因此,要使用 Git,我们必须将out 的Git 文件取出。这就是 git checkout——或者在 Git 2.23 或更高版本中,git switch——是为了:找到一些特定的提交并 提取 它。请注意,即使我们使用 b运行ch name 这样的 b运行ch name 也会 find 通过它的哈希 ID master。我们稍后会详细介绍。

不过,在我们深入探讨之前,我们应该看看提交到底为您做了什么,因为每个提交都有两个部分:

  • 一次提交包含所有文件的完整快照。这是它的主要数据:你告诉 Git 保存的每个文件的完整副本,在你告诉 Git 保存它的时候,以它当时在 Git 中的形式。

  • 一个提交还有元数据,比如提交人的名字。大多数元数据是您在 git log 输出中看到的内容:例如,某人的姓名和电子邮件地址、date-and-time-stamp 和日志消息。但是一个元数据对于 Git 本身是至关重要的。每个提交存储其直接 parent 提交的原始哈希 ID,或者对于合并提交,其 parents(复数).

大多数提交只有一个 parent。如果我们有这样的提交链,我们可以这样绘制它们:

... <-F <-G <-H

其中每个字母代表一些实际的提交哈希 ID。提交 H 只是链中的 last 提交。因为我们使用 commit Gmake commit H,commit H 本身会记住 commit G 的哈希 ID,在它的元数据中, 作为其 parent.

我们说提交 H 指向 提交 G。请注意,这些箭头始终指向后方。他们必须这样做,因为 提交中的所有内容 都是 read-only。但是提交 G 也向后指向,提交 F: G 的 parent 是 F。当然,提交 F 也向后指向另一个更早的提交。

这对我们来说意味着,如果我们只记得 last 提交的哈希 ID,就可以使用该提交找到

last =485=]所有较早的提交。 Git 现在有一个聪明的技巧:A b运行ch name like master 只是保存 last 提交的哈希 ID在链中。

构建新提交

所以,假设我们有一个只有三个提交的小型存储库。我们将调用这些提交 ABC 并像这样绘制它们:

A--B--C   <-- master

我在这里变得懒惰(出于某种原因)并且没有将提交箭头绘制为 backwards-pointing 箭头,但我们必须记住它们只会向后移动。也就是说,如果我们提交 B,我们不能轻易地 forwardsC,但我们可以很容易地 backwardsA.

建立一个新的提交,我们首先让 Git 提取我们现有的提交之一。让我们使用 name master 选择提交 C。 Git 将提交 C 提取到某处——我们稍后会详细介绍——并让我们查看和使用它的所有文件。我们还需要 stagecommit,稍后我们也会详细介绍。但是无论如何,我们将进行 new 提交,它会获得一个新的、唯一的哈希 ID。

新的哈希 ID 看起来 运行dom。它根本不是 运行dom:它实际上是所有提交内容的加密校验和。这取决于保存在 的每个文件的每一位,以及该提交的所有元数据中的每一位,包括你的名字,date-and-time 当你提交时,和 parent 哈希 ID。但这太难预测了:您需要知道所有这些 您将要进行提交的确切秒数,以便预测哈希 ID。所以它 看起来 运行dom,我们暂时假设它是。因为新的哈希 ID 又大又丑而且 random-looking,我们就叫它 D.

Git 提交 D 以便它指向现有的提交 C,像这样:

A--B--C
       \
        D

现在技巧来了:因为 D 是链中的 last 提交,Git 存储 D的实际哈希 ID 到名称 master. 名称移动!该名称现在指向新提交:

A--B--C
       \
        D   <-- master

现在,我们可以有多个 b运行ch 名称,指向同一个提交。假设我们首先创建了一个新的 b运行ch name dev,这样我们就可以这样开始:

A--B--C   <-- dev, master

如果我们选择 b运行ch dev 使用,我们选择提交 C。如果我们选择 b运行ch master 来使用,我们仍然 选择提交 C。这两个名称现在都指向 C,所以无论哪种方式我们都会提交 C。不同之处在于当我们进行 new 提交时会发生什么。

如果我们告诉 Git 我们想使用名字 dev——如果我们 git checkout devgit switch dev——那么我们从这个开始:

A--B--C   <-- dev (HEAD), master

也就是说,Git将这个特殊名称HEAD附加到一个(并且只有一个)b运行ch名称。然后当我们 进行新提交时 D 那就是 Git 更新 的名称。所以这一次,我们将有:

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

我们可以随时添加和删除 b运行ch 名称

假设我们有:

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

也就是说,我们dev提交D,但随后运行git checkout mastergit switch master 返回到之前的提交 C。我们的 HEAD 现在附加到名称 master.

假设我们现在告诉Git:删除名字dev。我们必须强制 Git 这样做,使用:

git branch --force --delete dev

或:

git branch -D dev

(旧版本的 Git 需要大写的 -D 选项,因为 git branch 曾经不知道如何将 --force--delete 一起使用) .结果是这样的:

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

提交 D 还在存储库中 一段时间,如果我们知道它的哈希 ID,我们就可以找到它。但是 Git 通过 b运行ch 名称找到提交,所以现在 Git 找不到提交D 对我们来说——除了,也就是说,通过 Git 的 reflogs.

我不会在这里详细介绍,但是每个 b运行ch 名称都有一个 reflog,HEAD 本身还有一个。当您 Git 删除一个 b运行ch 名称时,如 dev,这也会删除它的 reflog。1 幸运的是,如果您是 on b运行ch——曾使用git checkoutgit switch来获取它——最近,HEAD reflog将存储哈希ID。请注意,reflog 条目最终 过期 并被丢弃,而服务器 Gits——你 git push 的服务器——通常甚至没有 reflogs。


1删除 reflog 几乎可以肯定是一个错误,Git 已经忍受了 15 年。它可能有一天会被修复,特别是如果 Git 为其 name-to-value key-value 商店获取真正的数据库软件。这将允许取消删除 b运行ch 名称。


Git 可以找到的名称和其他提交是 Git 查找 提交的方式

我们之前提到 b运行ch 名称保存链中 last 提交的哈希 ID。例如,我们可以有:

          I--J   <-- branch2
         /
...--G--H   <-- master
         \
          K   <-- branch3

在这里,提交 J 是在 branch2 上的 最后一个 提交。提交 K 是在 branch3 上的 最后一次 提交。提交 H 所有三个 b运行ches 上,但是 last 提交在 master.

提交 G 可以通过以下三种方式之一找到:

  • master开始,后退一次,或者
  • branch2开始,然后后退三次(J,然后IHG),或者
  • branch3 开始,然后后退两次(KHG)。

如果我们忽略 reflogs,这就是找到 G 的三种方法,但是只有一种方法可以找到 IJK .如果我们删除名称 branch2 and/or branch3,这些提交将不会是 find-able.

Git 将 最终 丢弃未 find-able 提交。在您自己的存储库中,这需要更长的时间,因为那里有 reflogs 并且充当查找提交的替代方法。在服务器存储库中,没有任何 reflog,提交通常会在两周内过期。2

我们可以安全地删除 name master,但是,只要我们至少保留其他两个名字之一,因为 Git 将保留 H,只要它可以通过名称 branch2 找到 J,然后找到 I,然后找到 H,例如。


2两周的宽限期给了 Git 一些时间来正确命名它们:Git 中的各种操作创建 objects首先,然后调整名称以便能够找到它们。所以 Git 的清理 / garbage-collection 进程必须给其他 Git 命令一点时间来完成他们的工作。


我们可以随时强行移动名称

假设我们有:

...--G--H   <-- master (HEAD)

然后我们在 master:

上进行新的提交 I
...--G--H--I   <-- master (HEAD)

现在假设我们决定不喜欢提交 I 的某些内容。也许它的快照文件之一是错误的。也许我们在 git log 消息中拼错了一个词。我们可以让 Git 将 name master 后退一步,使用 git reset,像这样:

          I   ???
         /
...--G--H   <-- master (HEAD)

提交 I 在哪里?它仍然存在,在存储库中。如果愿意,我们可以在 git reflog 中找到它的原始哈希 ID,因为我们的 Git 有引用日志。但是我们无法使用 name master 找到它,因为 master 现在指向 H 而不是 I。我们无法使用提交 H 找到它,因为 H 指向 向后 ,提交 G,而不是转发到 I

这是 git reset 经常做的三件事之一:它移动 b运行ch name 附加到 HEAD .注意:git reset 命令非常庞大和复杂,这个答案只是谈论它的许多工作中的一小部分。

我们还可以使用 git branch 命令,带有 --force 标志,移动任何 b运行ch 名称以指向任何提交。那么,almost any b运行ch name: git branch 将拒绝移动我们现在使用的 name通过拥有它 checked-out。也就是说,如果 HEAD 附加到 master,我们可以移动任何 b运行ch 名称 除了 master,使用 git branch.3 这是有原因的,它与 Git 的 index 和你的 work-tree.


3如果你使用git worktree add创建额外的work-tree,它们每个都有自己的HEAD。这将 "lock down" 其他 b运行ch 这样的名称。


Git的索引和你的work-tree

到目前为止,我们已经多次注意到 提交 是 read-only 并且必须被提取。提取的文件很容易理解:它们只是普通文件。他们住在 Git 称为您的 工作树 work-tree.

的区域

标准(non-server,非"bare")存储库附带一个work-tree。 work-tree 实际上并不是 存储库中:构成存储库的所有内容都在 .git 目录/文件夹中。 .git 文件夹通常在 work-tree 的最顶层,所以它们几乎是 side-by-side。人们倾向于将它们视为一对,他们 工作 作为一对,但你的 work-tree 是 你的 。你可以随心所欲地处理它。 .git 文件夹中的东西是 Git 的,一般来说你不应该乱搞。

因此,当您选择一个提交时——例如,使用 git checkout mastergit switch dev 来处理/使用,Git 将复制 已提交 个文件,这些文件在 提交中保存到您的work-tree。提交的文件都是 read-only,并且会一直保存,或者至少,只要提交本身继续存在。只要 Git 可以 找到 提交,它的文件就会永远安全地保存——好吧,前提是你不要弄乱 Git 在 .git,而您的笔记本电脑没有 catch fire 或其他任何东西。

这将是一个很好的简单图片——Git 有它的提交,你的 work-tree 有你将用于进行下一次提交的文件——但它在一个重要但微妙的地方是错误的方法。 Git 实际上并没有从您的 work-tree!

进行 new 提交

我们之前注意到 Git 提交中的文件采用特殊的 Git-only、冻结和 de-duplicated 形式。一起去吧h 那,为了使它自己的工作更容易,当你 提取 一个提交时,Git 将提交的文件复制到一种 Git 调用的保存区域它的 indexstaging area.3 所以这意味着 Git 没有两个,但是每个文件 三个 副本。如果您在提交中有一个名为 README.md 的文件,Git 包含三个 README.md 文件:

  • git show HEAD:README.md:这是提交的副本。无法更改。您可以对 select 一些 other 提交的副本进行新的提交,或检查其他提交,但与所有提交的副本一样,它一直被冻结。

  • git show :README.md:这是index暂存区副本。 可以改!它处于冻结 格式,但您可以使用 git add 批量替换它,甚至完全删除它。

  • README.md(只是一个常规文件):这是 你的 副本 你的 work-tree。你可以用它做任何你想做的事。

这是每个文件的三个重要副本。当您 运行 git commit 时,Git 将通过打包所有 index 副本来使其 new 提交.那些成为新的快照,永远存在,或者至少,只要你和Git可以找到提交的哈希ID。

在您提交之前,文件的索引和 work-tree 副本只是临时的。它们可以被替换或移除!它们不像提交的副本那样是永久性的。在大多数情况下,work-tree 中的那个甚至不在 Git 中。 (上演的是一种halfway-intoGit。)

请注意,每个文件都有一个暂存副本, 不仅仅是您刚刚 git add 编辑的文件。 git add 所做的是 用 work-tree 的更新替换 暂存副本。如果您修改 work-tree 副本,然后更新暂存副本,现在暂存副本 HEAD 副本不匹配。如果您 un-modify work-tree 副本并再次暂存,现在暂存副本 确实 再次匹配 HEAD 副本。


3从技术上讲,索引/staging-area中的内容不是每个文件的副本,而是信息关于 文件:它的名字,它的模式,和一个内部Git blob hash ID。快照存储为内部 Git blob object。但是 Git 会为您无形地处理所有这些,并且在大多数情况下,您可以将索引视为保存文件的副本,采用 Git 的冻结格式。唯一失败的情况是你使用 git ls-files --stagegit update-index 在 extra-low 级别处理 Git。


git status运行s两次比较

当你使用git status——这是个好主意;它告诉你很多有用的东西——它实际上 运行s 两个 比较操作:

  • 首先,它将 HEAD 提交与索引进行比较。对于相同的每个文件,Git 什么都不说。对于每个 不同 的文件,Git 表示 staged for commit

  • 然后它将索引与您的 work-tree 进行比较。对于相同的每个文件,Git 什么都不说。对于每个 不同 的文件,Git 表示 not staged for commit.

在您的 work-tree 中但不在 Git 的索引中的文件有点特殊:它们被称为 untracked .我们不会在这里详细介绍,但请注意 Git 索引中的文件集会随着您 git addgit rm 文件的变化而变化,并且当您使用 git checkoutgit switch 提取其他提交。因此,文件是否被跟踪取决于 Git 的索引 现在 中的内容,您可以更改它。

有时,更改索引中的内容也会告诉 Git 以某种方式覆盖您的 work-tree。对于 git checkout 尤其如此,因为它必须填写 both Git 的索引 你的 work-tree.但 git reset 也是如此,这取决于你如何 运行 它。

你的那种 git reset 是关于做 1、2 或 3 件事

git reset 命令很长,我们不会全部讨论,但是您正在做的那种重置:

git reset --soft <hash>

使用的 git reset 版本最多可以做三件事:

  1. 它可以移动一个b运行ch name。你选择一个提交哈希 ID——例如,通过名称,或者通过剪切和粘贴一个实际的哈希 ID——然后你告诉 Git:移动当前的 b运行ch 名称, HEAD 附加到其中,因此它指向该提交 .

  2. 可以erase-and-reloadGit的索引。同样,您选择一个提交,Git 将从 t 加载它自己的索引在提交。以前在索引中的文件现在不在了。现在索引中的文件是来自该提交的文件。

  3. 而且,它可以erase-and-reload你的work-tree。索引中的任何 work-tree 文件,但根本不应该在索引中,将被删除;索引中需要更改的任何 work-tree 文件将在您的 work-tree 中被替换;对于您要移动到的提交中但之前不在 Git 的索引中的任何文件,Git 会将这些文件复制到其索引和您的 work-tree 中。

最后一步——更新你的work-tree——只有当你告诉Git去做时才会发生,使用git reset --hard。使用默认的git reset --mixed,Git只会执行第1步和第2步。如果使用git reset --soft,Git只会执行第1步。

请注意,Git 将始终执行第 1 步 — 它始终 移动 当前 b运行ch — 但您可以选择 current commit 作为要移动到的提交。也就是说,如果您当前的 b运行ch 名称当前命名为提交 H,并且您说 git reset --hard <em>hash-of-H </em>,名称 "moves",以便它现在指向提交 H,就像以前一样。所以这就是没有提交哈希 ID 的 git reset --hard 的用途:它 "moves" b运行ch 不移动它,但随后也重置 Git 的索引并重置你的 work-tree.

git reset --soft 变体使 Git 执行第 1 步——移动名称——然后停止。所以这只有在您打算真正移动名称时才有意义。您选择一个新的提交哈希 ID,并且当前的 name 移动到那里,没有其他更改。

如果你有:

...--G--H--I   <-- master (HEAD)

然后你使用git reset --soft <em>hash-of-H</em>,你得到:

          I
         /
...--G--H   <-- master (HEAD)

Git的索引和你的work-tree都没有变化,所以你现在可以运行git commit然后做一个new 犯罪。如果愿意,您可以编辑文件和 git add 它们,以更新您的 work-tree 并首先将更新复制到 Git 的索引中。无论哪种方式,您都可以使用新的 date-and-time 和可能不同的日志消息进行新的提交:

          I
         /
...--G--H--J   <-- master (HEAD)

因为J是一个不同的commit,它有不同的hash ID。

git push 是关于改变其他一些东西 Git

这将我们带到 git push。在 你的 Git 存储库中进行更改是很好的,通过添加和可能删除(或推开)一些提交并移动各种 b运行ch 名称大约。但这只会影响 您的 存储库。还有一些其他的 Git 存储库,您可能也想对它们进行更改。

如果您可以在那些机器上登录,则可以在那里进行提交。但是你在那里所做的提交将在不同的时间进行,并且会有不同的哈希 ID——也许你 不能 无论如何登录那些机器。例如,也许其他存储库位于 GitHub.

幸运的是,Git 具有 git push 作为内置操作。这让你的 Git 调用了其他 Git。您的 Git 和他们的 Git 将进行某种形式的对话。4 您的 Git 将针对他们的 Git 列出一些提交哈希 ID。他们的 Git 可以查看他们的存储库,看看他们是否有那个哈希 ID。如果他们这样做了,他们现在就有了承诺。如果没有,他们 不会 提交,您的 Git 将发送它。然后,您的 Git 也有义务提供提交的 parent(s),并将根据需要发送它/他们,以及他们的 parents,等等。

所有这一切的结果是,如果他们有:

...--G--H--I   <-- master

他们的 存储库中,并且您有:

          I
         /
...--G--H--J--K--L   <-- master (HEAD)

您的 Git 将提供 L,他们会说 请发送 ,您的 Git 将提供并发送 KJ,并提供 H 但他们会说 不,谢谢,我已经有了那个

因此,通过此过程,您的 Git 将他们 没有但需要 的任何提交发送给他们。然后你的 Git 发送一些最终请求,或者——如果你使用强制选项——命令:如果没问题,请将你的 master 设置为指向提交 L 将您的 master 设置为指向提交 L!

如果他们服从这个礼貌的请求或命令,他们现在将拥有:

          I
         /
...--G--H--J--K--L   <-- master

就像你一样。但是 他们没有任何 reflogs。所以他们将无法找到 提交I!他们的 Git 会非常快地删除提交 I——不是马上,但至少一个月内不会安全,就像你的那样。

如果你不使用--force,他们的Git会说:不,如果我设置我的master指向L ,我会丢失一些提交 master. 他们甚至不检查是否有其他 b运行ch 名称保护提交I。他们只是说。您的 Git 通过打印 non-fast-forward 错误告诉您这件事。


4注意最接近push对立面的其实是git fetch,它也和另一个Git。不同之处在于,通过这次对话,您的 Git 从他们的 Git 中获取 东西。它以您的 Git 更新您的 remote-tracking 名称.

结束

git pull 命令,这看起来可能是相反的,其实不是:它意味着 运行 git fetch,然后是 运行 秒Git 命令—经常git mergegit push 命令不会 运行 任何第二个 Git 命令;它从不进行任何合并。


获得您可能想要的东西的另一种方式

如果你有:

...--G--H--I   <-- master

并且您不喜欢 I 中某些文件的 ar运行gement?嗯,这不是真正的问题。您不必从此链中 删除 I。只需检查 master,这样它就是 HEAD 提交,并且所有文件都像往常一样处于活动状态:

...--G--H--I   <-- master (HEAD)

然后更改您的 work-tree 和索引,使其包含您想要的文件。您可以直接从提交 H 或提交 G 中获取一些文件。在 Git 2.23 或更高版本中,您可以使用 git restore 命令来执行此操作:

git restore --source=<hash-of-G> --staged --worktree path/to/file.ext

这会将 committed path/to/file.ext 文件从提交 G 复制到索引 / staging-area (--staged ) 和你的 work-tree (--worktree).

如果你的 Git 早于 Git 2.23,你可以使用 git checkout 命令来做到这一点——git checkout 命令至少是两个命令合二为一.5 所以在较早的 Git 中,或者如果你已经养成了这个习惯,你可以这样做:

git checkout <hash-of-G> -- path/to/file.ext

将文件复制到 Git 的索引和您的 work-tree,就像 git restore.6

无论如何,一旦您更新了索引并 work-tree 拥有了您希望它们拥有的内容,您就可以 运行 git commit 进行新的提交:

...--G--H--I--J   <-- master (HEAD)

新提交 J 包含您想要的快照,并具有您选择放入的任何日志消息。它的 parent 是现有提交 I。您现在可以调用其他一些 Git 并发送新的提交 J 以添加到 他们的 master,在 他们的 repository 指向(与您的相同,因此哈希 ID 相同)提交 I。这 添加到 他们的 master,没有放弃任何提交,因此他们将毫无怨言地接受这个 git push7


5在Git 2.23 中,Git 的人终于承认git checkout 的工作太多了,并将它们分开。或许有一天,他们可能会为 git reset 等其他命令执行此操作——尽管事实上,git restore 承担了很多您可以使用 git reset 执行的操作,并且允许 git reset ] 是更小的命令。但是 Git 试图通过升级保留它的命令 backwards-compatible,所以 git checkout 仍然有效,并且 git reset 不能被削减。

6git restore 不同,您 不能 选择只将文件放入这两个位置之一——不能无论如何都使用 git checkout

7在 GitHub 上,您可以将某些 b运行 设置为 protected,这要求 b运行ch 不是 "protected",或者 GitHub 已被告知您具有管理权限并且可以 git push 受保护的 b运行通道。受保护的 b运行ches 不是内置于 Git 本身:它们是 add-on。不过,现在大多数网络托管网站似乎都已添加了它们。