git撤销修改的方法

git撤消修改可以使用git stash、git checkout、git clean、git revert、git reset、git rm命令,每种撤消方法对应不同的撤销场景。

如果只是在提交代码时遇到更新问题,需要对修改做一些处理。有几个简单的思路供参考。如果不是这种场景,那可能就要读完整篇文章从中找答案了。

更新所引起撤销操作

git stash

git stash会暂存修改,把分支恢复到最近一次提交时的状态。如果使用stash就能解决的问题,应首选git stash。当问题变得复杂,不知如何处理时,最简单的做法是对修改做一个备份,然后,删除本地分支,重新从远程检出分支。

Git删除分支,首先切换到另一分支,然后执行删除本地分支的操作。

git branch -D 需要删除的分支

git stash 命令(本地/安全)

saving-changes-git-stash

git stash会将修改进行暂存。执行完git stash,本地git记录将回到最近一次push的时间点上,中间所有的修改都会被暂存起来。简单的说,如果想放弃最近的修改,在没有执行git push前,都可以用git stash进行撤消。

git stash命令简单安全,万一想反悔使用git stash pop把记录找回来。git stash功能相当于windows的回收站,git stash clear相当于清空回收站。详情参见Git-工具-储藏(Stashing)

Git撤销方案

从本地仓库到远程仓库,从未提交到已提交,从安全到不安全的操作,Git都提供了针对性的撤销方案。

Git撤销命令

git checkout、git clean、git revert、git reset、git rm

一个有意思的比喻:将Git视为时间线管理程序,提交的是项目时间轴上的时间点或兴趣点快照。并且,可以使用分支管理多个时间轴。Git执行“撤消”时,通常会回到过去,或者转移到另一个没有发生错误的时间线上。每个提交都有一个唯一的SHA-1哈希标识,哈希标识用于遍历时间线,对提交进行重新访问。

任何版本控制系统背后的思想,都是存储项目的“安全”副本,以避免被破坏后无法挽回。一旦创建了项目提交记录,可以通过命令就可查看提交。查看Git仓库提交记录命令:

git log

git log获取最新的提交记录列表。

git clean(本地/不安全)

git clean从某种程度上讲也是一个'撤消'命令,新增的文件在没有执行git add操作前,都可以使用git clean进行清理。git clean操作无法撤消。git clean会删除文件系统中的文件,类似于rm命令。执行此命令需谨慎。

git clean -n

-n选项通常用于“干运行”,-n选项会显示哪些文件将会被删除,但不会执行真正的删除操作。运行git clean命令的最佳做法,就是先使用-n参数运行一下。

git clean -f

因为Git的clean.requireForce选项默认为false,所以,直接运行git clean是不会成功的。这种安全机制防止误删除。加入-f选项强制执行清理命令。git clean -f 不会对目录进行递归。

git clean -i

-i选项是一种交互模式,用于确认操作和有选择的操作。

git clean -dn
git clean -df

-dn选项以“干运行”的方式演示将要删除的目录。-df选项则为直接删除目录。

git revert命令(远程/安全)

git revert命令被视为'undo'类型命令,但它不属于传统的撤消操作。

git revert不会从项目记录中删除提交,而是计算出反转更改,使用反转内容进行附加提交。git rever命令在版本库中创建一个“反向”提交,抵消原有的commit。这样能够防止Git记录丢失,确保变更记录的完整性,保证团队协作的可靠性。

saving-changes-git-revert

默认Git会立即提交反转请求,对于反转多个commit,可以使用-n参数,告诉git先不执行提交。运行git revert –n命令,git会暂存变更,最后一次性提交。

git revert HEAD~3

还原到倒数第四次的提交,并使用还原的更改创建一个新提交。

git revert -n master~5..master~2

将master倒数第五个提交,恢复到master倒数第三个提交。恢复仅用于修改本地工作树和索引,最好不要把还原做提交。

反转操作必须提供提交名称,反转总是按照从新到旧的顺序执行,后提交的最先反转,如此能够避免不必要的冲突。更多详情参考git-revert命令

git reset 命令(本地/不安全)

git reset是撤消修改的通用命令。git reset是几个撤消命令中最复杂的。git reset有三种模式,分别为--soft、--mixed、--hard。三个参数分别对应git三个内部状态:提交树(HEAD),分段索引和工作目录。

git rest与Git的三棵树

理解git reset用法,要先了解Git的内部状态管理。这些机制有时被称为Git的“三棵树”。树只是一个粗糙的比喻,它们不是传统意义上的树型数据结构。它是一种基于节点和指针的数据结构,Git用它跟踪编辑时间线。

第一棵树“工作目录”的一部分

对一个文件做出修改,在没有执行git add操作时,修改就会处于这棵树上。使用git status进行查看,将以红色字体显示。

Staging Index 树

对修改的文件执行git add操作后,修改就会处在这棵树上,使用git ls-files -s查看。这棵树是一个复杂的内部缓存机制。Git通常试图隐藏Staging index的实现细节。

Commit history

最后一棵树是提交历史。git commit命令会将修改添加到这棵树上,git log将显示“提交记录“。

git reset命令三种模式

git reset命令除了更新ref指针外,还会修改三棵树的状态。并且对第三棵树(Commit树)的更新一定会执行。

--soft、--mixed、--hard选项用于决定如何修改分段索引(Staging Index)和”工作目录“这两棵树。

git reset默认参数为--mixed HEAD。意味着执行git reset等同于执行git reset --mixed HEAD。

HEAD参数可用commit id(SHA-1 commit hash)替代。

git reset modes

上图是git reset模型中的三种作用域,分别对应--soft、--mixed、--hard选项。

git reset --hard

最直接最常用的选项,也是最危险的操作。

使用--hard选项时,”提交历史“的指针被更新为指定的提交(hard之后的参数所指定的记录)。将重置Staging Index和”工作目录“。Staging Index和"工作目录"中被挂起的修改都将被重置,这意味着在Staging Index和"工作目录"中被挂起的修改都将丢失。

使用--hard选项需谨慎,该选项会从版本库和工作目录树中同时删除提交,--hard就像操作delete命令一样,不可恢复。

git reset --mixed

git reset默认模式,ref指针会被更新。Staging Index会被重置为指定的提交。从Staging Index中撤消的修改都将移至"工作目录"。

git reset --soft

使用--soft参数,ref指针会被更新。 Staging Index和”工作目录“保持不变。但这种行为很难展示出来。

不要reset公共仓库的git记录

当<commit>之后的快照被推送到公共仓库时,在使用git reset <commit>时应当注意,或永远不应该使用它。

被push到公共仓库的提交,就应假设它已被其他开发人员所依赖了。删除其他团队成员的提交,会给团体协作带来严重问题。当他们尝试与远程仓库同步时,项目记录看起来像突然丢失了一大块。

git reset

把当前分支返回到之前的某一次提交上。对于已经push的修改,git stash是无能为力的,所以,使用git真正的撤销命令:

git reset --soft HEAD~1
--soft:保留撤消中的更改

$ git reset --hard HEAD~1
--hard 不保留更改

git reset以提交名称作为参数,默认值是HEAD。可以用^和~来指定版本。

HEAD^会把版本库恢复到HEAD之前的那个节点上,把HEAD这个版本的修改扔到工作目录树中,更多详情参考Git分支节点的概念

例如:54ac23b8~2表示复位到54ac23b8的前两个节点上,即把当前的提交和之前的一个提交一同放在到工作目录树中。

git reset --hard HEAD^ 强制回到前一个提交中。

git reset与git checkout区别

从表面上看,git reset行为与git checkout相似。但git checkout仅对HEAD ref指针进行操作,git reset会移动HEAD ref指针和当前分支的ref指针。

git checkout与git reset对比

git 分支

图中展示了master分支上的一系列提交,HEAD ref和master branch ref当前都指向了d提交。

git checkout

上图展示了执行git checkout b命令后,HEAD ref指向了b。master branch ref仍指向d。

git reset

上图展示了执行git reset b命令后,HEAD ref和master branch ref都指向指定的提交b。

git reset与revert的区别

Resetting vs. reverting

git revert是撤消单个提交的命令,它不会通过删除后续的提交,来让项目“恢复”到先前状态。

git reset则会对节点后续的提交进行删除。

git revert相比git reset的优势

首先,git revert不会修改已存在的历史记录,git revert成为公共仓库的“安全”操作。

其次,git revert能够针对历史记录中的任意点,而git reset只能从当前提交向后操作。

例如,如果要用git reset撤消旧的提交,则必须删除目标节点后发生的所有提交,然后重新提交后续操作。这不是一个优雅的撤消方案。

git rm

git rm命令可用于删除单个文件或文件集。git rm用于从Git仓库中删除文件。该命令是git众多撤消命令中的一个。git rm是一个运行在Git内部两个主要状态树上的命令:工作目录和staging index。

git rm功能是从Git索引中删除跟踪的文件。git rm能够从staging index和”工作目录“中删除文件。但git rm没有提供只删除”工作目录“中文件的选项。正在操作的文件必须与当前文件的HEAD相同。

如果文件的HEAD版本与staging index或工作树版本不一致,Git将阻止删除。这种操作是一种安全机制,可防止删除正在进行的更改。git rm不会删除分支。

git rm Documentation/\*.txt

使用通配符匹配文件名,删除Documentation及其子目录下的所有*.txt文件。

git diff --name-only --diff-filter=D -z | xargs -0 git rm --cached 

该命令将从工作目录中生成已删除文件的列表,并将该列表传输到git rm --cached。并将更新staging index。

git rm --cached --cached

选项表示清除缓存区,但不影响工作目录树。