search
LoginSignup
57

More than 1 year has passed since last update.

posted at

updated at

Organization

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

警告

今回紹介している内容はGitHubの公式からは「やるなよ!!」と言われている内容を紹介しています
私のディスク容量はいくつですか?
これを理解した上で以降を読み進めてください

問い

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

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

実際にやってみた結果

console.jpg
finder.jpg

約1254652ファイル約164.6GB のリポジトリ全てGitHubにpushすることに成功しました!!
GitHubすごい!!

finder.jpg
リポジトリはこのような感じになっておりました。
(あまり広めたくないのでprivateにしています)

ダウンロードできるの?

上記のリポジトリをGitHub上のzipにしてダウンロードすることは怖かったのでやっていません。
git clone を用いたダウンロードはできたので、ダウンロードするときは git clone を用いてダウンロードしてください

きっかけ

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

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

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

そしてこちらの記事

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

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

  • 1ファイル100MB 以下にする → 1ファイル100MB以上のファイルをpushしたときはエラーになってpushができないため
    • 1ファイル100MB 以上のファイルをpushする場合は Git LFS を用いて git push する(ただし、Git LFS は容量次第で有料になる)
  • 1回の git push2GB を超えるを超える場合エラーになってpushできない(非公式な仕様)

結論

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

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

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

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

*.DS_Store
*~
Thumbs.db
node_modules/

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

// 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 をインストールします。
(package.json を作るほどのものでもないと思うのでこちらは省略)

npm install simple-git

そして、その後

node roop-commit-and-push.js

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

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

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

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

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

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

今回「もしかしてできるのではないか?」と思い至ったので実際に検証してみました。
しかし公式からは
私のディスク容量はいくつですか?
にあるようにやらないでくださいと明確に指摘されています。

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
57