git stash 的一次惊心动魄的误删操作
git stash 的一次惊心动魄的误删操作
简介:行走在互联网最低端的小熊
问题--源起:
小熊和所有混迹在互联网中的开发一样,公司里面用git来管理项目,由于可能经常有几个问题要开发,要频繁在多分支中切换,但是经常会遇到以下情况:
小熊当前正在分支A上干活,突然有一个紧急任务要去分支B上操作,但是由于分支A的活还没有做完,小熊又不想做一次无畏的提交,所以小熊就将分支A上修该的文件使用git stash save 'message' 保存起来,再切换到分支B去处理紧急任务
HINT:要切换分支,必去当前的分支没有改动的文件,否者可能需要处理冲突
刚开始,小熊使用git stash 命令用的不亦乐乎,由于以前都是用git commit 去提交之后在切换分支,之后且会有又取消这次的提交,操作麻烦;现在使用git stash完全可以满足小熊的需求,并且操作简单。
git stash 使用教程
git stash用于将当前工作区的修改暂存起来,就像堆栈一样,可以随时将某一次缓存的修改再重新应用到当前工作区。一旦用好了这个命令,会极大提高工作效率。
举例说明:
1、准备工作,首先初始化一个git仓
随便建立一个目录,进去,然后使用 :
$: git init .
添加一个文件:
$: touch hello
$: git add .
$: git commit -m "first add"
2、暂存当前修改内容(git stash)
假设我们在写一个C函数,如下:
void func1(void) {
printf("this is func1");
}
$:~/code/linux/git$ vim hello.c
$:~/code/linux/git$ git diff
diff --git a/hello.c b/hello.c
index e69de29..bdc92a5 100644
--- a/hello.c
+++ b/hello.c
@@ -0,0 +1,2 @@
+void func1(void) {printf("this is func1");}
+void main(void) {return func1();}
调试OK,发现func1功能OK,但是应该优化一下,可能效率更高,这个时候怎么办?
直接改func1的话,如果发现修改不合适,想回退的话很麻烦,这个时候可以用git stash将将修改暂存起来。
$: ~/code/linux/git$ git stash
Saved working directory and index state WIP on master: 452b08d rename hello as hello.c
HEAD is now at 452b08d rename hello as hello.c
$:~/code/linux/git$ git status
On branch master
nothing to commit, working directory clean
3、弹出修改内容(git stash pop)
这个时候你重新编写func1, 发现效果不好,后悔了,于是可以用git stash pop命令,弹出刚才的内容(注意先用git checkout . 清空工作区)
$:~/code/linux/git$ vim hello.c
$:~/code/linux/git$ git diff
diff --git a/hello.c b/hello.c
index e69de29..9c5bff3 100644
--- a/hello.c
+++ b/hello.c
@@ -0,0 +1 @@
+some bad chenges....
$:~/code/linux/git$ git checkout .
$:~/code/linux/git$ git stash pop
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: hello.c
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (208ca2e2c0c455da554986a6770a74ad0de5b1e0)
$:~/code/linux/git$ git diff
diff --git a/hello.c b/hello.c
index e69de29..bdc92a5 100644
--- a/hello.c
+++ b/hello.c
@@ -0,0 +1,2 @@
+void func1(void) {printf("this is func1");}
+void main(void) {return func1();}
注意,git stash pop 弹出成功后,暂存列表里面就没有了,如果当前工作区不干净,弹出时有冲突,则暂存列表会继续保留修改。
4、可以保存多个修改
假设你在实现一个功能,有好几种算法可以实现,你想逐个尝试看效果。
现在你在func1中实现了一种方法,准备尝试写func2,用另一种方法。
那么可以将func1的修改入栈,去写fun2,等fun2写好后,你又想试试func3,那么没关系,可以用同样的方法保存func2的修改:
$:~/code/linux/git$ git diff
diff --git a/hello.c b/hello.c
index e69de29..bdc92a5 100644
--- a/hello.c
+++ b/hello.c
@@ -0,0 +1,2 @@
+void func1(void) {printf("this is func1");}
+void main(void) {return func1();}
$:~/code/linux/git$ git stash
Saved working directory and index state WIP on master: 452b08d rename hello as hello.c
HEAD is now at 452b08d rename hello as hello.c
$:~/code/linux/git$ git status
On branch master
nothing to commit, working directory clean
$:~/code/linux/git$ vim hello.c
$:~/code/linux/git$ git diff
diff --git a/hello.c b/hello.c
index e69de29..7fd0a13 100644
--- a/hello.c
+++ b/hello.c
@@ -0,0 +1,2 @@
+void func2(void) {printf("this is func2");}
+void main(void) {return func2();}
$:~/code/linux/git$ git stash
Saved working directory and index state WIP on master: 452b08d rename hello as hello.c
HEAD is now at 452b08d rename hello as hello.c
$:~/code/linux/git$ git status
On branch master
nothing to commit, working directory clean
5、查看保存的内容列表(git stash list)
现在我们保存了两个修改,一个func1,一个func2,可以通过git stash list去查看保存内容列表:
$:~/code/linux/git$ git stash list
stash@{0}: WIP on master: 452b08d rename hello as hello.c
stash@{1}: WIP on master: 452b08d rename hello as hello.c
可以清楚的看到这两次修改,stash@{0}和stash@{1}, 那么哪个对应func1,哪个对应func2的修改呢?
这时我们需要使用git stash show stash@{X}命令来查看,其中‘X’表示列表号。
$:~/code/linux/git$ git show stash@{0}
commit 72e6a391bcad186ab24676aa1db8d5831c99cec9
Merge: 452b08d 6c95c30
Author: hiekay
Date: Sat Mar 12 19:56:18 2016 +0800
WIP on master: 452b08d rename hello as hello.c
diff --cc hello.c
index e69de29,e69de29..7fd0a13
--- a/hello.c
+++ b/hello.c
@@@ -1,0 -1,0 +1,2 @@@
++void func2(void) {printf("this is func2");}
++void main(void) {return func2();}
$:~/code/linux/git$ git show stash@{1}
commit 7fcca4b66640c51ca76e637df03264b7c41885be
Merge: 452b08d 1c37881
Author: hiekay
Date: Sat Mar 12 19:54:35 2016 +0800
WIP on master: 452b08d rename hello as hello.c
diff --cc hello.c
index e69de29,e69de29..bdc92a5
--- a/hello.c
+++ b/hello.c
@@@ -1,0 -1,0 +1,2 @@@
++void func1(void) {printf("this is func1");}
++void main(void) {return func1();}
发现stash@{0}对应func2的修改, stash@{1}对应func1的修改,原来新入栈的修改,其代号为0,循环命名。
6、应用任意一次修改到当前目录(git apply stash@{x})
如果现在又想回到func1的修改,怎么办呢?在工作区干净的情况下,要使用git stash apply stash@{1}。
注意这时不能使用git stash pop, 它将最栈顶,即stash@{0}的修改弹出来,而func1现在已经是stash@{1}了。
$:~/code/linux/git$ git stash apply stash@{1}
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: hello.c
no changes added to commit (use "git add" and/or "git commit -a")
$:~/code/linux/git$ git diff
diff --git a/hello.c b/hello.c
index e69de29..bdc92a5 100644
--- a/hello.c
+++ b/hello.c
@@ -0,0 +1,2 @@
+void func1(void) {printf("this is func1");}
+void main(void) {return func1();}
可见git stash apply可以将列表中任何一次修改应用到当前工作区,我们再次git stash list一把:
$:~/code/linux/git$ git stash list
stash@{0}: WIP on master: 452b08d rename hello as hello.c
stash@{1}: WIP on master: 452b08d rename hello as hello.c
我们发现,虽然func1的修改已经被弹出应用到当前工作区,其修改内容还继续保留在暂存列表,并未丢弃。
当然,我们可以使用git stash drop stash@{1}来丢掉stash@{1}
7、保存时打标记(git stash save)
假设现在我们又开始尝试写func3, 这样越来越多,这样列表会越来越大,你要想搞清楚某次修改对应哪个函数,就要挨个用git stash show看一遍,很麻烦。
那么,这个时候git stash 的save参数就有用了,它可以为这次暂存做个标记,使得你用git stash list的时候显示这些标记,方便你回忆是修改的什么:
$:~/code/linux/git$ vim hello.c
$:~/code/linux/git$ git diff
diff --git a/hello.c b/hello.c
index e69de29..786c214 100644
--- a/hello.c
+++ b/hello.c
@@ -0,0 +1,2 @@
+void func3(void) {printf("this is func3");}
+void main(void) {return func3();}
$:~/code/linux/git$ git stash save "this is func3"
Saved working directory and index state On master: this is func3
HEAD is now at 452b08d rename hello as hello.c
$:~/code/linux/git$ git stash list
stash@{0}: On master: this is func3
stash@{1}: WIP on master: 452b08d rename hello as hello.c
stash@{2}: WIP on master: 452b08d rename hello as hello.c
我们在save后面指定一个字符串,作为提醒,这样在git stash list查看时就能知道每一个代号对应的修改了。
可是由于一次的误操作事件,让小熊瞬间陷入了恐慌;那是一个夜黑风高的夜晚,小熊刚刚改完这几天的bug,还没commit,只是用stash 保存起来了,但是由于小熊的一个手滑操作,本打算删除以前使用stash保存的废弃记录,可是由于手滑的失误让小熊这几天修改的BUG,被删除了;突然小熊一下子就懵了,还好小熊深呼吸一口,沉着稳定的去查找是否有,回退的操作。
问题--修复
小熊,本着忐忑的心情,再百度输入了
git stash 误删
想要找到相关的回退操作文档,果然皇天不负有心人,一篇精彩绝伦的回退操作文档,完美的解决了,小熊这次的手贱操作。
1. 使用git fsck --unreachable命令查找所有unreachable的记录
这条命令会打印出所有不能从任何索引节点访问但是确存在的对象,回车之后我们会看到如下的显示:
如图中所示,大概有三种类型的内容,blob
、tree
和commit
。我们这样看的话是看不出任何有用信息的,我们需要另外一条命令将其内容show出来。
记录太多,还好小熊把当时误删除的stash id 记录下来了,可以用删除的stash id直接定位到,那条记录,可以看到是一个commit 类型的记录。
2. 使用git show命令显示记录内容
Shows one or more objects (blobs, trees, tags and commits).
git show
后面跟上要show的id,就可以展示这个id所对应的blob
,tree
,tag
和commit
。比如:git show f19aa7d5a056b4f1fe9f30ec86137431d063db57
。比较诡异的是,这些id所对应的记录并不是有序的,如果想要找到之前误删的内容,需要我们一条条的去show出这些内容,然后判断是否是要找回的。万幸的是有两点如果能善加利用的话,会起到事半功倍的效果。一是我们只需要关心commit
类型的内容,这些才是我们保存过或者提交过的内容;二是show出来的内容是有日期的,我们只要能大概记住误删内容的保存或提交日期,那么找起来自然轻松很多。
3. 使用git stash apply命令恢复记录内容
根据第二步找到我们所要恢复内容的id,使用git stash apply
就可以完美治愈本次的手残,命令执行完后会发现,之前的stash内容又在工作区出现了,是不是很有趣呢,git果然是无比强大!这个地方需要注意的是git stash apply
只能恢复commit
类型的记录,如果使用这条命令来恢复blob
,tree
或tag
可能会报错。
在使用
git fsck –unreachable
命令输出的很多文件里面,有很多是带有commit
和tree
的标识的,这些可以使用git stash apply
加标记号进行找回。而blob
的文件是只能手动拷贝的,或者使用>
输出到指定的路径去 [ 例如git show 302063e31742cbce7c5fdb917edf520183154cc1 > D:\recovery\backup.txt
]
小记
git使用了这么久,其实还是有很多东西没能深入了解,比如blob
,tree
,tag
和commit
都代表了什么含义,各有什么作用,查找了一些资料,学习记录一下。
每个object包含三个部分:类型,大小和内容。大小就是指内容的大小,内容取决于对象的类型,有四种类型的对象,也就是上面提到的blob
,tree
,tag
和commit
。
blob
用来存储文件数据,通常是一个文件tree
有点像一个目录,它管理一些tree
或blob
,就像文件和子目录tag
标签,用来标记某一次的commit
commit
只指向一个tree
,它用来标记项目某一个特定时间点的状态。它包括一些关于时间点的元数据,比如时间戳、最近一次提交的作者,指向上次提交的指针等等。
git stash 常用命令
git stash save "message"
git stash list
git stash drop <stash@{num}>
git stash apply <stash@{num}>