赞
踩
本文介绍通过命令 git reset, git rebase, git revert 实现对已commit或push的文件进行撤销操作。
图示环境是 MacOS + GitLab + Sourcetree + Shell。
其中GitLab是笔者在MacOS上借助Docker自行搭建的git仓库管理系统。
1. 先来看 git reset 的用法
多次commit之后,test分支的文件状态:
需求:将test分支最近一次提交的 “commit fff file”{cid:dd55e5e} 这条记录撤销
git reset --soft HEAD~1
撤销最近一次的commit(撤销commit,不撤销git add)
git reset --mixed HEAD~1
撤销最近一次的commit(撤销commit,撤销git add)
git reset --hard HEAD~1
撤销最近一次的commit(撤销commit,撤销git add,工作区的代码改动将丢失。操作完成后回到上一次commit状态)
复制代码HEAD~1的意思是最近一个版本,也可以写成HEAD^
如果需要撤回最近两次提交的commit,可以使用HEAD~2,依次类推。
执行 git reset --soft HEAD~1 后,状态如下:
执行 git reset --mixed HEAD~1 后,状态如下:
执行 git reset --hard HEAD~1 后,状态如下:
可见,“commit fff file”{cid:dd55e5e} 这条记录在 git log 打印的提交日志中都不存在了。只是三种方式对工作空间的改动不一样。
--soft 保留已撤销commit的代码变更,不会撤销git add,
--mixed 保留已撤销commit的代码变更,撤销git add,
--hard 删除已撤销commit的代码变更,撤销git add,工作空间回到上一次commit状态。
如果一次撤销多个commit,工作区状态如何?
2. git rebase 撤销某个commit
如果不是撤销最近的一个或多个commit,而是撤销某个commit呢?例如:
先来看看git reset 能否做到。git reset有个用法是git reset --soft/--mixed/--hard commitId,如果用git reset --soft d9a16cc,会撤销eee的提交记录吗?
看来并不能!!! git reset --soft commitId的用法只是把HEAD指针指向${commitId}对应的提交记录,该记录之后的提交记录会被撤销。
如果${commitId}是最近的第二次提交,`git reset --soft commitId`相当于`git reset --soft HEAD~1`,如果${commitId}是最近的第三次提交,则相当于HEAD~2,依次类推。
复制代码
用git rebase可以做到撤销某个commit。
如果要撤销“commit eee file”{cid:d9a16cc}这个提交,使用git rebase -i 9df3805,其中「9df3805」是eee的上一次提交的commitId。当然,也可以使用git rebase -i HEAD~2。
执行 git rebase -i 9df3805 之后,会出现下面的交互式vim编辑框,
按照图示将 "commit eee file" 左侧的pick改为d或者drop后,会丢掉对应的commit。从而达到撤销的目的。
用 git rebase 实现撤销和 git reset --hard 的效果类似,即「删除已撤销commit的代码变更,撤销git add,工作空间回到上一次commit状态」。如果被撤销commit的代码还有用,使用时须谨慎。
git rebase命令可以做很多工作,例如优化本地分支的提交记录,分支线性化处理(避免过多的merge出现)等等。
3. 撤销已push的文件
主要思路就是在本地分支撤销了commit之后,将变更推送到远端。但必须用git push -f强制提交,否则会提交失败,原因是:本地的版本号低于远端的版本号。
需要注意的是,如果test分支不只是你自己一个人维护,别人也在向这个分支上push代码,在进行强制推送之前就要注意下了,有可能会把别人的提交撤销掉。
如果你是项目的owner,在本地master分支使用git rebase 或者 git reset撤销了一些commit之后,想要强制推送到远端,以使远端的记录也撤销掉。你会使用git push origin master -f,但可能会遇到下面的错误。
意思就是master分支是“protected branch”,不允许强制变更。解决方法是登录GitLab,进入项目的设置页面,选择Repository,找到Protected Branches,对分支进行「Unprotect」即可。
风险问题大家根据项目情况自行评估。
「Unprotect」之后 push -f 就可以成功了。
4. git revert 回退
git revert 是一个很安全也很好用的命令,不同于git reset的重置,它是通过反向操作来完成撤销的。先来看用法。
需求:撤销“commit eee file”{cid:d9a16cc} 的变更。
git revert 后面一般跟commitId, 是你想回退的commit的id。例如在上图示例中,我想回退eee的提交,则commitId即是「d9a16cc」。
git revert 执行后会自动生成一个类似「Revert "commit message"」的新的commit。该commit的内容和需要revert的内容相反。若回退前新增了一个文件,revert后会将该文件删除;若会提前删除了一个文件的一行代码,revert后会将该文件的该行代码补回来。
如果需要将撤销更新到远端,push即可,不需要push -f。
总结1
git rebase (drop) 相当于 get reset --hard,不会保留要撤销commit的代码变更。
git reset 和 git rebase (drop) 都是通过删除之前commit的方式,达到撤销操作的目的,而 git revert 则是通过自动的反向操作完成这一目的。不同于前两个指令,git revert的HEAD指针是继续前进的。
根据要撤销commit所在分支的情况,选择适当的命令。
一般来说,git revert 更安全,但也会生成新的revert commit,如果撤销的commit很多的话,git log 不是很好看(当然,也有办法优化,可以通过git rebase 将多个revert commit 合并成一个。见总结2)。
如果要撤销的commit还没被推到远端,不妨使用 git rebase (drop) 或者 git reset。如果已经被推送到和他人共同维护的远端分支,或者已经被merge到主分支,最好使用git revert。
总结2
如果想使用git revert回退多个commit,且只生成一个 revert commit 提交,以使git log看上去更加简洁(有时候回退的多个commit实为同一功能),可以借助 git rebase 实现。
正如前文讲的,git rebase 很有用。以后有时间,会讲讲 git rebase 对提交线性化的处理,这个在日常工作中git和svn同步开发时,非常有用。
总结3
如果使用git reset --hard 撤销了commit,而且也推送到了远端,后悔了想要找回已撤销commit的代码变更,不用担心,git reflog 完全可以做到。
或许你会发现,本文的示例图,初始状态都一样,即最近一次提交 "commit fff file"{cid:dd55e5e}。这是因为在做了撤销和回退操作后,都用 git reflog 回退了撤销操作。
推荐阅读
很开心你读完此篇。
我是夕月,程序媛一枚。
30而立,2020年是摸索前行的一年,和星辰一起立下了发文分享的flag。
如果你感兴趣,不妨常来看看,我们成长路上或许会有共鸣。
我们的博客站:xiyuechen.net
微信公众号「星辰和夕月」。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。