11.2 推上云端,开始协作
本节目标:理解远程仓库的价值,把代码推到 GitHub,让朋友 clone 下来,解决协作中的冲突和安全问题。
所有鸡蛋在一个篮子里
小明的代码只在他的电脑上。这意味着:硬盘坏了,代码全没;电脑被偷了,代码全没;系统崩溃重装了,代码全没。这在技术上叫单点故障(Single Point of Failure)——整个系统依赖一个节点,这个节点挂了,一切归零。
你一定有过类似的恐惧——手机丢了,照片全没了。或者至少听过别人的惨痛经历。现在的人会把照片同步到云相册,道理是一样的:重要的东西不能只存在一个地方。 你不会把所有钱都放在一个口袋里,你不会把唯一的钥匙放在门垫下面,你的代码也不应该只存在一块硬盘上。
还记得第六章吗?你把数据库放到了 Neon 的云端,而不是跑在本地。当时的理由是:本地数据库重装系统就没了,云端数据库不受影响。代码也是同一个思路——推到云端,不依赖单台设备。代码的"云相册"叫做代码托管平台,最主流的是 GitHub,国内访问更快的有 Gitee 和 CNB。
除了备份,远程仓库还解决了另一个问题:协作。小明的朋友想参与开发,但代码在小明的电脑上,朋友拿不到。发压缩包?序言里已经试过了,两个人改了同一个文件,对着微信消息逐行核对,痛苦不堪。有了 GitHub,小明把代码推上去,朋友从 GitHub 拉下来,两个人就能各自开发、互相同步了。GitHub 在中间充当"中转站",保证双方看到的是同一份代码的同一个版本。
平台选择与注册
GitHub 是国际主流,生态最丰富,开源项目首选。Gitee 国内访问快,中文界面。CNB 是国内新平台,访问速度也不错。选哪个都行,本章以 GitHub 为例,其他平台的操作大同小异。如果你在国内访问 GitHub 速度慢,可以先用 Gitee 或 CNB 起步,以后随时可以迁移。
如果你还没有 GitHub 账号,去 github.com 注册一个。注册过程和普通网站一样——邮箱、用户名、密码。用户名会出现在你的仓库 URL 里(比如 github.com/你的用户名/项目名),选一个你不会后悔的。
SSH 认证:配一次,永久免密
注册好 GitHub 账号后,你需要让你的电脑和 GitHub 之间建立信任关系。GitHub 已经禁用了密码登录终端,所以你不能像登录网站一样输入用户名和密码来推送代码。取而代之的是 SSH 密钥认证。
SSH 密钥的本质是一把锁和一把钥匙。公钥是锁芯,放在 GitHub 上,告诉 GitHub "拿着这把钥匙的人是我"。私钥是钥匙,留在你的电脑上,证明"我就是我"。
每次你向 GitHub 推送代码时,GitHub 用公钥验证你的私钥,确认身份后放行。整个过程自动完成,你感觉不到——就像你用指纹解锁手机,不需要每次都输入密码。
配置过程是一次性的。告诉 Claude Code 你要配置 SSH 连接 GitHub,它会帮你生成一对密钥(如果还没有的话),然后显示公钥内容。你把公钥复制到 GitHub 的 Settings → SSH and GPG keys 页面,测试连接成功,就完事了。
以后每次 push 和 pull 都不需要输入任何凭证。就像你家的门锁——装好之后,带着钥匙出门就行,不用每次都重新装锁。整个配置过程可能需要在 GitHub 网页和终端之间切换几次,但只需要做这一次。
如果 SSH 配置遇到困难(比如公司网络限制了 SSH 端口),可以用 HTTPS + Personal Access Token 的方式作为备选。在 GitHub Settings → Developer settings → Personal access tokens 生成一个 Token——它不是你自己设的密码,而是平台发给你的一串临时通行证。推送时用 Token 代替密码。但 SSH 是更推荐的长期方案——Token 有过期时间,SSH 密钥没有。而且 Token 需要你在某个地方保存它,忘了就得重新生成;SSH 密钥存在你电脑的固定位置,不需要你记住任何东西。
第一次推送
SSH 配好了,现在把代码推到 GitHub。
小明告诉 Claude Code:"在 GitHub 上创建一个仓库,把我的项目推上去。" Claude Code 会帮他完成创建远程仓库、关联本地仓库、推送代码这一系列操作。第一次推送时,它会执行类似这样的命令(你不需要记住,了解即可):
git remote add origin git@github.com:xiaoming/personal-douban.git
git push -u origin mainorigin 是你给远程仓库起的昵称——就像手机通讯录里你把 GitHub 仓库的地址存成了"origin",以后 push 和 pull 直接拨昵称就行,不用每次都输完整地址。-u 则是把这个昵称设为默认联系人,以后直接按拨号键就行。这些命令你不需要背——Claude Code 会帮你处理。但知道它们的存在有助于你理解终端里在发生什么,不至于看到一堆命令输出时一头雾水。
推送完成后,打开 GitHub 仓库页面,你会看到你的项目文件都在这里,和本地一模一样。如果有 README.md,会自动渲染在页面下方。点击 "commits" 可以看到所有提交记录——和你在本地用 git log 看到的一样,只不过现在是在网页上看。你的代码现在有了两份:一份在本地,一份在 GitHub。硬盘坏了也不怕了。

创建仓库时可以选择 Public(公开)或 Private(私有)。个人项目建议选 Private——你的代码不需要让全世界看到。以后想开源了,随时可以改成 Public。
push 和 pull:保持同步
代码推到 GitHub 后,日常开发的节奏变成了:你在本地改了代码、提交了,用 push 同步到 GitHub;别人推了新代码到 GitHub,你用 pull 把更新拉到本地。
这就像两个人各自在自己的电脑上编辑文档,定期同步到共享网盘。网盘(GitHub)是中转站,保证大家看到的是同一份最新版本。
push 和 pull 的关系很简单:push 是"我改好了,传上去",pull 是"别人改好了,我拉下来"。两个方向,一个上行一个下行,GitHub 在中间。你不需要时刻保持同步——Git 的设计就是让你在本地自由开发,想同步的时候再同步。这和 Google Doc 的实时协作不同,Git 给你更多的自主权,代价是你需要主动触发同步。
Claude Code 在你开发过程中会帮你提交代码,但 push 通常需要你主动触发——毕竟推送到远程是一个"发布"动作,你可能想攒几个提交再一起推。
一个好的习惯是:每完成一个阶段性的工作就 push 一次。 不需要每次 commit 都 push,但也不要攒太久——万一电脑出问题,没 push 的提交就丢了。想想你写了一下午的文档没保存到云端,晚上电脑蓝屏了——那种感觉你一定不想体验第二次。
clone:朋友第一次获取代码
小明把 GitHub 仓库链接发给朋友。朋友第一次获取代码,用的是 clone。
clone 和"下载 zip"有什么区别?zip 只给你当前的文件,clone 给你整个项目加上全部提交历史。clone 下来的目录自带 .git 文件夹,可以直接 push 和 pull——它是一个完整的 Git 仓库副本,不是一堆散装文件。
这就是为什么序言里小明发压缩包给朋友的方式不靠谱——压缩包没有版本历史,没有分支信息,没有和远程仓库的关联,就是一堆孤立的文件。朋友拿到压缩包后,改了代码想同步回来,只能再压缩发回去,然后两个人对着微信逐行核对。clone 一次就建立了双向通道,以后所有同步都通过 push 和 pull 完成。
朋友 clone 之后,还需要安装项目依赖。对于 Node.js 项目,就是在项目目录下运行 npm install(或 pnpm install)。
这就是为什么 node_modules 不需要提交到仓库——每个人 clone 后自己安装就行,而且不同系统的依赖包可能编译出不同的二进制文件,Windows 上编译的 .node 文件在 Mac 上跑不了。package.json 记录了项目需要哪些依赖,npm install 会根据它自动下载——这就像菜谱和食材的关系,你分享菜谱(package.json)就行,不需要把食材(node_modules)也打包寄过去。
默认情况下,只有仓库创建者能 push。小明需要在 GitHub 仓库的 Settings → Collaborators 里邀请朋友,朋友接受邀请后才能推送代码。邀请完成后,两个人就可以各自开发了:
小明的电脑 GitHub 朋友的电脑
│ │ │
├── push ──────────→ │ │
│ │ ←────────── push ────┤
├── pull ←────────── │ │
│ │ ──────────→ pull ────┤冲突:两个人改了同一行
小明和朋友各自开发,大部分时候相安无事——你改你的文件,我改我的文件,Git 自动合并,毫无问题。
Git 在合并时非常聪明:如果两个人改的是不同的文件,它直接把两边的修改合在一起;如果改的是同一个文件但不同的位置,它也能自动处理。只有当两个人改了同一个文件的同一行时,Git 才会"举手投降"——它不知道该听谁的。
小明和朋友就碰到了这种情况。小明把电影评分的满分从 5 改成了 10,朋友把满分从 5 改成了 100。两个人都提交了,小明先 push 成功,朋友再 push 时被拒绝了——Git 说"远程有更新,先 pull"。
朋友 pull 下来,Git 发现同一行有两个不同的修改,不知道该听谁的,于是在文件里用一种"选择题"格式标记出来,让你决定保留哪个版本:
<<<<<<< HEAD
const MAX_SCORE = 100 // 朋友的修改(当前分支)
=======
const MAX_SCORE = 10 // 小明的修改(远程分支)
>>>>>>> origin/main<<<<<<< HEAD 和 ======= 之间是你本地的版本,======= 和 >>>>>>> origin/main 之间是远程的版本。你需要决定保留哪个(或者两个都保留、或者写一个全新的版本),然后删掉这些标记符号。
如果你用过 Google Doc 或飞书文档,一定见过"某某正在编辑"的实时提示。Git 没有这种实时提示——它是在你同步代码时才发现冲突的。这不是 Git 的缺陷,而是它的设计哲学:让每个人自由地在本地开发,只在同步时处理分歧。
遇到冲突不用慌。告诉 Claude Code "帮我解决冲突",说明你想保留哪边的逻辑(或者两边都保留),它会帮你合并好。然后提交、推送,冲突就解决了。
实际上,冲突在小团队协作中并不常见。大部分时候两个人改的是不同的文件,Git 会自动合并,你甚至不会注意到。只有当两个人恰好改了同一个文件的同一段代码时才会产生冲突。
所以不要因为"怕冲突"而不敢协作——冲突是正常的,解决起来也不难。减少冲突最有效的方式是分工明确——你负责前端页面,朋友负责后端接口,各改各的文件,冲突自然就少了。下一节的"分支"机制会进一步帮你隔离各自的工作。
.gitignore:打包前的清理清单
小明第一次 push 的时候,把 node_modules 也推上去了。朋友 clone 下来发现仓库有 500MB——其中 490MB 是 node_modules。
这就像你给朋友发压缩包,里面夹带了一堆系统临时文件、缓存、回收站里的东西。.gitignore 就是"打包前的清理清单"——列在里面的文件和文件夹,Git 会自动忽略,不会提交到仓库。
一个典型的 .gitignore 应该排除这些东西:node_modules/ 是依赖包,体积巨大(几百 MB 很常见),朋友 clone 后自己 npm install 就行;.env 和 .env.local 是环境变量文件,包含你的 API 密钥和数据库密码,这个后面会专门讲;.DS_Store 和 Thumbs.db 是 macOS 和 Windows 操作系统自动生成的隐藏文件,对项目没有任何用处;dist/、.next/、build/ 是构建产物,可以从源代码重新生成,没必要存在仓库里。
Claude Code 在初始化项目时通常会自动生成 .gitignore。如果你发现仓库里有不该出现的文件,告诉它帮你清理并更新 .gitignore。这里有一个容易踩的坑:如果 node_modules 已经被提交到仓库了,光在 .gitignore 里加一行是不够的——.gitignore 只对尚未跟踪的文件生效。已经被 Git 跟踪的文件,即使加到 .gitignore 里,Git 也会继续跟踪它的变化。你需要先从仓库中移除它(但保留本地文件),然后再提交。告诉 Claude Code "从 Git 仓库中移除 node_modules 但保留本地文件,并添加到 .gitignore",它会用 git rm --cached 来处理——这就像把一本书从图书馆的借阅登记表上划掉,但书还在你手里。Git 不再跟踪这个文件夹了,但你本地的文件原封不动。
安全警告:绝对不能提交的东西
.gitignore 排除的大部分文件只是"没必要提交"。但有一类文件是绝对不能提交的——不是因为体积大,而是因为提交了就等于把钥匙挂在了公告栏上。
这些东西一旦出现在 Git 历史中,就算你后来删了文件,历史记录里还有——因为 Git 的设计就是"记住一切"。你在第三次提交里加了 .env 文件,第四次提交里删了它,但任何人只要查看第三次提交的内容,就能看到你的 API 密钥。这不是 Git 的 bug,这是它的核心功能——完整的历史记录。只不过在这个场景下,这个功能变成了安全隐患。
公开仓库意味着任何人都能看到你的代码——包括你不小心提交的密钥。GitHub 上有专门的爬虫在扫描公开仓库里的 API 密钥,一旦发现就会被滥用。即使是私有仓库,也不应该把密钥放在代码里——万一以后改成公开的呢?万一你邀请了一个不太信任的协作者呢?
正确的做法是把密钥放在 .env 文件里,然后确保 .env 在 .gitignore 中。第八章讲安全的时候已经提过这个原则,这里再强调一次,因为 Git 让这个问题变得更严重——本地文件泄露影响有限,推到 GitHub 上就是全网可见。绝对不能提交的东西包括:API 密钥和 Access Token(OpenAI、Stripe、各种第三方服务的密钥)、数据库连接字符串(包含密码的 DATABASE_URL)、私钥文件(.pem、.key、id_rsa)、环境变量文件(.env、.env.local、.env.production)。
已经提交了敏感信息怎么办
如果你不小心把 .env 文件提交并推送到了 GitHub,仅仅删除文件再提交是不够的——Git 历史中仍然保留着那次提交的内容,任何人都能翻到。你需要从 Git 历史中彻底清除这个文件。告诉 Claude Code "从 Git 历史中彻底删除 .env 文件",它会使用 git filter-branch 或 BFG Repo-Cleaner 来处理。同时,立即轮换所有泄露的密钥——去对应的服务平台重新生成 API Key,旧的作废。这一步不能省,因为你无法确定在你清除历史之前是否已经有人看到了。
GitHub 页面导览
第一次打开 GitHub 仓库页面,你可能不知道自己在看什么。这里简单介绍几个你会用到的地方。
仓库首页是你看到的第一个页面,也叫 Code 标签。上方是文件列表,和你本地的项目目录一样,下方是 README.md 的渲染预览。文件列表右侧显示每个文件最后一次修改的提交信息和时间。如果你的项目有 README.md(Claude Code 通常会自动创建),它会被渲染成格式化的文档展示在页面下方——这是别人第一眼看到你项目时的"门面"。
点击文件列表上方的 "X commits" 链接,进入提交历史页面。这里能看到所有提交记录,每条记录显示提交信息、作者、时间。点进去能看到那次提交具体改了哪些文件的哪些行——绿色是新增的,红色是删除的。这个页面在你想回顾"上周到底改了什么"或者"这个 bug 是哪次提交引入的"时特别有用。
仓库顶部还有 Issues 和 Pull Requests 两个标签页。Issues 用来记录 bug 和待办事项,Pull Requests 用来审查和合并代码(下一节会详细讲 PR)。现在知道它们在哪就行。
Settings → Collaborators 是邀请朋友协作的地方。添加朋友的 GitHub 用户名,朋友接受邀请后就能 push 代码了。
朋友加入后的第一次协作
小明邀请了朋友,朋友 clone 了仓库,安装了依赖,项目跑起来了。现在两个人要开始分工。
一个简单的分工方式:小明继续做评分功能的优化,朋友负责推荐算法。两个人改的是不同的文件,大部分时候不会冲突。日常节奏变成了:开始工作前先 pull 一下,拿到对方最新的代码;各自开发,完成后 commit;push 到 GitHub,对方再 pull。
这个节奏一开始可能会有点不习惯——你得记得在开始工作前 pull,完成工作后 push。但用不了几天就会变成肌肉记忆,就像你出门前检查手机钥匙钱包一样自然。如果你忘了 pull 就开始改代码,也不是什么大事——等你 push 的时候 Git 会提醒你"远程有更新,先 pull",pull 下来合并一下就行。
这个"pull → 开发 → commit → push"的循环就是 Git 协作的基本节奏。简单、直接,两个人就能跑起来。但随着项目变复杂,小明发现了一个新问题:他想试一个大胆的新功能,但怕改坏现有的代码。下一节会介绍"分支"机制,让这个节奏更安全、更有条理。
本节核心要点
代码推到 GitHub,你就有了云端备份和协作基础。SSH 是一次性配置,push/pull 是日常同步,clone 是朋友第一次获取代码的方式。冲突不可怕——它只是 Git 在说"这里我拿不准,你来决定"。最重要的安全底线:密钥和密码永远不要出现在 Git 仓库里。
下一节:小明想加推荐功能,但怕改坏评分系统。11.3 分支、PR 与团队工作流 解决"想试新东西又怕搞坏现有的"这个问题。
