打包、发布和日常开发 本章关于自由软件项目如何打包和发布软件,以及如何让整个开发模式的组织围绕这个目标。 开源项目和私有项目的主要区别是缺乏对开发团队的中央管理。当准备新版本时,这个区别尤其明显:一个公司可以让整个开发团队集中精力在即将发生的版本上,而将新特性开发和不重要的bug修正放在一边。志愿团队不会如此整齐划一。人们因为各种各样的原因为项目工作,总有些人会对发布版本不感兴趣,会希望在发布时继续常规的开发工作。因为开发不会结束,开源的发布流程很容易变长,但不会如商业发布流程那样分裂。这就像修理高速路。有两种修理方法:你可以将其完全关闭,这样船员们可以全力投入,直到问题被解决,或者你可以在多个小道上同时工作,而让其他人可以自由通行。第一种方法对修理船员非常有效率,但对于其他人来说—完成任务前道路被完全关闭。第二种方法,修理船员(现在他们需要与较少)会需要更长的时间,但至少道路保持可用,尽管不能完全畅通。 开源项目通常会使用第二种方法。实际上,对于同时拥有多条版本线的成熟软件,项目一直处于修理小路的状态。总会有些小道会被关闭;一直存在的但是较低级别的不便能够被整个开发团队容忍,所以才能够有规律的计划发布版本。 这个模型不仅仅能用于版本发布。其原理是并行任务并不是互相依赖的—这并不是只存在于开源项目的原理,但开源项目使用了自己的方法实现了它。他们不能承受对修路工队员或常规交通的过度打扰,同样也不能承受让人们只是站在黄线以外,等待交通疏导。因而,他们会向平缓的、稳定管理负担的过程发展,而不会是充满更多的山谷和高峰。志愿者通常会希望工作中只有一些虽然持续但不太严重的不便;提供些许他们可预测性会让他们可以自由的安排自己的日程,无需担心在项目中发生冲突。但是,如果项目主日程中有些活动会排除其他活动,就会导致很多开发者停止很长时间—不仅非常没有效率,也非常无聊,因而会很危险,因为无聊的开发者会很快变成前开发者。 版本发布工作实际上是并行开发中最容易被注意到的非开发任务,所以下面章节中描述的方法主要针对如何允许发布。然而,也有其他的并行任务,例如翻译和国际化、在整个代码的基础上逐渐扩大API变更等。 版本号 在我们讨论如何发布之前,首先看一下如何命名版本,用户需要从版本中知道什么。一个版本发布意味着: 老的bug被修正了。恐怕所有的用户都认为每个版本都理所当然应该做到这一点。 会带来新bug。一般情况下这是必然的,除非是安全版本或其他特殊情况(见本章后面)。 也加入了新的特性。 新配置选项也会被添加,或者一些以前的选项有了微妙的变化。和上个版本相比,安装步骤可能有轻微的调整,即使人们不希望这样。 也可能带来不兼容的变化,例如以前版本的软件所用的数据格式必须做出某种(可能是手工的)单向的转化,才能继续使用。 就像你看到的,发生的不都是好事。这就是为什么经验丰富的用户总是对新版本保持恐惧,特别是当软件已经足够成熟,已经能够满足他们的需要时(或者认为他们满足了)。即使新特性是好坏参半的事情,也意味着软件现在可能以不可预期的方式运行。 因此发布版本编号的目的是双重的:很明显这个编号可以在交流中明确发布的顺序(例如,通过比较两个版本号,你可以看出哪个版本更老),另一方面,也应该尽可能紧凑的表明该发布所带来变更的程度和性质。 仅仅是号码吗?或多或少是。版本编号策略是最古老的自行车库讨论(见),这个世界也似乎从来没有能达成一个单独的完整的标准。然而,还是出现了一些好的策略,依据了一些广泛认同的原理:一致性。采取一种编号方式,记录下来,并坚持使用。你的用户会感激不尽。 版本号组成部分 本小节详细描述了一些发布版本号的正式惯例,认为是已知的预先知识。目的仅仅是作为一个参考。如果你已经了解了这些惯例,你可以跳过本小节。 发布号码是一组点分割的数字: Scanley 2.3 Singer 5.11.4 ...等等。这些点不是小数点,而仅仅是分隔符;“5.3.9”之后将会是“5.3.10”。某些项目偶尔也会用其他的提示,例如非常有名的Linux内核,在Linux 1.0之前使用的是“0.95”、“0.96”... “0.99”,但是这种点号成为了固有的习惯,已经被认为是一种标准。数字部分(不包含点的数字部分)的数量没有限制,但是大多数项目不应该超过3或4。在后面我们会对原因有更清楚的认识。 除了数字部分之外,一些项目也会附加一个描述标签,例如“Alpha”或“Beta”(见),例如: Scanley 2.3.0 (Alpha) Singer 5.11.4 (Beta) Alpha或Beta修饰意味着这个版本是一个将要发布版本(同样的版本号,但没有修饰)的先例。因此,“2.3.0 (Alpha)”将会带来“2.3.0”。为了能排列好多个这样的候选版本,修饰符也可以有一个之后的修饰。例如,下面是一系列即将发布的版本,根据发布时间排序: Scanley 2.3.0 (Alpha 1) Scanley 2.3.0 (Alpha 2) Scanley 2.3.0 (Beta 1) Scanley 2.3.0 (Beta 2) Scanley 2.3.0 (Beta 3) Scanley 2.3.0 请注意当使用“Alpha”修饰符时,Scanley的"2.3"写作"2.3.0"。这两个号码是等同的—出于简短的目的,结尾所有的0都可以丢掉—但是当有修饰词时,简短成为无需考虑的问题,所以人们会选择完整的方式。 另外一些半正规的修饰词包括“Stable”、“Unstable”、“Development”和“RC”(“发布候选”)。最常用的还是“Alpha”和“Beta”,而“RC”用于第三方,但是请注意“RC”总会包含一个数字修饰。也就是不要使用“Scanley 2.3.0 (RC)”,而使用“Scanley 2.3.0 (RC 1)”,然后是RC2,以此类推。 “Alpha”、“Beta”和“RC”都是已经广为人知的标签了,所以我不建议你使用其他标签,即使是那些乍看起来更常见而非方言的词汇,似乎是更好的选择。但是那些从发布包安装软件的人对于这三个词汇已经非常熟悉,没有理由选择特立独行。 尽管发布版本号码中的点数并不是小数点,但也是起到了位置标示的作用。所有的“0.X.Y”早于“1.0”(等同于“1.0.0”)。“3.14.158”是“3.14.159”直接前继版本,而“3.14.160”和“3.15.任意数”则是“3.14.158”的后继版本,担不是直接后继。 一致的发布版本号码策略可以让用户仅仅从某个软件的版本号就可以判断出版本的重要程度。在一个三部分的系统中,第一部分是主 版本号,第二部分是次 版本号码,而第三部分是小 版本号码。例如版本“2.10.17”是主版本2系列的次要版本10开发线的小版本17。词汇“开发线”和“系列”在这里并不正式,但他们代表了人们期望的含义。一个主系列仅仅是共享同一个主号码的版本,而次要系列(或次要开发线)则由相同次要主号码的版本。也就是说,“2.4.0”和“3.4.1”并不是位于同一个次要系列,即使他们都有次要版本号码4。另一个情况下,“2.4.0”和“2.4.2”则位于同一个次要开发线,尽管他们并不是相邻的版本,因为“2.4.1”版本位于他们之间。 这些号码的含义可以是你自己所期望的:主号码的变更表示发生了主要的变化;次要号码的变化表示发生了次要的变更。有一些项目会有第4部分,通常叫做补丁 号码,特别是一些对区别进行细致控制的项目(有一点让人混淆的是,一些项目将三部分系统中的“微(micro)”版本作为“补丁”。)。也有一些项目使用最后一部分作为构建 号码,随着项目的每一次构建递增,代表了每次变更的变化。这样帮助了项目将每个bug报告与特定构建联系起来,特别是当二进制发布包是发布默认方法时特别有用。 尽管对于使用多少个部分,每个部分的含义有许多不同的习惯,区别通常很小—你会受到一些压力,但是不会太大。下面两小节讨论了最常用的几个习惯。 简单策略 如果仅仅会改变微小版本号码,大多数项目对于将何种变更纳入到发布中会有一些规则,改变主要版本号码则也会有相应的规则。没有这些规则的标准集合,但下面描述是已经在许多项目广泛使用的的政策。你或许会在自己的项目中采用这些方法,但即使不使用,这仍是发布号码所应传达信息的好案例。这个政策是APR项目使用的编号系统,请看 对于微小号码的变更(也就是在同一个次要开发线上发生的变化)只能是向前和向后兼容的。也就是说,变更只能是bug修正,或仅仅是对现有特性较小的改进。新特性一定不能在微小版本发布中引入。 次要版本号码(也就是位于同一个主开发线)的变更必须是向前兼容,但不必向后兼容。在次要版本中引入新特性非常常见,但是通常不要一次包含过多特性。 主版本号码的变更标识了兼容性的边界。新的主版本发布不必向前和向后兼容。新的主版本发布应该包含新的特性,甚至完全的新特性集合。 向后兼容向前兼容的含义取决于你的软件,但通常无需明确的解释。例如,如果你的项目是客户端/服务器应用,那么“向后兼容”意味着将服务器升级到2.6.0不会导致2.5.4的客户端失效,或者工作方式发生变化(当然要排除修正的bug)。另一方面,将客户端升级到2.6.0,可以让客户端享受2.5.4无法使用功能。如果不能做到这一点,则升级不是“向前兼容”:显然,你现在不能将客户端降级到2.5.4,并保持2.6.0的功能,因为一些功能是2.6.0新增的。 这也是微小版本主要用于bug修正的原因。一定要保持双向的兼容性:如果你从2.5.3升级到2.5.4,然后改变主意降级到2.5.3也不会有任何功能损失。当然,2.5.4中修正的bug会再次出现,但不会损失任何新特性,只是bug可能会妨碍现有特性的使用。 客户端/服务器协议仅仅是许多可能的兼容性领域的一种情况。另一种是数据格式:软件会将数据写入永久存储吗?如果是,则数据的写入和读取必须依照发布号码政策所承诺的兼容性方针。版本2.6.0需要能够读取2.5.4写的文件,但是可能会暗自将格式升级到2.5.4无法阅读的新格式,因为跨次要版本边界的无需有降级的能力。如果其他程序使用你的项目发布的代码库,则API也是需要考虑的兼容性领域:你必须确保明确说明源代码和二进制兼容性规则,用户无需担心直接升级是否安全。她应当可以通过版本号码立刻知道结果。 在这个系统中,只有增加主版本号码时你才能从头开始。这确实有些不便:也许你会希望增加某些特性,希望重新设计协议,但是为了维护兼容性而无法实现。这个问题没有魔法解决方案,除非你能在一开始就以可扩展的方式进行设计(值得用单独一本书讨论的主题,当然超出了本书的范围)。但是通过公布发布兼容性政策,并遵守它,是发布软件不能回避的一部分。某个低劣的惊奇只会疏远许多用户。刚刚描述的政策能起到的作用有限,因为它已经广泛传播,但是因为它易于解释和记忆,所以不熟悉的人也可以很快接受。 也需要知道上述规则并不适用于pre-1.0的版本(尽管您的发布政策应当明确陈述,只是要说清楚)。一个还处于初始发布状态的开发可以发布0.1、0.2、0.3以及依次的后续版本,直到1.0,这些发布之间的区别可以任意大。pre-1.0版本的微小版本号可以省略。取决于项目的特性和版本的区别,你或许会发现0.1.0、0.1.1也很有效。pre-1.0的版本号码规则通常比较松散,主要是因为人们明白在项目初期,较强的兼容性限制会严重阻碍开发,而且较早的使用者也较能够容忍这种变化。 请牢记所有这些指令仅适用于三部分系统。你的项目可以轻易的得到不同的三部分系统,甚至可以决定不使用这么细致的粒度,而仅仅使用两部分系统。重要的是要尽早决定,仅按照每个部分的含义发布,并坚持下去。 奇偶数策略 一些项目使用次要版本号码部分表示项目的稳定程度:偶数表示稳定,奇数表示不稳定。仅适用于次要版本号码,不能用于主版本号码和微小版本号码。微小版本号码依然表示bug修正(没有新特性),主版本号码的递增依然表示重大变更,新特性集合。 奇偶系统优势在于它已经被Linux内核项目使用,它提供了一种方法,可以发布新功能用于测试,而无需让产品用户受到潜在不稳定代码的影响。人们在看到“2.4.21”时可以认为能够用于他们使用的web服务器,但当看到“2.5.1”时,则可以用于家用工作站的实验中。开发团队掌握了来自不稳定(奇数次要版本号码)系列的bug报告,当在该系列的一些微小版本中完成了许多工作后,他们便增加次要版本号码(变成偶数),将微小版本号码恢复到“0”,并发布推定的稳定包。 这个系统保留了前面给定的兼容性政策,或至少没有发生冲突。仅仅是在次要版本号码上重载了一些额外的信息。仅仅是让需要的人每次需要增加两个次要版本号码,并没有重大的损害。奇偶系统通常最适合用于拥有较长发布周期的项目,以及因为本性上就需要较高比例的保留用户能为新特性评估稳定性的项目。当然这不是让新功能得以测试的唯一方法,本章后面的章节描述了另一个方法,也是更常见的公布不稳定代码的方法,直接通过发布名称的标识让人们知晓风险/收益的代价。 发布分支 从开发者的角度讲,一个自由软件项目处于连续发布的状态。开发者通常一直在任何时候都运行最新的可用代码,因为他们需要定位bug,而且因为他们近距离的接触项目,可以避开当前特性的不稳定区域。他们通常会每天更新他们软件的备份,有时一天几次,当他们检入变更时,他们有道理认为其他开发者会在24小时内得到。 然而,何时项目应该做出正式的发布?是否仅仅取得某个时刻的快照,打包并交给世界,然后说“3.5.0”?常识告诉我们不是。首先,几乎没有一个时刻整个开发树是干净和准备好发布的。新开始的特性可能处于不同的状态。一些人可能检入了修正bug的主要变更,但是这个变更可能充满争议,在发生快照时依然处于辩论阶段。在这种情况下,仅仅是延后快照,等待辩论的结束是没有用的,因为此刻另一个不相关的辩论可能同时发生,那时你就需要等待那个辩论的结束。无法保证这个过程的终止。 在任何情况下,使用整个树的快照作为发布都会干扰正在进行的开发工作,即使整个树已经进入了可发布状态。假定快照将会成为“3.5.0”;而下个快照将会是“3.5.1”,会包含在3.5.0版本发现的大多数bug修正。但是如果快照来自同一个树,开发者在两个版本之间应该怎么做?他们不可以添加新特性;兼容性政策不允许这种行为。但是,不是每个人都会有激情修改3.5.0的代码bug。因为人们可能有需要完成的新特性,如果必须在静候和不想做的事情之间做出选择,他们会非常愤怒,而原因是项目发布过程要求开发树保持不自然的静默。 这个问题的解决方案一直是使用发布分支。一个发布分支仅仅是版本控制系统(见)的一个分支,其中预定要发布的代码已经与开发主线分离。发布分支的概念不仅仅来自自由软件;许多商业开发组织也会使用它。然后,在商业环境中,发布分支通常被认为是昂贵的—一类正式的“最佳实践”可以在主开发线针对最终期限的同时,可以让团队的每个人分散精力去完成稳定主开发树的工作。 但是,发布分支在开源项目中是不可或缺的。我经历过的一些没有发布分支的项目,但是这样总会导致一些开发者必须停止下来等待别人—通常是微小的—发布出门的工作。在许多情况下通常结果是不好的。首先,整体开发动力被降低。其次,发布版本可能无法达到必须的质量,因为只有少数人在上面工作,而且他们会急于完成工作,这样别人才能回来工作。第三,通过设定了一个情形,不同类型的工作不必要的互相干扰了别人的工作,在心理上分割了开发团队。处于停滞的开发者可能会很乐意为发布分支贡献一些精力,只要他们可以根据自己的日程和兴趣做出选择。但是,没有这个分支,他们的选择就变成“今天我可以参与项目吗?”而不是“我可以为发布工作,还是为主开发线上的新特性的工作?” 发布分支的技巧 创建发布分支的确切技巧取决于你的版本控制系统,当然基本概念基本上是相同的。一个分支通常从另一个分支或主干分出。传统上,主干(trunk)是主要开发进行的地方,不受发布的限制。第一个发布分支,也就是将会变成“1.0”版本的分支是从主干分出的。在CVS中,分支命令类似下面的形式 $ cd trunk-working-copy $ cvs tag -b RELEASE_1_0_X 或者在Subversion中,类似: $ svn copy http://.../repos/trunk http://.../repos/branches/1.0.x (这些例子都假设使用三部分的发布号码系统。因为我无法展示每个版本控制系统中的精确命令,我将会给出CVS和Subversion的例子,希望其他系统中对应的命令可以从中推导出来。) 请注意,我们创建了分支“1.0.x”(包含文字“x”),而不是“1.0.0”。这是因为同一条次要开发线—也就是同一个分支—将会被所有微小版本共用。用于发布的分支稳定化将会在本章后面的描述。这里,我们仅仅关注与版本控制系统的交互和发布过程。当发布分支已经稳定并做好准备,则应该从分支完成标记快照了: $ cd RELEASE_1_0_X-working-copy $ cvs tag RELEASE_1_0_0 $ svn copy http://.../repos/branches/1.0.x http://.../repos/tags/1.0.0 现在标签代表了项目源代码树在1.0.0版本的精确状态(在较老版本的打包发布和二进制程序被去掉后,如果某人希望获取时非常有用)。同一开发线的下个微小版本也很可能需要在1.0.x分支上准备,完成后,则增加1.0.1的标签。再次,重复完成1.0.2等等。当需要完成1.1.x版本时,则从主干再创建一个分支。 $ cd trunk-working-copy $ cvs tag -b RELEASE_1_1_X $ svn copy http://.../repos/trunk http://.../repos/branches/1.1.x 维护可以在1.0.x和1.1.x上并行继续,而版本发布也可以在两条线上独立进行。实际上,在两个不同的开发线上近乎同步的发布版本并不罕见。较旧的系列是保守的站点管理员应该使用的,他们可能不希望在没有小心准备的情况下做出重大的跳跃到(假设到)1.1。而同时,更多勇于冒险的人会将版本保持在最高的开发线上,以确保他们能获取最新的特性,即使要冒更大的稳定性风险。 这并不是唯一的发布分支策略,当然。在一些情况下,可能也不是最好的,只是在我参与过的项目中它的表现相当好。尽可以使用有效的策略,但请牢记要点:发布分支的目的是隔离发布工作与日常开发,并给项目一个物理实体用于组织整个发布过程。这个过程将会在下一小节详细描述。 稳定发布版本 稳定化是让一个发布分支进入发布状态的过程;也就是决定哪些变更将会进入发布版本,并以此为根据修整分支的内容。 “决定”一词有许多潜在的不幸。在协作软件项目中最后一分钟特性冲击是非常常见的现象:当开发者看到软件发布将要发生,他们便混乱的结束当前的变更,不希望错过这班船。当然,这是在发布时你最不想看到的场面。如果人们能在比较以舒适的节奏,无需担心变更是进入这个版本还是下一个版本时完成这个特性,效果会更好。设法在最后一分钟进入发布的变更越多,代码就越不稳定,而且(通常是)也会造成更多的新bug。 大多数软件工程师认可在稳定阶段,变更进入发布版本线时使用严苛标准的理论。很明显,对于严重bug,尤其是没有临时解决办法的bug的修正应该进入。文档更新以及错误信息修改(除非是被认为是界面的一部分,并必须保持稳定的信息)也没问题。许多项目也允许特定类型的低风险或非核心变更在稳定期进入,并提供评估风险的正式指导。但是没有正式的文档可以回避对于人们判断的需要。在很多情况下项目需要为是否将哪个变更纳入发布作出决定。危险的是因为每个人都希望看到自己喜欢的变更进入发布版本,并会激发足够的人赞成这个变更,而不会激发足够的反对者。 因此,稳定一个发布版本也就是要创建一种说“不”的机制。对于开源项目来说,技巧在于如何能在说“不”的同时,尽可能避免造成过多的情绪伤害或失望的开发者,并且还要防止在发布版本中漏掉真正需要的变更。当然有许多不同的方法。可以很容易的设计一个满足这个标准的系统,只要项目能够将其视作重要的标准。在广阔的范围中,这里我仅仅介绍两种最流行的系统,希望这不会让你失去在项目中发挥创造性的激情。有足够多的其他方式也是可能的;但这两个是我在实践中使用过的。 发布所有者独裁 团队认可让某人成为发布所有者。这个人可以最终确定进入发布版本的变更。当然,有研讨和辩论也是正常和可以设想的,但是最后团队必须赋予所有者足够的权威作出最终的决定。对于此类系统,必须选出一个合适的人,具备理解所有变更的技术能力,具备在避免伤害过多感情的情况下将讨论引入发布的社会威望和社交技巧。 发布所有者场景会说“我不认为这个变更有什么错误,只是我们没有足够的时间测试,所以不能进入这个发布版本。”如果发布所有者对于项目具备广泛的技术知识将会非常有益,这样可以解释为什么这个变更会潜在的导致不稳定性(例如,当与软件的其他部分交互时,或移植性考虑等)。人们有时会为这类决定辩护,或者质问什么样的变更没有这种风险。这种对话不需要是对抗性的,只要发布所有者认为所有的论点都是客观的,而不是条件反射式的坚定自己的立场。 需要注意的是发布所有者与项目领导不必是同一个人(如果有项目领导的话,见)。实际上,有时要确保他们不是同一个人。成为一个优秀项目领导的技巧与成为优秀发布所有者的技巧并不一定相同。在有些时候,与发布过程同等重要的是能有一个人可以成为项目领导判断力的平衡力量。 发布所有者角色与较弱独裁者角色的比较本章后面的 变更表决 发布所有者独裁方式的另一个极端就是为进入发布的变更进行表决。然而,因为发布稳定化最重要功能是排除变更,所以势必要设计一种表决系统,只有在大多数开发者表示正面意见时才将变更纳入发布。只有比简单多数更多的赞成才可以引入一个变更(见)。否则,一个人提出,没有人反对,一个变更就足以进入发布,一个不幸的变化就是每个开发者都会提出自己的变更,而且处于防止他人报复的原因,他们也不会再反对其他人提出的变更。为了避免这个情况,必须安排一些开发者组成子团队,起到将变更纳入发布的协作作用。这并不是意味着让更多的人评审每个变更,而是为了减少每个单独的开发者在反对某个变更时的犹豫,因为她知道赞成该变更的所有人不会将她的反对意见当作人身攻击。子团队参与的人数越多,讨论就会更加集中与变更本身,而不会是关于某个人。 我们在Subversion项目中使用的系统达到了非常好的平衡,所以我在这里将会推荐它。为了让一个变更进入发布分支,必须至少有3位开发者要投票赞成它,而且没有反对者。一个单独的“反对”票足以阻止变更的进入;在发布场景中一个“反对”票等同于否决票(见)。很自然,这类表决必须伴随着辩护意见,而且如果有足够多的人认为辩护毫无道理,也可以发起一次针对该变更的特别表决。在实践中,这种情况还没有发生过,我也不认为将会发生。人们面对发布时总是趋向于保守,当人们感到可以否决某个变更时,通常是因为有了足够好的理由。 因为,发布规程故意倾向于保守,所以否决时的辩护有时是出于程序上的原因,而非技术上的。例如,一个人认为某个变更写的很好,不太可能导致新bug,但是否决它进入微小版本的意见仅仅是它太大了—或许它引入了新特性,又或者它以微妙的方式违反了兼容性政策。我也偶尔会看到一些开发者仅仅因为有不好的感觉而否决一些东西,他们认为这些变更需要更多的测试,即使无法检查出任何bug。人们总会发些牢骚,但是否决已经成立,而且变更不会进入发布(即使我不记得之后的测试是否发现了bug)。 管理协作发布稳定化 如果你的项目选择了一种变更表决系统,一定要确保设置投票和发布表决的物理机制尽可能的便利。尽管有大量开源电子投票软件,在实践中最简单的方式还是在发布分支上设置一个叫做STATUSVOTES或诸如此类的文本文件。这个文件列出所有的变更提议—任何开发者可以提出包含某个变更的提议—之后就是同意和反对它的表决,以及注释或评论。 (提出一个变更并不意味着一定要为此投票,尽管两者经常一起出现。)这个文件中任意一个条目的内容类似这个: * r2401 (issue #49) Prevent client/server handshake from happening twice. Justification: Avoids extra network turnaround; small change and easy to review. Notes: This was discussed in http://.../mailing-lists/message-7777.html and other messages in that thread. Votes: +1: jsmith, kimf -1: tmartin (breaks compatibility with some pre-1.0 servers; admittedly, those servers are buggy, but why be incompatible if we don't have to?) 在这个情况下,该变更得到了两个赞成票,但是被tmartin否决,他在附加说明中给出了原因。该条目的详细格式并不重要;你的项目如何设置都可以—或许tmartin的解释应该出现在“Notes:”部分,也许变更描述也应该有一个”Description: “头来匹配其他小节。重要的是需要评估这个变更的所有信息都是触手可及的,进行投票的机制也是尽可能的保持轻量级。提议的变更直接用版本库中的修订号码引用(如果是单个修订可能是r2401,当然提议的变更也可能由多个修订组成)。修订可以是引用在主干上的变更;而如果变更就发生在发布分支,可能没必要再表决了。如果你的版本控制系统没有引用某个变更的明确语法,则项目需要建立一个。为了表决的可操作性,每个需要考虑的变更必须是明确可标识的。 这些提议和表决可以保证变更可以干净的进入发布分支,也就是不会发生冲突(见)。如果有冲突,则该条目应当包含指向一个可以使变更变干净的补丁,或者是包含已修正变更的临时分支,例如: * r13222, r13223, r13232 Rewrite libsvn_fs_fs's auto-merge algorithm Justification: unacceptable performance (>50 minutes for a small commit) in a repository with 300,000 revisions Branch: 1.1.x-r13222@13517 Votes: +1: epg, ghudson 这个例子取自真实的生活;来自Subversion 1.1.4发布过程的STATUS文件。请注意,它是如何使用原始的修订版本作为变更的标准描述方式,即使有一个分支已经有了修正冲突后的变更版本(为了更简单的将一定会被通过的变更合并到发布版本,分支也将三个trunk的修订版本合并为一个r13517)。这里还是提供了原始的修订版本,作为最早的可以检查的实体,因为他们都提供了原始的日志信息。临时分支不会有那些日志信息,为了避免信息的复制(),分支上r13517的日志信息仅仅是“调整r13222、r13223和r13232回到分支1.1.x。”关于变更的所有其他信息都可以跟踪原始的修订版本得到。 发布经理 将已确认变更合并到发布分支的实际过程(见)可以由任何开发者执行。不必有一个人专门负责合并变更;如果变更很多,最好能有人分担工作。 然而,尽管表决和合并都以非集中的样式出现,在实践中通常会有一到两个人掌控着发布过程。这个角色有时被正式的称作发布经理,但是与拥有最终决定权的发布所有者(见本章前面的)有很大的区别。发布经理跟踪当前有多少正在考虑的变更,有多少已经确认,有多少可能会被确认等等。如果他们感到重要的变更未能获得足够的关注,或者可能因为缺少投票而无法进入发布,他们会有礼貌的提醒其他开发者检查并投票。当一组变更经过确认,这些人会自己去将它们合并到发布分支;如果有其他人愿意自己完成这个任务也没有问题,只要所有人都理解如果他们没有明确的声明要自己做,这便不是强制要做的工作。当要将发布公布于众时(本章后面的),发布经理将会完成最终发布包的创建,收集数字签名,上传发布包并作出公告。 打包 分发自由软件的标准形式是源代码。无论软件是否以源代码的形式(例如解释性语言Perl、Python和PHP等等)运行,还是必须首先编译(例如C、C++和Java等),这一点是毋庸置疑的。通过编译好的软件,大多数用户可能无需自己编译源代码,而只需安装预先编译的二进制包(见本章后面的)。然而,这些二进制包依然来自主源代码分发包。原因是源代码包明确定义了发布版本。当项目分发“Scanley 2.5.0”时,真正的含义是“源代码文件的目录树,当编译(如果需要)和安装后将产生Scanley 2.5.0”。 对于源代码发布的式样有一个相对严格的标准。可能会有与标准的偏差出现,但是那只是例外,不是规则。除非有强有力的理由,否则你的项目也应该遵守这个标准。 格式 源代码必须以标准格式传输目录树。对于Unix和类Unix的操作系统,习惯上是TAR格式,压缩为compressgzipbzipbzip2。对于微软Windows,分发目录树的标准方法是zip格式,也是压缩格式,所以不必再进一步压缩归档文件。 TAR文件 TAR代表了“Tape ARchive”,因为tar格式用线性数据流表示了目录树,所以非常适于将目录保存到磁带。这个特性也使之成为已单个文件分发目录树的标准。生成压缩的tar文件(tarballs)也非常简单。在某些系统上,tar命令可以自己产生压缩归档;在另外一些系统上,则需要使用单独的压缩程序。 命名和布局 打包的名称必须包含软件名称和发布版本号,然后是特定归档类型的格式后缀名。例如Scanley 2.5.0在Unix上使用GNU Zip(gzip)压缩的包类似: scanley-2.5.0.tar.gz 或者是在Windows上使用zip压缩: scanley-2.5.0.zip 所有的这些归档解压后,都应该能在当前目录创建一个名为scanley-2.5.0的单独目录。这个新目录中,所有的源代码应该是处于准备好进行编译(如果需要编译)的布局。在新目录树的最上层,应该有一个README文件,解释了软件是什么,发布版本是哪个,并给出了其他资源的指针,例如项目站点以及其他有用的文件等。README旁边也应该有一个INSTALL,说明在所有支持的操作系统上构建和安装软件的方法。就像所说明的,应该有COPYINGLICENSE,说明软件分发的条款。 也应当有一个CHANGES文件(有时称为NEWS),解释了发布版本的新功能。CHANGES文件按照逆向的历史顺序,汇集了所有发布版本的变更列表,所以最新的发布位于文件最顶部。完成这个列表通常是稳定发布分支的最后一项工作;一些项目会随着开发列出所有的片段,而另外一些项目更倾向于在最后阶段,让某人根据版本控制日志组合信息一次完成。这个列表类似下面: Version 2.5.0 (20 December 2004, from /branches/2.5.x) http://svn.scanley.org/repos/svn/tags/2.5.0/ New features, enhancements: * Added regular expression queries (issue #53) * Added support for UTF-8 and UTF-16 documents * Documentation translated into Polish, Russian, Malagasy * ... Bugfixes: * fixed reindexing bug (issue #945) * fixed some query bugs (issues #815, #1007, #1008) * ... 根据具体情况,这个列表可能会很长,但是不需要包含所有的小bug修正和特性提升。它的目的仅仅是给用户一个印象,通过升级到最新版本将会获得哪些好处。实际上,习惯上会将变更列表包含在声明邮件(见本章后面的)中,所以在编写时要考虑你的读者。 CHANGES还是ChangeLog 传统上,名为ChangeLog的文件会列出项目的每个变更—也就是提交到版本控制系统的每个修订版本。ChangeLog文件有许多种格式;具体的格式并不重要,但都需要包含相同的信息:变更的日期、作者和简介(或仅仅是该变更的日志信息)。 CHANGES文件有所不同。尽管它也是变更的列表,但是仅应该包含对特定读者比较重要的变更,而且准确日期和作者之类的元数据也可以省略。为了避免混淆,不要使用可替换的术语。一些项目使用“NEWS”而不是“CHANGES”,尽管避免了与“ChangeLog”混淆的可能,但却有些用词不当,因为文件CHANGES保留了所有发布版本的变更信息,所以在最顶部的新闻之后是许多旧闻。 文件ChangeLog可能会渐渐消失。当CVS是版本控制系统的唯一选择时使用这个文件非常重要,因为变更数据很难从CVS中获取。然而,在许多现在的版本控制系统中,ChangeLog中保存的信息可以在任意时间从版本控制版本库中获取,所以再使用一个静态文件保存这些信息变得毫无意义—实际上,不仅仅是毫无意义,因为ChangeLog仅仅是重复版本库已经保存的日志信息。 目录树中源代码的布局与项目版本控制系统检出的源代码的布局应当相同,或者尽可能的近似。通常情况下,会有些区别,例如因为发布包会包含一些用于配置和编译(见本章后面的)的生成文件,或者因为它包含了非本项目维护的,而用户一般不会拥有的第三方软件,。但是,即使发布的目录树与版本控制系统中的开发目录树完全一致,发布包本身也不应当是一个工作拷贝(见)。发布版本代表了一个静态参考点—源文件特定的,不可改变的配置。如果它是工作拷贝,就会存在用户不小心作出更新的风险,而用户还会以为使用的是发布版本,尽管实际上已经有所不同。 请牢记无论打包方式如何,这个发布包应该是一样的。这个发布版本—精确的引用了某人所说的“Scanley 2.5.0”—是zip文件或tarball解压缩所创建的目录树。所以项目可以提供所有这些下载: scanley-2.5.0.tar.bz2 scanley-2.5.0.tar.gz scanley-2.5.0.zip ...但是通过解压他们创建的源代码树必须相同。源代码树是分发物;具体的形式只是为了方便使用。源代码包也可以有些许的差异:例如,在Windows包中,文本文件必须以CRLF作为行结束符(回车和换行),而Unix包应该使用LF。不同操作系统下如果因为编译的原因需要有不同的布局,源包的布局也可以有所不同。然而,这些都是些无关紧要的变形。同一发布版本不同包的基本源代码文件必须相同。 大写还是不大写 当通过名称引用一个项目时,人们通常会以正常的名词进行大写,如果是缩略词则也要大写:”MySQL5.0“,”Scanley2.5.0“等等。是否在包名上大写也取决于项目。例如,Scanley-2.5.0.tar.gzscanley-2.5.0.tar.gz都可以(我个人倾向于后者,因为我不喜欢让人去按shift键,不过很多项目使用有大写的包)。重要的是解压tarball得到的目录使用相同的大小写。不应该有什么意外:用户总是预计解压得到的目录会和压缩包使用相同的名称。 预发布 当发送预发布或候选发布时,合格者成为发布号码的一部分,所以在包的名称中要包含这个名字。例如,在之前提到的alpha和beta系列的发布包名称为: scanley-2.3.0-alpha1.tar.gz scanley-2.3.0-alpha2.tar.gz scanley-2.3.0-beta1.tar.gz scanley-2.3.0-beta2.tar.gz scanley-2.3.0-beta3.tar.gz scanley-2.3.0.tar.gz 第一个解压后进入目录scanley-2.3.0-alpha1,第二个是scanley-2.3.0-alpha2,以此类推。 编译和安装 对于需要从源代码编译的安装的软件,有许多经验丰富的用户希望能够遵循的标准步骤。例如,以C、C++或特定其他编译语言编写的程序,在类Unix系统下的标准是输入: $ ./configure $ make # make install 第一个命令自动检测构建过程中需要的环境,第二个命令在原地构建软件(但不安装),最后一个命令是在系统上安装。前两个命令作为普通用户执行,而第三个以root用户。设置系统的详细信息可以看Vaughan、Elliston、Tromey和Taylor编写的优秀图书GNU Autoconf, Automake, and Libtool。它作为New Riders的treeware发布,内容也可以在网上免费得到。 这不是唯一的标准,只是传播最广泛的一个。Ant()构建系统也渐渐流行,特别是Java编写的项目,它拥有自己的构建和安装的标准步骤。另外,特定的编程语言,例如Perl和Python都有大多数使用这些语言所推荐的相同方法(例如Perl模块使用命令perl Makefile.pl)。如果不是清楚适应于项目的标准,可以询问资深的开发者;你可以安全的假定某些标准更加合适,即使一开始并不清楚是什么。 无论你的项目适合哪个标准,则如非必要一定不能与之偏离。标准安装过程对于许多系统管理员已经成为条件反射。如果在你的项目INSTALL文件中看到了熟悉的实施步骤,他们就会认识到你的项目遵守了一般的习惯,也就会轻松的完成其他的事情。另外,就像在中讨论的,拥有标准的构建程序可以让潜在的开发者满意。 在Windows中,构建和安装的标准比较薄弱。对于需要编译的项目,通常要提供一个适用于标准微软开发环境(Developer Studio、Visual Studio、VS.NET和MSVC++等等)工作空间/项目模型的目录树。取决于项目的本性,可以通过Cygwin()环境提供类Unix的构建选项。当然,如果你使用的语言或编程框架使用自己的构建和安装习惯—例如Perl或Python—你应当使用该框架标准的方法,无论是Windows、Unix、Mac OS X或任何其他操作系统。 要乐于花费额外的精力让项目遵守相关的构建或安装标准。构建和安装是切入点:如果一定需要,在这之后可以更加困难,但是如果用户或开发者一开始就需要使用意想不到的步骤与软件进行交互则是一种耻辱。 二进制包 尽管正式发布是源代码包,大多数用户会从二进制包安装,可以通过他们操作系统的软件分发机制得到,也可以从项目站点或第三方手工获取。这里“二进制”并不一定是“已编译”;它仅仅意味着一种预配置形式的包,允许用户在自己的电脑上无需执行一般的基于源代码的构建和安装程序,便可以进行安装。在RedHat GNU/Linux上,这是RPM系统;在Debian GNU/Linux上,则是(.deb)系统;在MS Windows,通常是.MSI文件或自安装的.exe文件。 无论这些二进制包是由项目相关的人组装,还是由关系较远的第三方组装,用户都会认为其等同于项目的官方发布版本,会根据二进制包的行为在项目bug跟踪系统上发起问题。因此,项目能否为打包者提供明确的指导方针就非常有意义,应该与他们更紧密的合作,认识到他们是否能够清楚和准确的产生软件。 打包者需要知道的主要问题是是否应当一直根据官方源代码版本发布他们的二进制包。有时,打包者会喜欢获取版本库较晚版本的代码,或者选择在发布后包含某个变更,从而为用户提供特定的bug修正或其他改进。打包者认为通过最新的代码,他是在为用户谋利益,但实际上这样会导致许多混乱。项目已经准备好了接受某个发布版本以及trunk和分支上(那些故意运行最前沿代码的人发现的)的bug报告。当一个bug来自这些源,回应者通常可以能确认bug在该快照出现,而且已经被修正,用户可以升级或等待下个发布。如果是一个还未知的bug,拥有精确的发布版本时,重现就会比较简单,在跟踪系统中也比较容易分类。 项目没有准备好根据未指明媒介或混血的版本接受bug报告。此类bug很难重现;另外,因为无法预期与来自发布之后开发的孤立变更进行交互的结果,所以产生的不正常也不应该成为对开发者进行谴责理由。我曾经非常沮丧的浪费了许多时间,因为某个bug似乎消失了,而实际上应该出现:某人运行的是轻微补丁的版本,基于(但不相同)官方发布版本,当预期的bug没有出现时,每个人都会尝试寻找原因。 在有一些情况下,打包者也确实需要在原发布基础上做出一些修改。要鼓励打包者向项目开发者提出这个问题,并描述他们的方案。他们可能得到许可,即使失败,也至少会让项目知道他们的目的,项目也可以关注一些不寻常的bug报告。开发者可以在项目站点上设置一个免责声明,并告知所有的打包者在合适的地方放置同样的东西,这样该二进制包的用户就可以知道他们获取的东西与项目官方发布并不完全相同。这种情形并没有任何敌意,但不幸的是经常会有这种结果。打包者与项目开发者有些不太一样的目标。打包者主要希望为用户提供最佳的开箱即用体验。开发者也希望如此,但他们也需要确保自己知道别人所用软件的版本,这样可以获取到一致的bug报告,并作出兼容性的保证。有时这些目标会有冲突。当发生这种情况时,需要牢记项目无法控制打包者,两种方式都承担了各自的义务。诚然项目通过产生软件为打包者提供了服务。但是打包者也是在为项目服务,通过提供这种单调的工作让软件更广泛的传播。当然可以不认可打包者,但是不要迁怒于他们;只需要尽自己的可能将工作做好。 测试和发布 一旦源代码tarball已经从稳定的发布分支产生,发布过程公共部分便已经开始。但是在tarball进入公开之前,必须经过少量开发者的确认,通常需要三位或者更多。确认不仅仅是检测发布的明显缺陷;理想情况下,开发者应该下载tarball,在干净的系统上构建并安装,运行回归测试包(见),然后执行一些手工测试。假如通过了这些检查以及项目的其他的发布检查列表条件,开发者可能需要使用GnuPG()、PGP()或其他可以产生PGP兼容签名的程序为tarball作出数字签名。 大多数项目中,开发者仅仅使用个人的数字签名,而不是许多开发者(有一小部分,担不是大多数)希望使用的项目共享密钥。签名的开发者越多,经过的测试也就越多,一个关心安全的用户也就越可能找到一个到达该tarball的数字信任路径。 一经确认,发布版本(所有的tarballs、zip文件以及其他需要分发的格式)就应当放置到项目的下载区,并伴有数字签名,以及MD5/SHA1校验(see)。有许多做这些工作的标准。一种方法是为每个发布包提供一个对应的数字签名,以及一个校验文件。例如,如果发布包是scanley-2.5.0.tar.gz,在同一目录的scanley-2.5.0.tar.gz.asc包含了这个tarball的数字签名,另一个文件scanley-2.5.0.tar.gz.md5则包含了MD5校验,也可以有另外一个scanley-2.5.0.tar.gz.sha1文件,包含SHA1校验。另一种方法是收集所有发布包的签名到一个单独的文件scanley-2.5.0.sigs;校验文件与之类似。 具体怎样做并不重要。只要保持简单的模式,描述清楚,并在每次发布保持一致即可。所有签名和校验的目的是为了用户校验自己的拷贝未经恶意修改。用户会在自己的电脑上运行这些代码—所以如果代码被篡改,攻击者可以立刻拥有到达所有数据的后门。本章后面的有更详细的介绍。 候选发布 对于包含许多变更的发布版本,许多项目会首先推出发布候选,例如scanley-2.5.0之前的scanley-2.5.0-beta1。候选的目的让代码在发布之前接受更广泛的测试。如果发现了问题,可以在发布分支修正,并推出新的发布候选(scanley-2.5.0-beta2)。这个周期会持续到不能发现不可接受的bug为止,最后的发布候选成为正式发布版本—也就是说最后的候选发布和正式发布的唯一区别只是版本号码的修饰词。 在大多数其他方面,候选发布一定要与正式发布保持相同的待遇。alphabetarc修饰足以警告保守的用户等待真正的发布,而候选发布的声明邮件也必须指出他们的目的是征求反馈。除此以外,应该对候选发布提供与正式发布相同的关注。毕竟,你希望人们使用候选版本,因为暴露是发现bug的最佳方法,而且也因为你永远无法获知哪个候选会最终成为正式版本。 宣告发布 宣告发布很像宣布其他事件,一定要采用在中描述的程序。当然,对于发布要额外注意一些事情。 每当你提供下载发布tarball的URL时,一定要确保给出MD5/SHA校验和数字签名文件的链接。因为宣告会出现在许多论坛里(邮件列表,新闻页等),这意味着人们会从许多地方获取到校验信息,这可以给关心安全的用户额外的保证,证明了校验本身没有被篡改。反复提供数字签名的链接并不会使其更安全,但是会让人们(尤其是与项目比较疏远的人)感受到项目对于安全的重视。 在宣告邮件和新闻页中,不应该仅仅报告关于发布的宣传信息,而应该包含CHANGES文件中相关的部分,这样人们就能够看到自己是否有兴趣去升级。这对于发布候选和最终发布同样重要;bug修正的出现和新特性的引入会吸引人们尝试候选版本。 最后,不要忘记感谢项目团队、测试人员以及所有花时间发起bug报告的人。不要单独提出某人的名字,除非某人承担了大量的工作,其价值被项目的每个人所认可。小心坐上信用贬值的索道(见)。 维护多发布线 大多数成熟的项目都平行的维护多个发布线。例如,1.0.0发布后,该发布线会继续微小发布1.0.1,1.0.2等等,直到项目明确的决定终止这条线。请注意,仅仅因为发布了1.1.0不足以终止1.0.x线。例如,一些用户会制定某类政策,永远不升级到较新的次要或主要版本的第一个发布—他们希望其他人能将bug试验出来,例如1.1.0,那么就等待1.1.1。这不一定是自私(请牢记,他们也放弃了bug修正和新特性);仅仅是出于某些原因,他们必须在升级上非常小心。因此,假设项目在发布1.1.0之前发现1.0.3中有一个重大bug,如果只是将bug修正纳入到1.1.0,而告知所有的1.0.x用户必须升级到1.1.0,其结果将会非常恶劣。为什么不同时发布1.1.0和1.0.4,这样大家都能高兴吧? 待1.1.x线圆满后,你可以宣布1.0.x进入了生命结束(end of life,EOL)。这一步必须正式宣告。宣告可以是独立的,也可以作为1.1.x发布宣告的一部分;可是这样做时,用户必须能够知道老的开发线正在被关闭,这样他们可以根据情况决定是否升级。 一些项目设置了保证对于前一发布线作出支持的窗口时间。在开源环境中,“支持”意味着接受针对该线的bug报告,并在发现重大bug后发布维护版本。另外一些项目并没有给出预定义的时间,而是根据报告的bug数量判断还在使用较旧发布线的用户。当低于某个百分比时,就可以宣布发布线的结束并停止对它的支持。 对于每个发布,请确保在bug跟踪系统中有目标版本目标里程碑,这样人们可以根据正确的发布填写bug。当然也不要忘记为最新的开发源代码提供叫做“开发”或“最新”的目标,因为总有些人—不仅仅是活跃的开发者—通常会在官方发布的最前沿。 安全发布 对于安全bug的处理请参考,但是对于安全发布有许多特殊的细节需要讨论。 一个安全发布是一个专门关闭安全漏洞的发布。修正bug的代码在发布之前不能公布于众,也就是说在发布日之前代码不能提交到版本库,也意味着在公开之前代码不能经过公共测试。很明显,开发者可以自己检查这个修正,并在私下里测试发布,但是无法进行广泛的真实世界测试。 因为缺乏测试,所以安全发布必须是在现有发布基础之上,只附加了安全bug的修正,没有其他变更的发布。这是因为未经测试的变更越多,越有可能导致新的bug,甚至是新的安全bug!这种保守也是对管理员的一种友好,他们将要部署安全修正,但是他们的部署策略倾向于不在同一时间部署任何其他变更。 作出安全发布有时有些小的诡计。例如,项目可能工作于发布1.1.3,对于1.1.2的一些bug修正已经公开声明,此时出现了安全报告。很自然,开发者在完成修正前不能讨论安全问题,他们必须继续讨论,好像1.1.3还会按照计划推出一样。但是当1.1.3实际上到来时,与1.1.2的区别只有安全补丁,而所有其他的修正都会进入1.1.4(当然,现在也会包含安全修正,以后所有的版本也会一直包含)。 你也可以为现有的版本号码添加一个额外的部分,以说明它只包含安全变更。例如,人们能够知道1.1.2.1是针对1.1.2的一个安全发布,所有更高的发布(1.1.3,1.2.0等)会包含相同的安全修正。对于了解内情的人,这个系统可以传递许多信息。在另一方面,那些不能紧跟项目的人,当大多数时间看到的是3部分的发布号码,偶尔看到4部分的号码会感到混乱。我见过的大多数项目会选择一致性,使用常规的下个号码作为安全发布,即使它意味着计划中的发布前进一个版本。 发布和日常开发 维护同时的平行发布包含了如何完成日常开发的暗示。特别是应该遵守每次提交只包含一个单独逻辑变更的铁律,绝不要在一次提交中混杂不相关的变更。如果一次提交的变更太大,或具有破坏性,可以分为N此提交,每次提交都是一个整体变更的分区子集,而且不包含与整体变更无关的内容。 这里是一个未经慎重考虑进行提交的例子: ------------------------------------------------------------------------ r6228 | jrandom | 2004-06-30 22:13:07 -0500 (Wed, 30 Jun 2004) | 8 lines Fix Issue #1729: Make indexing gracefully warn the user when a file is changing as it is being indexed. * ui/repl.py (ChangingFile): New exception class. (DoIndex): Handle new exception. * indexer/index.py (FollowStream): Raise new exception if file changes during indexing. (BuildDir): Unrelatedly, remove some obsolete comments, reformat some code, and fix the error check when creating a directory. Other unrelated cleanups: * www/index.html: Fix some typos, set next release date. ------------------------------------------------------------------------ 当某人需要将BuildDir错误检查修正搬运到维护即将到来维护发布分支时,这种问题立刻变得非常明显。搬运者不希望任何其他的变更—例如,#1729问题的修正未能通过维护分支的确认,而且index.html的修改变得毫无关系。但是她不能仅仅通过版本控制工具的合并功能只获取BuildDir的变更,因为版本控制系统被告知该变更在逻辑上由所有其他不相关的事务组成。实际上,在合并之前这个问题也十分明显。仅仅为表决列出变更会有许多问题:不仅仅要提供修订版本号码,提议者也必须提供特别的补丁或变更分支,才能将提议的部分分离出来。对于其他人来说还要承受许多任务,仅仅因为最初的提交者未能按照逻辑把事情分组。 实际上,这个提交应该分为次独立的提交:一个用于修正#1729,另一个删除BuildDir中废弃的注释,并重新格式化代码,还有一个修正BuildDir中的错误检查,最后要修改index.html。其中第三个提交应该是为维护发布分支所做的提议。 当然,发布稳定不仅仅是要求每次提交仅包含一个逻辑变更的唯一原因从心理学上讲,语义上统一的提交更利于检查,更利于在必要时回退(在某些版本控制系统,回退只是一种特殊的合并)。这种每个人预先遵守的纪律可以避免项目之后的头痛。 计划发布 与私有项目相比,开源项目在发布计划上有历史上的区别。私有项目通常有严格的最后期限。有时是因为已经向客户许诺在规定时间完成升级,可能因为新发布需要配合其他市场目标的投入,也可能是因为风险投资希望在进一步投入前看到些结果。而对于自由软件项目,现阶段主要是由业余开发者以最字面意义的方式激励着:因为喜爱,所以编码。在所有的特性完毕之前,没有人觉得需要装运。并不是所有人的工作都在开发线上。 现今,许多开源项目由公司资助,越来越受到公司文化中的最后期限影响。无论如何这也是一件好事,但是会导致有工资的私有开发者与志愿贡献时间的冲突。这些冲突通常会围绕何时以及如何计划发布等问题发生。处于压力之下的有工资开发者很自然会希望选择一个发布发生的日期,并让每个人的活动投入到这个发布线。但是志愿者有自己的日程—或许是他们希望完成的特性,或一些希望进行的测试—他们希望发布能等待这些工作完成。 当然对于此类问题,除了讨论和妥协没有普通的解决方案。但是通过将发布中某个提议的出现与其完成的日期解耦,你可以最小化所导致阻力的频率和程度。也就是将讨论主题导向到项目在近期,以及中期将要作出的发布,以及其中包含的哪些特性,而不必一开始就确定所有关于日期的事情,除了粗略的一些猜测。作为另外一个选择,你或许希望阅读Martin Michlmayr博士的论文Quality Improvement in Volunteer Free and Open Source Software Projects: Exploring the Impact of Release Management)。它使用的是基于时间的发布过程,而不是基于特性的大型自由软件项目。Michlmayr也在Google提供了一个该主题的演讲,可以通过Google Video的观看。。通过尽早明确特性集合,你减少了针对任何单个发布讨论的复杂度,因而改进了可预测性。这也创建了一种惯性偏见,针对通过添加特性或其他复杂度的提议以扩展发布定义的人。如果发布的内容定义良好,则提议者需要承担证明该扩展的负担,即使发布的日期还没有设定。 在Thomas Jefferson的多卷传记Jefferson and His Time中,Dumas Malone讲了一个故事,Jefferson如何处理决定弗吉尼亚大学未来组织结构的第一次会议。大学首先来自Jefferson的一个构想,但是(不仅仅是在开源项目,其他领域也屡见不鲜)有许多其他参与者,都有自己的兴趣和日程。当他们召集在一起举行第一次会议时,Jefferson确保展示了精心准备的架构图,以及希望从欧洲引入的特定教职员工的姓名。房间中所有其他人都没有任何准备;这个团队从本质上就需要服从Jefferson的远见,而这个大学最终几乎按照他的计划建立。事实上整个建设远超预算,他的许多想法出于很多原因,最终未能得到解决,但那都是Jefferson一开始就了解,并预计到会发生的事情。他的目的是策略性的:通过在会议上展示非常确实的东西,让其他所有人仅仅需要履行修改提议的角色,所以项目整体的形状,以及随之而来的日程也可以和他的预期大体一致。 对于开源软件项目,没有一个单独的“会议”,而是一系列由问题跟踪系统代表的小建议。但是如果你在项目开始时有一些信誉,而且根据宣称的整体计划将许多特性、改进和bugs赋予到问题跟踪系统中的目标发布版本,人们会和你走到一起。一旦你根据自己的需要确立了一些事情,关于实际发布日期的对话将会变得非常平滑。 另一个很重要的,不要把任何决定当作是天经地义的。对于未来特定发布的某个问题所关联的注释中,邀请讨论、异议并尽可能真诚的希望被说服。不要为了练习控制而练习控制:其他人越是深入的参与到发布计划过程(见),越是容易说服其他人分享你在这个问题上本属于你的特权。 另一个降低项目发布计划紧张程度的方法是提高发布的频率。当发布之间的时间很长时,每次发布在每个人心目中的地位也被放大;如果他们的代码未能进入,他们会感觉到更多的压力,因为他们知道下一次机会要等待很久。根据发布过程的重要程度,以及项目的本性,发布的间隔可以在3个月到6个月之间,尽管如果有需求时,维护线可以让微小发布更快一点。