拉取请求(PR)工作流程

Godot使用的所谓 PR工作流程 对于许多使用Git的项目来说都很常见,并且对于资深自由软件贡献者应该很熟悉。这个想法是只有少数(如果有的话)直接提交给 master 分支。相反,贡献者 fork 项目(即创建它的副本,他们可以按照自己的意愿修改),然后使用GitHub接口从其fork的一个分支请求 pull 到原始(通常命名为 upstream )存储库的一个分支。

然后,生成的 拉取请求 (PR)可以由其他贡献者审查,可能批准它、拒绝它、或者最常要求修改它。一旦获得批准,PR就可以由其中一个核心开发人员合并,其提交将成为目标分支(通常是 master 分支)的一部分。

We will go together through an example to show the typical workflow and associated Git commands. But first, let's have a quick look at the organization of Godot's Git repository.

Git源存储库

GitHub上的 存储库 是一个 Git 代码存储库以及一个嵌入式问题跟踪器和PR系统。

注解

如果您正在为文档做贡献,可以在 这里 找到它的存储库。

Git版本控制系统是用于跟踪源代码的连续编辑的工具——为了高效地为Godot做贡献,强烈 建议学习Git命令行的基础知识。Git有一些图形界面,但是它们通常会鼓励用户养成关于Git和PR工作流程的不良习惯,因此我们建议不要使用它们。特别是,我们建议不要使用GitHub的在线编辑器进行代码贡献(尽管可以进行较小的修复或文档更改),因为它会对每个文件和每个修改强制执行一次提交,因此很快导致PR的Git历史记录不可读(尤其是在同行评审之后)。

参见

Git的“书”的第一部分很好地介绍了该工具的原理以及您在日常工作流程中需要掌握的各种命令。您可以在 Git SCM 网站上在线阅读它们。

Git存储库上的分支被组织如下:

  • master 分支是开发下一个主要版本的地方。作为开发分支,它可能不稳定,不适合用于生产。这是应该优先进行PR的地方。
  • The stable branches are named after their version, e.g. 3.1 and 2.1. They are used to backport bugfixes and enhancements from the master branch to the currently maintained stable release (e.g. 3.1.2 or 2.1.6). As a rule of thumb, the last stable branch is maintained until the next major version (e.g. the 3.0 branch was maintained until the release of Godot 3.1). If you want to make PRs against a maintained stable branch, please check first if your changes are also relevant for the master branch, and if so make the PR for the master branch in priority. Release managers can then cherry-pick the fix to a stable branch if relevant.
  • There might occasionally be feature branches, usually meant to be merged into the master branch at some time.

分叉和克隆

第一步是在GitHub上 分叉 godotengine/godot 库。 为此,您需要拥有一个GitHub帐户并登录。在存储库的GitHub页面的右上角,您应该看到如下所示的 Fork 按钮:

../../_images/github_fork_button.png

点击它,一段时间后,您应该被重定向到您自己的Godot存储库分叉,并将GitHub用户名作为名称空间:

../../_images/github_fork_url.png

然后您可以 克隆 您的分叉,即创建在线存储库的本地副本(在Git中叫做 origin remote)。如果您还没有,若您使用的是Windows或macOS,请从 其网站 下载Git;若您使用的是Linux,请通过您的软件包管理器安装它。

注解

如果您使用的是Windows,请打开Git Bash键入命令。macOS和Linux用户可以使用各自的终端。

要从GitHub克隆您的fork,请使用以下命令:

$ git clone https://github.com/USERNAME/godot

注解

在我们的示例中,$ 字符表示典型UNIX shell上的命令行提示符。 它不是命令的一部分,不应该键入。

稍后,您应该在当前工作目录中有一个 godot 目录。 使用 cd 命令进入它:

$ cd godot

我们将从建立对我们分叉的原始存储库的引用开始:

$ git remote add upstream https://github.com/godotengine/godot
$ git fetch upstream

This will create a reference named upstream pointing to the original godotengine/godot repository. This will be useful when you want to pull new commits from its master branch to update your fork. You have another remote reference named origin, which points to your fork (USERNAME/godot).

您只需要做一次上面的步骤,只要您保留本地的 godot 文件夹(您可以随意移动它,相关的元数据隐藏在它的 .git 子文件夹中)。

注解

分支、拉取、编码、暂存、提交、推送、重新设置基线……技术。

这对Daft Punk的 技术 的不良看法显示了Git初学者对其工作流程的一般概念:许多奇怪的命令可以通过复制和粘贴来学习,希望它们能按预期运行。这实际上并不是一种糟糕的学习方式,只要您好奇并且在迷失时毫不犹豫地询问您的搜索引擎,因此,我们将为您提供在Git中工作时要了解的基本命令。

In the following, we will assume as an example that you want to implement a feature in Godot's project manager, which is coded in the editor/project_manager.cpp file.

分支

默认情况下,git clone 应该让您进入fork(origin)的 master 分支。要开始自己的功能开发,我们将创建一个功能分支:

# Create the branch based on the current branch (master)
$ git branch better-project-manager

# Change the current branch to the new one
$ git checkout better-project-manager

此命令是等效的:

# Change the current branch to a new named one, based on the current branch
$ git checkout -b better-project-manager

如果您想回到 master 分支,您会使用:

$ git checkout master

您可以使用 git branch 命令查看当前使用的分支:

$ git branch
  2.1
* better-project-manager
  master

创建新分支之前,请务必始终返回到“ master”分支,因为您当前的分支将用作新分支的基础。或者,您可以在新分支的名称之后指定一个自定义基础分支:

$ git checkout -b my-new-feature master

更新您的分支

第一次(在您分叉上游存储库之后)不需要这样做。 但是,下次您想要处理某些事情时,您会注意到您的fork的 master 落后于上游 master 分支几个提交:其他贡献者的拉取请求同时被合并。

为了确保您开发的功能与当前的上游 master 分支之间不会发生冲突,您将不得不通过 上游分支来更新您的分支。

$ git pull --rebase upstream master

--rebase参数将确保您所提交的任何本地更改将被“重新应用”到“拉动”分支的顶部,这通常是我们在PR工作流程中想要的。这样,当您打开请求请求时,您自己的提交将是与上游``master''分支的唯一区别。

在重新定基础时,如果您提交同时在上游分支中已更改的修改后的代码,则可能会发生冲突。如果发生这种情况,Git将在冲突的提交处停止,并要求您解决冲突。您可以使用任何文本编辑器执行此操作,然后进行更改(稍后再进行介绍),然后继续执行git rebase --continue。如果以后的提交也有冲突,请重复该操作,直到重新设置操作完成。

如果您不确定重新设置期间发生了什么并且感到恐慌(不用担心,我们都要做前几次),则可以使用git rebase --abort中止重新设置。然后您将返回分支的原始状态,然后再调用``git pull --rebase''。

注解

如果省略了--rebase参数,则将创建一个合并提交,告诉Git如何处理两个不同的分支。如果发生任何冲突,将通过此合并提交立即解决所有冲突。

虽然这是有效的工作流程,并且默认行为是``git pull'',但PR工作流程中不赞成PR中的合并提交。我们仅在将PR合并到上游分支时才使用它们。

我们的理念是,PR应该代表对代码库所做更改的最后阶段,并且我们对合并之前在中间阶段所做的错误和修复不感兴趣。 Git提供了很棒的工具来“重写历史记录”,使它好像我们第一次就做对了一样,并且我们乐于使用它来确保更改在合并之后很容易被查看和理解。

如果您已经创建了一个合并提交而不使用``rebase'',或者进行了其他任何导致不希望的历史记录的更改,那么最好的选择是在上游分支上使用一个* interactive rebase *。有关说明,请参见<doc_pr_workflow_rebase>专用部分。

小技巧

如果您想在任何时候将本地分支“重置”为给定的提交或分支,则可以使用“ git reset --hard <提交ID>”或“ git reset --hard <remote> /”来实现。 <branch>``(例如git reset --hard上游/ master``)。

请注意,这将删除您可能已在此分支中提交的所有更改。如果您错误地丢失了提交,请使用git reflog命令查找您想要还原的先前状态的提交ID,并将其用作git reset --hard的参数。回到那个状态。

做出变更

然后,您将使用常用的开发环境(文本编辑器,IDE等)对我们的示例的 editor/project_manager.cpp 文件进行更改。

By default, those changes are unstaged. The staging area is a layer between your working directory (where you make your modifications) and the local Git repository (the commits and all the metadata in the .git folder). To bring changes from the working directory to the Git repository, you need to stage them with the git add command, and then to commit them with the git commit command.

在暂存之前,暂存后和提交之后,您应该了解各种命令来查看当前工作。

  • git diff 将显示当前未暂存的更改,即工作目录和暂存区域之间的差异。
  • git checkout -- <files> 将撤消给定文件的未暂存更改。
  • git add <files>暂存 列出的文件的更改。
  • git diff --staged 将显示当前的暂存的更改,即暂存区域和上次提交之间的差异。
  • git reset HEAD <files>取消暂存 列出的文件的更改。
  • git status 将显示当前暂存和未暂存的修改。
  • git commit 将提交暂存文件。它将打开一个文本编辑器(您可以使用 GIT_EDITOR 环境变量或Git配置中的 core.editor 设置来定义要使用的编辑器),以便您编写提交日志。您可以使用 git commit -m "Cool commit log" 直接写日志。
  • git commit --amend允许您使用当前暂存的更改来修改最后一次提交(添加了git add)。如果要修复上一次提交中的错误(错误,错别字,样式问题等),这是最佳选择。
  • git log 将显示当前分支的最后提交。 如果您做了本地提交,它们应该显示在顶部。
  • git show 将显示上次提交的更改。您还可以指定提交哈希以查看该提交的更改。

要记住的东西太多了!不用担心,当您需要进行更改时,只需检查一下备忘单,然后边做边学即可。

以下是我们的示例中shell历史记录的样子:

# It's nice to know where you're starting from
$ git log

# Do changes to the project manager with the nano text editor
$ nano editor/project_manager.cpp

# Find an unrelated bug in Control and fix it
$ nano scene/gui/control.cpp

# Review changes
$ git status
$ git diff

# We'll do two commits for our unrelated changes,
# starting by the Control changes necessary for the PM enhancements
$ git add scene/gui/control.cpp
$ git commit -m "Fix handling of margins in Control"

# Check we did good
$ git log
$ git show
$ git status

# Make our second commit
$ git add editor/project_manager.cpp
$ git commit -m "Add a pretty banner to the project manager"
$ git log

有了这个,我们应该在 better-project-manager 分支中有两个新的提交,这些提交不在 master 分支中。它们仍然只是本地的,远程分支不知道它们,上游存储库也不知道。

将更改推送到远程

这就是 git push 将发挥作用的地方。在Git中,提交总是在本地存储库中完成(与Subversion不同,其提交将直接修改远程存储库)。您需要 推送 新提交到远程分支以与世界共享它们。这个语法是:

$ git push <remote> <local branch>[:<remote branch>]

如果您希望远程分支与本地分支具有相同的名称,则可以省略有关远程分支的部分,在本示例中就是这种情况,因此我们将执行以下操作:

$ git push origin better-project-manager

Git会要求您提供用户名和密码,更改将发送到您的远程分支。如果您在GitHub上查看fork的页面,则应该看到一个带有已添加提交的新分支。

发出拉取请求

When you load your fork's branch on GitHub, you should see a line saying "This branch is 2 commits ahead of godotengine:master." (and potentially some commits behind, if your master branch was out of sync with the upstream master branch).

../../_images/github_fork_make_pr.png

On that line, there is a "Pull request" link. Clicking it will open a form that will let you issue a pull request on the godotengine/godot upstream repository. It should show you your two commits, and state "Able to merge". If not (e.g. it has way more commits, or says there are merge conflicts), don't create the PR, something went wrong. Go to IRC and ask for support :)

为PR使用明确的标题,并将必要的详细信息放在注释区域。您可以拖放屏幕截图,GIF或压缩的项目(如果相关),以展示您的工作实现的内容。点击“创建拉取请求”,没错!

修改拉取请求

虽然它是由其他贡献者审核的,但您经常需要对尚未合并的PR进行更改,或者是因为贡献者要求他们,或者是因为您在测试时发现了自己的问题。

好消息是您可以简单地通过对您发出拉取请求的分支进行操作来修改拉取请求。 您可以,例如在该分支上进行新的提交,将其推送到您的分支,PR将自动更新:

# Check out your branch again if you had changed in the meantime
$ git checkout better-project-manager

# Fix a mistake
$ nano editor/project_manager.cpp
$ git add editor/project_manager.cpp
$ git commit -m "Fix a typo in the banner's title"
$ git push origin better-project-manager

但是,请注意,在我们的PR工作流程中,我们偏向于将代码库从一种功能状态转换为另一种功能状态的提交,而没有中间提交来解决您自己的代码或样式问题中的错误。在大多数情况下,我们会倾向于在给定的PR中进行一次提交(除非有充分的理由将更改分开),因此与其撰写新的提交,不如考虑使用git commit --amend来修改先前提交的修复程序。上面的示例将变为:

# Check out your branch again if you had changed in the meantime
$ git checkout better-project-manager

# Fix a mistake
$ nano editor/project_manager.cpp
$ git add editor/project_manager.cpp
# --amend will change the previous commit, so you will have the opportunity
# to edit its commit message if relevant.
$ git commit --amend
# As we modified the last commit, it no longer matches the one from your
# remote branch, so we need to force push to overwrite that branch.
$ git push --force origin better-project-manager

交互式衍合

如果您没有严格按照上述步骤将*更改*修改为提交而不是创建修订提交,或者如果您在不了解我们的工作流程和Git使用技巧的情况下编写了更改,则审阅者可能会要求您*进行更改*您的分支将*部分或全部提交压缩为一个。

确实,如果对某些修订进行了一些修订,以解决原始提交中的错误,错别字等,则它们与将来的变更日志阅读器无关,后者希望了解Godot代码库中发生了什么,或者何时以及如何进行给定文件是最后修改的。

To squash those extraneous commits into the main one, we will have to rewrite history. Right, we have that power. You may read that it's a bad practice, and it's true when it comes to branches of the upstream repo. But in your fork, you can do whatever you want, and everything is allowed to get neat PRs :)

We will use the interactive rebase git rebase -i to do this. This command takes a commit ID or a branch name as argument, and will let you modify all commits between that commit/branch and the last one in your working branch, the so-called HEAD.

While you can give any commit ID to git rebase -i and review everything in between, the most common and convenient workflow involves rebasing on the upstream ``master`` branch, which you can do with:

$ git rebase -i upstream/master

注解

Referencing branches in Git is a bit tricky due to the distinction between remote and local branches. Here, upstream/master (with a /) is a local branch which has been pulled from the upstream remote's master branch.

Interactive rebases can only be done on local branches, so the / is important here. As the upstream remote changes frequently, your local upstream/master branch may become outdated, so you can update it with git fetch upstream master. Contrarily to git pull --rebase upstream master which would update your currently checked out branch, fetch will only update the upstream/master reference (which is distinct from your local master branch... yes it's confusing, but you'll become familiar with this little by little).

这将打开一个文本编辑器(默认为``vi'',请参阅`Git docs <https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_core_editor> __`进行配置您最喜欢的一个),内容可能如下所示:

pick 1b4aad7 Add a pretty banner to the project manager
pick e07077e Fix a typo in the banner's title

编辑器还将显示有关如何对这些提交采取行动的说明。特别是,它应该告诉您 pick 意味着使用该提交(什么都不做),并且 squashfixup 可以用于在其父提交中 合并 提交。squashfixup 之间的区别在于 fixup 会从压缩的提交中丢弃提交日志。在我们的示例中,我们对保持 修复错字 提交的日志不感兴趣,因此我们使用:

pick 1b4aad7 Add a pretty banner to the project manager
fixup e07077e Fix a typo in the banner's title

保存并退出编辑器后,将发生重新设置基线。第二个提交将被合并到第一个提交中,并且 git loggit show 现在应该确认您只有一个具有先前两个提交的更改的提交。

但!你改写了历史,现在你的本地和远程分支有分歧。实际上,以上示例中的提交1b4aad7将已更改,因此获得了新的提交哈希。如果您尝试推送到远程分支,将引发错误:

$ git push origin better-project-manager
To https://github.com/akien-mga/godot
 ! [rejected]        better-project-manager -> better-project-manager (non-fast-forward)
error: failed to push some refs to 'https://akien-mga@github.com/akien-mga/godot'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart.

This is a sane behavior, Git will not let you push changes that would override remote content. But that's actually what we want to do here, so we will have to force it:

$ git push --force origin better-project-manager

还有没错! Git很乐意用您在本地拥有的东西 替换 您的远程分支(所以使用 git log 确保您想要的东西)。这也将相应地更新PR。

删除Git分支

在您的提交请求合并之后,您应该做的最后一件事是:删除用于PR的Git分支。如果不删除分支不会有问题,但是这样做是一个好习惯。您将需要执行两次,一次是对本地分支,另一次是对GitHub上的远程分支。

要在本地删除 better project manager 分支,请使用以下命令:

$ git branch -d better-project-manager

或者,如果分支尚未合并,我们想要删除它,不是使用 -d,而是使用 -D

接下来,要删除GitHub上的远程分支,请使用以下命令:

$ git push origin -d better-project-manager

您还可以从GitHub PR本身删除远程分支,一旦合并或关闭它就会出现一个按钮。