回滚、还原、重置和变基
- 关键词:Git For Teams – 读书笔记
Part2 在工作流中使用命令
回滚、还原、重置和变基
轻则微调一条提交,重则抹除历史记录。错误的提交和移除通常都发生在个人仓库,但你处理错误的方式将会影响到其他人与代码库的交互。
- 学习目的:
- 修补一份提交,并加入新的工作
- 将一份文件恢复到之前的状态
- 将你的工作目录恢复到上次提交时的状态
- 还原之前作出的变更
- 使用变基重塑提交历史记录
- 从仓库中移除一份文件
- 移除某个分支上因为错误的合并引入的提交
“你将会学习容易被忽略,但影响巨大的使用技巧”。
1 最佳实践
1.1 描述问题
先了解三个概念:工作目录(你的文件系统中可见的文件)、暂存区(在下一次commit后即将写入仓库的变更的索引)和仓库(存储文件并记录了文件的每次变更)。
git将信息存放在不同地方,当你将问题隔离在这些不同的地方时,将能够更好地选择正确的命令序列,将工作恢复到你想到的状态。
选择正确的撤销方法 | 备注 | 解决方案 |
---|---|---|
舍弃工作目录中对一个文件的修改 | 修改的文件未被暂存或提交 | checkout –filename |
舍弃工作目录中所有未保存的变更 | 文件已暂存,但未被提交 | reset –hard |
合并与某个特定提交(但不含)之间的多个提交 | reset –commit | |
移除所有未保存的变更,包含未跟踪的文件 | 修改的文件未被提交 | clean -fd |
移除所有已暂存的变更和在某个提交之前提交的工作,但不移除工作目录中的新文件 | reset –hard commit | |
移除之前的工作,但完整保留提交历史记录(“前进式回滚”) | 分支已经被发布,工作目录是干净的 | revert commit |
从分支历史记录中移除一个单独的提交 | 修改的文件已经被提交,工作目录是干净的,分支尚未进行发布 | rebase –interactive commit |
保留之前的工作,但与另一提交合并 | 选择squash(压缩)选项 | rebase –interactive commit |
比如:你希望舍弃工作目录中对一个文件的修改,错误的文件副本没有被暂存或提交
比如:创建一个列表,列出所有你想要从中恢复的问题场景。问题描述的越好,就越有可能找到正确的解决方案。
创建一个流程图来帮助你选择合适的命令
1.2 使用分支进行试验性的工作
在分支树上,一个分支与它的兄弟分支是相互独立的。尽管它们有共同的祖先分支,你通常可以看到在树上移除一个分支却不会影响到其他分支。
在Git中,你添加到仓库中的提交与一个或多个分支相关联。如果你签出一个不同的分支,并在这个新的分支上操作提交对象,那么系统会给这些提交对象分配一个新的标识符,而绑定到旧分支上的提交对象不会改变。也就是说,在新的私有分支中工作一定是安全的,当你对结果满意时,再将你的分支并入主分支。
在分支中工作帮助你免遭意外的修改,在工作正确且完整后再将它并入主分支
如果你在一个工单上进行工作,但不确定应该使用两种方法中的哪一种。这种情况下,你可以从你的工单分支上创建一个新的分支,在新的分支上进行试验性的修改,如果你想要保存这些修改,再将试验性的分支并入你的工单分支。
git checkout -b exprerimental_idea(使用试验性的分支来测试修改)
git add –all
git commit
注意:当你合并两个分支时,可以选择使用带有–squash参数的合并,以将所有提交并入一个提交。通过这种方式合并分支,以后你不能撤销分支合并。只有在合并你本来就不希望分离的分支时,才使用–squash。
git checkout master(将你的试验性分支合并回主分支)
git merge experimental_idea –squash
git commit
在合并后,你可以删除你的试验性分支:
git branch –delete experimental_idea(如果是删除一个未合并的分支,你需要使用-D参数,而不是–delete参数)
2.分步变基
在rebase、reset和revert三个命令中,rebase是唯一作用不局限于撤销工作的命令。
一般当我们谈论变基时,我们指的是使用父分支上的提交来更新一个分支的过程。通常这个过程直截了当:当你想要更新的分支上,运行rebase命令并使用父分支的名称作为参数。git将你的提交从工作所在的子分支上移除,并将父分支上产生的新提交添加到你的分支顶端。
如果在变基过程中,你完成的工作与你试图放到父分支上的工作出现了有冲突的修改,这个过程将会停止,Git会要求你在继续之前先手动解决冲突。
rebase造成困扰的第二个原因是当它被用于强制更新公共分支时。在这种情况下,时间线上将会出现两个(或多个)拥有不同ID的提交对象,它们包含相同的代码(应该变基还是合并)。
2.1开始变基
保证父分支的本地副本与项目主仓库中最新的提交同步:
git checkout master
git pull –rebase=preserve remote_nickname master
当前分支上的修改与主项目不同步,而新项目中的新工作尚未被引入:
git checkout feature
开始变基过程:
git rebase master
2.2文件删除造成的变基中冲突
当你和父分支中新的提交修改了同一行时,你在变基时会遇到冲突。
比如,ch10在源分支master中被删除了,但我在feature分支上继续对它进行修改
以下步骤操作 | 备注 |
---|---|
解决合并冲突 | |
当我认为合并冲突已经被解决时,运行命令 | git rebase –continue |
使用我喜欢的文件对比工具打开有问题的文件,完成第一步 | git mergetool ch10 |
文件中不再显示有合并冲突,因此我退出合并工具,并继续进行Git提示的下一步操作 | git rebase –continue |
通过git status得到Unmerged paths和后面的deleted by us | 我不希望这个文件被删除,因此我需要unstage(取消暂存)Git的修改 |
通过下面的文字,告诉我如何阻止这个变更发生 | git reset HEAD ch10(其实是清除暂存区,将指针回退到最新的一个已知的提交) |
2.3单个文件合并冲突造成的变基中冲突
总是可以使用git rebase –abort命令退出这个流程,你将回到开始变基前你的分支所处的状态。
3.定位丢失的工作概述
比较git log –oneline和git reflog两个命令的输出。这两个命令都会显示仓库中某个状态的提交ID。只要你找到了提交ID,你就可以签出这个提交,及时将代码库的版本临时恢复到那个节点。
git checkout commit(在仓库中签出特定提交)
git checkout -b restoring_old_commit
git checkout working_branch
git merge restoring_old_commit
在合并完成后,你现在应该删除临时分支来整理你的本地仓库,如下:
git branch –delete restoring_old_commit
如果你已经发布了临时分支,并且希望从远程仓库删除它,那么你需要显式进行这一步:
git push –delete restoring_old_commit
git cherry-pick -x commit(使用cherry-pick将提交复制到新的分支)
假设这些提交被干净地应用到了你当前的分支,如果出现了问题,你可能需要手动解决提交冲突。
你可能会遇到的另一个输出是:你想要并入的提交是一个合并提交。在这种情况下,你将会需要选择父分支。
git log –oneline –graph(确认你想要保留的父分支是图形化输出的日志中左侧第一个分支)
git cherry-pick -x commit –mainline 1(再次运行cherry-pick命令,这次使用–mainline参数标识父分支)
git reset –merge ORIG_HEAD(如果你决定不保留恢复的工作,可以舍弃这些变更)
4.还原文件
不小心错误地删除了文件,或者编辑了不该编辑的文件。你可以将文件内容恢复到你当前所在的分支中最新的已知提交,并存储这个文件。
rm README.md
git status
状态消息说明了如何撤销变更并恢复被删除的文件。如下:
git checkout –README.md
……待更新
撤销工作需要的git命令 | 备注 |
---|---|
git checkout -b branch | 创建一个名为branch的分支 |
git add filename(s) | 暂存文件,准备提交至仓库 |
git commit | 将暂存的变更保存至仓库 |
git checkout branch | 切换到指定分支 |
git merge branch | 将branch中的提交并入当前分支 |
git branch –delete | 移除本地分支 |
git branch -D | 移除不包含并入其他分支的提交的本地分支 |
git clone URL | 下载一份远程仓库的副本 |
git log | 查看项目历史记录 |
git reflog | 查看分支的详细历史记录 |
git checkout commit | 切换到另一个本地分支 |
git cherry-pick commit | 将提交从一个分支复制到另一个分支 |
git reset –merge ORIG_HEAD | 移除当前分支中所有在最近一次合并中引入的提交 |
git checkout –filename | 还原已变更但尚未提交的文件 |
git reset HEAD filename | 从暂存区移除提出的文件修改 |
git reset –hard HEAD | 将所有已变更的文件还原到之前保存的状态 |
git reset commit | 取消暂存在这个提交之前的所有提交中的变更 |
git rebase –interactive commit | 编辑,或压缩提交后的所有提交 |
git rebase –continue | 在解决合并冲突后,继续变基过程 |
git revert commit | 取消应用指定提交中的变更,创建一个共享友好的历史记录还原 |
git log –oneline –graph | 显示分支的图形化历史记录 |
git revert –mainline 1 commit | 反转一个合并提交 |
git branch –contains commit | 列出所有包含指定提交对象的分支 |
git revert –no-commit last_commit_to_keep..newest_commit_to_reject | 使用一个提交反转一组提交,而不是为每个撤销的提交都创建一个对象 |
git filter-branch | 从仓库中永久移除文件 |
git reflog expire | 忽略详细历史记录,仅使用存储的提交消息 |
git gc –prune=now | 运行垃圾回收器并确保所有未提交的变更从本地内存中移除 |
9.总结
Reset(重置):将分支顶端移至一个之前的提交。这个命令不要求提交消息,如果没有使用–hard参数可能返回一个混杂的仓库。
Rebase(变基):允许你改变分支历史记录中提交的存放方式。通常用于将多个提交压缩成一个提交,来清理分支,并且保持与另一个分支处于最新状态。
Revert(还原):还原共享分支上一个特定提交中做出的变更。这个命令与提交搭配使用,返回一个干净的工作目录。