惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Vulnerabilities – Threatpost
月光博客
月光博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
美团技术团队
Last Week in AI
Last Week in AI
Jina AI
Jina AI
P
Privacy International News Feed
有赞技术团队
有赞技术团队
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
T
Tenable Blog
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
T
Tailwind CSS Blog
Apple Machine Learning Research
Apple Machine Learning Research
P
Privacy & Cybersecurity Law Blog
人人都是产品经理
人人都是产品经理
S
Schneier on Security
Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
C
Cisco Blogs
K
Kaspersky official blog
C
Cybersecurity and Infrastructure Security Agency CISA
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
O
OpenAI News
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tor Project blog
博客园 - 【当耐特】
腾讯CDC
V
V2EX
A
Arctic Wolf
Webroot Blog
Webroot Blog
S
Securelist
小众软件
小众软件
大猫的无限游戏
大猫的无限游戏
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 三生石上(FineUI控件)
The GitHub Blog
The GitHub Blog
量子位
J
Java Code Geeks
博客园 - 叶小钗
S
SegmentFault 最新的问题
Project Zero
Project Zero
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Scott Helme
Scott Helme
Cyberwarzone
Cyberwarzone

少数派

派早报:Google 发布 Fitbit Air 等 - 少数派 「新人报到」確認需求,再開始 - 少数派 从 SOLO 独立开发者社区,我看到了越来越多开发者开始做自己的产品 - 少数派 我怎么管理那些"不常做,但总会忘"的生活事项 - 少数派 人形机器人量产元年,数据才是具身智能的“生死线” - 少数派 BuhoLaunchpad 高度还原 Mac 启动台:开发历程与思考 - 少数派 五年陪伴依然不舍,DIY 换壳后让罗技 MX Master 3 继续服役 - 少数派 新玩意 240|少数派的编辑们最近买了啥? - 少数派 一日一技|为什么你应该关闭 iOS 的键盘声音 - 少数派 我做了个插件和 Skills,一键提取任何网站的设计规范 Design.md - 少数派 住在三四线城市的你,该开始录播客了 - 少数派 甘南秘境,大白高国 - 少数派 AI的审美:谁让把我变成川内倫子 - 少数派 返工怎能不烦恼,打工人片单总有一部是你的「嘴替」 - 少数派 为了让「上厕所」更健康,我做了一个小工具 - 少数派 AI + Skill,能够让生成的文章去除 AI 味吗? - 少数派 新玩意|韶音OpenDots ONE 耳夹式耳机 - 少数派 《美满》| 在每一个春天的晚上相爱(362) - 少数派 新玩意|优篮子 PS01 MagSnap 磁吸支架 - 少数派 自我整合手记 | 我开始早睡了:用稳定规则,为自由托底 - 少数派 用龙虾(OpenClaw)两个多月,我最深的12个体会 - 少数派 听歌时间到,12 张你可能错过的 2025 华语乐坛好专辑 - 少数派 承诺能追吗 - 少数派 macOS 26启动台没了? 我做了个不一样的App启动器 - Keboard - 少数派 《四海为家的人》| INTJ对话INTJ(361) - 少数派 你发过的那些黑历史,是时候一次清干净了 - 少数派 新玩意:安安静静玩,越玩越专注:计客密码机 - 少数派 iPad 用户首次体验 Android 平板:vivo Pad6 Pro - 少数派 数据逻辑强 - 少数派 极北行+ | 一路向北,探访日本至北之地 | 001 - 少数派 万字剖析:千问App深度体验报告(2026) - 少数派 在2026年,如何真正防止别人抄袭你的作品 - 少数派 怎么用 50 块搭个 AI 语音助手?我踩了 3 天坑 - 少数派 YeeroAI:让 AI 对话真正成为知识管理的一部分 - 少数派 爬泰山 - 少数派 「旅图显影」 App 更新:这次,我们补上了一点「手感」 - 少数派 假期出门太折磨?我的 23 条经验帮你规划惬意旅行 - 少数派 工作流会变吗 - 少数派 Claude Opus 4.6 怎么用最省钱?我测了 5 种方案 - 少数派 GPT Image 2 让图文并茂不再稀罕 - 少数派 用户侧出发——什么是AI,我要不要学习? - 少数派 找片、转存、整理、播放一条龙!让你的付费网盘值回票价 - 少数派 欢迎试用!日课一问2.0插件 - 少数派 自己做的MDeditor,原本想购买 Typora 试了两次支付不成功,干脆自己做一个 - 少数派 vibe coding了一个 3MB 的小工具,让 ~/Downloads 彻底告别混乱 - 少数派 因为受不了 Mac 的风扇策略,我做了一个风扇控制工具 - 少数派 别只怪模型 - 少数派 Warp 终端的 AI 功能怎么用?我测了一周的体验 - 少数派 AI 写代码老是出 bug?这 5 个配置我后悔没早知道 - 少数派 「新玩意」苹果出相机可能就这样:Sigma BF + 45mm F2.8 DG Contemporary - 少数派 一个面向2030年的AI操作系统是什么样子的:浅谈cola这款有灵魂的Agent - 少数派 别只看写代码 - 少数派 每天解决10个问题,还是一口气攻坚解决400个? - 少数派 AI 交易机器人怎么搭?我用 Claude 跑了一周实盘 - 少数派 Maptoposter Online:把你爱的城市画成艺术海报 - 少数派 Function Calling 怎么用?我测了 3 个模型发现差距真大 - 少数派 Legend Talk:我做了个 AI 圆桌,让 160 位思想家围着你的问题转 - 少数派 如何找到自己的蓝方?在小县城寻找压力测试 - 少数派 语音输入与软件接口|2026年聊AI时,我们都聊些什么(上) - 少数派 混动已经卖爆,纯电又来补刀——钛7闪充版简直“不讲武德” - 少数派 本月玩什么|朋友收藏、识质存在、沙罗周期 - 少数派 为什么要每天坚持输出? - 少数派 Claude API 挂了好几个小时,你的项目有备用方案吗? - 少数派 Function Calling 没你想的复杂——我用它做了个有点用的工具 - 少数派 登录系统立即播放视频或者图片音乐的软件 - 少数派 我为什么创建 FlipHTML5 下载工具 - 少数派 残局没电?多品牌外设电量统一管理软件EasyBluetooth已支持RTSS游戏内显示以及AIDA64 - 少数派 前往通义路的路 - 少数派 太好看了,媲美Sun的个人导航页,NAS部署星云门户 - 少数派 乌黑嘴唇“一键检测”上线了 - 少数派 派早报:Claude AI 接入多个创意软件生态、FILCO 生产方接手品牌等 - 少数派 【更新】BearCLI、Claude 连接器与 MCP 服务器 - 少数派 记了上千条流水,还是看不懂财务?我做了一个让 AI 读懂账本的工作台 - 少数派 MINI R56 升级原厂 Sport 模式 - 少数派 新玩意 | 一棵柠檬树(仿真版) - 少数派 Momenta的“物理AI”野望,需迈过“含摩量”这道关 - 少数派 网页直接投屏控制手机!NAS一键部署PandaScrcpy,流畅丝滑可远程。 - 少数派 众测|邀你一同探索随身 AI 硬件入口 YoooClaw C·ONE - 少数派 2050大会:分享时间是真诚 参会记 - 少数派 iPad 赋能电影创作:国内首部宣纸手绘长片《燃比娃》的幕后故事 - 少数派 AI的审美:我用 8 个大模型给 100 张旅行照片打分 - 少数派 普通人如何破圈?去参加一个本地协会 - 少数派 把极空间的图标全换了,主题DIY全攻略打造你的专属NAS桌面 - 少数派 电子便签墙,帮你实现便签自由 - 少数派 我如何用三个 CLI 工具取代文档创建需求 - 少数派 原来真的有人可以玩一辈子 - 少数派 社区速递 139 | 派友热议三月买了啥、复古单反尼康 Df 体验 - 少数派 06 作品的赏析与评价 - 少数派 TDS REVIEW|索尼 WF-1000XM6 降噪真无线耳机体验 - 少数派 35.98万起售的第二代腾势D9,我看重的不是堆料,而是不凑合 - 少数派 鼠须管 Squirrel 皮肤配置指北 - 少数派 从watch ultra2换到redmi watch6 - 少数派 派早报:阿里巴巴发布视频生成模型 HappyHorse 1.0 等 - 少数派 别迷信1M - 少数派 家人们天塌了!网盘“大封杀”,多个渠道多条路,NAS部署PanHub - 少数派 AI与人勾心斗角!NAS一键部署AI狼人杀,假日休闲必备。 - 少数派 电商必备!Comfyui工作流批量生图插件,一次生成12张!支持Nano banana pro模型 - 少数派 Comfyui工作流配置Gpt-image-2模型教程,0.03/张 - 少数派 OpenClaw第三方APi怎么配置?可使用Gpt-image-2模型 - 少数派 会员社区话题精选 Ep. 103 - 少数派
从「原子化」提交到妙用 rebase,一次搞定清爽的 Git 提交记录 - 少数派
2025-01-13 · via 少数派

Matrix 首页推荐 

Matrix 是少数派的写作社区,我们主张分享真实的产品体验,有实用价值的经验与思考。我们会不定期挑选 Matrix 最优质的文章,展示来自用户的最真实的体验和观点。 
文章代表作者个人观点,少数派仅对标题和排版略作修改。


作为一名软件开发者,Git 是我离不开的版本管理工具,它可以记录并追溯开发进程,功能强大,用法灵活多样。围绕着它诞生的协作平台 GitHub、Gitee 等也深入人心。

Git 本身就是一门学问,其重要性不亚于软件开发。在长期的开源软件开发维护的过程中,我长期与 Git 打交道,对 Git 有了更多的感悟,也探索了一些非常实用的技巧。这些技巧,充分发挥了 Git 本身的特性,极大改善了我使用 Git 管理项目的体验。

下面,我就来分享让我受益最深的几点技巧,希望能给读者朋友们一些启发。

「原子化」提交,一个提交只做一件事

小时候,老师和长辈或许都告诫我们,在做事情的时候要「一心一意」,不要分散注意力到其他的事情上,这样做事才会高效、有成果。

一旦没有遵循「一心一意」的原则,那么干正事的时候碰壁就再寻常不过。就拿我本人来说,做正事的时候「摸鱼」,狂欢一阵子后再回来,面对还没有完成的正式任务,我是容易一脸懵的:我做了啥?进度怎么样了?我刚刚是怎么攻关难题的来着?——显然,这导致了时间和精力的浪费,更不利于事后复盘和提升。

不单单是做事,在使用 Git 的过程中,做每一个提交时也需要「一心一意」,也就是「原子化」(atomic)——确保每个提交只完成一项任务。

1)引例

可能一些开发者在组织 Git 提交时,习惯让一个提交做多件事情,把多项各自独立且不相关联的功能修改都放在同一个提交里。比如:

  • 一个项目有 A、B、C 三个功能模块,作者对这三个模块都做了修改,还顺带在 2025 年来临之际修改了所有源码开头注释里版权说明的年份1
  • 一个算法函数有多个各自独立的部分,作者在一个提交里就对这几个部分做了修改。
  • 作者原本在写 A 函数,但是发现 B、C、D 这几个函数的注释需要补充,于是统统做了修改,并放在同一个提交里。

这种提交方式看似省事儿,但无疑会给后续的开发工作「埋雷」。

2)为什么会「埋雷」?

根据笔者自己的经验体会,如果一个提交做了不止一件事情,那么有可能会在你日后开发的时候,给你带来一些「麻烦」:

0x00:难以排错

也许在开发过程中,某个提交给项目引入了 Bug。当你好不容易找到了存在问题的提交,但却发现这个提交做了不止一件事,你还要花费额外的精力来定位造成 Bug 的地方。

而如果你有不止一个提交都没有做到「一心一意」,那么你就很难直接浏览提交记录来找到问题所在,你就不得不翻阅一个个提交,浪费成倍的精力。

0x01:不便复盘

很多时候,开发者需要复盘自己的提交记录,看看自己做了哪些工作。然而,假如一个提交做了不止一件事,那么就不便于复盘开发过程。

例如,当你想要回顾某个功能的实现过程,找出实现该功能的提交时,却发现关键的代码都被混在几个大的、综合的提交里,难以找出。好比是考前复习的时候,你总结的关键知识点混在了一堆厚厚的复习资料里,自然会影响复习效率。

或许有些读者会选择详尽编写提交说明,通过检索提交说明来找出自己想要的提交。不过这带来的好处有限——你后续还是离不开使用 git show,在一长串「包罗万象」的 diff 记录中,找到真正想找的修改记录。

2)「原子化」提交

践行「原子化」提交的理念,以上的麻烦将不复存在。

C++ 从 C++11标准开始,引入了「原子操作」(atomic)特性,可以确保一个变量在同一时间只有一个操作者在读写,从而防止多线程抢占造成冲突。我借鉴了「原子操作」的理念,提出了「原子化」提交——也就是确保每个提交只完成一个任务,从而改进提交记录的组织方式,防止潜在的杂乱和冲突。

原子化提交最大的优势,在于提交记录简洁、可追溯、易于查错

  • 使用 git log 浏览提交记录时,你的开发进程一目了然,可以精确到每个提交实现的功能、修复的 Bug,等等,十分利于复盘。
  • 一旦某个提交出现问题,你可以快速浏览提交记录,短时间内找到可能存在问题的提交。然后,你就可以通过 git checkout <疑似有问题提交的前一个提交>,暂时回到应用该提交前的状态,来排查故障。
  • 在实现一个新功能、修复一个 Bug 的时候,你可以先浏览提交记录,快速判定之前是否有做了相关工作,从而避免「无用功」。

3)实践「原子化」提交

下面,我就以几个在实际开发场景中的例子,来演示「原子化」提交的操作。

实例一:修复 Bug 的「原子化」提交

假设你正在开发一个电商平台的项目,最近有用户反馈在使用优惠券时,无法正确抵扣金额。经过排查,你发现是优惠券计算逻辑中的一个 Bug。你修复了这个 Bug,并且为了提高代码的可读性,对相关代码进行了重构,将一些冗长的函数拆分成了更小的函数。

如果你遵循「原子化」提交的原则,你可以将这个过程分为两个提交:

提交序号提交信息(commit message)提交内容
1:专门修复 BugFix discount calculation bug in coupon system在这个提交中,你只修改了计算优惠券金额的代码,确保 Bug 被修复。
2:进行代码重构Refactor coupon calculation code for better readability在这个提交中,你对代码进行了拆分和重构,但不涉及任何 Bug 修复的逻辑。

这样做的好处是,如果后续发现修复 Bug 的代码引入了新的问题,你可以很容易地通过 git log 找到第一个提交,然后使用 git checkout 检出到修复 Bug 之前的版本,快速定位问题。同时,代码重构的提交也不会干扰到 Bug 修复的提交,使得提交记录清晰明了。

实例二:新功能开发的「原子化」提交

假设你正在开发一个在线教育平台,需要添加一个新的功能:允许用户上传视频作业。这个功能涉及到多个方面的开发,包括前端页面的设计、后端接口的编写以及数据库表结构的调整。

如果你遵循「原子化」提交的原则,你可以将这个功能的开发过程分为多个提交:

提交序号提交信息(commit message)提交内容
1:专注于前端页面设计Design video assignment upload page在这个提交中,你创建了新的 HTML 文件和 CSS 样式,实现了页面的基本布局和样式。
2:编写后端接口Implement backend API for video assignment upload在这个提交中,你创建了新的 API,实现了视频文件的接收和存储逻辑。
3:调整数据库表结构Add database table for video assignments在这个提交中,你创建了新的数据库表,用于存储视频作业的相关信息,如用户 ID、作业 ID、视频文件路径等。

通过这样的「原子化」提交,你可以清晰地记录每个功能模块的开发进度,便于后续的复盘和维护。如果某个模块出现问题,你可以快速定位到相应的提交,进行针对性的排查和修复,而不会被其他模块的代码干扰。

实例三:性能优化的原子提交

假设你正在开发一款合成器软音源插件,最近发现插件占用处理器资源过高,影响用户体验。经过分析,你发现是由于合成器的振荡器、滤波器算法优化不当,且编译参数存在问题,拖累性能。为了优化性能,你需要调整合成器算法,并修改编译系统的编译参数。

如果你遵循「原子化」提交的原则,你可以将这个优化过程分为多个提交:

提交序号提交信息(commit message)提交内容
1:优化振荡器算法Optimize oscillator with SIMD在这个提交中,你使用 SIMD 指令来改善振荡器的性能。
2:优化滤波器算法Optimize filter with SIMD在这个提交中,你同样使用了 SIMD 指令,来提升滤波器的工作效率。
3:优化编译系统的编译参数设置Enable compiler optimization在这个提交中,你为编译系统添加了编译器优化参数,使编译器充分优化代码性能。

通过这样的原子化提交,你可以清晰地记录每个优化措施的实施过程,便于后续的评估和维护。如果某个优化措施带来了新的问题,你可以快速定位到相应的提交,进行针对性的排查和修复,而不会被其他优化措施的代码干扰。

4)原子化提交的潜在优势

值得一提的是,原子化提交除了有利于你自己的代码管理,还能起到「利他」的作用。

在 GitHub 等开源社区中,原子化提交可以方便其他开发者浏览你的提交记录和开发进程,使你的开源成果更好惠及他人。井井有条的提交记录,还可以给他人留下好印象,彰显你的专业水准。

另一方面,像 Linux 这样的大型项目,每个提交也都是原子化的,并且更加规范,这大大方便了二次开发时对新特性的反向移植工作。

我自己也在做 Android 内核的适配,曾经将新版本内核的特性反向移植到华为 P6 的 3.0.6 内核中。开发过程中,我会从新版本内核的提交日志中找到与某个特性有关的补丁,然后将其应用到当前内核中,几乎是一找一个准,节省了在海量代码中「找重点」的精力。

可见,原子化提交利己利人,具有非常显著的潜在优势,受益不只一点点。

记得大概是 2018 年,我在刷知乎的时候见到一个提问:《Git commits 历史是如何做到如此清爽的?》,提问者非常惊讶于知名前端框架 Vue.js 源码库提交记录的干净、清爽。

Vue.js core 组件提交记录的一部分。

就在这个问题下,作者尤雨溪(Evan You)现身说法

多用 rebase。

就是大佬这短短三个字的经验谈,让我对 git rebase 这个功能产生了浓厚的兴趣。那时的我恐怕也不会想到,后来的我会如此频繁地使用 rebase,从而摆脱既往使用 Git 时提交记录琐碎、杂乱的问题,离正确使用 Git 更近一步。

1)琐碎的提交从何而来?

大家都知道,作为版本管理系统,Git 要保证提交记录可靠、可追溯,因此不能像文本编辑那样随意更改提交记录,最多只提供 git commit --amend 功能来允许你更改最新的提交。

但在开发过程中,或许你不太可能时时刻刻像 Vue.js 的提交记录那样,使自己每个提交都保持清爽、规范。回头使用 git log 浏览提交记录,你可能会发现你提交了太多琐碎的内容,「细致」到写一行代码、修一个错别字、修一个注释都有单独的提交。

很多时候这些提交是很琐碎的,没有必要单独保留,好比是你在文字处理软件中每写一句话就按“保存”,而每个修改都被单独保存成了一个文件。久而久之,提交记录就变得冗长琐碎,管理的时候就很难追溯到有用的、重要的提交。

这个时候, git rebase 就派上用场了。

Git 的设计师考虑到用户整理提交记录的需求,于是就设计了 git rebase 这一功能,允许你合并、编辑、重排已有的提交,使修改后的提交井然有序,就像 Vue.js 的提交记录那样清晰。

2)实战演示如何合并琐碎提交

以下面这个提交记录为例,记录了某项目从零开始写 main() 函数的过程,仅仅是添加文本输出与修改注释的提交就有好几个。

注意:假设这些提交彼此之间没有冲突,每个提交都是在原有提交之上的微调。

$ git log --pretty=oneline    # 使用单行模式输出提交记录。最新的提交在前。
3a51f37493191451413b8dc7428d63351ce4b1e3 (HEAD -> main, origin/main) main: 修改注释中的错别字
6587eeb437c8b139965085ddf99bd72bae682f89 main: 添加注释
61b04c318c24434996587eeb437c8b13996587e6 main: 添加操作结束的文本输出
61500445ebae1eb855ab216c6bbcec6ee73bd270 main: 微调操作开始的文本
7dad6bec684949ab0188085ddf99bc724c7b7b59 main: 添加操作开始的文本输出
996587eeb437c8b13996451413b8dc7603452f32 main: 添加基本文本输出
554bb6603452f3bf5705ac200effbdfc0aa97465 main: 创建main函数

接下来,笔者就要把所有提交都合并到第一个提交「main: 创建main函数(以下简称「目标提交」)当中。

0x00:检查代码树是否有未提交的更改

为了防止 rebase 弄乱代码仓库,Git 强制要求你的工作区「干净」,也就是不存在已经修改但没有提交的文件。否则,你是没有办法 rebase 的。

根据你的开发进度,你可以先提交修改;或者是使用 git stash 将修改暂时保存起来,等到完成 rebase 后再运行 git stash pop 恢复你的修改。

0x01:进入 git rebase 模式

使用 git log 查看提交记录,记住「目标提交」的 ID。然后,运行以下命令,开始 rebase:

git rebase -i 554bb6603452f3bf5705ac200effbdfc0aa97465~

这个命令,允许你修改从「最新提交」到「目标提交」在内的所有提交。注意不要漏了提交 ID 后面的波浪线,否则会把「目标提交」给漏掉2

稍等片刻,Git 会自动打开文本编辑器(通常是 Vim 或 Nano),列出一系列提交。其中:

  • 较新的提交列在文档的后面3,顺序是与 git log 相反的;
  • 每一行前面的 pick 是操作指令,意为采用该提交。这是默认的行为。
pick 554bb660 main: 创建main函数
pick 996587ee main: 添加基本文本输出
pick 7dad6bec main: 添加操作开始的文本输出
pick 61500445 main: 微调操作开始的文本
pick 61b04c31 main: 添加操作结束的文本输出
pick 6587eeb4 main: 添加注释
pick 3a51f374 main: 修改注释中的错别字

0x02:合并提交

在本例中,从996587ee3a51f374 的这几个提交,都是要并入「目标提交」的提交。

我们把这些提交对应行行首的 pick 改为 fixup (或单个字母“f”)。fixup 指令的作用是合并提交,但是只保留前一个提交(相邻一个比它早的提交)的说明。如下所示:

pick 554bb660 main: 创建main函数
f 996587ee main: 添加基本文本输出
f 7dad6bec main: 添加操作开始的文本输出
f 61500445 main: 微调操作开始的文本
f 61b04c31 main: 添加操作结束的文本输出
f 6587eeb4 main: 添加注释
f 3a51f374 main: 修改注释中的错别字

随后,保存文件并关闭编辑器,Git 就会自动开始 rebase,一个个把新的提交并入相邻的前一个提交中,直到目标提交。这样,那些琐碎的提交都被并入我们的目标提交中,如此一来提交记录就清爽了不少:

$ git log --pretty=oneline    # 使用单行模式输出提交记录
f5815166356e85a5fe244f6024c2e401f04b10fa (HEAD -> main, origin/main) main: 创建main函数

如果你希望保留相关提交的说明文本(以备参考等),那么你可以使用 squash 指令(或单个字母“s”),保存文件并关闭编辑器后, Git 会打开一个新文档,在这里你可以检查、修改提交说明。

注意:
经过 rebase 之后,原本的「目标提交」ID 会发生变化,因为 Git 实际上生成了一个新的提交。

3)如果琐碎的提交是后来才做出的

随着项目开发的推进,你达成的目标越来越多,提交数量也随之增长。但你发现一个早期编写的功能里,注释、代码缩进这些细节存在问题,于是再做了几个提交来修改。

现在,你想把这些琐碎的修改合并到该功能的提交中。此时你依然可以运用 git rebase,先调整提交顺序,然后再使用 fixup(或 squash)指令来合并提交。

0x00:准备工作

在继续操作之前,你需要确保仓库里没有未提交的更改。

另一方面,你还要保证那些琐碎的提交不与你既往的修改产生冲突。比如说,如果你的提交除了修改注释,还顺带修改了函数结构、变量定义等内容,那么很可能会与你的其他提交造成冲突,需要你手动干预,造成不必要的麻烦。

注意:
假设下文的提交彼此之间没有冲突,每个提交都是在原有提交之上的调整。

0x01:进入 rebase 模式,重排提交顺序

在运行 git rebase 之前,你需要通过 git log 检索提交日志,找到你的「目标提交」。

在下面的例子中,目标提交是554bb660 (「创建 main」函数),你的任务就是要把你的目标是把 3a51f3746587eeb4 这两个琐碎的提交合并到 554bb660 这个提交中。4

$ git log --pretty=oneline    # 使用单行模式输出提交记录
...
3a51f374 main: 修改注释中的错别字
6587eeb4 main: 修正代码缩进
996587ee process: 使用libfftw3,优化合成器算法逻辑
34e87ac3 process: 创建process函数
554bb660 main: 创建main函数
...

然后,运行 git rebase 命令:

git rebase -i 554bb660~

此时, Git 依然会打开一个文本编辑器,内容如下:

pick 554bb660 main: 创建main函数
pick 34e87ac3 process: 创建process函数
pick 996587ee process: 使用libfftw3,优化合成器算法逻辑
pick 6587eeb4 main: 修正代码缩进
pick 3a51f374 main: 修改注释中的错别字

我们在 git rebase 给我们打开的文本编辑器里,把3a51f3746587eeb4 这两个提交对应的行,整体复制到 554bb660 的后面,并将原有的行注释掉。就像下面这样:

pick 554bb660 main: 创建main函数

# 将提交所对应的行复制到我们的目标提交后面
pick 6587eeb4 main: 修正代码缩进
pick 3a51f374 main: 修改注释中的错别字

pick 34e87ac3 process: 创建process函数
pick 996587ee process: 使用libfftw3,优化合成器算法逻辑

# 为保险起见,将原有的行注释掉,而不是直接移动
#pick 6587eeb4 main: 修正代码缩进
#pick 3a51f374 main: 修改注释中的错别字

警告:
不要修改 pick 后面的内容,尤其是提交 ID,否则提交记录可能会发生混乱。

0x02:合并提交

确认提交顺序无误后,将待合并的提交对应行行首的 pick 指令改为 fixup (或 squash),如下所示:

pick 554bb660 main: 创建main函数

# 将提交所对应的行复制到我们的目标提交后面
fixup 6587eeb4 main: 修正代码缩进
fixup 3a51f374 main: 修改注释中的错别字

pick 34e87ac3 process: 创建process函数
pick 996587ee process: 使用libfftw3,优化合成器算法逻辑

# 为保险起见,将原有的行注释掉,而不是直接移动
#pick 6587eeb4 main: 修正代码缩进
#pick 3a51f374 main: 修改注释中的错别字

保存文件后,Git 随即开始 rebase 工作,这样我们就可以化琐碎为清爽,得到一个干净的提交记录了。

3)注意事项

在进行 rebase 前,务必要检查你要合并的提交与「目标提交」之间是否存在冲突。一旦存在冲突,那么 git rebase 就无法继续,会要求你手工修改你的仓库代码来处理冲突,这需要更多的时间和精力——因为你要保证代码正常无误。

另一方面,经过 rebase 合并所得的提交,本质上是一个全新的提交,并且也改变了原有的提交记录,因此如果别人 fork 了你的代码,在与你的仓库同步时必定会发生冲突。你或许需要告知你的团队成员,或者是通过 README 来告知代码共享平台5上的用户,告诉他们使用 git pull --rebase 来同步你的修改。

提交顺序不满意?也可以用 rebase 搞定

我自己在找到开发项目的新灵感后,会马上新建一个 Git 仓库,开始动手实践,并把我写的源代码提交到仓库里。然而当我想进一步把仓库上传到 GitHub 时,却发现:我忘了加上许可协议、README 和 .gitignore

考虑到这些文件都是在新建项目时就要添加的,如果我在完成了一部分程序功能后补上去,再回看 Git 提交记录,总是会觉得格外「别扭」。在这样的情况下,我依然可以运用 git rebase,单独调整提交顺序,还我自己一个科学有序的提交记录。

1)实践如何调整提交顺序

这里举一个高度简化的例子:假设笔者有一个项目,已经完成了程序的主体开发工作,后来才补上 README 与许可协议。提交日志如下(较新的提交在前面):

$ git log --pretty=oneline    # 使用单行模式输出提交记录
0fb4a3b0 添加 README.md
acbc6080 添加许可协议(GPLv3)
a841dbc1 UI 的 bug 修复
16f688a6 DSP 性能优化
75ca23f6 完成 UI 开发
277aad5f 完成 DSP 开发
eb323b0e 初始提交(Initial commit)

我希望把与 README、许可协议相关的提交──也就是 0fb4a3b0acbc6080 ──挪到时间顺序上的初始提交之后,也就是提交日志中初始提交的前一行。

0x01:打开 git rebase

在本例中,我已经确定好了「目标提交」,也就是初始提交,并且已经确保工作区没有未提交的代码。

然后,运行 git rebase,定位到目标提交:

git rebase -i eb323b0e

接下来 Git 会打开文本编辑器,显示以下内容(较新的提交在文档的后面):

pick 277aad5f 完成 DSP 开发
pick 75ca23f6 完成 UI 开发
pick 16f688a6 DSP 性能优化
pick a841dbc1 UI 的 bug 修复
pick acbc6080 添加许可协议(GPLv3)
pick 0fb4a3b0 添加 README.md

需要注意的是,git rebase 不能显示初始提交,所以我们能看到的最早的提交是紧邻初始提交之后(比初始提交新)的第一个提交。

0x02:开始挪动

先把0fb4a3b0acbc6080 这两个提交对应的行整体复制到 277aad5f (也就是初始提交后的第一个提交)前面,然后将原有的那两行注释掉,如下所示:

# 将提交所对应的行复制到我们的目标提交后面。
# 本例中的目标提交是初始提交,所以我们实际上是把要挪动的提交放在 git rebase 文档的最前面
pick 0fb4a3b0 添加 README.md
pick acbc6080 添加许可协议(GPLv3)

pick 277aad5f 完成 DSP 开发
pick 75ca23f6 完成 UI 开发
pick 16f688a6 DSP 性能优化
pick a841dbc1 UI 的 bug 修复

# 为保险起见,将原有的行注释掉,而不是直接移动
#pick acbc6080 添加许可协议(GPLv3)
#pick 0fb4a3b0 添加 README.md

确认无误后,直接保存,此时 Git 就会自动开始 rebase 工作,稍等片刻再查看提交记录,你会发现提交顺序变了,README 与许可协议所对应的提交就出现在了初始提交的后面,大功告成。

2)注意事项

考虑到 rebase 时可能会面临的冲突,你需要三思而后行。

通常只建议重排那些对其他提交记录几乎没什么干扰的提交,例如你的提交只创建、修改了某一个特定的文件(README、许可协议这样的文件)。若重排不慎,你就不得不花费大量的精力来处理 rebase 的冲突,还有可能把提交记录弄乱。6

同样地,你还需要告知你的团队成员或用户,使用 git pull --rebase 来合并你的更改。

对于未成型项目,git commit --amend 也许更适合你

git commit 是 Git 用于提交修改的命令。它有一个参数 --amend,允许你修改最新一次提交的内容。有时你发现刚刚提交的代码有错误,或者是提交说明有问题,但是你不想再新建一个提交来修正这些错误,那么你就可以使用 git commit --amend 这个命令。

然而,对我来说,git commit --amend 的用途远不止于此。在项目还未成型的阶段,我使用它来保持提交记录的清爽。

1)为什么我会常用 git commit --amend

我开发的项目,主要是将现有的开源音频插件移植到 DPF 这个跨平台框架7,这往往是「摸着石头过河」——无论是 DPF 还是被移植的插件,都缺乏文档,全靠我自己摸索。

在早期阶段,代码文件结构、模块和功能代码、编译系统等都还没有定稿,程序也只实现了部分功能:这就是未成型的状态。我个人习惯一边写功能一边调试,常常反复调整代码结构和算法,直到真正实现我预期的目标为止。

这,往往意味着我要持续修改源代码库。如此背景之下,如果每个修改都单独提交到仓库里,那么就意味着仓库里会有数十甚至上百条极其琐碎的提交记录,待到项目成型时还要用 git rebase 来整理提交。你可以想象一下,用 WPS 写文章,每写一句话就另存一个文件,是什么样的感觉。

2)如何妙用 git commit --amend

为了解决上述问题,git commit --amend 就成为了我最常用的操作之一。我会采用这样的思路:

  • 第一步,用一个提交来存放项目成型前的所有开发工作。提交说明为「Early development (WIP!)」,其中「WIP(Work In Progress)」表示该提交随时可能被覆盖。做出这个提交之后,就不要再做新的提交。
  • 第二步,每次完成一部分功能,确认代码编写与程序运行无误时,就使用 git add 命令来将修改过的文件暂存(stage)起来,随后使用 git commit --amend 更新这个提交的内容。
  • 第三步,继续完成开发,直到项目成型。此时,再运行 git commit --amend,重新修改提交说明,将「WIP」等字样删除,代表我的早期开发工作已经定稿。

我个人偏向在项目成型之后,才开始转变为以一个个单独提交的方式来持续开发。这里的「成型」,大致可理解为代码结构稳定,程序预期功能已经实现,至少有可以跑起来的 Alpha 版本推出。

如此一来,提交记录就会变得清爽,不会让琐碎的早期开发记录「挤占」你的 git log,事后也不需要再单独花时间来 rebase。

3)注意事项

  • 由于这种方法并不会为每个更改留下单独的提交,后续难以通过 git log 回溯,故只建议在项目未成型时使用。如果你不放心,依然可以每做一组工作就提交一次,对此我建议在提交说明里做标记,以备项目成形后再 rebase 提交记录。
  • git commit --amend 之前,也务必确保程序与代码无误,以免混乱。

除此之外,当你在为你的项目添加新功能时,你也可以在原型设计、测试的阶段使用git commit --amend ,因为这个过程常常就像写一篇新文章,你或许也不希望每写一句话就又来另存一个新文档。

写在最后

在日常与 Git 打交道的过程中,我一直致力于精进 Git 的使用。上面这几点技巧,就是我自己的实践成果,着力于让提交日志更清爽、规范,方便后续的维护。让我们再来回顾一下:

  • 使用「原子化」提交,每个提交只做一件事,利于管理、维护和回顾你的开发进度;
  • 灵活运用 git rebase,合并细碎的提交,按需要重排提交,使提交记录清爽、规范;
  • 项目未成型时,活用 git commit --amend,保持提交记录清爽。

当然,以上的技巧,更多体现出我个人的使用习惯,客观上也改善了我自己 Git 仓库的质量。相信我的分享能为感兴趣的读者朋友们提供参考,一同将 Git 用得更自在。