上个周末遇到了一个这样的场景
场景
自己写了大半天的一个小东西的代码,目录结构大概如下
node_modules
src
- ...files
test
- test.js
package.json
睡前本来准备上传到github仓库
git init
git add -A
发现忘记添加.gitignore
,把node_modules
文件都add进去了
于是手贱输入了git reset --hard
然后发现...目录里的东西全部没了(只剩下.git/
文件架),
当时我的内心
挽救
心急如焚懊悔不已的我,经过查阅相关资料,还是找到了一些拯救代码的方法
由于每次git命令进行操作时git都会对相关文件进行快照,并通过一定形式把信息保存再.git/
目录下。
由于此前我使用过git add -A
命令,因此当文件被放进暂存区时,快照信息对象就已经保存了,而实用git reset --hard
之后,这些对象就变成了悬空文件对象(dangling blob
)。
我们可以使用git fsck
命令显示他们
git fsck:用于验证当前git仓库数据的有效性和一致性,能够显示那些"丢失"的commit
、blob
(文件)、tree
等。
我们可以通过以下命令git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)")
我们得到一大堆blob
的hash ID
unreachable blob 907b308167f0880fb2a5c0e1614bb0c7620f9dc3
unreachable blob 72663d3adcf67548b9e0f0b2eeef62bce3d53e03
...
接下来使用git show
就能显示这些对象的内容了,例如git show 907b308
自动还原
但是由于我曾经添加的文件实在太多node_modules
里的文件可能有上千个,因此对逐个ID进行git show
肉眼筛选是非常不科学。
因此我写了个简单的nodejs脚本(因为我比较熟悉),筛选还原那些我需要的文件。
首先使用git fsck
把hash ID都存到一个文件里git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") > allhashes
"use strict";
const fs = require("fs");
const shelljs = require("shelljs");
const through = require("through2");
let buf = fs.readFileSync("./allhashes")
buf = buf.toString();
let hashes = []
buf.replace(/dangling blob (\w+)/gi,function (matached, hash) {
hashes.push(hash)
});
let all = hashes.length;
let left = all;
hashes.forEach(hash=>{
let fullContent = ""
let stdout = shelljs.exec("git show "+hash,{silent:true}).stdout;
let input = through();
console.log((left--)+"/"+all);
//TODO:through2原来是为了处理stdout流的异步数据引入的,当前同步过程下不需要
input.pipe(through((buf,_,next)=>{
fullContent = fullContent+buf.toString();
next(null,buf)
},flush=>{
if (matchContent(fullContent)){
fs.writeFile("./objects/"+hash,fullContent)
}
flush()
}))
input.push(stdout);
input.push(null);
})
function matchContent(content){
// ... 匹配规则
}
于是经过几分钟的执行,我找回了我的代码
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。