1、为什么"没有版本控制"是灾难

先看一个真实场景。

一个三人小组做课程设计,共享文件夹放在网盘上。两周后,文件夹里是这些文件名:

main.py
main_最终版.py
main_最终版2.py
main_最终版_真的最终.py
main_别动这个_能跑.py

最后谁能跑通、改了什么、为什么改,没人说得清。这就是没有版本控制的代价。

版本控制(Version Control)解决三件事:

  1. 历史:每次改动都有记录,随时能回到任何一个版本。
  2. 协作:多人同时改同一份代码,不会互相覆盖。
  3. 追溯:出问题时能查到"哪次改动引入的、为什么改"。

Git 是目前事实上的标准。它不只是"备份工具",而是团队协作的基础设施——后面第 13 课的分支评审、第 16 课的 CI,全都建立在 Git 之上。

2、Git 的心智模型:三个区域

新手最困惑的是"改了文件为什么没生效"。根源是没搞清 Git 的三个区域:

工作区(Working Directory)    你编辑的文件,所见即所得
    │  git add
    ▼
暂存区(Staging Area)         准备提交的改动清单
    │  git commit
    ▼
本地仓库(Repository)         .git 里的历史快照
    │  git push
    ▼
远程仓库(Remote)              GitHub / GitLab,团队共享

关键理解:

  • git add 不是"保存文件",是"把改动放进下次提交的清单"。
  • git commit 是"给当前暂存区拍一张快照",只影响本地。
  • git push 才是"同步给团队"。

一次改动的完整旅程:

git status              # 看现在处于什么状态(最常用的命令)
git diff                # 看工作区改了啥(还没 add 的)
git add main.py         # 把改动放进暂存区
git diff --staged       # 看暂存区里将提交的内容
git commit -m "修复登录时空密码报错"   # 拍快照
git push                # 同步到远程

3、提交(Commit):小步、原子、信息清楚

提交是 Git 的最小历史单位,它的质量直接决定历史好不好用。

黄金法则:一次提交只做一件事(原子性)。

❌ 反例——一次提交干了五件事:

commit a1b2c3d
"改了点东西:加了登录、修了样式、升级依赖、删了旧文件、顺手改了 README"

结果:想回滚"升级依赖"这一件事,做不到,因为和别的改动混在一起了。

✅ 正例——拆成五个提交:

commit e5f6a7b  "feat: 新增用户登录接口"
commit d4c3b2a  "style: 调整登录页按钮样式"
commit c3b2a1f  "chore: 升级 axios 到 1.7"
commit b2a1f9e  "chore: 删除废弃的 legacy 目录"
commit a1f9e8d  "docs: 补充登录流程说明"

提交信息规范(Conventional Commits):

<类型>(<范围>): <简述>

<可选正文:为什么这么改,而非改了什么>
<可选脚注:关联 issue,如 Closes #42>
类型含义
feat新功能
fix修 bug
docs文档
refactor重构(不改外部行为)
test测试
chore杂务(构建、依赖)
style格式(不影响逻辑)
perf性能优化

为什么信息要规范? 因为提交历史可以被机器解析——自动生成 changelog、自动判断版本号(语义化版本)、快速定位引入 bug 的提交,都靠它。

写提交信息的三条标准:

  1. 一句话说清做了什么(标题≤50 字符)。
  2. 需要时补为什么(动机比改动本身更重要)。
  3. 别写"update""fix bug""修改一下"这种废话。

4、分支(Branch):平行世界的隔离

分支就是一条独立的开发线,在上面折腾不影响主线。

main    ──●──●──────────●──●──      (稳定,随时可发布)
              \        /
feature/login   ●──●──●              (开发登录,实验完再合并)

基本操作:

git branch feature/login      # 创建分支
git switch feature/login      # 切换过去
git switch -c feature/login   # 创建并切换(一条命令)
git switch main               # 切回主线
git branch -d feature/login   # 开发完删掉它

为什么必须用分支?

  • 在 main 上直接开发,写错代码就污染了稳定版本。
  • 分支让你大胆实验:写崩了直接删掉分支,主线毫发无伤。
  • 多人协作时,各人各分支,靠合并(merge)汇合。

一个直觉:main(或 master)应该永远处于可运行、可发布的状态。任何未完成、有风险的改动都待在分支上,完成并验证通过后才合回主线。

5、合并与冲突:Git 最让人紧张的环节

当两个分支都改了同一处代码,合并时 Git 无法自动决定用哪一个,这就是冲突(Conflict)。

冲突长这样:

<<<<<<< HEAD
    return user.email
=======
    return user.contact_email
>>>>>>> feature/profile

<<<<<<< 到 ======= 是当前分支的内容,======= 到 >>>>>>> 是被合并分支的内容。

解决冲突四步:

  1. 理解两边各自为什么这么改(看提交信息、问作者)。
  2. 手动编辑成正确的结果(可能是一边、另一边,或是融合两者的新写法)。
  3. 删掉 <<<<<<<、=======、>>>>>>> 标记。
  4. git add 冲突文件 → git commit 完成合并。

降低冲突痛苦的做法:

  • 勤拉取:git pull 要频繁,别攒一周才合一次。
  • 小改动:分支生命周期越短,冲突越少。
  • 别乱动格式:全文件重排缩进、改行尾符,会让 diff 全是噪声,冲突爆炸(这也是第 14 课强调统一格式的原因之一)。
  • 一个人别同时改同一文件同一区域,能从分工上避免就从分工上避免。

6、回退与救命:Git 的"安全网"

这是 Git 最让人安心的价值:犯错几乎总能挽回。

我想……用什么说明
撤销未暂存的改动git restore file把工作区文件还原到上次提交
撤销已暂存的改动git restore --staged file从暂存区取出,改动仍在工作区
修改最后一次提交信息git commit --amend只改最近一次,别用于已推送的提交
回退到某个历史提交git revert <hash>安全:生成一个新提交抵消旧提交
看历史git log --oneline --graph图形化看分支与合并
找回"丢失"的提交git reflog记录所有 HEAD 移动,救命神器

关键区分:revert vs reset

  • git revert:再做一个反向提交,历史保留,适合已推送的公共分支。
  • git reset:直接移动分支指针,会改写历史,只在自己未推送的本地分支上用。
铁律:已经 push 到共享分支的提交,不要用 reset 改写历史——队友的本地历史会和你对不上,制造灾难。用 revert。

7、.gitignore 与别把秘密提交上去

有些东西不该进版本库:

  • 依赖目录(node_modules/、venv/)——用包管理器的清单文件还原即可
  • 构建产物(dist/、target/、*.o)
  • 本地配置与密钥(.env、*.key、credentials.json)
  • 系统/编辑器文件(.DS_Store、.idea/)

写成 .gitignore:

node_modules/
dist/
.env
*.key
.DS_Store
__pycache__/

最危险的错误:把密钥提交上去。 一旦提交,即使后来删除,历史里仍然能翻出来——GitHub 上有机器人专门扫描泄露的密钥。

如果真提交了密钥,处理顺序:

  1. 立刻作废并轮换那个密钥(这是最重要的一步,历史清理是次要的)。
  2. 从历史中清除(用 git filter-repo 等工具)。
  3. 反思流程:为什么密钥会出现在代码里?(用环境变量/密钥管理,见第 14 课)

8、动手实验 🎯

任务:把一次真实开发用 Git 完整走一遍。

  1. 新建一个仓库,配置好 .gitignore(把依赖、构建产物、.env 排除)。
  2. 完成一次原子提交练习:故意把三个不相关的改动分开提交,各自写规范的提交信息(含类型前缀)。
  3. 创建一个功能分支,写点代码,再切回 main 观察两边的差异(用 git log --oneline --graph)。
  4. 制造一次冲突:在两条分支上改同一文件的同一行,合并,然后按"冲突解决四步"处理掉。
  5. 练习救命三招:restore 撤销未暂存改动、revert 撤销一个已提交的改动、reflog 找回一次误操作的提交。
  6. 写一份你自己的提交信息规范(类型清单 + 3 条写作要求),贴到项目 README 或团队 Wiki。

做完这步,你就掌握了代码协作的底座——下一课讲怎么在这个底座上组织团队工作流:分支策略与代码评审。

9、今天你带走的三句话

  1. Git 的核心是三个区域:工作区 →(add)→ 暂存区 →(commit)→ 仓库 →(push)→ 远程;git status 是你最该常敲的命令。
  2. 提交要小步、原子、信息清楚;main 永远可发布,风险改动都待在分支上。
  3. 已推送的提交别用 reset 改写历史,用 revert;密钥永不入库,进了库先轮换再清历史。

下一课进入分支策略与代码评审——把"个人的 Git"升级成"团队的协作流"。

标签: 软件工程, 项目管理, 开发流程

添加新评论