Git提交最佳实践

39
我正在使用git管理一个C++项目。当我在项目上工作时,如果需要更改与许多地方相关的内容,就很难将这些更改组织到提交中。
例如,我可能会更改一个头文件中的类接口,这将影响相应的.cpp文件以及其他使用它的文件。我不确定是否合理将所有内容放入一个大的提交中。
直觉上,我认为提交应该是模块化的,每个提交对应于一个功能性的更新/更改,这样协作者就可以相应地选择事物。但有时似乎不可避免地要包括许多文件和更改才能使功能性更改实际起作用。
搜索并没有给我提供任何好的建议或提示。因此,我想知道是否有人能够给我一些关于提交的最佳实践。
注:我已经使用git一段时间了,知道如何交互式地添加/重置/分离/修改等。我询问的是哲学部分。
更新:感谢所有的建议。也许这应该通过实践来学习。我会保持这个问题开放一段时间,看看是否有更多的建议。

我认为提交应该是模块化的,可以使用标签实现。标签不一定是版本号。只要不破坏构建,就可以根据需要经常进行提交。 - glmxndr
1
@subtenante:使用标签可能不是最好的选择。你会完全失去重要标签的追踪。 - knittl
1
@knittl:看不出为什么。对于“重要”的标签使用命名约定,并在列出它们时使用模式。 - glmxndr
1
@subtenante:不幸的是,Git在有太多标签时无法很好地扩展。 - knittl
7个回答

23

我倾向于按您所建议的进行提交:一个 commit 是一个逻辑上相关的更改集。我的提交可以是任何东西,从一行代码到修改所有文件(例如在源文件中添加/更改版权声明)。更改的原因不必是我正在实现的完整任务,但通常是任务的一个重要里程碑。

如果我修改了与当前提交无关的内容,我倾向于进行交互式添加以分离出不相关的更改,即使只是进行空格整理。

我发现仅将工作状态转储到仓库的提交会使它们变得没有用:如果提交分散在各处,则很难将错误修复回退到早期版本或轻松地在另一个分支中包含实用程序功能。

这种方法的替代方案是在功能分支中使用许多微小的提交,并在整个功能完成后,对历史记录进行大量重写,将提交整理为逻辑结构。但我发现这种方法浪费时间。


推荐多次提交还是将多个提交合并为一个提交? - alper

18
这正是引入 Git 中的index暂存区的使用场景。
您可以随意进行尽可能多的不相关更改。然后,您选择哪些更改相关,然后一次性进行几个原子提交。
我经常这样做。如果您使用git-gui或其他GUI客户端,则不仅可以选择要提交的文件,还可以选择文件中的补丁,以使您的提交尽可能原子化。

2
你也可以使用 git add -p 命令行来选择性地提交补丁,而不需要使用 GUI 客户端。 - Dave Sherohman
这是真的。我通常使用 gitx 来进行交互式暂存工作。 - Ivan Xiao
2
git add -i比-p功能更强大。它提供了一个基于菜单的CLI系统,您可以在其中更新整个文件、添加补丁等等。 - Zefira
你也可以互动地进行变基操作,以从主题分支的过去提交中添加或删除内容,如果这些提交已经被创建了(git rebase --interactive HEAD~x,其中x是向后几个提交)。这是为什么 应该始终创建原子提交(以及它们的确切含义)。 - Fagner Brack

18

我尝试按照以下顺序遵循这些实践:

  1. 提交必须通过构建。最重要的!

  2. 它应该由一个逻辑变更单元组成——无论是单行/字符还是整个文件/类以及代码其他部分相应的更改,仍然遵循#1。

    什么是逻辑变更单元?就git而言,如果您可以在最少的字符数中在提交消息中指定更改,在一句话中(当然没有AND),并且您不能将该描述进一步拆分为较小的单元,则我称之为一个单元。

  3. 提交消息应清楚地指定提交的实质。

  4. 提交消息应该很小,通常不超过80个字符。任何更多的阐述都应该是“描述”的一部分。


6
我发现第四点有些令人困惑。提交信息是编辑器中的整段文本,它应该尽可能长以便解释提交的内容。它应该结构化为一个一行的“主题”,最多不超过50个字符(这是git自己的编码指南),然后是一个空行,接着是任何需要的详细解释。 - Jan Hudec
1
你所提到的“subject”在他们的术语中是“message”,其余部分属于“description”。 - Sailesh
1
是的,我也遵循这些策略。但是,仍然有情况,即使我遵循这些实践,甚至单个函数的更改也可能引入大量提交。 - Ivan Xiao
2
我不会完全归咎于Git,如果你需要一个大的提交来改变一个函数。这与你如何构建代码、分离关注点和封装数据同样重要。 - theodorton

12

免责声明:我也正在努力弄清楚哪些提交应该是什么,以及最终历史记录应该是什么样子。但是,我想分享一些我在自己的研究过程中遇到的资源。

首先,Linux Kernel 项目有一个很好的页面,介绍了 合并策略,可用于将您的代码合并到上游分支。他们谈论制作小块提交;在实际添加之前,进行一项或多项重构提交(重构当然是为了使您的功能更加清洁)等。

我最喜欢的另一个页面是 Seth Robertson 的Git 最佳实践。 这不仅是一个关于使用 git 的许多最佳实践的页面,而且还是一个巨大的资源,包含足够的关于各种 git 主题的信息,使得搜索更深入的信息变得微不足道。


3
感谢您提供的 Seth Robertson 链接并点赞。现在我了解了与 Git 相关的“制作香肠”术语,我可以安心离开了。 - user458541

5
我所询问的是哲学部分。
我认为我可以回答这个问题,因为最近我参与了一些个人研究。
应该专注于创建原子提交。这意味着需要在提交中特别注意以下几点:
  • 如果部分完成,则不应具有任何价值
  • 不应破坏构建
  • 应包含良好的消息和正文以进行可追溯性(尽可能附带票证引用)
  • 不应包含大量差异噪声(空格和样式更改,除非提交专门针对此问题)
提交应专注于一个变化,仅一个变化。超过一个变化可能会产生负面影响。
一些人可能会认为这太过分了,不切实际。但即使对于小公司来说,支持它的最好论据是,构建原子提交将迫使您的设计更加解耦和一致,因为实现完全优化的原子提交的一个要求是拥有一个健康的代码库,而不是一团乱麻。
如果您始终强制执行良好的提交实践,则将能够将工程文化和代码本身推向更好的状态。

3
有时候进行大规模重构时,不可避免地需要在一次提交中更改许多文件。当您更改类的接口时,必须在一次提交中更改头文件、实现文件和所有使用该接口的地方,因为中间状态都无法工作。
然而,推荐的做法是在实际引入任何新功能之前更改接口,测试您没有破坏现有功能并进行提交。然后再实现需要更新接口的实际功能并单独进行提交。在此过程中,您可能会对重构进行一些调整,并使用交互式变基将其压缩到第一个提交中。
这样就有了一个大的提交,但它并不做任何困难的事情,只是重新排列代码,所以即使它很大,也应该大部分容易理解,然后是第二个提交(如果特性很大,则可能还有更多提交),它不会太大。

1

在工作中,对我非常有帮助的一件事是将我们的代码库组织转换为“特性分支”模型,这种模型由Git Flow扩展广泛使用。

通过拥有描述正在开发的每个特性(或更新、错误修复等)的分支,提交变得不再关注于特性本身,而更多地关注于如何实现该特性。例如,最近我正在修复一个时区错误,它有自己的错误修复分支(例如bugfixes/gh-87),并且提交被分成了服务器端和前端以及测试中完成的内容。因为所有这些都发生在专门用于该错误的分支上(还有GitHub问题编号,以便清晰和自动关闭),我的提交被视为解决该问题的逐步步骤,因此需要较少的解释为什么要这样做。


谢谢提供信息。我知道Git Flow。然而,它与我所问的问题不太相关。 - Ivan Xiao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接