1. 概述

现今,Git 是一个非常流行的版本控制系统。在这篇简短教程中,我们将探讨如何将已存在的但未提交的更改移动到新的分支。

2. 问题介绍

首先,让我们考虑一下在 Git 管理的项目中添加新特性的典型工作流程:

  1. 创建一个新的特性分支,比如 feature,然后切换到该分支
  2. 实现功能并在本地仓库中进行提交
  3. 将更改推送到远程仓库的特性分支,并创建拉取请求
  4. 在其他队友审查后,新更改可以合并到 masterrelease 分支

然而,有时我们可能已经开始做修改,却忘记了创建新特性分支并切换到它。结果,当我们打算提交更改时,可能会发现自己在错误的分支上,例如 master 分支。

因此,我们需要创建一个新的特性分支并将未提交的工作移动到新分支,同时确保 master 分支不受影响。

举个例子来快速说明这种情况:假设我们有一个名为 myRepo 的 Git 存储库:

$ git branch
* master

$ git status
On branch master
nothing to commit, working tree clean

如上所述,我们当前位于 master 分支,工作目录是干净的。

接下来,我们做一些更改:

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   Readme.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    a-new-file.txt

no changes added to commit (use "git add" and/or "git commit -a")

如上输出所示,我们添加了一个新文件 a-new-file.txt 并修改了 Readme.md 的内容。现在,我们意识到这些工作应该在一个特性分支而不是 master 分支上进行提交。

3. 使用 git checkout 命令

git checkout -b <BranchName> 命令会创建一个新的分支并切换到该分支。此外,此命令会 保留当前分支不变,并将所有未提交的更改带到新分支

接下来,让我们在 myRepo 项目上测试 git checkout 命令:

$ git branch
* master

$ git co -b feature1
Switched to a new branch 'feature1'

$ git status
On branch feature1
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   Readme.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    a-new-file.txt

no changes added to commit (use "git add" and/or "git commit -a")

如上述命令所示,我们创建了 feature1 分支,并将 master 上的所有未提交更改移动到了 feature1。接着,让我们暂存并提交更改:

$ git add . && git commit -m'implemented feature1'
[feature1 2ffc161] implemented feature1
 2 files changed, 2 insertions(+)
 create mode 100644 a-new-file.txt

$ git log --abbrev-commit feature1
commit 2ffc161 (HEAD -> feature1)
Author: ...
Date:   ...
    implemented feature1

commit b009ddf (master)
Author: ...
Date:   ...
    init commit

现在,让我们回到 master 分支,检查是否未对其进行任何更改:

$ git checkout master
Switched to branch 'master'
$ git status
On branch master
nothing to commit, working tree clean
$ git log --abbrev-commit master
commit b009ddf (HEAD -> master)
Author: ...
Date:   ...
    init commit

如输出所示,master 分支上的本地更改没有变化,也没有新的提交。

4. 使用 git switch 命令

正如我们所知,Git 的 checkout 命令就像一把瑞士军刀,可以执行多种不同的操作,如恢复工作目录文件、切换分支、创建分支、移动 HEAD 等。checkout 命令的用法相当复杂。

因此,Git 自版本 2.23 开始引入了 git switch 命令,以消除 checkout 命令过度使用带来的部分混乱。顾名思义,git switch 允许我们在分支间切换。此外,我们可以使用 -C 选项 一次创建新分支并切换到它,这与 git checkout -b 命令的效果基本相同。

接下来,让我们在 myRepo 项目上像 git checkout -b 那样进行相同的测试:

$ git branch
  feature1
* master

$ git status
On branch master
Changes not staged for commit:
  (use "git add/rm ...)
  (use "git restore ...)
    deleted:    Readme.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    ReadmeNew.md

...

如上输出所示,我们目前位于 master 分支。这次,我们删除了文件 Readme.md 并添加了一个新文件 ReadmeNew.md

接下来,让我们使用 git switch 命令将这些未提交的更改移动到名为 feature2 的新分支:

$ git switch -C feature2
Switched to a new branch 'feature2'

$ git status
On branch feature2
Changes not staged for commit:
  (use "git add/rm ...)
  (use "git restore ...)
        deleted: Readme.md
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        ReadmeNew.md
...
$ git add . && git commit -m 'feature2 is done'
[feature2 6cd5933] feature2 is done
1 file changed, 0 insertions(+), 0 deletions(-)
rename Readme.md => ReadmeNew.md (100%)
$ git log --abbrev-commit feature2
commit 6cd5933 (HEAD -> feature2)
Author: ...
Date:   ...
      feature2 is done
commit b009ddf (master)
Author: ...
Date:   ...
      init commit

如上输出所示,git switch -C 创建了新分支 feature2,并将我们带到 feature2 分支。所有未提交的更改已从 master 移动到 feature2 分支。然后,我们在 feature2 分支上提交了更改。

接下来,让我们回到 master 分支并检查是否未被修改:

$ git switch master
Switched to branch 'master'

$ git status
On branch master
nothing to commit, working tree clean
$ ls -1 Readme.md 
Readme.md

$ git log --abbrev-commit master
commit b009ddf (HEAD -> master)
Author: ...
Date:   ...
    init commit

如我们所见,master 分支上,我们之前对工作目录文件所做的所有更改都已恢复。例如,删除的文件 Readme.md 返回了。此外,git log 命令显示 master 上没有新的提交。

5. 总结

本文讨论了几种快速将未提交更改移动到 Git 新分支的方法。这两个命令都非常直观易用:

  • git checkout -b <NEW_BRANCH>
  • git switch -C <NEW_BRANCH>