开发工作流程过程

备注

本指南适用于已经熟悉在 GitHub 上为开源项目做出贡献的人。如果你是 GitHub 新手,请先阅读 设置开发环境 指南。

贡献清单

以下是向 SymPy 提交拉取请求时需要完成的事项清单。这些事项在拉取请求合并之前都需要完成。在打开拉取请求之前,不必完成所有这些事项,但在打开拉取请求甚至提交更改之前,通常先检查基本事项是一个好主意。

  • 确保 代码质量检查 通过。

    ./bin/test quality
    flake8 sympy/
    
  • 添加测试 所有新功能都应该进行测试。错误修复应添加回归测试。测试以 pytest assert f(x) == y 风格编写,并包含在 sympy/ 源代码的相应 tests 目录中。请参阅关于 编写测试 的指南。

  • 新的公共函数和方法应该有一个文档字符串。

  • 文档字符串应包含 doctests

  • 确保所有测试通过。 你可能想在提交之前在本地运行测试套件的相关子集(例如,./bin/test solvers)。当你打开一个拉取请求时,所有测试将在CI上运行。CI必须全部通过,PR才能被合并。

  • 编写好的提交信息

  • (仅限首次贡献者) 将您的名字添加到 .mailmap 文件中。如果未正确完成此操作,GitHub 上的“测试 / 作者”CI 构建将会失败。

  • 在拉取请求描述中交叉引用相关问题。 如果拉取请求修复了一个问题(即,问题应在PR合并后关闭),请使用 “fixes #123”语法

  • 在原始问题中添加一个评论,交叉引用拉取请求以提高可见性。如果没有相应的问题,这是可以的。除非你的PR需要进一步改进,否则没有必要打开一个问题。

  • 添加发布说明条目 这应在打开拉取请求时完成,位于拉取请求描述字段中。在拉取请求合并之前,可以随时编辑。

  • 回应评审意见。 所有 SymPy 的拉取请求在合并之前必须由其他人进行评审。

选择一个问题来修复

开始使用主代码库的最佳方式是修复一些现有的错误。浏览问题跟踪器中的“易于修复”问题,看看是否有你感兴趣的。如果你想尝试修复它,请在问题中创建一条消息,表明你想处理它。如果不清楚如何修复,请在问题本身或邮件列表中寻求建议。

SymPy 的代码被组织成 Python 包和模块。核心代码位于 sympy/core 目录中,而 sympy 目录中的其他包则包含更具体的代码。例如,sympy/printing 包含了处理 SymPy 对象如何打印到终端和 Jupyter 的代码。

如果你正在进行的更改还没有相关的问题,那么不需要先打开一个问题。只有当你觉得需要在提交拉取请求之前讨论这个更改时,才需要这样做,例如,如果你不确定某件事是否真的是一个错误,或者你不确定一个新功能是否在范围内。直接打开一个拉取请求并在那里讨论也是可以的。当有实际的代码可以讨论时,讨论会更容易进行,所以如果你有更改,即使它们还没有完全准备好合并,提交一个拉取请求也是更好的选择。

创建一个新分支

在对代码进行更改之前,首先要做的是在 git 中创建一个分支。

记住,永远不要提交到 mastermaster 只应用于从 sympy/sympy 主仓库拉取上游更改。如果你提交到 master,将难以拉取这些更改,并且如果你想同时进行多个拉取请求,也会变得困难。

首先为你的分支选择一个名称。请参见下方的 分支名称。要创建并检出(即,使其成为工作分支)一个新分支,请运行

# Pull any upstream changes from the main SymPy repo first
git checkout master
git pull

git branch <your-branch-name>
git checkout <your-branch-name>

最后两个命令也可以合并为一个命令:

git checkout -b <your-branch-name>

要查看所有分支,并突出显示当前分支,请输入:

git branch

记住,永远不要在主分支中输入以下命令git mergegit addgit commitgit rebase。如果你不小心将一些提交推送到了本地主分支,你将不得不进行硬重置以丢弃这些提交。

分支名称

使用一个简短、易于输入的分支名称,该名称与更改内容有一定关联。记住,希望尝试你的代码的开发者需要在命令行中输入你的分支名称。

避免在分支名称中使用问题编号,因为这些编号不容易输入(大多数 SymPy 问题编号是 5 位数长),而且如果不查看问题,它们并不能真正指示更改的内容。

一些好的分支名称示例是

fix-solve-bug
typo-fix
core-improvements
add-simplify-tests

最终分支名称并不是那么重要,所以不要花太多时间去思考它。它的唯一功能是区分这个贡献的代码与你可能做出的其他贡献。

修改代码

在修复问题时,请记住每个贡献都应遵循以下几个要求:

代码质量

SymPy 的贡献必须具有足够的代码质量才能被接受。一旦你创建了一个拉取请求,CI 上会自动运行一些代码质量检查,但你也可以在本地运行它们。

./bin/test quality
flake8 sympy/

此外,所有测试都必须通过。CI 会自动运行测试,但你也可以 自己运行测试。建议在提交之前至少运行与你修改的代码相关的测试,以确保你没有犯任何错误或意外破坏了某些功能。

一旦你提交了你的拉取请求,你应该在GitHub Actions检查完成后查看是否有任何测试失败。如果有,你需要在拉取请求被接受之前修复它们。

添加测试

所有新功能都应该进行测试。如果你在修复一个错误,应该附带一个回归测试。也就是说,一个在错误修复之前会失败但现在通过的测试。通常你可以使用问题中的代码示例作为测试用例,尽管简化这些示例或编写自己的测试用例也是可以的,只要它能测试到相关问题。

测试位于 tests/ 目录中的代码旁边,文件名为 test_<thing>.py。在大多数情况下,如果你修改了 sympy/<submodule>/<file>.py,那么功能的测试将放在 sympy/<submodule>/tests/test_<file>.py 中。例如,sympy/simplify/sqrtdenest.py 中的函数的测试位于 sympy/simplify/tests/test_sqrtdenest.py。这条规则有一些例外,所以通常情况下,尝试找到现有测试函数的位置,并将你的测试添加到它们旁边。

测试遵循一个简单的模式,从阅读现有的测试文件中可以明显看出。测试位于以 test_ 开头的函数中,并包含类似以下的行。

assert function(arguments) == result

例如

# from sympy/functions/elementary/tests/test_trigonometric.py

def test_cos_series():
    assert cos(x).series(x, 0, 9) == \
        1 - x**2/2 + x**4/24 - x**6/720 + x**8/40320 + O(x**9)

如果相关,可以将新的测试用例添加到现有的测试函数中,或者您可以创建一个新的测试函数。

文档

所有新的方法、函数和类都应该有一个展示如何使用它们的文档字符串。文档字符串是一个紧跟在 def 行之后的三个引号括起来的字符串,用于描述该函数。文档字符串应遵循 文档字符串风格指南 中概述的格式。

每个文档字符串中应该包含的一个重要内容是示例。示例也被称为 doctests,因为它们通过 bin/doctest 脚本进行测试,以确保输出是正确的。

Doctests 需要为使用的每个函数包含导入,并定义使用的任何符号。用户应该能够将示例输入复制并粘贴到他们自己的 Python 会话中,并获得完全相同的输出。from sympy import * 在 doctests 中是不允许的,因为这会使哪些函数来自 SymPy 变得不明确。

The docstring style guide 提供了更多关于如何在docstrings中格式化示例的详细信息。

请记住,doctests 不是 测试。可以把它们看作是碰巧被测试的例子。一些关键区别:

  • 编写doctests以提供信息;编写常规测试以检查回归和边缘情况。

  • doctests 可以随时更改;常规测试不应更改。

特别是,如果修改或删除任何doctest能使文档字符串更容易理解,我们应该能够随时进行这些操作。

以下是一个带有doctests的示例docstring(来自 sympy/functions/special/delta_functions.py)。

def fdiff(self, argindex=1):
    """
    Returns the first derivative of a Heaviside Function.

    Examples
    ========

    >>> from sympy import Heaviside, diff
    >>> from sympy.abc import x

    >>> Heaviside(x).fdiff()
    DiracDelta(x)

    >>> Heaviside(x**2 - 1).fdiff()
    DiracDelta(x**2 - 1)

    >>> diff(Heaviside(x)).fdiff()
    DiracDelta(x, 1)

    """
    if argindex == 1:
        return DiracDelta(self.args[0])
    else:
        raise ArgumentIndexError(self, argindex)

此外,所有公共函数的文档字符串都应包含在 Sphinx API 文档中。根据模块的不同,这可能意味着你需要在相应的 doc/src/modules/<module>.rst 文件中添加 .. autofunction:: 行。你应该 构建文档 并查看它,以确保在渲染的 HTML 中没有标记错误。

如果你想写一个更长的指南或教程,你可以将其作为Markdown或RST文件包含在Sphinx文档中,而不是放在docstring中。虽然这对新贡献不是必需的,但我们一直在寻找添加新的写得好的长篇指南到我们的文档中。

一旦你在GitHub上提交了拉取请求,CI将自动构建一个你可以查看的文档预览。在拉取请求页面,滚动到底部查看检查结果,并找到写着“点击此处查看文档预览”的链接。

运行测试

有几种运行 SymPy 测试的方法,但最简单的是使用 bin/test 脚本。

该脚本接受多个选项和参数。运行 bin/test --help 以获取所有支持的参数。在底层它使用 pytest,如果你愿意,也可以直接使用它。

使用以下命令运行所有测试:

$ ./bin/test

要运行特定文件的测试,请使用:

$ ./bin/test test_basic

其中 test_basic 来自文件 sympy/core/basic.py

要运行模块的测试,请使用:

$ ./bin/test /core /utilities

这将运行 coreutilities 模块的测试。

同样地,使用以下命令运行质量测试:

$ ./bin/test code_quality

提交更改

一旦更改准备就绪,你应该提交它们。你可以检查哪些文件发生了更改:

git status

检查总变化:

git diff

如果你创建了任何新文件,请用以下方式添加它们:

git add new_file.py

你已准备好本地提交更改。提交还包含一个 提交信息 ,用于描述它。有关编写良好提交信息的指南,请参见下一节。输入:

git commit

在这种情况下,编辑器窗口将自动出现。在Linux中,默认情况下是vim。您可以通过更改 $EDITOR shell变量来更改弹出的编辑器。

同样,借助选项 -a,你可以告诉 commit 命令自动暂存已修改和删除的文件,但新文件(你尚未告知 git 的文件)将不受影响,例如:

git commit -a

如果你想只暂存部分更改,可以使用交互式提交功能。只需输入:

git commit --interactive

并在生成的界面中选择您想要的更改。

删除垃圾文件

许多编辑器可以在您的 SymPy 目录中创建一些配置文件、二进制文件或临时文件,这些文件应在合并提交之前删除。

追踪单个文件可能会很麻烦。

你可能会考虑使用 .gitignore,然而,编辑 .gitignore 本身需要得到社区的同意。

使用 .git/info/exclude 是最好的,因为它仅在本地应用。

https://stackoverflow.com/questions/22906851/什么时候你会使用git-info-exclude而不是gitignore来排除文件

https://docs.github.com/get-started/getting-started-with-git/ignoring-files

编写提交信息

提交信息有两部分:标题(第一行)和正文。两者之间用空行分隔。

提交信息总结了提交的内容。就像代码一样,你的提交信息将成为项目 git 历史的一部分。因此,你应该努力使它们保持高质量。提交信息旨在供人类读者阅读,无论是现在正在审查你代码的人,还是将来在研究代码中的某些更改时可能会遇到你的提交的人。因此,如果必要,请在此包含有助于他人理解你的提交的信息。

git shortlog 和 GitHub UI 这样的工具默认只显示提交的第一行,因此在第一行传达提交的最重要方面是很重要的。

  • 保持第一行不超过71个字符,后续行不超过78个字符。这样可以让日志的一行形式在不换行的情况下显示摘要。

  • 确保在摘要后留一个空白行

  • 不要在第一行末尾加句号(句点)。后续行应使用句号。

  • 如果可能,请为提交提供上下文,

    例如 integrals: 改进了 heurisch() 的速度 而不是仅仅 改进了 heurisch() 的速度

  • 引用任何相关的问题编号。你不需要引用变更本身的拉取请求,但应修复的问题应通过 #12345https://github.com/sympy/sympy/issues/12345 引用。你还应提供问题的简要总结,而不仅仅是引用问题编号,以便人们不必四处寻找上下文。

  • 提交并不总是在分支的上下文中可见,因此为每个提交提供一些上下文通常是有帮助的。不过,这并不是必需的,因为查看提交元数据以了解哪些文件被修改,或者查看提交历史以查看附近的相关提交并不困难。

  • 使用简单的英语。写完整的句子。

  • 描述实际发生了什么变化。不要只是写一些类似 Modified solvers.py 的内容。人们已经可以从提交的差异中看到哪些文件被修改了。提交信息的作用是告诉他们差异实际上做了什么,这样他们就不必自己去弄清楚。同样,尽管相关的问题应该如上所述进行交叉引用,但消息应包含足够的基本总结,以便人们无需查找问题就能理解发生了什么。问题可以为感兴趣的人提供更详细的背景信息。

  • 尽量避免简短的提交信息,如“修复”,以及没有上下文的提交信息,如“找到了错误”。如果有疑问,较长的提交信息可能比简短的更好。避免使用 git commit-m 开关在命令行上编写提交信息。相反,让它打开你的编辑器,这样你就可以编写更长的提交信息。

  • 如果仅通过查看差异难以理解提交的内容,请概述提交的作用。

  • 包含其他相关信息,例如

    • 已知问题

    • 一个具体的例子(用于添加新功能/提升性能等的提交)

  • 在合适的情况下使用项目列表

  • 请随意使用 Unicode 字符,例如 SymPy Unicode 漂亮打印机的输出。

一个好的提交信息的例子

以下是一个来自提交 bf0e81e12a2f75711c30f0788daf4e58f72b2a41 的提交信息示例,这是 SymPy 历史的一部分:

integrals: Improved speed of heurisch() and revised tests

Improved speed of anti-derivative candidate expansion and solution
phases using explicit domains and solve_lin_sys(). The upside of
this change is that large integrals (those that generate lots of
monomials) are now computed *much* faster. The downside is that
integrals involving Derivative() don't work anymore. I'm not sure
if they really used to work properly or it was just a coincidence
and/or bad implementation. This needs further investigation.

Example:

In [1]: from sympy.integrals.heurisch import heurisch

In [2]: f = (1 + x + x*exp(x))*(x + log(x) + exp(x) - 1)/(x + log(x) + exp(x))**2/x

In [3]: %time ratsimp(heurisch(f, x))
CPU times: user 7.27 s, sys: 0.04 s, total: 7.31 s
Wall time: 7.32 s
Out[3]:
   ⎛ 2        x                 2⋅x      x             2   ⎞
log⎝x  + 2⋅x⋅ℯ  + 2⋅x⋅log(x) + ℯ    + 2⋅ℯ ⋅log(x) + log (x)⎠          1
──────────────────────────────────────────────────────────── + ───────────────
                             2                                      x
                                                               x + ℯ  + log(x)

Previously it took 450 seconds and 4 GB of RAM to compute.

合著者

偶尔,可能有多人作为一个团队为一个PR工作,或者你采纳了社区的一些建议。

对于这些情况,您可以通过添加来使用GitHub的共同作者功能

Co-authored-by: NAME NAME@EXAMPLE.COM
Co-authored-by: AUTHOR-NAME ANOTHER-NAME@EXAMPLE.COM

到提交信息的底部。参见 https://docs.github.com/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors。

发起拉取请求

一旦你的更改准备好进行审查,将它们推送到GitHub,并创建一个拉取请求。

在更改完全准备好之前,提出拉取请求也是可以的,这样可以尽早获得一些反馈。在花费太多时间之前尽早获得反馈会更好。如果你的拉取请求还没有完全准备好合并,可以在GitHub上将其打开为“DRAFT”状态。你还可以在拉取请求标题的开头添加“[WIP]”(代表“正在进行的工作”)来表明这一点。只需确保在PR准备好最终审查时移除DRAFT状态或[WIP]。

编写拉取请求的标题和描述

当你发起一个拉取请求时,请务必填写拉取请求描述模板。这包括添加对任何相关问题的交叉引用(如果适用,使用“修复”),并添加一个发布说明条目。

  • 描述性标题非常重要。 拉取请求的标题应表明修复了什么。标题不明确的拉取请求往往会被评审者忽略。

    不好的拉取请求标题示例有

    • 修改后的 solvers.py

    • 修复问题 #12345

    这些确实向评审者指出了在拉取请求中实际更改的内容,因此,他们可能会直接通过而不是进行评审。一个更好的拉取请求标题的例子是

    • 修复了在超越函数上使用solve()的错误

  • 不要在拉取请求的标题中放入问题编号或文件名。问题编号应放在描述中。

  • 如果你还没有准备好让拉取请求被合并,请使用DRAFT状态或在标题中包含前缀“[WIP]”,并在准备好时移除状态/前缀。

描述是一个很好的地方来:

  • 展示你所做的内容,可能通过比较主分支的输出与更改后的输出来实现。

  • 参考已解决的问题,如“#1234”;这种格式将自动创建指向相应问题或拉取请求的链接,例如“这与问题 #1234 中的问题类似…”。这种格式在拉取请求的讨论部分也有效。

  • 使用诸如“closes #1234”或“fixed #1234”(或其他符合自动关闭语法的类似语句,这些语句也在这里讨论)。然后,当你的拉取请求被合并时,这些其他问题或拉取请求将自动关闭。注意:此语法在拉取请求的讨论中不起作用。请参阅此快速指南,了解从拉取请求自动关闭问题的有效和无效语法。

  • 拉取请求需要一个发布说明条目。请参阅 https://github.com/sympy/sympy/wiki/Writing-Release-Notes 了解如何在拉取请求描述中编写发布说明。SymPy Bot 将自动检查您的 PR 是否有发布说明。

最好直接填写拉取请求模板(打开拉取请求时出现的文本)。如果你填写了模板中的所有部分,你将会有一个良好的拉取请求描述。

将您的姓名和电子邮件地址添加到 .mailmap 文件中。

每个作者的姓名和电子邮件地址都存储在 AUTHORS 文件中,但不应直接编辑此文件。当发布新版本的 SymPy 时,AUTHORS 文件会根据提交中记录的姓名和电子邮件地址自动更新。使用 git 进行的每次提交都会存储 git 配置的姓名和电子邮件地址(参见 配置 git 设置)。.mailmap 文件用于将提交中记录的姓名/电子邮件与将在 AUTHORS 文件中列出的作者姓名和电子邮件地址关联起来。

第一次提交拉取请求时,您需要通过添加类似的一行来将您的姓名和电子邮件地址添加到 .mailmap 文件中。

Joe Bloggs <joe@bloggs.com>  joeb <joe@bloggs.com>

这一行在 .mailmap 文件中将作者姓名与相应的提交关联起来。第一个姓名和电子邮件地址将最终进入 AUTHORS 文件。第二个条目是记录在提交元数据中的内容。(参见 将用户名映射到 AUTHORS 文件条目

提交元数据的名称和电子邮件应与您在提交前使用git配置的名称和电子邮件完全匹配(参见配置git设置)。bin/mailmap_check.py脚本可以检查是否正确完成此操作。如果您已提交但尚未将自己添加到.mailmap文件中,那么您将看到以下内容:

$ python bin/mailmap_check.py
This author is not included in the .mailmap file:
Joe Bloggs <joe@bloggs.com>

The .mailmap file needs to be updated because there are commits with
unrecognised author/email metadata.


For instructions on updating the .mailmap file see:
https://docs.sympy.org/dev/contributing/new-contributors-guide/workflow-process.html#mailmap-instructions

The following authors will be added to the AUTHORS file at the
time of the next SymPy release.

这意味着你应该将你的姓名和电子邮件地址添加到 .mailmap 文件中。如果你在文件末尾添加这些信息,那么 git diff 将会显示:

$ git diff
diff --git a/.mailmap b/.mailmap
index 3af6dc1..7fa63b1 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1307,3 +1307,4 @@ zsc347 <zsc347@gmail.com>
 Øyvind Jensen <jensen.oyvind@gmail.com>
 Łukasz Pankowski <lukpank@o2.pl>
+Joe Bloggs <joe@bloggs.com>

现在你可以重新运行 bin/mailmap_check.py 脚本,你应该会看到:

$ python bin/mailmap_check.py
The mailmap file was reordered

For instructions on updating the .mailmap file see:
https://docs.sympy.org/dev/contributing/new-contributors-guide/workflow-process.html#mailmap-instructions

The following authors will be added to the AUTHORS file at the
time of the next SymPy release.

Joe Bloggs <joe@bloggs.com>

第一行指出 .mailmap 文件被“重新排序”。这是因为文件应按字母顺序排列。脚本已将您的名字移动到正确的位置,因此现在您可以看到更改如下:

$ git diff
diff --git a/.mailmap b/.mailmap
index 3af6dc1..7598d94 100644
--- a/.mailmap
+++ b/.mailmap
@@ -562,6 +562,7 @@ Joannah Nanjekye <joannah.nanjekye@ibm.com> Joannah Nanjekye <jnanjekye@python.o
 Joannah Nanjekye <joannah.nanjekye@ibm.com> nanjekyejoannah <joannah.nanjekye@ibm.com>
 Joaquim Monserrat <qmonserrat@mailoo.org>
 Jochen Voss <voss@seehuhn.de>
+Joe Bloggs <joe@bloggs.com>
 Jogi Miglani <jmig5776@gmail.com> jmig5776 <jmig5776@gmail.com>
 Johan Blåbäck <johan_bluecreek@riseup.net> <johan.blaback@cea.fr>
 Johan Guzman <jguzm022@ucr.edu>

现在如果你重新运行脚本,你将会看到:

$ python bin/mailmap_check.py
No changes needed in .mailmap

The following authors will be added to the AUTHORS file at the
time of the next SymPy release.

Joe Bloggs <joe@bloggs.com>

这里的关键信息是“无需更改 .mailmap”,这意味着您已正确更新了 .mailmap 文件。现在,您应该将这些更改添加并提交:

git add .mailmap
git commit -m 'author: add Joe Bloggs to .mailmap'

将用户名映射到 AUTHORS 文件条目

有时,提交会使用不正确的名称或电子邮件地址,或者作者会使用不同的名称和电子邮件地址进行多次提交,或者作者希望使用与其github名称不同的正式名称。在这种情况下,应在.mailmap文件中添加一行,其中第一个名称和电子邮件地址是应在AUTHORS文件中记录的内容,其他则是其他提交中错误使用的名称和电子邮件地址。例如,如果提交记录的名称为joeb,电子邮件地址为wrong@email.com,但AUTHORS文件应显示如上所述的Joe Bloggs,则.mailmap文件中应有一行如下:

Joe Bloggs <joe@bloggs.com> joeb <wrong@email.com>

这种情况常见的原因是使用 GitHub 网页界面进行提交,这总是将名字记录为 GitHub 用户名,并将电子邮件记录为类似 1785690389+joeb@users.noreply.github.com 的形式。在这种情况下,需要在 .mailmap 中添加一行,例如:

Joe Bloggs <joe@bloggs.com> joeb <1785690389+joeb@users.noreply.github.com>

可以在 .mailmap 文件中添加多行这样的内容。它们应记录作者使用的所有不同姓名和电子邮件地址组合,并将它们映射到 AUTHORS 文件中显示的单一作者姓名。

如果你的拉取请求被合并,并且你之前没有被添加到 AUTHORS 文件中,那么你的名字将在 SymPy 的下一个版本发布时被添加。