Git撤销本地修改及远程仓库回滚

git reset命令是放弃本地修改的杀手锏,也有一些简单的丢弃本地修改的命令,先上几道常用的撤销命令,再细看其撤销原理。

git clean -n

查看将被清理的新增文件,但并不真正执行清理动作 ↑

git clean -f

清理新增的文件,没有执行git add的文件都将被清理 ↑

git reset

把git add操作的文件撤回到未执行前的状态,不带任何参数 ↑

git reset --soft HEAD~1

把最近一次git commit提交的内容,撤回到未执行前的状态 ↑

如果闲麻烦,可以使用git stash一次把所有未commit的修改,都扔进暂存区,确认永久删除时再执行git stash drop删除暂存区里内容。

$ git stash

$ git stash list

$ git stash drop stash@{0}

git stash操作就像把修改内容扔进回收站,在未做drop操作之前,随时都可以用git pop命令找回来。

git reset

git reset命令功能足够强大,所以,要想达到目的要先理解git resett三种模式,分别对应--soft、--mixed、--hard选项。

理解git三种模式,先理解下面这张图,示例中对文件做了三次修改和提交,分别使用git reset三种选项,对git本地仓库、git索引空间、本地工作空间的修改进行撤消。

git rest

git reset --hard

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

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

在这种特殊情况下,如果在本地对文件做了提交,Git 数据库中会留有文件的 v3 版本,可以通过 reflog 找回它。但是如果该文件还未提交,git reset --hard 覆盖操作将导致无法恢复。

git reset --hard误操作恢复

先使用 git reflog show命令查看操作日志。

git reflog show [dev|master|分支名]

git reflog show

接着使用git reset回到想回到的位置,如:  dev@{1}

 git reset --hard  dev@{0}

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

git reset --mixed

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

git reset --soft

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

git三棵树

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

git tree

工作目录(Working Directory)

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

Staging Index

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

Commit history

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

禁止reset公共仓库

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

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

git reset

git reset用法

把当前分支回退到之前的某一次提交上,使用git reset撤销命令:

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 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 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 stash

saving-changes-git-stash

git stash会将未提交的修改进行暂存。如果想放弃最近的修改,在没有执行git commit前,都可以用git stash进行撤消。详情参见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 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

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