Git撤销操作详解

Git放弃本地修改时需分清文件状态,撤销本地修改分三种,远程仓库版本回退操作分两种。

撤销本地修改

本地撤销根据文件状态分为:新增文件的丢弃、已有文件变更的还原。

清理新增文件

git clean -n

-n参数执行空运行,不执行真正清理操作 ↑

git clean -f

清理新增文件,未执行git add前可用上述命令清理 ↑

注:若有新增目录需清理,加上-d参数。删除git已忽略的文件加上-X参数,如:

git clean -df

还原已有文件

对已有文件的修改执行还原(未执行git add):

git checkout -- <file>

如:

git checkout -- ./com/putdns/bb.txt

注:文件路径可通过git status获得。

对已执行git add操作的文件,需先对文件的状态执行还原:

git reset ./com/putdns/aa.txt

然后,对内容执行还原:

git checkout -- ./com/putdns/aa.txt

注:checkout为不可恢复的替换操作,如有需要可用stash替代:

$ git stash

git stash对已修改的内容执行暂存,将文件恢复到未修改前的状态。

对已执行git commit操作的文件执行内容还原:

git reset --soft HEAD~1

注:reset命令基于最近一次提交,多次执行会回到更早版本,可能会超出预期。

撤销合并请求

远程仓库合并代码后,会出现Revert按钮。

如需将代码还到原到合并前,点击Revert。

Git计算已合并的变更记录,生成一个反向提交,实现还原。

撤销提交请求

从提交详细信息页面还原提交。

与还原合并请求类似,可选择将更改直接还原到目标分支中,也可选择创建新的合并请求达到还原效果。

Git reset

Git reset操作相对复杂,分为:--soft、--mixed、--hard。

代表三种文件状态和模式。如下图所示:

git rest

示例中对文件做了三次修改和提交,分别使用git reset三个选项,对本地仓库、索引空间、本地工作空间的修改执行撤消。

git reset --hard

常用选项,较危险的操作。

--hard选项。

将重置Staging Index和“工作目录”。Staging Index和“工作目录”中被挂起的修改都将被重置,在Staging Index和“工作目录”中被挂起的修改都将丢失。

被commit操作后的文件,在git数据库中留有文件的v3版本。

reset --hard操作后,依然可通过reflog找回。

若未对文件执行commit,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操作不可恢复。“提交历史”的指针被更新为指定的提交(hard之后参数所指定的记录ID)。

git reset --mixed

git reset默认模式,Staging Index会被重置为指定提交。

从Staging Index中撤消的修改都将移至"工作目录"。

git reset --soft

--soft,Staging Index和“工作目录”保持不变。这种行为很难展现出来。

Git三棵树

理解git reset用法,需了解git内部状态管理,称为git“三棵树”,它不是传统意义上的树型数据结构。是一种基于节点和指针的数据结构,git用它跟踪编辑时间线。

git tree

工作目录(Working Directory)

未执行git add前,文件的修改位于此树上。

git status以红色字体显示。

git status

Staging Index

执行git add后,文件的修改位于此树上。

git ls-files -s可查看。

git ls-files -s

注:此树是一个复杂的缓存机制,git会试图隐藏Staging index实现细节。

Commit history

git commit会将修改添加到此树上。

git log可查看记录。

git log

禁止reset公共仓库

commit后的快照被推送到公共仓库后,应避免使用git reset <commit>操作。

应假设修改已被其他开发人员所依赖。

使用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 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 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 log

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

git clean

未执行git add操作前,清理新增文件。

git clean操作不能撤消。git clean会删除文件系统中的文件,类似于rm命令。

git clean -n

-n选项通常用于“干运行”,-n选项会显示哪些文件将被删除,但不执行真正的删除操作。

运行git clean命令最佳做法,是先使用-n参数运行一下。

git clean -f

直接运行git clean不会成功。

这种安全机制防止误删除。

加入-f选项强制执行清理命令。

git clean -f 不会对目录执行递归。

git clean -i

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

git clean -dn
git clean -df

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

-df选项则为直接删除目录。

git rm

git rm命令可用于删除单个文件或文件列表,是一个运行在两个主要状态树上的命令:

工作目录和staging index。

git rm未提供只删除“工作目录”内容选项。

若文件的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

清除缓存区,不影响工作目录树↑。