主要内容:【Git 重置】、【Git 检出】、【恢复进度】
Git 重置
分支游标 master 的探秘
git log --graph --oneline |
引用 refs/heads/master
就好像是一个游标,在有新的提交发生的时候指向了新的提交。
Git 提供了 git reset
命令,可以将“游标”指向任意一个存在的提交 ID。注意下面的命令中使用了 --hard
参数,会破坏工作区未提交的改动,慎用。
git reset --hard HEAD^ |
用 reflog 挽救错误的重置
通过 .git/logs
目录下日志文件记录了分支的变更。默认非裸版本库(带有工作区)都提供分支日志功能,这是因为带有工作区的版本库都有如下设置:
git config core.logallrefupdates |
查看一下 master
分支的日志文件 .git/logs/refs/heads/master
中的内容。
tail -5 .git/logs/refs/heads/master |
Git 提供了一个 git reflog
命令,对这个文件进行操作。
git reflog show master | head -5 |
重置 master 为两次改变之前的值。
git reset --hard master@{2} |
深入了解 git reset 命令
git reset [-q] [<commit>] [--] <paths>... |
为了避免路径和引用(或者提交 ID)同名而冲突,可以在 <paths>
前用两个连续的短线(减号)作为分隔。
--hard
会执行上图中的 1、2、3 全部的三个动作。
- 替换引用的指向。引用指向新的提交 ID。
- 替换暂存区。替换后,暂存区的内容和引用指向的目录树一致。
- 替换工作区。替换后,工作区的内容变得和暂存区一致,也和 HEAD 所指向的目录树内容相同。
--soft
会执行上图中的操作 1。--mixed
(缺省)会执行上图中的操作 1 和操作 2。
git reset |
Git 检出
重置命令 git reset
的一个用途就是修改引用(如 master)的游标。如果 HEAD 要改变该如何改变呢?检出命令 git checkout
该命令的实质就是修改 HEAD 本身的指向,该命令不会影响分支“游标”(如 master)。
HEAD 的重置即检出
git co HEAD^ |
什么叫做 detached HEAD
“分离头指针”状态?查看一下此时 HEAD 的内容就明白了。
cat .git/HEAD |
原来“分离头指针”状态指的就是 HEAD 头指针指向了一个具体的提交 ID,而不是一个引用(分支)。注意上面的 reflog
是 HEAD
头指针的变迁记录,而非 master
分支。
查看一下 HEAD 和 master 对应的提交 ID,会发现现在它们指向的不一样。
git rev-parse HEAD master |
在“分离头指针”模式仍然可以进行提交:
git status |
但是在 checkout 到其他分支时,刚才的提交会丢失,但是这个提交仍然在版本库中,由于这个提交没有被任何分支跟踪到,因此并不能保证这个提交会永久存在。
实际上当 reflog 中含有该提交的日志过期后,这个提交随时都会从版本库中彻底清除。
挽救分离头指针
在“分离头指针”模式下进行的测试提交除了使用提交 ID acc2f69
访问之外,不能通过 master 分支或其他引用访问到。使用合并操作 git merge
将提交 acc2f69 合并到 master 分支中来。
git merge acc2f69 |
恢复进度
继续暂存区未完成的实践
# 保存当前工作进度 |
git stash [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] |
--patch
会显示工作区和 HEAD 的差异,通过对差异文件的编辑决定在进度中最终要保存的工作区的内容,通过编辑差异文件可以在进度中排除无关内容。- 使用
-k
或者--keep-index
参数,在保存进度后不会将暂存区重置。
# 不删除恢复的进度之外,其余和 git stash pop 命令一样 |
探秘 git stash
在执行 git stash
命令时,Git 实际调用了一个脚本文件实现相关的功能。
git --exec-path |
本地没有被版本控制系统跟踪的文件并不能保存进度。因此本地新文件需要执行添加 add
再执行 git stash 命令。
在用 git stash
命令保存进度时,提供说明更容易找到对应的进度文件。
每个进度的标识都是 stash@{<n>}
格式,像极了前面介绍的 reflog
的格式。git stash
的就是用到了前面介绍的引用和引用变更日志 reflog
来实现的。
用 git stash 保存进度,实际上会将进度保存在引用 refs/stash 所指向的提交中。多次的进度保存,实际上相当于引用 refs/stash 一次又一次的变化,而 refs/stash 引用的变化由 reflog(即.git/logs/refs/stash)所记录下来。
如何在引用 refs/stash 中同时保存暂存区的进度和工作区中的进度
git log --graph --pretty=raw refs/stash -2 |
最新的提交说明中有 WIP
(Work In Progess)字样,说明代表了工作区进度。而最新提交的第二个父提交(上图中显示为第二个提交)有 index on master 字样,说明这个提交代表着暂存区的进度。