软件项目开发全流程 · 第 12 课 · 版本控制入门:Git 核心概念
1、为什么"没有版本控制"是灾难
先看一个真实场景。
一个三人小组做课程设计,共享文件夹放在网盘上。两周后,文件夹里是这些文件名:
main.py
main_最终版.py
main_最终版2.py
main_最终版_真的最终.py
main_别动这个_能跑.py最后谁能跑通、改了什么、为什么改,没人说得清。这就是没有版本控制的代价。
版本控制(Version Control)解决三件事:
- 历史:每次改动都有记录,随时能回到任何一个版本。
- 协作:多人同时改同一份代码,不会互相覆盖。
- 追溯:出问题时能查到"哪次改动引入的、为什么改"。
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 的提交,都靠它。
写提交信息的三条标准:
- 一句话说清做了什么(标题≤50 字符)。
- 需要时补为什么(动机比改动本身更重要)。
- 别写"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<<<<<<< 到 ======= 是当前分支的内容,======= 到 >>>>>>> 是被合并分支的内容。
解决冲突四步:
- 理解两边各自为什么这么改(看提交信息、问作者)。
- 手动编辑成正确的结果(可能是一边、另一边,或是融合两者的新写法)。
- 删掉
<<<<<<<、=======、>>>>>>>标记。 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 上有机器人专门扫描泄露的密钥。
如果真提交了密钥,处理顺序:
- 立刻作废并轮换那个密钥(这是最重要的一步,历史清理是次要的)。
- 从历史中清除(用
git filter-repo等工具)。 - 反思流程:为什么密钥会出现在代码里?(用环境变量/密钥管理,见第 14 课)
8、动手实验 🎯
任务:把一次真实开发用 Git 完整走一遍。
- 新建一个仓库,配置好
.gitignore(把依赖、构建产物、.env排除)。 - 完成一次原子提交练习:故意把三个不相关的改动分开提交,各自写规范的提交信息(含类型前缀)。
- 创建一个功能分支,写点代码,再切回
main观察两边的差异(用git log --oneline --graph)。 - 制造一次冲突:在两条分支上改同一文件的同一行,合并,然后按"冲突解决四步"处理掉。
- 练习救命三招:
restore撤销未暂存改动、revert撤销一个已提交的改动、reflog找回一次误操作的提交。 - 写一份你自己的提交信息规范(类型清单 + 3 条写作要求),贴到项目 README 或团队 Wiki。
做完这步,你就掌握了代码协作的底座——下一课讲怎么在这个底座上组织团队工作流:分支策略与代码评审。
9、今天你带走的三句话
- Git 的核心是三个区域:工作区 →(add)→ 暂存区 →(commit)→ 仓库 →(push)→ 远程;
git status是你最该常敲的命令。 - 提交要小步、原子、信息清楚;
main永远可发布,风险改动都待在分支上。 - 已推送的提交别用
reset改写历史,用revert;密钥永不入库,进了库先轮换再清历史。
下一课进入分支策略与代码评审——把"个人的 Git"升级成"团队的协作流"。