【问题标题】:What is the `git restore` command and what is the difference between `git restore` and `git reset`?什么是`git restore`命令,`git restore`和`git reset`有什么区别?
【发布时间】:2020-01-20 00:24:21
【问题描述】:

当我想取消暂存文件时,我所有的 Git 教程都会显示如下内容:

$ git add *
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README
    modified:   CONTRIBUTING.md

此提示告诉我们使用git reset 取消暂存文件。

但是,在我的终端中,我看到了:

git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    renamed:    cat.js -> catcat.js
    renamed:    tolendo.gogo -> tolendo.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    readme (copy).md
    tolendo (copy).txt
    zing (copy).html

我的终端告诉我使用git restore --staged,但教程以及Git’s website 告诉我使用git reset HEAD

我不知道新的restore 命令。我尝试用 Google 找出 git resetgit restore 之间的区别,但似乎没有什么适合我的问题。

【问题讨论】:

  • 我正在阅读这个git-scm.com/book/en/v2/Git-Basics-Undoing-Things,正如我上面所说,在那里,git 显示了重置命令,但在我的计算机中,它显示了恢复命令,所以,我想知道有什么区别关于它们,我用谷歌搜索了一些链接,但它们太模糊了,这就是为什么我带来这里以获得明确的答案。
  • git restore 在 2.23.0 中引入(带有git switch)。要取消暂存文件,git reset HEAD &lt;file&gt;git restore --staged &lt;file&gt; 是相同的。有关其他差异,请参阅手册 git-scm.com/docs/git-resetgit-scm.com/docs/git-restore
  • 感谢@ElpieKay 还有一个小问题:为什么你们不回答而不是评论?我是新来的,所以想知道,tks
  • 因为我无法完整回答您的问题。相反,评论可能会提供一些线索并帮助您找出答案。
  • 另一个混杂的例子叫做 git。为什么不是一个简单的“git revert”?现在有三个 git 选项:restore、reset 和 revert,它们之间有细微的差别

标签: git restore git-reset unstage


【解决方案1】:

我已经在“How to reset all files from working directory but not from staging area?”中展示了git restore(仍标记为“实验性”),以及最近的 Git 2.23(2019 年 8 月)。

它有助于将git checkout 分成两个命令:

正如reset, restore and revert 文档所述:

共有三个名称相似的命令:git resetgit restoregit revert

  • git-revert 是关于进行新的提交,以恢复其他提交所做的更改。
  • git-restore 是关于从索引或另一个提交恢复工作树中的文件。
    此命令不会更新您的分支。
    该命令还可用于从另一个提交恢复索引中的文件。
  • git-reset 是关于更新您的分支,移动提示以便在分支中添加或删除提交。此操作会更改提交历史记录。
    git reset 也可用于恢复索引,与 git restore 重叠。

所以:

恢复索引中的文件以匹配 HEAD 中的版本(这与使用 git-reset 相同)

git restore --staged hello.c

或者您可以同时恢复索引和工作树(这与使用git-checkout 相同)

git restore --source=HEAD --staged --worktree hello.c

或者更实用但可读性较差的简短形式:

git restore -s@ -SW hello.c

在 Git 2.25.1(2020 年 2 月)中,“git restore --staged”没有正确更新缓存树结构,导致之后要写入虚假树,已更正。

discussion

Jeff King (peff)commit e701bab(2020 年 1 月 8 日)。
(由 Junio C Hamano -- gitster -- 合并于 commit 09e393d,2020 年 1 月 22 日)

restore:使用 --staged 删除条目时使缓存树无效

报告人:Torsten Krah
签字人:Jeff King

当“git restore --staged”删除索引中的路径时,它会用CE_REMOVE, 标记条目,但我们不会做任何事情来使缓存树无效。
在非分阶段的情况下,我们以checkout_worktree() 结束,它调用remove_marked_cache_entries()。这实际上会从索引中删除条目,并使缓存树和未跟踪缓存无效。

但是对于--staged,我们从不调用checkout_worktree(),并且CE_REMOVE 条目仍然存在。有趣的是,当我们写出索引时它们会被删除,但这意味着生成的索引不一致:它的缓存树与实际条目不匹配,然后立即运行“git commit”会创建错误的树。

我们可以通过在写出索引之前自己调用remove_marked_cache_entries() 来解决这个问题。请注意,我们不能只是将其从checkout_worktree() 中取出;该函数需要在删除它们之前迭代 CE_REMOVE 条目(以删除其匹配的工作树文件)。

对测试的一个好奇:没有这个补丁,它实际上在运行 git-restore 时会触发一个 BUG():

BUG: cache-tree.c:810: new1 with flags 0x4420000 should not be in cache-tree

但在使用类似配方的原始问题报告中,git restore 实际上创建了虚假索引(并且使用错误的树创建了提交)。我不确定为什么这里的测试与我的套房外复制的行为不同,但这里的内容应该可以捕捉到任何一种症状(并且修复可以纠正这两种情况)。


在 Git 2.27(2020 年第二季度)中,“git restore --staged --worktree现在默认从“HEAD”中取出内容,而不是出错。

参见 Eric Sunshine (sunshineco)commit 088018e(2020 年 5 月 5 日)。
(由 Junio C Hamano -- gitster -- 合并到 commit 4c2941a,2020 年 5 月 8 日)

restore:结合 --staged 和 --worktree 时默认为 HEAD

签字人:Eric Sunshine
审核人:Taylor Blau

默认情况下,从--worktree 的索引和--staged 的 HEAD 恢复文件。

--worktree--staged 组合在一起时,必须指定--source 以消除还原源的歧义,从而使同时还原工作树和索引中的文件变得很麻烦。

(由于疏忽,--source 要求虽然已记录在案,但实际上并未强制执行。)

然而,HEAD 也是--worktree--staged 组合时的合理默认值,因此在使用--staged 时将其设为默认值(无论是否与--worktree 组合)。

所以现在,这行得通:

git restore --staged --worktree
git restore -SW

【讨论】:

  • 从 git 版本 2.29.2 开始,“这个命令是实验性的。行为可能会改变。” git restore 在说明部分的末尾。
  • @IvanChau 别担心,这些命令不会去任何地方,也不会发生巨大变化。你可以使用它们。见public-inbox.org/git/xmqq4kor6g8z.fsf@gitster.c.googlers.com
  • 我建议git restore --staged 的用户永远不要错误地运行git restore --staged *(或使用任何通配符匹配)来简单地从暂存中删除当前暂存的文件区域。 git 版本 2.27.0 对我的实际作用是 git rm 在我的存储库中提交的每个文件。
  • 你能解释一下为什么git restore * 会抛出关于不在 git 中的文件的错误,但 git restore '*' 会按预期工作吗?这对我来说毫无意义。这是一个错误吗?
  • @BrianVPS 我从不在 Git 命令中使用 '*'*:星号由 shell 解释并以不总是适用于 Git 命令的方式扩展。最好简单地提及您希望从中应用还原的文件夹:git restore --source=HEAD --staged --worktree -- .:结尾的 -- . 将指定当前文件夹为进行还原的文件夹。
【解决方案2】:

要添加到VonC's answer,并将所有相关命令按字母顺序放入图片中,我将介绍:

  • git checkout
  • git reset
  • git restore
  • git switch

我会再添加一个,错误命名的git revert,以及。

从最终用户的角度来看

需要的只有git checkoutgit resetgit revert。这些命令一直在 Git 中。

git checkout 实际上有两种操作模式。一种模式是“安全”的:它不会意外破坏任何未保存的工作。另一种模式是“不安全的”:如果你使用它,它告诉 Git 清除一些未保存的文件,Git 假定 (a) 你知道它的意思,并且 (b) 你确实是想清除未保存的文件,所以 Git 会立即清除你未保存的文件。

这不是很友好,所以 Git 人员最终——经过多年的用户抱怨——将 git checkout 拆分为两个新命令。这导致我们:

从历史的角度来看

git restore,于 2019 年 8 月在 Git 2.23 中首次出现。 git reset 很老了,一直在 Git 中,可以追溯到 2005 年之前。这两个命令都有能力销毁未保存的工作。

git switch 命令也是新的,在 Git 2.23 中与 git restore 一起引入。它实现了git checkout 的“安全一半”; git restore 实现了“不安全的一半”。

你什么时候使用哪个命令?

这是最复杂的部分,要真正理解它,我们需要知道以下几点:

  • Git 真的是关于提交。提交被存储在 Git 存储库中。 git pushgit fetch 命令将 commits(作为一个孤注一掷的交易1 的整个提交)转移到另一个 Git。你要么拥有所有的承诺,要么你没有。其他命令,例如 git mergegit rebase,都适用于 local 提交。 pull 命令运行 fetch(以获取提交),然后运行第二个命令来处理本地提交。

  • 新提交添加到存储库。您几乎从不remove 提交 存储库。此处列出的五个命令中只有一个(checkout、reset、restore、revert 和 switch)能够删除提交。2

  • 每个提交都由它的 哈希 ID 编号,这对于那个特定的提交是唯一的。它实际上是根据提交的 in 中的内容计算得出的,这就是 Git 如何使这些数字在所有 Gits 中都能工作的方式。这意味着提交中的内容将永远冻结:如果您更改任何内容,您将得到一个带有新编号的新提交,而旧提交仍然存在,具有相同的旧编号。

  • 每个提交都存储两件事:快照和元数据。元数据包括一些先前提交的哈希 ID。这使得提交形成了向后看的链。

  • 分支名称保存一个提交的哈希 ID。这使得分支名称 find 提交,这又意味着两件事:

    • 那个特定的提交是那个分支的提示提交;和
    • 导致并包括该提示提交的所有提交都该分支上。
  • 稍后我们还将讨论 Git 的索引,以及您的工作树。它们与这些是分开的,但值得一提的是,特别是因为索引有三个名称:Git 有时称其为 index,有时称其为 staging area,有时——这些天很少 - 称之为缓存。这三个名字都指的是同一个东西。

我认为,通过分支名称的所有内容最好通过图片来理解(至少对于大多数人而言)。如果我们绘制一系列提交,较新的提交在右侧,每次提交使用o 并省略一些提交以获取空间或其他内容,我们会得到如下结果:

        o--o---o   <-- feature-top
       /        \
o--o--o--o--...--o---o--o   <-- main
    \               /
     o--o--...--o--o   <-- feature-hull

如您所见,它是一个小船存储库。 ? 一共有三个分支。主线分支保存每个提交,包括顶行和底(外壳)行的所有提交。 feature-top 分支包含前三个提交以及沿左侧主线的三个提交,但不包含底行中的任何提交。 提交之间的所有连接器都是——嗯,应该是,但我没有足够好的字体——单向箭头,指向左,或者向下和向左, 或向上和向左。

这些“箭头”,或从提交到提交的单向连接,在技术上是 arcs, or one-way edges,在 directed graph 中。这个有向图是一个没有循环的图,使它成为有向无环图或DAG,它有一堆对 Git 有用的属性。

如果您只是使用 Git 在提交中存储文件,那么您真正关心的只是循环 o nodes or vertices (again two words for the same thing),每个都用于存储您的文件,但您至少应该隐约知道如何他们被安排。这很重要,尤其是因为 merges。合并提交是那些具有两个输出弧的提交,它们向后指向 Git 所称的两个 父提交。子提交是“较晚”的提交:就像人类的父母总是比他们的孩子年长一样,Git 的父母提交也比他们的孩子年长。

不过,我们还需要一件事:新提交从何而来?我们注意到提交中的内容——快照(保存所有文件)和元数据(保存其余部分) Git 保存的关于提交的信息——都是只读的。您的文件不仅被冻结,它们也被转换,然后转换后的数据被去重复,这样即使每次提交都有每个文件,存储库本身保持相对较小。但这意味着 in 提交的文件只能由 Git 读取,而 nothing——甚至 Git 本身也不能写入 给他们。它们被保存一次,并从那时起被删除重复数据。提交充当档案,几乎就像 tar 或 rar 或 winzip 或其他任何东西。

要使用 Git 存储库,我们必须让 Git 提取文件。这会将某些提交的文件取出,将那些特殊的归档格式的东西变成常规的、可用的文件。请注意,Git 很可能能够存储您的计算机实际上无法存储的文件:一个典型的例子是一个名为aux.h 的文件,对于某些 C 程序,在 Windows 机器上。我们不会详细介绍所有细节,但理论上仍然可以使用此存储库完成工作,该存储库可能构建在 Linux 系统上,即使您使用的是无法使用aux.h 直接归档。

无论如何,假设没有像 aux.h 这样令人讨厌的小惊喜,您只需运行 git checkoutgit switch 以获取 Git 的一些提交输出。这将填充您的工作树,从存储在某个分支的提示提交 中的文件中填充它。 tip commit 同样是该分支上的 last 提交,由 branch name 找到。您的git checkoutgit switch 选择该提交作为当前提交,方法是将该分支名称选择为当前分支。您现在拥有提交的所有文件来自,在您可以看到它们并对其进行处理的区域:您的工作树

请注意,工作树中的文件实际上并不在 Git 本身中。它们只是 Git 中提取出来的。这很重要,因为当git checkout Git 提取文件时,它实际上将每个文件放在两个地方。这些地方之一是您看到和处理/使用的普通日常文件。 Git 将每个文件放入的另一个位置是 Git 的 index

正如我刚才提到的,索引有三个名称:索引、暂存区和缓存。所有都指的是同一件事:Git 粘贴每个文件的这些“副本”的地方。每一个实际上都是预先去重的,所以“复制”这个词有点错误,但是——不像它的其他大部分内容——Git实际上在隐藏去重方面做得很好。除非你开始使用像git ls-filesgit update-index这样的内部命令,否则你不需要知道这部分,并且可以将索引视为保存文件的副本,准备进入下一次提交

这对你作为一个使用 Git 的人来说意味着索引/暂存区作为你提议的下一次提交。当您运行git commit 时,Git 会将文件的这些 副本打包为要在快照中存档的副本。您在工作树中的副本是你的;index / staging-area 副本是Git 的,可以使用了。因此,如果您更改您的副本并希望 更改的副本成为下一个快照中的内容,您必须告诉 Git:更新 Git 副本,在Git index / staging-area。您可以使用git add 执行此操作。3git add 命令意味着使建议的下一个提交副本与工作树副本匹配。执行更新的是 add 命令:这是 Git 压缩和去重文件并准备归档的时候,而不是在 git commit 时间。4

然后,假设您有一系列以 hash-N:

结尾的提交
[hash1] <-[hash2] ... <-[hashN]   <--branch

您运行git commit,为其提供所需的任何元数据(提交日志消息),然后您将获得第 N+1 次提交:

[hash1] <-[hash2] ... <-[hashN] <-[hashN+1]   <--branch

Git 自动更新 分支名称 以指向 新提交,因此它已添加到分支中

现在让我们看一下各个命令:

  • git checkout: 这是一个大而复杂的命令。

    我们已经看过这个,或者至少是这个的一半。我们用它来挑选一个分支名称,因此是一个特定的提交。这种检查首先查看我们当前的提交、索引和工作树。它确保我们已经提交了所有修改过的文件,或者——这部分有点复杂——如果我们没有提交所有修改过的文件,切换到另一个分支是“安全的”。如果它安全,git checkout 会告诉您由于文件已修改而无法切换。如果它安全的,git checkout 将切换;如果您不是要切换,则可以切换回去。 (另见Checkout another branch when there are uncommitted changes on the current branch

    但是git checkout 有一个不安全的一半。假设您修改了工作树中的某个文件,例如 README.mdaux.h 或其他任何文件。您现在回顾一下您所做的更改并认为:不,这是个坏主意。我应该摆脱这种变化。我希望文件恢复原样。

    要做到这一点——清除你对README.md的更改——你可以运行:

    git checkout -- README.md
    

    这里的-- 部分是可选的。使用它是个好主意,因为它告诉 Git -- 之后的部分是文件名,而不是分支名

    假设您有一个名为hello分支 和一个名为hello文件。做什么:

    git checkout hello
    

    是什么意思?我们是要求 Git 破坏 file hello 以删除我们所做的更改,还是要求 Git 检查 分支 hello?为了明确这一点,你必须写:

    git checkout -- hello        (clobber the file)
    

    或:

    git checkout hello --        (get the branch)
    

    这种情况下,存在同名的分支和文件或目录,是一种特别阴险的情况。它已经吸引了真正的用户。这就是 为什么 git switch 现在存在。 git switch 命令绝不意味着破坏我的文件。这只意味着做安全的git checkout

    git checkout 命令也被智能化了,所以如果你有新命令并且运行“坏”的那种模棱两可的git checkout,Git 只会抱怨你,什么也不做。要么使用更智能的拆分命令,或在正确的位置添加-- 以选择您想要的操作。)

    更准确地说,这种 git checkout,理想情况下拼写为git checkout -- <em>paths</em>,是Git 将文件从Git 的索引复制到您的工作树的请求。这意味着破坏我的文件。您还可以运行 git checkout <em>tree-ish</em> -- <em>paths</em>,在其中将提交哈希 ID5 添加到命令中。这告诉 Git 从该提交中复制文件,首先复制到 Git 的索引,然后复制到您的工作树。这也意味着破坏我的文件:不同之处在于 Git 从哪里获取它正在提取的文件的副本。

    如果您在某个文件上运行 git add 并将其复制到 Git 的索引中,则需要 git checkout HEAD -- <em>file</em> 从当前提交中取回它。 Git 的 index 中的副本是您 git add-ed 的副本。所以这两种git checkout 形式,带有提交哈希ID(或名称HEAD)、可选的-- 和文件名,是不安全的破坏我的文件 形式。

  • git reset:这也是一个大而复杂的命令。

    根据您的计算方式,git reset 最多有五六种不同形式。我们将在这里集中讨论一个较小的子集。

    • git reset [ --hard | --mixed | --soft ] [ <em>commit</em> ]

      在这里,我们要求 Git 做几件事。首先,如果我们给出一个 commit 参数,例如 HEADHEAD~3 或类似的,我们选择了一个特定的 commit Git 应该 >重置为。这是一种通过将提交从分支末端弹出来删除提交的命令。在此处列出的所有命令中,这是唯一删除任何提交的命令。另一个命令 —git commit --amend — 具有在放置新替换时弹出 last 提交的效果,但该命令仅限于弹出 one 提交。

      让我们将其显示为绘图。假设我们有:

      ...--E--F--G--H   <-- branch
      

      也就是说,这个名为 branch 的分支以四个提交结束,其哈希 ID 我们将依次调用 EFGH。名称 branch 当前存储了最后一次提交的哈希 ID,H。如果我们使用git reset --hard HEAD~3,我们是在告诉 Git 退出最后三个提交。结果是:

             F--G--H   ???
            /
      ...--E   <-- branch
      

      名称branch 现在选择提交E,而不是提交H。如果我们没有写下(在纸上、在白板上、在文件中)最后三个提交的哈希 ID,它们就会变得有点难以找到。 Git 确实提供了一种在一段时间内再次找到它们的方法,但大多数情况下它们似乎已经消失了

      该命令的HEAD~3 部分是我们选择删除最后三个提交的方式。它是 Git 中整个子主题的一部分,记录在 the gitrevisions manual 中,关于命名特定提交的方法。重置命令只需要实际提交的哈希 ID 或任何等效项,HEAD~3 表示 返回三个第一父步骤,在这种情况下,我们从提交 H 返回到提交E

      git reset--hard 部分是我们告诉 Git 如何处理 (a) 它的索引和 (b) 我们的工作树文件的方式。我们在这里有三个选择:

      • --soft 告诉 Git:别管他们了。 Git 将移动 分支名称 而不会触及索引或我们的工作树。如果您现在运行git commit,那么(仍然)在索引中的任何内容都会进入 new 提交。如果索引与提交H 中的快照匹配,这将为您提供一个新的提交,其快照H,但其父级E,就像提交一样FH 都被折叠成一个新的提交。人们通常称之为挤压

      • --mixed 告诉 Git:重置你的索引,但不要管我的工作树。 Git 将移动分支名称,然后将索引中的每个文件替换为新选择的提交中的文件。但是 Git 会留下你所有的工作树文件。这意味着就 Git 而言,您可以启动 git adding 文件来进行新的提交。你的新提交不会匹配 H 除非你 git add everything,所以这意味着你可以,例如,构建一个新的中间提交,有点像 E+F 或其他东西,如果你想要。

      • --hard 告诉 Git:重置你的索引我的工作树。 Git 将移动分支名称,替换其索引中的所有文件,并替换所有工作树中的文件,都是一件大事。现在就好像您根本没有进行这三个提交。您不再拥有来自FGH 的文件:您拥有来自提交E 的文件。

      请注意,如果您省略了这种(硬/软/混合)resetcommit 部分,Git 将使用HEAD。由于HEAD 命名当前提交(由当前分支名称选择),因此分支名称本身保持不变:它仍然选择与以前相同的提交。所以这只对--mixed--hard 有用,因为git reset --soft,没有提交哈希ID,意味着不要移动分支名称,不要改变Git 的索引,不要碰我的工作树。这是git reset 可以做的三件事——移动分支名称、更改 Git 索引中的内容以及更改工作树中的内容——而你只是排除了所有这三件事。 Git 什么都不做也没关系,但何必呢?

    • git reset [ <em>tree-ish</em> ] -- <em>path</em>

      这是我们将在这里关注的另一种git reset。这有点像混合重置,因为它意味着破坏文件的一些索引副本,但在这里您指定要破坏的文件。这也有点不同混合重置,因为这种git reset 永远不会移动分支名称。

      相反,您可以选择要从某处复制的文件。 somewhere 是你给的tree-ish;如果您不提供,则 somewhereHEAD,即当前提交。这只能将提议的下一次提交中的文件恢复到它们在一些现有提交中的形式。默认为 当前 现有提交,这种git reset -- <em>path</em> 具有撤消git add -- <em>path</em> 的效果。6

      git reset 还有其他几种形式。要了解它们的含义,请咨询the documentation

  • git restore:这是从 git checkout 中分离出来的。

    基本上,这与破坏文件(在您的工作树和/或 Git 的索引中)的各种形式的 git checkoutgit reset 的作用相同。它比旧的git checkout-and-clobber-my-work 变体更智能,因为您可以选择文件的来源它们的去向,全部在一个命令行。

    要执行您过去使用 git checkout -- <em>file</em> 所做的事情,您只需运行 git restore --staged --worktree -- <em>file</em>。 (在大多数情况下,您可以省略-- 部分,就像git checkout 一样,但养成使用它的习惯通常是明智的。像git add,这个命令的设计使得只有名为@ 的文件987654475@其实是有问题的。)

    要执行您过去使用 git reset -- <em>file</em> 所做的事情,您只需运行 git restore --staged -- <em>file</em>。也就是说,您告诉git restoreHEAD 复制到暂存区/索引,这就是git reset 的操作方式。

    请注意,您可以将某个现有提交中的文件复制到 Git 的索引中,而无需触及该文件的工作树副本:git restore --source <em>commit</em> --staged -- <em>file</em> 就是这样做的。旧的git checkout 根本无法做到这一点,但您可以 使用旧的git reset 做到这一点,就像git reset <em>commit</em> -- <em>file</em>。而且,您可以将某个现有提交中的文件复制到您的工作树,而无需触及暂存副本:git restore --source <em>commit</em> --worktree -- <em>file</em> 会这样做。存在重叠部分(恢复和重置)因为git restore 是新的,这种恢复是有意义的;也许,理想情况下,我们应该在这里始终使用git restore,而不是使用旧的git reset 做事方式,但Git 试图保持向后兼容性。

    新功能——从任意来源复制到你的工作树,而不触及 Git 的索引/暂存区副本——就是这样:新的。你以前做不到。 (您之前可以运行 git show <em>commit</em>:<em>path</em> &gt; <em>path</em>,但这超出了我们要检查的五个命令。)

  • git switch:这只是git checkout 的“安全一半”。这就是你需要知道的一切。使用git switch,没有--force,Git 不会覆盖你未保存的工作,即使你打错了或其他什么。旧的git checkout 命令可能会覆盖未保存的工作:例如,如果您的拼写错误将分支名称转换为文件名,那么,哎呀。

  • git revert(为了完整起见,我添加了这个):这是一个新的提交。新提交的重点是退出某人在某些现有提交中所做的事情。因此,您需要命名恢复应该退出的现有提交。这个命令可能应该被命名为git backout

    如果您退出最近的提交,这确实会恢复到最近的第二个快照:

      ...--G--H   <-- branch
    

    变成:

      ...--G--H--Ħ   <-- branch
    

    commit Ħ (H-bar) "撤消" commit H 并因此留下与commit G 相同的文件。但我们不必撤消 最近的 提交。我们可以采取:

      ...--E--F--G--H   <-- branch
    

    并添加一个提交 Ǝ 撤消 E 以获取:

      ...--E--F--G--H--Ǝ   <-- branch
    

    可能与任何先前提交的源快照不匹配!


1Git 正在缓慢地发展一种“部分获取”提交的工具,这样您就可以处理具有大量提交的大型存储库,而不必一次等待整个提交,因为实例。现在这不是普通用户会看到的,而当它涉及到普通用户时,它意味着作为提交的基本“全有或全无”模式的附加组件。它将把这从“你要么有一个提交,要么没有”变成“你有一个提交——要么全部,要么部分承诺很快交付其余部分——或者没有;如果你有一部分提交,您可以使用该部件,但仅此而已”。

2即便如此,“已移除”的提交还没有消失:您可以将其取回。不过,这个答案不会涵盖如何做到这一点。另外,git commit --amend 是一个特例,我们会提到,但这里并没有真正涵盖。

3要从工作树 Git 的索引中删除文件,您可以使用git rm。如果您从工作树中删除文件,然后在该文件名上运行 git add,Git 将“添加”删除,因此也可以。

4如果你使用git commit -a,Git 将在那时对所有文件运行git add。这是以一种棘手的方式完成的,可能会破坏一些编写不佳的预提交钩子。我建议学习这两个步骤的过程,部分原因是那些写得不好的钩子——尽管我会尽量避免或修复它们——部分原因是如果试图避免学习Git 的索引就像那些写得很糟糕的钩子的作者所做的那样,Git 以后会给你带来更多麻烦。

5这是一个 tree-ish 而不是 commit-ish 的原因是您可以使用任何指定一些现有内部Git 对象。但是,每个提交都有一个保存的快照,它适合在这里,并且是您通常放在这里的内容。

6与所有其他 Git 命令一样,您可以add 命令和要添加的路径之间使用--。这实际上是一个好习惯,因为这意味着您可以添加一个名为 -u 的路径,如果您有这样的路径:git add -- -u 意味着 添加名为 -u 的文件 但 @ 987654516@ 根本不是这个意思。当然,名称与选项序列匹配的文件比名称与分支名称匹配的文件少见,也不令人惊讶:拥有dev 分支一组名为dev/whatever 的文件真的很容易。由于文件路径将使用目录匹配,对于添加、签出、重置和恢复,这些可能会混淆。 add 命令不采用分支名称,因此在这方面更安全。

【讨论】:

  • 真的很感谢写这么详细的答案的努力!
  • 关于“谢谢”是否适用于 SO 或只是浪费他人时间(因此应谨慎使用)的一些争议,但谢谢。很好的答案! (我并不是说没有真正的优点 - 你已经整合了分散在太多其他地方的大量信息)
  • @Larry:作为一般规则,问题和答案不应该有这样“蓬松”的部分,但 cmets 可以(尽管有时会删除长评论线程)。
【解决方案3】:

对于您的第一个问题“什么是 git-restore?”:

git-restore 是一个恢复未提交更改的工具。未提交的更改是:a) 工作副本中的更改,或 b) 索引(也称为暂存区)中的内容。

这个命令是在 git 2.23 中引入的(与 git-switch 一起),用于分离以前在 git-checkout 中合并的多个关注点。

git-restore 可以在三种不同的模式下使用,具体取决于您是要恢复工作副本、索引还是两者中的工作。

git restore [--worktree] &lt;file&gt; 用索引 (*) 中的内容覆盖工作副本中的 。换句话说,它会还原您在工作副本中所做的更改。是否指定--worktree 无关紧要,因为如果您不另外说明,则它是隐含的。

git restore --staged &lt;file&gt; 用本地存储库中的当前 HEAD 覆盖索引中的 。换句话说,它取消了以前上演的内容。到目前为止,确实相当于旧的git reset HEAD &lt;file&gt;

要使用当前 HEAD 覆盖工作副本和索引,请使用 git restore --staged --worktree --source HEAD &lt;file&gt;。此版本同时执行以下操作:将您的工作副本恢复为 HEAD 并取消暂存以前暂存的工作。

对于您的第二个问题“git-restore 和 git-reset 有什么区别?”:

这两个命令有重叠,也有区别。

两者都可用于修改您的工作副本和/或暂存区域。但是,只有 git-reset 可以修改您的存储库。从这个意义上说,如果您只想恢复本地工作,git-restore 似乎是更安全的选择。

还有更多的不同,这里就不一一列举了。

(*) 一个没有added 到索引的文件仍然被认为在索引中,但是在当前 HEAD 版本中它处于“干净”状态。

【讨论】:

    猜你喜欢
    • 2021-04-02
    • 1970-01-01
    • 2011-04-08
    • 2015-01-17
    • 2021-07-04
    • 2020-06-22
    • 2012-06-28
    • 2017-12-10
    相关资源
    最近更新 更多