什么是“git restore”命令,“git restore”和“git reset”有什么区别?

Thi*_* NV 107 git restore git-reset unstage

当我想取消暂存文件时,我所有的 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
Run Code Online (Sandbox Code Playgroud)

此提示告诉我们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
Run Code Online (Sandbox Code Playgroud)

我的终端告诉我使用,git restore --staged但教程以及Git 的网站告诉我使用git reset HEAD.

我不知道新restore命令。我尝试使用谷歌找到 和 之间的区别git resetgit restore但似乎没有什么适合我的问题。

Von*_*onC 91

我在最近的 Git 2.23(2019 年 8 月)中介绍了git restore(仍标记为“实验性”)“如何从工作目录而不是从暂存区重置所有文件? ”。

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

作为复位,恢复和还原文档状态:

有三个名称相似的命令:git reset,git restoregit revert.

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

所以:

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

git restore --staged hello.c
Run Code Online (Sandbox Code Playgroud)

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

git restore --source=HEAD --staged --worktree hello.c
Run Code Online (Sandbox Code Playgroud)

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

git restore -s@ -SW hello.c
Run Code Online (Sandbox Code Playgroud)

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

讨论

请参阅Jeff King ( )提交的 e701bab (2020 年 1 月 8 日)(由Junio C Hamano合并-- --2020 年 1 月 22 日提交 09e393d 中peff
gitster

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
Run Code Online (Sandbox Code Playgroud)

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


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

请参阅Eric Sunshine ( ) 的提交 088018e(2020 年 5 月 5 日(由Junio C Hamano合并-- --提交 4c2941a 中,2020 年 5 月 8 日)sunshineco
gitster

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

签字人:Eric Sunshine
评论人:Taylor Blau

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

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

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

但是, HEAD--worktree与 结合使用时也是合理的默认值--staged,因此在任何时候--staged使用时都将其设为默认值(无论是否结合使用--worktree)。

所以现在,这有效:

git restore --staged --worktree
git restore -SW
Run Code Online (Sandbox Code Playgroud)

  • @IvanChau 别担心,这些命令不会发生任何变化,也不会发生巨大变化。你可以使用它们。请参阅 https://public-inbox.org/git/xmqq4kor6g8z.fsf@gitster.c.googlers.com/ (3认同)
  • 我建议“git Restore --staged”的用户永远不要错误地运行“git Restore --staged *”(或任何通配符匹配),天真地尝试简单地从目录中删除当前暂存的文件。暂存区。这对 git 版本 2.27.0 的实际作用是“git rm”在我的存储库中提交的每个文件。 (3认同)
  • @BrianVPS我从不在Git命令中使用`'*'`或`*`:星号由shell解释并以并不总是与Git命令一起使用的方式扩展。最好简单地提及要应用还原的文件夹:`git Restore --source=HEAD --staged --worktree -- .`:结尾的 `-- .` 将指定当前文件夹作为进行恢复的地方。 (2认同)

fie*_*res 23

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

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

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

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

git restore [--worktree] <file>用索引 (*) 中的内容覆盖工作副本中的 <file>。换句话说,它会还原您在工作副本中的更改。您是否指定--worktree并不重要,因为如果您不另行说明,则暗示了这一点。

git restore --staged <file>使用本地存储库中的当前 HEAD 覆盖索引中的 <file>。换句话说,它取消暂存先前暂存的内容。到目前为止,它确实相当于旧的git reset HEAD <file>.

要使用当前 HEAD 覆盖工作副本和索引,请使用git restore --staged --worktree --source HEAD <file>. 此版本兼具以下两项功能:将您的工作副本恢复为 HEAD 并取消暂存先前暂存的工作。

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

这两个命令之间存在重叠和差异。

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

还有更多不同之处,我无法在此一一列举。

(*) 未add编入索引的文件仍被视为在索引中,但在当前 HEAD 修订版中处于“干净”状态。


tor*_*rek 17

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

  • 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 restoreGit 2.23一起引入。它实现了“安全的一半” git checkoutgit restore实现“不安全的一半”。

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

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

  • Git 真的是关于commits 的。提交被存储Git 存储库中。该git pushgit fetch传送命令提交-whole提交,作为一个全有或全无的交易1 -to其他的Git。您要么拥有全部提交,要么没有。其他命令,例如git mergeor git rebase,都适用于本地提交。该pull命令运行fetch(以获取提交),然后是第二个命令,以便在提交位于本地后处理这些提交。

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

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

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

  • 一个分支名称持有的哈希ID的一个承诺。这使得分支名称找到那个提交,这又意味着两件事:

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

我认为,通过分支名称的所有内容最好通过图片来理解(至少对大多数人而言)。如果我们绘制一系列提交,更新的提交向右,使用ofor 每次提交并省略一些提交空间或其他什么,我们得到这样的东西:

        o--o---o   <-- feature-top
       /        \
o--o--o--o--...--o---o--o   <-- main
    \               /
     o--o--...--o--o   <-- feature-hull
Run Code Online (Sandbox Code Playgroud)

如您所见,这是一个船库。共有三个分支。主线分支保存每个提交,包括顶行和底行(外壳)上的所有提交。该feature-top分公司持有的前三名提交和也沿着主线左边的三个提交,但没有任何底行的提交的。提交之间的所有连接器都是——嗯,应该是,但我没有足够好的字体——单向箭头,指向左,或向下和向左,或向上和向左。

这些“箭头”,或从提交到提交的单向连接,在技术上是有向图中的或单向边。这个有向图是一个没有循环的图,使其成为有向无环图或DAG,它具有一系列对 Git 有用的属性。

如果您只是使用 Git 在提交中存储文件,那么您真正关心的是圆形o 节点顶点(同样是两个词),每个都用于存储您的文件,但您至少应该模糊知道它们是如何排列的。这很重要,尤其是因为merges。合并提交是那些有两个外向弧的提交,向后指向 Git 称之为parent commits 的两个。子提交是“较晚”的:就像人类父母总是比他们的孩子大一样,Git 父母提交比他们的孩子大。

不过,我们还需要一件事:新的提交来自哪里? 我们注意到提交中的内容——保存所有文件的快照和保存 Git 保留的关于提交的其余信息的元数据——都是只读的。您的文件不仅会被冻结,还会被转换,然后对转换后的数据进行重复数据删除,这样即使每次提交都有每个文件的完整快照,存储库本身仍然相对较小。但是,这意味着这些文件提交只能通过的Git,并没有什么-不是甚至Git的本身,可以给他们。它们会被保存一次,并从那时起进行重复数据删除。提交充当档案,几乎就像 tar 或 rar 或 winzip 或其他任何东西。

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

无论如何,假设没有像 那样令人讨厌的小惊喜aux.h,您只需运行git checkoutGit 中git switch获取一些提交即可。这将填充您的工作树,从存储在某个分支的提示提交中的文件中填充它。该提示提交的,再次,在最后提交的分支,由发现分支名。通过选择该分支名称作为当前分支,您或选择该提交作为当前提交。您现在拥有的所有文件那次提交,在一个地区,你可以看到他们,对他们的工作:您的git checkoutgit switch工作树

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

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

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

然后,假设您有一系列以以下内容结尾的提交hash-N

[hash1] <-[hash2] ... <-[hashN]   <--branch
Run Code Online (Sandbox Code Playgroud)

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

[hash1] <-[hash2] ... <-[hashN] <-[hashN+1]   <--branch
Run Code Online (Sandbox Code Playgroud)

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

现在让我们看看每个不同的命令:

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

    我们已经看到了这个,或者至少是这个的一半。我们用它来挑选一个分支名称,因此是一个特定的提交。这种检出首先查看我们当前的提交、索引和工作树。它确保我们已经提交了所有修改过的文件,或者——这部分有点复杂——如果我们没有提交所有修改过的文件,切换到另一个分支是“安全的”。如果它是不是安全的,git checkout告诉你,你不能切换由于有修改过的文件。如果它安全的,git checkout将切换; 如果你不想切换,你可以切换回来。(另请参阅当当前分支上有未提交的更改时签出另一个分支

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

    为了得到这个——消除你对,说,README.md——你可以运行:

    git checkout -- README.md
    
    Run Code Online (Sandbox Code Playgroud)

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

    假设你有一个分支命名hello 一个文件命名hello。有什么作用:

    git checkout hello
    
    Run Code Online (Sandbox Code Playgroud)

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

    git checkout -- hello        (clobber the file)
    
    Run Code Online (Sandbox Code Playgroud)

    或者:

    git checkout hello --        (get the branch)
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    更确切地说,这种git checkout,理想的拼写,是混帐将文件复制到你的工作树的请求从Git的指数。这意味着破坏我的文件。您还可以运行,在其中向命令添加提交哈希 ID 5。这告诉 Git 从该提交中复制文件,首先复制到 Git 的索引,然后复制到您的工作树。这也意味着破坏我的文件:不同之处在于 Git 获取它正在提取的文件的副本。git checkout -- pathsgit checkout tree-ish -- paths

    如果你运行git add了某个文件并因此将它复制到 Git 的索引中,你需要从当前提交中取回它。Git索引中的副本是您编辑过的副本。因此,带有提交哈希 ID(或名称)、可选的和文件名的这两种形式是不安全的破坏我的文件形式。git checkout HEAD -- filegit addgit checkoutHEAD--

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

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

    • git reset [ --hard | --mixed | --soft ] [ commit ]

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

      让我们用一张图来展示它。假设我们有:

      ...--E--F--G--H   <-- branch
      
      Run Code Online (Sandbox Code Playgroud)

      也就是说,这个分支,命名branch,有四个提交其哈希标识,我们称之为两端EFG,并H按此顺序。该名称branch当前存储这些提交中最后一个的哈希 ID H。如果我们使用git reset --hard HEAD~3,我们是在告诉 Git 弹出最后三个提交。结果是:

             F--G--H   ???
            /
      ...--E   <-- branch
      
      Run Code Online (Sandbox Code Playgroud)

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

      HEAD~3这个命令的一部分是我们如何选择删除最后三个提交。它是 Git 整个子主题的一部分,记录在gitrevisions 手册中,关于命名特定提交的方法。reset 命令只需要实际提交的哈希 ID 或任何等效的东西,这HEAD~3意味着返回三个第一父步骤,在这种情况下,我们从提交H返回到提交E

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

      • --soft告诉 Git:不要管这两个。Git 会在不触及索引或我们的工作树的情况下移动分支名称。如果您git commit现在运行,索引中(仍然)的内容就是进入提交的内容。如果该指数在提交快照匹配H,这可以让你一个新的提交,其快照H,但其母公司E,仿佛提交FH已全部合并为一个新的提交。人们通常称之为挤压

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

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

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

    • git reset [ tree-ish ] -- path

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

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

      还有其他几种形式git reset。要了解它们的含义,请查阅文档

  • git restore: 这从git checkout.

    基本上,这与各种形式的git checkoutgit resetclobber 文件(在您的工作树和/或 Git 的索引中)做同样的事情。它比旧的-and-clobber-my-work 变体更聪明git checkout,因为您可以在一个命令行中选择文件的来源去向。

    要做你以前做的事情,你只需运行。(与 一样,在大多数情况下,您可以省略该部分,但养成使用它的习惯通常是明智的。例如,此命令的设计使得只有命名的文件实际上是有问题的。)git checkout -- filegit restore --staged --worktree -- file--git checkoutgit add-whatever

    要做你以前做的事情,你只需运行。也就是说,你告诉复制到暂存区/索引,这是如何操作的。git reset -- filegit restore --staged -- filegit restoreHEADgit reset

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

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

  • git switch:这只是git checkout. 这就是你需要知道的全部。使用git switch, 不使用,--forceGit 不会覆盖您未保存的工作,即使您打错字或其他什么。旧git checkout命令可能会覆盖未保存的工作:如果您的拼写错误将分支名称转换为文件名,例如,哎呀。

  • git revert(为了完整性,我添加了这个):这会产生一个新的 commit。新提交的重点是取消某人在某个现有提交中所做的事情。因此,您需要命名还原应该退出的现有提交。这个命令可能应该被命名为git backout.

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

      ...--G--H   <-- branch
    
    Run Code Online (Sandbox Code Playgroud)

    变成:

      ...--G--H--?   <-- branch
    
    Run Code Online (Sandbox Code Playgroud)

    其中 commit ?(H-bar) “撤消”提交H,因此给我们留下与commit相同的文件G。但是我们不必撤消最近的提交。我们可以采取:

      ...--E--F--G--H   <-- branch
    
    Run Code Online (Sandbox Code Playgroud)

    并添加一个?撤销的提交E以获得:

      ...--E--F--G--H--?   <-- branch
    
    Run Code Online (Sandbox Code Playgroud)

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


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

2即便如此,“已删除”的提交仍未消失:您可以将其取回。不过,这个答案不会涵盖如何做到这一点。此外,这git commit --amend是一个特殊情况,我们将提及,但在这里并未真正涵盖。

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

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

5这是一个树形而不是提交形的原因是你可以使用任何指定一些现有内部 Git对象的东西。但是,每个提交都有一个已保存的快照,它适用于此处,并且通常会放在此处。

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

  • 非常感谢写出如此详细的答案的努力! (32认同)
  • 关于“谢谢你”是否适合SO或只是浪费别人的时间(所以应该谨慎使用)存在一些争议,但谢谢你。很好的答案!(我并不是说没有真正的优点 - 你已经整合了分散在太多其他地方的大量信息) (6认同)
  • 我读过的关于 Git 的最好的讨论/描述/解释。恭喜,谢谢! (3认同)
  • @Rusi:“git switch”相当于“git checkout &lt;tag&gt;”,只是“git switch --detach &lt;tag&gt;”。switch 命令需要您提供 `--detach` 标志,以便您确认您了解结果是一个分离的 HEAD。 (2认同)

mse*_*any 5

作为快速参考的绘图可能会有所帮助(箭头代表更新位置和来源):




git恢复参考图




请注意,这些git命令还需要其他参数,例如path.
例子:

git restore . 
git add .
git commit -m "commit name here"
git restore --staged .
Run Code Online (Sandbox Code Playgroud)

这不是对OP问题的直接回答,而是关于restore部分的回答,因为搜索结果git restore直接到这里。reset部分已经在其他答案中解释了。