Git 是分布式版本控制系统,GitHub 则在 Git 仓库之上提供代码托管、Issue、Pull Request、代码审查和自动化检查等协作能力。在不具备原仓库写权限的情况下,GitHub 上常见的贡献方式是:Fork 原仓库,在个人 Fork 中维护功能分支,再通过 Pull Request 向原仓库提交变更。

本文以一个完整的开源贡献场景为主线,介绍 Fork、Clone、远程仓库配置、上游同步、分支开发、提交整理、Push、Pull Request、代码审查和分支清理。主流程参考 GitHub 官方 Fork 与 Pull Request 文档;rebase 相关内容以 Git 官方《Pro Git》为依据。具体项目的 CONTRIBUTING.md 和维护者要求具有更高优先级。


0. Git 与 GitHub 远程协作命令速查

示例仓库:

1
2
3
原仓库:alice/task-board
个人 Fork:xiaoming/task-board
任务分支:fix/issue-128-empty-title

将示例中的账号、仓库和分支替换为实际名称。

0.1 Fork 项目

在原仓库页面执行:

1
Fork → Create a new fork

0.2 SSH 配置(每台计算机执行一次)

Linux、macOS 或 Git Bash:

1
2
3
4
5
git config --global user.name "Xiao Ming"
git config --global user.email "xiaoming@example.com"

ssh-keygen -t ed25519 -C "xiaoming@example.com"
cat ~/.ssh/id_ed25519.pub

Windows PowerShell:

1
2
3
4
5
git config --global user.name "Xiao Ming"
git config --global user.email "xiaoming@example.com"

ssh-keygen -t ed25519 -C "xiaoming@example.com"
Get-Content $HOME\.ssh\id_ed25519.pub

将公钥添加到:

1
GitHub → Settings → SSH and GPG keys → New SSH key

测试连接:

1
ssh -T git@github.com

0.3 首次克隆 Fork 并配置上游仓库

1
2
3
4
5
git clone git@github.com:xiaoming/task-board.git
cd task-board

git remote add upstream git@github.com:alice/task-board.git
git remote -v

预期远程仓库:

1
2
origin   → xiaoming/task-board
upstream → alice/task-board

0.4 每次开始新任务

同步 main

1
2
3
4
5
git status --short --branch
git switch main
git fetch upstream
git merge --ff-only upstream/main
git push origin main

创建任务分支:

1
git switch -c fix/issue-128-empty-title

修改、检查、测试并提交:

1
2
3
4
5
6
7
8
9
git status
git diff

# 修改代码
# 执行项目规定的测试

git add <file1> <file2>
git diff --staged
git commit -m "fix: reject tasks with an empty title"

首次 Push 前同步上游:

1
2
git fetch upstream
git rebase upstream/main

发生 rebase 冲突:

1
2
3
4
5
6
7
8
9
git status

# 修改冲突文件

git add <resolved-file>
git rebase --continue

# 放弃本次 rebase
git rebase --abort

推送功能分支:

1
git push -u origin fix/issue-128-empty-title

使用 GitHub CLI 创建 Pull Request:

1
2
gh auth login
gh pr create --repo alice/task-board --base main --head xiaoming:fix/issue-128-empty-title --fill

查看 Pull Request:

1
2
gh pr view --web
gh pr checks

0.5 Review 后继续修改

追加提交:

1
2
3
4
5
6
7
8
git switch fix/issue-128-empty-title

# 修改代码并执行测试

git add <file1> <file2>
git diff --staged
git commit -m "fix: address review comments"
git push

维护者要求先同步上游时:

1
2
3
git fetch upstream
git rebase upstream/main
git push --force-with-lease

--force-with-lease 仅用于自己独占、允许改写历史的功能分支。

0.6 Pull Request 合并后

1
2
3
4
5
6
7
8
9
10
git switch main
git fetch upstream
git merge --ff-only upstream/main
git push origin main

git branch -d fix/issue-128-empty-title
git push origin --delete fix/issue-128-empty-title

git fetch --prune origin
git fetch --prune upstream

0.7 常用状态检查

1
2
3
4
5
6
7
git status --short --branch
git branch --show-current
git branch -vv
git remote -v
git log --oneline --graph --decorate --all
git diff
git diff --staged

1. 协作场景与目标

假设 GitHub 上存在以下项目和 Issue,名称均为示例:

1
2
3
4
5
6
7
8
原作者:alice
原仓库:alice/task-board
默认分支:main
待处理问题:Issue #128,创建任务时允许提交空标题

贡献者:xiaoming
个人 Fork:xiaoming/task-board
功能分支:fix/issue-128-empty-title

本次协作需要完成以下工作:

  1. alice/task-board Fork 到 xiaoming 账号;
  2. 将个人 Fork 克隆到本地;
  3. 配置原仓库为上游远程仓库;
  4. 从最新的上游代码创建功能分支;
  5. 完成代码修改、测试和提交;
  6. 将功能分支推送到个人 Fork;
  7. alice/task-board:main 创建 Pull Request;
  8. 根据代码审查结果继续更新;
  9. Pull Request 合并后同步主分支并清理功能分支。

代码流向如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
alice/task-board(原仓库)

│ Fork

xiaoming/task-board(个人 Fork)

│ Clone

本地 task-board(功能分支开发)

│ Push

xiaoming/task-board:fix/issue-128-empty-title

│ Pull Request

alice/task-board:main

2. Git 远程协作模型

2.1 工作区、暂存区与本地仓库

本地修改在形成可共享提交之前,会经过工作区和暂存区:

1
工作区 ── git add ──> 暂存区 ── git commit ──> 本地仓库
  • 工作区:项目文件实际所在的目录;
  • 暂存区:下一次提交准备包含的变更集合;
  • 本地仓库:保存在 .git 目录中的提交历史和分支引用。

git add 只更新暂存区,git commit 只创建本地提交。只有执行 git push,本地提交才会被发送到远程仓库。

2.2 Git 与 GitHub

Git 负责版本管理,可以在离线环境中创建提交、切换分支和查看历史。GitHub 负责托管 Git 仓库,并提供 Pull Request、Issue、Review 和 Actions 等协作功能。

1
2
Git:版本控制
GitHub:仓库托管与协作平台

2.3 Fork 与 Clone

Fork 和 Clone 都会产生一份仓库副本,但发生位置和用途不同:

操作 发生位置 结果
Fork GitHub 服务器 个人账号下的远程仓库
Clone 本地计算机 包含项目文件和 Git 历史的本地仓库

Fork 不会在本地创建项目目录,Clone 也不会在 GitHub 账号中创建 Fork。本文采用“先 Fork,后 Clone 个人 Fork”的顺序。

2.4 分支与提交

分支是指向某次提交的可移动引用。创建新提交后,当前分支引用会向前移动。

1
2
3
main:       A---B---C
\
feature: D---E

main 通常承担稳定主线的职责。功能开发和问题修复应在独立分支中进行,使 Pull Request 只包含一个明确任务的变更。

2.5 originupstream

Git 允许为远程仓库设置本地名称。Fork 协作通常采用以下命名约定:

远程名 指向 主要用途
origin 个人 Fork:xiaoming/task-board 推送个人分支
upstream 原仓库:alice/task-board 获取项目最新提交

数据方向可以概括为:

1
2
从 upstream 获取更新
向 origin 推送变更

originupstream 只是约定俗成的名称,并非 GitHub 预定义的特殊仓库。


3. Git 环境与 GitHub 认证

3.1 Git 安装与身份配置

确认 Git 已安装:

1
git --version

配置提交者姓名和邮箱:

1
2
git config --global user.name "Xiao Ming"
git config --global user.email "xiaoming@example.com"

姓名和邮箱会写入提交历史。为了让 GitHub 正确关联提交,应使用 GitHub 账号中已经验证的邮箱,或者 GitHub 提供的 noreply 邮箱。

查看全局配置:

1
git config --global --list

如果特定仓库需要使用不同身份,可以在仓库目录中省略 --global

1
2
git config user.name "Work Name"
git config user.email "work@example.com"

3.2 SSH 认证

本文使用 SSH 地址访问 GitHub。先检查本机是否已有 SSH 密钥:

1
ls -al ~/.ssh

如果没有可用的 Ed25519 密钥,可以生成新密钥:

1
ssh-keygen -t ed25519 -C "xiaoming@example.com"

默认生成:

1
2
~/.ssh/id_ed25519       私钥
~/.ssh/id_ed25519.pub 公钥

私钥不应上传到仓库、网盘或通信工具。GitHub 账号只需要配置公钥。

显示并复制公钥:

1
cat ~/.ssh/id_ed25519.pub

进入 GitHub 的以下位置添加公钥:

1
头像 → Settings → SSH and GPG keys → New SSH key

完成后测试连接:

1
ssh -T git@github.com

首次连接时,应核对终端显示的主机指纹与 GitHub 官方公布的指纹。认证成功后会显示包含 GitHub 用户名和 successfully authenticated 的提示。由于 GitHub 不提供普通 Shell,ssh -T 即使认证成功也可能返回退出码 1,应以认证提示内容判断结果。

3.3 HTTPS 认证

HTTPS 远程地址的形式为:

1
https://github.com/xiaoming/task-board.git

HTTPS 可以使用 GitHub CLI、Git Credential Manager 或 Personal Access Token 完成认证。GitHub 的 Git 命令行认证不接受账号登录密码。

SSH 和 HTTPS 任选一种即可。后续示例统一使用 SSH 地址。


4. 贡献规范与仓库状态检查

4.1 项目贡献规范

开始开发前,应阅读原仓库中的以下文件:

1
2
3
4
README.md
CONTRIBUTING.md
CODE_OF_CONDUCT.md
.github/PULL_REQUEST_TEMPLATE.md

需要确认的内容包括:

  • 依赖安装和本地启动方式;
  • 分支命名和提交信息规范;
  • 必须执行的测试和格式检查;
  • Issue 的认领或讨论要求;
  • Pull Request 的目标分支;
  • 项目采用的 merge、rebase 或 squash 策略。

同时应阅读目标 Issue 的完整讨论,确认任务尚未由其他贡献者处理,并确保实现方案符合维护者预期。

4.2 仓库根目录

1
2
pwd
git rev-parse --show-toplevel

git rev-parse --show-toplevel 用于显示当前 Git 仓库的根目录。如果出现 not a git repository,说明终端当前不在 Git 仓库中。

4.3 当前分支

1
git branch --show-current

修改、提交、rebase 和 Push 之前均应确认当前分支,避免把功能提交写入 main 或其他无关分支。

4.4 工作区状态

1
git status

Changes not staged for commit 表示存在未暂存修改,Changes to be committed 表示暂存区中已有内容,Untracked files 表示存在尚未由 Git 跟踪的文件。

4.5 远程仓库配置

1
git remote -v

该命令用于核对 originupstream 的 Fetch、Push 地址。涉及远程操作时,应先确认目标地址与预期一致。


5. Fork、Clone 与远程仓库配置

5.1 Fork 原仓库

打开原仓库:

1
https://github.com/alice/task-board

点击页面右上角的 Fork,选择个人账号 xiaoming 作为 Owner,然后创建 Fork。完成后,个人账号下会出现:

1
https://github.com/xiaoming/task-board

此时仓库关系为:

1
2
alice/task-board       原仓库
xiaoming/task-board 个人 Fork

如果贡献者已经拥有原仓库写权限,项目可能要求直接在原仓库创建功能分支,此时不一定需要 Fork。本文讨论的是没有原仓库写权限的贡献模式。

5.2 Clone 个人 Fork

在个人 Fork 页面点击 Code,复制 SSH 地址:

1
2
git clone git@github.com:xiaoming/task-board.git
cd task-board

Clone 的目标应是个人 Fork,而不是原仓库:

1
2
个人 Fork:git@github.com:xiaoming/task-board.git
原仓库: git@github.com:alice/task-board.git

普通贡献者通常没有原仓库写权限。如果将原仓库克隆为 origin,后续 Push 可能因权限不足而失败。

Clone 完成后检查:

1
2
git status
git remote -v

预期远程配置为:

1
2
origin  git@github.com:xiaoming/task-board.git (fetch)
origin git@github.com:xiaoming/task-board.git (push)

git clone 会自动将被克隆的仓库命名为 origin

5.3 添加 upstream

将原仓库添加为 upstream

1
git remote add upstream git@github.com:alice/task-board.git

再次检查:

1
git remote -v

预期输出:

1
2
3
4
origin    git@github.com:xiaoming/task-board.git (fetch)
origin git@github.com:xiaoming/task-board.git (push)
upstream git@github.com:alice/task-board.git (fetch)
upstream git@github.com:alice/task-board.git (push)

如果出现 remote upstream already exists,应先查看已有地址:

1
git remote get-url upstream

地址错误时使用以下命令修改:

1
git remote set-url upstream git@github.com:alice/task-board.git

6. 默认分支同步

本地 main 应跟踪原仓库的 upstream/main,个人 Fork 的 origin/main 则保存同步后的远程副本。

6.1 同步流程

确认工作区状态:

1
git status

切换到本地 main

1
git switch main

获取原仓库的最新提交:

1
git fetch upstream

将本地 main 快进到 upstream/main

1
git merge --ff-only upstream/main

更新个人 Fork:

1
git push origin main

同步完成后的关系为:

1
upstream/main = 本地 main = origin/main

6.2 upstreamorigin 的同步职责

在 Fork 工作流中:

1
2
upstream/main   原项目的主分支
origin/main 个人 Fork 的主分支

git pull --ff-only origin main 只能从个人 Fork 拉取。如果个人 Fork 已经落后于原仓库,该命令无法获取原项目的最新提交。因此,原仓库 upstream 应作为项目更新来源。

6.3 merge --ff-only 的使用依据

GitHub 官方命令行同步文档使用 git merge upstream/main。本文在该流程中增加 --ff-only,原因是本地 main 被约定为 upstream/main 的干净镜像,不承载个人开发提交。

--ff-only 具有以下行为:

  • 本地 main 仅落后于上游时执行快进;
  • 本地 main 存在额外提交时停止;
  • 不创建新的合并提交;
  • 不改写已有提交。

这是针对当前分支模型增加的严格安全约束,并非所有项目必须使用的唯一同步命令。

6.4 默认分支与 rebase 的边界

本地 main 不应包含个人开发提交。如果在 main 上执行 git rebase upstream/main,Git 可能将误提交在 main 上的个人提交重新应用到最新上游之后,从而掩盖分支使用错误。

本文采用以下策略:

1
2
同步 main:git merge --ff-only upstream/main
更新个人功能分支:项目允许时使用 git rebase upstream/main

如果 merge --ff-only 返回 Not possible to fast-forward,说明本地 main 已与上游分叉。此时应先检查提交图:

1
git log --oneline --graph --decorate --all -20

在确认本地额外提交是否需要保留之前,不应执行 reset --hard 或强制 Push。


7. 功能分支创建

在最新的 main 上创建并切换到任务分支:

1
git switch -c fix/issue-128-empty-title

确认当前分支:

1
git branch --show-current

预期输出:

1
fix/issue-128-empty-title

常见分支前缀包括:

前缀 用途 示例
feature/ 新功能 feature/task-filter
fix/ 问题修复 fix/issue-128-empty-title
docs/ 文档更新 docs/setup-guide
refactor/ 代码重构 refactor/task-service
chore/ 工程维护 chore/update-dependencies

一个功能分支应对应一个明确任务,避免把无关功能、依赖更新和格式化修改放入同一个 Pull Request。


8. 本地开发与提交

假设本次修改涉及:

1
2
src/components/TaskForm.js
tests/TaskForm.test.js

8.1 变更检查与测试

开发过程中可以使用以下命令检查状态和差异:

1
2
git status
git diff
  • git status 显示修改、新增和暂存状态;
  • git diff 显示工作区中尚未暂存的代码差异。

完成修改后,按照项目文档执行测试和格式检查。例如:

1
2
npm test
npm run lint

具体命令应以项目的 README.mdCONTRIBUTING.md 和构建配置为准。

8.2 提交内容边界

提交中不应包含:

1
2
3
4
5
6
7
8
9
.env
访问令牌
数据库密码
SSH 私钥
云服务密钥
本地配置
依赖目录
临时日志
无关构建结果

.gitignore 只能阻止尚未跟踪的文件进入仓库。已经被 Git 跟踪的文件不会因为后来加入 .gitignore 而自动停止跟踪。

如果凭据曾经进入提交,应立即吊销并更换凭据。仅删除文件不能消除已经发生的泄露。

8.3 暂存与提交

明确暂存当前任务涉及的文件:

1
git add src/components/TaskForm.js tests/TaskForm.test.js

检查即将提交的内容:

1
git diff --staged

创建提交:

1
git commit -m "fix: reject tasks with an empty title"

验证结果:

1
2
git status
git log -1 --oneline

提交信息应准确描述变更目的,例如:

1
2
3
fix: reject tasks with an empty title
test: cover whitespace-only task titles
docs: explain local development setup

在未检查状态的情况下直接使用 git add .,可能将无关文件或敏感内容一并加入暂存区。显式指定文件能够提供更清晰的提交边界。


9. 功能分支变基

开发期间,upstream/main 可能已经包含其他贡献者的新提交。在项目允许整理个人提交的前提下,可以在首次 Push 前将功能分支变基到最新上游。

尚未共享的个人功能分支是 rebase 风险较低的使用场景。rebase 不是创建 Pull Request 的必要条件,项目对提交历史的要求应以贡献规范为准。

9.1 rebase 原理

假设功能分支基于提交 C 创建,并产生提交 DE;与此同时,上游增加了 FG

1
2
3
              D---E  功能分支
/
A---B---C---F---G upstream/main

执行:

1
git rebase upstream/main

Git 会找到共同祖先 C,暂存功能分支独有的修改,将功能分支移动到 G,然后按顺序重新应用 DE

1
2
3
A---B---C---F---G          upstream/main
\
D'---E' 功能分支

D'E' 的父提交已经改变,因此它们是具有新提交哈希的提交。rebase 改写的是当前功能分支,不会修改 upstream/main

9.2 执行流程

确认当前分支和工作区:

1
2
git branch --show-current
git status

获取上游更新:

1
git fetch upstream

确保位于目标功能分支:

1
git switch fix/issue-128-empty-title

执行变基:

1
git rebase upstream/main

变基完成后应重新执行项目测试,因为功能代码现在基于更新后的上游代码:

1
2
npm test
npm run lint

9.3 冲突处理

Git 会逐个重新应用功能提交。如果某个提交与上游修改了相同位置,rebase 会暂停。

查看冲突状态:

1
git status

冲突文件使用以 <<<<<<< 开始、以 ======= 分隔、以 >>>>>>> 结束的标记划分两侧内容。处理流程如下:

  1. 理解功能提交与上游修改的目的;
  2. 编辑为最终正确内容;
  3. 删除冲突标记;
  4. 执行相关测试;
  5. 暂存已经解决的文件;
  6. 继续 rebase。
1
2
git add path/to/conflicted-file
git rebase --continue

如果后续提交再次产生冲突,应重复上述步骤。

取消本次变基:

1
git rebase --abort

该命令会将分支恢复到 rebase 开始前的状态。

git rebase --skip 会跳过当前提交并可能丢失修改。只有确认该提交已经被上游完整替代时才应使用。

9.4 rebase 与 merge

功能分支也可以通过 merge 同步上游:

1
git merge upstream/main

两种策略的区别如下:

对比项 rebase merge
历史结构 线性排列 保留分叉,可能产生合并提交
原提交哈希 改变 不变
是否改写历史
尚未 Push 的个人分支 适用 适用
已公开或多人协作的分支 谨慎使用 通常更稳妥

Git 官方并未规定 rebase 或 merge 在所有项目中具有绝对优先级。项目贡献规范和团队协作方式决定最终策略。

9.5 rebase 安全边界

不应随意 rebase 以下分支或提交:

  • main 等公共分支;
  • 多人共同开发的分支;
  • 其他开发者已经基于其继续工作的提交;
  • 提交关系尚未确认、仅为了消除 Push 报错的分支。

Git 官方《Pro Git》给出的核心原则是:不要对已经公开且其他人可能基于其继续工作的提交进行 rebase。


10. 功能分支推送

将本地功能分支推送到个人 Fork:

1
git push -u origin fix/issue-128-empty-title

参数含义:

1
2
3
origin                         目标远程仓库
fix/issue-128-empty-title 目标功能分支
-u 建立远程跟踪关系

-u--set-upstream 的缩写,但它并不表示将代码推送到名为 upstream 的远程。该选项会使本地功能分支跟踪:

1
origin/fix/issue-128-empty-title

后续 Push 可以简化为:

1
git push

查看跟踪关系:

1
git branch -vv

Push 只会把功能分支上传到个人 Fork,不会修改原仓库的 main


11. Pull Request 创建

Push 成功后,可以从原仓库或个人 Fork 页面进入 Pull Request 创建界面。GitHub 通常会显示 Compare & pull request;手工创建时需要选择 compare across forks

11.1 Base 与 Compare 配置

正确配置如下:

GitHub 选项 含义
base repository alice/task-board 接收变更的原仓库
base main 原仓库目标分支
head fork xiaoming/task-board 提供变更的个人 Fork
compare fix/issue-128-empty-title 个人功能分支

合并方向为:

1
2
3
4
5
xiaoming/task-board:fix/issue-128-empty-title

│ Pull Request

alice/task-board:main

Base 与 Compare 选反会导致请求方向错误。

11.2 提交与文件审查

创建 Pull Request 前应检查 CommitsFiles changed

  • 提交是否只属于当前任务;
  • 文件是否与 Issue 相关;
  • 是否包含依赖目录、构建结果或无关格式化;
  • 是否包含凭据、私钥或个人配置;
  • 测试结果是否完整。

旧任务提交出现在当前 Pull Request 中,通常说明功能分支不是从干净的目标分支创建,不能通过修改描述解决。

11.3 标题与描述

Pull Request 标题应概括最终变更:

1
fix: reject tasks with an empty title

描述示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
## 问题

创建任务时,标题只包含空格也能通过校验,导致列表出现空任务。

## 修改

- 提交前对标题执行 trim
- 拒绝清理后为空的标题
- 增加空字符串和纯空格输入的测试

## 验证

- npm test
- npm run lint

Fixes #128

Fixes #128 可以关联对应 Issue,并在符合 GitHub 关闭规则时随 Pull Request 合并自动关闭 Issue。

尚未完成但需要提前讨论的变更,可以创建 Draft Pull Request,完成后再标记为 Ready for review。

11.4 维护者编辑权限

Allow edits from maintainers 允许原仓库维护者直接修改 Pull Request 分支。如果 Fork 包含 GitHub Actions 工作流,GitHub 可能同时提示开放与 Secrets 相关的权限。

是否启用该选项应根据页面显示的实际权限范围和项目协作要求决定。


12. 代码审查与分支更新

12.1 Review 修改

收到 Review 意见后,应继续在原功能分支修改:

1
git switch fix/issue-128-empty-title

完成修改和测试后创建新提交:

1
2
3
git add tests/TaskForm.test.js
git commit -m "test: cover whitespace-only task titles"
git push

GitHub 会自动更新现有 Pull Request,无需重新 Fork、创建新分支或创建新 Pull Request。

Review 讨论的处理应包含修改内容和验证结果。实现方案发生变化时,应同步更新 Pull Request 描述。

12.2 已公开分支的 merge 更新

Review 期间 upstream/main 可能继续前进。先获取更新并切换到功能分支:

1
2
git fetch upstream
git switch fix/issue-128-empty-title

对于已经公开并进入审查的分支,merge 不会改写已有提交:

1
git merge upstream/main

解决冲突并完成测试后执行:

1
git push

该方式可能产生合并提交,但不需要强制推送。

12.3 已公开分支的 rebase 更新

如果项目明确要求线性历史,并且功能分支没有被其他开发者使用,可以按照项目规范执行:

1
git rebase upstream/main

由于提交哈希已经改变,普通 Push 会被拒绝。执行强制更新前,应获取个人 Fork 的最新状态并检查提交关系:

1
2
git fetch origin
git log --oneline --graph --decorate --all -20

确认远程功能分支没有其他人的新增提交后,可以使用:

1
git push --force-with-lease origin fix/issue-128-empty-title

--force-with-lease 会在远程分支与本地预期不一致时拒绝覆盖,比 --force 更安全。但它仍然会改写远程历史,可能使旧 Review 评论失去对应代码位置,也可能导致已有审批失效。

多人共同使用的分支不应自行 rebase 后强制推送。


13. Pull Request 合并后的同步与清理

13.1 更新默认分支

Pull Request 合并后,变更已经进入 alice/task-board:main。本地和个人 Fork 需要再次同步:

1
2
3
4
git switch main
git fetch upstream
git merge --ff-only upstream/main
git push origin main

13.2 删除功能分支

删除本地功能分支:

1
git branch -d fix/issue-128-empty-title

删除个人 Fork 上的远程分支:

1
git push origin --delete fix/issue-128-empty-title

如果已经在 GitHub Pull Request 页面删除远程分支,则不需要重复执行远程删除命令。

13.3 Squash 合并后的分支删除

使用 Squash and merge 时,功能分支的多个提交会被压缩为一个新提交。原功能提交不再是 main 的直接祖先,因此 git branch -d 可能拒绝删除。

执行强制删除前应确认:

  • Pull Request 已经合并;
  • 最新 upstream/main 已包含目标修改;
  • 功能分支不存在尚未保存的额外工作。

确认后可以执行:

1
git branch -D fix/issue-128-empty-title

-D 会跳过未合并检查,不应在未确认分支内容时使用。

13.4 清理远程跟踪引用

1
2
git fetch --prune origin
git fetch --prune upstream

--prune 只清理本地保存的、远程已不存在的引用,不会删除仍存在的远程分支。


14. 后续贡献流程

Fork、Clone 和 upstream 配置通常只需执行一次。后续任务从同步默认分支开始:

1
2
3
4
5
6
git status
git switch main
git fetch upstream
git merge --ff-only upstream/main
git push origin main
git switch -c feature/new-task

标准循环为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
同步 main

创建任务分支

开发、测试和提交

项目允许时,在首次 Push 前 rebase

Push 到个人 Fork

创建 Pull Request

处理 Review

合并后同步 main 并清理分支

每个任务都应从最新、干净的 main 创建新分支。复用已经合并的旧功能分支可能使新 Pull Request 混入旧提交。


15. 常见故障排查

15.1 origin 指向原仓库

检查远程地址:

1
git remote -v

如果 origin 指向 alice/task-board,将其修改为个人 Fork:

1
git remote set-url origin git@github.com:xiaoming/task-board.git

原仓库应配置为:

1
git remote add upstream git@github.com:alice/task-board.git

如果 upstream 已存在,应使用 git remote set-url upstream ... 修改,而不是重复添加。

15.2 在 main 上产生功能修改

如果修改尚未提交,可以从当前位置创建功能分支:

1
git switch -c fix/issue-128-empty-title

未提交修改通常会随工作区进入新分支。

如果已经在 main 创建提交,应先创建分支保存当前提交,再分析如何恢复 main,不应直接执行 reset --hard

15.3 Pull Request 方向错误

正确方向为:

1
个人 Fork 的功能分支 → 原仓库的目标分支

创建时应核对 base repository、base、head fork 和 compare。

15.4 non-fast-forward

Push 被拒绝并显示 non-fast-forward,表示远程分支包含本地没有的提交,或者本地历史已经被 rebase 等操作改写。

先获取并查看关系:

1
2
git fetch origin
git log --oneline --graph --decorate --all -20

在确认远程新增提交的来源和保留方式之前,不应直接强制 Push。

15.5 未提交修改阻止分支切换

检查当前修改:

1
2
git status
git diff

完整修改可以提交;临时修改可以使用 stash;确认不需要的修改可以使用 restore。处理方式取决于修改是否仍需保留。

15.6 GitHub 未显示本地提交

git commit 只创建本地提交,还需要执行 git push。可以检查当前分支和跟踪关系:

1
2
git branch -vv
git remote -v

15.7 原仓库未出现已 Push 的修改

Push 到个人 Fork 后,还需要向原仓库创建 Pull Request。只有 Pull Request 被合并,变更才会进入原仓库目标分支。

15.8 Pull Request 包含无关提交

常见原因包括:

  • 从旧功能分支创建新分支;
  • 在同一分支处理多个任务;
  • 本地 main 包含个人提交;
  • Pull Request 的 base 分支错误。

预防方式是先同步 upstream/main,再从干净的本地 main 创建独立任务分支。

15.9 SSH 公钥认证失败

出现 Permission denied (publickey) 时,检查:

1
2
ssh -T git@github.com
git remote -v

需要确认公钥已添加到正确的 GitHub 账号、SSH 使用了预期私钥,并且仓库地址与账号权限一致。


16. 变更撤销与恢复

撤销操作前应先运行 git status,并确认目标变更是否已经提交或 Push。

16.1 取消暂存

取消暂存但保留工作区修改:

1
git restore --staged path/to/file

16.2 丢弃工作区修改

1
git restore path/to/file

该命令会丢失目标文件中尚未提交的修改,执行前必须确认内容不再需要。

16.3 修改最近一次本地提交

对于尚未 Push 的最近一次提交,可以补充文件或修改提交信息:

1
2
git add forgotten-file
git commit --amend

amend 会生成新的提交哈希。提交已经公开后,不应在未评估影响时使用。

16.4 撤销已共享提交

对于已经 Push 或合并的提交,通常使用反向提交:

1
git revert <commit-id>

revert 保留原历史,适合公共分支。reset 会移动分支引用,不适合作为共享历史的常规撤销方式。

16.5 临时保存工作区

1
git stash push -u -m "wip: issue 128"

恢复暂存内容:

1
git stash pop

stash 适合短期切换任务,不应长期代替分支和提交。

16.6 使用 reflog 定位丢失提交

1
git reflog

reflog 记录本地分支和 HEAD 最近移动过的位置。误删分支或错误 reset 后,提交可能仍可通过 reflog 找回。发生历史操作错误后,应停止继续改写历史并记录相关提交哈希,再制定恢复方案。


17. 命令流程汇总

17.1 首次建立协作仓库

在 GitHub 页面完成 Fork 后执行:

1
2
3
4
git clone git@github.com:xiaoming/task-board.git
cd task-board
git remote add upstream git@github.com:alice/task-board.git
git remote -v

17.2 创建任务分支

1
2
3
4
5
6
git status
git switch main
git fetch upstream
git merge --ff-only upstream/main
git push origin main
git switch -c fix/issue-128-empty-title

17.3 开发与提交

1
2
3
4
5
6
7
8
9
git status
git diff

# 修改代码并执行项目规定的测试

git add src/components/TaskForm.js tests/TaskForm.test.js
git diff --staged
git commit -m "fix: reject tasks with an empty title"
git status

17.4 项目允许时执行变基

1
2
3
4
5
git fetch upstream
git switch fix/issue-128-empty-title
git rebase upstream/main

# 重新执行测试

17.5 推送与 Pull Request

1
git push -u origin fix/issue-128-empty-title

Pull Request 方向:

1
2
3
xiaoming/task-board:fix/issue-128-empty-title

alice/task-board:main

17.6 合并后同步与清理

1
2
3
4
5
6
7
8
git switch main
git fetch upstream
git merge --ff-only upstream/main
git push origin main
git branch -d fix/issue-128-empty-title
git push origin --delete fix/issue-128-empty-title
git fetch --prune origin
git fetch --prune upstream

18. 总结

Fork 模式下的远程协作由三份仓库构成:原仓库、个人 Fork 和本地仓库。upstream 用于获取原项目更新,origin 用于接收个人功能分支。

默认分支和功能分支承担不同职责:

1
2
main:使用 merge --ff-only 保持为 upstream/main 的干净镜像
功能分支:承载单一任务,项目允许时可在首次 Push 前 rebase

完整流程包括:Fork 原仓库、Clone 个人 Fork、配置 upstream、同步 main、创建功能分支、开发和提交、推送个人分支、创建 Pull Request、处理 Review,以及合并后的同步与清理。

在执行分支切换、历史整合和远程操作前,应持续检查:

1
2
3
git status
git branch --show-current
git remote -v

这些状态信息能够明确当前操作对象,是避免分支污染、错误 Push 和历史误改的基础。


参考资料

[1] GitHub Docs:Fork 仓库

[2] GitHub Docs:为 Fork 配置远程仓库

[3] GitHub Docs:同步 Fork

[4] GitHub Docs:从 Fork 创建 Pull Request

[5] GitHub Docs:通过 SSH 连接 GitHub

[6] Pro Git:变基

[7] Git 官方文档

[8] Git 官方文档:git status

[9] Git 官方文档:git switch

[10] Git 官方文档:git restore