git switch和git checkout <branch>有什么区别?

832

Git 2.23 引入了一个新命令git switch——阅读文档后,它似乎与git checkout <branchname>基本相同。有人可以解释一下区别或使用情况吗?

引入了两个新命令“git switch”和“git restore”,将“检出要推进其历史记录的分支”和“从索引和/或树状结构中检出路径以在推进当前历史记录上工作”从单个“git checkout”命令中拆分出来。


17
关于这个主题,InfoQ上有一篇很好的文章:https://www.infoq.com/news/2019/08/git-2-23-switch-restore/ - rsenna
13
Git 团队是否计划弃用 git checkout?在使用时我并未看到弃用警告。但是随着 git switchgit restore 的推出,我现在已经看不到使用 checkout 的必要性了。然而,如果它没有被弃用,Git 团队只是使事情变得更加混乱,而不是更简单。是否有人知道 git checkout 的计划?如果它仍然有用途,请问有人能够添加或编辑答案以详细说明其用途吗? - Mike Williamson
4
@MikeWilliamson 我不这么认为,你仍需要使用 git checkout <commit> - Bastian Venthur
4
没错,谢谢!不过,这意味着 checkout 只会因为它的少数情况而继续存在。这将延长混淆的时间。但我想我们只能互相帮助来解决这个问题 :) - Mike Williamson
7
你可以使用命令 git switch --detach <commit> - benftwc
显示剩余2条评论
9个回答

746

根据您提供的文档,它的唯一目的是分离和澄清两种不同使用方式的git checkout:

  • git switch 现在可以用来更改分支,就像git checkout<branchname>一样
  • git restore 可以用来将文件重置为某个版本,就像git checkout --<path_to_file>一样

人们对于这些不同的git checkout使用方式感到困惑,正如您在Stackoverflow上看到的关于git checkout的许多问题所示。Git开发人员似乎已经考虑到了这一点。


235
这似乎是一个不错的改变。创建一个分支?git checkout 切换分支?git checkout 获取某个文件的特定版本?git checkout 撤销对一个文件的更改?git checkout 说实话,我在思考能否用 git checkout 的各种标志完成正常的 git 工作流程。 - Captain Man
27
那么现在的想法是,git checkout 在技术上已经不再需要用于任何事情了吗?还是它仍然用于某些特定的事情,比如检出一个不是分支头的提交(进入“分离头”模式)? - PieterNuyts
22
@Mike 在你说 checkout 会创建分支后,又怎么能说 checkout 不会创建分支呢?不管 -b 标志的内部工作方式如何,它仍然会创建一个分支。 - Captain Man
27
@Mike,我理解你对于操作和命令的区别的看法,但是这次讨论是关于命令的。除非我们使用超低级别的API,否则我们无法直接访问“操作”。我们只能访问命令。在这里强调这个区别没有必要。 - Captain Man
76
提示:对于习惯使用 git checkout -b <分支名称> 的人,您可以使用 git switch -c <分支名称> 来实现相同的效果。 - Default
显示剩余10条评论

244

git checkout 是一个瑞士军刀般的工具,因为它有几个不相关的用途。

如果您修改了一个文件但尚未暂存更改,则 git checkout <filename> 将撤消这些修改... 这是一种取消文件更改的快速简便方法。您仍然停留在同一分支。

git checkout <branchname>(如您所指出的)则切换分支。

这是两个完全不同的目的,如果文件名和分支名相似,可能会导致混淆。

将其作为两个命令使用更清晰易懂。


正如您所提到的,分支和文件具有相同的名称会令人困惑。我假设分支优先于文件,因为这通常更加理想?或者是怎样运作的呢? - AgentM
@AgentM 是的,没错。如果分支和文件同名,那么执行 git checkout <name> 会优先选择分支而不是文件。 - Kartik Soneji
8
如果您想要检出文件而非分支,您需要在选项中使用" -- "将它们分开。这是许多Git和其他Unix命令的常用习语。 - bjhend
5
@bjhend和SteveTurczyn,git checkout <filename>常常可以正常工作,但是更好的方法是使用git checkout -- <filename>,因为--明确地告诉git解析器已经传递给git checkout的选项已经结束,而且一个文件或目录路径列表已经开始了。这可能很重要,例如,如果您的文件名以破折号开头,比如-myfile。在这种情况下,执行git checkout -- -myfile应该可以正常工作,如果没有前面的---myfile看起来就像是传递给git checkout的一个乱七八糟的选项。 - Gabriel Staples

235
switch命令确实与checkout命令执行相同的操作,但仅适用于切换分支的情况。特别是,与checkout不同的是,它不能恢复工作树文件,而是使用与switch同时引入的restore命令来完成。

详细解释

正如您在引用的2.23.0版本发布说明中所指出的,switchrestore命令被引入以将checkout命令拆分为两个独立的部分:

  • “切换到一个分支以推进其历史记录”
  • “从索引和/或树状对象中检出路径以推进当前历史记录”

换句话说,checkout执行两个不同的操作,而此版本将每个操作拆分为自己专注的命令。

可以从文档中的摘要描述中看出checkout的这种双重目的:

git-checkout - 切换分支或恢复工作树文件

添加了switch命令的提交在其提交消息中解释了新命令的原因:

“git checkout”做太多事情会让许多用户感到困惑(甚至有时候会让老手们感到困惑)。为了解决这个问题,该命令将被拆分为两个新命令:switch和restore。好老的“git checkout”命令仍然存在,并且将一直存在,直到所有用户(或大多数用户)对其感到厌倦。

从这个说明可以清楚地看出,引入新命令是为了通过拥有两个专注的命令而减少混淆,而不是一个多功能命令。
请注意(截至2023年9月),新命令仍被列为实验性(switchrestore):

此命令是实验性的。行为可能会发生变化。

命令比较

我在任何地方都没有找到完整的命令比较。通过阅读文档,我认为这应该是一个相当完整的比较。
以前的命令 新的命令
git checkout <branch> git switch <branch>
git checkout N/A(使用git status
git checkout -b <new_branch> [<start_point>] git switch -c <new-branch> [<start-point>]
git checkout -B <new_branch> [<start_point>] git switch -C <new-branch> [<start-point>]
git checkout --orphan <new_branch> git switch --orphan <new-branch>
git checkout --orphan <new_branch> <start_point> N/A(先使用git switch <start-point>,然后使用git switch --orphan <new-branch>
git checkout [--detach] <commit> git switch --detach <commit>
git checkout --detach [<branch>] git switch --detach [<branch>]
git checkout [--] <pathspec>… git restore [--] <pathspec>…
git checkout --pathspec-from-file=<file> git restore --pathspec-from-file=<file>
git checkout <tree-ish> [--] <pathspec>… git restore -s <tree> [--] <pathspec>…
git checkout <tree-ish> --pathspec-from-file=<file> git restore -s <tree> --pathspec-from-file=<file>
git checkout -p [<tree-ish>] [--] [<pathspec>…] git restore -p [-s <tree>] [--] [<pathspec>…]
如本比较所示,一些先前的用法可以通过将旧命令名称(checkout)替换为新命令名称(switchrestore)来转换,而其他一些则需要额外的调整。值得注意的变化包括:
  • 在切换之前创建新分支的选项-b/-B已更名为-c/-C。它们还有长选项变体(--create/--force-create),而以前它们只有单字母选项版本。
  • 切换到分离头部时现在始终需要使用--detach(或-d),而以前对于提交来说是可选的,但对于分支来说是必需的。
  • 恢复的源树现在由-s(或--source)选项给出,而不是作为内联参数。
  • 使用--force(或-f)进行切换现在如果存在未合并的条目将会失败,而不是忽略它们。--force也已更名为--discard-changes,而--force仍然保留作为别名。

7

switch 有一些限制:目前您可以从任何提交切换到 <分支名称>,但无法从带有 detached HEAD 状态的 <分支名称> 切换到特定的提交。因此,您需要使用 git checkout 5efb(其中5efb是任意提交的哈希引用示例)。


22
我认为这实际上是一种特性而不是缺陷(限制)。 switch 的创建目的是用于切换分支,当你这样做时,你确实希望处于该分支的 HEAD。checkout 是一种更通用的操作,可以将您的工作副本与历史记录中的任何给定状态(=提交)保持一致。由于任何分支名称都是该分支的 HEAD 提交的别名,因此检出分支在技术上与检出任何其他提交没有区别。 - Mike
71
通过使用 -d 参数,您可以执行以下操作:git switch -d 6c13 - Mendi Barel

2

简而言之: 当使用checkout命令时,加上--force选项,你可以在合并过程中切换分支。 但是如果使用switch命令,就不能这样做。

细节:

其他回答已经解释了将checkout分成switchrestore的原因,以及存在语法用法差异的事实。(例如,您可以直接使用checkout与提交或远程跟踪分支(如origin/main),但是对于switch,您还必须显式指定--detach选项。)然而,我至少发现了一个重要的功能区别。

git switch -f文档描述如下:

即使索引或工作树与 HEAD 不同也要进行更改。 索引和工作树都将恢复为匹配的转换目标。 如果指定了--recurse-submodules,则子模块内容也将恢复为匹配的转换目标。 这用于丢弃本地更改。

同样,git checkout -f文档描述如下(重点在最后一句):

当切换分支时,即使索引或工作树与 HEAD 不同,甚至有未跟踪的文件阻碍时,也要继续进行。 这用于丢弃本地更改以及任何妨碍途中未跟踪的文件或目录。

从索引检出路径时,不要在未合并的条目上失败; 相反,未合并的条目将被忽略。

这似乎适用于checkout的另一种含义,即restore命令等效版本。 但是,在尝试在当前处于合并过程中的情况下切换分支时,git checkout -f将成功,即使您此时存在未解决的冲突。 而如果您当前正在合并(即使没有冲突),git switch -f根本无法工作,因为您将收到以下错误消息:

fatal: cannot switch branch while merging

注意:此差异是使用Git版本2.37.1进行测试的。


1
那是一个很好的发现。 - M. Justin
1
restore 命令中没有 --force 选项,这表明它可能是为了分支切换场景而存在的,而不是文件恢复场景。 - M. Justin
1
@TTT,已完成。 就你的回答而言,这是否意味着checkout -f等同于git reset --hard; git switch <branch>(在switch中不带有-f)?或者我忽略了一些副作用? - Mike
1
@Mike 是的,那应该有相同的效果。(我不确定它是否完全相同,但我想不出它可能有什么不同。)这也是一个很好的回答陈述方式:switch -f 不像 checkout -f 那样尝试重置。 - TTT

1
M.Justin的回答真的很棒。然而,我刚刚注意到git checkout --orphangit switch --orphan之间的行为有所不同。
$ git checkout --orphan new-branch
$ git status

On branch new-branch

No commits yet

Changes to be committed:
.....

将切换到 new-branch 分支并保留已跟踪的文件。

而使用 switch 将删除所有已跟踪的文件,创建一个全新的分支。

$ git switch --orphan new-branch
$ git status
Switched to a new branch 'new-branch'

我的 Git 版本是 2.34.1。

0
除了其他用户解释的相似之处和差异之外,如果您的意图是“只是更改分支”,那么git命令switch可能比checkout更安全。
使用checkout时,您可能会意外地将正在进行中的工作文件“还原”为其原始状态(然后它就消失了)就像在这种情况下一样。像输入add而不是checkout这样的单个错误可能导致更改丢失(我经常更改分支并创建提交,因此犯了这个错误)。
因此,使用switch命令可以防止这种更改丢失。

-4
这是从Git手册中提取的一部分——man git-switch

Synopsis

git switch [<options>] [--no-guess] <branch>
git switch [<options>] --detach [<start-point>]
git switch [<options>] (-c|-C) <new-branch> [<start-point>]
git switch [<options>] --orphan <new-branch>

Description

Switch to a specified branch. The working tree and the index are updated to match the branch. All new commits will be added to the tip of this branch.

Optionally a new branch could be created with either -c, -C, automatically from a remote branch of same name (see --guess), or detach the working tree from any branch with --detach, along with switching.

Switching branches does not require a clean index and working tree (i.e. no differences compared to HEAD). The operation is aborted however if the operation leads to loss of local changes, unless told otherwise with --discard-changes or --merge.

THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.


-4

git switch ~= git checkout -b [branch]git checkout [branch] 会让你进入分离头指针状态。


2
-b 标志创建一个新分支。如果目标不是分支名称,则 git checkout [branch] 仅会给您一个分离的 HEAD。如果它是一个分支名称,它将检出它,与 git switch 相同。 - M. Justin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接