本文将深入探讨在 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
非常相似。 Merge
和 rebase
的目标都是从我的功能分支获取提交,并将它们放到主分支或任何其他分支上。假设我有一个如下所示的图表:
假设您正在与其他开发人员一起工作。在这种情况下,您可以想象情况会变得非常复杂,因为您有一群其他开发人员在不同的功能分支上工作,并且他们一直在合并多个更改,导致难以追踪。
因此,这正是 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 中的 reset
、revert
和 rebase
命令的全部内容。
结论
这就是关于 Git 中的 reset
、revert
和 rebase
命令的全部内容。我希望这个分步指南对您有所帮助。现在,您知道如何使用本文中提到的命令根据需要处理您的提交。