• 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的记录

    这条命令会打印出所有不能从任何索引节点访问但是确存在的对象,回车之后我们会看到如下的显示:

    在这里插入图片描述

    如图中所示,大概有三种类型的内容,blobtreecommit。我们这样看的话是看不出任何有用信息的,我们需要另外一条命令将其内容show出来。

    记录太多,还好小熊把当时误删除的stash id 记录下来了,可以用删除的stash id直接定位到,那条记录,可以看到是一个commit 类型的记录。

    在这里插入图片描述

    2. 使用git show命令显示记录内容

    Shows one or more objects (blobs, trees, tags and commits).

    git show后面跟上要show的id,就可以展示这个id所对应的blobtreetagcommit。比如:git show f19aa7d5a056b4f1fe9f30ec86137431d063db57。比较诡异的是,这些id所对应的记录并不是有序的,如果想要找到之前误删的内容,需要我们一条条的去show出这些内容,然后判断是否是要找回的。万幸的是有两点如果能善加利用的话,会起到事半功倍的效果。一是我们只需要关心commit类型的内容,这些才是我们保存过或者提交过的内容;二是show出来的内容是有日期的,我们只要能大概记住误删内容的保存或提交日期,那么找起来自然轻松很多。

    在这里插入图片描述

    3. 使用git stash apply命令恢复记录内容

    根据第二步找到我们所要恢复内容的id,使用git stash apply就可以完美治愈本次的手残,命令执行完后会发现,之前的stash内容又在工作区出现了,是不是很有趣呢,git果然是无比强大!这个地方需要注意的是git stash apply只能恢复commit类型的记录,如果使用这条命令来恢复blobtreetag可能会报错。

    在使用 git fsck –unreachable 命令输出的很多文件里面,有很多是带有 committree 的标识的,这些可以使用 git stash apply 加标记号进行找回。而 blob 的文件是只能手动拷贝的,或者使用> 输出到指定的路径去 [ 例如git show 302063e31742cbce7c5fdb917edf520183154cc1 > D: ecoveryackup.txt]

    小记

    git使用了这么久,其实还是有很多东西没能深入了解,比如blobtreetagcommit都代表了什么含义,各有什么作用,查找了一些资料,学习记录一下。

    每个object包含三个部分:类型,大小和内容。大小就是指内容的大小,内容取决于对象的类型,有四种类型的对象,也就是上面提到的blobtreetagcommit

    • blob 用来存储文件数据,通常是一个文件
    • tree 有点像一个目录,它管理一些treeblob,就像文件和子目录
    • tag 标签,用来标记某一次的commit
    • commit 只指向一个tree,它用来标记项目某一个特定时间点的状态。它包括一些关于时间点的元数据,比如时间戳、最近一次提交的作者,指向上次提交的指针等等。

    git stash 常用命令

    • git stash save "message"
    • git stash list
    • git stash drop <stash@{num}>
    • git stash apply <stash@{num}>

    参考链接:

  • 相关阅读:
    minimsg升级扩展
    一起学习Avalonia(十三)
    @Import注解源码
    Python入门随记(3)
    NET WebApi 后端重定向指定链接
    Net Nlog 持久化到数据库
    NetCore Xunit单元测试依赖注入
    VS 调试时,提示无法启动iis服务器
    NET 反射,对可空类型动态赋值
    MSSQL 查询表结构
  • 原文地址:https://www.cnblogs.com/dream-it-possible/p/13213453.html
Copyright © 2020-2023  润新知