Git 基础教程:原理、常见操作与企业实践规范
Git 是目前最主流的分布式版本控制系统,几乎所有软件团队都在用。但很多人停留在 add / commit / push 三板斧,遇到冲突或误操作就不知道怎么处理。这篇从原理出发,把常见操作和企业实践规范串起来。
Git 的三个区域和分布式原理
工作区、暂存区、本地仓库
Git 把文件状态分成三个区域,理解这个是所有操作的基础:
1 | 工作区(Working Directory) |
- 工作区:你实际编辑文件的目录
- 暂存区:
git add后文件进入这里,是一个”预提交缓冲区”,可以精确控制这次 commit 包含哪些改动 - 本地仓库:
git commit后改动被永久记录,存在.git/objects/目录 - 远程仓库:
git push后同步到 GitHub/GitLab 等远端
暂存区是很多人忽略的设计:它让你可以把一个大改动拆分成多个有意义的 commit,而不是把所有修改塞进一个 commit。
分布式的含义
Git 是分布式的,每个开发者本地都有完整的仓库历史,不依赖中央服务器就能查历史、切分支、提交。这和 SVN 等集中式系统不同——SVN 断网就不能提交。
git clone 会把远端的完整历史都拉下来,所以 git log 是本地操作,不需要网络。
Git 对象模型
Git 内部用四种对象存储所有内容,每个对象用其 SHA-1 哈希值唯一标识:
| 对象类型 | 存储内容 |
|---|---|
| blob | 文件内容(不含文件名) |
| tree | 目录结构(文件名 + blob 引用) |
| commit | 提交信息 + tree 引用 + parent commit 引用 |
| tag | 附注标签(指向 commit) |
一个 commit 对象大概长这样:
1 | tree 8f3b2a... # 指向这次提交的目录快照 |
branch 和 HEAD 是什么:branch 只是一个指向某个 commit 的指针(存在 .git/refs/heads/ 里),HEAD 是当前所在分支的指针。切换分支不是”复制文件”,只是移动指针,速度极快。
常见操作速查
初始化和克隆
1 | # 初始化新仓库 |
查看状态和差异
1 | # 查看工作区和暂存区的状态 |
add 和 commit
1 | # 把指定文件加入暂存区 |
git add -p 是个容易被忽视的功能:当一个文件有多处修改,但只想把其中一部分提交时,用它可以逐块选择。
分支操作
1 | # 查看本地分支 |
合并和变基
1 | # 合并分支(在 main 分支上执行,把 feature/login 合并进来) |
merge vs rebase 的区别:
merge保留完整历史,会生成一个 merge commit,清楚地记录”这两条线在这里合并了”rebase重写 feature 分支的提交历史,让它看起来像是在最新的 main 上线性开发的,历史更干净,但会改变 commit 的 SHA
已推送到远端的分支不要 rebase,rebase 会改变提交历史,会和别人的本地仓库产生冲突。
远端操作
1 | # 查看远端仓库配置 |
暂存工作区:stash
git stash 把当前工作区和暂存区的改动临时存起来,让工作区恢复干净状态,适合”临时切换分支处理紧急 bug”的场景。
1 | # 把当前改动暂存起来 |
回滚操作
1 | # 撤销工作区的修改(恢复到上次 commit 的状态,改动丢失) |
reset --hard 会直接丢弃工作区改动,执行前确认好。已经 push 的提交用 revert 更安全,它不改历史,而是新增一个撤销的 commit。
分支策略:Git Flow 与 GitHub Flow
多人协作时,如果每个人随意创建分支、随意往 main 合并,很快就会出现以下问题:main 上有未测试的代码导致线上崩溃;不知道哪个分支是”当前最新”的;hotfix 合到了错误的地方,修复没生效。
分支策略就是团队约定好”什么分支做什么事、从哪切出、合到哪里去”,把协作流程标准化,避免上面这些混乱。不同规模和发布节奏的团队适合不同的策略,没有统一答案,但必须有一套。
Git Flow
Git Flow 是经典的企业分支模型,有两条长期分支和三类临时分支:
长期分支:
main(或master):只存放生产环境的稳定代码,每次更新都打 tagdevelop:集成分支,所有功能开发完成后先合并到这里
临时分支(用完即删):
feature/xxx:从 develop 切出,功能开发完合回 developrelease/x.x.x:从 develop 切出,做最终测试和 bug 修复,完成后合并到 main 和 develophotfix/xxx:从 main 切出,紧急修复生产 bug,完成后合并到 main 和 develop
适合场景:有明确版本节奏的项目(比如定期发版的 App、SDK)。
缺点:分支多、流程重,小团队或持续交付场景下嫌麻烦。
GitHub Flow
GitHub Flow 更轻量,只有一条规则:main 随时可部署,所有改动通过 PR 合并。
1 | main(始终可部署) |
流程:
- 从 main 创建 feature 分支
- 在 feature 分支开发、提交
- 发起 PR,Code Review
- CI 通过后合并到 main
- 立即部署
适合场景:持续交付、小团队、Web 服务(随时上线)。
怎么选
| 场景 | 推荐 |
|---|---|
| 有固定发版周期(App、SDK) | Git Flow |
| 持续部署、随时上线 | GitHub Flow |
| 小团队、快速迭代 | GitHub Flow |
| 需要维护多个版本 | Git Flow |
企业实践规范
commit message 规范:Conventional Commits
混乱的 commit message 让 git log 毫无意义,也没法自动生成 changelog。Conventional Commits 是目前最主流的规范:
1 | <type>(<scope>): <subject> |
常用 type:
| type | 含义 |
|---|---|
feat |
新功能 |
fix |
Bug 修复 |
docs |
文档改动 |
refactor |
重构(不影响功能) |
test |
测试相关 |
chore |
构建、依赖、CI 等杂项 |
perf |
性能优化 |
revert |
撤销某次提交 |
示例:
1 | feat(auth): add Google OAuth login |
实践建议:
- subject 用英文或中文均可,保持团队统一就行
- subject 不超过 72 个字符
- 用祈使句(”add”,不是”added”或”adding”)
- 破坏性改动在 footer 加
BREAKING CHANGE: 说明
可以用 commitlint 在 git hook 里强制校验格式。
分支命名规范
1 | feature/issue-42-user-login # 新功能(关联 issue 号) |
关联 issue 号的好处:在 GitHub/GitLab 上可以直接从分支名跳转到对应 issue,review 时方便查背景。
保护主分支
在 GitHub/GitLab 上给 main 开启 branch protection:
- 禁止直接 push,所有改动必须通过 PR
- PR 合并前必须通过 CI(单测、lint、构建)
- 至少一个 Code Review 通过才能合并
- 禁止 force push
这几条规则能防止 90% 的低级事故。
PR/MR 规范
一个好的 PR 应该:
- 只做一件事:一个 PR 只解决一个问题,越小越容易 review
- 有清晰的描述:说明改了什么、为什么改、怎么测试
- 关联 issue:在描述里加
Closes #42,合并后自动关闭 issue - 自己先 review 一遍:提交前 diff 看一眼,去掉调试代码和无关改动
.gitignore 配置
.gitignore 要在项目初始化时就配好,不要等到不该提交的文件已经进了仓库再处理。
常见需要忽略的内容:
1 | # 依赖 |
gitignore.io 可以按语言和框架自动生成。
常见问题处理
误操作恢复:git reflog
git reflog 记录了 HEAD 的所有移动历史,包括 reset --hard 这类”危险操作”之后的状态。只要没有 git gc,几乎所有误操作都能恢复。
1 | # 查看 HEAD 的操作历史 |
reset --hard 把工作区搞没了?先跑 git reflog,找到那个 commit 的哈希,reset --hard 回去就行。
解决合并冲突
冲突发生时 Git 会在文件里标记冲突区域:
1 | <<<<<<< HEAD |
处理步骤:
- 用编辑器打开冲突文件,选择保留哪一方或手动合并
- 删除所有
<<<<<<<、=======、>>>>>>>标记 git add标记冲突已解决git commit完成合并
1 | # 查看哪些文件有冲突 |
如果冲突太复杂,可以用 git mergetool 调出可视化合并工具(需要先配置)。
修改最近一次 commit
1 | # 修改最近一次 commit 的 message |
--amend 会重写最近一次 commit,产生新的 SHA。已推送到远端的 commit 不要 amend,否则需要 force push,会影响其他人。
修改指定 commit:git rebase -i
--amend 只能改最近一次 commit,修改更早的提交要用交互式 rebase。
1 | # 修改最近 3 个 commit(打开交互式编辑界面) |
弹出的编辑器里每行是一个 commit,把要修改的那行前面的 pick 改成对应指令:
| 指令 | 含义 |
|---|---|
pick |
保留这个 commit(默认) |
reword |
只修改 commit message |
edit |
暂停在这个 commit,可以修改内容 |
squash |
把这个 commit 合并到上一个 commit |
drop |
删除这个 commit |
以修改 message 为例,把 pick 改成 reword,保存退出,Git 会再弹出编辑器让你改 message。
以修改内容为例,把 pick 改成 edit,保存退出后 Git 会暂停在那个 commit:
1 | # Git 提示你现在在指定 commit,可以修改文件 |
同样:已推送到远端的 commit 不要用 rebase -i 修改,会改写历史,影响所有人。
cherry-pick:把某个 commit 单独移植
1 | # 把 abc1234 这个 commit 的改动应用到当前分支 |
场景:hotfix 在 main 分支修复了一个 bug,需要把这个修复也应用到正在维护的 release 分支,用 cherry-pick 比重新写一遍高效。




