熟悉 GitHub/Git(建设中)
最后更新于
最后更新于
Git/GitHub 是开源社区贡献的必备技能,本文将详细介绍 OpenMMLab 社区中 GitHub 经常用到的几项功能,Github 是人类开源财富的聚集地,作为社区用户,我们需要充分熟悉 Github 的使用,汲取来自开源社区的能力。
本文将介绍
Github 相关介绍
Git 基础知识
Git 实战
GitHub (https://github.com)是最大的 Git 版本库托管平台,包括 OpenMMLab 在内的许多开源项目使用 GitHub 网站实现代码托管、版本管理、问题追踪、社区交流等功能。即使你还不熟悉 Git 的使用,也很可能已经使用过 GitHub 来查找所需的开源软件或资料。我们首先简单熟悉一下 GitHub 的页面和基本功能。 以 MMPose (https://github.com/open-mmlab/mmpose)为例,一个软件项目在 GitHub 上的首页通常如下图所示:
在页面最上方的区域展示了该软件项目的名称和基本信息,如 Watch(关注)、Star(点赞)和 Fork(使用或开发)的人数。基本信息下方是一排功能标签,包括 Code、Issues、Pull Requests、Actions、Insights 等,下面会依次介绍。
默认的 Code 页面,内容即为图1所示,主要包括:
文件结构和代码内容:该区域显示了项目中所有的文件,可以在此浏览文件结构,或点进文件浏览其内容。
其他信息:通常包括使用许可、发布版本、贡献者等信息。
README:如果当前浏览的路径下有 README.md 文件,其内容会显示在这一区域。通常软件项目会在根目录下包含 README.md 文件作为项目介绍。
Issues 页面类似用于社区沟通的论坛。使用者可以在这里创建 issue 来提出遇到的问题,报告发现的 bug,或提出意见建议等;开发者可以在 issue 下进行回复,或将 issue 指定(assign)给特定的人来解决。此外,还可以用 issue 功能发布消息,如项目的 Roadmap、开发计划等,并将这些重要信息置顶。每个 issue 都会有一个编号,如 #900, 可以用于在其他 issue 或稍后将介绍的 Pull Request 中关联这个 issue。
Pull Requests 页面顾名思义用来浏览和管理 pull request(下简称 PR),如图4所示。通常,在 Github 上多人共同开发和维护一个项目时,会遵循一定的开发流程,这部分将在3.1中详细介绍。在此只简要说明开发流程,以引入 PR 的概念:
开发者从官方代码仓库(如 open-mmlab/mmpose)fork 一份副本到自己的帐号 (如 zhang3/mmpose),并 clone 到本地。
开发者在自己的代码仓库进行某项功能的开发。
开发者将自己的修改推到自己的远程仓库,并向官方仓库发出申请,要求官方仓库拉取(pull)此次修改,即将此次修改加入到官方代码中。这样的请求就叫做 PR。(与之相对的还有 GitLab 平台所采用的 merge request,拓展阅读:https://stackoverflow.com/questions/22199432/pull-request-vs-merge-request)
代码库的维护者或其他开发者会对 PR 进行 Review,并与作者共同进行讨论和修改。最终将修改完成的 PR 合入到官方代码仓库中。至此,一个开发项的开发周期完成。
在 PR 列表中点进某个 PR 后,可以看到其内容包括 PR 的描述信息、作者提交代码的历史、Reviewer的意见以及和作者的往来沟通等,这部分在 3.1 中也会详细介绍。与 issue 类似,每个 PR 也会有一个编号,用来在别处引用或关联该 PR。
Actions 指 GitHub Actions,是 GitHub 提供的简化和方便开发流程的功能,用来在开发周期中自动触发执行特定的操作。如在 PR 被提交时,自动运行 CI; 在发布新版本时,自动编译并更新 pypi 上托管的安装包等。在 Actions 页面中,可以看到最近运行过的 action。点开其中一个action,可以看到详细信息、执行的具体操作步骤和输出的 log 等。如图 5 所示。
Insights 页面展示了项目的汇总信息和统计数据,以方便开发者快速了解项目的整体开发情况、社区活跃度和关注点的等。通常我们较关注的项目有:
Pulse:项目的总览,包括最近的贡献者,PR,issue 等信息。
Contributors:代码贡献者信息。
Traffic: 项目流量信息,包括近期的访问量、clone 次数、访问者的来源以及访问量最高的项目内容等。
在 PR 提交成功后,正常流程是由至少两个 Reviewer 对 PR 进行 Review,内容包括代码规范、命名、逻辑、功能等等,一般至少要经过 2 轮 Review 过程。
在 Review Approve 后由 maintainer 进行 merge 到指定分支。developer 未经 maintainer 允许不允许merge 代码,也就是说除了已经被许可情况下,除了 maintainer 其他人谁也不允许点 merge 代码,其merge 按钮大概位置在每个 PR 页面的最下面,图示内容如下:
上图最下面 Merge pull request
即为合并操作,灰色表示本身就没有合并权限,如果是绿色则表示有合并权限。
如本文第 1 部分所述,GitHub 是基于 Git 的版本库托管网站。那么 Git 究竟是什么,又如何使用呢? 在这部分中,我们将介绍这一强大工具的基本概念和用法。
简单地说,Git 是一个版本控制系统,用来记录、管理和恢复对源代码(或任意类应的文件)所做的各种修改。与其他版本管理工具相比,Git 具有以下特点:
快速:Git 的大部分操作都在本地进行,而不需要访问远程数据,因而非常快速。
安全:Git 中所有数据记录在存储前都会计算校验和,并用校验和来引用。并且,Git 通常的操作只会向数据库中添加数据,而不会删除数据。因此,使用 Git 可以完整地记录版本信息,避免传输过程中的信息缺失或文件损坏,并且通常不会执行不可逆的操作。
完全分布式:即各个客户端都保留完整的代码仓库历史记录,并可以指定和若干不同的远端代码仓库进行交互(如 GitHub 上的多个远程仓库)。
强力支持非线性开发: Git 中的分支(branch)管理功能,可以支持大量开发者并行开发同一个软件项目。
现在请注意,如果你希望后面的学习更顺利,请记住下面 Git 中的这三个区,工作区(Working Directory)、暂存区(Staging Area) 以及 Git 仓库区(.git directory)。
工作区(Working Directory)
当我们在本地创建一个 Git 项目,或者从 GitHub 上 clone 代码到本地后,项目所在的这个目录就是“工作区”。顾名思义,这里就是我们对项目文件进行编辑和使用的地方。如图 6 所示:
暂存区(Stage/Index)
暂存区是 Git 中独有的一个概念,它其实是位于.git
目录中的一个索引文件,记录了下一次提交(commit)时将要存入仓库区的文件列表信息。当我们在工作区进行了一些修改后,并不直接将其提交进版本仓库,而是先通过git add
指令将改动放入暂存区(即记录将要提交的文件索引)。在若干次git add
后,再通过git commit
指令将暂存区的修改提交到仓库区。这样的设计,让使用者可以更灵活地选择每次提交的内容。这里涉及到的 git 指令,将在 2.4 部分详细介绍。
仓库区 / 本地仓库(Repository)
在项目目录中,会发现一个.git
隐藏目录,它不是软件代码的一部分,也不属于工作区,而是 Git 的版本仓库。简单来说,这个仓库区才是一个 Git 项目的“本体”,其中包含了所有历史版本的完整信息。而工作区正是对某个特定版本提取出来的内容。我们通常所说的仓库区,除了指.git
目录这个物理位置,也指一种逻辑状态,即某个修改“记录进 Git 版本仓库”。
基本的 Git 工作流程如下:
在工作区中修改文件。
git add
将你想要下次提交的更改选择性地暂存,这样只会将更改的部分添加到暂存区。
git commit
提交更新,找到暂存区的文件,并将暂存永久性存储到 Git 目录。
如果 Git 目录中保存着特定版本的文件,就属于 已提交 状态。 如果文件已修改并放入暂存区,就属于 已暂存 状态。 如果自上次检出后,作了修改但还没有放到暂存区域,就是 已修改 状态。
结合 2.2.1 中工作区、暂存区和仓库区的概念,我们接下来介绍 Git 工作区中的文件状态:
请记住,工作目录下的每一个文件都不外乎这两种状态:已跟踪 或 未跟踪。 已跟踪的文件是指那些被纳入了版本控制的文件,在上一次快照中有它们的记录,在工作一段时间后, 它们的状态可能是未修改(Unmodified),已修改(Modified)或已放入暂存区(Staged)。简而言之,已跟踪的文件就是 Git 已经知道的文件。
图7概括了文件状态的变化周期。
工作目录中除已跟踪文件外的其它所有文件都属于未跟踪文件,它们既不存在于上次快照的记录中,也没有被放入暂存区。 初次克隆某个仓库的时候,工作目录中的所有文件都属于已跟踪文件,并处于未修改状态,因为 Git 刚刚检出了它们, 而你尚未编辑过它们。
编辑过某些文件之后,由于自上次提交后你对它们做了修改,Git 将它们标记为已修改文件。 在工作时,你可以选择性地将这些修改过的文件放入暂存区,然后提交所有已暂存的修改,如此反复。
有人把 Git 的分支模型称为它的“必杀技特性”,也正因为这一特性,使得 Git 从众多版本控制系统中脱颖而出。 为何 Git 的分支模型如此出众呢? Git 处理分支的方式可谓是难以置信的轻量,创建新分支这一操作几乎能在瞬间完成,并且在不同分支之间的切换操作也是一样便捷。 与许多其它版本控制系统不同,Git 鼓励在工作流程中频繁地使用分支与合并,哪怕一天之内进行许多次。 理解和精通这一特性,你便会意识到 Git 是如此的强大而又独特,并且从此真正改变你的开发方式。
在这部分,我们介绍一些常用的 Git 指令。Git 的指令通常有着强大而丰富的功能,可以灵活地设置众多参数。这里只给出每个指令最基础和常见的用法,更多用法可以在 Git 官方教程(Git - 设置与配置)中学习,或者在命令行中查看对应指令的文档,如:
git config
上面指令中的 --global
指定了配置的层级。Git 中支持3个层级的配置,分别是 system(系统)、global(全局)和 local(本地),靠后的层级会覆盖掉靠前层级的配置。git config
指令会将配置写入特定的文件中,因而我们也可以通过手动编辑配置文件的方式对 Git 进行配置。在 Linux 系统中,这文件的路径通常是:
system:/etc/gitconfig(需要 sudo 权限,不推荐)git
global: ~/.gitconfig (或者 ~/.config/git/config)
local:正在操作的仓库的 .git 路径下的配置文件, 即 [REPO_PATH]/.git/config
git init
git init
指令用于在一个本地的路径下创建和初始化 Git 仓库。该指令会在项目目录下创建.git
目录,但不会自动关联远程仓库(如 GitHub 上的远程仓库)。
git clone
git clone
指令用将远程代码仓库下载到本地,以创建本地仓库。这是实际开发中更常用的一种创建本地仓库的方式。
git clone
中使用的远程代码库 URL,可以在其 GitHub 首页看到(如图8),其他平台如 GitLab 也类似。
git status
git status
指令用于查看当前本地仓库的状态,包括所在的分支、文件状态等。
在上面的例子中,可以看到返回的信息包括:
本地处于 v2.0_dataset 分支
有一个新增文件new_file.py
处于未被追踪的状态(关于文件状态,可回顾 2.2.2 文件状态)
git diff
git diff
指令用来比较仓库中的文件在修改先后的差异。常见的用法有
git add
git add
指令用来将工作区的修改添加到暂存区。可参考本文 2.2 中关于工作区、暂存区和文件状态的介绍。
git commit
git commit
指令用来将所有已添加到暂存区的修改生成一个提交对象,保存进仓库区,并将当前分支的指针(HEAD)移动到该次提交上。
git branch
git branch
指令用来进行分支管理操作,如列出有的分支、创建新分支、删除分支及重命名分支等。
git checkout
git checkout
指令主要用于切换分支。
此外,git checkout
指令还用于丢弃工作区中的修改(未提交到暂存区),或从其他提交对象(如其他分支,或特定commit等)中提取文件到当前工作区。
由于git checkout
的功能较多且分散,在较新版本的 Git 中引入了git switch
指令,来代替git checkout
的分支切换功能,可以参考 Git - git-switch Documentation.
git merge
git merge
指令用来合并分支。关于分支合并的说明,可以参考 2.2.3 部分。
git remote
git remote
指令用来管理本地仓库与远程仓库的关联。
在上面的例子中,可以看到已经关联的远程仓库有2个,命名分别是 origin(URL:https://github.com/ly015/mmpose.git)和 upstream(URL:https://github.com/open-mmlab/mmpose.git)。git remote
除了用于查看关联仓库,还用于进行关联的管理:
git fetch
git fetch
指令用于与一个远程的仓库交互,并且将远程仓库中有而本地仓库中没有的所有信息拉取下来,然后存储在本地仓库中。这个指令不会修改本地工作区、暂存区的内容,也不会修改本地分支的信息。例如,从远程仓库 origin 拉取的 master 分支会在本地存储为 origin/master, 而不会与原有的本地分支 master 相混淆。
git push
git push
指令用于将本地仓库的分支推送到远程仓库,即比较本地分支与远程仓库中对应关联的分支,并将差异同步到远程仓库。该指令需要有远程仓库的写权限,因此这通常是需要验证的。
git pull
git pull
指令用于拉取远程分支到本地,即比较本地分支与远程仓库中对应关联的分支,并将差异合入本地分支。通常git pull
指令可以看作是git fetch
+git merge
。如同一般的分支合并,如果远程关联分支和本地仓库中的提交历史存在冲突,则需要解决冲突后才能继续合并。
git mv
git mv
指令用于重命名或移动文件、目录或链接。与mv
不同的是,git mv
会自动将这一修改添加到暂存区。
git rm
git rm
指令用于从工作区或暂存区移除文件。git rm
会自动将这一修改添加到暂存区(即将该文件从已追踪的文件清单中移除),类似git mv
。如果我们只想把文件从暂存区移除(即让 Git 不再跟踪该文件),但仍保留在工作区,则可以使用--cached
选项。
git stash
git stash
指令用来临时保存一些还没有提交的工作,以便在分支上不需要提交未完成工作就可以清理工作目录。例如,我们正在dev分支编辑run.py
文件,临时需要切换到master分支跑一个测试任务。在切换分支前,需要清理工作区和暂存区,但我们并不希望将未完成的修改提交到仓库,此时可以用git stash
指令临时贮藏当前的工作,并在完成临时任务后回复这些工作:
git stash
会将贮藏的修改存放在一个栈上,因此可以多次使用git stash
贮藏修改。栈的状态可以用git stash list
查看,例如。
git stash
的常见用法汇总如下
git reset
git reset
指令用来执行撤销操作。例如,它可以将当前分支的 HEAD 指针移动到指定的提交上,从而实现撤销到该提交之后的所有提交。根据指令参数,这种撤销可以是只移动 HEAD 指针,而不改变工作区内容(被撤销的提交内容会保留在工作区,变成已修改,未提交的状态);也可以直接修改工作区的内容。此外,git reset
还可以用来撤销提交到暂存区的内容。一些例子如下:
由于git reset --hard
指令会直接修改工作区,有可能会导致数据丢失(Git 会真正销毁数据的仅有的几个操作之一),所以在这样使用前一定要确保自己知道其后果。
git cherry-pick
git cherry-pick
指令用来获得在单个提交中引入的变更,然后尝试将作为一c个新的提交引入到你当前分支上。 这通常用来从另一个分支合并单个或几个提交,而不是整个分支。例如,当前有 main 和 dev 两个分支,提交历史如下:
如果我们只希望将 dev 分支中的 C3 提交合并到 main 分支中,可以使用以下方式:
此时,提交历史会变为:
git cherry-pick
指令的常见用法如下:
git revert
git revert
指令用来撤销之前的提交。与git reset
指令移动HEAD
指针的方式,git revert
会创建一个与之前提交的变更完全相反的新提交(本质上是一个特殊的 cherry-pick),从而实现撤销:
下图中可以更直观地对比git reset
和git revert
的区别:
git rebase
git rebase
指令采用“变基”的方式整合不同分支的修改。回顾之前介绍的git merge
指令,其工作方式是在当前分支创建一个新提交,在其中合并目标分支和当前分支的修改。与之不同,git rebase
会首先寻找当前分支和目标分支的“分叉点”,并尝试将当前分支在“分叉点“之后的部分”转移“到目标分支的末尾。下图中对比了使用git rebase
和git merge
合并分支的不同:
$ git checkout main && git merge dev
$ git checkout dev && git rebase main
在上图中可以看到,git rebase
和git merge
都可以实现分支的整合,但git rebase
可以避免提交历史中出现分叉,保持较为清晰的提交历史。因此在实际项目开发中,通常会在将开发分支合入主分支前,先将其 rebase 到最新的主分支(在 3.1 部分中可以看到这一步)。
git rebase
指令的常见用法如下:
与 merge 类似,在 rebase 的过程中也可能会遇到冲突,需要手动解决。解决冲突的过程也与 merge 中相似(使用--continue
,--abort
等标志)。需要注意的是,由于 rebase 的工作方式是先切换到目标分支,然后依次添加当前分支中的提交,因此在处理冲突时,HEAD
指向的是目标分支,这一点与 merge 稍有不同(但在实际开发中,通常 merge 时把主分支作为当前分支,而 rebase 时把主分支作为目标分支,所以 HEAD
都会指向主分支 master(或 main)分支,所以不至于混淆)。
在这一部分,我们以在 MMPose 算法库中完成一个功能开发的过程为例,介绍基于 Git 的常规开发流程。
Fork 源代码仓库
在 MMPose 的 Github 页面(https://github.com/open-mmlab/mmpose)点击 Fork 按键,在自己的 Github 帐号下创建一个副本仓库(如:https://github.com/ly015/mmpose)。
创建本地仓库并关联远程仓库
到本地,并配制远程仓库地址。通常,我们会关联两个远程仓库: origin 为自己 fork 的副本仓库,upstream 为官方仓库
配制 pre-commit Hook
参考 Contributing to OpenMMLab 中的说明,在本地仓库配制 Pre-commit Hook。 Pre-commit Hook 是 Git 支持的钩子函数的一种,通常用于在提交修改(Commit)前自动完成代码风格检查等工作。以上准备工作只是第一次提 PR 之前需要进行,后续开发工作,在每个 PR 开发中都需要做。
创建开发分支
通常我们会为每个开发项创建一个独立的分支,以避免耦合带来的混乱。分支的命名最好能简洁地体现开发项的内容。
完成功能开发
在开发过程中,建议多使用git add
和git commit
及时提交修改。
将本地修改推送到远程仓库
在完成开发并将本地修改都提交到本地仓库后,使用git push
指令将本地修改同步到远程仓库。注意,我们通常不会将修改推送到官方远程仓库,而是推送到自己 fork 的远程仓库,再通过向官方仓库提 PR 的方式向其贡献代码。(这样不需要官方远程仓库的推送权限,并且有利于维护官方仓库的内容整洁)
开始创建 PR
当我们将本地的修改推送到自己的远程仓库后,需要向官方仓库提交一个 PR,要求官方仓库将这一修改合入到主分支(或其他指定分支)。创建 PR 可以在浏览器中操作。如果是近期推送的修改,在官方仓库或自己仓库的页面上都会出现提示,点击“Compare & pull request” 即可(如下图红色部分)。如果没有看到这个提示,也可以在自己仓库的页面上切换到开发分支,点击“Contribute”→“Open pull request”即可(如下图蓝色部分)。
提交 PR
完成上一步后,会进入创建 PR 页面,如下图所示。这里我们需要填写 PR 相关的信息。红色部分是分支信息,默认将当前分支合入官方主分支,一般不用修改。绿色部分是 PR title 和 PR message,用来说明这个 PR 的内容。这部分非常重要,必须认真填写。PR title 一般格式为 [Prefix] Short description of the pull request (Suffix)Prefix:
新增功能 [Feature]
修 bug [Fix]
文档相关 [Docs]
开发中 [WIP] ( work in process 暂时不会被review)
PR message 的主要修改内容,结果,以及对其他部分的影响,通常我们的代码库会准备 PR 模板,只需要按照模板填入对应的内容即可。另外, PR message 可以关联相关的 issue 和 PR,通过 fixes/resolves issue ID 可以在 PR merge 的时候 close issue。蓝色部分是其他信息,在这里可以指定 reviewer 来 review 这个 PR。完成所有信息后,点击下方的“Create pull request”即可完成 PR 提交。
查看 CI 状态
PR 创建后会自动触发 CI(OpenMMLab 的算法库都基于 GitHub Actions 配置了 CI),完成代码格式、单元测试等检查工作。在 PR 页面可以查看 CI 的运行状态和结果,如下图所示。如果 CI 中有失败的项,可以点击“Details” 查看详细情况,修改后推送到自己的仓库,PR 也会随之更新。
PR 提交后,会由 reviewer 进行 review 并提出意见建议,再由作者修改并更新。这样的迭代通常会进行几轮,直到大家均认为该 PR 已经可以合入主分支后,由该项目的 maintainer 来进行合入操作。关于 Review 代码的方法和技巧,后续详细介绍,这里我们只简单介绍相关的 Github 操作。 如下图所示,在 PR 页面的 “Files changed” 标签页,可以看到该 PR 的所有修改。将鼠标指向某一行,会在行号出显示 “+” 按钮,点击即可输入 review 意见和建议。作者或其他 reviewer 也可以进行回复讨论。在 “Conversation” 标签页会显示这些对话内容。
PR 开发和 review 过程中如果 master 分支有相关的更新,需要及时 rebase,更新本地代码,以免和 master 分支有冲突。
gitignore
文件用来设置不希望 Git 进行跟踪和管理的文件路径,例如编译或运行时生成的临时文件、较大的数据文件等。gitignore
文件是一个文本文件,通常路径是项目目录下的.gitignore
,写法规范如下:
每一行指定一个要忽略的文件路径,除了空行和以 “#” 开头的注释行
文件路径可以使用 glob 模式匹配的写法
路径以 “/” 结尾表示要忽略的是一个目录
在文件路径前加入取反符号 “!” ,表示这是一个不应忽略的特例
以下是一个例子:
通常项目里已经提供了gitignore
文件,必要时可以对其进行修改。gitignore
文件本身也属于项目内容,需要添加修改 (git add
)、提交修改(git commit
)、开 PR 合入等。
在合并分支时,经常需要手动解决冲突,这里可以选用一些可视化的工具帮助我们提高效率,例如 VS Code 就有较好的可视化功能,可以协助解决冲突。除此之外我们也可以借助第三方工具,这里我们以 meld 这个工具为例,介绍 Git 中 mergetool 的配置和使用。首先需要在本地安装 meld,可以参考其官网的文档。安装完成后,可以在 Git 配置文件(见 2.3.1onfig)中写入如下内容:
完成这些配制后,当需要解决冲突时,Git 会自动启动 meld,界面如下图所示。可以看到界面分成三列,对应与上面设置中的L11,分别是本地分支内容、合并后内容和目标(远程)分支内容。在这个界面中可以清晰对比两个分支的不同,还可以三列之间的箭头状按钮选快速选择内容添加到对应文件中。这里要注意,LOCAL 和 REMOTE 对应的分支,在 merge 和 rebase 两种情况下是不同的,这在 git-rebase 说明 中做过介绍。
由于我们同时配制了 "difftool",所以在运行git diff
指令时,也会启动 meld,用左右两列来对比文件差异。
在 3.1.1 中我们提到,Git 提供了 pre-commit hook 机制来帮助开发者自动完成格式检查等工作。一些常用的格式格式检查三方库,都提供了可以直接调用的 hook,可以在.pre-commit-config.yaml
文件进行配制。除此之外,我们也可以在项目中添加本地代码脚本作为 pre-commit hook 以实现特定功能。以 MMPose 中自动给代码文件添加版权信息的功能为例。我们首先添加了实现该功能的脚本.dev_scripts/github/update_copyright.py
,该脚本以一个文件列表作为输入参数,检查并添加版权信息到这些文件中。如果所有输入文件均已包含版权信息而未作修改,则正常返回 0, 否则返回 1。如果该 hook 在提交时对代码做了修改,就会中止这次提交,待开发者添加这些修改后重新提交。最后,我们将该脚本添加到.pre-commit-config.yaml
中即可:
关于 Git hook 的详细介绍可以参考 Git - Git Hooks
与远程仓库通信可以使用 https 或 ssh 两种方式。在 2.3.6 中主要基于 https 方式进行了介绍,这里我们简单介绍 ssh 方式。在 clone 远程仓库到本地时,可以选择 ssh 地址,如下图所示:
使用 ssh 方式的仓库同步操作与使用 https 方式没有区别(如git clone
,git pull
,git push
等)。ssh 方式需要配置公钥-私钥对,并将公钥提交到 GitHub 上,用以在通信时进行身份验证,不需要每次输入账号密码。配置 ssh 秘钥的具体的做法可以参考这里的文档:About SSH - GitHub Docs
如何修改 commit 但不产生新的提交
请参考 2.3.4 中 git commit --amend
的用法。请注意,严格来说这个操作是用一个新的提交替换了原来的提交。
如何撤销 commit 但仍需保留修改
请参考 2.4 中 git reset --mixed
或git reset --soft
的用法。
如何合并多个 commit
请参考 2.4 中 git rebase -i HEAD~n
的用法,这里举一个简单的例子。假如当前分支历史中有3个提交,如下:
使用git rebase
对后2个提交进行合并:
此时会出现交互界面,如下图左。可以看到相关的 commit 被逐行列出,并且每个 commit 前面有一个指令(pick),这个指令是可以按照需求修改的,所有可选项都在下方的注释中有说明。例如,我们现在希望合并2个 commit, 则可以将第一个 7d97740 的指令设置为 reword,将第二个 7eea97e 的指令设置为 fixup,如下图中,然后保存退出。由于第一个指令是 reword,接下来会跳出重新输入 commit message 的界面,我们将原本的 “Add b.txt” 改成 “ Add b.txt and c.txt ”, 并保存退出,如下图右。
此时我们再查看分支提交历史,会发现之前的 2 个 commit 已经被合并:
如何删除 add 或者 commit 之后的大文件
在开发中会遇到不慎将一些文件错误添加到 Git 仓库中的情况(比如未及时更新gitignore
文件就可能会导致这种情况)。尤其是以一些大文件(如数据文件或临时文件),如果被错误包含在项目仓库中,会使仓库变得很大而严重影响和远程仓库通信的效率(如git clone
,git push
,git pull
等操作会变得很慢)。如果只是通过git add
添加到了暂存区,只需要用前面介绍过的git rm --cached
或git reset
清理暂存区即可。但如果已经通过git commit
提交到了仓库,情况会变得比较棘手,因为 Git 仓库会记录完整的提交历史,即使撤销这次提交,或者在后续提交中移除这些错误添加的文件,它们依旧会存在与仓库中。这种情况也不是无法解决的,这篇文档 Git - 维护与数据恢复 中给出了一个清除历史提交中错误引入的大文件的完整例子,大家可以仔细学习并 follow 其步骤。
如何往源算法库中其他人提的 PR 中提交代码
在创建 PR 时,通常会默认勾选 “Allow edits and access to secrets by maintainers”,这将允许其他具有官方仓库 Write 权限的用户提交修改到这个 PR 对应的分支。这在多人合作开发时会用到。当需要向别人的 PR 中提交代码时,需要将作者的远程仓库地址添加到 remote 中([参考 2.3.6 git remote]),然后拉取 PR 对应的分支。在本地完成修改后,再推送到作者的远程仓库即可。在将 PR 拉取到本地时,除了从作者的远程仓库拉取,还可以用以下简单的指令:
如何手动运行 CI
可以在 PR 页面的 Checks 标签页,点击右上角的 “Re-run all jobs” 按键,即可重新运行 CI。
提交一个 PR
向源算法库中其他人提的 PR 中提交代码
解决分支冲突
[1] Git 官方教程:https://git-scm.com/book/zh/v2
[2] https://learngitbranching.js.org/
[3] https://git-school.github.io/visualizing-git/
[4] 廖雪峰 Git 教程:https://www.liaoxuefeng.com/wiki/896043488029600