技术记录栈

记录点滴:java、datebase、linux、spring、javascript、nginx

2018/11/19

GIT本地撤销修改的几种方法

git提供了一系列的取消本地修改的方法,根据撤销提交记录的作用域不同,对git撤销命令的选择和用法也大有不同。

从本地仓库到远程公共仓库,从未提交到已提交、从安全到不安全的操作,git都提供了针对性的撤销方法。如:git checkout、git clean、git revert、git reset、git rm命令。

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

任何版本控制系统背后的想法是存储项目的“安全”副本,这样就不必担心代码库被破坏后无法挽回。一旦创建了项目提交记录,就可以查看和访问历史记录中的任何提交。查看Git存储库历史记录的最佳实方式是git log命令。使用git log可以获取最新的提交列表。

本文将介绍如何利用git已提交的版本,对远程公共仓库的提交进行回滚,以及对本地计算机上的修改进行重置,并简单说明它们之间的区别。

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 clean

本地/不安全

git clean在某种程度上是一个'撤消'命令,它主要对没有进行git add操作的文件进行清理。git clean无法撤消。完全执行后,git clean将删除文件系统,类似于执行命令行rm命令。执行此命令需谨慎。

git clean -n
//用于演示执行

-n选项将执行git clean“干运行”。-n选项将显示哪些文件将被删除,而不是真的删除它们。所以运行 git clean命令时,应先执行“干运行”是git clean的最佳做法。

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丢失历史记录,这一点对于保证修订记录的完整性,和协作的可靠性非常重要。

saving-changes-git-revert

反转已经提交的内容使用git revert 命令,此命令会在版本库中创建一个“反向”的提交,来抵消原有的commit。Git会立即提交反转请求,可以使用-n参数,让git先不进行提交,这对于反转多个commit非常有用,多次运行git revert –n命令,Git会暂存所有的变更,最后做一次性提交。

git revert HEAD~3
还原到从最近一次提交数起,倒数第四次的提交,并使用还原的更改创建一个新提交。
git revert -n master~5..master~2
将从master倒数第五个提交,恢复到master倒数第三个提交,但不要使用还原做任何提交。恢复仅修改工作树和索引。

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

git reset 命令

本地/不安全

git reset命令是用于撤消更改的通用命令,也是几个撤消命令中最复杂的。它有三种主要的调用形式。对应的命令行参数分别为--soft、--mixed、--hard。这三个参数分别对应于git的三个内部状态管理机制,即提交树(HEAD),分段索引和工作目录。

git rest与Git的三棵树

要正确理解git reset用法,首先要了解Git的内部状态管理系统。这些机制有时被称为Git的“三棵树”。这里的树只是一个粗糙的比喻,因为它们不是传统意义上的树型数据结构。它们是git用于跟踪编辑时间线的,一种基于节点和指针的数据结构。演示这些机制的最佳方法是在存储库中创建变更集,并通过三棵树进行跟踪。 

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

对一个文件做了修改,但还没有执行git add操作,这个修改就处于这棵树上。可以用git status进行查看,它们将以红色字体显示,带有“ modified:”前缀。

Staging Index 树

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

Commit history

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

已经简要的说明了三棵树所跟踪的变化集,可以开始使用git reset命令了。从表面上看,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命令除了更新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选项。

--hard

这是最直接最常用的选项,但也是最危险的操作。当使用--hard选项时,”提交历史“的指针被更新为指定的提交(hard后面参数所指定的记录)。然后,重新设置Staging Index和”工作目录“。Staging Index和"工作目录"中任何被挂起的更改都将被重置,这意味着在Staging Index和"工作目录"中被挂起的修改都将丢失。

--mixed

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

--soft

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

不要reset公共仓库的历史记录
当<commit>之后的快照被推送到公共仓库时,在使用git reset <commit>时应当注意,或永远不应该使用它。因为,被push到公共仓库的提交,就应假设它已被其他开发人员所依赖了。删除其他团队成员的提交会给协作带来严重问题。当他们尝试与仓库同步时,项目历史看起来像突然丢失了一大块。

git reset

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

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

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

git rm

git rm命令可用于删除单个文件或文件集合。git rm主要功能是从Git索引中删除跟踪的文件。此外,git rm可能从staging index和”工作目录“中删除文件。没有只删除”工作目录“中文件的选项。正在操作的文件必须与当前文件的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是一个运行在Git 内部两个主要状态树上的命令:工作目录和staging index。git rm用于从Git仓库中删除文件。该命令是git众多撤消命令中的一个。

git reset与revert的区别

Resetting vs. reverting

重要的是要理解git revert是撤消单个提交 - 它不会通过删除后续的提交,来让项目“恢复”到先前状态。而git rest则会对节点后续的提交进行删除。git revert比git rest有两个重要优势。首先,它不会更改项目历史记录,这使得git revert成为共享存储库的“安全”操作。

其次,git revert能够针对在历史记录中的任意点,而git reset只能从当前提交向后操作。例如,如果要用git reset撤消旧的提交,则必须删除目标节点后发生的所有提交,然后重新提交后续操作。这不是一个优雅的撤消解决方案。

git reset概要说明

git reset是以提交名称作为参数的,默认值是HEAD,可以用^和~作为提交名称的修饰符来指定版本。HEAD^是指把版本库恢复到HEAD之前的那个节点上,把HEAD这个版本的修改扔到工作目录树中,更多详情参考Git分支节点的概念

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

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

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

git reset HEAD 清除已经add到缓存区但并没有进行commit的内容。

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

git reset命令可以复位版本库后,暂存工作目录树中因复位产生的版本库的差异。