前言

最近我一直跟着《代码随想录》刷 LeetCode。在学习图论章节时,我发现部分题目的 Go 版本存在缺失或代码与题解主题不一致的情况。

例如,卡码网第 0099 题的广度优先搜索题解中,Go 示例实际写的是递归 DFS;官网的深搜页面中,Go 标题下面也没有正常显示代码。后来做到第 0100 题时,我又发现原题解中没有 Go 的 BFS 版本。

刚好卡尔哥也一直鼓励大家共同维护项目,于是我决定不只是自己在本地改一改,而是尝试 Fork 仓库、修正代码并提交 Pull Request。这也是我第一次完整参与开源仓库的贡献流程,所以把操作步骤和遇到的问题记录下来。

1

2

一、Fork 原仓库

首先进入代码随想录的 GitHub 仓库,点击右上角的 Fork,将项目复制一份到自己的 GitHub 账号下。

Fork 完成后,会得到一个属于自己的远程仓库。页面上通常会显示:

1
forked from youngyangyang04/leetcode-master

3

这里需要理解两个远程仓库的概念:

  • origin:自己账号下的 Fork 仓库,拥有推送权限。
  • upstream:代码随想录的原仓库,用来获取上游最新代码。

我本地的远程仓库配置类似这样:

1
2
origin    https://github.com/自己的用户名/leetcode-master.git
upstream https://github.com/youngyangyang04/leetcode-master.git

可以使用以下命令查看:

1
git remote -v

如果克隆后还没有添加 upstream,可以执行:

1
git remote add upstream https://github.com/youngyangyang04/leetcode-master.git

二、同步上游并创建修改分支

开始修改前,先同步原仓库的最新代码:

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

然后从最新的 master 创建一个本次修改专用的分支。以修复第 0099 题为例:

1
git switch -c fix/kamacoder-0099-go

这里不需要提前在 GitHub 网页上创建分支。本地创建分支并推送后,远端会自动创建同名分支。

分支名可以自己决定,但最好让人一眼看懂修改内容,例如:

1
2
fix/kamacoder-0099-go
feat/kamacoder-0100-go-bfs

三、一个 PR 使用一个独立分支

我一开始容易混淆“分支”和“commit”的关系。正确的理解不是每次 commit 都要创建分支,而是每个独立任务或者 PR 使用一个分支:

1
2
3
4
5
一个修改任务 / PR
└── 一个分支
├── commit 1
├── commit 2
└── commit 3

例如,第 0099 题的两项修改属于同一个任务,所以可以放在同一个分支中,并拆成两个 commit:

1
2
修正0099岛屿的数量 Go 广搜版本
优化0099岛屿的数量 Go 深搜版本

而第 0100 题新增 Go BFS 是另一个任务,应从最新的 master 新建分支:

1
git switch -c feat/kamacoder-0100-go-bfs

这样做可以避免第 0100 题的修改自动混入第 0099 题已经创建的 PR。

四、在错误分支修改后,使用 stash 转移

我在修改第 0100 题时,忘记切换分支,仍然停留在第 0099 题的分支上。因为修改还没有提交,可以先使用 stash 暂存工作区内容,再切换到正确分支:

1
2
3
4
5
6
7
8
git stash push -m "添加0100 Go BFS版本" -- "problems/kamacoder/0100.岛屿的最大面积.md"

git switch master
git fetch upstream
git merge --ff-only upstream/master
git switch -c feat/kamacoder-0100-go-bfs

git stash pop

第一次执行时我还遇到了下面的错误:

1
fatal: not a git repository (or any of the parent directories): .git

原因是终端位于 D:\Github-Fork,而真正的仓库目录是 D:\Github-Fork\leetcode-master。进入仓库后再执行 Git 命令即可:

1
cd "D:\Github-Fork\leetcode-master"

五、修改代码和 Markdown 时需要注意什么

1. 算法实现要与题解主题一致

广搜应该使用队列,而不是递归调用自身。一个典型的 Go BFS 结构如下:

1
2
3
4
5
6
7
queue := [][2]int{{x, y}}
for len(queue) > 0 {
current := queue[0]
queue = queue[1:]

// 遍历当前节点的四个相邻方向
}

节点应在加入队列时立即标记为已访问,防止同一节点重复入队:

1
2
3
4
if grid[nextx][nexty] == 1 && !visited[nextx][nexty] {
visited[nextx][nexty] = true
queue = append(queue, [2]int{nextx, nexty})
}

遍历每个岛屿的入口也要判断是否访问过:

1
2
3
4
if grid[i][j] == 1 && !visited[i][j] {
visited[i][j] = true
area := bfs(grid, visited, i, j)
}

2. Go 代码应保持统一格式

需要注意变量名、空格、缩进和注释格式,例如:

1
2
3
var n, m int
queue := [][2]int{{x, y}}
area := 1 // 当前岛屿的面积

尽量使用符合 Go 习惯的写法:

1
!visited[i][j]

而不是:

1
visited[i][j] == false

3. Markdown 代码块要完整

添加 Go 代码时,需要使用带语言标识的代码块,并确认开头与结尾成对出现:

1
2
3
4
5
```go
package main

// Go 代码
```

修改完成后,还要确认后面的 Rust、JavaScript 等章节没有被错误包含进 Go 代码块。

4. 清理行尾空格

普通空行可以保留,但空行中不应包含空格或 Tab,代码行末尾也不应留下多余空格。可以使用下面的命令检查:

1
git diff --check

如果没有输出,就说明没有检测到这类空白问题。

Windows 下有时会看到:

1
LF will be replaced by CRLF the next time Git touches it

这通常只是换行符提醒,并不等于代码出错。

六、检查、暂存与提交

修改完成后,先查看状态和差异:

1
2
3
git status
git diff --check
git diff

确认只修改了目标文件后,将文件加入暂存区:

1
git add "problems/kamacoder/0100.岛屿的最大面积.md"

然后检查真正准备提交的内容:

1
2
3
git diff --cached --check
git diff --cached
git status

git status --short 中常见的状态有:

1
2
3
 M 文件名   工作区已修改,但尚未暂存
M 文件名 修改已经暂存
MM 文件名 已暂存后又继续修改,需要重新 git add

如果出现 MM,保存文件后重新执行一次 git add 即可更新暂存区。

七、规范填写 commit 信息

代码随想录要求 commit 信息说明本次提交具体修改了什么。如果不同文件对应不同目的,可以拆分提交。

第 0099 题我使用了两个 commit:

1
2
3
4
5
git add "problems/kamacoder/0099.岛屿的数量广搜.md"
git commit -m "修正0099岛屿的数量 Go 广搜版本"

git add "problems/kamacoder/0099.岛屿的数量深搜.md"
git commit -m "优化0099岛屿的数量 Go 深搜版本"

第 0100 题新增 BFS 版本则可以使用:

1
git commit -m "添加0100岛屿的最大面积 Go BFS版本"

提交后检查:

1
2
git status
git log -2 --oneline

正常情况下,工作区应显示:

1
nothing to commit, working tree clean

八、推送分支到自己的 Fork

以第 0100 题为例:

1
git push -u origin feat/kamacoder-0100-go-bfs

这条命令会:

  1. 将本地分支推送到自己的 Fork。
  2. 如果远端不存在同名分支,则自动创建。
  3. 通过 -u 建立本地分支与远端分支的跟踪关系。

建立跟踪关系后,后续在该分支继续修改时,通常直接执行 git push 即可。

3

九、创建 Pull Request

推送完成后,进入自己的 Fork,点击 GitHub 页面提示的 Compare & pull request

创建 PR 时需要仔细确认方向:

1
2
3
4
5
base repository: youngyangyang04/leetcode-master
base: master

head repository: 自己的用户名/leetcode-master
compare: 本次修改分支

也就是说,请求将自己分支中的代码合入代码随想录原仓库的 master,不要把目标错误地选成自己 Fork 的 master

如果一个 PR 只有一个 commit,PR 标题可以与 commit 信息相同:

1
添加0100岛屿的最大面积 Go BFS版本

如果一个 PR 包含多个 commit,PR 标题和描述应该对所有修改进行汇总。例如第 0099 题:

1
修正0099岛屿的数量 Go 广搜版本,优化 Go 深搜版本

PR 描述可以写成:

1
2
- 修正0099岛屿的数量 Go 广搜版本,将误用的递归深搜实现改为队列广搜
- 优化0099岛屿的数量 Go 深搜版本,调整代码结构与格式

建议保留勾选 Allow edits by maintainers,方便维护者在必要时直接调整代码。

3

确认无误后,点击 Create pull request。创建 PR 并不会直接修改原仓库,而是向维护者发起合并请求,之后等待代码审查即可。

4

十、这次贡献让我记住的几点

第一次完整走完 Fork 和 PR 流程,真正容易出错的不只是代码,还有分支、暂存区和提交范围。对我来说最重要的是以下几点:

  1. origin 是自己的 Fork,upstream 是原仓库。
  2. 每个独立任务或者 PR 使用一个单独分支,而不是每个 commit 都创建分支。
  3. 开始新任务前,先回到 master 并同步上游。
  4. 在错误分支上产生未提交修改时,可以使用 git stash 转移。
  5. git diff 查看工作区修改,git diff --cached 查看准备提交的修改。
  6. 提交前使用 git diff --check 清理行尾空格。
  7. commit 描述单次修改,PR 描述汇总本次所有 commit。
  8. 创建 PR 时一定确认 base 和 compare 的方向。

这次尝试也让我意识到,参与开源维护并没有想象中那么遥远。发现问题、确认问题、在独立分支完成修改、认真检查并提交 PR,本身就是一次完整的贡献。即使只是补充一种语言的实现或者修正一个小错误,也能让后来阅读题解的人少踩一个坑。

后面如果继续在刷题过程中发现 Go 版本缺失或实现有问题,我也会继续尝试提交修改,并逐渐熟悉更规范的协作流程。