Git 培训营和备忘单

注意

本节提供有关 CPython 工作流中常见任务的说明。它旨在帮助对 Git 和 GitHub 有所了解的新贡献者。

如果您是 Git 和 GitHub 的新手,请在提交拉取请求之前熟悉这些说明。由于使用 Git 和 GitHub 完成这些任务的方法有多种,因此本节反映了一种适合新贡献者的方法。经验丰富的贡献者可能希望采用不同的方法。

在本节中,我们将介绍一些与 CPython 工作流相关的常用 Git 命令。

注意

为您设置常见任务

Forking CPython GitHub 存储库

您只需执行此操作一次。

  1. 转到 https://github.com/python/cpython

  2. 按右上角的 Fork

  3. 当询问将存储库 fork 到何处时,选择将其 fork 到您的用户名。

  4. 您的 fork 的 CPython 存储库将创建在 https://github.com/<username>/cpython。

克隆 fork 的 CPython 存储库

您只需在每台机器上执行此操作一次。从您的命令行

$ git clone git@github.com:<username>/cpython.git

还建议配置 upstream 远程存储库

$ cd cpython
$ git remote add upstream https://github.com/python/cpython

您还可以使用基于 SSH 或 HTTPS 的 URL。

配置远程

配置 gitupstream 远程拉取 main

$ git config --local branch.main.remote upstream

由于不应该尝试推送到 upstream,因此配置 git 始终推送到 origin

$ git remote set-url --push upstream git@github.com:<username>/cpython.git

列出远程存储库

要列出已配置的远程存储库及其 URL

$ git remote -v

您应该有两个远程存储库:指向您 fork 的 CPython 存储库的 origin,以及指向官方 CPython 存储库的 upstream

origin  git@github.com:<username>/cpython.git (fetch)
origin  git@github.com:<username>/cpython.git (push)
upstream        https://github.com/python/cpython (fetch)
upstream        git@github.com:<username>/cpython.git (push)

要验证 main 的上游

$ git config branch.main.remote

它应发出 upstream,表示从 upstream 远程跟踪/拉取 main 的更改。

设置您的姓名和电子邮件地址

$ git config --global user.name "Your Name"
$ git config --global user.email your.email@example.com

--global 标志全局设置这些参数,而 --local 标志仅为当前项目设置这些参数。

在 Windows 上启用 autocrlf

autocrlf 选项将自动修复任何 Windows 特定的行尾。这应在 Windows 上启用,因为公共存储库有一个钩子,它将拒绝所有行尾错误的变更集

$ git config --global core.autocrlf input

创建和切换分支

重要

切勿直接提交到 main 分支。

main 创建一个新分支并切换到它

$ git switch -c <branch-name> main

这等同于

$ # create a new branch from main
$ git branch <branch-name> main
$ # switch to the new branch
$ git switch <branch-name>

要查找您当前所在的的分支

$ git branch

当前分支的旁边会有一个星号。请注意,这只会列出您所有的本地分支。

要列出所有分支,包括远程分支

$ git branch -a

要切换到不同的分支

$ git switch <another-branch-name>

其他版本只是存储库中的分支。例如,要从 upstream 远程处理 3.12 版本

$ git switch -c 3.12 upstream/3.12

删除分支

要删除您不再需要的本地分支

$ git switch main
$ git branch -D <branch-name>

要删除远程分支

$ git push origin -d <branch-name>

您可以指定多个要删除的分支。

重命名分支

CPython 存储库的默认分支在 Python 3.10b1 发布后已从 master 重命名为 main

如果您在 GitHub 上有一个分支(如 Forking CPython GitHub repository 中所述),并且是在重命名之前创建的,您应该访问分支的 GitHub 页面来重命名那里的分支。您只需要执行此操作一次。GitHub 应该会为您提供一个对话框。如果没有(或对话框已被关闭),您可以按照 这些 GitHub 说明 手动重命名分支。

在分支中重命名分支后,您还需要更新所有本地克隆。这只需要在每个克隆中执行一次

$ git branch -m master main
$ git fetch origin
$ git branch -u origin/main main
$ git remote set-head origin -a

(GitHub 也会在您重命名分支后提供这些说明。)

如果您在 GitHub 上没有分支,而是在分支重命名之前直接克隆了主存储库,您仍然必须更新本地克隆。这仍然只需要在每个克隆中执行一次。在这种情况下,您可以按如下方式重命名本地分支

$ git branch -m master main
$ git fetch upstream
$ git branch -u upstream/main main

暂存和提交文件

  1. 显示当前更改

    $ git status
    
  2. 暂存要包含在提交中的文件

    $ git add -p  # to review and add changes to existing files
    $ git add <filename1> <filename2>  # to add new files
    
  3. 提交已暂存的文件(在步骤 2 中完成)

    git commit -m "This is the commit message."
    

还原更改

还原尚未提交的文件的更改

$ git checkout <filename>

如果更改已提交,现在您想将其重置为源代码中的任何内容

$ git reset --hard HEAD

隐藏更改

隐藏尚未准备好提交的更改

$ git stash

重新应用上次隐藏的更改

$ git stash pop

比较更改

查看所有未提交的更改

$ git diff

main 分支进行比较

$ git diff main

使用 attr pathspec 从 diff 中排除生成的文件(注意单引号)

$ git diff main ':(attr:!generated)'

默认情况下从 diff 中排除生成的文件

$ git config diff.generated.binary true

generated 属性 在存储库根目录中找到的 .gitattributes 中定义。

推送更改

一旦您的更改准备好进行审查或拉取请求,您需要将它们推送到远程存储库。

$ git switch <branch-name>
$ git push origin <branch-name>

创建拉取请求

  1. 转到 https://github.com/python/cpython

  2. New pull request 按钮。

  3. 单击 compare across forks 链接。

  4. 选择基本存储库:python/cpython 和基本分支:main

  5. 选择头存储库:<username>/cpython 和头分支:包含您更改的分支。

  6. Create pull request 按钮。

您应该在 PR 的标题中包含问题编号,格式为 gh-NNNNN: <PR Title>

链接到问题和拉取请求

您可以使用 gh-NNNNN 链接到问题和拉取请求(此形式优于 #NNNNN)。如果引用出现在列表中,链接将展开以显示问题/PR 的状态和标题。

当您创建包含标题中 gh-NNNNN 的 PR 时,bedevere 会自动在第一条消息中添加指向该问题的链接。

此外,拉取请求支持 特殊关键字,这些关键字可用于链接到问题并在 PR 合并时自动关闭问题。但是,问题通常需要多个 PR 才能关闭(例如,回传到其他分支),因此只有当您确定单个 PR 足以解决和关闭问题时,此功能才有用。

更新你的 CPython 分支

场景

  • 你前段时间分叉了 CPython 仓库。

  • 时间流逝。

  • 上游 CPython 仓库中已提交了新的提交。

  • 你分叉的 CPython 仓库不再是最新的了。

  • 你现在想更新你的分叉的 CPython 仓库,使其与上游 CPython 仓库相同。

请不要尝试通过从 python:main<username>:main 创建一个拉取请求来解决此问题,因为补丁的作者会收到不必要的通知。

解决方案

$ git switch main
$ git pull upstream main
$ git push origin main

注意

要让上述命令起作用,请按照 获取源代码 部分中的说明进行操作。

另一个场景

  • 你前段时间创建了 some-branch

  • 时间流逝。

  • 你对 some-branch 做了一些提交。

  • 与此同时,上游 CPython 仓库中有一些最近的更改。

  • 你想将上游 CPython 仓库中的最近更改合并到 some-branch 中。

解决方案

$ git switch some-branch
$ git fetch upstream
$ git merge upstream/main
$ git push origin some-branch

当你运行 git merge upstream/main 时,你可能会看到“冲突”和“自动合并失败”之类的错误消息。

当发生这种情况时,你需要解决冲突。请参阅有关解决冲突的以下文章

将补丁应用到 Git

场景

  • 存在一个补丁,但没有针对它的拉取请求。

解决方案

  1. 在本地下载补丁。

  2. 应用补丁

    $ git apply /path/to/patch.diff
    

    如果出现错误,请更新到创建补丁时的修订版本,然后再次尝试 git apply

    $ git checkout $(git rev-list -n 1 --before="yyyy-mm-dd hh:mm:ss" main)
    $ git apply /path/to/patch.diff
    

    如果补丁仍然无法应用,则补丁工具将无法应用该补丁,并且需要手动重新实现它。

  3. 如果应用成功,请创建一个新分支并切换到该分支。

  4. 暂存并提交更改。

  5. 如果补丁应用于旧修订版本,则需要更新它并解决合并冲突

    $ git rebase main
    $ git mergetool
    

    对于非常旧的更改,在解决冲突方面,git merge --no-ff 可能比变基更容易。

  6. 推送更改并打开一个拉取请求。

下载他人的补丁

场景

  • 一位贡献者向 CPython 发出了一个拉取请求。

  • 在合并之前,你想能够在本地测试他们的更改。

如果你已安装 GitHub CLIhub,则可以执行以下操作

$ gh co <pr_number>  # GitHub CLI
$ hub pr checkout <pr_number>  # hub

这两种工具都将为该分支配置一个远程 URL,因此如果你在创建拉取请求时选中了“允许维护者编辑”,则可以 git push

如果你没有安装 GitHub CLI 或 hub,则可以设置一个 git 别名

$ git config --global alias.pr '!sh -c "git fetch upstream pull/${1}/head:pr_${1} && git checkout pr_${1}" -'
git config --global alias.pr "!sh -c 'git fetch upstream pull/${1}/head:pr_${1} && git checkout pr_${1}' -"

别名只需要设置一次。设置别名后,你可以按如下方式获取拉取请求的本地副本

$ git pr <pr_number>

接受并合并拉取请求

拉取请求可以由 Python 核心开发者接受并合并。你可以阅读更多内容,了解在接受更改之前需要寻找什么 在此处

在合并更改之前,所有拉取请求都需要通过必要的检查。请参阅 “保持 CI 绿色”,了解你可以采取的一些简单措施来帮助检查变为绿色。

在任何时候,核心开发者都可以通过单击灰色的 Enable auto-merge (squash) 按钮来安排自动合并更改。你可以在拉取请求页面的底部找到它。仅当所有必需的检查都通过时,才会进行自动合并,但 PR 不需要获得批准才能进行成功的自动合并。

如果你正在审查的 PR 上的所有必需检查已经完成,则你将找到一个绿色的 Squash and merge 按钮,而不是灰色的 Enable auto-merge 按钮。

在任一情况下,调整并清理提交信息。

✅ 这是一个的提交信息的示例

gh-12345: Improve the spam module (GH-777)

* Add method A to the spam module
* Update the documentation of the spam module

❌ 这是一个的提交信息的示例

gh-12345: Improve the spam module (#777)

* Improve the spam module
* merge from main
* adjust code based on review comment
* rebased

这个坏的示例包含了与最终更改无关的 PR 生命周期直接影响的要点。

注意

如何编写 Git 提交信息 是一篇描述如何编写好的提交信息的不错文章。

最后,按 确认 合并 合并 按钮。

取消自动合并

如果您注意到一个已被接受且启用了自动合并的拉取请求存在问题,您仍然可以在 GitHub 自动合并更改之前取消工作流。

按拉取请求页面底部的灰色“禁用自动合并”按钮以完全禁用自动合并。这是推荐的方法。

要暂停自动合并,请将“DO-NOT-MERGE”标签应用到 PR 或提交请求更改的审查。后者将在 PR 上放置一个“等待更改”标签,该标签会以类似于“DO-NOT-MERGE”的方式暂停自动合并。在作者提交修复并重新请求审查后,您可以通过提交批准审查或驳回您之前请求更改的审查来恢复自动合并流程。

请注意,在启用自动合并流程后推送新更改不会停止它。

回传已合并的更改

在拉取请求被接受并合并到 main 之后,可能需要将其回传到其中一个维护分支。这通常由拉取请求本身上的标签 needs backport to X.Y 指示。

使用实用脚本 cherry_picker.py 回传提交。

用于回传的提交哈希是合并到 main 分支的压缩提交。在合并的拉取请求中,滚动到页面底部。找到类似于以下内容的事件

<core_developer> merged commit <commit_sha1> into python:main <sometime> ago.

通过关注 <commit_sha1> 的链接,您将获得完整的提交哈希。

或者,还可以通过以下 Git 命令获取提交哈希

$ git fetch upstream
$ git rev-parse ":/gh-12345"

上述命令将打印出包含 "gh-12345" 作为提交信息一部分的提交的哈希。

在为回传提交格式化提交信息时:保留原始信息,并删除回传拉取请求的编号。

✅ 良好的回传提交信息的示例

 gh-12345: Improve the spam module (GH-777)

 * Add method A to the spam module
 * Update the documentation of the spam module

 (cherry picked from commit 62adc55)

❌ 坏的回传提交信息的示例

 gh-12345: Improve the spam module (GH-777) (#888)

 * Add method A to the spam module
 * Update the documentation of the spam module

合并前编辑拉取请求

当拉取请求提交者启用了 允许维护者编辑 选项时,Python 核心开发者可能会决定在合并之前自行进行任何剩余的编辑,而不是要求提交者进行编辑。当剩余的更改是簿记项目(例如更新 Misc/ACKS)时,这可能特别合适。

要编辑一个针对 main 的开放拉取请求

  1. 在拉取请求页面中,在描述下方,有一些关于贡献者的已分叉 CPython 存储库和分支名称的信息,这些信息在稍后会很有用

<contributor> wants to merge 1 commit into python:main from <contributor>:<branch_name>
  1. 使用 git pr 别名获取拉取请求

    $ git pr <pr_number>
    

    这将在 <pr_number> 处检出贡献者的分支。

  2. 在分支上进行并提交您的更改。例如,合并自提交 PR 以来对 main 所做的更改(当接受更改时,任何合并提交都将被后来的 合并 合并 删除)

    $ git fetch upstream
    $ git merge upstream/main
    $ git add <filename>
    $ git commit -m "<message>"
    
  3. 将更改推回贡献者的 PR 分支

    $ git push git@github.com:<contributor>/cpython <pr_number>:<branch_name>
    
  4. 可选,删除 PR 分支

GitHub CLI

GitHub CLI 是一个命令行界面,允许您创建、更新和检查 GitHub 问题和拉取请求。

您可以按照 这些说明 安装 GitHub CLI。安装后,您需要进行身份验证

$ gh auth login

有用命令示例

  • 创建 PR

    $ gh pr create
    
  • 签出其他 PR

    $ gh co <pr-id>
    
  • ssh 设置为 Git 协议

    $ gh config set git_protocol ssh
    
  • 设置浏览器

    $ gh config set browser <browser-path>
    

Git 工作树

使用 Git 工作树,你可以将多个隔离的工作树与单个存储库(.git 目录)相关联。这允许你在不同的版本分支上同时工作,无需维护和分别更新多个独立的克隆。此外,它减少了克隆开销并节省了磁盘空间。

设置 Git 工作树

使用现有的 CPython 克隆(参见 克隆分叉的 CPython 存储库),将 cpython 目录重命名为 main 并将其移至新的 cpython 目录,这样我们就有了一个类似于

cpython
└── main (.git is here)

接下来,为其他分支创建工作树

$ cd cpython/main
$ git worktree add -b 3.11 ../3.11 upstream/3.11
$ git worktree add -b 3.12 ../3.12 upstream/3.12

这给出了类似这样的结构,每个分支的代码在其自己的目录中签出

cpython
├── 3.11
├── 3.12
└── main

使用 Git 工作树

列出你的工作树,例如

$ git worktree list
/Users/my-name/cpython/main  b3d24c40df [main]
/Users/my-name/cpython/3.11  da1736b06a [3.11]
/Users/my-name/cpython/3.12  cf29a2f25e [3.12]

切换到目录以从该分支工作。例如

$ cd ../3.12
$ git switch -c my-3.12-bugfix-branch  # create new branch
$ # make changes, test them, commit
$ git push origin my-3.12-bugfix-branch
$ # create PR
$ git switch 3.12  # switch back to the 3.12 branch
...