回滚、还原、重置和变基

  • 关键词:Git For Teams – 读书笔记

Part2 在工作流中使用命令

回滚、还原、重置和变基

轻则微调一条提交,重则抹除历史记录。错误的提交和移除通常都发生在个人仓库,但你处理错误的方式将会影响到其他人与代码库的交互。

  • 学习目的:
    • 修补一份提交,并加入新的工作
    • 将一份文件恢复到之前的状态
    • 将你的工作目录恢复到上次提交时的状态
    • 还原之前作出的变更
    • 使用变基重塑提交历史记录
    • 从仓库中移除一份文件
    • 移除某个分支上因为错误的合并引入的提交

“你将会学习容易被忽略,但影响巨大的使用技巧”。

1 最佳实践

1.1 描述问题

先了解三个概念:工作目录(你的文件系统中可见的文件)、暂存区(在下一次commit后即将写入仓库的变更的索引)和仓库(存储文件并记录了文件的每次变更)。

image

git将信息存放在不同地方,当你将问题隔离在这些不同的地方时,将能够更好地选择正确的命令序列,将工作恢复到你想到的状态。

选择正确的撤销方法 备注 解决方案
舍弃工作目录中对一个文件的修改 修改的文件未被暂存或提交 checkout –filename
舍弃工作目录中所有未保存的变更 文件已暂存,但未被提交 reset –hard
合并与某个特定提交(但不含)之间的多个提交   reset –commit
移除所有未保存的变更,包含未跟踪的文件 修改的文件未被提交 clean -fd
移除所有已暂存的变更和在某个提交之前提交的工作,但不移除工作目录中的新文件   reset –hard commit
移除之前的工作,但完整保留提交历史记录(“前进式回滚”) 分支已经被发布,工作目录是干净的 revert commit
从分支历史记录中移除一个单独的提交 修改的文件已经被提交,工作目录是干净的,分支尚未进行发布 rebase –interactive commit
保留之前的工作,但与另一提交合并 选择squash(压缩)选项 rebase –interactive commit

比如:你希望舍弃工作目录中对一个文件的修改,错误的文件副本没有被暂存或提交

image

比如:创建一个列表,列出所有你想要从中恢复的问题场景。问题描述的越好,就越有可能找到正确的解决方案。

创建一个流程图来帮助你选择合适的命令

image

1.2 使用分支进行试验性的工作

在分支树上,一个分支与它的兄弟分支是相互独立的。尽管它们有共同的祖先分支,你通常可以看到在树上移除一个分支却不会影响到其他分支。

在Git中,你添加到仓库中的提交与一个或多个分支相关联。如果你签出一个不同的分支,并在这个新的分支上操作提交对象,那么系统会给这些提交对象分配一个新的标识符,而绑定到旧分支上的提交对象不会改变。也就是说,在新的私有分支中工作一定是安全的,当你对结果满意时,再将你的分支并入主分支。

在分支中工作帮助你免遭意外的修改,在工作正确且完整后再将它并入主分支

image

如果你在一个工单上进行工作,但不确定应该使用两种方法中的哪一种。这种情况下,你可以从你的工单分支上创建一个新的分支,在新的分支上进行试验性的修改,如果你想要保存这些修改,再将试验性的分支并入你的工单分支。

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(还原):还原共享分支上一个特定提交中做出的变更。这个命令与提交搭配使用,返回一个干净的工作目录。