Git 高级提交操作

GitBeginner
立即练习

介绍

欢迎回来,Git 探险家!你已经迈出了进入版本控制世界的第一步,现在是时候提升你的技能了。在本实验中,我们将探索 Git 一些更高级的提交操作。这些技术将让你对项目的历史拥有更强的控制力,使你能够清理错误、重新组织工作并更有效地进行协作。

把这个实验看作是升级你的时光机。你不仅可以穿梭时空,现在还将学习如何改变时间线本身!如果这听起来有些令人生畏,请不要担心——我们将引导你完成每一个步骤,不仅解释如何进行这些操作,还会说明为什么它们在现实场景中非常有用。

到本实验结束时,你将能够修补提交、回滚更改、遴选特定提交、执行交互式变基以及压缩提交。这些都是专业开发人员每天用来保持项目历史整洁有序的强大工具。让我们深入其中,将你的 Git 技能提升到新的高度!

这是一个引导实验,提供分步说明以帮助你学习和练习。请仔细遵循说明完成每个步骤并获得动手经验。历史数据表明,这是一个初学者级别的实验,完成率为 85%。它获得了学习者 96% 的好评率。

设置工作区

在开始高级操作之前,让我们先设置一个新的工作区。我们将创建一个新目录并在其中初始化一个 Git 仓库。这将为我们提供一个干净的实验空间,而不会影响你现有的任何项目。

打开终端并输入以下命令,每行结束后按 Enter 键:

cd ~/project
mkdir git-advanced-lab
cd git-advanced-lab
git init

让我们逐步分解这些命令的作用:

  1. cd ~/project:「cd」代表「切换目录」(change directory)。~/project 是一个通常指向你主目录(~)下「project」文件夹的路径。此命令将终端导航到该目录。如果你的主目录中不存在「project」目录,可能会遇到错误。如果是这样,请先使用 mkdir ~/project 创建它,然后再次尝试 cd ~/project
  2. mkdir git-advanced-lab:「mkdir」代表「创建目录」(make directory)。此命令在你当前目录(执行前一条命令后应为 ~/project)内创建一个名为「git-advanced-lab」的新文件夹。这个新目录将作为我们本实验 Git 仓库的根目录。
  3. cd git-advanced-lab:此命令再次更改当前目录,这次将你移至新创建的「git-advanced-lab」目录中。现在,你运行的任何命令都将在该目录内执行。
  4. git init:这是初始化仓库的关键 Git 命令。git init 在当前目录(git-advanced-lab)内设置所有必要的 Git 结构,将其转变为一个 Git 仓库。你会看到在 git-advanced-lab 内部创建了一个名为 .git 的隐藏文件夹。这个 .git 文件夹是 Git 仓库的核心,存储了所有的版本历史和配置。

现在我们已经初始化了一个 Git 仓库,让我们创建一个简单的文件并进行首次提交:

echo "Hello, Advanced Git" > hello.txt
git add hello.txt
git commit -m "Initial commit"

以下是这些命令中发生的事情:

  1. echo "Hello, Advanced Git" > hello.txt:「echo」是一个显示文本的命令。"Hello, Advanced Git" 是我们要显示的文本。> 是重定向操作符。它将 echo 命令的输出重定向到名为「hello.txt」的文件中。如果「hello.txt」不存在,它将被创建;如果已存在,它将被新内容覆盖。因此,此命令创建了一个内容为「Hello, Advanced Git」的新文件「hello.txt」。
  2. git add hello.txt:在 Git 能够跟踪文件更改之前,你需要明确告诉 Git 开始跟踪它。git add hello.txt 将「hello.txt」文件放入暂存区(stage)。暂存意味着准备将该文件包含在下一次提交中。可以将暂存区看作提交的准备区。
  3. git commit -m "Initial commit"git commit 将当前暂存的所有更改(在我们的例子中是添加了「hello.txt」)保存为仓库历史中的一个新提交。-m "Initial commit" 添加了提交信息。"Initial commit" 是信息本身,它应该是对你所提交更改的简短描述。养成编写清晰简洁的提交信息的习惯是很好的做法。

太棒了!我们现在有一个包含一次提交的仓库。让我们检查状态以确认一切设置正确:

git status

运行 git status 后,你应该在终端中看到类似以下的信息:

On branch master
nothing to commit, working tree clean

此消息表明你的工作树是干净的。「工作树」(Working tree)是指项目文件所在的目录。「干净」(Clean)意味着你的工作目录中没有尚未提交的更改。这确认了我们已准备好从一个干净的状态开始高级操作!

修补最后一次提交

想象一下,你刚刚完成了一次提交,但随后意识到忘记在文件中包含某项更改,或者在提交信息中犯了一个小的拼写错误。与其为这种微小的修复创建一个全新的提交,Git 允许你使用 --amend 选项来修正最近的一次提交。这就像是「稍微」回到过去来调整你的最后一个动作。

让我们尝试一下。首先,通过添加另一行来修改我们的 hello.txt 文件:

echo "This is an important file." >> hello.txt

此命令向我们的「hello.txt」文件追加新的一行。让我们理解一下 >> 操作符:

  • > 会用新内容覆盖整个文件。
  • >> 将新内容追加到现有文件的末尾,保留原始内容。

因此,运行此命令后,「hello.txt」将包含两行:

Hello, Advanced Git
This is an important file.

现在,假设我们意识到应该在初始提交中就包含这个「重要文件」的注释。我们可以修补之前的提交以包含此更改,并更新提交信息以反映这一添加。

git add hello.txt
git commit --amend -m "Initial commit with important note"

以下是这些命令的分解:

  1. git add hello.txt:我们修改了 hello.txt,所以需要使用 git add 再次暂存更改。这告诉 Git 我们希望在提交中包含「hello.txt」的当前状态。
  2. git commit --amend -m "Initial commit with important note":这就是见证奇迹的时刻。
    • git commit --amend--amend 标志告诉 Git,我们不想创建新提交,而是想修改「最后一次」提交。
    • -m "Initial commit with important note":这提供了一个新的提交信息。如果你省略 -m 选项,Git 将打开你的默认文本编辑器,允许你编辑原始提交信息。

刚才发生了什么?

Git 并没有在你的 Git 历史中创建新提交,而是有效地将之前的「Initial commit」替换为了一个新的、改进后的版本。旧的提交消失了,新的提交合并了我们暂存的更改和更新后的提交信息。

让我们通过检查提交日志来验证这一点:

git log --oneline

你应该在输出中只看到一个提交,但提交信息现在应该是「Initial commit with important note」。

<commit_hash> Initial commit with important note

如果日志视图没有自动关闭,请按 q 退出。

修补时的重要注意事项:

  • 仅修补未推送的提交:你只能修补尚未推送到共享远程仓库(如 GitHub、GitLab 等)的提交。修补已经推送的提交会重写他人可能正在依赖的历史,这在协作环境中会导致严重的问题和混乱。
  • 本地历史清理:修补主要用于在分享工作之前清理你的「本地」提交历史。例如,当你仍在功能分支上工作时,这是完善提交的一种绝佳方式。

回滚提交

有时,你进行了一次提交,后来发现它引入了错误,或者你只是想撤销这些更改。你可能会想到使用 git reset 回到过去并删除该提交。然而,git reset 可能是破坏性的,特别是如果你已经推送了更改或者其他人在同一个分支上工作。撤销更改的一种更安全、更具协作性的方法是使用 git revert(回滚)。

git revert 会创建一个「新」提交,该提交会「撤销」特定提交所引入的更改。它不会从历史记录中抹除原始提交;相反,它添加了一个新提交,有效地反转了不想要提交的影响。这保留了历史记录,对于共享仓库来说要安全得多。

让我们创建一个新提交,然后将其回滚:

echo "This line will be reverted" >> hello.txt
git add hello.txt
git commit -m "Add line to be reverted"

这些命令与我们之前做的类似:

  1. echo "This line will be reverted" >> hello.txt:向我们的「hello.txt」文件添加新行。
  2. git add hello.txt:暂存修改后的文件。
  3. git commit -m "Add line to be reverted":创建一个提交信息为「Add line to be reverted」的新提交,其中包含在「hello.txt」中添加的这一新行。

现在,假设我们决定添加「This line will be reverted」是一个错误,并且我们想要删除它。我们可以使用以下命令回滚最后一次提交:

git revert HEAD

让我们分解这个命令:

  • git revert:这是回滚更改的命令。
  • HEAD:在 Git 中,HEAD 是指向当前分支当前提交的指针。在大多数情况下,HEAD 指的是最近的一次提交。因此,git revert HEAD 意味着「回滚最近的一次提交」。你也可以指定特定的提交哈希值来回滚更早的提交。

当你运行 git revert HEAD 时,Git 将执行以下操作:

  1. 分析更改:它查看 HEAD 提交引入的更改(在我们的例子中是「Add line to be reverted」)。
  2. 创建反向更改:它计算出如何撤销这些更改(在我们的例子中是从「hello.txt」中删除「This line will be reverted」这一行)。
  3. 创建新提交:它会自动创建一个应用这些反向更改的新提交。
  4. 打开文本编辑器(可选):Git 通常会打开你的默认文本编辑器(如 Vim、Nano、VS Code 编辑器等),并为回滚提交预填提交信息。默认信息通常类似于「Revert 'Add line to be reverted'」。你可以接受默认信息,也可以修改它以提供更多上下文。

git revert 期间处理文本编辑器:

如果你的默认编辑器打开了,而你对它不熟悉(特别是如果是 Vim),请不要担心!过程通常很简单:

  • 接受默认信息并完成回滚:
    • 如果是 Vim:按 Esc 键(进入命令模式),然后输入 :(冒号)进入命令行模式,接着输入 wq(写入并退出),最后按 Enter
    • 如果是 Nano:按 Ctrl + X(退出),然后按 Y(保存更改),最后按 Enter(确认文件名)。
    • 对于其他编辑器:寻找「保存」(Save)和「关闭」(Close)或「退出」(Exit)选项。

保存并关闭编辑器后(如果它已打开),Git 将完成回滚提交。

现在让我们检查提交历史:

git log --oneline

你应该在日志中看到三个提交,按反向时间顺序排列(最新的在前):

  1. 以「Revert」开头的提交(我们刚刚创建的回滚提交)。
  2. 「Add line to be reverted」(我们回滚的那个提交)。
  3. 「Initial commit with important note」(我们最初修补过的提交)。
<commit_hash_revert> Revert "Add line to be reverted"
<commit_hash_original> Add line to be reverted
<commit_hash_initial> Initial commit with important note

如果你现在查看 hello.txt 的内容,「This line will be reverted」这一行应该已经消失了,有效地撤销了来自「Add line to be reverted」提交的更改。

为什么在共享仓库中 git revert 优于 git reset

  • 非破坏性历史git revert 不会重写历史。它添加一个新提交来撤销更改,这使得协作变得安全。每个人都可以在历史记录中看到原始错误和修正过程。
  • 避免强制推送:如果你已经推送了提交,git reset 通常需要强制推送(git push --force)到远程仓库。强制推送可能具有极大的破坏性,并覆盖他人的工作。git revert 避免了这个问题。
  • 清晰的审计追踪git revert 提供了何时以及为何撤销更改的清晰记录。这对于理解项目的演变和调试问题非常有价值。

遴选提交

Git 中的遴选(Cherry-picking)就像从树上摘樱桃一样——你从一个分支中选择一个特定的提交,并将其应用到当前分支。当你想要从另一个分支合并某个特定的功能或修复时,这非常有用。

让我们模拟一个场景:我们在一个单独的分支中开发了一个功能,现在只想将该功能引入我们的主分支。

首先,我们需要创建一个新分支并在其上进行一次提交:

git checkout -b feature-branch
echo "This is a new feature" >> feature.txt
git add feature.txt
git commit -m "Add new feature"

让我们分解这些命令:

  1. git checkout -b feature-branch
    • git checkout:此命令用于切换分支或创建新分支。
    • -b feature-branch-b 标志告诉 git checkout 创建一个名为「feature-branch」的新分支并立即切换到该分支。因此,此命令创建了一个新分支并使其成为你的活动分支。
  2. echo "This is a new feature" >> feature.txt:在我们的「feature-branch」中创建一个名为「feature.txt」的新文件,内容为「This is a new feature」。
  3. git add feature.txt:暂存新创建的「feature.txt」文件。
  4. git commit -m "Add new feature":在「feature-branch」上创建一个提交信息为「Add new feature」的提交,包含添加的「feature.txt」。

现在,假设我们目前完成了这个功能,并希望将其集成到我们的主分支(master)中。首先,我们需要切换回 master 分支:

git checkout master

此命令只是将你的活动分支切回「master」。

现在我们想将「feature-branch」上的「Add new feature」提交应用到我们的 master 分支。我们为此使用 git cherry-pick

git cherry-pick feature-branch

等等,为什么是 feature-branch 而不是提交哈希值?在这个简单的例子中,feature-branch 作为一个提交引用将解析为 feature-branch 上的「最新」提交。由于我们在 feature-branch 上只做了一次提交,这实际上就是遴选了那次提交。如果你在 feature-branch 上有多次提交并想挑选其中特定的一个,你需要使用该特定提交的哈希值。例如,你可以使用 git log feature-branch --oneline 找到提交哈希,然后使用 git cherry-pick <commit_hash>

当你运行 git cherry-pick feature-branch 时,Git 会执行以下操作:

  1. 查找提交:它识别出「feature-branch」上的最新提交(或你通过哈希指定的提交)。
  2. 应用更改:它尝试将该提交引入的更改应用到你当前的分支(master)。
  3. 创建新提交:如果成功(没有冲突),Git 会在你的 master 分支上创建一个「新」提交,该提交具有与被遴选提交相同的更改,并且通常具有相同的提交信息。

让我们检查 master 分支上的提交日志:

git log --oneline

你应该在 master 分支上看到一个提交信息为「Add new feature」的新提交。重要的是,这是一个具有不同提交哈希值的「新」提交,即使更改内容与 feature-branch 上的提交相同。

此外,如果你列出 master 分支中的文件(例如使用 ls 命令),你应该也能在那里看到新文件「feature.txt」。

遴选的重要注意事项:

  • 新提交:遴选总是创建一个「新」提交。它不会移动或复制原始提交。在思考提交历史时,记住这一点很重要。
  • 潜在冲突:遴选有时会导致冲突,特别是如果两个分支的代码库已经发生了显著偏离。如果发生冲突,Git 将暂停遴选过程并要求你解决冲突,类似于合并冲突。
  • 谨慎使用:虽然遴选功能强大,但应审慎使用。过度使用遴选会使你的历史记录难以追踪,特别是如果你以后还要合并整个功能分支。它最有效地用于在不希望或不适合进行完整合并时,从一个分支向另一个分支应用特定的、孤立的更改或修复。

交互式变基

交互式变基(Interactive rebasing)是 Git 最强大且可能最复杂的功能之一。它允许你以非常灵活的方式重写提交历史。你可以重新排序提交、合并(压缩)提交、编辑提交信息,甚至完全删除提交。这就像是为你的提交历史提供了一个精细的编辑器。

警告: 交互式变基,特别是在共享分支上,是有风险的,因为它会重写历史。除非你完全了解其后果并已与团队协调,否则切勿对已推送到共享仓库的提交进行变基。 变基通常更适合在与他人分享之前清理你的「本地」提交。

让我们创建一系列提交来操作,以便看到交互式变基的效果:

echo "First change" >> hello.txt
git commit -am "First change"
echo "Second change" >> hello.txt
git commit -am "Second change"
echo "Third change" >> hello.txt
git commit -am "Third change"

这些命令快速连续地创建了三个新提交,每个提交都向我们的「hello.txt」文件添加了一行。git commit -am 中的 -am 简写结合了暂存所有已修改和已删除的文件(-a)与提交(-m)。当你已经在跟踪文件并只想提交对它们的更改时,这是一个方便的快捷方式。

现在,假设我们想清理这三个提交。也许「First change」和「Second change」实际上属于同一个逻辑更改,我们想将它们合并为一个提交。而且也许我们想改进「Third change」的提交信息。交互式变基非常适合这种情况。

使用以下命令启动交互式变基会话:

git rebase -i HEAD~3

让我们理解这个命令:

  • git rebase -igit rebase 是变基命令。-i 标志代表「交互式」(interactive),告诉 Git 我们想要执行交互式变基。
  • HEAD~3:这指定了我们要变基的提交范围。HEAD 指的是当前提交。~3 意味着「从 HEAD 往回数三个提交」。因此,HEAD~3 选择了最后三个提交。你也可以指定一个提交哈希值而不是 HEAD~3,从历史中的特定点开始变基。

当你运行此命令时,Git 将打开你的默认文本编辑器,其中列出了最后三个提交。编辑器界面是你指示 Git 如何修改历史的地方。你会看到类似这样的内容(提交哈希值会有所不同):

1 pick 63c95db First change
2 pick 68e7909 Second change
3 pick 5371424 Third change
4
5 ## Rebase 3bf348d..5371424 onto 3bf348d (3 commands)
6 #
7 ## Commands:
8 ## p, pick <commit> = use commit
9 ## r, reword <commit> = use commit, but edit the commit message
10 ## e, edit <commit> = use commit, but stop for amending
11 ## s, squash <commit> = use commit, but meld into previous commit
12 ## f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
13 ##                    commit's log message, unless -C is used, in which case
14 ##                    keep only this commit's message; -c is same as -C but
15 ##                    opens the editor
16 ## x, exec <command> = run command (the rest of the line) using shell
17 ## b, break = stop here (continue rebase later with 'git rebase --continue')
18 ## d, drop <commit> = remove commit
19 ## l, label <label> = label current HEAD with a name
20 ## t, reset <label> = reset HEAD to a label
21 ## m, merge [-C <commit> | -c <commit>] <label> [## <oneline>]
22 ## .       create a merge commit using the original merge commit's
23 ## .       message (or the oneline, if no original merge commit was
24 ## .       specified); use -c <commit> to reword the commit message
25 #
26 ## These lines can be re-ordered; they are executed from top to bottom.
27 #
28 ## If you remove a line here THAT COMMIT WILL BE LOST.
29 #
30 ## However, if you remove everything, the rebase will be aborted.

理解交互式变基编辑器:

  • 提交列表:文件的顶部列出了属于变基操作一部分的提交。默认情况下,它们按时间顺序排列(最早的在顶部,最新的在底部)。
  • 命令:在提交列表下方,Git 提供了一系列可用命令,你可以使用这些命令来修改每个提交。最常用的有:
    • pick(或 p):原样使用提交。这是默认选项。
    • reword(或 r):使用提交,但允许你更改提交信息。
    • edit(或 e):使用提交,但在该提交处暂停变基过程,以便你可以进行进一步更改(例如修补文件、添加更多更改)。
    • squash(或 s):将此提交合并到列表中的「前一个」提交中。此提交的提交信息将附加到前一个提交的信息之后(稍后你可以编辑合并后的信息)。
    • fixup(或 f):类似于 squash,但丢弃此提交的提交信息,仅使用前一个提交的信息。
    • drop(或 d):从历史记录中完全删除此提交。

我们的交互式变基计划:

我们想要:

  1. 将「Second change」压缩(squash)到「First change」中。
  2. 重写(reword)「Third change」以获得更好的信息。

为了实现这一点,将编辑器文件修改为如下所示:

pick abc1234 First change
squash def5678 Second change
reword ghi9101 Third change

不要更改编辑器文件中的提交哈希值。

在 Vim 中编辑的说明(如果那是你的编辑器):

  1. 进入插入模式:按 i 键。这允许你键入和编辑文件。
  2. 进行更改:使用箭头键导航到各行,并将「pick」更改为如上所示的「squash」和「reword」。
  3. 退出插入模式:按 Esc 键。
  4. 保存并退出:输入 :wq 并按 Enter

保存并关闭编辑器文件后,Git 将开始根据你的指令执行变基操作。

首先,压缩提交:

Git 将首先处理「squash」命令。它会将「Second change」中的更改合并到「First change」中。然后,它会「再次」打开你的文本编辑器,这次是让你为压缩后的提交编辑合并后的提交信息。你会在编辑器中看到类似这样的内容:

## This is a combination of 2 commits.
## This is the 1st commit message:

First change

## This is the 2nd commit message:

Second change

## Please enter the commit message for your changes. Lines starting
## with '#' will be ignored, and an empty message aborts the commit.
#
## Date:      Tue Oct 24 10:30:00 2023 +0000
#
## interactive rebase in progress; onto <base_commit_hash>
#
## Last commands done (2 commands done):
##    pick abc1234 First change
##    squash def5678 Second change
## Next command to do (1 remaining command):
##    reword ghi9101 Third change
## You are currently rebasing branch 'master' on '<base_commit_hash>'.

此编辑器向你显示了正在压缩的提交的原始提交信息。你现在可以编辑此文本,为合并后的提交创建一个单一、连贯的提交信息。例如,你可以将其更改为:

Combined first and second changes: Initial setup of hello.txt

编辑完信息后,保存并关闭编辑器。

接下来,重写提交信息:

Git 随后将继续执行针对「Third change」的「reword」命令。它会「再次」打开编辑器,这次是让你编辑「Third change」的提交信息。你会看到原始信息:

Third change

## Please enter the commit message for your changes. Lines starting
## with '#' will be ignored, and an empty message aborts the commit.
#
## Date:      Tue Oct 24 10:35:00 2023 +0000
#
## interactive rebase in progress; onto <base_commit_hash>
#
## Last commands done (3 commands done):
##    pick abc1234 First change
##    squash def5678 Second change
##    reword ghi9101 Third change
## Next command to do (0 remaining commands):
##    (finish)
## You are currently rebasing branch 'master' on '<base_commit_hash>'.

将信息更改为更具描述性的内容,例如:

Improved third change: Added a more descriptive line to hello.txt

保存并关闭编辑器。

变基完成:

在处理完交互式变基指令中的所有命令后,Git 将完成变基过程。你通常会看到一条消息,如「Successfully rebased and updated refs/heads/master.」。

验证变基后的历史:

现在,再次检查你的提交日志:

git log --oneline

你应该看到类似这样的历史记录(提交哈希值会有所不同):

<commit_hash_third_revised> Improved third change: Added a more descriptive line to hello.txt
<commit_hash_combined> Combined first and second changes: Initial setup of hello.txt
<commit_hash_cherry_pick> Add new feature
<commit_hash_revert> Revert "Add line to be reverted"
<commit_hash_original_reverted> Add line to be reverted
<commit_hash_initial_amended> Initial commit with important note

请注意:

  • 你现在的提交数量比以前少了(我们将两个提交压缩为了一个)。
  • 原本「First change」和「Second change」的提交信息现在已合并且更具描述性。
  • 原本「Third change」的提交信息已被重写。
  • 变基后的提交哈希值已经改变,因为我们重写了历史。

关于交互式变基的重要提醒:

  • 本地历史工具:交互式变基主要是一个用于清理「本地」提交历史的工具。
  • 避免对共享分支进行变基不要对已推送到共享仓库的分支进行变基,除非你绝对确定自己在做什么并已与团队协调。重写共享历史会给协作者带来严重问题。
  • git rebase --abort:如果你在交互式变基期间犯了错误,或者感到困惑或遇到了无法解决的冲突,你始终可以使用 git rebase --abort 来取消变基操作,并将分支恢复到开始变基之前的状态。如果事情进展不顺利,这是退出变基的一种安全方式。

从根节点开始变基

在步骤 5 中,我们使用 git rebase -i HEAD~3 来变基最后三个提交。但交互式变基可以更加强大。git rebase -i --root 允许你交互式地变基仓库历史中的「所有」提交,从第一个(根)提交开始。这为你重塑整个项目历史提供了终极控制权。

极端谨慎: 根节点变基是一项极其强大的操作,能力越大,责任(以及误用带来的潜在重大问题)也就越大。在共享仓库上进行根节点变基几乎总是一个坏主意,除非有特殊且被充分理解的理由,并且只有在与整个团队仔细协调后才能进行。 它会完全重写整个历史,更改从根节点开始的「每一个」提交的哈希值。如果其他人在同一个仓库上协作,这将导致重大混乱。

在本实验中,我们在一个本地、隔离的仓库中工作,因此可以安全地尝试根节点变基以了解其工作原理。只需记住在现实世界的协作项目中的警告即可!

让我们尝试一次根节点变基:

git rebase -i --root

此命令与之前的变基非常相似,但我们使用 --root 代替了 HEAD~3--root 告诉 Git 在交互式变基中考虑从仓库历史最开始的所有提交。

当你运行此命令时,你的默认文本编辑器将再次打开,但这次它将列出仓库中的「所有」提交,从第一个提交(「Initial commit with important note」或你在步骤 2 中修补后的内容)开始。你现在看到的列表会比上一步更长。

你可能会看到类似这样的内容(你的提交哈希和信息可能会根据你之前步骤的操作而有所不同):

pick <hash_initial> Initial commit with important note
pick <hash_reverted> Add line to be reverted
pick <hash_revert_commit> Revert "Add line to be reverted"
pick <hash_new_feature> Add new feature
pick <hash_combined> Combined first and second changes: Initial setup of hello.txt
pick <hash_third_revised> Improved third change: Added a more descriptive line to hello.txt

我们的根节点变基计划:

在本次练习中,让我们通过这次根节点变基做几件事:

  1. 更改第一个提交的信息。假设我们想进一步完善初始提交的信息。
  2. 将「Revert」提交压缩到引入被回滚行的那个提交中。这是一个有点刻意的例子,但它展示了你如何操纵历史。我们可以认为「Revert」提交实际上只是清理初始错误的一部分,因此我们可以将其压缩到引入错误的提交中。(在现实场景中,你可能并不总是想压缩回滚提交,因为它们提供了撤销更改的清晰记录)。

为了实施此计划,将编辑器文件修改为如下所示:

reword <hash_initial> Initial commit with important note
squash <hash_reverted> Add line to be reverted
pick <hash_revert_commit> Revert "Add line to be reverted"
pick <hash_new_feature> Add new feature
pick <hash_combined> Combined first and second changes: Initial setup of hello.txt
pick <hash_third_revised> Improved third change: Added a more descriptive line to hello.txt

我们将第一个提交的「pick」更改为「reword」,将第二个提交(「Add line to be reverted」)的「pick」更改为「squash」。暂时将「Revert」提交保留为「pick」。

保存并关闭编辑器。

首先,重写初始提交的信息:

Git 将首先暂停,允许你重写初始提交的信息。编辑器将打开,显示当前信息「Initial commit with important note」(或你在步骤 2 中修补后的内容)。将其更改为类似:「Initial setup and file creation」。保存并关闭编辑器。

接下来,将「Add line to be reverted」压缩到初始提交中:

Git 随后将处理「squash」命令。它会将「Add line to be reverted」中的更改合并到「前一个」提交(现在是「Initial setup and file creation」)中。编辑器将再次打开,让你编辑合并后的提交信息。你会看到两个提交的原始信息。你可以编写一个反映合并后更改的新信息。例如,你可以将信息更改为:

Initial setup and file creation with subsequent line removal

保存并关闭编辑器。

变基完成:

Git 将继续处理其余命令(对剩余提交执行「pick」)并完成根节点变基。

验证根节点变基后的历史:

最后一次检查你的提交日志:

git log --oneline

你应该看到你的整个提交历史,但包含了你在根节点变基中指示的更改:

  • 第一个提交现在应该有新信息「Initial setup and file creation with subsequent line removal」(或类似内容)。
  • 「Add line to be reverted」提交和「Revert」提交可能仍然存在,或者根据你指示变基的具体方式已被合并。(在上面的示例说明中,我们将「Revert」保留为「pick」,因此它应该仍然存在)。
  • 从根提交开始,整个历史记录中的提交哈希值都已更改。

再次提醒,在共享仓库中进行根节点变基要极端谨慎! 本步骤主要用于教学目的,以展示交互式变基的强大功能和灵活性。

总结

恭喜你,Git 大师!你刚刚进一步提升了你的版本控制技能。让我们回顾一下你在本实验中学到的强大技术:

  1. 修补提交:你现在可以修正微小的错误或向最近的一次提交添加细微更改,而无需创建新提交,从而保持历史记录在进行微调时的整洁。
  2. 回滚提交:你学习了如何通过创建一个反转之前更改的新提交来安全地撤销更改。这是在协作环境中纠正错误的非破坏性方式。
  3. 遴选:你现在可以将特定提交从一个分支应用到另一个分支,从而对合并哪些更改进行精细控制,非常适合选择性地引入功能或修复。
  4. 交互式变基:你掌握了重写历史的艺术,允许你在与他人分享之前清理和组织你的提交。这包括重新排序、压缩和编辑提交信息,以获得更完美的历史记录。
  5. 根节点变基:你学习了如何从第一个提交开始重塑整个项目历史,从而获得对仓库叙事的终极控制权。请记住与之相关的权力和风险!

这些高级 Git 操作是开发人员工具箱中极其强大的工具。它们允许你维护一个干净、有序的提交历史,这在处理大型项目或与团队协作时至关重要。一个维护良好的提交历史不仅是为了美观,它还能显著改善代码审查、调试以及对项目随时间演变的理解。