一文搞懂本地和远程分支代码回退git reset
代码中有时候不可避免会出现不该commit的文件被commit,不该push的文件被push到Github里等场景,这时,git reset 这个不那么熟悉的命令就派上用场了。但是 ,git reset hard、soft、mixed三者有区别吗?git reset会有哪些风险呢?本地和远程仓库的git reset有什么区别?我们该如何避免git reset?
最近痴迷于git flow,打算对之前大部分人一提起就害怕的代码回退——git reset来进行一个实践,也算是一篇操作总结,想直接看结论的可以拉到文末,如有纰漏,欢迎大佬们指正补充。
git进阶之路——git reset
一、本地git reset
在代码还没push到代码仓时,我们的代码都是本地操作,这时的git reset都在本地进行。那一般会有几种情况需要reset呢?总结了一下,大概有以下三种:
- 不该add的文件被add;
- 某些代码commit后想回退;
- 某些已经commit的代码完全不想要了
下面就让我们来模拟一下,这三种情况下,代码要怎么进行reset。
在开始之前,给大家送上一些福利图,这对于接下来的场景理解一定会有所帮助。
文件状态转化:
文件存储流程:
git提交文件流程:
git-reset图:
1.1 把不该add的文件add了
我们在码云上新建一个代码仓,pull之后,在本地添加.gitignore文件,然后我们push到remote。这时本地和远程上的提交信息只有一个:
然后,我们在本地新添加一个Java文件:GitDemo.java,如下图:
然后再添加并add一个新文件:GitRollback.java:
然后我们commit这两个文件:
这时,我们发现GitRollback这个类是不需要提交的,那怎么办呢?
我们根据一些网友说的checkout来试试:
git checkout -- GitRollback.java
结果如下图,根本不起作用,:
git push 时还是默认选择两个文件:
根据网友的说法,是因为此时的代码已经被add了,但是checkout这个命令究竟什么时候可以用?我们下文再谈。
继续第一种场景,从开头的git-reset图,我们可以知道,这时我们要回到没有被add的场景,那我们应该选择的是git reset – soft,我们来执行一下:
git reset --soft --GitRollback.java
没有任何作用,说明该命令不能只回退一个文件,那我们就只能进行版本回退了,在版本回退的同时,我们直接用idea工具来回退。
步骤如下:
1、选择版本:
2、在idea的工具栏上面,点击git—reset head ,出现如下:
填入版本号之后,我们发现本地的两个文件都已经恢复到没被add的状态,并且commit记录消失:
要是我们选择的是soft回退会怎么样?我们重新刚才的场景:
这次我们选择soft,点击reset后,再次commit时,发现文件还是add状态:
可以得知:
如果要让文件回退到未被add的状态,只能选择用git reset --mixed
1.2 某些代码commit后想回退;
在第一种情境的基础上,我们先commit&push代码,然后我们在GitRollback.java 上添加一些代码,并commit:
这时,我们只是想保存代码,而不是回退到未add的状态,那我们执行git reset soft即可:
那如果选择 mixed,代码会变成没有add的状态吗?我们复现一下:
发现没有区别,因为我们并没有在上一次commit中去新增文件,所以只是回退代码到未commit的状态,而不是回到没有被add的状态,所以这种场景下,选择soft或者mixed都可以。
1.3 某些已经commit的代码完全不想要了
让我们模拟第三种场景,我们在GitRollback.java中,添加了一下代码,并新增了一个类,同时commit:
但是,如果我们不想要这段代码而且要保存这个类,也就是部分回退,那应该怎么操作呢?
打开命令窗口,输入以下命令:
git reset 1144a121 GitRollback.java
命令中的1144a121是上个版本的版本号。命令格式如下:
git reset 版本号 filename
结果如图:
我们需要再次在命令窗口执行commit代码,随便输入一个commit message,例如;
git commit -m "just reset local second commit on GitRollback.java"
这时我们的工作区还没有任何变化,我们在控制台输入:
git checkout GitRollback.java
再看我们的工作区:
代码已经回退,并且新增的类依旧在。查看git log:
多了一个commit信息。如果我们不想要commit,又该怎么样操作?我们复现一下。
将代码回退到上一个版本之后,我们使用hard命令来回退试试:
git reset --hard 1144a121 GitRollback.java
结果如下,并不能回退:
这时,我们上文提到的checkout命令就派上用场了。我们在命令窗口输入:
git checkout 1144a121 -- GitRollback.java
结果如下:
本地新增文件没有回退,并且指定文件代码回退到上一个版本,查看git log:
可以发现,并没有多余的commit信息,所以,对于这种情况,我们推荐使用checkout命令。
可能会有读者好奇,每次我都是怎么复现场景的,其实,我是用了git reset hard 来快速复现的,这个命令的应用场景又是怎么样的呢?
1.4 将本地近期commit的版本的代码完全放弃,直接用远程的代码
git reset hard这个命令,就是用于这种场景的。当我们本地修改的代码不多,或者是不小心改动了多次之后,如果不想再从remote clone code,这应该是最好的实现方式。
我们来看看本地和 第一次commit之间的区别:
第一次commit的时候,本地只有两个Java类,而现在,我们有四个类,那如果我们想不保留这两个类,而且不留下commit信息,如何做呢?
复制第一次commit的版本号,打开idea最上方的git工具栏,git —— reset head,选择hard并输入版本号:
结果如下:
本地代码文件变成两个,没有额外添加新的commit记录,但是本地旧的commit记录仍然保持。
再看远程的commmit记录:
说明本地的git commit 记录没有实时更新。
1.5 本地代码回退多次
我们在远程新建一个文件:helloword.java,然后进行commit:
之后,我们在本地进行3次commit:
然后,我们update本地代码,发现本地最近的两次修改都有问题,这时就面临一个git reset的问题。
其实,git reset 除了指定版本号进行回退外,默认还有一个参数head,代表当前版本,我们只需要在head后加上要回退的次数,即可:
git reset head~2
操作如下:
我们validate一下,看看内容:
发现是第二次提交的内容,不是第一次commit的内容,回退后查看工作区:
发现我们第二次commit的内容还在,这是因为我们在本地commit之后还进行了update,update默认也算一次提交。
我们再次进行update,这次,我们选择rebase,看看git reset head 是否会回退到本地只提交一次时的代码:
git rebase之后,可以发现本地没有多余的commit信息,我们再执行一次:
git reset head~2
validate一下:
发现是第一次commit的内容,reset之后查看工作区:
代码成功回退到第一次commit的场景。
总结:
1、要回退本地的多次commit,用git reset head~num 命令,其中num代表你要回退几次commit;
2、update project时,建议使用git rebase命令,这样update时不会生成多余的commit信息,而且会将本地的commit放到最前面,回退时就可以精准回退。
二、远程git reset
一般情况下,我们不会去回退远程版本到指定版本,因为远程代码在多人协作的情况下,可能更新速度比较快,一旦回退会影响团队其他人的代码,为了演示,这里我们看看这两种场景。
1、因为码云默认分支的设置缘故,不能force push,所以我们新建dev分支并push到remote之后,我们在dev分支上去push一些代码,发现最新一次的push代码有问题,回退到上一个版本
如上图,我们决定把代码回退到the second commit这个版本。
我们复制码云上的版本号,然后用idea自带的git reset 功能选择mixed,填入版本号,执行:
结果如图:
第三次commit的代码已经重新变成未commit状态,查看git log,commit信息还在:
那远程要怎么和本地保持一致呢?
git 有个不常用的force push 这时就派上用场了,如下图:
点击后,本地commit信息消失:
查看远程代码:
commit信息同步一致,说明版本已经回退。
2、发现某次push的代码里有问题,该如何回退到指定版本
在本地和远程同时commit&push几次代码,现在我们的版本到了v5,相对于the second commit而言,我们多了一个文件和一行代码。
同样,我们要reset to the second commit,那我们同样复制该版本号。然后,本地回退:
回退完成后,我们再次选择commit时,会把remote的新建文件也默认勾选了,这会造成一定的困扰:这个文件究竟是我之前修改的,还是其他人修改的呢?
那我们暂时不看commit,直接force push,代码强推上仓库了。
所以这时说明版本也回退了,本地代码没有被覆盖的问题。
三、如何避免git reset
虽然上面说了种种关于git reset的技巧和方法,但我希望各位读者永远都用不上。作为一个coder,我们应该尽量避免这种可能会造成代码覆盖的高风险事情的发生。
改掉一个bug,最好的方式是重写代码后commit,而不是在本地搞一堆reset之后,还担心把别人的代码给覆盖掉,有时还会分不清哪些代码是自己改的,哪些是别人改的。
这是我一直持有的观点。那我们如何避免呢?我认为有以下两种方式。
1、设立保护分支
现在类似码云、GitHub上都有设置保护分支的功能,本地的代码只能被提交到自己的分支上,如果要合并到保护分支,那就必须先经过commiter的审视,这样子,即使本地代码再怎么玩,只要最后一关没被合入保护分支,那就不会造成代码仓的代码问题。
2、idea自带的rollback和local history功能
作为一个高级开发工具,idea在git上的功能还是比较强大的,当我们本地的代码要commit之前,可以点击下图的rollback功能,看看我们改了哪些东西,哪些需要回退:
也可以打开local history:
查看各个文件的history,避免修改有误。
四、总结
-
如果要将本地已经add的文件回退到工作区(untracked ),请使用git reset --mixed 功能;
-
已经commit的文件代码如果想回退,不管用git reset --mixed还是git reset --soft,都只是将代码回退到add状态,并不会将其回退到untracked ;
-
回退commit中的某个文件到某个指定版本,可以用
git checkout 版本号 -- filename
- 回退到某个版本并不保留commit记录和代码时,用git reset hard。
- 所有的git reset命令都会改写commit log,只是有时local和remote不一致。
- git reset remote 只能 force push到非保护分支,会造成remote repository的commit log和代码消失问题。
- 避免git reset,最好是设置保护分支,然后合理利用好idea自带的rollback和local history功能。
参考链接:
What’s the difference between git reset --mixed, --soft, and --hard?
更多推荐
所有评论(0)