前言
这年头,还有人不会用git? git作为一种分布式版本控制系统,对于开发来说,无论前后端,再熟悉不过了,但你真的能用好git吗? 遇到问题还在一个个百度?今天一次性解决,各种异常都给你列出,请收好。
场景 1:新建项目并关联远程仓库
小 A 接到任务, 要求做一个新项目, 并由她和小 B, 小 C 共同协作开发。
这时小 A 有两种选择,
一种是创建远程仓库并克隆到本地
//前提是已经创建远程仓库(本文省略)
git clone 'github/yidongying.com' //克隆到本地
一种是创建本地仓库并推送到远程仓库。
mkdir projectA //创建空文件夹
cd projectA && git init //初始化本地仓库
touch REMADME.md //创建readme文件
git add REMADME.md //提交文件到暂存区
git commit -m '添加readme文件' //将文件提交到本地仓库
git remote add origin '远程仓库地址' //关联远程仓库,前提是已经创建该仓库
git push -u origin master //将文件改动提交到远程仓库
然后小 A 开始建分支 a 并准备开发, 于是
git checkout -b feature/a //新建并切换分支
git push --set-upstream origin feature/a //推送分支到远程仓库
小 B 也需要在分支 feature/a 上开发, 所以她选择检出远程分支
git checkout --track origin/feature/a //检出远程分支
场景 2: 前进后退版本
a.保留现场并回滚版本
描述:发布新版本后,小A切到分支feature/a开始新任务的开发, 突然被告知线上版本存在紧急bug,于是小A决定清理工作区,回退版本并拉hotfix分支进行修复
# git stash保留现场
git stash save 'feature/a的开发'
# 切换分支到master
git checkout master
# git reset 回退版本并推送到远端
git reset –-hard HEAD //将当前版本重置到上一次commit提交,
git push -f origin master, //回退之后要强制推送到远端,保持远程与本地同步。
# 创建并切到hotfix
git checkout -b hotfix //等于git branch hotfix,git checkout hotfix
# 修改完之后,切到master并把修改合并到master,
git merge hotfix
# 切回feature/a并且同步master信息
git checkout feature/a && git merge master //也可以用git cherry-pick commitId,下文有介绍
# 将现场恢复,继续开发
git stash pop
tips: HEAD有两种方式表示版本号,一种是HEAD^, 一个^表示后退一步,从0开始,
另一种是HEAD~1,//~符号后加数字, 数字1表示回退1个版本
如果是指定版本,可以先用git log --pretty=oneline//查看要回退的commit id
然后执行git reset --hard b2968d5 //将当前版本重置到b2968d5,即指针指向b2968d5
tips: 添加
--hard参数后,会回到上次 commit 的状态,也就是说从上次 commit 之后的的修改都将被重置,即这些数据都丢失了,所以要谨慎操作哦。
reset 命令的三个参数对比
1、--soft 参数: 仅仅在本地库移动 HEAD 指针。
2、--mixed 参数:在本地库移动 HEAD 指针、重置暂存区。
3、--hard 参数:在本地库移动 HEAD 指针、 重置暂存区、重置工作区。
那么问题来了, 小 C 也有 master 分支,并且她的本地跟回退前是一致的,这时如果她直接 git pull 那么万事大吉,可当她看到下面命令,
一键 git push,一切白费。那怎么破?轮到 git revert 上场了
git revert 命令意思是撤销某次提交。它会产生一个新的提交,虽然代码回退了,但是版本依然是向前的,所以,当你用 revert 回退之后,所有人 pull 之后,他们的代码也自动的回退了.
git revert HEAD //撤销最近一次提交
git revert HEAD~1 //撤销上上次的提交,注意:数字从0开始
git revert 0ffaacc //撤销0ffaacc这次提交
如果在 revert 过程中出现了冲突, 而你又不想解决冲突, 那你可以终止操作:
git revert --abort
b.前进到指定版本
小 A 发现自己回退错了, 还能补救吗? git 允许你吃后悔药
git reflog //这条命令会显示所有你的历史版本信息,包括你回退前的版本
git reset --hard 924341d2d //前进到924341d2d版本
场景三:提交错了,git 帮你修复
a.在工作区丢弃某个文件的修改(或误删某个文件)
当你在工作区修改,改乱了某个文件的内容,要丢弃,或误删某个问题,需要恢复
git checkout -- <file>
b. git add 之后发现提交错了
在你执行 git add . 之后发现某个文件不该提交,
方式 1(推荐):reset
git reset HEAD [<file>] //<file>参数表示只重置该文件,否则为全部
没有带参数的 git reset 命令,默认执行了 --mixed 参数,即用 reset 版本库到指定版本,并重置缓存区,在上面的命令中指定的目录版本是 HEAD,即当前版本,所以实际上没有任何修改,仅是重置了缓存区.
方式 2:rm
git rm --cached <file> //从缓存区移除文件,使该文件变为未跟踪的状态,同时下次提交时从本地库中删除
c.commit 后,修改最近一次 commit 提交信息
git commit --amend -m "your new message" //这个操作修改你最近的一次commit提交, 并不会产生新的commit
d.修改历史记录的某一次提交
小 A 发现小 C 的某一次提交信息不对,于是帮她修改
(1)查看修改
git rebase -i master~1 #最近一次(可以用分支~数字的形式,数字n表示最近的第n次)
git rebase -i HEAD~3 #最近的三次提交,也可以用HEAD^的形式
git rebase -i afb927b3 #指定的提交
(2)按 vi 进入到编辑界面, 修改信息,esc,并:wq 退出
编辑界面可看到 git rebase 包含以下 Commands:
p, pick:正常选中
r, reword:选中,并且修改提交信息
e, edit:选中,rebase 时会暂停,允许修改这个 commit
s, squash:选中,会将当前 commit 与上一个 commit 合并
f, fixup:与 squash 相同,但不会保存当前 commit 的提交信息
x, exec :执行其他 shell 命令
b, break:停止执行,若要继续则使用 git rebase --continue
d, drop:删除提交
出现以下提示:
并且你的分支名称后面带有(rebasing)
此时可运行git commit –amend立即提交或运行git rebase –continue继续修改,也可运行git rebase --abort放弃 rebase 操作
出现Successfully rebased and updated refs/heads/XXX.则修改成功。
e.将多次 commit 提交合并为一次提交(git 压缩)
git rebase -i HEAD~3 //查看最近3次提交,HEAD^和HEAD~3用法同上
进入编辑界面, 将后面两次的 pick 改成 s(squash)或者 f(fixup),保存退出
如果保存的时候,你碰到了这个错误:
error: cannot 'squash' without a previous commit
注意不要合并先前提交的东西,也就是已经提交远程分支的纪录。
如果你异常退出了 vi 窗口,不要紧张:git rebase --edit-todo
如果遇到冲突, 先解决冲突, 之后 git rebase --continue继续执行
另外,在 merge 合并分支时, 使用 squash 也可以多次提交合并为一次提交
git merge --squash feature1 //当前在其他分支
git commit -m "message"
git branch -D feature1
f.分支合并时清理 commit 提交,保持分支整洁
小 A 从主分支 master 拉了一个 feature1 分支开发新需求,之后小 C 从 hotfix 分支修复了 bug,并合并到 mster,这时小 A 需要保持 feature1 与 master 分支同步,
可以使用
git merge master
查看 git log 会发现多了一些 merge 信息,这样看起来不太整洁,那么试试 git rebase
git rebase master
rebase 做了什么操作呢?
首先,git 会把 feature1 分支里面的每个 commit 取消掉;
其次,把上面的操作临时保存成 patch 文件,存在 .git/rebase 目录下
然后,把 feature1 分支更新到最新的 master 分支;
最后,把上面保存的 patch 文件应用到 feature1 分支上;
从 commit 记录我们可以看出来,feature1 分支是基于 hotfix 合并后的 master ,自然而然的成为了最领先的分支,而且没有 merge 的 commit 记录,是不是感觉舒服了很多。
g.提交错了分支,怎么破?
小 B 原本在 feature2 分支开发, 结果某天发懵,切到了 dev 并且提交了, 方法 1:reset
# 取消最新的提交,然后保留现场原状
git reset HEAD~ --soft
git stash
# 切换到正确的分支
git checkout feature2
git stash pop
git add . # 或添加特定文件
git commit -m "commit message"
方法 2:cherry-pick(摘樱桃)
git log //在dev分支找到要合并的commit记录
git checkout feature2
# 把主分支上的最新提交摘过来
git cherry-pick commitId //需合并到feature2的commitId
# 再删掉主分支上的最新提交
git checkout dev
git reset HEAD~ --hard
摘樱桃同样适用于将其他分支的某一次提交合并到当前分支。
h.手速太快,分支命名错误?
小B在新分支feature/test时,不小心打成了feature/tset,
git branch -m feature/tset feature/test //第一个为错误名称,第二个为正确名称
场景四:撤销/删除提交,撤销分支合并,删除分支
a.需求变更,撤销提交
产品经理告诉小 A, 需求变更了,原来的内容不要了,即某一次历史提交不需要了,小 A 以为是最近的一次提交,于是
git reset --hard HEAD //重置到上一次提交,HEAD指向前一次commit
git push -f
可实际是历史中间的某一次提交,所以
git revert -n commitId
git commit -m 'commit message'
与 reset 不同的是, revert 是撤销本次提交并生成新的提交(与此次相反),HEAD 是向前的,不会改变过去的历史。
tips: revert 有两个比较常用的参数, 分别是 -n, --no-commit:只抵消暂存区和工作区的文件变化,不产生新的提交。
-e, --edit:执行时不打开默认编辑器,直接使用 Git 自动生成的提交信息。
如果有冲突,解决冲突后,可git revert --continue继续操作,或git revert --abort放弃操作
b.提交有误,删除提交
小 A 不小心提交了一个配置信息的修改, 想要删除此次提交
#通过git log找到此次commitId,
git rebase -i commitId //进入编辑,将pick改为d,保存提交
c.撤销一次分支的合并 merge
方法1:reset 到 merge 前的版本,然后再重做接下来的操作,但这要求其他协作者也要将本地的 HEAD 都回滚回去;
git reset --hard commitId
git push origin HEAD --force //强推到远程
方法2:当merge以后还有别的操作和改动时,用git revert
git revert -m commitId
d.远程分支删除,本地分支解除关联
小A删除了分支feature1, 并推送到远端, 这时小C无法推送到远程,想要重新建立与远程仓库的关联,就需要先删除其原本的与已删除的远程分支的关联。
git push origin --delete feature1,
git branch -vv //查看远程分支,已经没有其关联分支
场景五: 恢复提交(文件)/分支
a.恢复已删除分支的提交
小C在feature/collect-luck1分支一顿操作之后将改动提交到本地仓库了,切到dev后打算合并feature/collect-luck1,但临时被打断,之后以为已经合并,于是 git branch -D feature/collect-luck1,回过神来才发现没合并,咋办?
git fsck --lost-found //找出刚才删除的分支里面的提交对象
#返回信息 dangling commit 7f665fc6024d16e42d4332b07f7588cb1937ad6d
git show 7f665fc //确认是否是自己要找的内容
git rebase 7f665fc 找到之后,用git rebase进行恢复
若使用了git gc清除,使用git fsck并不能找回,更多关于找回丢失提交问题,请查阅:Git Community Book中文版-找回丢失的对象, 关于git fsck 命令请查阅这里。
b.恢复误删的本地分支
上述提到小C误删了feature/collect-luck1,她仍想找回该分支,于是
git reflog //找到操作删除的commitid
git checkout -b feature/collect-luck1 HEAD@{2}
出现
unknow switch 'e',别急,换成commitId就行,
git checkout -b feature/collect-luck1 c38bda30
执行完成后,分支恢复到 HEAD@{2} 的快照,并且当前在feature/collect-luck1分支, 但现在丢失了最新的一次提交test111, 只需要使用git reset恢复即可。
四. 一些git命令常常搞混?一时不知道用哪个?
有些命令很相似,别再傻傻分不清了,一次性帮你理清。
1.git log 和 git reflog
git log 可以显示所有提交过的信息,
可以加上
--pretty=oneline 则只会显示版本号和提交时的备注信息
git reflog会显示所有的操作记录,包括删除,合并等。
如果不是需要恢复reset或rebase的记录,直接使用git log会更加便捷。
2.git reset 和 git revert
两者最大的区别是,git revert 仅仅是撤销某次提交,而git reset会将撤销点之后的操作都回退到暂存区中。
git revert是用一次新的commit来回滚之前的commit,新的commit与要revert的内容正好相反,HEAD依然是前进的;
git reset是直接删除指定的commit,HEAD会移动到上一次提交。
3.git merge 和 git rebase
两者都是可以合并其他分支到当前分支,区别在于:
git merge会产生一次新的commit, 出现冲突时,可修改后直接commit;
git rebase会合并之前的commit历史,不会产生新的commit,出现冲突时,修改后需要先git add,再执行 git rebase --continue(如果无效,可执行 git rebase --skip)。
git rebase的黄金法则是: never use it on public branches(不要在公共分支上使用),git rebase是魔鬼还是天使,查阅博文
更多关于merge还是rebase,查阅Merging vs. Rebasing
文末福利
1.给大家推荐一个实用工具,gitignore,
mac,window, vscode有哪些需要忽略的文件,一查便知。
2.git commit提交规范
一条规范的的commit信息,不仅让自己容易排查问题,也有助于他人review,写好commit也是非常必要的。
一条commit信息大致分为三个部分(使用空行分割):
标题行: 必填, 描述主要修改类型和内容
主题内容: 描述为什么修改, 做了什么样的修改, 以及开发的思路等等
页脚注释: 放 Breaking Changes 或 Closed Issues
分别由如下部分构成:
type: commit 的类型
feat: 新特性
fix: 修改问题
refactor: 代码重构
docs: 文档修改
style: 代码格式修改, 注意不是 css 修改
test: 测试用例修改
chore: 其他修改, 比如构建流程, 依赖管理.
scope: commit 影响的范围, 比如: route, component, utils, build...
subject: commit 的概述, 建议符合 50/72 formatting
body: commit 具体修改内容, 可以分为多行, 建议符合 50/72 formatting
footer: 一些备注, 通常是 BREAKING CHANGE 或修复的 bug 的链接.
这样一个符合规范的 commit message, 就好像是一份邮件.
参考文献:
Git如何回滚一次错误的合并
一份值得收藏的 Git 异常处理清单
Git常用命令参考手册
优雅的提交你的 Git Commit Message