1.错误场景
有时我们会遇到这种情况:我们从develop 分支新建一个名为feat/home 分支去做A功能,然后由于一些其他原因A 功能需要延后,然后我们再从develop分支新建一个分支去做B功能或者C功能,在多分支多功能开发时,就容易出现做B功能时,忘记切换分支,一直等做完了提交了push之后才发现 push 错了远端的分支,并且 push 的改动与该分支需要开发的功能并没有交集,因此我们需要将已经提交错的分支内容回滚并提交push 到正确的远端分支。
2.已经commit,但是未push到远端
使用 git reset
命令,可以在提交层面在私有分支舍弃一些没有提交的更改:
# 回退到上一个版本 git reset --hard HEAD^ # 向前回退两个版本 git reset --hard HEAD~2
git reset 命令主要有三个选项: --soft、--mixed 、--hard,默认参数为 --mixed。
git reset --soft 软重置,只撤销了git commit操作,保留了 git add 操作
git reset --hard 具有破坏性,是很危险的操作,它很容易导致数据丢失,如果我们真的进行了该操作想要找回丢失的数据,那么此时可以使用git reflog 穿梭到未来,找到丢失的commit
git reset --mixed 会保留提交的源码改动,只是将索引信息回退到了某一个版本,如果还需要继续提交,再次执行 git add 和 git commit
第一种方法:
适用于多个分支一起开发的时候将A分支的改动错误的提交到B的场景:
# 将该分支的本不应该提交的commit撤销 git reset HEAD^ # 按需选择想要回到哪个版本 # 回到HEAD git reset --soft HEAD # 回到HEAD的前一个版本 git reset --soft HEAD^ # 回到HEAD的前10个版本 git reset --soft HEAD~5 # 利用id回到指定版本 git reset --soft a06ef2f # 将撤销的代码暂存起来 git stash # 切换到正确的分支 git checkout feat/xxx # 重新应用缓存 git stash pop # 在正确的分支进行提交操作 git add . && git commit -m "update xxxx"
第二种方法:
适用于在不小心在 master 分支上提交了代码,而实际想要在 feature 分支上提交代码的场景: # 新建出一个新分支,但是仍在master 分支上,并不会切换到新分支 git branch feat/update # 恢复master本身提交的状态 git reset --hard origin/master # 提交错的代码已经在新检出的分支上面了,可以继续进行开发或者push git checkout feat/update
第三种方法:
适用于想要对特定的某一个或几个commit 进行“嫁接”,使其复制一份到正确的 feature 分支的场景;
在功能性迭代开发中发现一个bug,并提交了一个 commit 进行修复,但是发现该bug也存在线上的发布版本上,必须要尽快对线上进行修复,此时可以使用git cherry-pick 将bug修复的commit 嫁接到 fix 分支上进行代码修复,并及时发布,解决线上bug。
# 先切换到正确的分支 git checkout feat/update # 取出提交错误的或bug fix的 commit 引入到feat/update 分支中 git cherry-pick a06ef2f # 回到错误的分支 git checkout feat/feedback # 将 a06ef2f 的改动从当前分支销毁 git reset --head a06ef2f
如果你只想把改动转移到目标分支,但是并不想提交,可以这样做:
# --no-commit 参数会使嫁接过来的改动不会提交,只会放在暂存区 git cherry-pick b9dabf9 --no-commit
第四种方法:
适用于当多个文件被缓存时,发现其中一个文件是其他分支的功能性改动,想直接取消该文件的缓存:
# 编辑了 1.js 2.js 3.js # 缓存所有改动的文件 git add . # 发现 3.js 不应该出现在此时提交的功能上,要取消它的缓存 git reset 3.js # 此时3.js 被取消了缓存,我们继续提交1.js 2.js git commit -m "Update 1.js 2.js" # 将3.js 暂存起来 git stash # 切换到提交 3.js 改动的分支 git checkout feat/update # 重新应用缓存起来的 stash(3.js) # pop 参数会将缓存栈的第一个stash删除,并将对应修改应用到当前分支目录下 git stash pop # 继续提交 git add && git commit -m "update 3.js"
3.Commit之后已经 push 到了远端
此时我们需要借助 git revert
命令来撤销我们的操作。
解决方式:
# 撤销最近的一次提交 git revert HEAD --no-edit
接着我们使用 sourceTree 查看撤销之后的提交历史:
我们看到想要撤销的 SHA1 为 db6bb3 的 commit(Update 2.js)记录还在,并且多了一个SHA1 为 6e1d7ee 新的 commit(Revert “Update 2.js”)。因此可以看出,git revert 是对给定的 commit 提交进行逆过程,该命令会引入一个新的提交来抵消给定提交的影响。 和 git cherry-pick 一样,revert命令不修改版本库的现存历史记录,相反它只会在记录添加新的提交。
接下来我们已经解决了错误分支的提交,但是还要把这次提交放到正确的分支上,依然可以使用 git cherry pick 去操作:
# 将revert commit push到远端 git push origin feat/feedback # 切换到正确的分支 git checkout feat/update 将目标commit 嫁接到当前分支 git cherry pick db6bb3f
git revert 后面可以加不同的参数达到不同的撤销效果,常用的如下:
--edit :该参数为git revert 的默认参数,它会自动创建提交日志提醒,此时会弹出编辑器会话,可以在里面修改提交消息,然后再提交。
--no-edit :表示不编辑 commit 信息,revert 的 commit 会直接自动变回 ‘Revert + 想要撤销的commit 的message’ 的格式。上面例子中使用的就是这种方式。
--no-commit:该命令会使撤销的 commit 里面的改动放到暂存区,不进行提交,用户可以自行再次提交。这种参数并且适用于将多个 commit 结果还原到索引中,集体放置在缓冲区,进行用户自定义的操作。
4.改动不仅已经 push 到远端,并且已经合到主仓库
当我们把本不属于该分支的代码或者不需要提交的改动提交到主仓库,并合并到了develop 仓库之后,这是想要撤销合到主仓库的改动,解决方式如下:
1. 当以pull request 的方式进行的合并:
Github中支持pull request撤销操作,在对应的pull request页面点击 Revert 按钮救命即可😊
2. 当用命令行执行合并时:
上面展示了通过界面按钮去操作如何撤销已经合并develop 分支的改动,那么在个人项目中用命令行操作是怎么样的呢?
# 添加三个文件 echo 1 > 1.html echo 2 > 2.html echo 3 > 3.html # 以为提交的是1.html 2.html,将改动推到了远端分支 git add . && git commit -m "Add 1.html 2.html" git push origin feat/update # 将feat/update的改动创建一个“合并提交”合入develop 分支,生成的 Merge commit 的SHA1 为 f439c6f git checkout develop git merge feat/update --no-ff # 如果存在冲突,先解决冲突,然后继续请求合并 git add . && git merge --continue # 将develop 合并的最后结果提交到远端 git push origin develop # 合并之后发现不应该将3.html 不应该放入功能迭代中。需要撤销本次合并 # 做任何操作前,先保证本地的develop 代码是最新状态 git pull --rebase origin develop # 从develop分支新建一个 revert 分支 git checkout -b revert-feat/update # 用 -m 参数指定父编号(从1开始),因为它是“合并提交” git revert -m 1 f439c6f # push revert 的改动 git push origin revert-feat/update # 切换回 develop 分支,将 revert-feat/update 分支进行合并 git checkout develop git merge revert-feat/update --no-ff git push origin develop
5.revert 错误,需要再次补救
当我们的代码合到主仓库,并且成功发布到生产环境,此时发现线上有集中报错,必须马上将线上代码回滚到最新版本。这是我们需要进行revert 操作。revert 的代码发布到生产之后,发现错误仍旧存在,最后排查到是某个外部服务依赖出现问题,本次revert 的改动无关,并且外部服务已经恢复。此时需要将 revert 的改动再次发布上生产环境。
我们可以再用一次git revert,revert 掉我们之前的 revert commit:
git revert HEAD --no-edit
这样 revert 撤销的改动又回来了,此时会发现提交历史上又会出现一个新的revert commit。