为什么?还有怎样才能保持你的 Git 提交历史清晰?

file
提交是 Git 仓库的重要组成部分之一,不仅于此,提交信息  贯穿于 Git 仓库的整个生命周期。随着项目/仓库的发展(新特性的增加、Bugs 的修复、架构的重构),提交信息可以让我们看到改了什么地方以及是如何改动的。因此,这些信息以一种简短、精确的方式反映着潜在的变化是非常重要的。

为什么有意义的提交历史很重要

Git 提交消息就像是你在你接触过的代码上面留下的指纹。任何你今天提交的代码,一年以后当你看到这段代码的变化时,你将感谢自己当时留下的清晰、有意义的提交信息,同时它还将使你的开发变得更为容易。当提交被基于上下文独立时,由某个提交引入的 Bug 将会更快被找到,并且更容易恢复到导致错误提交前的代码。

当你工作在一个大的项目中时,我们经常更新、新增或者移动文件。确保在这种情况下维持提交消息将很棘手,尤其是在开发周期跨越数天,数周甚至数月的情况下。因此,为了简化维护简洁提交历史记录的工作,这篇文章将使用开发人员在处理 Git 仓库时可能遇到的一些常见情况。

在我们深入讨论之前让我们快速的了解一下,在我们假设的 Ruby 应用程序中,典型的开发工作流是什么样子的。

注意: 这篇文章假定您了解基本的 Git 知识 、分支的工作方式、如何在阶段中添加未提交的分支更改以及如何提交更改。如果你不了解这些流程, 我们的文档 是一个很好的起点。

日常开发中的一天

现在,我们正在开发一个小型的 Ruby on Rails 项目。我们需要在首页添加一个导航视图,这涉及到更新和添加几个文件。下面是整个流程的逐步细分:

  • 你首先从更新某个文件开始来开发新功能;我们叫它为application_controller.rb
  • 这个功能也需要你更新一个视图文件:index.html.haml
  • 你添加了一个局部视图,它会在首页中用到:_navigation.html.haml
  • 页面样式也需要更新,让它作用到我们的视图上:styles.css.scss
  • 新功能已经准备好所有期望的修改,该更新测试文件了;要更新的文件如下:
    • application_controller_spec.rb
    • navigation_spec.rb
  • 测试文件更新完毕并如期通过,现在是时候提交所有更改了!

因为所有的文件分属于架构的不同区域,所以我们对这些更改彼此隔离进行提交,确保每次提交代表一个特定的上下文,并按顺序提交。我通常喜欢从后端 -> 前端的顺序。首先提交大多数以后端为中心的更改,接着是中间层,最后是在提交列表中以前端为中心的更改。

  1. application_controller.rb & application_controller_spec.rbAdd routes for navigation
  2. _navigation.html.haml & navigation_spec.rbPage Navigation View
  3. index.html.hamlRender navigation partial
  4. styles.css.scssAdd styles for navigation

现在我们已经提交了更改,我们使用分支来创建 merge 请求。一旦创建了 merge 请求,通常会在更改合并进 master分支之前由原仓库的拥有者进行审核。现在我们来了解一下,在代码审核期间我们可能会面临的不同情况。

情况1:我需要修改最近的提交

想象一下,审阅者查看了styles.css.scss文件并建议进行更改。在这种情形下,进行更改非常简单,因为对样式的更改是你分支上 最近一次 提交的一部分。下面是我们怎样处理这次更改的步骤:

  • 你可以直接在你的分支上对styles.css.scss文件做必要的更改。
  • 一旦你完成了这些更改,请将这些改变添加到暂存区;运行git add styles.css.scss
  • 当这些改变被暂存之后,我们需要将这些更改 添加 到上次的提交中;运行 git commit --amend
    • 命令分解:在这里,我们要求git commit命令 修改 暂存区中的最近一次提交。
  • 这将会用你已定义的 Git 文本编辑器打开最近一次提交,并且提交消息是 Add styles for navigation
  • 因为我们只更新了 CSS 声明,所以无需修改提交消息。此时,你只需要保存并退出 Git 为你打开的文本编辑器,所有的更改将会反映在本次提交中。

由于你修改了现有的提交,所以必须使用git push --force-with-lease <remote_name> <branch_name>将这些更改 强制推送 到你的远程仓库。这个命令将会使用我们在本次仓库更新后的提交,覆盖远程仓库上消息为Add styles for navigation的那次提交。

在强制推送分支时需要注意的一件事是,如果你和其他同事工作于同一分支,当其他人尝试向一个刚被强制推送过的远程分支正常推送他们的更改时,可能遇到一些麻烦。所以,请明智地使用强制推送。你可以在 这里 了解有关 Git 强制推送的更多选项。

情况 2:我需要更正一个特定的提交

在之前的情况下,由于我们只能修改最后一次提交,所以这种修复非常简单,但是想象一下,如果审稿人建议改变_navigation.html.haml。 在这种情况下,因为它是第二次从顶部提交,所以要想改变它不会像第一种情况那样直接。让我们还是来看看如何处理这个问题吧:

每当在分支中进行提交时,它就被唯一的SHA1哈希字符串标识。把它看作是一个唯一的ID,它将一个提交与另一个提交分开。通过运行git log,您可以查看分支中的所有提交以及它们的SHA1哈希。通过它,你会看到一个看起来有点类似下面这样的输出,其中最近的提交在顶部;

commit aa0a35a867ed2094da60042062e8f3d6000e3952 (HEAD -> add-page-navigation)
Author: Kushal Pandya <kushal@gitlab.com>
Date: Wed May 2 15:24:02 2018 +0530

    Add styles for navigation

commit c22a3fa0c5cdc175f2b8232b9704079d27c619d0
Author: Kushal Pandya <kushal@gitlab.com>
Date: Wed May 2 08:42:52 2018 +0000

    Render navigation partial

commit 4155df1cdc7be01c98b0773497ff65c22ba1549f
Author: Kushal Pandya <kushal@gitlab.com>
Date: Wed May 2 08:42:51 2018 +0000

    Page Navigation View

commit 8d74af102941aa0b51e1a35b8ad731284e4b5a20
Author: Kushal Pandya <kushal@gitlab.com>
Date: Wed May 2 08:12:20 2018 +0000

    Add routes for navigation

这就是 Git ReBase 命令发挥作用的地方。每当我们希望编辑 git ReBase 的特定提交时,我们需要先将我们的分支重新定位为在我们希望编辑的提交之前,将头移到右边。在我们用例中,我们需要更改读取的提交  Page Navigation View

file

这里请注意,正确的提交哈希是我们想要修改提交的前一个提交哈希;拷贝那个哈希,然后执行下面的步骤:

  • 变基(rebase)分支,移动到目标提交的前一次提交;运行git rebase -i 8d74af102941aa0b51e1a35b8ad731284e4b5a20
    • 命令分解:这里我们运行rebase命令的 交互 模式,并提供了要变基的提交哈希。
  • 这将在 Git 交互模式运行 rebase 命令,并打开你的文本编辑器,里面显示了正在变基的提交(8d74af)之后 的所有提交。它看起来就像这样:
pick 4155df1cdc7 Page Navigation View
pick c22a3fa0c5c Render navigation partial
pick aa0a35a867e Add styles for navigation

# 在 8d74af10294 上变基 8d74af10294..aa0a35a867e 范围(3次提交)
#
# 命令:
# p, pick = 保留本次提交
# r, reword = 保留本次提交,但要修改提交消息
# e, edit = 保留本次提交,但暂停下来进行修改(不只修改提交消息)
# s, squash = 保留本次提交,但合并到前一次提交
# f, fixup = 与“squash”类似,但丢弃本次提交消息
# x, exec = 运行 shell 命令(本行的剩余内容)
# d, drop = 删除本次提交
#
# 这些行可以重排顺序,并自顶向底依次执行。
#
# 如果你删除一行,那次提交将会丢失。
#
# 如果你删除所有行,本次变基(rebase)将会中止。
#
# 注意空提交已注释掉。

注意每个提交前面都有一个单词pick,下面的注释是我们可能用到的关键字。因为我们想要 编辑 某此提交(4155df),所以需要把pick 4155df1cdc7 Page Navigation View改成edit 4155df1cdc7 Page Navigation View。保存修改,然后退出编辑器。

现在你的分支就重置到了所做的修改包含_navigation.html.haml的时刻。打开文件根据审核反馈执行需要的修改。一旦修改完毕,通过运行git add _navigation.html.haml命令进行暂存。

既然我们暂存了这些改变,现在是时候把 HEAD 分支移回我们的原始提交了(包含我们添加的最新修改),运行git rebase --continue,这将会在终端打开你的默认编辑器然后显示在变基(rebase)期间的提交消息;Page Navigation View。如果希望的话,你可以改变这个提交消息,但是我们现在让它保持现状,保存并退出编辑器。此刻,Git 会重新播放你所编辑的提交之后的所有提交,并且现在的HEAD分支回溯到了我们原始的顶部提交。

由于我们再次修改了远程仓库中已存在的一次提交,所以需要使用git push --force-with-lease <remote_name> <branch_name>强制推送分支。

场景3:我需要添加、删除或者合并 commit

一个很常见的场景就是为了修复之前提交的内容,你已经 commit 了几次。现在我们来尽可能地减少 commit 的次数并把它们和原来的提交合并起来。

你需要做的就是就像你在其他场景一样启动交互式 rebase。

pick 4155df1cdc7 Page Navigation View
pick c22a3fa0c5c Render navigation partial
pick aa0a35a867e Add styles for navigation
pick 62e858a322 Fix a typo
pick 5c25eb48c8 Ops another fix
pick 7f0718efe9 Fix 2
pick f0ffc19ef7 Argh Another fix!

假设你现在想要把这些提交记录都合并到 c22a3fa0c5c Render navigation partial。你只需要做到:

  1. 将 fixes 向上移动直到它们位于你希望保留的最后的提交下面
  2. 把每一个 fix 的 pick 改为 squash 或者 fixup

注意: squash 保留了描述中的提交注释。 fixup 不会保留提交的注释而只保留原始注释。

你会得到这样的结果:

pick 4155df1cdc7 Page Navigation View
pick c22a3fa0c5c Render navigation partial
fixup 62e858a322 Fix a typo
fixup 5c25eb48c8 Ops another fix
fixup 7f0718efe9 Fix 2
fixup f0ffc19ef7 Argh Another fix!
pick aa0a35a867e Add styles for navigation

保存更改,退出编辑,你就完成了!这是产生的历史:

pick 4155df1cdc7 Page Navigation View
pick 96373c0bcf Render navigation partial
pick aa0a35a867e Add styles for navigation

和以前一样,你要做的就是 git push --force-with-lease <remote_name> <branch_name>,改变就生效了。

如果你想要完全地删除一个提交,把 squash 或者 fixup 换成 drop 或者干脆删掉那一行。

避免冲突

为了避免冲突,请确保你移动到的目标没有编辑到同一个文件。

pick 4155df1cdc7 Page Navigation View
pick c22a3fa0c5c Render navigation partial
fixup 62e858a322 Fix a typo                 # this changes styles.css
fixup 5c25eb48c8 Ops another fix            # this changes image/logo.svg
fixup 7f0718efe9 Fix 2                      # this changes styles.css
fixup f0ffc19ef7 Argh Another fix!          # this changes styles.css
pick aa0a35a867e Add styles for navigation  # this changes index.html (no conflict)

技巧: 快速 fixup

如果你清楚知道你将 fixup 哪一个提交,你不需要浪费时间构思一个临时的名称,如: "Fix 1", "Fix 2", ..., "Fix 42"。你可以使用以下的方法:

第一步:开始使用 --fixup

在你 stage 并且修复了你想要的内容时,使用以下命令:

git commit --fixup c22a3fa0c5c

(注意这个 hash 信息对应 c22a3fa0c5c Render navigation partial)

以上命令会生成一个 commit:: fixup! Render navigation partial.

第二步:召唤你的好朋友 --autosquash

简单的交互性 rebase,以下命令会让 git 在正确的位置里设置 fixup:

git rebase -i 4155df1cdc7 --autosquash

现在你的历史是:

pick 4155df1cdc7 Page Navigation View
pick c22a3fa0c5c Render navigation partial
fixup 62e858a322 Fix a typo
fixup 5c25eb48c8 Ops another fix
fixup 7f0718efe9 Fix 2
fixup f0ffc19ef7 Argh Another fix!
pick aa0a35a867e Add styles for navigation

你还可以使用 git rebase --autosquash 命令来直接跳过 review 阶段,但是一般建议除非你感觉特别安全,否则还是少用为妙,因为你没机会 review 到具体变更。

场景 4: 我的提交信息太乱了,我需要重新开始

如果你在开发一个大功能,有时候你在分支里(如:add-page-navigation)留下很多 commit ,而你不喜欢让这些 commit 进入主分支,接下来我教你一个方法:

  • 在开始之前,请确保你的分支已经是最新的并且不会与  master 分支冲突;
  • 当你 checkout 在 add-page-navigation 分支下时,可以使用 git rebase master 或者 git merge master 命令来保持与 master 的更新; 
  • 接下来使用命令创建补丁文件  git diff master add-page-navigation > ~/add_page_navigation.patch
    • 命令分解:我们使用 Git 的 diff 功能,对 master 分支和 add-page-navigation 分支做了一个 diff 操作,并且将结果输出到  ~/add_page_navigation.patch 文件中。
  • 文件路径你可以随意设置;
  • 命令成功执行后没报错的话,补丁文件就算创建成功了;
  • 现在我们运行 git checkout master 切换到  master 分支上;
  • 使用命令 git branch -D add-page-navigation 删除本地分支 add-page-navigation (我们已经有补丁文件了,不要害怕);
  • 接下来使用命令 git checkout -b add-page-navigation 创建一个新的分支;
  • 此时我们在全新的 add-page-navigation 分支上,此分支没有任何更改;
  • 最后,使用命令 git apply ~/add_page_navigation.patch 来应用补丁里的修改;
  • 所有的修改都会应用上,并且会以 未提交 的状态显示着;
  • 此时你可以选择将所有更改一次提交,或者单个文件提交,随意操作即可。

就跟之前遇到的场景一样,我们修改了整个分支,你需要做一个 force push 了。

总结

尽管我们已经介绍了使用 Git 进行日常工作流程中出现的大多数常见的情况,但重写 Git 历史是一个巨大的话题,并且当你在熟悉上述提示时,你可以在 Git 官方文档 学习围绕该主题的更高级概念。祝你愉快的学习 Git。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://about.gitlab.com/2018/06/07/keep...

译文地址:https://learnku.com/devtools/t/13616/why...

本帖已被设为精华帖!
本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 2

感受一下我司的 git 堪比天书,自己都看不懂
file

file

4年前 评论
翟宇鑫

我司一个三人组 API 项目,半年提交了 400 多次,太顶了

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!