GitHub Hacking ~GitHubを容量無制限のクラウドストレージとして使用する試み~

# 警告

<font color="#FF000">今回紹介している内容は[GitHub](https://github.com/)の公式からは「やるなよ!!」と言われている内容を紹介しています
[私のディスク容量はいくつですか？](https://docs.github.com/ja/github/managing-large-files/what-is-my-disk-quota)
これを理解した上で以降を読み進めてください</font>

# 問い

```
GitHubを容量無制限のクラウドストレージとして使用できるのか?
```

[GitHub](https://github.com/) には `git push` した場合には容量制限があり、`1ファイル100MB` を超える場合は [Git LFS](https://git-lfs.github.com/) を使って `git push` を行わないとエラーが発生してしまい`git push` することができません。
この仕様については共通の認識としてよく出てきますがリポジトリの総計の容量の上限については言及されていません。そのため、実際にやってみてどこまでできるのか試してみたいと思います。

# 実際にやってみた結果

![console.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/8aaee338-eeee-0faf-8552-95786d4fbf53.jpeg)
![finder.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/ed327a2b-6a29-d818-6383-6198a2186923.jpeg)

`約1254652ファイル`、`約164.6GB` のリポジトリ全て[GitHub](https://github.com/)にpushすることに成功しました!!
[GitHub](https://github.com/)すごい!!

![finder.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/4afe22af-1718-523b-01f5-336b5bdf9c31.jpeg)
リポジトリはこのような感じになっておりました。
(あまり広めたくないのでprivateにしています)

### ダウンロードできるの?

上記のリポジトリを[GitHub](https://github.com/)上のzipにしてダウンロードすることは怖かったのでやっていません。
`git clone` を用いたダウンロードはできたので、ダウンロードするときは `git clone` を用いてダウンロードしてください

# きっかけ

去年(2019年)のアドベントカレンダーにてプチ炎上しましたこちらの記事
* [ハッカソンの開催情報を自動でお知らせするBotをGithub Actionsに移行して運用費が0円になりました](https://qiita.com/taptappun/items/51afcbbd00f1514bc895)
この内容を実践していた時に

```
1ファイル100MB以下にする仕様を満たしていれば、容量の際限[GitHub](https://github.com/)にアップロードできるのではないか?
```

という疑問を抱いたため実験も兼ねて実践してみようと思いました。
(ちなみにプチ炎上した記事のその後の展開は後日記事にします。現在、この記事に書かれていることは[GitHub](https://github.com/)では行なっておらず、Gitlabにて行なっております)

そしてこちらの記事

 * [画像クローラー](https://qiita.com/taptappun/items/051e7d9d57770c7cb718)

記事を作成し、運営していた時に収集した画像ファイルが `AWS S3` に保管していて管理費用がかかっていたので、費用削減も兼ねてできたらいいなという希望も込めて検証しました。

# 実践してみて判明したGitHubの仕様

* `1ファイル100MB` 以下にする → 1ファイル100MB以上のファイルをpushしたときはエラーになってpushができないため
  * `1ファイル100MB` 以上のファイルをpushする場合は [Git LFS](https://git-lfs.github.com/) を用いて `git push` する(ただし、[Git LFS](https://git-lfs.github.com/) は容量次第で有料になる)
* 1回の `git push` で `2GB` を超えるを超える場合エラーになってpushできない(非公式な仕様)

# 結論

* 1ファイル100MB以下
* 1回のpush(1回のcommit)が `2GB` 以下

上記の条件を満たしていれば[GitHub](https://github.com/)の物理的なサーバーストレージの範囲内で無制限のサーバーストレージとして利用できることがわかりました

# めんどくさかったのでスクリプトを作った

上記の検証を行なっている時に最初は1回ずつ手作業で `git commit` と `git push` を行なって行きましたが、途方のない作業でしたのでスクリプトを作成して、それを実行するようにしました。
今回はNode.jsで簡単に実行できるスクリプトを作成して実行するようにしました。
`.gitignore` の設定は以下のようになります。

```.gitignore
*.DS_Store
*~
Thumbs.db
node_modules/
```

スクリプトの中身は以下のようになります。

```javascript
// roop-commit-and-push.js

const fs = require('fs');
const { promisify } = require( 'util' )
const simpleGit = require('simple-git');
const git = simpleGit();

async function executeGitStatus(){
  return git.status();
}

const limitFileSize = 1900000000;

const eachSlice = (arr, n = 2) => {
  const dup = [...arr]
  const result = [];
  let length = dup.length;

  while (0 < length) {
    result.push(dup.splice(0, n));
    length = dup.length
  }

  return result;
};


async function executeCommitAndPushRoutine(){
  let statusResult = await executeGitStatus();
  let remainFileCount = statusResult.created.length + statusResult.not_added.length;
  while(remainFileCount > 0){
    let sumSize = 0;
    // ダブっているファイルがあるためSetにして除去する
    const addFileSet = new Set();
    const createdFilePromises = [];
    // すでに git add されているファイルの容量を計算して、残りまだgit addできるもののみを全てgit addする
    for(const notAddFile of statusResult.created){
      const statPromise = promisify(fs.stat)(notAddFile).then(stat => {
        sumSize = sumSize + stat.size;
      });
      createdFilePromises.push(statPromise);
    }
    await Promise.all(createdFilePromises);
    console.log(sumSize);
    if(sumSize <= limitFileSize) {
      // 速度優先のため非同期でgit addするファイルの選別をファイルサイズを取得した上で行う
      const addFilePromises = [];
      for (const notAddFile of statusResult.not_added) {
        let isStop = false;
        const statPromise = promisify(fs.stat)(notAddFile).then(stat => {
          if(isStop){
            return sumSize;
          }
          if (sumSize + stat.size > limitFileSize) {
            isStop = true;
            return sumSize;
          }
          sumSize = sumSize + stat.size;
          addFileSet.add(notAddFile.toString());
          return sumSize;
        });
        addFilePromises.push(statPromise);
        if (isStop) {
          break;
        }
      }
      await Promise.all(addFilePromises);
    }
    console.log(sumSize);
    const total = addFileSet.size;
    remainFileCount = remainFileCount - total;
    console.log("add files:" + total.toString());
    // git addできるファイル数の上限が約2000。これ以上のファイル数をgit addするとエラーになるので分割する
    for(const files of eachSlice(Array.from(addFileSet), 2000)){
      await git.add(files).catch(err => {
        console.error(err);
      });
    }
    console.log("add file completed:" + total.toString());
    const nowDate = new Date();
    const dateString = [nowDate.getFullYear(), nowDate.getMonth(), nowDate.getDay()].join("/");
    const timeString = [nowDate.getHours(), nowDate.getMinutes(), nowDate.getSeconds()].join(":");
    const commitMessage = [dateString, timeString, total, "image files add"].join(" ");
    console.log(commitMessage);
    await git.commit(commitMessage);
    console.log("committed " + total.toString() + " files");
    await git.push().catch(err => {
      console.error(err);
    });
    console.log("pushed and remained " + remainFileCount.toString() + " files");
    // git statusで取得できるファイル数には上限があるので、現状で取得できたファイルが無くなったら再度git statusを行なって補充できるか確認する
    if(remainFileCount <= 0) {
      statusResult = await executeGitStatus();
      remainFileCount = statusResult.created.length + statusResult.not_added.length;
    }
  }
}

executeCommitAndPushRoutine();
```

上記スクリプトを実行するためにまずは [simple-git](https://www.npmjs.com/package/simple-git) をインストールします。
(`package.json` を作るほどのものでもないと思うのでこちらは省略)

```sh
npm install simple-git
```

そして、その後

```sh
node roop-commit-and-push.js
```

として上記のスクリプトを実行すると、`git add` する必要があるファイルがなくなるまで[GitHub](https://github.com/)に`git push` を行なってくれます。
(もちろん事前に `git init` `git remote add origin url` などの基本的な初期設定を事前に行なっている必要があります。)

### パッケージ化した方が良さそう?

Node.jsのインストールなどの初期設定が面倒な場合は上記スクリプトをパッケージ化して実行してもいいかもしれません。
以下参考
[nodeアプリケーションを実行可能ファイルにして出力する](https://qiita.com/hana_asia/items/ed8876962e3d1035c0a6)


# 闇の魔術に対する防衛術?

ここまで読んでいただいたらお分かりかと思いますが、防衛者側は [GitHub](https://github.com/) になります。私も`Github Support` からゴルァされたら大人しく[Gitlab](https://about.gitlab.com/)に移行するなど別の方法を模索するようにします...

# 絶対にやってはダメですよ!!!

今回「もしかしてできるのではないか?」と思い至ったので実際に検証してみました。
しかし公式からは
[私のディスク容量はいくつですか？](https://docs.github.com/ja/github/managing-large-files/what-is-my-disk-quota)
にあるようにやらないでくださいと明確に指摘されています。

これをみた皆さんは絶対にマネしてはダメですよ!!!
もし同じことを真似しようと思った方はくれぐれも自己責任でやるようにしてください。

なお
2020/12/06 現在、規約違反ではないのでペナルティはないと思われます。