Git是目前最流行的分布式版本控制系统,几乎每个程序员都在使用。但是,很多人对Git的使用还停留在基础层面:git add、git commit、git push、git pull,遇到冲突就头疼,遇到复杂场景就不知所措。
其实,Git的功能非常强大,掌握一些高级技巧,能大大提升开发效率,让版本管理更加优雅和高效。2016年,Git已经成为版本控制的绝对主流,GitHub、GitLab、Bitbucket等平台都基于Git,掌握Git高级技巧是每个程序员的必备技能。
今天就来深入讲解Git的高级技巧,包括rebase变基、cherry-pick精选提交、stash暂存、交互式rebase、reset回退、reflog恢复,以及常见的Git工作流。
一、rebase(变基)
1. 什么是rebase
rebase(变基)是Git中另一种合并分支的方式。与merge不同,merge会创建一个新的合并提交,而rebase会把当前分支的提交"移动"到目标分支的最新提交之后,形成一条线性的提交历史。
举个例子,假设你从master分支切出一个feature分支,在feature分支上做了3次提交(C4、C5、C6),同时master分支上也有了新的提交(C7、C8):
master: C1 -- C2 -- C3 -- C7 -- C8
\
feature: C4 -- C5 -- C6如果用merge合并:
git checkout feature
git merge master结果会创建一个新的合并提交C9:
master: C1 -- C2 -- C3 -- C7 -- C8
\ \
feature: C4 -- C5 -- C6 -- C9如果用rebase变基:
git checkout feature
git rebase master结果会把C4、C5、C6"移动"到C8之后,形成线性历史:
master: C1 -- C2 -- C3 -- C7 -- C8
\
feature: C4' -- C5' -- C6'注意:C4'、C5'、C6'是新的提交,虽然内容和C4、C5、C6一样,但commit hash不同。
2. rebase vs merge
| 对比项 | merge | rebase |
|---|---|---|
| 提交历史 | 保留分支结构,有合并提交 | 线性历史,无合并提交 |
| 安全性 | 安全,不改变已有提交 | 会改变提交历史,已推送的分支慎用 |
| 冲突处理 | 一次解决所有冲突 | 逐个提交解决冲突,可能多次冲突 |
| 适用场景 | 公共分支、多人协作 | 个人分支、整理提交历史 |
| 可读性 | 能看到分支合并关系 | 历史清晰简洁,像一条直线 |
3. 什么时候用rebase
- ✅ 个人开发分支,想保持提交历史线性整洁
- ✅ 把master的最新更新同步到feature分支,避免分叉
- ✅ 合并前整理提交历史,让PR/MR更清晰
- ❌ 已经推送到远程的公共分支(会改变历史,导致其他人冲突)
- ❌ 多人协作的分支(其他人可能基于旧的提交开发)
黄金法则:不要对已经推送到公共仓库的分支执行rebase。rebase只用于本地未推送的分支,或者你确定只有自己一个人使用的分支。
4. rebase冲突处理
rebase过程中如果遇到冲突,Git会暂停,让你解决冲突:
# 解决冲突后
git add <冲突文件>
git rebase --continue # 继续rebase
# 如果想放弃rebase
git rebase --abort # 回到rebase之前的状态
# 如果想跳过当前提交
git rebase --skip注意:rebase可能会在多个提交上遇到冲突,需要逐个解决,这比merge一次性解决所有冲突更麻烦。这也是很多人不喜欢rebase的原因之一。
5. 交互式rebase(git rebase -i)
交互式rebase是Git中最强大的功能之一,可以让你重新编辑、合并、删除、重新排序提交。
# 对最近3次提交进行交互式rebase
git rebase -i HEAD~3执行后会打开编辑器,显示类似内容:
pick a1b2c3d 提交1:添加登录功能
pick d4e5f6g 提交2:修复登录bug
pick h7i8j9k 提交3:优化登录性能
# 命令:
# p, pick = 使用该提交
# r, reword = 使用该提交,但修改提交信息
# e, edit = 使用该提交,但暂停以便修改
# s, squash = 使用该提交,但合并到前一个提交
# f, fixup = 类似squash,但丢弃该提交的信息
# x, exec = 执行命令你可以修改每一行前面的命令,实现:
- reword:修改提交信息
- squash:把多个提交合并成一个(保留所有提交信息)
- fixup:把多个提交合并成一个(丢弃被合并的提交信息)
- edit:暂停rebase,修改提交内容
- drop:删除某个提交(直接删除该行)
- 重新排序:调整行的顺序
例如,把3个提交合并成1个:
pick a1b2c3d 提交1:添加登录功能
squash d4e5f6g 提交2:修复登录bug
squash h7i8j9k 提交3:优化登录性能保存后,Git会让你编辑合并后的提交信息,最终生成一个包含所有更改的提交。
交互式rebase是整理提交历史的利器,让你的提交历史清晰、有意义。
二、cherry-pick(精选提交)
1. 什么是cherry-pick
cherry-pick可以把另一个分支上的某个(或某几个)提交"挑选"出来,应用到当前分支。不同于merge会合并整个分支,cherry-pick只合并指定的提交。
使用场景:
- 只需要某个分支上的一个或几个提交,不需要整个分支
- 把bug修复从master分支应用到release分支
- 把某个功能提交从feature分支应用到另一个feature分支
- 误提交到错误分支,把提交cherry-pick到正确分支
2. 基本用法
# 把某个提交应用到当前分支
git cherry-pick <commit-hash>
# 把多个提交应用到当前分支
git cherry-pick <commit1> <commit2> <commit3>
# 把一个范围的提交应用到当前分支(不包含start,包含end)
git cherry-pick <start>..<end>
# 包含start的范围
git cherry-pick <start>^..<end>例如,把feature分支上的abc1234提交应用到当前master分支:
git checkout master
git cherry-pick abc12343. cherry-pick冲突处理
cherry-pick过程中如果遇到冲突:
# 解决冲突后
git add <冲突文件>
git cherry-pick --continue # 继续
# 放弃cherry-pick
git cherry-pick --abort
# 跳过当前提交
git cherry-pick --skip4. 只应用更改,不自动提交
# -n 或 --no-commit:只应用更改到工作区,不自动提交
git cherry-pick -n <commit-hash>
# 然后你可以修改后手动提交
git commit -m "自定义提交信息"5. cherry-pick注意事项
- cherry-pick会生成新的提交(commit hash不同),不是移动原提交
- 不要对已经推送的公共分支cherry-pick后又rebase,会导致历史混乱
- 如果需要把多个提交从一个分支移到另一个分支,且这些提交是连续的,考虑用rebase --onto
- cherry-pick后,原分支上的提交仍然存在,不会被删除
三、stash(暂存)
1. 什么是stash
stash可以把当前工作区的修改暂时"储藏"起来,让工作区恢复干净,等以后再恢复。
使用场景:
- 正在开发新功能,突然需要切换到其他分支修复bug,但当前修改还不想提交
- 切换分支时,工作区有未提交的修改会导致冲突,先stash再切换
- 临时需要保存当前工作状态,去做其他事情
2. 基本用法
# 暂存当前工作区的修改(包括已add和未add的,但不包括未跟踪的新文件)
git stash
# 暂存时添加说明,方便以后识别
git stash save "正在开发登录功能,临时暂存"
# 暂存包括未跟踪的新文件(-u 或 --include-untracked)
git stash -u
# 暂存所有文件(包括忽略的文件,-a 或 --all)
git stash -a3. 查看stash列表
# 查看所有stash
git stash list
# 输出示例:
# stash@{0}: On feature/login: 正在开发登录功能,临时暂存
# stash@{1}: On master: 修复首页样式
# 查看某个stash的详细内容
git stash show stash@{0}
git stash show -p stash@{0} # 显示diff详情4. 恢复stash
# 恢复最近的stash,并从stash列表中删除
git stash pop
# 恢复指定的stash,并从列表中删除
git stash pop stash@{1}
# 恢复stash,但不从列表中删除(可以多次恢复)
git stash apply
git stash apply stash@{1}5. 删除stash
# 删除指定的stash
git stash drop stash@{0}
# 清空所有stash
git stash clear6. 从stash创建分支
如果stash的修改和当前分支冲突太多,可以从stash创建一个新分支:
# 从stash创建新分支并恢复stash
git stash branch new-branch-name stash@{0}7. stash注意事项
- stash是全局的,不绑定分支,可以在任何分支恢复
- stash默认不包含未跟踪的新文件,需要加-u参数
- stash可以堆叠,最新的stash是stash@{0}
- pop会删除stash,apply不会删除,根据需要选择
- 建议用save添加说明,否则stash列表只有分支名,不好识别
四、reset(回退)
1. 什么是reset
reset可以把当前分支的HEAD指针移动到指定提交,同时可以选择如何处理工作区和暂存区的修改。
2. reset的三种模式
| 模式 | HEAD移动 | 暂存区(index) | 工作区(working tree) | 说明 |
|---|---|---|---|---|
| --soft | ✅ | 不变 | 不变 | 只移动HEAD,修改保留在暂存区,相当于撤销commit但保留add |
| --mixed(默认) | ✅ | 重置为指定提交 | 不变 | 移动HEAD,暂存区重置,修改保留在工作区,相当于撤销commit和add |
| --hard | ✅ | 重置为指定提交 | 重置为指定提交 | 移动HEAD,暂存区和工作区都重置,彻底丢弃修改,危险! |
3. 常用场景
# 撤销最近一次commit,但保留修改在暂存区(可以重新commit)
git reset --soft HEAD~1
# 撤销最近一次commit和add,保留修改在工作区(可以重新add和commit)
git reset --mixed HEAD~1
# 或(默认就是mixed)
git reset HEAD~1
# 彻底撤销最近一次commit,丢弃所有修改(危险!)
git reset --hard HEAD~1
# 回退到指定提交
git reset --hard <commit-hash>
# 撤销git add(把暂存区的文件放回工作区)
git reset HEAD <file>
# 或
git restore --staged <file>4. reset注意事项
- --hard会彻底丢弃工作区修改,不可恢复(除非用reflog),使用前确认
- 不要对已经推送到远程的分支执行reset --hard,会导致历史不一致
- 如果已经推送,需要用git revert(创建反向提交)而不是reset
- reset --soft常用于合并多个提交:reset --soft到某个提交,然后重新commit
五、reflog(引用日志)
1. 什么是reflog
reflog记录了HEAD和分支引用的所有变化历史,包括commit、reset、rebase、merge等操作。即使你用reset --hard"删除"了提交,reflog中仍然有记录,可以恢复。
reflog是Git的"后悔药",几乎所有操作都可以通过reflog恢复。
2. 基本用法
# 查看HEAD的reflog
git reflog
# 查看指定分支的reflog
git reflog show master
# 输出示例:
# abc1234 (HEAD -> master) HEAD@{0}: commit: 添加登录功能
# def5678 HEAD@{1}: reset: moving to HEAD~1
# ghi9012 HEAD@{2}: commit: 修复bug
# ...3. 用reflog恢复误操作
场景1:不小心用reset --hard删除了提交,想恢复:
git reflog # 找到删除前的commit hash,假设是abc1234
git reset --hard abc1234 # 恢复场景2:rebase过程中出错,想回到rebase前:
git reflog # 找到rebase前的状态,假设是HEAD@{5}
git reset --hard HEAD@{5}场景3:误删了分支,想恢复:
git reflog # 找到分支被删除前的commit hash
git branch <branch-name> <commit-hash> # 重建分支4. reflog注意事项
- reflog默认保留90天(可通过gc.reflogExpire配置)
- reflog是本地的,不会推送到远程
- 定期执行git gc会清理过期的reflog
- 只要reflog中还有记录,几乎所有操作都可以恢复,所以误操作后不要慌,先看reflog
六、其他实用技巧
1. 查看提交历史
# 简洁的单行显示
git log --oneline
# 图形化显示分支合并关系
git log --oneline --graph --all
# 显示最近N次提交
git log -n 10
# 按作者筛选
git log --author="张三"
# 按时间筛选
git log --since="2016-01-01" --until="2016-06-01"
# 搜索提交信息
git log --grep="登录"
# 显示某个文件的修改历史
git log -p <file>
# 显示每次提交的统计信息
git log --stat2. 查看差异
# 查看工作区和暂存区的差异
git diff
# 查看暂存区和最新提交的差异
git diff --cached
# 或
git diff --staged
# 查看工作区和最新提交的差异
git diff HEAD
# 查看两个提交之间的差异
git diff <commit1> <commit2>
# 查看某个文件的差异
git diff <file>
# 只显示变更的文件名
git diff --name-only3. 撤销修改
# 撤销工作区的修改(恢复到最新提交状态)
git checkout -- <file>
# 或(Git 2.23+)
git restore <file>
# 撤销暂存区的修改(放回工作区)
git reset HEAD <file>
# 或
git restore --staged <file>
# 撤销某次提交(创建一个反向提交,安全,适合已推送的分支)
git revert <commit-hash>4. 标签管理
# 查看所有标签
git tag
# 创建轻量标签
git tag v1.0.0
# 创建附注标签(推荐,包含标签信息、创建者、日期等)
git tag -a v1.0.0 -m "版本1.0.0发布"
# 给指定提交打标签
git tag -a v1.0.0 <commit-hash> -m "版本1.0.0"
# 查看标签信息
git show v1.0.0
# 推送标签到远程
git push origin v1.0.0
# 推送所有标签
git push origin --tags
# 删除本地标签
git tag -d v1.0.0
# 删除远程标签
git push origin --delete v1.0.05. 远程仓库管理
# 查看远程仓库
git remote -v
# 添加远程仓库
git remote add origin <url>
# 修改远程仓库URL
git remote set-url origin <new-url>
# 删除远程仓库
git remote remove origin
# 拉取远程更新但不合并
git fetch origin
# 拉取并合并(= fetch + merge)
git pull origin master
# 推送到远程
git push origin master
# 推送并设置上游分支(以后可以直接git push)
git push -u origin master
# 查看远程分支
git branch -r
# 删除远程分支
git push origin --delete <branch-name>七、Git工作流
1. Git Flow
Git Flow是最经典的Git工作流,适合有明确版本发布周期的项目。
分支模型:
- master:生产环境代码,随时可发布
- develop:开发主分支,集成最新开发功能
- feature/*:功能分支,从develop切出,开发完成后合并回develop
- release/*:发布分支,从develop切出,用于发布前的测试和bug修复,完成后合并到master和develop
- hotfix/*:热修复分支,从master切出,修复生产环境bug,完成后合并到master和develop
优点:分支职责清晰,适合版本发布明确的项目 缺点:分支多,流程复杂,不适合快速迭代和持续部署
2. GitHub Flow
GitHub Flow是更简单的工作流,适合持续部署的项目。
流程:
- 从master切出feature分支
- 在feature分支上开发和提交
- 提交Pull Request(PR)
- 代码审查和讨论
- 合并到master
- 部署
优点:简单、灵活,适合快速迭代和持续部署 缺点:没有明确的版本概念,依赖自动化测试和部署
3. GitLab Flow
GitLab Flow介于Git Flow和GitHub Flow之间,结合了两者的优点,有环境分支(pre-production、production)但流程更简单。
4. 如何选择
- 有明确版本发布周期(如桌面软件、App)→ Git Flow
- 持续部署、快速迭代(如Web应用、SaaS)→ GitHub Flow
- 介于两者之间 → GitLab Flow
- 个人项目、小团队 → 简单的分支模型即可,不必拘泥于流程
八、常见问题与解决方案
1. 提交信息写错了怎么办?
# 修改最近一次提交的信息
git commit --amend -m "新的提交信息"
# 如果已经推送,需要强制推送(慎用)
git push --force2. 把不该提交的文件提交了怎么办?
# 从Git中移除但保留本地文件
git rm --cached <file>
echo "<file>" >> .gitignore
git commit -m "移除误提交的文件"
# 如果是敏感信息(如密码),需要彻底从历史中清除
# 使用git filter-branch或BFG Repo-Cleaner3. 合并冲突太多怎么办?
# 放弃合并,回到合并前
git merge --abort
# 使用可视化工具解决冲突
git mergetool
# 只接受某一方的版本
git checkout --ours <file> # 接受当前分支
git checkout --theirs <file> # 接受合并进来的分支4. 大文件导致仓库臃肿怎么办?
# 查看大文件
git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | sort -k3 -n -r | head -20
# 使用Git LFS管理大文件
git lfs install
git lfs track "*.psd"
git add .gitattributes5. .gitignore不生效怎么办?
.gitignore只能忽略未跟踪的文件,如果文件已经被Git跟踪,需要先移除:
git rm -r --cached .
git add .
git commit -m "重新应用.gitignore"总结
Git是一个功能强大的版本控制系统,掌握高级技巧能大大提升开发效率。本文讲解了:
- rebase:变基,保持线性提交历史,注意不要对公共分支使用
- cherry-pick:精选提交,只合并需要的提交
- stash:暂存,临时保存工作区修改
- reset:回退,三种模式(soft/mixed/hard)区别使用
- reflog:引用日志,Git的"后悔药",几乎所有误操作都能恢复
- 工作流:Git Flow、GitHub Flow、GitLab Flow,根据项目特点选择
Git的学习曲线比较陡,但是一旦掌握,会成为你开发中不可或缺的工具。建议多练习、多使用,遇到问题多查文档和reflog。记住:Git几乎所有操作都是可恢复的,所以不要害怕尝试。
最后,推荐几个学习Git的好资源:
- Pro Git(免费电子书,Git官方推荐)
- Git Cheat Sheet(Git速查表)
- Learn Git Branching(交互式Git学习网站)
- Git官方文档
希望本文能帮助你更深入地理解和使用Git,让版本管理更加高效和优雅。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录