GitHub 从入门到放弃

每个程序员的 GitHub 主页上,都躺着几个只有一次 commit 的仓库。那是梦想开始的地方,也是梦想结束的地方。

outlier 16 分钟 6055 字 1 0

写在前面

说来惭愧,我用 GitHub 已经很多年了,但真要问我"GitHub 到底是什么",我第一反应还是:"就……放代码的地方?"

这个回答大概相当于说"支付系统就是转钱的地方"——不能说错,但基本等于没说。

最近 AI 编程工具一波接一波,Copilot、各种 Coding Agent 都在往 GitHub 上"住",我突然意识到:这个我天天打开、却从没认真了解过的网站,可能正在变成整个软件世界的基础设施。于是决定系统地折腾一遍,顺便写下来。

按照惯例,本文的阅读体验大致如下:

journey title 一个程序员的 GitHub 心路历程 section 入门 注册账号,选了个酷炫的用户名: 5: 我 第一次 git push 成功: 5: 我 给喜欢的项目点 Star: 4: 我 section 进阶 第一次遇到 merge conflict: 2: 我 git rebase 之后历史消失了: 1: 我 第一次提 PR 被维护者关掉: 2: 我 section 放弃 不小心把 API Key 提交上去: 1: 我 git push --force 到 main: 1: 我, 同事 发现 AI 写的 PR 比我的先合并: 1: 我

系好安全带,我们出发。


第一章:入门——先把 Git 和 GitHub 分清楚

Git 是工具,GitHub 是平台

很多新手(包括当年的我)会把 Git 和 GitHub 混为一谈。其实它们的关系很简单:

  • Git:一个分布式版本控制工具,装在你电脑上,负责记录代码的每一次变化。
  • GitHub:一个基于 Git 的云端协作平台,把你的 Git 仓库放到网上,再加上协作、社区、自动化、AI 等一大堆能力。

打个比方:Git 是记账本,GitHub 是银行。你可以自己在家记账,但想跟别人对账、转账、做联名账户,就得有个大家都信任的机构。

一段八卦:Git 是怎么来的

2005 年之前,Linux 内核一直用一个叫 BitKeeper 的商业版本控制工具,对开源社区免费。后来双方闹翻,BitKeeper 收回了免费授权。

Linus Torvalds 的反应很符合他的人设:既然没有好用的,那我自己写一个。

据说他只用了大约十天就写出了 Git 的第一个可用版本,并很快用它来管理 Linux 内核本身。至于为什么叫 "Git"——这个词在英式俚语里有点"讨厌鬼"的意思,Linus 自嘲说他总是以自己的名字给项目命名,Linux 是这样,Git 也是这样。

三年后的 2008 年,几个年轻人觉得 Git 好用但门槛太高,于是做了 GitHub,口号是"Social Coding"。再后来的故事大家都知道了:2018 年微软以 75 亿美元收购 GitHub,当时社区一片哀嚎,一大批人宣布"迁移到 GitLab"。

然后……大部分人又默默回来了。这大概就是"从放弃到真香"。

Git 的四个区域

理解 Git,最重要的是搞清楚代码在哪几个地方"流转":

flowchart LR A[工作区
Working Directory
你正在改的文件] -->|git add| B[暂存区
Staging Area
准备提交的改动] B -->|git commit| C[本地仓库
Local Repository
你电脑上的历史] C -->|git push| D[远程仓库
Remote Repository
GitHub 上的历史] D -->|git pull / fetch| C C -->|git checkout / restore| A

为什么要有"暂存区"这个中间层?因为你可能改了十个文件,但只想把其中三个作为一次提交。暂存区就像购物车,先挑好,再结账。

分支:平行宇宙

分支(Branch)是 Git 最强大的概念。你可以从主线拉出一个"平行宇宙",在里面随便折腾,折腾好了再合并回来;折腾坏了,直接删掉,主线毫发无损。

gitGraph commit id: "init" commit id: "add README" branch feature/login checkout feature/login commit id: "登录页面" commit id: "修复 typo" checkout main commit id: "紧急 hotfix" checkout feature/login commit id: "接入 OAuth" checkout main merge feature/login commit id: "发布" tag: "v1.0"

小知识:GitHub 新建仓库的默认分支名,在 2020 年从 master 改成了 main。所以如果你看到老教程里写 git push origin master 结果报错,不是你的问题,是时代变了。


第二章:上手——第一次 push 的血泪史

最常用的命令

先上一张速查表,覆盖日常 90% 的场景:

场景命令
克隆一个仓库git clone <url>
查看当前状态git status
添加改动到暂存区git add <file> 或 git add .
提交git commit -m "说人话的提交信息"
推送到远程git push
拉取远程更新git pull
新建并切换分支git switch -c feature/xxx
查看提交历史git log --oneline --graph
撤销工作区改动git restore <file>
临时藏起改动git stash / git stash pop

新手三大坑

坑一:密码不好使了。 2021 年 8 月起,GitHub 不再支持用账号密码进行 Git 操作。你需要配置 SSH Key,或者使用 Personal Access Token。很多人第一次 push 就卡在这里,对着"Authentication failed"怀疑人生。

# 生成 SSH Key
ssh-keygen -t ed25519 -C "your_email@example.com"

# 把公钥内容复制到 GitHub → Settings → SSH and GPG keys
cat ~/.ssh/id_ed25519.pub

# 测试连接
ssh -T git@github.com

坑二:提交信息写成"update"。 打开某些仓库的历史,你会看到一整屏的 update、fix、改了点东西、再改一次、这次真的好了。三个月后的你,会想穿越回去打死现在的你。

推荐使用 Conventional Commits 规范:

feat: 新增用户登录功能
fix: 修复金额精度丢失问题
docs: 更新 README 安装说明
refactor: 重构订单状态机

坑三:把不该传的东西传了上去。 node_modules、target/、.idea/、.env……请在第一次提交前就写好 .gitignore。GitHub 新建仓库时可以直接选模板,别偷懒。


第三章:协作——Fork 与 Pull Request

如果说 Git 是 GitHub 的引擎,那 Pull Request(PR) 就是 GitHub 的灵魂。

想给一个开源项目贡献代码?你没有它的写权限,怎么办?流程是这样的:

sequenceDiagram participant Me as 我 participant Fork as 我的 Fork 仓库 participant Up as 原项目 (Upstream) participant CI as GitHub Actions participant Maintainer as 维护者 Me->>Up: 1. 点击 Fork Up-->>Fork: 复制一份到我的账号下 Me->>Fork: 2. clone、新建分支、修改代码 Me->>Fork: 3. push 到我的 Fork Me->>Up: 4. 发起 Pull Request Up->>CI: 5. 自动触发测试 CI-->>Up: 测试通过 ✅ Maintainer->>Up: 6. Code Review,提修改意见 Me->>Fork: 7. 根据意见修改,PR 自动更新 Maintainer->>Up: 8. Approve 并 Merge 🎉

几个过来人的建议:

  1. 先开 Issue 再写代码。 吭哧吭哧写了三天,提 PR 后维护者回一句"我们不打算加这个功能",那种感觉谁经历谁知道。
  2. 一个 PR 只做一件事。 一个改了 87 个文件、同时修 Bug 加功能顺手格式化代码的 PR,基本等于一份辞职信。
  3. 读 CONTRIBUTING.md。 大部分成熟项目都写了贡献指南,规定了代码风格、提交规范、CLA 签署等要求。

第四章:进阶——GitHub 不只是放代码的地方

GitHub Actions:免费的 CI/CD

在仓库里创建 .github/workflows/ci.yml,每次 push 就能自动跑测试:

name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Test
        run: go test ./... -race -cover

Actions 的玩法远不止 CI:定时爬数据、自动发版、自动给 Issue 打标签、自动同步博客……只要能写成脚本的事,基本都能塞进去。我见过有人用它每天自动签到、自动更新个人主页的"最近在读"。生命在于折腾,Actions 就是折腾的温床。

其他值得一用的能力

GitHub Pages:免费托管静态网站,很多技术博客、项目文档都放在上面。

Dependabot:自动扫描依赖漏洞,并直接帮你提一个升级 PR。对于依赖动辄几百个的 Java 和 Node 项目,这是救命功能。

Secret Scanning:检测你是否把 AWS Key、数据库密码之类的东西提交了上去。注意,它是"事后提醒",不是"事前拦截"的万能保险。

Codespaces:浏览器里打开一个完整的云端开发环境,适合临时改点东西,或者在 iPad 上假装自己在工作。

Releases 与 Packages:发布版本、托管 Docker 镜像和 Maven/npm 包。


第五章:GitHub vs GitLab——一个"叛徒"的自白

写到这里必须坦白一件事:在很长一段时间里,我其实是 GitLab 的忠实用户,而且是本地部署的那种。内网一台服务器,装上 Omnibus 包,配好域名和 Runner,所有代码、CI、镜像都在自己手里,那种掌控感非常踏实。

后来,我还是"叛逃"到了 GitHub。这一章就聊聊两者的区别,以及我为什么最终换了阵营。

GitLab 是谁

GitLab 诞生于 2011 年,比 GitHub 晚三年,走的是 Open Core(开放核心) 路线:核心功能以开源的社区版(CE)提供,高级功能放在付费的企业版(EE)里。

它最大的两个卖点:

  • 可以完整地自建部署:免费的社区版就能装在自己的服务器上,代码不出内网。
  • 一体化 DevOps 平台:代码托管、CI/CD、镜像仓库、项目管理、安全扫描,全部开箱即用,不需要拼凑第三方服务。

概念对照表

从 GitLab 迁移到 GitHub,第一件事是重新适应一套"方言":

维度GitHubGitLab
合并请求Pull Request(PR)Merge Request(MR)
组织结构Organization + Team,组织本身不能嵌套Group 可以多层嵌套 Subgroup
CI/CD 配置.github/workflows/*.yml,可以有多个.gitlab-ci.yml,一个入口文件
执行器GitHub 托管 Runner 或自托管 RunnerGitLab Runner
自建方案GitHub Enterprise Server(付费)社区版免费自建,企业版付费
平台本身是否开源否核心开源
插件生态Actions Marketplace,数量庞大模板和组件,相对较少
开源社区全球最大规模小得多
AI 能力Copilot 及编码 AgentGitLab Duo

同一条流水线,两种写法

同样是"推送后跑 Go 测试",两边的写法是这样的:

GitLab CI(.gitlab-ci.yml):

stages:
  - test

test:
  stage: test
  image: golang:1.22
  script:
    - go test ./... -race -cover

GitHub Actions(.github/workflows/ci.yml):

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.22'
      - run: go test ./... -race -cover

GitLab 的写法更"脚本化",直接指定镜像、写命令;GitHub 的写法更"积木化",大量步骤是 uses: 引用别人写好的 Action。前者直观,后者复用性强。说实话,单论 CI 的设计,我至今仍觉得 GitLab CI 的 stages 模型更清晰。

当年我为什么选 GitLab

公平地说,当年选 GitLab 一点都不冤:

  1. 代码不出内网:对安全和合规有要求的场景,自建是刚需,而 GitLab 社区版免费就能做到。
  2. CI 一体化:在 GitHub Actions 出现之前(Actions 是 2019 年才正式推出的),GitHub 要做 CI 得接 Travis、Jenkins 之类的第三方服务,而 GitLab 早就内置了。
  3. 多层 Group 管理项目方便:项目一多,按"部门/业务线/服务"多层组织,结构非常清楚。

为什么最终选择了 GitHub

第一,我不想再当 GitLab 管理员了。

本地部署的代价,是你得同时扮演开发者和运维。GitLab 升级不能随便跳版本,需要按官方的升级路径一站一站地升;每次大版本升级都伴随数据库迁移,升级前得先备份、心里还得默默祈祷。再加上 CI 产物和镜像仓库慢慢吃满磁盘、Runner 时不时罢工、证书到期忘了续……到后来我发现,自己伺候 GitLab 的时间,快赶上写代码的时间了。

迁到 GitHub 之后,这些事情全部消失了。省下的不是服务器钱,是心力。

第二,内网是孤岛,GitHub 是广场。

本地 GitLab 上的代码,只有自己和少数几个人能看到。而我日常依赖的开源库、想提 Issue 的项目、想学习的代码,全都在 GitHub 上。在两个平台之间来回切换,就像工作在一座城、生活在另一座城,每天通勤。

第三,Actions Marketplace 的复用红利。

在 GitLab 上,很多流水线步骤我都是自己写 shell 脚本。到了 GitHub,大部分常见需求——缓存依赖、构建 Docker 镜像、发布 Release、部署到云——都有现成的 Action,uses: 一行搞定。自己造轮子的乐趣是有的,但不是每个轮子都值得造。

第四,AI 时代投下了决定性的一票。

这是最关键的原因。Copilot 原生集成在 GitHub 里;主流的 AI 编码工具和 Agent,几乎都优先对接 GitHub 的 Issue、PR 和 Actions;MCP 生态里与 GitHub 相关的工具也是最丰富的。更微妙的一点是:大模型本身"读"过大量 GitHub 上的工作流和讨论,它们对 GitHub 的那一套操作天然更熟悉。

当你的 AI 同事都住在 GitHub,你也很难不搬过去。

第五,写作和分享的需要。

写博客之后,文章配套的示例代码需要一个读者能直接访问的地方。放在内网 GitLab 上,读者看不到;放在 GitHub 上,读者可以直接 clone、Star、提 Issue 指出我的错误。代码和文章形成了闭环。

怎么选?

flowchart TD Start([选 GitHub 还是 GitLab?]) --> Q1{代码必须留在内网,
或有严格的合规要求?} Q1 -->|是| Q2{预算充足,且看重
GitHub 生态?} Q2 -->|是| GHES[GitHub Enterprise Server] Q2 -->|否| GLSelf[自建 GitLab] Q1 -->|否| Q3{看重开源社区、
Actions 生态和 AI 工具?} Q3 -->|是| GH[GitHub] Q3 -->|否| Q4{想要一体化 DevOps,
又不想自己运维?} Q4 -->|是| GLCloud[GitLab.com] Q4 -->|否| GH

说句公道话

GitLab 并没有输。在金融、政企这类对数据主权和合规要求很高的场景,本地部署的 GitLab 至今仍是很多团队的首选,它的一体化体验和自建友好度,GitHub 也没能完全替代。

我换平台,本质上是因为我的场景变了:个人项目更需要的是社区、生态和 AI,而不是内网和管控。

如果非要打个比方:本地部署的 GitLab 像是自己盖的一栋房子,结构、门锁、水电全由你说了算,但漏水了也得你自己修;GitHub 像是住进了市中心的公寓,物业全包,楼下就是全世界的程序员,代价是房子不完全属于你。

年轻的时候想要一栋自己的房子。后来发现,我其实更想住得离人群近一点。


第六章:开源协议——代码公开 ≠ 开源

这一章是很多开发者的知识盲区,也是最容易给公司埋雷的地方。

一个反直觉的事实

放在 GitHub 上公开的代码,如果没有附带开源协议,默认是"保留所有权利"的。 你可以看,但在法律上不能随意复制、修改、商用。

所以如果你希望别人能用你的代码,请一定加一个 LICENSE 文件。

两大门派

开源协议大体分两派:

  • 宽松派(Permissive):随便用,保留版权声明就行,你拿去做闭源商业产品也没问题。代表:MIT、BSD、Apache 2.0。
  • 著佐权派(Copyleft,俗称"传染性"协议):你可以用、可以改,但如果分发修改后的版本,必须用同样的协议开源。代表:GPL、AGPL。

主流协议对比

协议可闭源商用修改后须开源明确专利授权代表项目
MIT✅❌❌React、Vue
BSD✅❌❌FreeBSD
Apache 2.0✅❌(需注明修改)✅Kubernetes、Kafka、Dubbo
MPL 2.0✅仅修改过的文件✅Firefox
LGPL✅(动态链接时)仅修改库本身时v3 有glibc
GPL v2/v3❌(分发时)✅ 整个衍生作品v3 有Linux、Git
AGPL v3❌✅ 网络提供服务也算✅Grafana、Nextcloud

几个关键区别

MIT 和 Apache 2.0 有什么不同? 都很宽松,但 Apache 2.0 多了专利条款:贡献者自动授予你相关专利的使用权;如果你反过来起诉别人说这个项目侵犯你的专利,你的授权会自动终止。这也是为什么企业主导的项目更喜欢 Apache 2.0。

GPL 的"SaaS 漏洞"。 GPL 只在分发软件时才要求开源。如果你修改了 GPL 代码,只部署在自己的服务器上提供服务,不把软件分发给别人,那就不触发开源义务。

AGPL 专门堵这个漏洞。 只要用户通过网络使用了你的服务,就视同分发。所以很多做商业后端系统的公司,在合规清单里会直接把 AGPL 标红——引入一个 AGPL 组件,理论上可能要求你把整个服务的源码都交出来。

怎么选?

flowchart TD Start([我要给项目选协议]) --> Q1{希望别人修改后
也必须开源吗?} Q1 -->|不在乎,越多人用越好| Q2{关心专利问题吗?} Q2 -->|不关心,要最简单的| MIT[MIT] Q2 -->|关心,企业级项目| Apache[Apache 2.0] Q1 -->|是的,改进要回馈社区| Q3{主要以什么形式被使用?} Q3 -->|作为库被别人引用| LGPL[LGPL / MPL 2.0] Q3 -->|作为独立软件分发| GPL[GPL v3] Q3 -->|作为网络服务运行| AGPL[AGPL v3]

近几年的"协议战争"

最近几年还有一个趋势值得关注:一些明星开源公司把协议改成了"源码可见但限制商用"的类型,比如 BSL、SSPL、Elastic License。原因很现实——云厂商直接拿开源项目包装成托管服务卖钱,原作者却赚不到。

结果就是社区分裂出了一批分支:Elasticsearch 之后有了 OpenSearch,Redis 之后有了 Valkey,Terraform 之后有了 OpenTofu。开源世界的政治斗争,一点不比公司内部少。


第七章:AI 时代——GitHub 为什么变得更重要了

如果说过去十几年 GitHub 是"程序员的社交网络",那么在 AI 时代,它正在变成三样东西。

1. AI 的教科书

大模型为什么会写代码?很大程度上是因为它们读过海量的公开代码、Issue 讨论、PR 评审和文档。而这些内容,相当一部分就来自 GitHub。

这也带来了新的争议:如果模型学习了 GPL 协议的代码,再生成相似的代码给闭源产品用,算不算违反协议?围绕 Copilot 的诉讼争的就是这个问题,目前法律层面还没有完全定论。开源协议这门"老学问",在 AI 时代突然变得前沿起来。

2. AI 开源生态的主战场

PyTorch、Transformers、llama.cpp、vLLM、Ollama、LangChain,以及层出不穷的 Agent 框架和 MCP Server,几乎都在 GitHub 上开发。

AI 领域的迭代速度快到离谱:一篇论文早上挂出来,下午 GitHub 上就有复现,晚上就有人提了优化 PR,第二天已经有三个衍生项目。这种速度,离不开 GitHub 这套开放协作的基础设施。

(顺便一提,模型权重和数据集更多托管在 Hugging Face,可以理解为"AI 界的 GitHub",两者是互补关系。)

3. 人与 AI Agent 的协作工作台

这是我认为最有意思的变化。

仔细看 GitHub 的核心流程:Issue 描述需求 → 分支隔离改动 → PR 提交变更 → CI 自动验证 → 人工 Review → 合并。这套流程本来是为人与人协作设计的,但它恰好也是 AI Agent 写代码最需要的"安全护栏"。

flowchart LR I[📋 Issue
人类描述需求] --> A[🤖 AI Agent
领取任务] A --> B[🌿 新建分支
编写代码] B --> P[📬 提交 PR] P --> C{⚙️ CI 自动测试} C -->|失败| A C -->|通过| R{👀 人类 Review} R -->|要求修改| A R -->|批准| M[✅ 合并到 main]

AI 可以生成代码,但它会犯错;分支保证错误不会污染主线,CI 保证基本质量,Review 保证最终有人负责。当写代码的成本趋近于零,审查、验证和信任反而成了最稀缺的东西,而 GitHub 恰好掌握着这一整套机制。

所以现在的情况是:你给一个 Issue 打上标签分配给 AI,过一会儿它就提了个 PR 过来,测试全绿,描述写得比你还清楚。

这时候你的心情,大概就是本文标题的后半句。


第八章:放弃——那些让人想删库跑路的瞬间

说了这么多正经的,终于到了本系列的保留节目。以下是我亲身经历或亲眼目睹的"放弃时刻",以及对应的急救方案。

放弃姿势一:merge conflict

<<<<<<< HEAD
BigDecimal amount = order.getAmount();
=======
long amount = order.getAmountInCents();
>>>>>>> feature/refactor

两个人改了同一行,Git 不知道听谁的,于是把选择权交还给你。急救:冷静,读懂两边的意图,手动保留正确的版本,删掉标记,git add 后提交。多人协作时,小步提交、频繁同步主干,是减少冲突的唯一正道。

放弃姿势二:rebase 之后,提交"消失"了

git rebase -i 是整理提交历史的神器,也是新手的噩梦。一顿操作之后,git log 里少了几个提交,冷汗直流。

急救:git reflog。它记录了 HEAD 的每一次移动,几乎所有"丢失"的提交都能在这里找回来:

git reflog
# 找到操作前的那条记录,例如 HEAD@{5}
git reset --hard HEAD@{5}

记住这句话:在 Git 里,只要提交过,就很难真正丢失。 reflog 是你最后的后悔药。

放弃姿势三:把密钥提交上去了

这个最可怕。很多人的第一反应是再提交一次把它删掉——没用,它还在历史记录里,任何人翻历史都能看到。公开仓库里泄露的密钥,可能几分钟内就被自动化爬虫扫走。

急救的正确顺序是:

  1. 立刻作废并轮换这个密钥(这是最重要的一步,比清理历史优先得多)
  2. 再用 git filter-repo 之类的工具清理历史
  3. 以后用环境变量或密钥管理服务,并把 .env 写进 .gitignore

放弃姿势四:git push --force 到 main

你本地的历史和远程不一致,push 被拒绝,你一怒之下加了 --force。然后,同事们过去两天的提交都没了。

急救:让还保留着完整历史的同事重新 push,或者从 reflog 里捞。预防:在 GitHub 的仓库设置里给 main 开启分支保护(Branch Protection),禁止 force push,要求必须通过 PR 合并。另外,真要强推,请用 git push --force-with-lease,它会在远程有你没见过的新提交时拒绝覆盖。

放弃姿势五:一个只有 Initial commit 的仓库

* a1b2c3d Initial commit

这是最常见、也最温柔的放弃方式。每个人的 GitHub 主页上都有几个这样的仓库:宏大的项目名、精心写好的 README、清晰的规划,然后……就没有然后了。

急救:无。这是程序员的浪漫。


写在最后

一路写下来我发现,GitHub 其实是三层东西叠在一起的:

  • 最底层是 Git:记录代码的历史,让你敢于折腾。
  • 中间是协作:Fork、PR、Review、CI,让全世界的陌生人能围绕一份代码协作。
  • 最上层是规则:开源协议定义了协作的边界,而 AI 正在成为这个协作网络里的新成员。

至于标题里的"放弃"——我想通了,学 GitHub 最终要放弃的不是 GitHub,而是两样东西:一是放弃"代码写完就完事"的想法,因为真正的工程是协作、验证和维护;二是放弃抵抗,因为在 AI 时代,你的代码、你的工作流、甚至你的 AI 同事,大概率都住在这里了。

好了,我要去给那几个只有 Initial commit 的仓库补点代码了。

大概吧。


生命在于折腾。Coding the world!

—— 乔人尹

评论

还没有评论,来说点什么吧。

发表评论

Ctrl + Enter 快速提交
Enter 搜索Esc 关闭