什么是“git restore”命令?“git restore”和“git reset”有什么区别?
What is the `git restore` command and what is the difference between `git restore` and `git reset`?
当我想取消暂存文件时,我的所有 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 reset
和 git restore
之间的区别,但似乎没有什么符合我的问题。
我在“”中展示了git restore
(仍然标记为"experimental"),最近Git 2.23(2019年8月)。
它有助于将 git checkout
分成两个命令:
- 一个文件(
git restore
),可以覆盖git reset
个案例。
- 一个用于分支 (
git switch
, as seen in "Confused by git checkout"),它只处理分支,不处理文件。
如 reset, restore and revert 文档所述:
There are three commands with similar names: git reset
, git restore
and git revert
.
git-revert
is about making a new commit that reverts the changes made by other commits.
git-restore
is about restoring files in the working tree from either the index or another commit.
This command does not update your branch.
The command can also be used to restore files in the index from another commit.
git-reset
is about updating your branch, moving the tip in order to add or remove commits from the branch. This operation changes the commit history.
git reset
can also be used to restore the index, overlapping with git restore
.
所以:
To restore a file in the index to match the version in HEAD (this is the same as using git-reset
)
git restore --staged hello.c
or you can restore both the index and the working tree (this the same as using git-checkout
)
git restore --source=HEAD --staged --worktree hello.c
or the short form which is more practical but less readable:
git restore -s@ -SW hello.c
使用 Git 2.25.1(2020 年 2 月),“git restore --staged
”未正确更新缓存树结构,导致之后写入虚假树,已更正.
参见 discussion。
参见 commit e701bab (08 Jan 2020) by Jeff King (peff
)。
(由 Junio C Hamano -- gitster
-- in commit 09e393d 合并,2020 年 1 月 22 日)
restore
: invalidate cache-tree when removing entries with --staged
Reported-by: Torsten Krah
Signed-off-by: Jeff King
When "git restore --staged
" removes a path that's in the index, it marks the entry with CE_REMOVE,
but we don't do anything to invalidate the cache-tree.
In the non-staged case, we end up in checkout_worktree()
, which calls remove_marked_cache_entries()
. That actually drops the entries from the index, as well as invalidating the cache-tree and untracked-cache.
But with --staged
, we never call checkout_worktree()
, and the CE_REMOVE
entries remain. Interestingly, they are dropped when we write out the index, but that means the resulting index is inconsistent: its cache-tree will not match the actual entries, and running "git commit
" immediately after will create the wrong tree.
We can solve this by calling remove_marked_cache_entries()
ourselves before writing out the index. Note that we can't just hoist it out of checkout_worktree()
; that function needs to iterate over the CE_REMOVE
entries (to drop their matching worktree files) before removing them.
One curiosity about the test: without this patch, it actually triggers a BUG() when running git-restore:
BUG: cache-tree.c:810: new1 with flags 0x4420000 should not be in cache-tree
But in the original problem report, which used a similar recipe, git restore
actually creates the bogus index (and the commit is created with the wrong tree). I'm not sure why the test here behaves differently than my out-of-suite reproduction, but what's here should catch either symptom (and the fix corrects both cases).
随着 Git 2.27(2020 年第二季度),“git restore --staged --worktree
”现在默认从 "HEAD" 中取出内容,而不是犯错了。
参见 commit 088018e (05 May 2020) by Eric Sunshine (sunshineco
)。
(由 Junio C Hamano -- gitster
-- in commit 4c2941a 合并,2020 年 5 月 8 日)
restore
: default to HEAD when combining --staged and --worktree
Signed-off-by: Eric Sunshine
Reviewed-by: Taylor Blau
By default, files are restored from the index for --worktree
, and from HEAD for --staged
.
When --worktree
and --staged
are combined, --source
must be specified to disambiguate the restore source, thus making it cumbersome to restore a file in both the worktree and the index.
(Due to an oversight, the --source
requirement, though documented, is not actually enforced.)
However, HEAD is also a reasonable default for --worktree
when combined with --staged
, so make it the default anytime --staged
is used (whether combined with --worktree
or not).
所以现在,这有效:
git restore --staged --worktree
git restore -SW
第一个问题"What is git-restore?":
git-restore 是一种还原未提交更改的工具。未提交的更改是:a) 工作副本中的更改,或 b) 索引中的内容(a.k.a。暂存区)。
此命令在 git 2.23 中引入(与 git-switch 一起)以分离以前在 git-checkout 中联合的多个问题。
git-restore 可以在三种不同的模式下使用,具体取决于您是否喜欢在工作副本、索引或两者中还原工作。
git restore [--worktree] <file>
用索引 (*) 中的内容覆盖工作副本中的 。换句话说,它会还原您在工作副本中所做的更改。是否指定 --worktree
并不重要,因为如果您不另行说明,它就是隐含的。
git restore --staged <file>
用本地存储库中的当前 HEAD 覆盖索引中的 。换句话说,它取消了先前暂存的内容。到目前为止,它确实相当于旧的git reset HEAD <file>
。
要用当前 HEAD 覆盖工作副本和索引,请使用 git restore --staged --worktree --source HEAD <file>
。此版本同时执行:将您的工作副本还原为 HEAD 并取消暂存之前暂存的工作。
你的第二个问题"What's the difference between git-restore and git-reset?":
这两个命令之间有重叠,也有区别。
两者都可以用来修改你的工作副本and/or暂存区。但是,只有 git-reset 可以修改您的存储库。从这个意义上说,如果您只想恢复本地工作,git-restore 似乎是更安全的选择。
差异比较多,这里就不一一列举了
(*) 未 add
编入索引的文件仍被视为在索引中,但是在当前 HEAD 修订版中处于 "clean" 状态。
要添加到 ,并将所有相关命令纳入图片,按字母顺序排列,我将介绍:
git checkout
git reset
git restore
git switch
我再放一个,名字不对的 git revert
。
从end-user的角度来看
您需要的是git checkout
、git reset
和git revert
。这些命令一直在 Git 中。
但是 git checkout
实际上有两种 操作模式 。一种模式是“安全的”:它不会意外破坏任何未保存的工作。另一种模式是“不安全的”:如果你使用它,并且它告诉 Git 清除一些未保存的文件,Git 假设(a)你知道它意味着那个并且(b)你确实这样做了意思是清除你未保存的文件,所以Git立即清除你未保存的文件。
这不是很友好,所以 Git 的人最终——经过多年的用户抱怨——将 git checkout
分成两个新命令。这导致我们:
从历史的角度来看
git restore
是 new,于 2019 年 8 月在 Git 2.23 中首次出现。 git reset
很老了,一直在 Git 中,可以追溯到 2005 年之前。这两个命令都具有销毁未保存工作的能力。
git switch
命令也是新的,在 Git 2.23 中与 git restore
一起引入。它实现了 git checkout
的“安全一半”; git restore
实现“不安全的一半”。
你什么时候使用哪个命令?
这是最复杂的部分,要真正理解它,我们需要知道以下几点:
Git 实际上就是 提交 。提交存储在 Git 存储库中。 git push
和 git fetch
命令 t运行sfer commits——整个提交,作为 all-or-nothing deal1——给另一个Git。您要么拥有所有提交,要么没有。其他命令,例如 git merge
或 git rebase
,都适用于 local 提交。 pull
命令 运行s fetch
(获取提交)后跟第二个命令,一旦它们是本地的就处理提交。
新提交添加到存储库。您几乎从不 删除 提交 来自 存储库。此处列出的五个命令(checkout、reset、restore、revert 和 switch)中只有一个能够删除提交。2
每个提交都由其 哈希 ID 编号,这对于该特定提交是唯一的。它实际上是根据 在 提交的内容计算得出的,这就是 Git 如何使这些数字在所有 Git 的所有地方工作。这意味着提交中的内容将永远冻结:如果您更改任何内容,您得到的是具有新编号的新提交,而旧提交仍然存在,具有相同的旧编号。
每个提交存储两件事:快照和元数据。元数据包括一些先前提交的哈希 ID。这使得提交形成 backwards-looking 链。
A b运行ch name 保存了一次提交的哈希 ID。这使得 b运行ch name find 提交,这反过来意味着两件事:
- 该特定提交是该 b运行ch 的 提示提交;和
- 导致并包括该提示提交的所有提交都在 on that b运行ch.
稍后我们还将讨论 Git 的 index,以及您的 working tree。它们与这些不同,但值得提早提及,尤其是因为索引具有三个名称:Git 有时称其为 index,有时称其为 staging area,有时(现在很少见)将其称为 cache。这三个名字指的是同一个东西。
从 b运行ch name 开始的所有内容,我认为最好通过图片来理解(至少对大多数人而言)。如果我们绘制一系列提交,较新的提交向右,每次提交使用 o
并省略 space 或其他任何内容的一些提交,我们会得到这样的结果:
o--o---o <-- feature-top
/ \
o--o--o--o--...--o---o--o <-- main
\ /
o--o--...--o--o <-- feature-hull
如您所见,这是一个船库。一共有三个 b运行ches。主线 b运行ch 包含 每个提交 ,包括顶行和底行(外壳)上的所有提交。 feature-top
b运行ch 包含前三个提交以及左侧主线上的三个提交,但不包含底行的任何提交。 between commits 的所有连接器都是——好吧,应该是 但我没有足够好的字体——one-way 箭头,指向左,或down-and-left,或up-and-left。
这些“箭头”,或者一种连接方式从提交到提交,在技术上是 arcs, or one-way edges, in a directed graph. This directed graph is one without cycles, making it a Directed Acyclic Graph or DAG,它有一堆对 Git.
有用的属性
如果您只是使用 Git 在提交中存储文件,那么您真正关心的是轮次 o
nodes or vertices (again two words for the same thing),每个轮次都用于存储您的文件,但你至少应该隐约知道他们是如何被争论的运行。这很重要,尤其是因为 合并 。合并提交是那些有两个传出弧的提交,向后指向 Git 调用的两个 parent 提交 。 child 提交是“较晚”的提交:正如人类 parent 总是比他们的 children 更老一样,Git parent 提交也比他们的更早child仁.
不过,我们还需要一件事:新提交来自哪里?我们注意到提交中的内容——快照、保存所有文件和元数据, 保留有关提交的其余信息 Git — 全部 read-only。您的文件不仅被冻结,而且还 t运行sformed,然后 t运行sformed 数据 de-duplicated,因此即使每次提交都有 每个 文件的完整快照,存储库本身仍然相对较小。但这意味着 in 的文件只能被 Git 读取 read,而 nothing[=573] =]——甚至 Git 本身也不能 写 给他们。他们得救一次,从此就是de-duplicated。提交充当档案,几乎像 tar 或 rar 或 winzip 或其他任何东西。
要使用 Git 存储库,我们必须 Git 提取 文件。这会将一些提交的文件 out ,将那些特殊的 archive-formatted 东西变成常规的可用文件。请注意,Git 可能能够存储您的计算机字面上 不能 存储的文件:一个典型的例子是一个名为 aux.h
的文件,对于某些 C 程序, 在 Windows 机器上。我们不会详细介绍所有细节,但理论上仍然可以使用这个存储库完成工作,它可能是在 Linux 系统上构建的,即使你在 Windows 系统上也是如此你不能直接使用 aux.h
文件。
无论如何,假设没有像 aux.h
这样令人讨厌的小惊喜,你只需 运行 git checkout
或 git switch
来获得一些提交 out 个 Git。这将填充您的工作树,从存储在某些b运行ch 的提示提交 中的文件填充它。 tip commit 再次是 last 提交 b运行ch,由 b 发现运行通道名称。您的 git checkout
或 git switch
选择该提交作为 当前提交 ,方法是选择 b运行ch 名称作为 当前b运行ch。您现在拥有所有提交的 来自 的文件,在您可以看到并处理它们的区域:您的 工作树 .
请注意,您的工作树 中的文件实际上并不在 Git 本身 中。它们只是 从 Git 中提取出来的。这很重要,因为当 git checkout
从 Git 中提取文件 时,它实际上将每个文件放在两个地方。其中一个地方是您看到和处理/使用的普通日常文件。另一个地方 Git 将每个文件放入 Git 的 index.
刚才我也提到了,索引有三个名字:索引、暂存区、缓存。所有指的是同一件事:Git 位置粘贴每个文件的这些“副本”。每个实际上都是de-duplicated之前的,所以“复制”这个词有点不对,但是——不像它的大部分其他内部结构——Git实际上很好地隐藏了de-duplication方面。除非你tar t 进入像 git ls-files
和 git update-index
这样的内部命令,否则你不需要知道这部分,并且可以将索引视为持有文件,准备进入下一次提交。
这一切对你来说意味着 使用 Git 是索引 / staging-area 作为你的 提议的下一个提交。当你运行git commit
时,Git将打包这些个文件副本作为要在快照中存档的副本。您在工作树中拥有的副本是 您的; index / staging-area 副本是 Git的,准备出发。因此,如果您更改您的副本并希望更改副本成为下一个快照中的副本,您必须告诉Git: 更新 Git 副本,在 Git 索引 / staging-area. 你用 git add
.3 git add
命令意味着 使 proposed-next-commit 副本匹配 working-tree 副本 。这是执行更新的 add
命令:这是 Git 压缩和 de-duplicates 文件并使其准备好存档的时候,而不是在git commit
次。4
然后,假设您有一系列以 hash-N
:
结尾的提交
[hash1] <-[hash2] ... <-[hashN] <--branch
你 运行 git commit
,给它任何它需要的元数据(提交日志消息),然后你得到第 N+1 次提交:
[hash1] <-[hash2] ... <-[hashN] <-[hashN+1] <--branch
Git 自动更新 b运行ch name 以指向 new commit,它有因此 添加到 b运行ch.
现在让我们逐一查看各种命令:
git checkout
: 这是一个大而复杂的命令。
我们已经看过这个,或者至少 一半。我们用它来挑选出一个 b运行ch 名称,并因此挑选出一个特定的提交。这种检查首先查看我们当前的提交、索引和工作树。它确保我们已经提交了所有修改过的文件,或者——这部分变得有点复杂——如果我们还没有提交所有修改过的文件,切换到另一个b运行ch 是“安全的”。如果 不 安全,git checkout
会告诉您由于修改了文件而无法切换。如果安全,git checkout
会切换;如果你不想切换,你可以切换回来。 (另请参阅 Checkout another branch when there are uncommitted changes on the current branch)
但是 git checkout
有一个 不安全的 一半。假设您修改了工作树中的某些文件,例如 README.md
或 aux.h
或其他任何文件。您现在回顾一下您所做的更改并思考:不,那是个坏主意。我应该摆脱这种变化。我希望文件恢复原样。
要做到这一点——清除你对README.md
的更改——你可以运行:
git checkout -- README.md
这里的--
部分是可选的。使用它是个好主意,因为它告诉 Git --
之后的部分是 文件名 ,而不是 b 运行通道名称.
假设您有一个 b运行ch 名为 hello
和 的 文件 名为 hello
。什么:
git checkout hello
是什么意思?我们是要求 Git 破坏 文件 hello
以删除我们所做的更改,还是要求 Git 检查 [=350] =]b运行chhello
?为了明确这一点,您必须这样写:
git checkout -- hello (clobber the file)
或:
git checkout hello -- (get the branch)
这种情况下,有b运行ches和同名的文件或目录,是一个特别阴险的案例。它咬了真正的用户。 为什么 git switch
现在存在。 git switch
命令 永远不会破坏我的文件 。它只意味着做安全的git checkout
。
(git checkout
命令也变得更聪明了,所以如果你有新命令并且你 运行 那种“坏”的模棱两可 git checkout
,Git 只会抱怨你什么都不做。要么使用更聪明的 split-up 命令,要么在正确的地方添加 --
来选择你想要的操作类型。)
更准确地说,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
编辑的副本。所以这两种形式的 git checkout
,带有提交哈希 ID(或名称 HEAD
),可选的 --
,和文件名,是不安全的 clobber my文件 表格。
git reset
:这也是一个又大又复杂的命令
根据您的计数方式,git reset
有多达五六种不同的形式。我们将在这里集中讨论一个较小的子集。
git 重置 [ --hard | --混合 | --soft] [<em>提交</em>]
在这里,我们要求 Git 做几件事。首先,如果我们给出一个 commit
参数,例如 HEAD
或 HEAD~3
或类似的参数,我们选择了一个特定的 承诺 Git 应该重置为。这是一种通过将提交从 b运行ch 末尾弹出来 删除提交 的命令。在此处列出的所有命令中,这是唯一一个删除任何提交的命令。另一个命令——git commit --amend
——具有弹出 last 提交的效果,同时换上一个新的替代品,但那个替代品仅限于弹出 一个 提交。
让我们将其显示为绘图。假设我们有:
...--E--F--G--H <-- branch
也就是说,这个名为 branch
的 b运行ch 以四个提交结束,我们将其哈希 ID 称为 E
、F
、G
和 H
的顺序。名称 branch
当前存储这些提交的最后一个哈希 ID H
。如果我们使用 git reset --hard HEAD~3
,我们告诉 Git 弹出 最后三个提交 。结果是:
F--G--H ???
/
...--E <-- branch
名称 branch
现在选择提交 E
,而不是提交 H
。如果我们没有记下(在纸上、白板上、文件中)最后三个提交的哈希 ID,它们就会变得有点难以找到。 Git 确实提供了一种在一段时间内再次找到它们的方法,但大多数情况下它们似乎 消失了 。
此命令的 HEAD~3
部分是我们选择删除最后三个提交的方式。它是 Git 中整个 sub-topic 的一部分,记录在 the gitrevisions manual 中,关于命名特定提交的方法。重置命令只需要实际提交的哈希 ID,或任何等效的东西,HEAD~3
意味着 返回三个 first-parent 步骤 ,在这种情况下让我们从提交 H
回到提交 E
.
git reset
的 --hard
部分是我们如何告诉 Git 如何处理 (a) 它的索引和 (b) 我们的工作树文件。我们这里有三个选择:
--soft
告诉 Git:两个都不要管。 Git 将移动 b运行ch name 而不会触及索引或我们的工作树。如果你现在 运行 git commit
,索引中(仍然)的内容就是进入 new 提交的内容。如果索引与提交 H
中的快照匹配,这将为您提供一个新提交,其 snapshot 为 H
,但其 parent 是 E
,就好像 F
到 H
的提交都已折叠成一个新的提交。人们通常称之为 squashing.
--mixed
告诉 Git:重置你的索引,但不要管我的工作树。 Git 将移动 b运行ch 名称,然后 将索引中的每个文件替换为新选择的提交 中的文件。但是 Git 将单独保留所有 working tree 文件。这意味着就 Git 而言,您可以 start git add
ing 文件来进行新的提交。你的新提交不会匹配 H
除非你 git add
everything,所以这意味着你可以,例如,构建一个新的中间提交,有点像 E+F
或者什么的,如果你想要的话。
--hard
告诉Git:重置你的索引和我的工作树。 Git 将移动 b运行ch 名称,替换其索引中的所有文件,并替换工作树中的所有文件,所有这些都是一件大事。现在就好像您根本没有进行过这三个提交。您不再拥有来自 F
、或 G
、或 H
的文件:您拥有来自提交 E
.
的文件
请注意,如果您省略了这种 (hard/soft/mixed) reset
、Git 的 commit
部分将使用 HEAD
。由于 HEAD
命名 当前提交 (由当前 b运行ch 名称选择),这使 b运行ch 名称本身保持不变:它仍然选择与以前相同的提交。所以这仅对 --mixed
或 --hard
有用,因为 git reset --soft
没有提交哈希 ID,意味着 不要移动 b运行ch 名称,不要更改 Git 的索引,也不要触摸我的工作树 。这些是这种 git reset
可以做的三件事——移动 b运行ch 名称,更改 Git 索引中的内容,以及更改工作树中的内容——你只需排除了所有三个。 Git 什么都不做可以,但何必呢?
git重置[<em>tree-ish</em>]--<em>路径</em>
这是另一种 git reset
我们会在这里关心的。它有点像混合重置,因为它意味着破坏一些文件的索引副本,但在这里你指定破坏哪些文件。它也有点不像混合重置,因为这种git reset
永远不会移动b运行ch名称。
相反,您可以选择要从某处复制的文件。 某处就是你给的tree-ish
;如果您不提供,则 某处 是 HEAD
,即当前提交。这只能将 提议的下一次提交 中的文件恢复为它们在 某些现有提交 中的形式。默认为 current 现有提交,这种 git 重置 - <em>path</em>
具有撤消 git 添加的效果 -- <em>path</em>
.6
git reset
还有其他几种形式。要了解它们的含义,请咨询the documentation.
git restore
:这是从 git checkout
.
中分离出来的
基本上,这与破坏文件的 git checkout
和 git reset
的各种形式相同(在您的工作树 and/or 中 Git 的索引中).它比旧的 git checkout
-and-clobber-my-work 变体 更智能,因为您可以选择文件的来源 和 他们去哪里,都在一个命令行中。
做你以前用 git checkout 做的事 -- <em>file</em>
,你只是 运行 git 恢复 --staged --worktree -- <em>文件</em>
。 (在大多数情况下,您可以省略 --
部分,就像 git checkout
一样,但是养成使用它的习惯通常是明智的。就像 git add
,这个命令是专门设计的这样只有名为 -whatever
的文件实际上是有问题的。)
要做你以前用 git 重置的操作 -- <em>file</em>
,你只需 运行 git 恢复 --staged -- <em>文件</em>
。也就是说,您告诉 git restore
从 HEAD
复制到暂存区/索引,这就是 git reset
的操作方式。
请注意,您可以将某个现有提交的文件复制到 Git 的索引,而无需触及该文件的工作树副本:git restore -- source <em>commit</em> --staged -- <em>file</em>
这样做。你不能用旧的 git checkout
做到这一点,但你 可以 用旧的 git reset
做到这一点,因为 git 重置 <em>commit</em> -- <em>file</em>
。而且,您可以将文件从某个现有提交复制到您的工作树,而无需触及暂存副本:git restore --source <em>commit</em> -- worktree——<em>file</em>
就是这样做的。重叠部分(恢复和重置)存在因为 git restore
是新的,这种恢复是有意义的;也许,理想情况下,我们应该总是在这里使用 git restore
,而不是使用旧的 git reset
做事方式,但是 Git 试图保持向后兼容性。
新功能——从任意源复制到你的工作树,而不触及 Git 的索引/staging-area 副本——就是这样:新的。你以前做不到。 (你可以 运行 git show <em>commit</em>:<em>path</em> > <em>path</em>
,之前,但这不属于我们要检查的五个命令。)
git switch
:这只是 git checkout
的“安全一半”。这就是您真正需要知道的全部。使用 git switch
,不使用 --force
,Git 不会覆盖您未保存的工作,即使您输入错误或其他任何错误。旧的 git checkout
命令可能会覆盖未保存的工作:例如,如果您的拼写错误将 b运行ch 名称转换为文件名,那么糟糕。
git revert
(为了完整起见,我添加了这个):这使得 新提交 。新提交的目的是撤销 某人在某些现有提交中所做的事情。因此,您需要命名 revert 应该退出的现有提交。这个命令可能应该命名为 git backout
.
如果您取消最近的提交,这会恢复到 second-most-recent 快照:
...--G--H <-- branch
变为:
...--G--H--Ħ <-- branch
where commit Ħ
(H-bar) “undoes” commit H
因此给我们留下与 commit [=] 相同的 files 99=]。但我们不必撤消 最近的 提交。我们可以采取:
...--E--F--G--H <-- branch
并添加撤消 E
的提交 Ǝ
以获得:
...--E--F--G--H--Ǝ <-- branch
这可能与任何先前提交的源快照不匹配!
1Git 正在慢慢地发展一种“部分获得”提交的工具,这样您就可以处理带有大量提交的大型存储库而无需等待例如,一次性完成整个提交。现在这不是普通用户会看到的东西,当它出现在普通用户身上时,它意味着 add-on 到提交的基本“全有或全无”模式。它将把它从“你要么有一个提交,要么没有”变成“你有一个提交——要么是全部,要么是其中的一部分,并承诺很快交付其余部分——或者不交付;如果你有一部分提交,您可以使用该部分,但仅此而已。
2即便如此,“已删除”的提交还没有消失:您可以取回它。不过,这个答案不会涵盖如何做到这一点。另外,git commit --amend
是一个特例,我们会提到,但这里没有真正涵盖。
3要从工作树 和 Git 的索引中删除文件,您可以使用 git rm
。如果您从工作树中删除该文件,则该文件名上的 运行 git add
,Git 将“添加”删除,因此也可以。
4如果你使用git commit -a
,Git会在那个时候,运行 git add
文件。这是以一种棘手的方式完成的,可以破坏一些 poorly-written pre-commit 钩子。我建议学习两步过程,部分原因是那些 poorly-written 钩子——尽管我会尽可能避免或修复它们——部分原因只是因为如果 you尽量避免像那些 poorly-written 钩子的作者那样学习 Git 的索引,Git 以后会给你带来更多麻烦。
5这是 tree-ish 而不是 commit-ish[ 的原因=573=] 是你可以使用任何指定一些现有内部 Git tree object 的东西。不过,每个提交都有一个已保存的快照,适合放在这里,通常也是您放在这里的内容。
6与所有这些其他 Git 命令一样,您 可以 在 add
命令和要添加的路径。这实际上是一个很好的习惯,因为这意味着你可以添加一个名为 -u
的路径,如果你有这样一个路径: git add -- -u
意味着 添加名为 [=179] 的文件=] 但 git add -u
根本不是这个意思。当然,名称匹配选项序列的文件比名称匹配 b运行ch 名称的文件更不常见,也不那么令人惊讶:拥有一个 dev
b运行ch [=350 真的很容易=]和一组名为dev/whatever
的文件。由于文件路径将使用目录匹配,对于添加、检出、重置和恢复,这些可能会混淆。 add
命令不使用 b运行ch name,所以在这方面它更安全。
当我想取消暂存文件时,我的所有 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 reset
和 git restore
之间的区别,但似乎没有什么符合我的问题。
我在“git restore
(仍然标记为"experimental"),最近Git 2.23(2019年8月)。
它有助于将 git checkout
分成两个命令:
- 一个文件(
git restore
),可以覆盖git reset
个案例。 - 一个用于分支 (
git switch
, as seen in "Confused by git checkout"),它只处理分支,不处理文件。
如 reset, restore and revert 文档所述:
There are three commands with similar names:
git reset
,git restore
andgit revert
.
git-revert
is about making a new commit that reverts the changes made by other commits.git-restore
is about restoring files in the working tree from either the index or another commit.
This command does not update your branch.
The command can also be used to restore files in the index from another commit.git-reset
is about updating your branch, moving the tip in order to add or remove commits from the branch. This operation changes the commit history.
git reset
can also be used to restore the index, overlapping withgit restore
.
所以:
To restore a file in the index to match the version in HEAD (this is the same as using
git-reset
)git restore --staged hello.c
or you can restore both the index and the working tree (this the same as using
git-checkout
)git restore --source=HEAD --staged --worktree hello.c
or the short form which is more practical but less readable:
git restore -s@ -SW hello.c
使用 Git 2.25.1(2020 年 2 月),“git restore --staged
”未正确更新缓存树结构,导致之后写入虚假树,已更正.
参见 discussion。
参见 commit e701bab (08 Jan 2020) by Jeff King (peff
)。
(由 Junio C Hamano -- gitster
-- in commit 09e393d 合并,2020 年 1 月 22 日)
restore
: invalidate cache-tree when removing entries with --stagedReported-by: Torsten Krah
Signed-off-by: Jeff KingWhen "
git restore --staged
" removes a path that's in the index, it marks the entry withCE_REMOVE,
but we don't do anything to invalidate the cache-tree.
In the non-staged case, we end up incheckout_worktree()
, which callsremove_marked_cache_entries()
. That actually drops the entries from the index, as well as invalidating the cache-tree and untracked-cache.But with
--staged
, we never callcheckout_worktree()
, and theCE_REMOVE
entries remain. Interestingly, they are dropped when we write out the index, but that means the resulting index is inconsistent: its cache-tree will not match the actual entries, and running "git commit
" immediately after will create the wrong tree.We can solve this by calling
remove_marked_cache_entries()
ourselves before writing out the index. Note that we can't just hoist it out ofcheckout_worktree()
; that function needs to iterate over theCE_REMOVE
entries (to drop their matching worktree files) before removing them.One curiosity about the test: without this patch, it actually triggers a BUG() when running git-restore:
BUG: cache-tree.c:810: new1 with flags 0x4420000 should not be in cache-tree
But in the original problem report, which used a similar recipe,
git restore
actually creates the bogus index (and the commit is created with the wrong tree). I'm not sure why the test here behaves differently than my out-of-suite reproduction, but what's here should catch either symptom (and the fix corrects both cases).
随着 Git 2.27(2020 年第二季度),“git restore --staged --worktree
”现在默认从 "HEAD" 中取出内容,而不是犯错了。
参见 commit 088018e (05 May 2020) by Eric Sunshine (sunshineco
)。
(由 Junio C Hamano -- gitster
-- in commit 4c2941a 合并,2020 年 5 月 8 日)
restore
: default to HEAD when combining --staged and --worktreeSigned-off-by: Eric Sunshine
Reviewed-by: Taylor BlauBy default, files are restored from the index for
--worktree
, and from HEAD for--staged
.When
--worktree
and--staged
are combined,--source
must be specified to disambiguate the restore source, thus making it cumbersome to restore a file in both the worktree and the index.(Due to an oversight, the
--source
requirement, though documented, is not actually enforced.)However, HEAD is also a reasonable default for
--worktree
when combined with--staged
, so make it the default anytime--staged
is used (whether combined with--worktree
or not).
所以现在,这有效:
git restore --staged --worktree
git restore -SW
第一个问题"What is git-restore?":
git-restore 是一种还原未提交更改的工具。未提交的更改是:a) 工作副本中的更改,或 b) 索引中的内容(a.k.a。暂存区)。
此命令在 git 2.23 中引入(与 git-switch 一起)以分离以前在 git-checkout 中联合的多个问题。
git-restore 可以在三种不同的模式下使用,具体取决于您是否喜欢在工作副本、索引或两者中还原工作。
git restore [--worktree] <file>
用索引 (*) 中的内容覆盖工作副本中的 --worktree
并不重要,因为如果您不另行说明,它就是隐含的。
git restore --staged <file>
用本地存储库中的当前 HEAD 覆盖索引中的 git reset HEAD <file>
。
要用当前 HEAD 覆盖工作副本和索引,请使用 git restore --staged --worktree --source HEAD <file>
。此版本同时执行:将您的工作副本还原为 HEAD 并取消暂存之前暂存的工作。
你的第二个问题"What's the difference between git-restore and git-reset?":
这两个命令之间有重叠,也有区别。
两者都可以用来修改你的工作副本and/or暂存区。但是,只有 git-reset 可以修改您的存储库。从这个意义上说,如果您只想恢复本地工作,git-restore 似乎是更安全的选择。
差异比较多,这里就不一一列举了
(*) 未 add
编入索引的文件仍被视为在索引中,但是在当前 HEAD 修订版中处于 "clean" 状态。
要添加到
git checkout
git reset
git restore
git switch
我再放一个,名字不对的 git revert
。
从end-user的角度来看
您需要的是git checkout
、git reset
和git revert
。这些命令一直在 Git 中。
但是 git checkout
实际上有两种 操作模式 。一种模式是“安全的”:它不会意外破坏任何未保存的工作。另一种模式是“不安全的”:如果你使用它,并且它告诉 Git 清除一些未保存的文件,Git 假设(a)你知道它意味着那个并且(b)你确实这样做了意思是清除你未保存的文件,所以Git立即清除你未保存的文件。
这不是很友好,所以 Git 的人最终——经过多年的用户抱怨——将 git checkout
分成两个新命令。这导致我们:
从历史的角度来看
git restore
是 new,于 2019 年 8 月在 Git 2.23 中首次出现。 git reset
很老了,一直在 Git 中,可以追溯到 2005 年之前。这两个命令都具有销毁未保存工作的能力。
git switch
命令也是新的,在 Git 2.23 中与 git restore
一起引入。它实现了 git checkout
的“安全一半”; git restore
实现“不安全的一半”。
你什么时候使用哪个命令?
这是最复杂的部分,要真正理解它,我们需要知道以下几点:
Git 实际上就是 提交 。提交存储在 Git 存储库中。
git push
和git fetch
命令 t运行sfer commits——整个提交,作为 all-or-nothing deal1——给另一个Git。您要么拥有所有提交,要么没有。其他命令,例如git merge
或git rebase
,都适用于 local 提交。pull
命令 运行sfetch
(获取提交)后跟第二个命令,一旦它们是本地的就处理提交。新提交添加到存储库。您几乎从不 删除 提交 来自 存储库。此处列出的五个命令(checkout、reset、restore、revert 和 switch)中只有一个能够删除提交。2
每个提交都由其 哈希 ID 编号,这对于该特定提交是唯一的。它实际上是根据 在 提交的内容计算得出的,这就是 Git 如何使这些数字在所有 Git 的所有地方工作。这意味着提交中的内容将永远冻结:如果您更改任何内容,您得到的是具有新编号的新提交,而旧提交仍然存在,具有相同的旧编号。
每个提交存储两件事:快照和元数据。元数据包括一些先前提交的哈希 ID。这使得提交形成 backwards-looking 链。
A b运行ch name 保存了一次提交的哈希 ID。这使得 b运行ch name find 提交,这反过来意味着两件事:
- 该特定提交是该 b运行ch 的 提示提交;和
- 导致并包括该提示提交的所有提交都在 on that b运行ch.
稍后我们还将讨论 Git 的 index,以及您的 working tree。它们与这些不同,但值得提早提及,尤其是因为索引具有三个名称:Git 有时称其为 index,有时称其为 staging area,有时(现在很少见)将其称为 cache。这三个名字指的是同一个东西。
从 b运行ch name 开始的所有内容,我认为最好通过图片来理解(至少对大多数人而言)。如果我们绘制一系列提交,较新的提交向右,每次提交使用 o
并省略 space 或其他任何内容的一些提交,我们会得到这样的结果:
o--o---o <-- feature-top
/ \
o--o--o--o--...--o---o--o <-- main
\ /
o--o--...--o--o <-- feature-hull
如您所见,这是一个船库。一共有三个 b运行ches。主线 b运行ch 包含 每个提交 ,包括顶行和底行(外壳)上的所有提交。 feature-top
b运行ch 包含前三个提交以及左侧主线上的三个提交,但不包含底行的任何提交。 between commits 的所有连接器都是——好吧,应该是 但我没有足够好的字体——one-way 箭头,指向左,或down-and-left,或up-and-left。
这些“箭头”,或者一种连接方式从提交到提交,在技术上是 arcs, or one-way edges, in a directed graph. This directed graph is one without cycles, making it a Directed Acyclic Graph or DAG,它有一堆对 Git.
有用的属性如果您只是使用 Git 在提交中存储文件,那么您真正关心的是轮次 o
nodes or vertices (again two words for the same thing),每个轮次都用于存储您的文件,但你至少应该隐约知道他们是如何被争论的运行。这很重要,尤其是因为 合并 。合并提交是那些有两个传出弧的提交,向后指向 Git 调用的两个 parent 提交 。 child 提交是“较晚”的提交:正如人类 parent 总是比他们的 children 更老一样,Git parent 提交也比他们的更早child仁.
不过,我们还需要一件事:新提交来自哪里?我们注意到提交中的内容——快照、保存所有文件和元数据, 保留有关提交的其余信息 Git — 全部 read-only。您的文件不仅被冻结,而且还 t运行sformed,然后 t运行sformed 数据 de-duplicated,因此即使每次提交都有 每个 文件的完整快照,存储库本身仍然相对较小。但这意味着 in 的文件只能被 Git 读取 read,而 nothing[=573] =]——甚至 Git 本身也不能 写 给他们。他们得救一次,从此就是de-duplicated。提交充当档案,几乎像 tar 或 rar 或 winzip 或其他任何东西。
要使用 Git 存储库,我们必须 Git 提取 文件。这会将一些提交的文件 out ,将那些特殊的 archive-formatted 东西变成常规的可用文件。请注意,Git 可能能够存储您的计算机字面上 不能 存储的文件:一个典型的例子是一个名为 aux.h
的文件,对于某些 C 程序, 在 Windows 机器上。我们不会详细介绍所有细节,但理论上仍然可以使用这个存储库完成工作,它可能是在 Linux 系统上构建的,即使你在 Windows 系统上也是如此你不能直接使用 aux.h
文件。
无论如何,假设没有像 aux.h
这样令人讨厌的小惊喜,你只需 运行 git checkout
或 git switch
来获得一些提交 out 个 Git。这将填充您的工作树,从存储在某些b运行ch 的提示提交 中的文件填充它。 tip commit 再次是 last 提交 b运行ch,由 b 发现运行通道名称。您的 git checkout
或 git switch
选择该提交作为 当前提交 ,方法是选择 b运行ch 名称作为 当前b运行ch。您现在拥有所有提交的 来自 的文件,在您可以看到并处理它们的区域:您的 工作树 .
请注意,您的工作树 中的文件实际上并不在 Git 本身 中。它们只是 从 Git 中提取出来的。这很重要,因为当 git checkout
从 Git 中提取文件 时,它实际上将每个文件放在两个地方。其中一个地方是您看到和处理/使用的普通日常文件。另一个地方 Git 将每个文件放入 Git 的 index.
刚才我也提到了,索引有三个名字:索引、暂存区、缓存。所有指的是同一件事:Git 位置粘贴每个文件的这些“副本”。每个实际上都是de-duplicated之前的,所以“复制”这个词有点不对,但是——不像它的大部分其他内部结构——Git实际上很好地隐藏了de-duplication方面。除非你tar t 进入像 git ls-files
和 git update-index
这样的内部命令,否则你不需要知道这部分,并且可以将索引视为持有文件,准备进入下一次提交。
这一切对你来说意味着 使用 Git 是索引 / staging-area 作为你的 提议的下一个提交。当你运行git commit
时,Git将打包这些个文件副本作为要在快照中存档的副本。您在工作树中拥有的副本是 您的; index / staging-area 副本是 Git的,准备出发。因此,如果您更改您的副本并希望更改副本成为下一个快照中的副本,您必须告诉Git: 更新 Git 副本,在 Git 索引 / staging-area. 你用 git add
.3 git add
命令意味着 使 proposed-next-commit 副本匹配 working-tree 副本 。这是执行更新的 add
命令:这是 Git 压缩和 de-duplicates 文件并使其准备好存档的时候,而不是在git commit
次。4
然后,假设您有一系列以 hash-N
:
[hash1] <-[hash2] ... <-[hashN] <--branch
你 运行 git commit
,给它任何它需要的元数据(提交日志消息),然后你得到第 N+1 次提交:
[hash1] <-[hash2] ... <-[hashN] <-[hashN+1] <--branch
Git 自动更新 b运行ch name 以指向 new commit,它有因此 添加到 b运行ch.
现在让我们逐一查看各种命令:
git checkout
: 这是一个大而复杂的命令。我们已经看过这个,或者至少 一半。我们用它来挑选出一个 b运行ch 名称,并因此挑选出一个特定的提交。这种检查首先查看我们当前的提交、索引和工作树。它确保我们已经提交了所有修改过的文件,或者——这部分变得有点复杂——如果我们还没有提交所有修改过的文件,切换到另一个b运行ch 是“安全的”。如果 不 安全,
git checkout
会告诉您由于修改了文件而无法切换。如果安全,git checkout
会切换;如果你不想切换,你可以切换回来。 (另请参阅 Checkout another branch when there are uncommitted changes on the current branch)但是
git checkout
有一个 不安全的 一半。假设您修改了工作树中的某些文件,例如README.md
或aux.h
或其他任何文件。您现在回顾一下您所做的更改并思考:不,那是个坏主意。我应该摆脱这种变化。我希望文件恢复原样。要做到这一点——清除你对
README.md
的更改——你可以运行:git checkout -- README.md
这里的
--
部分是可选的。使用它是个好主意,因为它告诉 Git--
之后的部分是 文件名 ,而不是 b 运行通道名称.假设您有一个 b运行ch 名为
hello
和 的 文件 名为hello
。什么:git checkout hello
是什么意思?我们是要求 Git 破坏 文件
hello
以删除我们所做的更改,还是要求 Git 检查 [=350] =]b运行chhello
?为了明确这一点,您必须这样写:git checkout -- hello (clobber the file)
或:
git checkout hello -- (get the branch)
这种情况下,有b运行ches和同名的文件或目录,是一个特别阴险的案例。它咬了真正的用户。 为什么
git switch
现在存在。git switch
命令 永远不会破坏我的文件 。它只意味着做安全的git checkout
。(
git checkout
命令也变得更聪明了,所以如果你有新命令并且你 运行 那种“坏”的模棱两可git checkout
,Git 只会抱怨你什么都不做。要么使用更聪明的 split-up 命令,要么在正确的地方添加--
来选择你想要的操作类型。)更准确地说,
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
编辑的副本。所以这两种形式的git checkout
,带有提交哈希 ID(或名称HEAD
),可选的--
,和文件名,是不安全的 clobber my文件 表格。git reset
:这也是一个又大又复杂的命令根据您的计数方式,
git reset
有多达五六种不同的形式。我们将在这里集中讨论一个较小的子集。git 重置 [ --hard | --混合 | --soft] [<em>提交</em>]
在这里,我们要求 Git 做几件事。首先,如果我们给出一个
commit
参数,例如HEAD
或HEAD~3
或类似的参数,我们选择了一个特定的 承诺 Git 应该重置为。这是一种通过将提交从 b运行ch 末尾弹出来 删除提交 的命令。在此处列出的所有命令中,这是唯一一个删除任何提交的命令。另一个命令——git commit --amend
——具有弹出 last 提交的效果,同时换上一个新的替代品,但那个替代品仅限于弹出 一个 提交。让我们将其显示为绘图。假设我们有:
...--E--F--G--H <-- branch
也就是说,这个名为
branch
的 b运行ch 以四个提交结束,我们将其哈希 ID 称为E
、F
、G
和H
的顺序。名称branch
当前存储这些提交的最后一个哈希 IDH
。如果我们使用git reset --hard HEAD~3
,我们告诉 Git 弹出 最后三个提交 。结果是:F--G--H ??? / ...--E <-- branch
名称
branch
现在选择提交E
,而不是提交H
。如果我们没有记下(在纸上、白板上、文件中)最后三个提交的哈希 ID,它们就会变得有点难以找到。 Git 确实提供了一种在一段时间内再次找到它们的方法,但大多数情况下它们似乎 消失了 。此命令的
HEAD~3
部分是我们选择删除最后三个提交的方式。它是 Git 中整个 sub-topic 的一部分,记录在 the gitrevisions manual 中,关于命名特定提交的方法。重置命令只需要实际提交的哈希 ID,或任何等效的东西,HEAD~3
意味着 返回三个 first-parent 步骤 ,在这种情况下让我们从提交H
回到提交E
.git reset
的--hard
部分是我们如何告诉 Git 如何处理 (a) 它的索引和 (b) 我们的工作树文件。我们这里有三个选择:--soft
告诉 Git:两个都不要管。 Git 将移动 b运行ch name 而不会触及索引或我们的工作树。如果你现在 运行git commit
,索引中(仍然)的内容就是进入 new 提交的内容。如果索引与提交H
中的快照匹配,这将为您提供一个新提交,其 snapshot 为H
,但其 parent 是E
,就好像F
到H
的提交都已折叠成一个新的提交。人们通常称之为 squashing.--mixed
告诉 Git:重置你的索引,但不要管我的工作树。 Git 将移动 b运行ch 名称,然后 将索引中的每个文件替换为新选择的提交 中的文件。但是 Git 将单独保留所有 working tree 文件。这意味着就 Git 而言,您可以 startgit add
ing 文件来进行新的提交。你的新提交不会匹配H
除非你git add
everything,所以这意味着你可以,例如,构建一个新的中间提交,有点像E+F
或者什么的,如果你想要的话。
的文件--hard
告诉Git:重置你的索引和我的工作树。 Git 将移动 b运行ch 名称,替换其索引中的所有文件,并替换工作树中的所有文件,所有这些都是一件大事。现在就好像您根本没有进行过这三个提交。您不再拥有来自F
、或G
、或H
的文件:您拥有来自提交E
.
请注意,如果您省略了这种 (hard/soft/mixed)
reset
、Git 的commit
部分将使用HEAD
。由于HEAD
命名 当前提交 (由当前 b运行ch 名称选择),这使 b运行ch 名称本身保持不变:它仍然选择与以前相同的提交。所以这仅对--mixed
或--hard
有用,因为git reset --soft
没有提交哈希 ID,意味着 不要移动 b运行ch 名称,不要更改 Git 的索引,也不要触摸我的工作树 。这些是这种git reset
可以做的三件事——移动 b运行ch 名称,更改 Git 索引中的内容,以及更改工作树中的内容——你只需排除了所有三个。 Git 什么都不做可以,但何必呢?git重置[<em>tree-ish</em>]--<em>路径</em>
这是另一种
git reset
我们会在这里关心的。它有点像混合重置,因为它意味着破坏一些文件的索引副本,但在这里你指定破坏哪些文件。它也有点不像混合重置,因为这种git reset
永远不会移动b运行ch名称。相反,您可以选择要从某处复制的文件。 某处就是你给的
tree-ish
;如果您不提供,则 某处 是HEAD
,即当前提交。这只能将 提议的下一次提交 中的文件恢复为它们在 某些现有提交 中的形式。默认为 current 现有提交,这种git 重置 - <em>path</em>
具有撤消git 添加的效果 -- <em>path</em>
.6git reset
还有其他几种形式。要了解它们的含义,请咨询the documentation.
中分离出来的git restore
:这是从git checkout
.基本上,这与破坏文件的
git checkout
和git reset
的各种形式相同(在您的工作树 and/or 中 Git 的索引中).它比旧的git checkout
-and-clobber-my-work 变体 更智能,因为您可以选择文件的来源 和 他们去哪里,都在一个命令行中。做你以前用
git checkout 做的事 -- <em>file</em>
,你只是 运行git 恢复 --staged --worktree -- <em>文件</em>
。 (在大多数情况下,您可以省略--
部分,就像git checkout
一样,但是养成使用它的习惯通常是明智的。就像git add
,这个命令是专门设计的这样只有名为-whatever
的文件实际上是有问题的。)要做你以前用
git 重置的操作 -- <em>file</em>
,你只需 运行git 恢复 --staged -- <em>文件</em>
。也就是说,您告诉git restore
从HEAD
复制到暂存区/索引,这就是git reset
的操作方式。请注意,您可以将某个现有提交的文件复制到 Git 的索引,而无需触及该文件的工作树副本:
git restore -- source <em>commit</em> --staged -- <em>file</em>
这样做。你不能用旧的git checkout
做到这一点,但你 可以 用旧的git reset
做到这一点,因为git 重置 <em>commit</em> -- <em>file</em>
。而且,您可以将文件从某个现有提交复制到您的工作树,而无需触及暂存副本:git restore --source <em>commit</em> -- worktree——<em>file</em>
就是这样做的。重叠部分(恢复和重置)存在因为git restore
是新的,这种恢复是有意义的;也许,理想情况下,我们应该总是在这里使用git restore
,而不是使用旧的git reset
做事方式,但是 Git 试图保持向后兼容性。新功能——从任意源复制到你的工作树,而不触及 Git 的索引/staging-area 副本——就是这样:新的。你以前做不到。 (你可以 运行
git show <em>commit</em>:<em>path</em> > <em>path</em>
,之前,但这不属于我们要检查的五个命令。)git switch
:这只是git checkout
的“安全一半”。这就是您真正需要知道的全部。使用git switch
,不使用--force
,Git 不会覆盖您未保存的工作,即使您输入错误或其他任何错误。旧的git checkout
命令可能会覆盖未保存的工作:例如,如果您的拼写错误将 b运行ch 名称转换为文件名,那么糟糕。git revert
(为了完整起见,我添加了这个):这使得 新提交 。新提交的目的是撤销 某人在某些现有提交中所做的事情。因此,您需要命名 revert 应该退出的现有提交。这个命令可能应该命名为git backout
.如果您取消最近的提交,这会恢复到 second-most-recent 快照:
...--G--H <-- branch
变为:
...--G--H--Ħ <-- branch
where commit
Ħ
(H-bar) “undoes” commitH
因此给我们留下与 commit [=] 相同的 files 99=]。但我们不必撤消 最近的 提交。我们可以采取:...--E--F--G--H <-- branch
并添加撤消
E
的提交Ǝ
以获得:...--E--F--G--H--Ǝ <-- branch
这可能与任何先前提交的源快照不匹配!
1Git 正在慢慢地发展一种“部分获得”提交的工具,这样您就可以处理带有大量提交的大型存储库而无需等待例如,一次性完成整个提交。现在这不是普通用户会看到的东西,当它出现在普通用户身上时,它意味着 add-on 到提交的基本“全有或全无”模式。它将把它从“你要么有一个提交,要么没有”变成“你有一个提交——要么是全部,要么是其中的一部分,并承诺很快交付其余部分——或者不交付;如果你有一部分提交,您可以使用该部分,但仅此而已。
2即便如此,“已删除”的提交还没有消失:您可以取回它。不过,这个答案不会涵盖如何做到这一点。另外,git commit --amend
是一个特例,我们会提到,但这里没有真正涵盖。
3要从工作树 和 Git 的索引中删除文件,您可以使用 git rm
。如果您从工作树中删除该文件,则该文件名上的 运行 git add
,Git 将“添加”删除,因此也可以。
4如果你使用git commit -a
,Git会在那个时候,运行 git add
文件。这是以一种棘手的方式完成的,可以破坏一些 poorly-written pre-commit 钩子。我建议学习两步过程,部分原因是那些 poorly-written 钩子——尽管我会尽可能避免或修复它们——部分原因只是因为如果 you尽量避免像那些 poorly-written 钩子的作者那样学习 Git 的索引,Git 以后会给你带来更多麻烦。
5这是 tree-ish 而不是 commit-ish[ 的原因=573=] 是你可以使用任何指定一些现有内部 Git tree object 的东西。不过,每个提交都有一个已保存的快照,适合放在这里,通常也是您放在这里的内容。
6与所有这些其他 Git 命令一样,您 可以 在 add
命令和要添加的路径。这实际上是一个很好的习惯,因为这意味着你可以添加一个名为 -u
的路径,如果你有这样一个路径: git add -- -u
意味着 添加名为 [=179] 的文件=] 但 git add -u
根本不是这个意思。当然,名称匹配选项序列的文件比名称匹配 b运行ch 名称的文件更不常见,也不那么令人惊讶:拥有一个 dev
b运行ch [=350 真的很容易=]和一组名为dev/whatever
的文件。由于文件路径将使用目录匹配,对于添加、检出、重置和恢复,这些可能会混淆。 add
命令不使用 b运行ch name,所以在这方面它更安全。