高级 Git 提交操作

GitGitBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

介绍

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

把这个实验想象成升级你的时间机器。你不仅可以穿越时间,现在还将学习如何改变时间线本身!如果这听起来有些令人生畏,别担心——我们将一步步引导你,不仅解释如何执行这些操作,还会说明它们在现实场景中的实用性。

在本实验结束时,你将能够修改提交、撤销更改、挑选特定的提交、执行交互式变基以及压缩提交。这些都是专业开发者每天用来维护干净、有序项目历史的强大工具。让我们深入探索,将你的 Git 技能提升到一个新的水平!


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL git(("`Git`")) -.-> git/SetupandConfigGroup(["`Setup and Config`"]) git(("`Git`")) -.-> git/BasicOperationsGroup(["`Basic Operations`"]) git(("`Git`")) -.-> git/DataManagementGroup(["`Data Management`"]) git(("`Git`")) -.-> git/BranchManagementGroup(["`Branch Management`"]) git/SetupandConfigGroup -.-> git/init("`Initialize Repo`") git/BasicOperationsGroup -.-> git/add("`Stage Files`") git/BasicOperationsGroup -.-> git/commit("`Create Commit`") git/DataManagementGroup -.-> git/reset("`Undo Changes`") git/BranchManagementGroup -.-> git/branch("`Handle Branches`") git/BranchManagementGroup -.-> git/checkout("`Switch Branches`") git/BranchManagementGroup -.-> git/log("`Show Commits`") git/BranchManagementGroup -.-> git/cherry_pick("`Cherry Pick`") git/BranchManagementGroup -.-> git/rebase("`Reapply Commits`") subgraph Lab Skills git/init -.-> lab-387471{{"`高级 Git 提交操作`"}} git/add -.-> lab-387471{{"`高级 Git 提交操作`"}} git/commit -.-> lab-387471{{"`高级 Git 提交操作`"}} git/reset -.-> lab-387471{{"`高级 Git 提交操作`"}} git/branch -.-> lab-387471{{"`高级 Git 提交操作`"}} git/checkout -.-> lab-387471{{"`高级 Git 提交操作`"}} git/log -.-> lab-387471{{"`高级 Git 提交操作`"}} git/cherry_pick -.-> lab-387471{{"`高级 Git 提交操作`"}} git/rebase -.-> lab-387471{{"`高级 Git 提交操作`"}} end

设置你的工作区

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

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

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

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

  1. cd ~/projectcd 代表 "change directory"(更改目录)。~/project 是一个路径,通常指向你主目录(~)中的 "project" 文件夹。此命令将你的终端导航到该目录。如果你的主目录中没有 "project" 目录,可能会遇到错误。如果遇到这种情况,请先使用 mkdir ~/project 创建它,然后再尝试 cd ~/project
  2. mkdir git-advanced-labmkdir 代表 "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.txtecho 是一个显示文本的命令。"Hello, Advanced Git" 是我们想要显示的文本。> 是一个重定向操作符。它将 echo 命令的输出重定向到一个名为 "hello.txt" 的文件中。如果 "hello.txt" 不存在,它将被创建。如果它已经存在,它将被新内容覆盖。因此,此命令创建了一个名为 "hello.txt" 的新文件,内容为 "Hello, Advanced Git"。
  2. git add hello.txt:在 Git 可以跟踪文件中的更改之前,你需要明确告诉 Git 开始跟踪它。git add hello.txt 将 "hello.txt" 文件暂存。暂存意味着准备将文件包含在下一次提交中。你可以将暂存区视为提交的准备区。
  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

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

修改你的最后一次提交

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

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

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

此命令在我们的 "hello.txt" 文件中追加了一行新内容。让我们理解一下 >> 操作符:

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

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

Hello, Advanced Git
This is an important file.

现在,假设我们意识到这个 "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(确认文件名)。
    • 对于其他编辑器: 寻找“保存”和“关闭”或“退出”的选项。

在你保存并关闭编辑器(如果它打开了)后,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 revertgit reset 更受欢迎?

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

挑选提交(Cherry-picking Commits)

Git 中的 cherry-pick 就像从树上摘樱桃一样——你从一个分支中选择一个特定的提交,并将其应用到当前分支。当你希望从另一个分支中引入某个特定的功能或修复,而不想合并整个分支时,这非常有用。

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

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

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 作为一个提交引用(commit-ish)会解析为 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"。

挑选提交时的重要注意事项:

  • 新提交: Cherry-pick 总是创建一个 新的 提交。它不会移动或复制原始提交。在考虑提交历史时,这一点很重要。
  • 潜在的冲突: Cherry-pick 有时可能会导致冲突,尤其是当两个分支的代码库差异较大时。如果发生冲突,Git 会暂停 cherry-pick 过程,并要求你解决冲突,类似于合并冲突。
  • 谨慎使用: 虽然 cherry-pick 功能强大,但应谨慎使用。过度使用 cherry-pick 可能会使你的历史记录更难追踪,尤其是如果你稍后合并整个功能分支。它最适合在不需要或不适合完全合并的情况下,将特定、独立的更改或修复从一个分支应用到另一个分支。

交互式变基(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" 压缩到 "First change" 中。
  2. 重新编写 "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 取消变基操作,并将分支恢复到变基之前的状态。这是在变基出错时安全退出的方法。

从根提交开始变基(Rebasing from the Root)

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

极度谨慎: 根变基是一项极其强大的操作,而强大的能力也伴随着巨大的责任(如果使用不当,可能会带来严重问题)。在共享仓库上进行根变基几乎总是一个坏主意,除非有特殊且充分理解的理由,并且在与整个团队仔细协调后才进行。 它会完全重写整个历史,从根提交开始更改 每个 提交的哈希值。如果其他人正在同一仓库上协作,这将导致重大混乱。

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

让我们尝试根变基:

git rebase -i --root

此命令与之前的变基非常相似,但我们使用 --root 而不是 HEAD~3--root 告诉 Git 从仓库历史的第一个提交开始考虑所有提交进行交互式变基。

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

你可能会看到类似以下内容(你的提交哈希和消息可能会根据你之前的步骤有所不同):

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. 修改提交(Amending Commits):你现在可以修复小错误或将小更改添加到最近的提交中,而无需创建新的提交,从而保持历史记录的整洁,适合进行小调整。
  2. 撤销提交(Reverting Commits):你已经学会了如何通过创建一个新的提交来安全地撤销更改,该提交会反转之前的提交。这是在协作环境中纠正错误的非破坏性方法。
  3. 挑选提交(Cherry-picking):你现在可以将一个分支中的特定提交应用到另一个分支,从而精细控制你要引入的更改,非常适合选择性引入功能或修复。
  4. 交互式变基(Interactive Rebasing):你已经掌握了重写历史的艺术,允许你在与他人分享之前清理和组织提交。这包括重新排序、压缩和编辑提交消息,以获得更完善的历史记录。
  5. 根变基(Root Rebasing):你已经学会了如何从第一个提交开始重塑整个项目历史,从而完全控制仓库的叙事。记住这种操作的力量和风险!

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

您可能感兴趣的其他 Git 教程