---
layout: post
title: 一次找回GitHub上被删除仓库的经历
tags: [GitHub, Git, 代码恢复, 软件存档]
---

  在GitHub中寻找踪迹也许是非常简单的事情……<!--more-->   

# 起因
  前段时间，有人和我聊天的时候提到了[Brainfuck](https://esolangs.org/wiki/Brainfuck)语言，让我回想起了高中时写的[演讲稿](/%E6%BC%94%E8%AE%B2%E7%A8%BF/2018/06/20/Coding.html)。那时候我在演讲时也介绍了Brainfuck语言。对于Brainfuck的解释器，[各种语言都可以实现](https://rosettacode.org/wiki/RCBF)，不过我当时为了方便理解用了一个在GitHub Pages上的网站，用可视化的方式演示了它的运行过程，效果很不错。现在既然聊到了，自然就想分享一下这个[演示的网站](https://fatiherikli.github.io/brainfuck-visualizer/)，但我正想打开时，发现网站已经404了😰。   
  在GitHub Pages上的网站都有对应的仓库，现在不仅原仓库消失了，连作者的[首页](https://github.com/fatiherikli)都打不开，看样子是完全退出GitHub了……那么我想找到这个网站的想法就无法实现了吗？不过GitHub有些有意思的特性也许能帮助我找回这个网站。   

# GitHub的特性
  在GitHub中，一个普通的仓库可能没有什么特别的，也许就是服务器上的一个文件夹。但是当仓库被其他人Fork的时候就不一样了，在执行Fork时，显然GitHub不会完整复制整个仓库。否则，同一个仓库在服务器上会占用双倍空间，这显然不合理。另外，想想Git的结构：它由提交对象和分支指针构成，每次提交都有唯一的Hash值且不会冲突。因此可以推测，GitHub在实现Fork时，所有被Fork的仓库可能共享同一个对象库，而每个用户仓库只保存指针，这样所有仓库只会占用增量空间，而不会存储重复内容。   
  但这样也会带来一个问题，首先因为很多人可能要共用一部分对象，所以也很难确认对象的所有权，而且也因为这个原因所有的对象要能被所有人访问。因此在整个Fork网络中，只要有一个仓库存在，GitHub就必须保留所有的对象，而且每个仓库都能访问这个网络中所有的对象。为了验证这一点，我们可以用最知名的[Linux内核仓库](https://github.com/torvalds/linux)做个示例。   
  首先对Linux仓库进行Fork，然后我们可以随便做一些改动，比如在README中写“Linux已经被我占领了😆”之类的内容，提交到自己的仓库，并且记下提交的Hash值，接下来就可以把自己的仓库删掉了。如果上面的猜想是正确的，那么在这个Fork网络中的任何一个仓库查看我刚刚的提交应该都可以，于是我直接在主仓库拼上了[提交的Hash值](https://github.com/torvalds/linux/tree/78e1d0446b94012da8639aa2b157d4f2dee481ce)（顺便一说只要值唯一，和其他的提交不冲突，[短的Hash值](https://github.com/torvalds/linux/tree/78e1d044)也可以），果不其然能找到刚刚修改的内容，这样一来，只要GitHub和任意一个Linux仓库的Fork还存在，这个提交就永远存在了😝。   

# 找回仓库
  那么接下来找回之前网站的方案就很简单了，我只要找到网站仓库的任意一个Fork，然后只要知道最新的提交Hash，我就可以还原最新的仓库了。Fork倒是好找，随便搜一下[就能找到一个](https://github.com/ashupk/brainfuck-visualizer)。这个Fork的最新提交是2016年，但要想找到我当年演讲的版本至少到2018年之后。不过这个Hash值也不太好找，虽然理论上爆破短Hash值也可以，但是感觉太麻烦了，没有那个必要，所以我干脆直接去互联网档案馆看看能找到的[最新的仓库页面](https://web.archive.org/web/20201229125043/https://github.com/fatiherikli/brainfuck-visualizer/)吧，这样我就能找到它的Hash值了，然后我再把Fork仓库的地址和Hash拼到一起，就看得到最新代码了。   
  当然，仅仅看到代码还不够。我想Fork这个项目并在自己的GitHub Pages上部署一份。有没有什么好办法可以将我仓库的HEAD指针指向最新的提交呢？其实很简单，首先我要Fork这个Fork仓库，然后Clone我的仓库到本地。不过，此时Clone下来的仓库并不包含GitHub上完整的对象库，因此直接checkout或reset是不行的。这时Hash值就派上用场了，通过fetch拉取对应提交后，就可以进行上述操作。具体命令如下：   
```bash
git fetch origin <commit-hash>
git reset --hard <commit-hash>
git push origin master
```
  最终我就获得了包含[最新代码](https://github.com/Mabbs/brainfuck-visualizer)的[Brainfuck可视化演示](https://mabbs.github.io/brainfuck-visualizer/)了🎉。   

# 结局
  后来我才知道，原来有一个专门的组织[Software Heritage](https://archive.softwareheritage.org)会保存所有代码，根本没必要搞这些花里胡哨的操作😂，像这个仓库也是能很轻易在[上面](https://archive.softwareheritage.org/browse/origin/directory/?origin_url=https://github.com/fatiherikli/brainfuck-visualizer)找到，这下以后知道了，再遇到类似情况就可以直接去Software Heritage查找，而不必在互联网档案馆上找线索瞎折腾了🤣。   