GitHub 协作开发指南
开源项目参与流程
在开源项目或团队协作开发中,使用 GitHub 是一个常见的实践。以下是一套从 Fork 项目到提交 Pull Request(PR)的完整流程,帮助你高效管理代码并与团队或社区协作。
1. Fork 项目仓库
进入项目的 GitHub 页面,点击右上角的 Fork 按钮,将该项目仓库复制到自己的 GitHub 账户下。这个 Fork 的副本是你对项目的个人副本,你可以在上面进行改动而不会影响原项目。
2. Clone 项目到本地
在你的 GitHub 账户中找到刚刚 Fork 的仓库,复制仓库的链接。在本地执行以下命令将仓库克隆到你的电脑:
1 | git clone <your-forked-repo-url> |
3. 添加上游仓库 (Upstream)
当你 Fork 了一个仓库后,你的代码会与原项目(官方项目)脱离直接联系。为了能够同步原项目的最新更改,你需要将原项目添加为“上游仓库”。进入克隆的项目文件夹,运行以下命令:
1 | git remote add upstream <original-repo-url> |
然后验证 git remote -v
,出现类似下面的内容:
1 | origin https://github.com/your-username/your-repo.git (fetch) |
4. 创建新分支
在协作开发中,不要在 main 分支上直接进行改动。每个功能或修复都应该单独创建一个分支,便于管理和代码评审。
1 | git checkout -b feature-branch-name |
命名规范:
- 功能分支:add-feature-name,例如 add-login-feature
- 修复分支:fix-issue-name,例如 fix-login-error
- 实验分支:test-experiment-name,例如 test-algorithm-optimization
5. 同步上游仓库的更新
在开始改动之前,确保你的代码基于上游的最新版本,避免提交包含旧代码或引发冲突。切换到你的分支,然后拉取上游项目的最新更新。
1 | git checkout <branch> |
随后有2个选择。使用 git merge upstream/main
,将上游分支的改动合并到当前分支,生成一个额外的 merge commit,用于记录合并过程。如果发生冲突,所有冲突会集中到一次解决,并记录在 merge commit 中。提交历史保留分叉点,便于追踪合并记录。
1 | A --- B --- C (upstream/main) |
快速同步,操作简单。适合开发过程中本地更新,但不适合提交 PR。
使用 git rebase upstream/main
,会将你的分支的提交(如 D 和 E)“摘下”,然后重新应用到 upstream/main 的最新提交后面,生成新的提交。不会产生 merge commit,历史保持线性,最终结果类似: A --- B --- C --- D' --- E'
。
推荐用于 PR,因为:
- 提交历史更整洁:不引入额外的 merge commit,适合开源贡献。
- 冲突处理细化:每个提交都会单独处理与上游代码的冲突。
- 重复操作安全性:多次 rebase 不会产生混乱,每次都会让你的提交位于上游分支的最新代码之后。
多次执行 git rebase upstream/main
会让你所有的提交都重新排列在官方分支的最新提交之后。比如说下面的操作得到的结果是 A --- B --- E --- C'' --- D'' --- F'
。这是因为你的本地提交会被摘下,然后调整顺序后再附加到末尾。(这里的字母表示基于共同起点,新增的commit)。
- 第一次 Rebase:C 和 D 提交。上游分支 (upstream/main): A — B,你的分支 (feature): C — D。本地结果是 A — B — C’ — D’ (feature)。
- 官方增加 E 后,第二次 Rebase:F的提交。上游分支 (upstream/main): A — B — E,你的分支 (feature): A — B — C’ — D’ — F。本地结果是 A — B — E — C’’ — D’’ — F’。
一般做法:在提交 PR 前,始终建议使用 git rebase upstream/main
来保持历史的整洁和线性化。 如果官方分支频繁更新,定期执行 rebase,确保你的分支与官方最新代码一致。
6. 进行改动并提交
按照项目的规范进行代码更改、添加功能或修复 bug。完成后,将改动添加并提交:
1 | git commit -m "清晰的提交信息" |
示例提交信息:
- 修复 (Fix):修复 Bug 或问题,例如
Fix login error in login page
- 添加 (Add):添加新功能或模块,例如
Add user authentication module
- 修改 (Update):改进已有功能或文档,例如
Update README with setup instructions
- 重构 (Refactor):代码结构调整,例如
Refactor authentication module for clarity
- 删除 (Remove):删除不需要的代码或文件,例如
Remove deprecated API call
如果项目涉及到的文件比较多,那么可以加入前缀,比如在哪个包内做的修改。
7. 将更改推送到 Fork 的仓库
推送到远程分支:
1 | git push origin feature-branch-name |
8. 创建 Pull Request (PR)
- 回到你的 GitHub 仓库页面,会看到一个 Compare & Pull Request 的按钮,点击它。
- 在 Pull Request 页面中,填写你的改动描述,包括做了哪些更改、为什么进行这些更改等。
- 提交 Pull Request,等待项目维护者的审核。
9. 与项目维护者沟通
项目维护者可能会对你的 Pull Request 提出建议或要求你修改。根据反馈进行调整,并再次提交到你的分支。更新 PR 时不需要重新创建 PR,直接推送到同一个分支即可 git push origin <branch-name>
。
提交的最佳实践
在协作开发中,良好的提交习惯可以大幅提升团队效率,同时让代码历史更加清晰,便于代码审查和问题追踪。
提交时机
一般来说,每个提交应当专注于一个小而清晰的更改,以下情况是理想的提交时机:
- 完成一个小的功能或逻辑:如实现一个函数、模块或特定的功能。
- 修复一个 Bug:针对特定问题的修复应单独提交,以便追踪问题。
- 文档更新:更新 README、注释或其他文档内容时,单独提交。
- 代码重构:调整代码结构但不影响功能时,例如提高可读性或优化性能。
- 测试用例更新:添加或修改测试用例以验证功能的正确性。
避免以下频繁提交的情况:
- 过于细碎的改动:如仅修改一行代码,除非是重要的修复。
- 未经测试的代码:在提交之前,应尽量保证代码稳定,或明确标记为“临时提交”以避免误导。
提交原则
保持提交的原子性
一个提交完成一项具体的任务:避免将多个不相关的改动混合在一个提交中,避免“万能提交”。
示例:
- 错误示例:将 Bug 修复和功能开发放在一个提交中。
- 正确示例:先提交
Fix login error
,再提交Add JWT authentication
。
定期同步上游代码
如果官方分支有更新, 这样可以减少冲突,并确保提交基于最新代码开发。使用以下命令将最新代码与本地分支同步:
1 | git fetch upstream |
在本地充分测试
提交前应在本地运行测试,确保改动不会破坏现有功能。如果改动无法完全验证,应明确标记为实验性或草稿提交。
避免提交多余文件
提交前检查是否有无关的临时文件或调试代码。使用 .gitignore 忽略不需要的文件,避免意外提交。
描述清晰的提交信息
提交信息应简明扼要,清楚地描述改动内容和原因。以动词开头,如 Fix、Add、Update 等,便于他人快速理解。
清晰的提交信息有助于团队快速了解改动内容。推荐以下格式:
1 | <类型>: <简短描述> |
示例提交信息:
- 功能添加。
1 | Add JWT authentication for login module |
- Bug 修复:
1 | Fix null pointer exception in login module |
- 文档更新:
1 | Update README with setup instructions |
总结
- 保持提交小而清晰:每个提交完成一个明确任务,避免提交过大或内容混杂。
- 定期同步代码:在官方分支更新后及时 rebase,减少冲突。
- 提交前充分测试:验证改动稳定性,确保代码不会破坏现有功能。
- 清晰描述提交信息:让每个提交的目的和内容一目了然,方便协作者审查。
- 通过遵循这些最佳实践,你的提交记录将更加有条理,协作者能够更高效地审查和管理代码,同时增强整个代码库的可维护性。
内部项目开发
当团队成员对主分支(main 或 develop)有直接提交权限时,需要特别注意代码质量、提交规范以及协作流程,避免代码冲突或影响项目稳定性。
分支策略要清晰:
主分支 (main):仅存放稳定的代码版本。应通过代码评审或 CI/CD 流程自动合并,避免直接提交。
开发分支 (develop):用于集成团队的开发代码。可以直接提交,但要确保不影响其他人开发。
功能分支 (feature-branch):每个功能、任务或 Bug 修复应该单独创建一个分支。
- 功能的添加、Bug 修复等依然推荐单独创建分支。完成后将分支合并到 develop 或通过 Pull Request (PR) 合并到目标分支,便于管理。
- 功能模块化管理,使用功能分支,确保每个功能改动独立,便于后续追溯和回滚。同一时间尽量不要多人修改同一模块,开发团队要多沟通,分配明确的开发任务,避免冲突。
- 实现最小化的测试函数。每个新功能或修复提交时,必须附带最小化的测试函数,确保业务逻辑正确。检查 1. 新增的核心功能(如 API 请求返回结果是否正确);2. 对现有功能的影响(回归测试)。并且比避免测试函数失败的代码提交。
Git 常用命令
怎么提交
git commit -m:仅提交已在暂存区的文件,不影响未暂存的文件。
git commit -am:将所有已跟踪文件的改动添加到暂存区并提交,但不包含新文件。
合并 Commit
场景一:多个commit合并
-
找到目标提交的哈希值:首先,使用以下命令查看提交历史,找到要保留的提交(即你希望将后续更改合并到的提交)。
git log --oneline
-
重置到指定提交:使用 git reset 将分支回退到指定提交。这样会取消指定提交后的所有提交,但保留文件改动在工作区中。
git reset --soft <commit-hash>
其中<commit-hash>
是目标提交的哈希值,例如 abc1234。
–soft 参数确保保留所有更改在暂存区,以便进行下一步的合并提交。hard 就会删除,请不要使用! -
重新提交所有更改:现在,所有更改都在暂存区中,你可以将它们合并为一个新的提交。
git commit -m "将所有更改合并为一次提交"
场景二:将未提交的更改合并到上一次提交。
如果在最近的提交之后还有未提交的更改,并希望将它们合并到该提交中,可以使用 git commit --amend。
- 暂存未提交的更改:首先,将所有未提交的更改添加到暂存区。
git add .
- 使用 --amend 更新上一个提交:将这些暂存的更改添加到上一个提交中。
git commit --amend -m "更新上一个提交的信息"
- 这将打开编辑器,允许你修改上一次提交的信息。如果你希望保留原始信息,只需保存并关闭编辑器即可。
要复原某个文件或者上次提交
场景一:将文件从暂存区移除,但保留在工作区中,改动还存在,但是 git 不记录。git reset <filename>
场景二:直接复原文件到上次提交。git checkout -- <filename>
场景三:直接复原整个项目到上次提交。git reset --hard HEAD
合并修改,建议在 IDE 里进行,很方便。更多的遇到的时候再学习吧。
使用Git 模块
Git 子模块用于管理嵌套的仓库,通常用于依赖项目或共享代码。子模块在主仓库中的文件夹表现为普通目录,但其内容实际上是独立的 Git 仓库。
子模块在 Git 中通过一些特殊文件和目录进行管理。这些文件记录了子模块的配置信息、关联的远程仓库、当前版本等。
.gitmodules
是一个配置文件,用于描述主仓库中的子模块信息,包含子模块的路径、远程仓库 URL 等。子模块在 .gitmodules 文件中配置后,初始化时会将其信息写入 .git/config
。
当子模块被初始化后,Git 会在主仓库的 .git/modules
目录下为每个子模块创建一个子目录。这些目录存储子模块的元数据,包括配置、对象和引用信息。
- 克隆包含子模块的仓库
git clone --recursive <repository-url>
- 添加子模块
git submodule add <submodule-repo-url> <path>
- 更新子模块:
git submodule update --remote
- 移除子模块
1 | git submodule deinit -f <path> |
一般用途:
- 依赖管理:如项目需要多个共享库(API、工具库)。
- 大型项目:将不同模块分成多个独立的子仓库,方便团队协作。
- 外部代码集成:如集成开源项目。
常见问题1:clone 时忘记使用 --recursive。可以通过这样补充
1 | git submodule update --init --recursive |
- 初始化子模块:如果子模块未被初始化(即 .git/modules 中没有子模块的相关信息),这个选项会初始化子模块并将其拉取到本地。
- 递归处理嵌套子模块:如果子模块本身还有嵌套的子模块,此选项会确保所有嵌套的子模块也被初始化并更新。
常见问题2:需要的版本不一致,拉取模块的指定版本。
1 | git submodule update --recursive |
常见问题3:更新模块到最新。
-
进入子模块目录:
cd submodule1
-
切换到远程分支并拉取最新代码:
1
2git checkout main
git pull origin main -
返回主仓库,并更新子模块的版本记录:
1
2
3cd ..
git add submodule1
git commit -m "Update submodule1 to the latest version"