Git
一、Git 基础概念
1. Git 是什么?
定义 Git 是一个分布式版本控制系统(Distributed Version Control System),由 Linus Torvalds 于 2005 年创建,用于管理 Linux 内核开发。
原理
- Git 以快照(Snapshot)的方式记录项目的每一次变更,而不是以差异(Delta)的方式
- 每个提交(Commit)都包含一个指向项目完整快照的指针
- 使用 SHA-1 哈希算法为每个对象生成唯一标识
- 所有数据在本地都有完整副本,支持离线操作
核心架构
scss
工作区 (Working Directory) → 暂存区 (Staging Area/Index) → 本地仓库 (Local Repository) → 远程仓库 (Remote Repository)
示例
bash
# 初始化仓库
git init
# 查看当前状态
git status
# 添加到暂存区
git add .
# 提交到本地仓库
git commit -m "Initial commit"
# 推送到远程仓库
git push origin main
常见误区
- 误区:Git 就是 GitHub → 正解:Git 是工具,GitHub 是基于 Git 的代码托管平台
- 误区:Git 保存的是文件差异 → 正解:Git 保存的是文件快照
- 误区:必须联网才能使用 Git → 正解:Git 是分布式系统,大部分操作可离线完成
2. HEAD、工作树和索引
定义
| 概念 | 说明 |
|---|---|
| 工作树 (Working Tree) | 当前在磁盘上可见的文件,开发者实际编辑的文件 |
| 索引 (Index/Stage) | 暂存区,记录即将被提交的变更,是一个中间状态 |
| HEAD | 指向当前所在分支或提交的指针,表示当前所在位置 |
原理
sql
工作树 ←→ git add ←→ 索引 ←→ git commit ←→ 本地仓库
↑ ↓
←←←←←←←←←←←←←←←←←←← git checkout/reset ←←←←←←←←←←←←←←←←←←←←
HEAD 的三种状态
bash
# 1. HEAD 指向某个分支(正常状态)
HEAD -> main
# 2. HEAD 指向某个具体提交(分离 HEAD 状态)
HEAD detached at abc1234
# 3. HEAD 指向 ORIG_HEAD、MERGE_HEAD 等特殊引用
HEAD -> MERGE_HEAD
示例
bash
# 查看 HEAD 指向
cat .git/HEAD
# 查看索引内容
git ls-files --stage
# 查看工作区与索引的差异
git diff
# 查看索引与最后一次提交的差异
git diff --cached
常见误区
- 误区:索引就是仓库 → 正解:索引是暂存区,是工作区和仓库之间的中间状态
- 误区:HEAD 总是指向最新提交 → 正解:HEAD 指向当前分支,分支才指向最新提交
3. Fork、Clone、Branch 区别
定义
| 概念 | 说明 | 层级 |
|---|---|---|
| Fork | 在服务器端复制别人的仓库到自己的账户下 | 服务器级别 |
| Clone | 将远程仓库完整下载到本地 | 远程 → 本地 |
| Branch | 同一仓库内的并行开发线 | 仓库内部 |
对比
| 对比维度 | Fork | Clone | Branch |
|---|---|---|---|
| 操作位置 | 远程服务器 | 远程 → 本地 | 本地仓库 |
| 独立性 | 完全独立的新仓库 | 原始仓库的副本 | 共享同一仓库历史 |
| 关系保持 | 与原仓库无自动关联 | 自动关联 origin | 共享同一历史 |
| 使用场景 | 开源项目贡献 | 获取代码到本地 | 功能开发 |
| 权限要求 | 不需要原仓库权限 | 需要读取权限 | 需要仓库写入权限 |
选择策略
- 参与开源项目 → Fork → Clone → Branch
- 自己项目开发 → Clone → Branch
- 同一项目不同功能 → Branch
示例
bash
# Fork: 在 GitHub 网页上操作,点击 Fork 按钮
# Clone: 复制 Fork 后的仓库到本地
git clone https://github.com/your-username/project.git
# Branch: 创建新分支进行开发
git checkout -b feature/new-feature
二、Git 基础命令
4. git init
定义 git init 用于在当前目录初始化一个新的 Git 仓库,创建 .git 隐藏目录。
原理 执行后会在当前目录创建 .git 目录,包含:
HEAD- 指向当前分支config- 仓库配置objects/- 存储所有 Git 对象refs/- 存储分支和标签引用hooks/- Git 钩子脚本
示例
bash
# 初始化当前目录
git init
# 初始化并指定分支名
git init -b main
# 初始化裸仓库(无工作区)
git init --bare
常见误区
- 误区:在已有仓库中再次 init 会破坏数据 → 正解:已存在仓库时 init 是安全的,不会覆盖已有数据
- 误区:
git init会自动创建初始提交 → 正解:git init只创建空仓库,需要手动添加文件并提交
5. git clone
定义 git clone 用于将远程仓库完整复制到本地,包括所有历史、分支和标签。
原理
- 克隆时会建立与远程仓库的关联(默认命名为 origin)
- 自动 checkout 默认分支(通常是 main/master)
- 下载所有对象和引用
示例
bash
# 基本克隆
git clone https://github.com/user/repo.git
# 克隆到指定目录
git clone https://github.com/user/repo.git my-project
# 只克隆最新一次提交(浅克隆)
git clone --depth 1 https://github.com/user/repo.git
# 克隆特定分支
git clone -b develop https://github.com/user/repo.git
# 使用 SSH 克隆
git clone git@github.com:user/repo.git
常见误区
- 误区:
git clone只下载当前分支 → 正解:会下载所有分支的引用,但只 checkout 默认分支 - 误区:浅克隆可以后续转为完整克隆 → 正解:可以使用
git fetch --unshallow转换
6. git add
定义 git add 将文件的变更从工作区添加到暂存区(Index),为下一次提交做准备。
原理
- Git 跟踪的是文件快照,
git add会将当前文件内容存入对象数据库 - 暂存区是一个索引文件,记录哪些变更将被包含在下次提交中
示例
bash
# 添加所有变更
git add .
# 添加指定文件
git add file.txt
# 添加指定目录
git add src/
# 交互式添加
git add -p
# 添加被修改和删除的文件(不包括新文件)
git add -u
# 添加新文件和修改的文件(不包括删除)
git add -A
常见误区
- 误区:
git add .只添加新文件 → 正解:git add .添加所有变更(新增、修改、删除) - 误区:add 后就无法修改文件 → 正解:可以再次 add 更新暂存区内容
7. git commit
定义 git commit 将暂存区的变更提交到本地仓库,创建一个新的提交对象。
原理
- 提交对象包含:作者信息、提交者信息、提交信息、父提交指针、指向树对象的指针
- 每次提交生成唯一的 SHA-1 哈希值
- 提交是不可变的,修改提交会创建新的提交
示例
bash
# 基本提交
git commit -m "feat: add user login feature"
# 跳过暂存区直接提交已跟踪的文件
git commit -am "fix: correct typo"
# 修改最后一次提交
git commit --amend -m "updated message"
# 空提交(创建新提交但不包含变更)
git commit --allow-empty -m "trigger CI build"
# 提交签名
git commit -S -m "signed commit"
提交信息规范(Conventional Commits)
xml
<type>(<scope>): <subject>
<body>
<footer>
常见 type:
feat- 新功能fix- 修复 bugdocs- 文档更新style- 代码格式refactor- 重构test- 测试chore- 构建/工具
常见误区
- 误区:commit 后会推送到远程 → 正解:commit 只提交到本地仓库,需要 push 才会到远程
- 误区:可以随时修改历史提交 → 正解:已推送的提交不应修改,会影响协作者
8. git push
定义 git push 将本地分支的提交推送到远程仓库。
原理
- 推送前会检查远程分支的提交历史是否包含本地提交的祖先
- 如果远程有新提交,需要先 pull 合并后才能 push
示例
bash
# 推送到默认远程和分支
git push
# 推送到指定远程和分支
git push origin main
# 首次推送并建立追踪关系
git push -u origin feature-branch
# 强制推送(危险!)
git push --force-with-lease
# 推送所有分支
git push --all
# 推送标签
git push --tags
# 删除远程分支
git push origin --delete branch-name
常见误区
- 误区:
git push --force是安全的 → 正解:--force会覆盖远程历史,应使用--force-with-lease - 误区:push 会自动推送所有分支 → 正解:默认只推送当前分支
9. git pull 与 git fetch
定义
| 命令 | 说明 |
|---|---|
| git fetch | 从远程仓库下载最新的提交和引用,但不合并到当前工作区 |
| git pull | 从远程仓库下载并自动合并到当前分支(= fetch + merge) |
对比
| 对比维度 | git fetch | git pull |
|---|---|---|
| 下载远程更新 | ✅ | ✅ |
| 自动合并 | ❌ | ✅ |
| 安全性 | 高(先审查再合并) | 低(直接合并) |
| 工作流程 | 两步操作 | 一步操作 |
| 推荐使用 | ✅ 推荐 | 熟悉项目后使用 |
原理
bash
# git pull 等价于:
git fetch origin
git merge origin/main
# 或者(使用 rebase 模式):
git fetch origin
git rebase origin/main
示例
bash
# 获取远程更新但不合并
git fetch origin
# 查看所有远程分支
git branch -r
# 拉取并合并(默认)
git pull origin main
# 拉取并变基
git pull --rebase origin main
# 仅获取特定分支
git fetch origin main
选择策略
- 新手或重要分支 →
git fetch+ 手动git merge - 日常开发且无冲突风险 →
git pull - 希望保持线性历史 →
git pull --rebase
常见误区
- 误区:fetch 会修改工作区 → 正解:fetch 只更新远程跟踪分支,不影响工作区
- 误区:pull 总是安全的 → 正解:pull 会自动合并,可能产生冲突或混乱的历史
10. git status
定义 git status 显示工作区和暂存区的状态,包括已修改、已暂存和未跟踪的文件。
示例
bash
# 查看状态
git status
# 简洁输出
git status -s
输出解读
ruby
M file.txt # 已修改且已暂存
M file.txt # 已修改但未暂存
A file.txt # 新文件已暂存
D file.txt # 已删除且已暂存
?? file.txt # 未跟踪的文件
11. git log
定义 git log 显示提交历史记录。
示例
bash
# 基本日志
git log
# 单行简洁输出
git log --oneline
# 图形化显示分支合并
git log --oneline --graph --all
# 显示最近 n 条
git log -n 5
# 显示每次提交的变更统计
git log --stat
# 显示具体变更内容
git log -p
# 按作者过滤
git log --author="username"
# 按时间过滤
git log --since="2024-01-01" --until="2024-12-31"
# 搜索提交信息
git log --grep="feat"
# 查看某个文件的提交历史
git log --follow -- file.txt
12. git diff
定义 git diff 显示不同版本之间的差异。
示例
bash
# 工作区 vs 暂存区
git diff
# 暂存区 vs 最近提交
git diff --cached
# 工作区 vs 最近提交
git diff HEAD
# 两个提交之间的差异
git diff commit1 commit2
# 两个分支之间的差异
git diff main..develop
# 查看具体文件的差异
git diff -- file.txt
13. git rm 和 git mv
定义
| 命令 | 说明 |
|---|---|
| git rm | 从工作区和暂存区删除文件,并记录删除操作 |
| git mv | 重命名或移动文件 |
示例
bash
# 删除文件
git rm file.txt
# 仅从暂存区删除(保留工作区文件)
git rm --cached file.txt
# 递归删除目录
git rm -r dir/
# 重命名文件
git mv old.txt new.txt
14. Git 和 SVN 的区别
对比
| 对比维度 | Git | SVN |
|---|---|---|
| 架构 | 分布式 | 集中式 |
| 版本号 | 基于哈希(SHA-1) | 顺序数字(r1, r2, r3) |
| 分支 | 轻量级,本地操作 | 重量级,目录复制 |
| 离线工作 | 完整支持 | 仅有限操作 |
| 速度 | 快(本地操作) | 较慢(依赖网络) |
| 存储方式 | 按快照存储 | 按差异存储 |
| 学习曲线 | 较陡峭 | 较平缓 |
| 适用场景 | 大型项目、开源协作 | 小型团队、集中管理 |
选择策略
- 新项目 → 优先选择 Git
- 需要管理大型二进制文件 → Git + LFS 或 SVN
- 传统企业环境 → 根据团队习惯选择
三、暂存与现场保存
15. git stash
定义 git stash 将当前工作区的修改临时保存起来,恢复工作区到干净状态,方便切换分支。
原理
- stash 实际上是创建了两个提交(一个保存工作区,一个保存暂存区)
- stash 列表是一个栈结构,后进先出
使用场景
- 正在开发功能,需要紧急修复 bug
- 需要拉取远程更新但不想提交当前工作
- 需要切换分支但当前工作未完成
示例
bash
# 保存当前修改
git stash
# 保存并添加描述
git stash save "WIP: user login feature"
# 或使用新版命令
git stash push -m "WIP: user login feature"
# 查看 stash 列表
git stash list
# 恢复最近一次 stash 并删除
git stash pop
# 恢复但不删除
git stash apply
# 恢复指定的 stash
git stash apply stash@{2}
# 删除指定 stash
git stash drop stash@{0}
# 清空所有 stash
git stash clear
# 从 stash 创建新分支
git stash branch new-branch stash@{0}
# 包含未跟踪的文件
git stash -u
常见误区
- 误区:stash 是永久的 → 正解:stash 只是临时存储,可能被清理
- 误区:stash 可以在分支间共享 → 正解:stash 是本地操作,不随 push 推送
四、分支管理
16. Git 分支概述
定义 Git 分支是指向提交的轻量级可移动指针。分支本质上是包含 40 个字符(SHA-1 哈希)的文件。
原理
- 创建分支只是创建一个指向当前提交的新指针,成本极低
- 切换分支会更新 HEAD 指向,并更新工作区文件
- 每个分支独立演进,互不干扰
示例
bash
# 创建分支
git branch feature-login
# 创建并切换
git checkout -b feature-login
# 或
git switch -c feature-login
# 查看所有分支
git branch
git branch -a # 包括远程分支
# 切换分支
git checkout main
git switch main
# 删除分支
git branch -d feature-login # 安全删除(已合并)
git branch -D feature-login # 强制删除(未合并)
# 重命名分支
git branch -m old-name new-name
17. git checkout 与 git switch
定义
| 命令 | 说明 |
|---|---|
| git checkout | 多功能命令:切换分支、恢复文件、分离 HEAD |
| git switch | Git 2.23+ 引入,专门用于切换分支(更安全) |
对比
| 对比维度 | git checkout | git switch |
|---|---|---|
| 功能范围 | 广泛(分支+文件) | 专注(仅分支) |
| 恢复文件 | git checkout -- file |
不支持 |
| 切换分支 | git checkout branch |
git switch branch |
| 创建分支 | git checkout -b branch |
git switch -c branch |
| 安全性 | 较低(可能误操作) | 较高(用途明确) |
选择策略
- Git 2.23+ → 推荐使用
git switch切换分支,git restore恢复文件 - 旧版本 → 使用
git checkout
18. 分支类型与命名规范
主分支(Main Branches)
| 分支 | 说明 | 命名 |
|---|---|---|
| main/master | 生产环境代码,始终可部署 | main 或 master |
| develop | 开发集成分支,包含最新开发成果 | develop 或 dev |
辅助分支(Supporting Branches)
| 分支类型 | 说明 | 命名规范 | 来源 | 合并到 |
|---|---|---|---|---|
| 功能分支 | 开发新功能 | feature/xxx |
develop | develop |
| 发布分支 | 准备新版本发布 | release/v1.x.x |
develop | main + develop |
| 热修复分支 | 紧急修复生产问题 | hotfix/xxx |
main | main + develop |
分支命名规范
bash
# 功能分支
feature/user-authentication
feature/123-add-login-page # 关联 issue 编号
# 修复分支
fix/incorrect-login-validation
bugfix/456-fix-crash
# 发布分支
release/v1.2.0
release/2024-spring
# 热修复分支
hotfix/critical-security-patch
hotfix/v1.2.1
# 实验分支
experiment/new-ui-framework
最佳实践
- 分支名使用小写字母,单词间用连字符分隔
- 关联 issue/ticket 编号便于追溯
- 及时删除已合并的分支
bash
# 删除已合并的本地分支
git branch --merged | grep -v "\*" | xargs git branch -d
# 删除已合并的远程分支
git remote prune origin
五、合并与变基
19. git merge
定义 git merge 将两个或多个分支的历史合并在一起,创建一个新的合并提交。
合并类型
| 类型 | 说明 | 是否创建新提交 |
|---|---|---|
| 快进合并 (Fast-forward) | 目标分支是当前分支的直接祖先 | 否 |
| 普通合并 (Recursive/Octopus) | 两个分支有分叉,需要合并 | 是 |
| 禁止快进 (No-ff) | 强制创建合并提交 | 是 |
示例
bash
# 基本合并
git merge feature-branch
# 禁止快进合并(保留分支历史)
git merge --no-ff feature-branch
# 快进合并(如果可以)
git merge --ff feature-branch
# 不自动提交
git merge --no-commit feature-branch
# 使用 squash(压缩所有提交为一个)
git merge --squash feature-branch
原理图解
css
# Fast-forward 合并
A---B---C (main)
\
D---E (feature)
# 合并后(fast-forward)
A---B---C---D---E (main, feature)
# No-ff 合并
A---B---C (main)
\
D---E (feature)
# 合并后
A---B---C-------F (main)
\ /
D---E (feature)
20. git rebase
定义 git rebase 将一个分支的提交"重新应用"到另一个分支的最新提交之上,创建线性历史。
原理
- rebase 会复制提交并在新基础上重新创建
- 原始提交不会被删除,但不再被分支引用
- 适用于清理本地未推送的提交历史
示例
bash
# 将当前分支变基到 main
git checkout feature
git rebase main
# 交互式变基
git rebase -i HEAD~3
# 变基到特定提交
git rebase --onto new-base old-base feature
# 继续变基(解决冲突后)
git rebase --continue
# 跳过当前提交
git rebase --skip
# 中止变基
git rebase --abort
交互式变基操作
bash
git rebase -i HEAD~4
可执行的操作:
sql
pick - 保留该提交
reword - 保留提交但修改提交信息
edit - 暂停以修改提交内容
squash - 将该提交合并到上一个提交
fixup - 同 squash,但丢弃提交信息
drop - 删除该提交
原理图解
css
# 变基前
A---B---C (feature)
/
D---E---F---G (main)
# 变基后
A'--B'--C' (feature)
/
D---E---F---G (main)
21. merge 与 rebase 的区别
定义
| 操作 | 说明 |
|---|---|
| merge | 创建新的合并提交,保留完整的历史分支结构 |
| rebase | 重写提交历史,将提交移到新基础上,保持线性历史 |
对比
| 对比维度 | merge | rebase |
|---|---|---|
| 历史记录 | 保留完整分支历史 | 重写历史,保持线性 |
| 提交数量 | 增加一个合并提交 | 不增加额外提交 |
| 可追溯性 | 高(知道何时合并) | 较低(丢失分支信息) |
| 安全性 | 高(不修改历史) | 低(修改已存在提交) |
| 冲突处理 | 一次解决所有冲突 | 可能需要多次解决 |
| 适用场景 | 公共分支合并 | 本地分支整理 |
| 协作友好 | ✅ 适合共享分支 | ❌ 不适合已推送分支 |
选择策略
| 场景 | 推荐 | 原因 |
|---|---|---|
| 合并到 main/develop | merge --no-ff | 保留功能开发的完整上下文 |
| 同步 main 到功能分支 | rebase | 保持功能分支历史清晰 |
| 整理本地提交 | rebase -i | 压缩、重组、修改提交 |
| 公共分支 | merge | 不修改他人可见的历史 |
最佳实践
bash
# 黄金法则:永远不要对已推送到公共仓库的提交执行 rebase
# ✅ 好的做法
git checkout feature
git rebase main # 在本地整理后再推送
git push origin feature
# ❌ 危险的做法
git rebase main # 如果 feature 已被他人拉取
git push --force # 会覆盖他人的历史
22. git cherry-pick
定义 git cherry-pick 从其他分支选择特定的提交应用到当前分支。
示例
bash
# 应用单个提交
git cherry-pick abc1234
# 应用多个提交
git cherry-pick abc1234 def5678
# 应用一个范围的提交(不包含 start)
git cherry-pick start^..end
# 应用但不自动提交
git cherry-pick -n abc1234
# 保留原提交者信息
git cherry-pick -x abc1234
使用场景
- 将热修复应用到多个版本分支
- 从实验分支挑选有用的提交
- 错误的分支上提交了,需要转移到正确分支
23. 选择性合并
定义 只合并其他分支的部分变更,而非整个分支。
方法
bash
# 方法1:cherry-pick(推荐)
git cherry-pick commit-hash
# 方法2:checkout 单个文件
git checkout source-branch -- path/to/file
# 方法3:交互式变基后合并
git rebase -i source-branch
# 删除不需要的提交后 merge
# 方法4:使用 patch
git diff source-branch -- path/to/file > changes.patch
git apply changes.patch
六、冲突解决
24. 合并冲突
定义 当两个分支修改了同一文件的同一区域,Git 无法自动合并时产生的情况。
冲突标记
markdown
<<<<<<< HEAD
当前分支的代码内容
=======
被合并分支的代码内容
>>>>>>> feature-branch
冲突解决步骤
bash
# 1. 查看冲突文件
git status
# 2. 手动编辑冲突文件,解决冲突标记
# 删除冲突标记,保留需要的代码
# 3. 标记冲突已解决
git add resolved-file.txt
# 4. 完成合并
git commit
# 如果是 rebase 过程中的冲突
git add resolved-file.txt
git rebase --continue
25. 冲突解决策略
策略对比
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 手动解决 | 编辑冲突标记,选择保留的代码 | 复杂冲突 |
| 使用 ours | 完全保留当前分支的代码 | 确定当前分支正确 |
| 使用 theirs | 完全保留被合并分支的代码 | 确定被合并分支正确 |
| 使用外部工具 | 使用 meld、kdiff3 等可视化工具 | 大规模冲突 |
示例
bash
# 使用 ours 策略(保留当前分支)
git merge -s ours feature-branch
# 解决单个文件时使用 ours
git checkout --ours conflicted-file.txt
git add conflicted-file.txt
# 解决单个文件时使用 theirs
git checkout --theirs conflicted-file.txt
git add conflicted-file.txt
# 使用合并驱动(在 .gitattributes 中配置)
# *.txt merge=union # 自动合并文本文件
冲突解决工具配置
bash
# 使用 VS Code 作为合并工具
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'
# 使用 meld
git config --global merge.tool meld
git config --global mergetool.meld.cmd 'meld $LOCAL $MERGED $REMOTE --output $MERGED'
# 启动合并工具
git mergetool
26. 冲突解决最佳实践
- 频繁同步:经常 pull/rebase 主分支,减少冲突概率
- 小步提交:每次提交只做一件事,便于冲突定位
- 明确分工:团队成员负责不同文件或模块
- 及时沟通:多人修改同一文件前先沟通
- 使用 .gitattributes:为特定文件类型配置合并策略
bash
# 示例 .gitattributes
# 二进制文件不进行合并
*.png binary
*.jpg binary
# 配置文件使用 ours 策略
*.xml merge=ours
*.json merge=ours
七、Git 工作流
27. Git 工作流概述
定义 Git 工作流是团队协作中使用 Git 的规范和流程,确保代码管理的有序性。
主要工作流类型
| 工作流 | 适用场景 | 复杂度 |
|---|---|---|
| Git Flow | 有固定发布周期的项目 | 高 |
| GitHub Flow | 持续部署的 Web 应用 | 低 |
| GitLab Flow | 需要环境分支管理 | 中 |
| Forking Flow | 开源项目 | 中 |
28. Git Flow
定义 Git Flow 是由 Vincent Driessen 提出的分支模型,适合有固定发布周期的项目。
分支结构
bash
main (master)
└── release/v1.x
└── develop
├── feature/login
├── feature/dashboard
└── hotfix/security-fix
工作流程
bash
# 1. 初始化 Git Flow
git flow init
# 2. 开发新功能
git flow feature start user-auth
# 开发完成后
git flow feature finish user-auth
# 3. 准备发布
git flow release start 1.0.0
# 修复发布问题
git flow release finish 1.0.0
# 4. 热修复
git flow hotfix start critical-fix
git flow hotfix finish critical-fix
各分支说明
| 分支 | 生命周期 | 说明 |
|---|---|---|
| main | 永久 | 生产环境代码,只有发布和热修复会合并进来 |
| develop | 永久 | 开发集成分支,包含最新的开发成果 |
| feature/* | 临时 | 功能开发,从 develop 创建,合并回 develop |
| release/* | 临时 | 发布准备,从 develop 创建,合并到 main 和 develop |
| hotfix/* | 临时 | 紧急修复,从 main 创建,合并到 main 和 develop |
优点
- 清晰的分支结构和职责
- 适合有版本发布计划的项目
- 支持并行开发和发布
缺点
- 分支结构复杂,学习成本高
- 不适合持续部署
- 发布流程较长
29. GitHub Flow
定义 GitHub Flow 是简化的工作流,适合持续部署的项目,只有 main 分支和功能分支。
工作流程
bash
# 1. 从 main 创建功能分支
git checkout main
git pull origin main
git checkout -b feature/add-search
# 2. 开发并提交
git add .
git commit -m "feat: add search functionality"
# 3. 推送到远程
git push origin feature/add-search
# 4. 创建 Pull Request
# 在 GitHub 上创建 PR,进行 Code Review
# 5. 合并后部署
# PR 合并后自动部署到生产环境
规则
- main 分支的任何代码都是可部署的
- 新功能必须在独立分支上开发
- 通过 Pull Request 进行代码审查
- 审查通过后合并到 main
- 合并后立即部署
优点
- 简单易懂
- 适合持续集成/持续部署
- 促进代码审查
缺点
- 不适合多版本维护
- 没有明确的发布流程
- 不适合复杂的项目管理
30. GitLab Flow
定义 GitLab Flow 是 Git Flow 和 GitHub Flow 的折中方案,增加了环境分支。
分支结构
css
main
└── pre-production
└── production
工作流程
- 在 main 上进行开发
- 将 main 合并到 pre-production 进行测试
- 将 pre-production 合并到 production 进行发布
变体:带环境分支
css
feature branches → main → staging → production
31. Forking 工作流
定义 每个开发者 Fork 仓库到自己的账户,在本地开发后发起 Pull Request。
适用场景
- 开源项目协作
- 不需要给所有贡献者写权限
- 需要严格的代码审查
工作流程
bash
# 1. Fork 仓库(在 GitHub 上操作)
# 2. Clone Fork 的仓库
git clone https://github.com/your-username/repo.git
# 3. 添加上游仓库
git remote add upstream https://github.com/original-owner/repo.git
# 4. 创建功能分支
git checkout -b feature/my-feature
# 5. 开发并推送
git push origin feature/my-feature
# 6. 创建 Pull Request
# 7. 同步上游更新
git fetch upstream
git checkout main
git merge upstream/main
32. Pull Request 与 Merge Request
定义
| 概念 | 平台 | 说明 |
|---|---|---|
| Pull Request (PR) | GitHub | 请求将分支的变更拉取到目标分支 |
| Merge Request (MR) | GitLab | 请求将分支的变更合并到目标分支 |
PR/MR 流程
- 开发者创建功能分支并推送
- 在平台创建 PR/MR
- 自动触发 CI 检查
- 团队成员进行 Code Review
- 解决反馈问题
- 审核通过后合并
Code Review 最佳实践
- 小批量提交审查(每次变更不超过 400 行)
- 使用模板确保审查一致性
- 设置至少一名审查者
- 自动化检查(lint、测试)先行
- 及时反馈,友好沟通
八、版本回退与重置
33. git reset
定义 git reset 将当前分支指针移动到指定提交,可选择性地修改暂存区和工作区。
三种模式
| 模式 | 分支指针 | 暂存区 | 工作区 | 说明 |
|---|---|---|---|---|
| --soft | 移动 | 不变 | 不变 | 变更保留在暂存区 |
| --mixed | 移动 | 重置 | 不变 | 变更保留在工作区(默认) |
| --hard | 移动 | 重置 | 重置 | 所有变更丢弃 |
示例
bash
# 软重置 - 回退提交,保留暂存
git reset --soft HEAD~1
# 混合重置 - 回退提交,变更回到工作区
git reset --mixed HEAD~1
git reset HEAD~1 # 默认模式
# 硬重置 - 完全回退,丢弃所有变更
git reset --hard HEAD~1
git reset --hard abc1234
# 重置特定文件到暂存区之前
git reset HEAD -- file.txt
# 只重置暂存区,不移动指针
git reset -- file.txt
图解
css
# 当前状态
A---B---C---D (main, HEAD)
# git reset --soft HEAD~2
A---B---C---D
↑
main, HEAD
(C和D的变更在暂存区)
# git reset --mixed HEAD~2
A---B---C---D
↑
main, HEAD
(C和D的变更在工作区)
# git reset --hard HEAD~2
A---B (main, HEAD)
(C和D的变更完全丢弃)
34. git revert
定义 git revert 创建一个新的提交,该提交的内容是指定提交的反向操作,用于安全地撤销已推送的提交。
与 reset 的区别
| 对比维度 | git reset | git revert |
|---|---|---|
| 历史记录 | 删除提交历史 | 保留完整历史 |
| 新提交 | 不创建新提交 | 创建新提交 |
| 安全性 | 低(修改历史) | 高(追加历史) |
| 适用场景 | 本地未推送的提交 | 已推送的公共提交 |
| 协作影响 | 影响他人 | 不影响他人 |
示例
bash
# 撤销最后一次提交
git revert HEAD
# 撤销指定提交
git revert abc1234
# 撤销多个提交
git revert HEAD~3..HEAD
# 撤销但不自动提交
git revert -n abc1234
# 编辑提交信息
git revert -e abc1234
选择策略
- 提交未推送 →
git reset - 提交已推送 →
git revert - 公共分支 → 只能用
git revert
35. git reflog
定义 reflog(Reference Log)记录 HEAD 和分支引用的所有变更历史,包括 reset、checkout 等操作。
原理
- reflog 是本地操作,不会推送到远程
- 默认保存 90 天
- 可用于恢复误删的提交
示例
bash
# 查看 reflog
git reflog
# 输出示例
# abc1234 HEAD@{0}: commit: add feature
# def5678 HEAD@{1}: reset: moving to HEAD~1
# ghi9012 HEAD@{2}: commit: fix bug
# 恢复到指定状态
git reset --hard HEAD@{2}
# 或使用提交哈希
git reset --hard def5678
# 查看特定分支的 reflog
git reflog show main
# 清理过期记录
git reflog expire --expire=now --all
恢复误删的提交
bash
# 1. 查看 reflog 找到误删的提交
git reflog
# 2. 找到对应的提交哈希
# abc1234 HEAD@{3}: commit: important feature
# 3. 恢复
git cherry-pick abc1234
# 或
git reset --hard abc1234
36. 撤销操作汇总
| 场景 | 命令 | 说明 |
|---|---|---|
| 撤销最后一次提交(保留变更) | git reset --soft HEAD~1 |
变更回到暂存区 |
| 撤销最后一次提交(保留到工作区) | git reset HEAD~1 |
变更回到工作区 |
| 完全撤销最后一次提交 | git reset --hard HEAD~1 |
丢弃所有变更 |
| 撤销已推送的提交 | git revert HEAD |
创建反向提交 |
| 撤销暂存 | git reset HEAD file.txt |
从暂存区移除 |
| 撤销文件修改 | git checkout -- file.txt |
恢复到最后一次提交 |
| 撤销所有未提交修改 | git reset --hard |
回到最后一次提交状态 |
| 恢复已删除的提交 | git reflog + git reset |
通过 reflog 找回 |
九、标签管理
37. Git 标签概述
定义 标签(Tag)用于标记重要的提交,通常用于发布版本。
标签类型
| 类型 | 说明 | 包含信息 |
|---|---|---|
| 轻量标签 (Lightweight) | 指向提交的简单引用 | 仅标签名 |
| 附注标签 (Annotated) | 完整的 Git 对象 | 标签名、作者、日期、消息、GPG签名 |
示例
bash
# 创建轻量标签
git tag v1.0.0
# 创建附注标签(推荐)
git tag -a v1.0.0 -m "Release version 1.0.0"
# 创建带 GPG 签名的标签
git tag -s v1.0.0 -m "Signed release"
# 查看所有标签
git tag
git tag -l "v1.*"
# 查看标签详情
git show v1.0.0
# 在指定提交上打标签
git tag -a v0.9.0 -m "Beta release" abc1234
38. 标签推送与删除
bash
# 推送单个标签
git push origin v1.0.0
# 推送所有标签
git push --tags
# 删除本地标签
git tag -d v1.0.0
# 删除远程标签
git push origin --delete v1.0.0
# 或
git push origin :refs/tags/v1.0.0
# 清理本地不存在的远程标签
git fetch --prune --prune-tags
最佳实践
- 发布版本时使用附注标签
- 遵循语义化版本规范(SemVer)
- 及时推送标签到远程
十、Git Hook
39. Git Hook 概述
定义 Git Hook 是在特定 Git 事件发生时自动执行的脚本,存放在 .git/hooks/ 目录。
常用 Hook 类型
| Hook | 触发时机 | 用途 |
|---|---|---|
| pre-commit | 执行 git commit 之前 | 代码检查、格式化 |
| commit-msg | 提交信息创建后 | 提交信息格式校验 |
| pre-push | 执行 git push 之前 | 运行测试、阻止推送 |
| post-commit | 提交完成后 | 发送通知 |
| pre-receive | 服务器接收推送前 | 服务器端校验 |
| post-receive | 服务器接收推送后 | 触发部署 |
40. pre-commit Hook
示例:创建 pre-commit 脚本
bash
#!/bin/sh
# .git/hooks/pre-commit
# 运行代码检查
npm run lint
# 如果检查失败,阻止提交
if [ $? -ne 0 ]; then
echo "Lint check failed. Commit aborted."
exit 1
fi
# 运行测试
npm test
if [ $? -ne 0 ]; then
echo "Tests failed. Commit aborted."
exit 1
fi
41. Husky 与 lint-staged
定义
| 工具 | 说明 |
|---|---|
| Husky | 简化 Git Hook 管理的工具,让 Hook 配置版本化 |
| lint-staged | 只对暂存的文件运行 lint,提高效率 |
配置示例
json
// package.json
{
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"pre-push": "npm test"
}
},
"lint-staged": {
"*.{js,ts}": [
"eslint --fix",
"prettier --write"
],
"*.{css,scss}": [
"stylelint --fix"
]
}
}
Husky v9+ 配置
bash
# 安装
npm install husky --save-dev
# 初始化
npx husky init
# 添加 hook
echo "npm run lint" >> .husky/pre-commit
优势
- Hook 配置随代码库版本控制
- 只检查暂存文件,速度快
- 自动修复可修复的问题
十一、Git 配置与优化
42. git config
配置层级
| 层级 | 范围 | 文件位置 |
|---|---|---|
| --system | 系统所有用户 | /etc/gitconfig |
| --global | 当前用户所有仓库 | ~/.gitconfig |
| --local | 当前仓库 | .git/config |
常用配置
bash
# 设置用户信息
git config --global user.name "Your Name"
git config --global user.email "your@email.com"
# 设置默认编辑器
git config --global core.editor "code --wait"
# 设置默认分支名
git config --global init.defaultBranch main
# 设置 Pull 默认行为
git config --global pull.rebase true
# 设置颜色输出
git config --global color.ui auto
# 设置自动补全
git config --global core.autocrlf input # Mac/Linux
git config --global core.autocrlf true # Windows
# 查看配置
git config --list
git config --global --list
# 设置别名
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.st status
git config --global alias.ci commit
git config --global alias.lg "log --oneline --graph --all"
43. .gitignore
定义 .gitignore 文件指定哪些文件或目录应该被 Git 忽略。
规则示例
gitignore
# 忽略所有 .log 文件
*.log
# 不忽略 important.log
!important.log
# 忽略整个目录
node_modules/
dist/
.env
# 忽略特定路径
logs/*.log
docs/notes/
# 忽略所有 .txt 文件,但保留 docs/ 下的
*.txt
!/docs/
# 忽略空目录(需要占位文件)
empty-dir/.gitkeep
# 忽略 IDE 配置
.vscode/
.idea/
*.swp
注意事项
.gitignore只对未跟踪的文件生效- 已被跟踪的文件需要先从缓存移除
- 可以使用全局
.gitignore
bash
# 设置全局 .gitignore
git config --global core.excludesFile ~/.gitignore_global
# 对已跟踪文件生效忽略
git rm --cached file.txt
44. .gitattributes
定义 .gitattributes 文件为特定文件设置 Git 行为,如换行符处理、合并策略等。
示例
gitattributes
# 统一换行符处理
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
# 二进制文件
*.png binary
*.jpg binary
*.pdf binary
# 特定合并策略
*.xml merge=ours
package.json merge=ours
# 自定义 diff 驱动
*.pdf diff=pdf
# 不导出到 archive
.gitattributes export-ignore
tests/ export-ignore
45. SSH 密钥配置
bash
# 生成 SSH 密钥
ssh-keygen -t ed25519 -C "your@email.com"
# 或(兼容旧系统)
ssh-keygen -t rsa -b 4096 -C "your@email.com"
# 启动 SSH Agent
eval "$(ssh-agent -s)"
# 添加密钥到 Agent
ssh-add ~/.ssh/id_ed25519
# 测试连接
ssh -T git@github.com
46. Git 别名
bash
# 基本别名
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.st status
git config --global alias.ci commit
git config --global alias.unstage 'reset HEAD --'
git config --global alias.last 'log -1 HEAD'
# 图形化日志
git config --global alias.lg "log --oneline --graph --all --decorate"
# 查看变更统计
git config --global alias.ls "log --oneline --stat"
# 查看文件变更历史
git config --global alias.hist "log --oneline --graph --decorate"
# 撤销最后一次提交
git config --global alias.undo 'reset --soft HEAD~1'
47. Git 性能优化
大仓库优化
bash
# 浅克隆
git clone --depth 1 <repo-url>
# 启用多线程
git config --global pack.threads 4
# 调整压缩级别
git config --global core.compression 0
# 预加载索引
git config --global core.preloadindex true
# 启用文件系统缓存
git config --global core.fscache true
定期维护
bash
# 垃圾回收
git gc --prune=now
# 优化对象存储
git repack -a -d --depth=250 --window=250
# 清理远程跟踪分支
git remote prune origin
十二、Git LFS 与子模块
48. Git LFS (Large File Storage)
定义 Git LFS 是 Git 的扩展,用于管理大文件(如二进制文件、媒体文件),将大文件存储在远程服务器上,只在仓库中保存指针。
适用场景
- 设计资源(PSD、AI 文件)
- 视频、音频文件
- 编译产物
- 数据集
示例
bash
# 安装 Git LFS
git lfs install
# 跟踪特定文件类型
git lfs track "*.psd"
git lfs track "*.mp4"
# 查看已跟踪的文件类型
git lfs track
# 提交 .gitattributes(LFS 配置存在于此)
git add .gitattributes
# 查看所有 LFS 文件
git lfs ls-files
# 推送 LFS 文件
git push origin main
# 拉取 LFS 文件
git lfs pull
49. Git 子模块 (git submodule)
定义 子模块允许将一个 Git 仓库作为另一个 Git 仓库的子目录,保持独立的提交历史。
示例
bash
# 添加子模块
git submodule add https://github.com/user/library.git lib/library
# 初始化子模块
git submodule init
# 更新子模块
git submodule update
# 克隆包含子模块的仓库
git clone --recursive https://github.com/user/project.git
# 或分步
git clone https://github.com/user/project.git
cd project
git submodule update --init --recursive
# 更新所有子模块
git submodule update --remote
# 查看子模块状态
git submodule status
# 删除子模块
git submodule deinit lib/library
git rm lib/library
注意事项
- 子模块记录的是特定提交,不是最新代码
- 需要手动更新子模块
- 替代方案:Git Subtree、包管理器
十三、Git 代理配置
50. Git 代理
bash
# 设置 HTTP 代理
git config --global http.proxy http://127.0.0.1:1080
git config --global https.proxy http://127.0.0.1:1080
# 设置 SOCKS5 代理
git config --global http.proxy socks5://127.0.0.1:1080
git config --global https.proxy socks5://127.0.0.1:1080
# 仅为 GitHub 设置代理
git config --global http.https://github.com.proxy http://127.0.0.1:1080
# 取消代理
git config --global --unset http.proxy
git config --global --unset https.proxy
十四、Git 常见问题
51. 常见操作问题
分离 HEAD 状态
bash
# 查看当前状态
git status
# HEAD detached at abc1234
# 如果想保留修改,创建新分支
git checkout -b new-branch
# 如果想丢弃修改,回到分支
git checkout main
推送被拒绝
bash
# 原因:远程有新提交
# 解决方案1:先拉取再推送
git pull --rebase origin main
git push
# 解决方案2:强制推送(慎用)
git push --force-with-lease
错误的提交信息
bash
# 修改最后一次提交信息
git commit --amend -m "新的提交信息"
# 修改更早的提交信息
git rebase -i HEAD~3
# 将 pick 改为 reword
误删分支
bash
# 通过 reflog 找回
git reflog
git branch recovered-branch abc1234
52. 版本管理理解
Git 的四种对象
| 对象类型 | 说明 | 存储内容 |
|---|---|---|
| Blob | 二进制大对象 | 文件内容 |
| Tree | 树对象 | 目录结构和文件名 |
| Commit | 提交对象 | 提交元数据和指向 tree 的指针 |
| Tag | 标签对象 | 指向 commit 的引用 |
提交对象结构
sql
commit abc1234567890
tree def5678901234
parent 1234567890abc
author Developer <dev@email.com> 1234567890 +0800
committer Developer <dev@email.com> 1234567890 +0800
Commit message here
引用机制
css
main ──→ commit C ──→ commit B ──→ commit A
HEAD ──→ main
十五、面试高频问答速查
基础概念
Q: Git 是什么? A: Git 是分布式版本控制系统,以快照方式记录项目变更,支持离线操作和完整的本地历史。
Q: Git 和 SVN 的区别? A: Git 是分布式的,SVN 是集中式的;Git 分支轻量,SVN 分支重量;Git 支持离线操作,SVN 依赖网络。
Q: HEAD、工作树、索引的关系? A: 工作树是可见文件,索引是暂存区,HEAD 指向当前分支。变更流程:工作树 → add → 索引 → commit → 仓库。
分支操作
Q: merge 和 rebase 的区别? A: merge 创建合并提交保留完整历史;rebase 重写历史保持线性。公共分支用 merge,本地整理用 rebase。
Q: 什么是快进合并? A: 当目标分支是当前分支的直接祖先时,Git 只是移动指针而不创建新提交,称为快进合并。
Q: Fork、Clone、Branch 区别? A: Fork 是服务器端复制仓库;Clone 是下载到本地;Branch 是仓库内的并行开发线。
版本回退
Q: reset 和 revert 的区别? A: reset 移动分支指针,修改历史,适合本地提交;revert 创建新提交反向操作,保留历史,适合公共提交。
Q: 如何恢复误删的提交? A: 使用 git reflog 查看操作历史,找到对应哈希后用 git reset 或 git cherry-pick 恢复。
工作流
Q: Git Flow 有哪些分支? A: main(生产)、develop(开发)、feature/(功能)、release/(发布)、hotfix/*(热修复)。
Q: Pull Request 流程是什么? A: 创建功能分支 → 开发推送 → 创建 PR → Code Review → CI 检查 → 合并到主分支。
高级操作
Q: stash 的使用场景? A: 临时保存工作区变更以便切换分支处理紧急任务,包括切换分支、拉取更新、代码审查等场景。
Q: 什么是 Git LFS? A: Git 大文件存储扩展,将大文件存在远程服务器,仓库中只保存指针,适用于二进制文件和媒体文件。
Q: Git Hook 有什么用途? A: 在 Git 事件发生时自动执行脚本,如 pre-commit 做代码检查,commit-msg 校验提交格式,pre-push 运行测试。