Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 374 – 为 Python 项目选择分布式版本控制系统

作者:
Brett Cannon <brett at python.org>,Stephen J. Turnbull <stephen at xemacs.org>,Alexandre Vassalotti <alexandre at peadrop.com>,Barry Warsaw <barry at python.org>,Dirkjan Ochtman <dirkjan at ochtman.nl>
状态:
最终版
类型:
流程
创建:
2008年11月7日
更新历史:
2008年11月7日,2009年1月22日

目录

基本原理

多年来,Python 一直使用集中式版本控制系统(VCS;最初是 CVS,现在是 Subversion)取得了显著成效。拥有 Python 官方版本的母版副本使人们能够在一个地方始终获取 Python 官方源代码。它还允许存储语言的历史记录,主要用于帮助开发,但也为了后代。当然,VCS 中的 V 在开发时非常有用。

但是,集中式版本控制系统也有其缺点。首先也是最重要的是,为了以无缝的方式获得 Python 版本控制的优势,必须是“核心开发者”(即拥有 Python 母版副本提交权限的人)。不是核心开发者但希望使用 Python 修订树的人,例如任何为 Python 编写补丁或创建自定义版本的人,都没有针对修订的直接工具支持。这可能是一个很大的限制,因为这些非核心开发者无法轻松地执行诸如将更改恢复到先前保存的状态、创建分支、发布具有完整修订历史的更改等基本任务。对于非核心开发者来说,最后一个安全树状态是 Python 开发人员碰巧设置的,这阻止了安全开发。这种二等公民身份阻碍了希望通过任何复杂程度的补丁为 Python 做出贡献并希望能够逐步保存其进度以使他们的开发生活更轻松的人们。

还有一个问题是必须在线才能提交工作。因为集中式 VCS 保留了一个存储所有修订版本的中央副本,所以必须拥有互联网访问权限才能存储他们的修订版本;没有网络,就不能提交。如果您碰巧在旅行并且没有互联网,这可能会很烦人。还有一种情况是有人希望为 Python 做出贡献,但互联网连接不好,提交耗时且昂贵,并且可能一次性完成会更好。

集中式 VCS 的另一个缺点是,一个常见的用例是开发人员根据审查意见修改补丁。在集中式模型中,这更困难,因为没有地方可以包含中间工作。要么全部签入,要么全部不签入。在集中式 VCS 中,在您处理功能或错误修复分支时,跟踪主干的更改也很困难。这增加了这些分支变得陈旧、过时或将它们合并到主干中会产生太多难以解决的冲突的风险。

最后,还有 Python 维护的问题。在任何时候,至少有一个主要版本的 Python 正在开发中(在撰写本文时,有两个)。对于每个正在开发的主要版本的 Python,至少有上一个次要版本的维护版本和正在开发的次要版本(例如,随着 2.6 刚刚发布,这意味着 2.6 和 2.7 都在开发中)。完成发布后,将在代码库之间创建一个分支,其中一个版本中的更改不属于(但可能属于)另一个版本。截至目前,中央 VCS 中没有对此分支在时间上的自然支持;您必须使用模拟分支的工具。对于开发者来说,跟踪合并同样很痛苦,因为修订版本通常需要在四个活动分支之间合并(例如,2.6 维护、3.0 维护、2.7 开发、3.1 开发)。在这种情况下,像 Subversion 这样的 VCS 只能通过神秘的第三方工具来处理这个问题。

分布式 VCS(DVCS)解决了所有这些问题。虽然可以保留修订树的母版副本,但任何人都可以自由地复制该树以供自己使用。这使每个人都有权在线或离线提交其副本的更改。它也更自然地与修订树历史中用于维护和开发面向 Python 的新功能的分支概念相关联。DVCS 还提供了许多集中式 VCS 不提供或无法提供的额外功能。

本 PEP 探讨了将 Python 使用 Subversion 更改为任何当前流行的 DVCS 的可能性,以获得上述优势。本 PEP 并不保证在完成本 PEP 后会切换到 DVCS。很有可能找不到明确的赢家,并且会继续使用 svn。如果发生这种情况,随着 DVCS 状态的发展,本 PEP 将在将来重新审视和修订。

术语

就通用术语达成一致出奇地困难,主要是因为每个 VCS 在描述细微不同的任务、对象和概念时都使用这些术语。在可能的情况下,我们尝试提供这些概念的通用定义,但您应该查阅各个系统的词汇表以获取详细信息。以下是一些来自每个 VCS 的一些标准网络参考的基本术语参考。您还可以参考每个 DVCS 的词汇表

分支
开发线;按时间排序的修订版本集合。
检出/工作副本/工作树
开发人员可以编辑的代码树,链接到一个分支。
索引
构建修订版本的“暂存区”(git 独有)。
存储库
组织成分支的修订版本集合。
克隆
分支或存储库的完整副本。
提交
在存储库中记录修订版本。
合并
将一个分支/存储库中的所有更改和历史记录应用到另一个分支/存储库。
拉取
从原始分支/存储库更新检出/克隆,可以是远程或本地
推送/发布
将修订版本及其依赖的所有修订版本从一个存储库复制到另一个存储库。
樱桃采摘
将一个或多个特定修订版本从一个分支合并到另一个分支,可能在不同的存储库中,可能不包含其依赖的修订版本。
变基
“分离”一个分支,并将其移动到新的分支点;将提交移动到分支的开头,而不是它们在时间上的发生位置。

典型工作流程

目前,Python 核心开发人员的典型工作流程是

  • 编辑检出的代码,直到它足够稳定以提交/推送。
  • 提交到主存储库。

这是一个相当简单的工作流程,但它有缺点。一方面,由于任何涉及存储库的工作都需要花费时间,这要归因于网络,因此提交/推送不一定尽可能地原子化。另一个缺点是没有必要廉价的方式来创建新的检出,而不仅仅是检出目录的递归副本。

DVCS 将导致更多类似于此的工作流程

  • 从主存储库的本地克隆中分支出来。
  • 编辑代码,以原子方式提交。
  • 将分支合并到主线,以及
  • 将所有提交推送到主存储库。

虽然有更多可能的步骤,但工作流程比目前可能的更独立于主存储库。通过能够以磁盘速度在本地提交,核心开发人员能够更频繁地执行原子提交,最大限度地减少对代码进行多项操作的提交。此外,通过使用分支,可以将更改(如果需要)与其他开发人员进行的其他更改隔离。因为分支很便宜,所以很容易创建和维护许多较小的分支来解决一个特定的问题,例如一个错误或一个新功能。DVCS 的更复杂功能允许开发人员在官方主线发展时更轻松地跟踪长期运行的开发分支。

候选者

名称 简称 版本 2.x 主干镜像 3.x 主干镜像
Bazaar bzr 1.12 http://code.python.org/python/trunk http://code.python.org/python/3.0
Mercurial hg 1.2.0 http://code.python.org/hg/trunk/ http://code.python.org/hg/branches/py3k/
git N/A 1.6.1 git://code.python.org/python/trunk git://code.python.org/python/branches/py3k

本 PEP 没有考虑 darcs、arch 或 monotone。这些 DVCS 的主要问题是它们不够流行,以至于当它们没有提供其他 DVCS 提供的一些非常引人注目的功能时,就不值得支持。Arch 和 darcs 还存在严重性能问题,在不久的将来似乎不太可能解决。

互操作性

对于那些已经决定要使用哪个 DVCS 并愿意自己维护本地镜像的人来说,所有三个 DVCS 都支持通过 git“快速导入”变更集格式进行交换。当然,git 本身就是这样做的,Bazaar 的原生支持正在积极开发中,并且截至 2009 年 2 月中旬,早期反馈良好。Mercurial 通过其 hg convert 命令对导入提供了特殊支持,并且 第三方快速导入支持 可用于导出。此外,Tailor 工具支持基于任何候选格式的官方存储库自动维护镜像,并在任何格式中使用本地镜像。

使用场景

可能帮助决定是否/哪个 DVCS 应该替换 Subversion 的最佳方法是看看执行开发人员(核心和非核心)必须使用的某些现实世界使用场景需要什么。每个使用场景都概述了它是什么,一个关于基本步骤的要点列表(每个 VCS 可能略有不同),以及如何在各种 VCS(包括 Subversion)中执行使用场景。

每个 VCS 都有一位负责人负责编写每个场景的实现(除非另有说明)。

名称 VCS
Brett svn
Barry bzr
Alexandre hg
Stephen git

初始设置

一些 DVCS 在你进行一些初始设置时会有一些额外优势。本节介绍在运行任何使用场景之前可以执行的操作,以便更好地利用这些工具。

所有 DVCS 都支持配置你的项目标识。与集中式系统不同,它们使用你的电子邮件地址来识别你的提交。(访问控制通常由 DVCS 外部的机制完成,例如 ssh 或控制台登录)。此标识可能与全名相关联。

所有 DVCS 都会查询系统以获取此信息的近似值,但这可能不是你想要的。它们还支持在每个用户基础上和每个项目基础上设置此信息。设置这些属性的便利命令各不相同,但都允许直接编辑配置文件。

一些 VCS 支持在检出/检入时进行换行符 (EOL) 转换。

svn

不需要,但建议你遵循 开发人员常见问题解答中的指南

bzr

无需设置,但为了更快、更节省空间的本地分支,你应该创建一个共享存储库来保存所有 Python 分支。共享存储库实际上只是一个包含 .bzr 目录的父目录。当 bzr 提交修订版时,它会从本地目录向上搜索文件系统以查找一个 .bzr 目录来保存修订版。通过在多个分支之间共享修订版,可以减少使用的磁盘空间。请执行此操作

cd ~/projects
bzr init-repo python
cd python

现在,所有 Python 分支都应该创建在 ~/projects/python 中。

你还可以将一些设置放在 ~/.bzr/bazaar.conf~/.bzr/locations.conf 文件中,以设置与 Python 代码交互的默认值。其中没有一个是必需的,尽管有些是推荐的。例如,我建议对所有提交进行 GPG 签名,但这对于开发人员来说可能门槛过高。此外,你可以根据默认要推送分支的位置设置默认推送位置。如果你对主分支有写权限,则推送位置可能是 code.python.org。否则,它可能是一个免费的 Bazaar 代码托管服务,例如 Launchpad。如果选择 Bazaar,我们应该确定策略和建议。

至少,我将设置你的电子邮件地址

bzr whoami "Firstname Lastname <email.address@example.com>"

与下面的 hg 和 git 一样,有一些方法可以在每个存储库的基础上设置你的电子邮件地址(或者实际上是任何参数)。你可以使用 $HOME/.bazaar/locations.conf 文件中的设置来执行此操作,该文件与其他 DVCS 一样具有 ini 样式的格式。有关详细信息,请参阅 Bazaar 文档,这些文档在很大程度上与本次讨论无关。

hg

最少,你应该设置你的用户名。为此,在你的主目录中创建文件 .hgrc 并添加以下内容

[ui]
username = Firstname Lastname <email.address@example.com>

如果你使用的是 Windows 并且你的工具不支持 Unix 样式的新行,则可以通过添加到你的配置中来启用自动换行翻译

[extensions]
win32text =

这些选项也可以通过自定义 <repo>/.hg/hgrc 而不是 ~/.hgrc 来在本地设置为给定存储库。

git

无需任何操作。但是,git 支持许多可以简化你工作的功能,只需进行少量准备即可。git 支持在工作区、用户和系统级别设置默认值。系统级别不在本 PEP 的范围内。用户配置文件在类 Unix 系统上是 $HOME/.gitconfig,工作区配置文件是 $REPOSITORY/.git/config

你可以使用 git-config 工具全局(对于你的系统登录帐户)或本地(对于给定的 git 工作副本)设置 user.name 和 user.email 的首选项,或者你可以编辑配置文件(与上面 Mercurial 部分中显示的格式相同)。

# my full name doesn't change
# note "--global" flag means per user
# (system-wide configuration is set with "--system")
git config --global user.name 'Firstname Lastname'
# but use my Pythonic email address
cd /path/to/python/repository
git config user.email email.address@python.example.com

如果你使用的是 Windows,你可能希望使用 git-config 将 core.autocrlf 和 core.safecrlf 首选项设置为 true。

# check out files with CRLF line endings rather than Unix-style LF only
git config --global core.autocrlf true
# scream if a transformation would be ambiguous
# (eg, a working file contains both naked LF and CRLF)
# and check them back in with the reverse transformation
git config --global core.safecrlf true

虽然存储库通常包含一个 .gitignore 文件,其中指定很少或从不应在 VCS 中注册的文件名,但你可能有一些个人约定(例如,始终在名为“.msg”的临时文件中编辑日志消息),你可能希望指定这些约定。

# tell git where my personal ignores are
git config --global core.excludesfile ~/.gitignore
# I use .msg for my long commit logs, and Emacs makes backups in
# files ending with ~
# these are globs, not regular expressions
echo '*~' >> ~/.gitignore
echo '.msg' >> ~/.gitignore

如果你使用多个分支,就像其他 VCS 一样,可以通过将所有对象放在一个公共对象存储中来节省大量空间。如果分支的来源位于不同的存储库中,这也节省下载时间,因为即使对象在上游存储库中不存在,它们也会在你的存储库中的分支之间共享。git 非常节省空间和时间,并自动应用了许多优化,因此此配置是可选的。(示例已省略。)

一次性检出

作为非核心开发人员,我想创建并发布一个一次性补丁来修复错误,以便核心开发人员可以审查它是否包含在主线中。

  • 检出/分支/克隆主干。
  • 编辑一些代码。
  • 生成补丁(基于 VCS 最佳支持的内容,例如分支历史记录)。
  • 接收审查意见并解决问题。
  • 为核心开发人员生成第二个补丁以提交。

svn

svn checkout http://svn.python.org/projects/python/trunk
cd trunk
# Edit some code.
echo "The cake is a lie!" > README
# Since svn lacks support for local commits, we fake it with patches.
svn diff >> commit-1.diff
svn diff >> patch-1.diff
# Upload the patch-1 to bugs.python.org.
# Receive reviewer comments.
# Edit some code.
echo "The cake is real!" > README
# Since svn lacks support for local commits, we fake it with patches.
svn diff >> commit-2.diff
svn diff >> patch-2.diff
# Upload patch-2 to bugs.python.org

bzr

bzr branch http://code.python.org/python/trunk
cd trunk
# Edit some code.
bzr commit -m 'Stuff I did'
bzr send -o bundle
# Upload bundle to bugs.python.org
# Receive reviewer comments
# Edit some code
bzr commit -m 'Respond to reviewer comments'
bzr send -o bundle
# Upload updated bundle to bugs.python.org

bundle 文件类似于超级补丁。它可以被 patch(1) 读取,但它包含其他元数据,以便可以将其馈送到 bzr merge 以生成一个完全具有历史记录的完全可用的分支。请参阅下面的 补丁审查 部分。

hg

hg clone http://code.python.org/hg/trunk
cd trunk
# Edit some code.
hg commit -m "Stuff I did"
hg outgoing -p > fixes.patch
# Upload patch to bugs.python.org
# Receive reviewer comments
# Edit some code
hg commit -m "Address reviewer comments."
hg outgoing -p > additional-fixes.patch
# Upload patch to bugs.python.org

虽然 hg outgoing 没有此标志,但大多数 Mercurial 命令都通过 --git 命令支持 git 的扩展补丁格式。这可以在一个人的 .hgrc 文件中设置,以便所有生成补丁的命令都使用扩展格式。

git

补丁也可以使用 git diff master > stuff-i-did.patch 创建,但是 git format-patch | git am 了解普通补丁无法处理的一些技巧(空文件、重命名等)。git 从提交消息中获取“我做的事情”来创建文件名 0001-Stuff-I-did.patch。有关 git-format-patch 格式的描述,请参阅下面的补丁审查。

# Get the mainline code.
git clone git://code.python.org/python/trunk
cd trunk
# Edit some code.
git commit -a -m 'Stuff I did.'
# Create patch for my changes (i.e, relative to master).
git format-patch master
git tag stuff-v1
# Upload 0001-Stuff-I-did.patch to bugs.python.org.
# Time passes ... receive reviewer comments.
# Edit more code.
git commit -a -m 'Address reviewer comments.'
# Make an add-on patch to apply on top of the original.
git format-patch stuff-v1
# Upload 0001-Address-reviewer-comments.patch to bugs.python.org.

撤销更改

作为核心开发人员,我想撤消尚未准备好包含在主线中的更改。

  • 撤消不需要的更改。
  • 将补丁推送到服务器。

svn

# Assume the change to revert is in revision 40
svn merge -c -40 .
# Resolve conflicts, if any.
svn commit -m "Reverted revision 40"

bzr

# Assume the change to revert is in revision 40
bzr merge -r 40..39
# Resolve conflicts, if any.
bzr commit -m "Reverted revision 40"

请注意,如果要撤消的更改是最后进行的更改,则只需使用 bzr uncommit

hg

# Assume the change to revert is in revision 9150dd9c6d30
hg backout --merge -r 9150dd9c6d30
# Resolve conflicts, if any.
hg commit -m "Reverted changeset 9150dd9c6d30"
hg push

请注意,你可以使用“hg rollback”和“hg strip”来撤消你在本地存储库中提交的更改,但尚未推送到其他存储库。

git

# Assume the change to revert is the grandfather of a revision tagged "newhotness".
git revert newhotness~2
# Resolve conflicts if any.  If there are no conflicts, the commit
# will be done automatically by "git revert", which prompts for a log.
git commit -m "Reverted changeset 9150dd9c6d30."
git push

补丁审查

作为核心开发人员,我想审查其他人提交的补丁,以便确保只有经过批准的更改才会添加到 Python 中。

核心开发人员必须审查其他人提交的补丁。这需要应用补丁,对其进行测试,然后丢弃更改。可以假设核心开发人员已经检出/分支/克隆了主干。

  • 从主干分支出来。
  • 应用补丁,不带任何补丁提交者生成的注释。
  • 将补丁推送到服务器。
  • 删除现在无用的分支。

svn

Subversion 不完全适合这种开发风格,因为它没有像本 PEP 中定义的“分支”这样的东西。相反,开发人员要么需要创建另一个检出来测试补丁,要么在服务器上创建分支。到目前为止,核心开发人员尚未采用“在服务器上创建分支”的方法来处理单个补丁。对于这种情况,假设开发人员创建了主干的本地检出来使用。

cp -r trunk issue0000
cd issue0000
patch -p0 < __patch__
# Review patch.
svn commit -m "Some patch."
cd ..
rm -r issue0000

另一种选择是只在任何时候运行一个检出,并使用 svn diff 以及 svn revert -R 来存储你可能做出的独立更改。

bzr

bzr branch trunk issueNNNN
# Download `patch` bundle from Roundup
bzr merge patch
# Review patch
bzr commit -m'Patch NNN by So N. So' --fixes python:NNNN
bzr push bzr+ssh://me@code.python.org/trunk
rm -rf ../issueNNNN

或者,因为你可能会将这些更改提交到主干,所以你可以只执行检出。这将为你提供一个本地工作树,而分支(即所有修订版)将继续驻留在服务器上。这类似于 svn 模型,并且可能允许你更快地审查补丁。在这种情况下,无需推送。

bzr checkout trunk issueNNNN
# Download `patch` bundle from Roundup
bzr merge patch
# Review patch
bzr commit -m'Patch NNNN by So N. So' --fixes python:NNNN
rm -rf ../issueNNNN

hg

hg clone trunk issue0000
cd issue0000
# If the patch was generated using hg export, the user name of the
# submitter is automatically recorded. Otherwise,
# use hg import --no-commit submitted.diff and commit with
# hg commit -u "Firstname Lastname <email.address@example.com>"
hg import submitted.diff
# Review patch.
hg push ssh://alexandre@code.python.org/hg/trunk/

git

我们假设一个由 git-format-patch 创建的补丁。这是一个 Unix mbox 文件,包含一个或多个补丁,每个补丁都格式化为 RFC 2822 消息。git-am 将每条消息解释为一次提交,如下所示。补丁的作者取自 From: 标头,日期取自 Date 标头。提交日志通过连接主题行的内容、一个空行和消息主体(直到补丁开始)来创建。

cd trunk
# Create a branch in case we don't like the patch.
# This checkout takes zero time, since the workspace is left in
# the same state as the master branch.
git checkout -b patch-review
# Download patch from bugs.python.org to submitted.patch.
git am < submitted.patch
# Review and approve patch.
# Merge into master and push.
git checkout master
git merge patch-review
git push

回退

作为核心开发人员,我想将补丁应用于 2.6、2.7、3.0 和 3.1,以便我可以在所有三个版本中修复问题。

由于始终拥有最先进的和最新的开发版本,Python 目前同时处理四个分支。这使得更改能够轻松地传播到各个分支变得至关重要。

svn

由于 Python 使用 svnmerge,更改从主干 (2.7) 开始,然后合并到 2.6 的发布版本中。要将更改添加到 3.x 系列,将更改合并到 3.1 中,进行修复,然后合并到 3.0 中 (2.7 -> 2.6; 2.7 -> 3.1 -> 3.0)。

这与端口转发策略形成对比,在端口转发策略中,补丁将被添加到 2.6 中,然后向前拉到更新的版本中 (2.6 -> 2.7 -> 3.0 -> 3.1)。

# Assume patch applied to 2.7 in revision 0000.
cd release26-maint
svnmerge merge -r 0000
# Resolve merge conflicts and make sure patch works.
svn commit -F svnmerge-commit-message.txt  # revision 0001.
cd ../py3k
svnmerge merge -r 0000
# Same as for 2.6, except Misc/NEWS changes are reverted.
svn revert Misc/NEWS
svn commit -F svnmerge-commit-message.txt  # revision 0002.
cd ../release30-maint
svnmerge merge -r 0002
svn commit -F svnmerge-commit-message.txt  # revision 0003.

bzr

Bazaar 在这里非常简单,因为它支持手动挑选修订版。在下面的示例中,我们可以提供修订版 ID 而不是修订版号,但这通常没有必要。Martin Pool 建议“我们通常建议首先在最旧的支持分支中进行修复,然后将其合并到更新的版本中。”

# Assume patch applied to 2.7 in revision 0000
cd release26-maint
bzr merge ../trunk -c 0000
# Resolve conflicts and make sure patch works
bzr commit -m 'Back port patch NNNN'
bzr push bzr+ssh://me@code.python.org/trunk
cd ../py3k
bzr merge ../trunk -r 0000
# Same as for 2.6 except Misc/NEWS changes are reverted
bzr revert Misc/NEWS
bzr commit -m 'Forward port patch NNNN'
bzr push bzr+ssh://me@code.python.org/py3k

hg

Mercurial 与其他 DVCS 一样,不适合 Python 核心开发人员当前用于回退补丁的工作流程。目前,错误修复首先应用于开发主线(即主干),然后回退到维护分支,并根据需要向前移植到 py3k 分支。此工作流程需要能够挑选单个更改。Mercurial 的移植扩展提供了此功能。以下是用此工作流程的场景示例

cd release26-maint
# Assume patch applied to 2.7 in revision 0000
hg transplant -s ../trunk 0000
# Resolve conflicts, if any.
cd ../py3k
hg pull ../trunk
hg merge
hg revert Misc/NEWS
hg commit -m "Merged trunk"
hg push

在上面的示例中,transplant 的作用与当前的 svnmerge 命令非常相似。当在没有修订版的情况下调用 transplant 时,该命令会启动一个交互式循环,用于移植多个更改。另一个有用的功能是 –filter 选项,它可以用于以编程方式修改变更集(例如,它可以用于自动删除对 Misc/NEWS 的更改)。

作为传统工作流程的替代方法,我们可以通过将错误修复提交到最旧的支持版本,然后将这些修复向上合并到更新的分支来避免移植变更集。

cd release25-maint
hg import fix_some_bug.diff
# Review patch and run test suite. Revert if failure.
hg push
cd ../release26-maint
hg pull ../release25-maint
hg merge
# Resolve conflicts, if any. Then, review patch and run test suite.
hg commit -m "Merged patches from release25-maint."
hg push
cd ../trunk
hg pull ../release26-maint
hg merge
# Resolve conflicts, if any, then review.
hg commit -m "Merged patches from release26-maint."
hg push

虽然这种方法使历史记录变得非线性并且稍微难以跟踪,但它鼓励在所有受支持的版本中修复错误。此外,当有许多更改需要回退时,它的扩展性更好,因为我们不需要查找要合并的特定修订版 ID。

git

在 Git 中,我会有一个工作区,其中包含所有相关的 master 仓库分支。git cherry-pick 无法跨仓库工作;您需要在同一个仓库中拥有这些分支。

# Assume patch applied to 2.7 in revision release27~3 (4th patch back from tip).
cd integration
git checkout release26
git cherry-pick release27~3
# If there are conflicts, resolve them, and commit those changes.
# git commit -a -m "Resolve conflicts."
# Run test suite. If fixes are necessary, record as a separate commit.
# git commit -a -m "Fix code causing test failures."
git checkout master
git cherry-pick release27~3
# Do any conflict resolution and test failure fixups.
# Revert Misc/NEWS changes.
git checkout HEAD^ -- Misc/NEWS
git commit -m 'Revert cherry-picked Misc/NEWS changes.' Misc/NEWS
# Push both ports.
git push release26 master

如果您定期从给定分支合并(而不是 cherry-pick),那么您可以通过合并然后回退来阻止给定提交在将来意外合并。这并不能阻止 cherry-pick 提取不需要的补丁,并且此技术需要阻止所有您不希望合并的内容。我不确定这是否与 svn 在这一点上有所不同。

cd trunk
# Merge in the alpha tested code.
git merge experimental-branch
# We don't want the 3rd-to-last commit from the experimental-branch,
# and we don't want it to ever be merged.
# The notation "^N" means Nth parent of the current commit. Thus HEAD^2^1^1
# means the first parent of the first parent of the second parent of HEAD.
git revert HEAD^2^1^1
# Propagate the merge and the prohibition to the public repository.
git push

协同开发新功能

有时核心开发人员最终会与多个开发人员一起处理一个主要功能。作为核心开发人员,我希望能够将功能分支发布到一个公共位置,以便与其他开发人员协作。

这需要在服务器上创建一个其他开发人员可以访问的分支。所有 DVCS 都支持在开发人员已能够提交的主机上创建新仓库,并对仓库主机进行适当的配置。这在概念上类似于 svn 中现有的沙箱,尽管仓库初始化的细节可能有所不同。

对于非核心开发人员,有各种或多或少公开访问的仓库托管服务。Bazaar 有 Launchpad,Mercurial 有 bitbucket.org,而 Git 有 GitHub。所有这些都具有易于使用的 CGI 接口,供维护自己的服务器的开发人员使用。

  • 分支 trunk。
  • 从服务器上的分支拉取。
  • 从 trunk 拉取。
  • 将合并推送到 trunk。

svn

# Create branch.
svn copy svn+ssh://pythondev@svn.python.org/python/trunk svn+ssh://pythondev@svn.python.org/python/branches/NewHotness
svn checkout svn+ssh://pythondev@svn.python.org/python/branches/NewHotness
cd NewHotness
svnmerge init
svn commit -m "Initialize svnmerge."
# Pull in changes from other developers.
svn update
# Pull in trunk and merge to the branch.
svnmerge merge
svn commit -F svnmerge-commit-message.txt

此场景不完整,因为在工作完成之前就做出了使用哪个 DVCS 的决定。

分离问题依赖关系

有时,在处理问题时,会发现正在处理的问题实际上是各种较小问题的复合问题。能够获取当前工作然后开始处理另一个问题对于将问题分解成单独的工作单元而不是将它们复合成一个大型单元非常有帮助。

  • 创建一个分支 A(例如 urllib 有一个 bug)。
  • 编辑一些代码。
  • 创建一个新的分支 B,分支 A 依赖于它(例如 urllib bug 暴露了一个 socket bug)。
  • 在分支 B 中编辑一些代码。
  • 提交分支 B。
  • 在分支 A 中编辑一些代码。
  • 提交分支 A。
  • 清理。

svn

为了弥补 svn 缺乏廉价分支的不足,它有一个更改列表选项,用于将文件与单个更改列表关联。这不如能够在提交级别关联那么强大。也没有办法表达更改列表之间的依赖关系。

cp -r trunk issue0000
cd issue0000
# Edit some code.
echo "The cake is a lie!" > README
svn changelist A README
# Edit some other code.
echo "I own Python!" > LICENSE
svn changelist B LICENSE
svn ci -m "Tell it how it is." --changelist B
# Edit changelist A some more.
svn ci -m "Speak the truth." --changelist A
cd ..
rm -rf issue0000

bzr

这是一个使用 bzr shelf(现在是 bzr 的标准部分)临时保存一些更改的方法,同时您绕道去修复 socket 错误。

bzr branch trunk bug-0000
cd bug-0000
# Edit some code. Dang, we need to fix the socket module.
bzr shelve --all
# Edit some code.
bzr commit -m "Socket module fixes"
# Detour over, now resume fixing urllib
bzr unshelve
# Edit some code

另一种方法是使用 loom 插件。Looms 可以极大地简化依赖分支的工作,因为它们会自动为您处理堆叠依赖关系。想象一下,loom 就像一个依赖分支的堆栈(在 loom 的说法中称为“线程”),可以轻松地在线程堆栈中上下移动,将更改合并到堆栈中到子线程,创建线程之间的差异等等。偶尔,您可能需要或希望将您的 loom 线程导出到单独的分支中,以便进行审查或提交。较高的线程会自动包含较低线程中的所有更改。

bzr branch trunk bug-0000
cd bug-0000
bzr loomify --base trunk
bzr create-thread fix-urllib
# Edit some code. Dang, we need to fix the socket module first.
bzr commit -m "Checkpointing my work so far"
bzr down-thread
bzr create-thread fix-socket
# Edit some code
bzr commit -m "Socket module fixes"
bzr up-thread
# Manually resolve conflicts if necessary
bzr commit -m 'Merge in socket fixes'
# Edit me some more code
bzr commit -m "Now that socket is fixed, complete the urllib fixes"
bzr record done

为了获得额外分数,假设其他人以您刚刚完成的完全相同的方式修复了 socket 模块。也许这个人甚至抓取了您的修复 socket 线程,并将其应用到 trunk 中。您希望能够将他们的更改合并到您的 loom 中并删除您现在多余的修复 socket 线程。

bzr down-thread trunk
# Get all new revisions to the trunk. If you've done things
# correctly, this will succeed without conflict.
bzr pull
bzr up-thread
# See? The fix-socket thread is now identical to the trunk
bzr commit -m 'Merge in trunk changes'
bzr diff -r thread: | wc -l # returns 0
bzr combine-thread
bzr up-thread
# Resolve any conflicts
bzr commit -m 'Merge trunk'
# Now our top-thread has an up-to-date trunk and just the urllib fix.

hg

一种方法是使用 shelve 扩展;此扩展未包含在 Mercurial 中,但易于安装。使用 shelve,您可以选择要临时搁置的更改。

hg clone trunk issue0000
cd issue0000
# Edit some code (e.g. urllib).
hg shelve
# Select changes to put aside
# Edit some other code (e.g. socket).
hg commit
hg unshelve
# Complete initial fix.
hg commit
cd ../trunk
hg pull ../issue0000
hg merge
hg commit
rm -rf ../issue0000

有几种其他方法可以使用 Mercurial 处理这种情况。Alexander Solovyov 在 Mercurial 的邮件列表中提出了一些 替代方法

git

cd trunk
# Edit some code in urllib.
# Discover a bug in socket, want to fix that first.
# So save away our current work.
git stash
# Edit some code, commit some changes.
git commit -a -m "Completed fix of socket."
# Restore the in-progress work on urllib.
git stash apply
# Edit me some more code, commit some more fixes.
git commit -a -m "Complete urllib fixes."
# And push both patches to the public repository.
git push

额外分数:假设您花了时间,其他人以您刚刚完成的方式修复了 socket,并将该修复程序合并到 trunk 中。在这种情况下,您的推送将失败,因为您的分支未更新。如果修复程序是一行代码,那么很有可能它在字符上是完全相同的。Git 会注意到这一点,然后您就完成了;Git 会静默地合并它们。

假设我们没有那么幸运。

# Update your branch.
git pull git://code.python.org/public/trunk master

# git has fetched all the necessary data, but reports that the
# merge failed.  We discover the nearly-duplicated patch.
# Neither our version of the master branch nor the workspace has
# been touched.  Revert our socket patch and pull again:
git revert HEAD^
git pull git://code.python.org/public/trunk master

与 Bazaar 和 Mercurial 一样,Git 也有扩展来管理补丁堆栈。您可以使用 Andrew Morton 的原始 Quilt,或者使用 StGit(“stacked git”),它以类似于 Mercurial Queues 或 Bazaar looms 的方式将大量补丁的补丁跟踪集成到 VCS 中。

发布 Python 版本

使用 DVCS 时,PEP 101 如何变化?

bzr

它会发生变化,但不会有实质性的变化。在进行维护分支时,我们将只推送到新位置,而不是执行 svn cp。标签完全不同,因为在 svn 中它们是目录副本,但在 bzr(我猜 hg 也是)中,它们只是特定分支上修订版本的符号名称。release.py 脚本将必须更改为使用 bzr 命令。由于 DVCS(特别是 bzr)能够很好地进行 cherry-pick 和合并,因此我们可能能够更快地创建维护分支。尝试从 bzr/hg 镜像中发布将是一项有益的练习。

hg

显然,PEP 101 和发布脚本中特定于 Subversion 的细节需要更新。特别是,发布标记和维护分支创建过程将必须修改为使用 Mercurial 的功能;这将简化和简化发布过程的某些方面。例如,标记和重新标记发布将成为一项微不足道的操作,因为在 Mercurial 中,标记仅仅是给定修订版本的符号名称。

git

它会发生变化,但不会有实质性的变化。在进行维护分支时,我们将只使用 git push 推送到新位置,而不是执行 svn cp。标签完全不同,因为在 svn 中它们是目录副本,但在 git 中它们只是修订版本的符号名称,分支也是如此。(标签和分支的区别在于标签引用特定的提交,并且除非您使用 git tag -f 强制它们移动,否则它们永远不会改变。另一方面,签出的分支会自动由 git commit 更新。)release.py 脚本将必须更改为使用 git 命令。使用 git,我会在选择发布工程师后立即创建一个(本地)维护分支。然后我会“git pull”,直到我不喜欢某个补丁,这时我会“git pull; git revert ugly-patch”,直到看起来应该分离出来,并开始对好的补丁进行“git cherry-pick”。

平台/工具支持

操作系统

DVCS Windows OS X UNIX
bzr 是(安装程序)带 tortoise 是(安装程序,fink 或 MacPorts) 是(各种软件包格式)
hg 是(第三方安装程序)带 tortoise 是(第三方安装程序,fink 或 MacPorts) 是(各种软件包格式)
git 是(第三方安装程序) 是(第三方安装程序,fink 或 MacPorts) 是(.deb 或 .rpm)

如上表所示,所有三个 DVCS 都可以在所有三个主要操作系统平台上使用。但它也显示,Bazaar 是唯一一个直接支持 Windows 并提供二进制安装程序的 DVCS,而 Mercurial 和 Git 需要您依赖第三方才能获得二进制文件。Bzr 和 Hg 都有 tortoise 版本,而 Git 没有。

Bazaar 和 Mercurial 还具有使用纯 Python 可选扩展来提高性能的优点。

CRLF -> LF 支持

bzr
据我了解,在我输入时正在努力支持这一点,将在某个版本中发布。我会尝试查找详细信息。
hg
通过 win32text 扩展支持。
git
我不能根据个人经验来说,但看起来通过 core.autocrlf 和 core.safecrlf 配置属性有很好的支持。

不区分大小写的文件系统支持

bzr
应该没问题。我一直在 Linux 和 OS X 之间共享分支。我已经做了大小写更改(例如 bzr mv Mailman mailman),只要我在 Linux 上执行了它(显然),当我在 OS X 上提取更改时,一切都很顺利。
hg
Mercurial 使用大小写安全的仓库机制并检测大小写折叠冲突。
git
由于 OS X 保留大小写,因此您也可以在那里进行大小写更改。Git 对任一方向的重命名都没有问题。但是,不区分大小写的文件系统支持通常意味着在区分大小写的文件系统上抱怨冲突。Git 不会这样做。

工具

在代码审查工具(例如 Review BoardRietveld)方面,前者支持所有三个,而后者支持 hg 和 git,但不支持 bzr。Bazaar 还没有在线审查委员会,但它有几种方法可以管理基于电子邮件的审查和 trunk 合并。有 Bundle BuggyPatch Queue Manager(PQM)和 Launchpad 的代码审查

所有三个都有一些在线网站,为想要将仓库放到线上的用户提供基本的托管支持。Bazaar 有 Launchpad,Mercurial 有 bitbucket.org,而 Git 有 GitHub。Google Code 也有关于如何使用 Git 与该服务一起使用的说明,包括如何保存仓库以及如何充当只读镜像。

所有三个也都 似乎受 Buildbot 支持。

在 Subversion 之上使用

DVCS svn 支持
bzr bzr-svn(第三方)
hg 多个第三方
git git-svn

所有三个 DVCS 都支持 svn,尽管 git 是唯一一个随附该支持的。

服务器支持

DVCS 网页界面
bzr loggerhead
hg hgweb
git gitweb

所有三个 DVCS 都支持客户端和服务器端上的各种钩子,例如预/后提交验证。

开发

所有三个项目都处于积极开发中。Git 似乎遵循每月发布计划。Bazaar 遵循定时发布的每月计划。Mercurial 遵循 4 个月的定时发布计划。

特殊功能

bzr

Martin Pool 补充道:“bzr 具有稳定的 Python 脚本接口,区分公共和私有接口,以及正在更改的 API 的弃用窗口。一些插件列在 https://edge.launchpad.net/bazaarhttp://bazaar-vcs.org/Documentation 中”。

hg

Alexander Solovyov 评论

Mercurial 具有易于使用的扩展 API,其中包含主要事件的钩子和扩展命令的功能。此外,还有 mq(mercurial queues)扩展,与 Mercurial 一起分发,它简化了补丁的工作。

git

Git 具有 cvsserver 模式,即,您可以使用 CVS 从 Git 中检出树。您甚至可以提交到树中,但合并等功能不存在,并且分支作为 CVS 模块处理,这可能会让经验丰富的 CVS 用户感到震惊。

测试/印象

由于我(Brett Cannon)最终要负责决定使用哪种(或任何)DVCS,而不是我的合著者,我觉得有必要记录下我运行的测试以及我对各种工具的印象,以便尽可能地保持透明。

入门门槛

获取Python代码库的签出所需的时间和精力至关重要。如果难度或时间过长,那么希望为Python做出贡献的人很可能会放弃。这种情况绝对不能发生。

我模拟非核心开发人员,测量了2.x主干的签出时间。时间测量使用zsh中的time命令,空间计算使用du -c -h

DVCS 旧金山 温哥华 空间
svn 1:04 2:59 139 M
bzr 10:45 16:04 276 M
hg 2:30 5:24 171 M
git 2:54 5:28 134 M

在将这些数字与svn进行比较时,重要的是要认识到这不是一对一的比较。Svn不会像所有DVCS那样下载整个修订历史。这意味着svn可以比DVCS更快地执行初始签出,仅仅是因为它需要从网络下载的信息更少。

基本信息功能的性能

为了查看这些工具在执行需要查询历史记录的命令时的表现,我记录了README文件的日志时间。

DVCS 时间
bzr 4.5 秒
hg 1.1 秒
git 1.5 秒

在此测试中需要注意的一件事是,git比其他三个工具花费更长的时间来确定如何在不使用分页器的情况下获取日志。虽然分页器功能通常很不错,但没有自动启用它花费了一些时间(事实证明,主要的git命令有一个--no-pager标志来禁用分页器的使用)。

从内置帮助中找出要使用的命令

我最终尝试找出查看仓库克隆自哪个URL的命令。为此,我仅使用了工具本身或其手册页提供的帮助。

Bzr 最简单:bzr info。运行bzr help没有显示我想要的内容,但提到了bzr help commands。该列表包含了描述有意义的命令。

Git 是第二简单的。命令git help没有显示太多内容,也没有列出所有命令的方法。这时我查看了手册页。在阅读了各种命令后,我发现了git remote。该命令本身只输出origin。尝试git remote origin显示错误并打印出命令用法。这时我注意到了git remote show。运行git remote show origin给了我想要的信息。

对于hg,我从未自己找到想要的信息。事实证明我想要hg paths,但这从hg help打印的“显示符号路径名的定义”描述中并不明显(需要注意的是,在PEP中报告这一点确实导致Mercurial开发者澄清了措辞,使hg paths命令的使用更清晰)。

更新检出

为了查看更新过时仓库需要多长时间,我记录了更新落后700次提交和50次提交的仓库的时间(分别为三周前和一周前)。

DVCS 700 次提交 50 次提交
bzr 39 秒 7 秒
hg 17 秒 3 秒
git N/A 4 秒

注意

Git 缺少700 次提交场景的值,因为它似乎不允许在特定版本签出仓库。

Git 因为其git pull的输出而值得特别提及。它不仅列出了每个文件的增量更改信息,还对信息进行了颜色编码。

决定

在2009年的PyCon上,决定使用Mercurial。

为什么选择 Mercurial 而不是 Subversion

虽然svn很好地服务于开发团队,但必须承认svn在满足非提交者的需求方面不如DVCS。因为svn只为拥有仓库提交权限的人提供其功能,如版本控制、分支等,这对于缺乏提交权限的人来说可能是一个障碍。但是DVCS没有这样的限制,任何人都可以创建Python的本地分支并执行他们自己的本地提交,而无需承担克隆整个svn仓库带来的负担。允许任何人都能拥有与核心开发者相同的开发流程是切换从svn到hg的主要原因。

除了允许任何人轻松地本地提交到他们自己的分支之外,另一个好处是离线、快速的操作。因为hg将所有数据存储在本地,所以无需向远程服务器发送请求,而是可以利用本地磁盘进行操作。这极大地提高了响应时间。它还允许在没有互联网连接的情况下离线使用。但这种好处是次要的,被认为仅仅是一个副作用,而不是切换Subversion的驱动力。

为什么选择 Mercurial 而不是其他 DVCS

没有选择Git有三个主要原因(参见2009年PyCon闪电演讲,Brett Cannon在其中列出了这些确切的原因;演讲从3:45开始)。首先,在考虑的三个DVCS中,git的Windows支持最弱,这是不可接受的,因为Python需要支持在其运行的任何平台上进行开发。由于Python运行在Windows上,并且有些人确实在该平台上进行开发,因此它需要可靠的支持。虽然git的支持正在改进,但就目前而言,它弱于其他两个DVCS的幅度足够大,足以将其视为一个问题。

其次,与第一个问题同等重要的是,Python核心开发者对git的喜爱程度远低于其他两个DVCS选项。如果你查看下表,你会看到对核心开发者进行的一项调查的结果,以及git在很大程度上是最不受欢迎的版本控制系统。

DVCS ++ 相同 不知情
git 5 1 8 13
bzr 10 3 2 12
hg 15 1 1 10

最后,在所有条件都相同的情况下(由于前面两个问题,并非如此),最好使用和支持用Python编写的工具,而不是用C和shell编写的工具。我们足够务实,不会仅仅因为一个工具是用Python编写的就选择它,但我们确实看到了在推广使用Python编写的工具时的用处,只要这样做是合理的,就像在本例中一样。

至于为什么选择Mercurial而不是Bazaar,归根结底是流行度。正如核心开发者调查显示的那样,hg比bzr更受欢迎。但在git被排除在考虑范围之外后,PyCon也显示出社区似乎更喜欢hg。许多人走到Brett面前,用各种方式表示他们希望选择hg。虽然没有人说他们不希望选择bzr,但也没有人说他们希望选择bzr。

基于所有这些信息,Guido和Brett决定Mercurial将成为Python的下一个版本控制系统。

过渡计划

PEP 385概述了从svn到hg的迁移过程。


来源:https://github.com/python/peps/blob/main/peps/pep-0374.rst

最后修改时间:2023-09-09 17:39:29 GMT