简体中文 ▾ 主题 ▾ 最新版本 ▾ git-checkout 最后更新于 2.53.0

名称

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

概要

git checkout [-q] [-f] [-m] [<branch>]
git checkout [-q] [-f] [-m] --detach [<branch>]
git checkout [-q] [-f] [-m] [--detach] <commit>
git checkout [-q] [-f] [-m] [[-b|-B|--orphan] <new-branch>] [<start-point>]
git checkout <tree-ish> [--] <pathspec>…​
git checkout <tree-ish> --pathspec-from-file=<file> [--pathspec-file-nul]
git checkout [-f|--ours|--theirs|-m|--conflict=<style>] [--] <pathspec>…​
git checkout [-f|--ours|--theirs|-m|--conflict=<style>] --pathspec-from-file=<file> [--pathspec-file-nul]
git checkout (-p|--patch) [<tree-ish>] [--] [<pathspec>…​]

描述

git checkout 有两种主要模式

  1. 切换分支,使用 git checkout <分支>

  2. 恢复文件的不同版本,例如使用 git checkout <提交> <文件名>git checkout <文件名>

关于 Git 如何决定执行哪种操作,请参阅下文的“参数消歧”。

git checkout [<分支>]

切换到 <分支>。这会将当前分支设置为 <分支> 并更新工作目录中的文件。如果 <分支> 与当前提交内容不同的任何文件存在未提交的更改,检出将失败。否则,未提交的更改将被保留。

如果找不到 <分支>,但在恰好一个远程库(称为 <远程库>)中存在名称匹配的跟踪分支,且未指定 --no-guess,则视为等同于:

$ git checkout -b <branch> --track <remote>/<branch>

运行不指定分支的 git checkout 除了打印当前分支的跟踪信息外,没有任何效果。

git checkout -b <新分支> [<起始点>]

创建一个名为 <新分支> 的新分支,从 <起始点>(默认为当前提交)开始,并检出该新分支。可以使用 --track--no-track 选项来设置分支的上游跟踪信息。

如果检出 <新分支> 出错(例如检出 <起始点> 提交会覆盖未提交的更改),操作将失败。

git checkout -B <分支> [<起始点>]

-b 相同,区别在于如果分支已存在,它会将 <分支> 重置到起始点,而不是报错失败。

git checkout --detach [<分支>]
git checkout [--detach] <提交>

git checkout <分支> 相同,但它不将 HEAD 指向分支,而是将 HEAD 指向提交 ID。更多信息请参阅下文的“分离头指针”部分。

省略 <分支> 会在当前分支的末梢分离 HEAD

git checkout <树对象> [--] <路径规范>...
git checkout <树对象> --pathspec-from-file=<文件> [--pathspec-file-nul]

将指定的文件和/或目录替换为给定提交或树对象中的版本,并将其添加到索引(也称为“暂存区”)。

例如,git checkout main file.txt 将用 main 分支中的版本替换 file.txt

git checkout [-f|--ours|--theirs|-m|--conflict=<样式>] [--] <路径规范>...
git checkout [-f|--ours|--theirs|-m|--conflict=<样式>] --pathspec-from-file=<文件> [--pathspec-file-nul]

用索引中的版本替换指定的文件和/或目录。

例如,如果您检出一个提交,编辑了 file.txt,然后觉得这些修改是个错误,git checkout file.txt 将丢弃对 file.txt 的任何未暂存更改。

如果文件存在合并冲突且您尚未运行 git add file.txt(或等效操作)将其标记为已解决,则此操作将失败。您可以使用 -f 来忽略未合并文件而不是报错,使用 --ours--theirs 将其替换为合并中特定一方的版本,或使用 -m 将其替换为原始冲突合并结果。

git checkout (-p|--patch) [<树对象>] [--] [<路径规范>...]

这与前两种模式类似,但允许您使用交互式界面查看“diff”输出并选择要在结果中使用的区块(hunks)。请参阅下文关于 --patch 选项的描述。

选项

-q
--quiet

安静模式,抑制反馈消息。

--progress
--no-progress

默认情况下,如果进度状态流被连接到终端,则进度状态会显示在标准错误流上,除非指定了 --quiet。此标志即使未连接到终端也会启用进度报告,无论 --quiet 如何。

-f
--force

切换分支时,即使索引或工作区与 HEAD 不同,甚至存在阻碍操作的未跟踪文件,也要继续进行。这用于丢弃本地修改以及任何碍事的未跟踪文件或目录。

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

--ours
--theirs

从索引检出路径时,针对未合并的路径检出暂存区 #2 (ours) 或 #3 (theirs)。

请注意,在 git rebasegit pull --rebase 期间,ourstheirs 可能会出现反转;--ours 提供更改被变基到的分支版本,而 --theirs 提供您正在变基的工作分支版本。

这是因为 rebase 用于将远程历史视为共享规范历史的工作流中,并将您变基的分支上的工作视为待集成的第三方工作,而在变基期间您临时承担了规范历史维护者的角色。作为规范历史维护者,您需要将远程历史视为 ours(即“我们共享的规范历史”),而将您在侧边分支上的工作视为 theirs(即“某个贡献者在其上的工作”)。

-b <新分支>

创建一个名为 <新分支> 的新分支,从 <起始点> 开始,并检出结果分支;详情请参阅 git-branch[1]

-B <新分支>

-b 相同,区别在于如果分支已存在,它会将 <分支> 重置到起始点,而不是报错失败。

-t
--track[=(direct|inherit)]

创建新分支时,设置“上游”配置。详情请参阅 git-branch[1] 中的 --track。为了方便,不带 -b 的 --track 暗含创建分支的操作。

如果未给出 -b 选项,新分支的名称将通过查看相应远程库配置的引用规范的本地部分,并剥离直到 “*” 的初始部分,从而从远程跟踪分支派生。这将告诉我们在从 origin/hack(或 remotes/origin/hack,甚至是 refs/remotes/origin/hack)分叉时使用 hack 作为本地分支。如果给定名称没有斜杠,或者上述猜测导致名称为空,则放弃猜测。在这种情况下,您可以使用 -b 显式指定名称。

--no-track

即使 branch.autoSetupMerge 配置变量为 true,也不设置“上游”配置。

--guess
--no-guess

如果找不到 <分支>,但在恰好一个远程库(称为 <远程库>)中存在名称匹配的跟踪分支,则视为等同于:

$ git checkout -b <branch> --track <remote>/<branch>

如果该分支存在于多个远程库中,且其中一个被 checkout.defaultRemote 配置变量命名,我们将使用该远程库进行消歧,即使 <分支> 在所有远程库中并不唯一。例如设置为 checkout.defaultRemote=origin,以便在 <分支> 模棱两可但在 origin 远程库上存在时,总是从那里检出远程分支。另请参阅 git-config[1] 中的 checkout.defaultRemote

--guess 是默认行为。使用 --no-guess 禁用它。

默认行为可以通过 checkout.guess 配置变量进行设置。

-l

创建新分支的引用日志 (reflog);详情请参阅 git-branch[1]

-d
--detach

与其检出一个分支来在其上工作,不如检出一个提交用于检查和可丢弃的实验。这是当 <提交> 不是分支名称时 git checkout <提交> 的默认行为。详情请参阅下文的“分离头指针”部分。

--orphan <新分支>

创建一个新的“孤儿”分支(未出生的分支),命名为 <新分支>,从 <起始点> 开始并切换到该分支。在这个新分支上进行的第一次提交将没有父提交,它将成为一个全新的历史记录的根,与所有其他分支和提交完全断开。

索引和工作区的调整就像您之前运行了 git checkout <起始点> 一样。这允许您通过简单地运行 git commit -a 来进行根提交,从而开始一个记录与 <起始点> 类似路径集的新历史。

当您想要发布某个提交中的树而不暴露其完整历史记录时,这很有用。您可能希望这样做来发布一个项目的开源分支,该项目的当前树是“干净”的,但其完整历史记录包含专有或受限的代码段。

如果您想开始一段断开的历史记录,其中记录的路径集与 <起始点> 完全不同,那么您应该在创建孤儿分支后立即通过在工作区顶层运行 git rm -rf . 来清除索引和工作区。之后,您就可以准备新文件,通过从别处复制、提取压缩包等方式重新填充工作区。

--ignore-skip-worktree-bits

在稀疏检出模式下,git checkout -- <路径>... 只会更新与 <路径>$GIT_DIR/info/sparse-checkout 中的稀疏模式相匹配的条目。此选项忽略稀疏模式并重新添加 <路径>... 中的任何文件。

-m
--merge

切换分支时,如果您对一个或多个文件进行了本地修改,且这些文件在当前分支和目标分支之间存在差异,为了在上下文中保留您的修改,该命令会拒绝切换分支。但是,使用此选项后,会执行当前分支、工作区内容和新分支之间的三方合并,然后您将处于新分支上。

当发生合并冲突时,冲突路径的索引条目将保持未合并状态,您需要解决冲突并使用 git add(如果合并结果应该是删除该路径,则使用 git rm)标记已解决的路径。

从索引检出路径时,此选项允许您在指定路径中重新创建冲突合并。此选项不能在从树对象检出路径时使用。

使用 --merge 切换分支时,暂存的更改可能会丢失。

--conflict=<样式>

与上面的 --merge 选项相同,但会更改冲突块的呈现方式,覆盖 merge.conflictStyle 配置变量。可能的值包括 merge(默认)、diff3zdiff3

-p
--patch

<树对象>(如果未指定则为索引)与工作区之间的差异中交互式地选择区块。选定的区块随后会反向应用到工作区(如果指定了 <树对象>,则也应用到索引)。

这意味着您可以使用 git checkout -p 有选择地丢弃当前工作区中的编辑。请参阅 git-add[1] 的“交互模式”部分,了解如何操作 --patch 模式。

请注意,此选项默认使用非覆盖模式(另请参阅 --overlay),且目前不支持覆盖模式。

-U<n>
--unified=<n>

生成具有 <n> 行上下文的 diff。如果配置选项未设置,则默认为 diff.context 或 3。

--inter-hunk-context=<n>

在差异块之间显示上下文,最多达指定行数 <number>,从而合并彼此接近的块。默认为 diff.interHunkContext,如果未设置配置选项则为 0。

--ignore-other-worktrees

当目标分支已被检出或正由另一个工作区使用时,git checkout 会拒绝操作。此选项使其无论如何都会检出该分支。换句话说,该分支可以被多个工作区同时使用。

--overwrite-ignore
--no-overwrite-ignore

切换分支时静默覆盖被忽略的文件。这是默认行为。使用 --no-overwrite-ignore 在新分支包含被忽略的文件时中止操作。

--recurse-submodules
--no-recurse-submodules

使用 --recurse-submodules 将根据主项目记录的提交更新所有活动子模块的内容。如果子模块中的本地修改会被覆盖,除非使用 -f,否则检出将失败。如果不使用任何选项(或使用 --no-recurse-submodules),子模块工作树将不会更新。就像 git-submodule[1] 一样,这会分离子模块的 HEAD

--overlay
--no-overlay

在默认的覆盖(overlay)模式下,git checkout 永远不会从索引或工作树中删除文件。当指定 --no-overlay 时,出现在索引和工作树中但不在 <树对象> 中的文件将被删除,以使它们与 <树对象> 完全匹配。

--pathspec-from-file=<文件>

Pathspec 从 <file> 而不是命令行参数传递。如果 <file>-,则使用标准输入。Pathspec 元素由 LFCR/LF 分隔。Pathspec 元素可以按 core.quotePath 配置变量的解释进行引用(参见 git-config[1])。另请参见 --pathspec-file-nul 和全局 --literal-pathspecs

--pathspec-file-nul

仅在与 --pathspec-from-file 一起使用时有意义。路径规范元素由 NUL 字符分隔,所有其他字符都被视为字面值(包括换行符和引号)。

<分支>

要检出的分支;如果它指的是一个分支(即一个在前面加上 "refs/heads/" 后是有效引用的名称),则检出该分支。否则,如果它指向一个有效的提交,您的 HEAD 将变为“分离”状态,您不再处于任何分支上(详见下文)。

您可以使用 @{-N} 语法来引用使用 "git checkout" 操作检出的倒数第 N 个分支/提交。您也可以指定 -,它与 @{-1} 同义。

作为特殊情况,如果 <rev-a><rev-b> 恰好有一个合并基点,您可以使用 <rev-a>...<rev-b> 作为该合并基点的快捷方式。您最多可以省略 <rev-a><rev-b> 中的一个,在这种情况下,默认值为 HEAD

<新分支>

新分支的名称。

<起始点>

启动新分支的提交名称;详情请参阅 git-branch[1]。默认为 HEAD

作为特殊情况,如果 <rev-a><rev-b> 恰好有一个合并基点,您可以使用 <rev-a>...<rev-b> 作为该合并基点的快捷方式。您最多可以省略 <rev-a><rev-b> 中的一个,在这种情况下,默认值为 HEAD

<树对象>

从中检出的树(当给出路径时)。如果未指定,将使用索引。

作为特殊情况,如果 <rev-a><rev-b> 恰好有一个合并基点,您可以使用 <rev-a>...<rev-b> 作为该合并基点的快捷方式。您最多可以省略 <rev-a><rev-b> 中的一个,在这种情况下,默认值为 HEAD

--

不再将任何后续参数解释为选项。

<路径规范>...

限制受操作影响的路径。

有关更多详细信息,请参阅 gitglossary[7] 中的 pathspec 条目。

分离头指针 (DETACHED HEAD)

HEAD 通常指向一个具名分支(例如 master)。同时,每个分支指向一个特定的提交。让我们看一个拥有三个提交的仓库,其中一个带有标签,且检出了 master 分支

           HEAD (refers to branch 'master')
            |
            v
a---b---c  branch 'master' (refers to commit 'c')
    ^
    |
  tag 'v2.0' (refers to commit 'b')

在此状态下创建提交时,分支会更新以指向新提交。具体来说,git commit 会创建一个新提交 d,其父提交是 c,然后更新 master 分支以指向新提交 dHEAD 仍然指向 master 分支,因此现在间接地指向了提交 d

$ edit; git add; git commit

               HEAD (refers to branch 'master')
                |
                v
a---b---c---d  branch 'master' (refers to commit 'd')
    ^
    |
  tag 'v2.0' (refers to commit 'b')

有时,能够检出一个不在任何具名分支末梢的提交,甚至创建一个不被具名分支引用的新提交,是很有用的。让我们看看检出提交 b 时会发生什么(这里展示了两种可行的方法)

$ git checkout v2.0  # or
$ git checkout master^^

   HEAD (refers to commit 'b')
    |
    v
a---b---c---d  branch 'master' (refers to commit 'd')
    ^
    |
  tag 'v2.0' (refers to commit 'b')

请注意,无论我们使用哪种检出命令,HEAD 现在都直接指向提交 b。这被称为处于分离头指针(detached HEAD)状态。它仅仅意味着 HEAD 指向一个特定的提交,而不是指向一个具名分支。让我们看看当我们创建一个提交时会发生什么

$ edit; git add; git commit

     HEAD (refers to commit 'e')
      |
      v
      e
     /
a---b---c---d  branch 'master' (refers to commit 'd')
    ^
    |
  tag 'v2.0' (refers to commit 'b')

现在有了一个新提交 e,但它仅被 HEAD 引用。当然,我们可以在这种状态下再添加一个提交

$ edit; git add; git commit

	 HEAD (refers to commit 'f')
	  |
	  v
      e---f
     /
a---b---c---d  branch 'master' (refers to commit 'd')
    ^
    |
  tag 'v2.0' (refers to commit 'b')

事实上,我们可以执行所有正常的 Git 操作。但是,让我们看看当我们随后检出 master 时会发生什么

$ git checkout master

               HEAD (refers to branch 'master')
      e---f     |
     /          v
a---b---c---d  branch 'master' (refers to commit 'd')
    ^
    |
  tag 'v2.0' (refers to commit 'b')

重要的是要意识到,此时没有任何东西指向提交 f。最终,提交 f(以及延伸出的提交 e)将被常规的 Git 垃圾回收过程删除,除非我们在那之前创建一个引用。如果我们还没有离开提交 f,以下任何操作都会为它创建一个引用

$ git checkout -b foo  # or "git switch -c foo"  (1)
$ git branch foo                                 (2)
$ git tag foo                                    (3)
  1. 创建一个新分支 foo,它指向提交 f,然后更新 HEAD 以指向分支 foo。换句话说,执行此命令后我们将不再处于分离头指针状态。

  2. 同样创建一个新分支 foo 指向提交 f,但保持 HEAD 处于分离状态。

  3. 创建一个新标签 foo 指向提交 f,保持 HEAD 处于分离状态。

如果我们已经离开了提交 f,那么我们必须首先恢复它的对象名称(通常通过使用 git reflog),然后我们可以为它创建一个引用。例如,要查看 HEAD 指向的最后两个提交,我们可以使用以下任一命令

$ git reflog -2 HEAD # or
$ git log -g -2 HEAD

参数消歧

当您运行 git checkout <内容> 时,Git 会尝试猜测 <内容> 是意指一个分支、一个提交,还是一组文件,然后要么切换到该分支或提交,要么恢复指定的文件。

如果有任何歧义,Git 会将 <内容> 视为分支或提交,但您可以使用双破折号 -- 来强制 Git 将参数视为文件和/或目录列表,如下所示

git checkout -- file.txt

示例

1. 路径

以下序列检出 master 分支,将 Makefile 还原到两个版本前,误删了 hello.c,并从索引中将其找回。

$ git checkout master             (1)
$ git checkout master~2 Makefile  (2)
$ rm -f hello.c
$ git checkout hello.c            (3)
  1. 切换分支

  2. 从另一个提交中取出一个文件

  3. 从索引恢复 hello.c

如果您想从索引中检出 *所有* C 源文件,您可以输入

$ git checkout -- '*.c'

注意 *.c 周围的引号。文件 hello.c 也会被检出,即使它不再处于工作树中,因为文件通配符(globbing)是用于匹配索引中的条目(而不是由 shell 在工作树中匹配)。

如果您不幸有一个名为 hello.c 的分支,这一步会被误认为切换到该分支。您应该改写为

$ git checkout -- hello.c

2. 合并

在错误的分支工作后,切换到正确的分支应该使用

$ git checkout mytopic

然而,您的“错误”分支和正确的 mytopic 分支可能在您本地修改的文件上存在差异,在这种情况下,上述检出会像这样失败

$ git checkout mytopic
error: You have local changes to 'frotz'; not switching branches.

您可以给命令加上 -m 标志,这将尝试进行三方合并

$ git checkout -m mytopic
Auto-merging frotz

在这次三方合并之后,本地修改 *不会* 注册在您的索引文件中,因此 git diff 会向您展示自新分支末梢以来您所做的更改。

3. 合并冲突

在使用 -m 选项切换分支期间发生合并冲突时,您会看到类似这样的内容

$ git checkout -m mytopic
Auto-merging frotz
ERROR: Merge conflict in frotz
fatal: merge program failed

此时,git diff 会显示如前例所示的干净合并的更改,以及冲突文件中的更改。像往常一样编辑并解决冲突,并使用 git add 标记已解决

$ edit frotz
$ git add frotz

配置

本节中以下所有内容均从 git-config[1] 文档中选择性地包含。内容与彼处相同:

checkout.defaultRemote

当您运行 git checkout <something>git switch <something> 且只有一个远程时,它可能会隐式地回退到签出和跟踪 e.g. origin/<something>。一旦您有多个具有 *<something>* 引用的远程,这种情况就会停止工作。此设置允许设置一个首选远程的名称,该名称在歧义化时应始终优先。典型用例是将其设置为 origin

目前,它由 git-switch[1]git-checkout[1] 使用,当 git checkout <something>git switch <something> 会签出另一个远程上的 *<something>* 分支时,以及由 git-worktree[1] 使用,当 git worktree add 指的是远程分支时。此设置将来可能会用于其他类似 checkout 的命令或功能。

checkout.guess

git checkoutgit switch 命令中的 --guess--no-guess 选项提供默认值。参见 git-switch[1]git-checkout[1]

checkout.workers

更新工作树时使用的并行工作进程数量。默认为一个,即顺序执行。如果设置为小于一的值,Git 将使用与可用逻辑核心数相同的数量的工作进程。此设置和 checkout.thresholdForParallelism 会影响所有执行 checkout 的命令。例如:checkout、clone、reset、sparse-checkout 等。

注意
并行 checkout 通常可以提高位于 SSD 或 NFS 上的存储库的性能。对于旋转硬盘和/或核心数较少的机器上的存储库,默认的顺序 checkout 通常性能更好。存储库的大小和压缩级别也可能影响并行版本的性能。
checkout.thresholdForParallelism

当使用少量文件进行并行 checkout 时,子进程生成和进程间通信的成本可能会超过并行化的收益。此设置允许您定义应尝试并行 checkout 的最小文件数。默认值为 100。

GIT

Git[1] 套件的一部分