Git 与托管
这一课你将能做到
- 独立完成「建分支 → 提交 → 推送 → Pull Request → 合并」的完整团队工作流
- 遇到合并冲突时,不慌张地手动解决并完成合并
- 写出符合 Conventional Commits 规范的提交信息,并说出代码评审(Code Review)都看什么
为什么要学
后端代码是团队资产:同一份服务多人同时改,改坏了要能回到昨天能跑的版本,出了线上事故要能定位是哪次提交引入的。没有 Git 的团队等于在悬崖边上不系安全绳。
人话:Git 是代码的「时间机器 + 并行宇宙」。时间机器让你随时回到任何一个历史版本;并行宇宙(分支)让你在不影响主线的情况下大胆尝试。GitHub/GitLab 则是把你的仓库放到云端,团队协作的枢纽。
面试与实战都绕不开:入职第一件事就是配 Git 权限,第一个任务就是在 feature 分支上改一个小需求然后提 PR。
先建立图像
Git 本地有三个「区域」,理解它们,90% 的 Git 疑惑就消失了:
工作区 暂存区 本地仓库 远程仓库
(你正在编辑的文件) (git add 后) (git commit 后) (git push 后)
┌──────────┐ add ┌──────────┐ commit ┌──────────┐ push ┌──────────┐
│ notes.js │ ─────▶ │ 待打包的 │ ──────▶ │ v1,v2,v3 │ ──────▶ │ GitHub │
│ 已修改 │ │ 变更快照 │ │ 历史版本 │ │ GitLab │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
git add:挑选本次要提交的变更(可以只挑一部分文件)
git commit:打包成一个带说明的版本快照
git push:同步到远端,让团队看见
跟着做
步骤 1:安装与最小配置
从 git-scm.com 安装(Windows 装 Git Bash),然后配置身份——每个提交都会署名:
git config --global user.name "你的名字"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main # 新仓库默认分支叫 main
步骤 2:创建仓库,理解三区
mkdir git-demo && cd git-demo
git init # 初始化仓库,出现隐藏的 .git 目录
echo "console.log('v1')" > app.js
git status # 红色:app.js 未跟踪
git add app.js # 进入暂存区
git status # 绿色:已暂存
git commit -m "feat: 初始化应用脚本" # 打包成第一个版本
git log --oneline # 查看提交历史,每行一个版本
git status 是你最好的朋友——任何时候不知道自己处于什么状态,先敲它。
步骤 3:分支操作
git branch # 查看分支,* 表示当前所在
git switch -c feature/add-greeting # 创建并切换到新分支(-c 即 create)
echo "console.log('hello from feature')" >> app.js
git add app.js
git commit -m "feat: 增加问候语输出"
git switch main # 切回 main,文件里没有那行——各分支互不干扰
git merge feature/add-greeting # 合并进 main(默认快进合并)
git branch -d feature/add-greeting # 合并完删掉分支
步骤 4:制造并解决一次冲突
冲突只发生在「两个分支改了同一文件的同一处」时,练一次就不怕了:
git switch -c feature/change-message
# 把 app.js 第一行改成 console.log('from feature'),然后:
git commit -am "feat: 修改启动信息为 feature 版" # -a 直接暂存已跟踪文件的修改
git switch main
# 把同一行改成 console.log('from main'),然后:
git commit -am "feat: 修改启动信息为 main 版"
git merge feature/change-message
# CONFLICT (content): Merge conflict in app.js
<<<<<<< HEAD
console.log('from main')
=======
console.log('from feature')
>>>>>>> feature/change-message
手动编辑成你想要的最终内容(比如保留一行 console.log('from main')),删掉所有 <<<<<<<、=======、>>>>>>> 标记,然后:
git add app.js
git commit -m "merge: 解决启动信息冲突,保留 main 版本"
步骤 5:推送到 GitHub,走一次 PR 流程
- 在 GitHub 上新建空仓库
git-demo(不要勾选 README)
- 关联并推送:
git remote add origin https://github.com/你的用户名/git-demo.git
git push -u origin main # -u 记住关联,以后直接 git push
git switch -c feature/readme
echo "# Git Demo" > README.md
git add README.md
git commit -m "docs: 添加 README"
git push -u origin feature/readme
- 打开 GitHub 仓库页面,点黄色提示条上的 Compare & pull request,写清标题与描述,点 Create pull request
- (模拟他人)在 Files changed 页面留一条评论 → 回到自己的终端补一个修改并 push(PR 会自动更新)→ 点 Merge pull request
企业里 PR 就是这么走的,只是评审人是同事而不是你自己。
步骤 6:撤销操作——先学安全的 revert
git log --oneline # 找到想撤销的提交哈希,如 a1b2c3d
git revert a1b2c3d # 生成一个「反向提交」,历史不被改写,安全
git revert 是撤销已推送提交的正确方式;git reset --hard 只适合撤销本地未推送的修改,用它改公共历史会坑全组人。
讲清楚要点
- 分支策略:小团队主流是 GitHub Flow——
main 永远可部署,所有改动走 feature 分支 + PR + 评审后合并;大型项目用 Git Flow(main + develop + feature/release/hotfix 多分支)。初学者先把 GitHub Flow 玩熟。
- Commit 规范(Conventional Commits):格式
类型: 描述,常见类型 feat(新功能)、fix(修 bug)、docs、refactor、test、chore。好处是历史可读、可自动生成变更日志。
- 小步提交:一个 commit 只做一件事。出了问题能精确回滚,评审的人也能快速看懂。500 行混杂大提交是评审灾难。
- 代码评审看什么:正确性(逻辑边界)、可读性(命名、结构)、安全(注入、密钥泄漏)、测试(是否补了用例)、性能(N+1、慢查询)。评审对事不对人,被提意见不是被否定。
- merge vs rebase:merge 保留分支历史、产生合并提交;rebase 把你的提交「搬到」目标分支末尾,历史更线性,但会改写提交,已推送的公共分支禁止 rebase。
你可能会懵的地方
-
git pull 和 git fetch 有什么区别?
fetch 只把远端新提交下载到本地仓库,不动你的工作区;pull = fetch + merge,直接合并到当前分支。养成 fetch 后先看 git log origin/main..main 再决定怎么合并的习惯,能避免很多意外。
-
clone 和 fork 有什么区别?
clone 是把仓库复制到本地;fork 是在 GitHub 上把别人的仓库复制成「自己账号下的仓库」。给开源项目贡献代码:fork → clone 自己的 fork → 改 → push 到自己的 fork → 向原仓库发 PR。
-
为什么我的提交作者显示不对?
提交署名来自 user.name/user.email,公司项目常要求用企业邮箱。仓库级覆盖:git config user.email "工作邮箱"(去掉 --global)。
-
.gitignore 是干什么的?
声明哪些文件不进版本库:node_modules/、.env(密钥!)、日志、编译产物。必须在第一次提交前就配好,密钥一旦提交,即使后面删掉,历史里仍然找得到。
-
HEAD 是什么?
指向「你当前所在的提交」的指针。HEAD~1 是上一个提交,git diff HEAD 看当前与最近提交的差异。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 所有开发直接在 main 上进行 | 无法评审、无法回滚单个功能 | feature 分支 + PR |
git commit -m "update" | 三个月后没人知道改了什么 | fix: 修复订单金额计算精度问题 |
git push -f 到共享分支 | 覆盖别人的提交,团队事故 | 强推只允许在自己的 feature 分支 |
用 reset --hard 撤销已推送提交 | 改写公共历史,协作者全乱 | 用 git revert |
提交 .env、密钥、大文件 | 泄漏风险 + 仓库膨胀 | 写进 .gitignore,密钥用环境变量 |
| 半年才 merge 一次分支 | 冲突堆积如山 | 分支存活不超过 2-3 天,勤拉 main |
卡点急救
- push 被拒
! [rejected] ... fetch first:远端有别人新提交。git pull --rebase 后再 push;有冲突就按步骤 4 解决。
- 不小心 commit 了不该提交的文件(还没 push):
git reset --soft HEAD~1 撤销提交(保留改动),重新选择性 add。
- 误改一堆文件想全部还原(未提交):
git restore .,注意这会丢弃工作区修改,不可恢复。
- GitHub push 要求登录:密码已废弃,用 Personal Access Token(Settings → Developer settings → Tokens)当密码,或配置 SSH key。
- 每次提交都提示换行符警告(LF/CRLF):Windows 上执行
git config --global core.autocrlf true,macOS/Linux 用 input,警告无害但统一配置可减少无意义 diff。
面试常问
-
merge 和 rebase 的区别与选择?
merge 保留完整分支拓扑、安全不改历史;rebase 线性干净但改写提交。个人未推送的分支 rebase 到最新 main 后再提 PR,历史整洁;公共分支一律 merge。
-
如何撤销一次已经 push 的错误提交?
git revert <hash> 生成反向提交再 push,绝不 reset 改写公共历史。若是敏感信息泄漏,revert 不够,需要历史清除 + 立即换密钥。
-
hotfix 流程怎么走?
从 main(或最新生产 tag)拉 hotfix/xxx 分支 → 修复 + 补测试 → PR 加急评审 → 合并 → 发布 → 按需同步回开发分支。
-
Git 内部的对象模型了解吗?
commit → tree → blob 三层对象:commit 记录快照指针与父提交,tree 记录目录结构,blob 存文件内容。这解释了为什么 Git 分支只是「一个指向 commit 的可移动指针」,建分支几乎零成本。
自测三问
- 工作区、暂存区、本地仓库、远程仓库分别对应哪条命令的流转?
git revert 和 git reset --hard 的本质区别是什么?各适合什么场景?
- 给团队写一份 5 条以内的 Git 提交规范(类型 + 描述格式 + 粒度要求)。
小结一句话
Git 用三区模型管理变更、用分支支持并行开发、用 PR 保证团队质量;把「小步提交 + 规范信息 + 评审合并」变成肌肉记忆,协作就不慌。
延伸