Git Reset vs Revert vs Rebase

本文将深入探讨在 Git 中处理提交的不同策略。

作为一名程序员,你可能会经常遇到需要回溯到先前提交的情况,但又不确定如何操作。即使你熟悉诸如 reset、revert 和 rebase 等 Git 命令,你也可能对它们之间的区别感到困惑。那么,让我们开始了解一下 git reset、revert 和 rebase 的具体含义吧。

Git 重置 (reset)

Git reset 是一个功能强大的命令,主要用于撤销代码变更。

你可以将 git reset 理解为一种时光倒流的功能。 通过 git reset,你可以在不同的提交之间自由穿梭。 此命令有三种模式可供选择:--soft--mixed--hard。 默认情况下,git reset 使用的是 mixed 模式。 在 git reset 的工作流程中,Git 的三个核心管理组件将发挥作用:HEAD 指针、暂存区 (索引) 和工作目录。

工作目录是您当前工作的场所,即存放您文件的位置。 您可以使用 git status 命令来查看工作目录中的所有文件和文件夹。

暂存区 (索引) 是 Git 用来跟踪和保存文件更改的地方。 保存的更改信息存储在 .git 目录中。 通过执行 git add "文件名",可以将文件添加到暂存区。 同样,使用 git status,你可以查看暂存区中已添加的文件。

当前 Git 分支被称作 HEAD。 它指向当前检出分支的最近一次提交,可视为指向引用的指针。 当您切换到其他分支时,HEAD 指针也会随之移动到新分支。

接下来,我将详细解释 git reset 在 hard、soft 和 mixed 模式下的具体运作方式。 hard 模式用于移动 HEAD 指针到指定提交,并且工作目录会更新为该提交的文件,暂存区也会被重置。 在 soft reset 模式下,仅移动 HEAD 指针到指定提交。 所有在重置前的修改仍然保留在工作目录和暂存区。 而在 mixed 模式 (默认) 下,HEAD 指针和暂存区都会被重置。

Git 硬重置 (hard reset)

git hard reset 的目标是将 HEAD 指针移动到指定的提交。 这将删除在该指定提交之后发生的所有提交。此命令会修改提交历史,并将其指向指定的提交。

在这个示例中,我将添加三个新文件,提交它们,然后再执行一次硬重置。

如下面的命令输出所示,当前没有待提交的更改。

$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.

(use "git push" to publish your local commits)

nothing to commit, working tree clean

现在,我将创建三个文件并向其中添加一些内容。

$ vi file1.txt
$ vi file2.txt
$ vi file3.txt

将这些文件添加到现有的存储库中。

$ git add file*

当您再次运行状态命令时,会显示刚刚创建的新文件。

$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.

(use "git push" to publish your local commits)

Changes to be committed:

(use "git restore --staged <file>..." to unstage)

new file:
file1.txt

new file:
file2.txt

new file:
file3.txt

在提交之前,我想告诉大家,我目前在 Git 中有 3 次提交的日志。

$ git log --oneline
0db602e (HEAD -> master) one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

现在,我将提交到存储库。

$ git commit -m 'added 3 files'
[master d69950b] added 3 files
3 files changed, 3 insertions(+)
create mode 100644 file1.txt
create mode 100644 file2.txt
create mode 100644 file3.txt

执行 ls-files 后,您将看到已添加的新文件。

$ git ls-files
demo
dummyfile
newfile
file1.txt
file2.txt
file3.txt

当我在 git 中运行 log 命令时,我有 4 个提交,并且 HEAD 指向最近的提交。

$ git log --oneline
d69950b (HEAD -> master) added 3 files
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

如果我手动删除 file1.txt 并执行 git status,它将显示更改未暂存的消息。

$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.

(use "git push" to publish your local commits)

Changes not staged for commit:

(use "git add/rm <file>..." to update what will be committed)

(use "git restore <file>..." to discard changes in working directory)

deleted:
file1.txt

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

现在,我将运行 hard reset 命令。

$ git reset --hard
HEAD is now at d69950b added 3 files

如果我重新检查状态,我会发现没有什么可提交的,我删除的文件又回到了存储库中。 发生回滚是因为删除文件后,我没有提交,所以硬重置后,又回到了以前的状态。

$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.

(use "git push" to publish your local commits)

nothing to commit, working tree clean

如果我检查 git 的日志,这就是它的样子。

$ git log
commit d69950b7ea406a97499e07f9b28082db9db0b387 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 19:53:31 2020 +0530

added 3 files

commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530

one more commit

commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530

new commit

commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530

test

hard reset 的目的是指向指定的 commit,并更新工作目录和暂存区。 让我再举一个例子。目前,我的提交可视化如下所示:

这里,我将使用 HEAD^ 运行命令,这意味着我想重置到上一次提交(回退一次提交)。

$ git reset --hard HEAD^
HEAD is now at 0db602e one more commit

您可以看到 head 指针现在已从 d69950b 更改为 0db602e。

$ git log --oneline
0db602e (HEAD -> master) one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

查看日志,d69950b 的 commit 已经消失,head 现在指向 0db602e 的 SHA。

$ git log
commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530

one more commit

commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530

new commit

commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530

Test

如果运行 ls-files,您可以看到 file1.txt、file2.txt 和 files3.txt 不再位于存储库中,因为该提交及其文件在硬重置后已被删除。

$ git ls-files
demo
dummyfile
newfile

Git 软重置 (soft reset)

接下来,我将展示一个软重置的例子。假设我已按照上述步骤再次添加了 3 个文件并提交了它们。 git 日志将如下所示。您可以看到“软重置”是我的最新提交,并且 HEAD 也指向它。

$ git log --oneline
aa40085 (HEAD -> master) soft reset
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

您可以使用以下命令查看日志中提交的详细信息。

$ git log
commit aa400858aab3927e79116941c715749780a59fc9 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 21:01:36 2020 +0530

soft reset

commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530

one more commit

commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530

new commit

commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530

test

现在,使用软重置,我想切换到 SHA 为 0db602e085a4d59cfa9393abac41ff5fd7afcb14 的旧提交之一。

为此,我将运行以下命令。您需要传递 6 个以上的 SHA 开头字符,而不需要完整的 SHA。

$ git reset --soft 0db602e085a4

现在,当我运行 git log 时,我可以看到 HEAD 已重置为我指定的提交。

$ git log
commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530

one more commit

commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530

new commit

commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530

test

但区别在于,我添加了 3 个文件的提交(aa400858aab3927e79116941c715749780a59fc9)仍然保留在我的工作目录中,它们并没有被删除。这就是为什么你应该使用软重置而不是硬重置的原因。软模式下不会有丢失文件的风险。

$ git ls-files
demo
dummyfile
file1.txt
file2.txt
file3.txt
newfile

Git 还原 (revert)

在 Git 中,revert 命令用于执行还原操作,即撤销某些更改。它与 reset 命令相似,但区别在于它会创建一个新的提交,用于回退到特定的提交状态。简而言之,git revert 命令相当于一次提交。

Git revert 命令在执行还原操作时不会删除任何数据。

假设我要添加 3 个文件,并为 revert 示例执行一次 git 提交操作。

$ git commit -m 'add 3 files again'
[master 812335d] add 3 files again
3 files changed, 3 insertions(+)
create mode 100644 file1.txt
create mode 100644 file2.txt
create mode 100644 file3.txt

日志将显示新的提交。

$ git log --oneline
812335d (HEAD -> master) add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

现在我想恢复到过去的提交,例如 “59c86c9 new commit”。我会运行以下命令。

$ git revert 59c86c9

这将打开一个文件,其中包含您尝试恢复到的提交的详细信息,您可以在此为新的提交命名,然后保存并关闭文件。

Revert "new commit"

This reverts commit 59c86c96a82589bad5ecba7668ad38aa684ab323.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# Your branch is ahead of 'origin/master' by 4 commits.
# (use "git push" to publish your local commits)
#
# Changes to be committed:
# modified: dummyfile

保存并关闭文件后,您将得到以下输出。

$ git revert 59c86c9
[master af72b7a] Revert "new commit"
1 file changed, 1 insertion(+), 1 deletion(-)

现在,已经进行必要的更改。与 reset 不同,revert 已经执行了一个新的提交。再次查看日志,会发现由于 revert 操作而产生了一个新的提交。

$ git log --oneline
af72b7a (HEAD -> master) Revert "new commit"
812335d add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

Git 日志将包含所有提交的历史记录。如果您想从历史记录中删除提交,那么 revert 不是一个好的选择。但是,如果您想保留历史记录中的提交更改,那么 revert 是一个合适的命令,而不是 reset

Git 变基 (rebase)

在 Git 中,rebase 是一种将一个分支的提交移动或合并到另一个分支的方法。作为一名开发人员,我不会在实际场景中的 master 分支上进行功能开发。我会创建我自己的分支(一个“功能分支”),并且当我的功能分支中有一些添加了功能的提交时,我想将它们移动到主分支上。

Rebase 有时可能有点难以理解,因为它与 merge 非常相似。 Mergerebase 的目标都是从我的功能分支获取提交,并将它们放到主分支或任何其他分支上。假设我有一个如下所示的图表:

假设您正在与其他开发人员一起工作。在这种情况下,您可以想象情况会变得非常复杂,因为您有一群其他开发人员在不同的功能分支上工作,并且他们一直在合并多个更改,导致难以追踪。

因此,这正是 rebase 可以提供帮助的地方。这一次,我不执行 git merge,而是执行 rebase,我想把我的两个功能分支提交并移动到 master 分支上。 Rebase 将从功能分支获取我的所有提交,并将它们移动到主分支提交之上。因此,在幕后,git 正在复制 master 分支上的功能分支提交。

此方法将为您提供一个清晰的直线图,其中所有提交都按顺序排列。

这可以很容易地跟踪提交的去向。您可以想象,如果您在一个拥有许多开发人员的团队中,所有提交仍然是连续的。因此,即使有多人同时从事同一个项目,也很容易理解。

让我实际演示一下。

这是我的主分支目前的样子。它有 4 次提交。

$ git log --oneline
812335d (HEAD -> master) add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

我将运行以下命令来创建并切换到一个名为 feature 的新分支,这个分支将从第二次提交 59c86c9 创建。

(master)
$ git checkout -b feature 59c86c9
Switched to a new branch 'feature'

如果您检查功能分支中的日志,它只有 2 个来自 master(主线)的提交。

(feature)
$ git log --oneline
59c86c9 (HEAD -> feature) new commit
e2f44fc (origin/master, origin/HEAD) test

我将创建功能 1 并将其提交到功能分支。

(feature)
$ vi feature1.txt

(feature)
$ git add .
The file will have its original line endings in your working directory

(feature)
$ git commit -m 'feature 1'
[feature c639e1b] feature 1
1 file changed, 1 insertion(+)
create mode 100644 feature1.txt

我将在特性分支中再创建一个特性,即特性 2 并提交它。

(feature)
$ vi feature2.txt

(feature)
$ git add .
The file will have its original line endings in your working directory

(feature)
$ git commit -m 'feature 2'
[feature 0f4db49] feature 2
1 file changed, 1 insertion(+)
create mode 100644 feature2.txt

现在,如果您检查功能分支的日志,它有两个新提交,我在上面执行过。

(feature)
$ git log --oneline
0f4db49 (HEAD -> feature) feature 2
c639e1b feature 1
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

现在我想把这两个新特性添加到 master 分支。为此,我将使用 rebase 命令。 从功能分支,我将对 master 分支进行 rebase。这将根据最近的更改重新定位我的功能分支。

(feature)
$ git rebase master
Successfully rebased and updated refs/heads/feature.

现在我要继续检查 master 分支。

(feature)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 3 commits.

(use "git push" to publish your local commits)

最后,将 master 分支重新设置为我的功能分支。这将获取我的功能分支上的这两个新提交,并在我的主分支上重播它们。

(master)
$ git rebase feature
Successfully rebased and updated refs/heads/master.

现在,如果我检查 master 分支上的日志,我可以看到我的 features 分支的两个提交已成功添加到我的 master 分支。

(master)
$ git log --oneline
766c996 (HEAD -> master, feature) feature 2
c036a11 feature 1
812335d add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

这就是关于 Git 中的 resetrevertrebase 命令的全部内容。

结论

这就是关于 Git 中的 resetrevertrebase 命令的全部内容。我希望这个分步指南对您有所帮助。现在,您知道如何使用本文中提到的命令根据需要处理您的提交。