Git的版本控制已成为现代开发者工具箱中的标配。诸如commit
、push
、pull
等命令已深入我们的肌肉记忆。然而,相对较少开发者了解Git中那些“更高级”的功能——以及它们所能带来的巨大价值!本文将探讨Git中最强大的工具之一:“交互式变基”。
为何交互式变基应成为每位开发者的必备技能
简而言之,毫不夸张地说,交互式变基能助你成为更优秀的开发者,通过让你在项目中创建整洁且结构良好的提交历史。
为何结构良好的提交历史至关重要?试想其反面:难以阅读的提交历史,你无从得知同事们实际对最近的改动做了什么。项目中越来越多的“暗角”开始显现,你只能理解自己亲手处理的那一小部分。
对比之下,整洁且结构良好的提交历史使项目的代码库更易读且更易理解。这是保持项目健康、长久的必要条件!
交互式变基能为你做什么
交互式变基助你优化并清理提交历史。它涵盖多种不同应用场景,其中一些允许你执行以下操作:
- 编辑旧提交信息
- 删除提交
- 压缩/合并多个提交
- 重新排序提交
- 修复旧提交
- 拆分/重新打开旧提交以进行编辑
何时使用交互式变基(以及何时避免使用!)
与其他一些Git工具类似,交互式变基“重写历史”。这意味着,当你通过交互式变基操作一系列提交时,这部分提交历史将被重写:提交的SHA-1哈希值会发生变化。可以说,它们是完全新的提交对象。
这一事实引出了一个简单但至关重要的原则:不要对已经与远程仓库上的同事共享的提交使用交互式变基(或其他重写历史的工具)。相反,应该在合并到团队分支之前,用于清理自己的本地提交——例如在自己的特性分支中。
交互式变基操作的基本机制
尽管交互式变基有许多不同的用途,但其基本工作流程始终相同。一旦你彻底理解了这个基本机制,交互式变基将不再显得“复杂神秘”,而是成为你工具箱中宝贵且易于使用的工具。
步骤1:应该从哪里开始会话?
首先你需要回答的问题是:“我想要操作哪部分提交历史?”这决定了你应从何处启动交互式变基会话。让我们通过一个实际例子来说明,假设我们希望编辑一个旧的提交信息(这也是我们接下来实际操作的内容)。
我们的初始情况如下图所示,此时我们正在通过交互式变基编辑一个旧的提交信息。
为了能够修改C2
中的提交信息,我们必须在其父提交(或者更早,如果你愿意)开始交互式变基会话。在这个示例中,我们将使用C1
作为交互式变基会话的起点。
步骤2:启动实际会话!
启动实际会话相当简单:
$ git rebase -i HEAD~3
我们使用带有-i
标志的git rebase
命令(表示我们确实希望它是“交互式”的),并提供基础提交(即我们在第一步中确定的提交)。在这个例子中,我使用了HEAD~3
来指定“比HEAD提交早3个”的提交。或者,我也可以提供一个特定的SHA-1哈希值。
步骤3:告诉Git你想要做什么
启动交互式变基会话后,你将看到一个编辑器窗口,其中Git列出了一系列提交——从最新的提交一直到(但不包括)你在第一步中选择的基础提交。
在这一步中,有两个重要的事项需要记住:
- 提交是按相反顺序列出的!我们预期出现在顶部的最新提交,实际上会出现在列表的底部。别担心:你的Git仓库状态良好!🥳记住,我们正在进行交互式变基操作,这要求Git在操作结束时从旧到新重新应用提交。
- 请勿在本编辑窗口中进行实际更改! 尽管你可能急于直接在这个编辑窗口中修改提交信息(毕竟,这正是我们想要做的…),但你需要保持耐心。在这里,我们只是告诉Git 我们想要做什么——而不是进行实际的更改。我很快就会通过实践来演示这一点!
理论概述完毕后,让我们一起深入探讨一些实际案例!
编辑旧的提交信息
交互式变基的一个非常流行的用途是,你可以在事后编辑旧的提交信息。你可能知道git commit --amend
也允许你更改提交信息——但仅限于最新的提交。对于任何更早的提交,我们必须使用交互式变基!
让我们来看一个具体的场景。下面是一张需要更正的糟糕提交信息的图片。
注意:为了更好地概览和清晰的可视化,我在一些截图中使用了Tower Git桌面客户端。在本教程中,你不需要Tower来跟随操作。
在我们的例子中,假设我们想要编辑当前标题为“优化index中的标记结构…”的提交信息。
我们的第一步是确定这次交互式重定基础提交的起始点。由于我们需要(至少)回退到我们的“问题提交”的父提交,我们选择从HEAD~3开始(即当前提交之前的第三个提交,其标题为“更改标题…”):
$ git rebase -i HEAD~3
执行此命令后,您最喜欢的编辑器将打开,并显示您刚选择的提交列表(通过指定基础提交)。
提醒一下:尽管您可能会想这么做,但我们在这里不修改提交信息。我们仅标记相应行,添加一个“操作关键词”。在我们的例子中,我们想要reword
这个提交(这意味着我们希望更改提交信息,但保持提交的其他部分不变)。
实际上,所有可用的操作关键词都在窗口底部有文档说明——因此无需死记硬背!
一旦您将默认的pick
关键词(意味着“按原样接受提交”)替换为您选择的操作关键词后,只需保存并关闭窗口即可。
完成这一步后,一个新的编辑器窗口会打开,其中包含当前的提交信息。最终,我们可以进行最初设定的操作:编辑这个旧提交的信息!
在做出更改并保存、关闭编辑器窗口后,交互式重定基础会话即告完成——我们的提交信息也更新了!🎉
删除不需要的提交
交互式变基还允许你删除历史中不再需要(或不想要)的旧提交。设想一下,你不小心在最近的提交中包含了一个个人密码:像这样的敏感信息,在大多数情况下,不应该包含在代码库中。
还要记住,仅仅删除信息并再次提交并不能真正解决问题:这意味着密码仍然以旧提交的形式保存在仓库中。你真正想要的是彻底并干净地删除这个数据项!
首先,我们需要确定交互式变基会话的基础提交。由于我们需要至少从错误提交的父提交开始,我们以“优化标记结构…”提交作为基础:
$ git rebase -i 6bcf266b
注意,这次我在git rebase -i
命令中使用了具体的SHA-1哈希。当然,我也可以使用HEAD~2
来指向那个提交。
执行此命令后,我们再次看到一系列提交的列表。
这次,我们使用drop
操作关键字来摆脱不需要的提交。或者,在这种特殊情况下,我们也可以直接从编辑器中删除整行。如果一行(代表一个提交)在保存并关闭窗口时不存在了,Git将删除相应的提交。
无论你选择哪种方式,保存并关闭编辑器窗口后,该提交将从你的仓库历史中删除!
将多个提交合并为一个
交互式变基的另一个应用场景是,当你希望将多个独立的提交合并成一个时。在我们深入探讨如何实现这一操作之前,先花点时间讨论一下何时或为何这样做可能具有价值。
通常来说,通过合并多个提交来“扩大”提交规模并非大多数情况下的好策略。一般原则是尽量保持提交的粒度尽可能小,因为“小”意味着“更容易阅读和理解”。然而,在某些情况下,这样做仍然有其意义。以下是两个例子:
- 设想你发现了一个较早提交中的问题。你可能会继续创建一个新提交来修复这个问题。在这种情况下,将这两个提交合并为一个非常有意义:毕竟,新的提交实际上只是为了修复原本就不应该存在的问题而采取的“创可贴”措施。通过合并这些提交,就好像问题从未存在过一样!
- 另一个例子是,当你意识到自己的提交过于细碎。虽然小粒度的提交本身是好事,但如果你的提交历史中充斥着大量不必要的小提交,那就可能过头了。
两者的理由相同:通过将原本就应该是一个整体的两个(或多个)提交合并,你能够生成一个更清晰、更易读的提交历史!
让我们一起通过一个实际例子来详细了解,以下图所示的情况作为我们的起点。
假设从语义上讲,将这两个提交合并为一个更为合理。通过使用交互式变基的squash
工具,我们确实可以将其合并:
$ git rebase -i HEAD~3
现在,你应该已经习惯了接下来的步骤:一个编辑器窗口会弹出,列出一系列提交。
I already mentioned that we’re going to use the squash
action keyword in this case. There’s an important thing to know about how squash
works: the line you mark up with the keyword will be combined with the line directly above! This explains why I marked line 2 with the squash
keyword in our example.
保存并关闭这个窗口后,另一个窗口会打开。这是因为,通过合并多个提交,我们自然会创建一个新的提交。而这个新提交也需要一条提交信息,就像其他任何提交一样!
上图所示的是Git为我们准备的内容:它将各自原始提交的信息合并,并附带了一些注释。你可以自由删除旧信息并重新开始,或者保留它们并添加更多信息。
保存并关闭这个编辑器窗口后,我们可以自豪地说:原本的两个独立提交现在已经合并为一个了!
利用交互式变基的力量
I hope you agree that Git’s interactive rebase tools can be very valuable! As developers, it’s important for us to strive for a clean and clear commit history. It’s a crucial ingredient in keeping a codebase healthy and easy to understand (both for your teammates, and yourself, after some time has passed).
如果你想了解更多,我强烈推荐“Git急救包”。这是一系列(免费)的短视频,教你如何清理Git并纠正错误。
玩得开心!
关于Git交互式变基的常见问题(FAQs)
Git Rebase和Git Merge之间有何区别?
Git Rebase 和 Git Merge 是两种不同的方式,用于将一个分支的更改集成到另一个分支中。Git Merge 是一种直接的方法,用于合并两个不同分支的代码。它在历史记录中创建一个新的提交,保留提交的时间顺序。另一方面,Git Rebase 是一种移动或合并一系列提交到新的基础提交的方法。这就像说“我想基于大家已经完成的工作来调整我的更改”。换句话说,它允许你将当前分支的更改置于另一分支之上。
如何撤销 Git Rebase?
要撤销 Git Rebase,你可以使用命令 git reflog
查找你想要返回的提交,然后使用命令 git reset --hard HEAD@{number}
。git reflog
命令显示对 HEAD 所做的每一个更改的列表,而 git reset
命令允许你将当前 HEAD 设置为指定的状态。
Git Interactive Rebase 的目的是什么?
Git Interactive Rebase 允许你以多种方式修改提交,如编辑、删除和压缩。你不仅可以更改提交消息,还可以在你犯了错误时更改实际的代码。这是一个强大的工具,让你完全控制项目的提交历史。
如何使用 Git Interactive Rebase 压缩提交?
压扁是将多个提交合并为一个的行为。在Git中,你可以使用git rebase -i
命令加上你想要压扁的提交哈希值来实现这一操作。在打开的文本编辑器中,通过将每个提交旁边的pick
替换为squash
或s
来标记你想要压扁的提交。
使用Git交互式变基有哪些风险?
尽管Git交互式变基是一个强大的工具,但如果使用不当,它可能会带来危险。它会重写提交历史,这在多人同时工作于公共分支时可能会引起问题。建议在尚未推送的本地分支上使用它。
在Git变基过程中如何解决冲突?
在变基过程中,可能会出现冲突。Git会暂停并允许你在继续之前解决这些冲突。你可以通过编辑文件来修复冲突的更改,然后使用git add
添加已解决的文件。解决所有冲突后,你可以使用git rebase --continue
继续变基。
我可以用Git交互式变基来分割一个提交吗?
是的,你可以使用Git交互式变基将一个提交分割成更小的提交。如果你在一个提交中进行了多项更改,但后来决定它们应该是单独的提交,这将非常有用。
如何使用Git交互式变基编辑提交消息?
在交互式变基过程中,你可以修改提交信息。在提交列表中,将你想编辑的提交旁边的pick
替换为reword
或r
。继续操作后,Git将为每个标记为reword
的提交打开一个文本编辑器,允许你更改提交信息。
Git Rebase与Git Pull有何区别?
Git Pull命令从远程仓库获取更改并将其合并到当前分支中。而Git Rebase命令则是移动或合并一系列提交到一个新的基础提交。尽管两者都用于整合更改,但它们的方式不同。
我能否使用Git交互式变基来改变提交的顺序?
是的,你可以使用Git交互式变基来改变提交的顺序。在提交列表中,只需更改行的顺序即可改变提交的顺序。如果你想让提交历史更加逻辑或清晰,这一功能非常有用。
Source:
https://www.sitepoint.com/git-interactive-rebase-guide/