おもってGemを作った。ゆるいタスク管理システムが必要だった通常、仕事のタスク管理はJIRAとかRedmineとかGithubとかTorelloとかなんかそういう専用のやつを使うと思います。いえ、「もう今日は帰ってるけど、明日こののプルリク見てください」とかSlackで伝えたり、「このプルリクレビュー通ってるんで、明日マージしといて」とかSlackで伝えたり、その程度のことをチケットにするのも妙な感じです。デイリーミーティングや口頭やSlackで伝えれば良い気もしますが、まあそういうのって大抵忘れます。そもそも言っても忘れ去られるし。Slackのリマインダも使いづらい。いう、なんか忘れるけど伝えときたいことを忘れないようにしたいなと思ったら、とりあえずbotを作ります。botを作ることの理由を問われても、まあなんとなくとしか言いようが無いですが、Slack上のbotだったらまあ大体みんな見てるだろうという程度の理由です。botじゃんと思いましたが、肝心なのはキューの操作履歴の参照です。キューの操作履歴、例えば新しいタスクをキューに追加したとか、先頭のタスクを完了したとか、n番目とm番目のタスクを入れ替えたとか、そういう類の操作履歴を残そうと思うと、これはなかなか厄介な気がしました。方法がパッと思いつくだけで5個くらいあり、そのどれもが概ね「DBかファイルに操作履歴を残す」という方法です。DBを使うのは大掛かりで嫌だし、ファイルに書き出すのはロックの問題や破損の問題に立ち向かうのが億劫です。何か手軽でいい感じの無いかと考えたところ、GitのようなVCSをキューのバックエンドに使えば、操作の履歴を完全に残して参照も手軽なタスクシステムが作れるのではないかと思いました。botからGitの操作をすると、次のような点で困ります。Botを動かす場所にGitコマンドが必要GitはChatBotのような並列操作で同時に扱うとワークスペースを壊す遅いこれらの問題に立ち向かう方法は、libgit2を使うことです。https://libgit2.github.com/libgit2は、Cで書かれた組み込み用のGitライブラリで、たくさんの言語へのバインディングとともに配布され、Gitコマンドに依存せずにGitの操作が可能です。通常のGitコマンドと違い、Gitの内部コマンドを直接呼び出すため、動作も高速です。それだけでなく、通常開発者が使うGitのコマンドの裏に隠されたアトミックな操作を直接行うため、完全とは言いませんが、ある程度の並列実行にも耐えられる安全性を持っています(ワークスペースが壊れて、Git操作を受け付けなくなるとかが起こらなくなる)。libgit2を使うことで、Botを動かす場所にGitコマンドが必要なく、並列操作にある程度耐え、本来のGitよりも早く処理を実行することが出来ます。その一方で、大きな欠点もあります。それは、libgit2で扱えるGitの世界は、我々が普段使っているGitコマンドとは大きく違う点です。libgit2を使ってGitリポジトリを操作するには、非常に複雑で手間のかかる手順と、Gitの裏側の世界への理解が必要です。Gitの10章を読んで下さい。磁器(Porcelain)と呼ばれるコマンドの裏で、隠された配管(Plumbing)と呼ばれるコマンドが複雑に呼び出され、操作されています。簡単に、Gitには表側の世界と裏側の世界があると考えてください。表側の世界は、我々開発者が普段見ている、VCSとしてのGitです。ファイルに変更を加え、ステージし、コミットして変更履歴を記録する。それが、裏側の世界のGitです。一方で、裏側の世界のGitは、内容アドレスファイルシステムです。内容アドレスファイルシステムは、少なくともGitの世界観ではほぼほぼKVSシステムとほぼ同じようなものと理解して問題ないです。言うとGitはKVSです。表側の世界を操作するのが、先にも出てきた普段みなさんが慣れ親しんでいるGitコマンドです。裏側の世界を操作するのが、今回つかったlibgit2です。我々のよく知るGitコマンドでも、裏の世界を操作することは出来ますし、そのためのコマンドが(普段は使わないけど)用意されています。libgit2は完全に裏の世界のために存在するライブラリで、表の世界のような使用方法は出来ません。内容アドレスファイルシステムとしてのGit例えば、Gitのワークスペースにあるファイル「something.txt」を追加して、内容を編集しコミットすることを考えてください。通常の我々の操作では、次のようなことを行います。コマンドで新しいコミットを作成します。それでは、その時にGitの裏側の世界では何が起こっているかを見てみましょう。行った時、Gitの裏側の世界では次のようなことが起こります。いうファイルに内容を書き出すワークスペースのファイルとディレクトリの構成をもとに、ツリーオブジェクトを更新するここで重要なのは、Gitではあるファイルのある時点の状態に、そのファイルのSHA1値でアクセスできるKVSだということです。Gitの裏側の世界では、ありとあらゆるファイルとディレクトリは、オブジェクトとして扱われます。上の例では、addされたファイルはblobと呼ばれるオブジェクトです。オブジェクトには、その内容と属性で一意のSHA1値が振られていて、Gitの裏側の世界ではこのSHA1値を使うことによってファイルにアクセスすることが可能です。blobオブジェクトは、あるファイルのある時点での完全な内容をzlibで圧縮したもので、Gitはその内容にSHA1の値のキーでアクセスできる状態です。これが、Gitが内容アドレスファイルシステムと呼ばれる理由です。ファイルのSHA1値というキーを知っていれば、そのファイルの内容にアクセスすることができます。その仕組が、ツリーオブジェクトです。ツリーオブジェクトとは、Linuxのディレクトリ構成に似た情報が書かれたファイルです。参照ツリーの内容の例として、次のようなものが挙げられます。lib各行の最初のブロックがファイルのアクセス権限、次のブロックがオブジェクトのタイプ、その次のブロックがアクセスすべきオブジェクトのSHA1値で、最後のブロックがファイル名です。コマンドを打った時の情報によく似ています。blobをファイル、treeをディレクトリと置き換えれば、ほぼおなじ情報が得られます。これらの各行がファイルの情報と参照先を表しています。参照ツリーは入れ子構造にすることが出来ます。ルートの参照ツリーの下に、サブディレクトリとしての参照ツリーを入れることも可能です。これによって、ファイルのディレクトリ構成を表現しています。このように、Gitの裏側の世界では、すべてのファイルとディレクトリにはSHA1値が割り振られ、その値を入れることでファイルとディレクトリの内容を参照することができます。コミットに関しては、Gitの表の世界のコマンドと大きな違いはありません。Gitは次の情報を集め、コミットオブジェクトを作ります。コマンドでもよく見るので、何かわかると思います。コマンドで見ることができるコミットの並びで親に該当するものです。目新しい情報は、ルートのツリーオブジェクトです。コマンドの裏側で作られた、ツリーオブジェクトです。ステージングで作られたツリーオブジェクトは、現在のステージングの内容のすべての内容にアクセスできるSHA1値が記録されていて、Linuxのディレクトリのようにたどることが出来ます。ステージングの際に作成されたツリーオブジェクトの情報をコミットに入れることで、ステージングの内容を、コミットとして確定させることができるのです。Gitのコミットとは、ある地点のツリーオブジェクトのSHA1値に対して親のコミットオブジェクトのSHA1値とコミッターの情報を与えたものです。コミットオブジェクトも当然オブジェクトであり、SHA1値でアクセスが可能です。Gitの表の世界でも、コミットのSHA1値はよく目にすると思います。あの値は、Gitの内容アドレスファイルシステムの世界では、コミットオブジェクトを参照するためのキーであり、ファイルやツリーのオブジェクトを参照することとほぼ同じなのです。libgit2を使おうGitの裏側の世界を理解すれば、libgit2を使ってgitリポジトリの操作が可能です。それでは、libgit2のRubyバインディングであるRuggedを使って、実際にリポジトリを操作してみましょう。Ruggedを使ってgitリポジトリするには、次のようにします。Rugged::Repository.new("PATH_TO_REPOSITORY")これで、リポジトリオブジェクトが作成できます。先程まで説明していたGitの内容アドレスファイルシステムの挙動を実際に見てみましょう。file"writeメソッドでリポジトリにblobオブジェクトを書き込み、exists?メソッドでGitのオブジェクトの中に作成したオブジェクトが存在するか確認しています。その後、readメソッドでオブジェクトを読み込み、そのオブジェクトのdataメソッドでblobの中身を読み取りました。このように、Gitリポジトリをlibgit2で操作すると、まるでKVSのようにblobファイルを保存できるデータベースとして扱えます。同じように、コミットも見てみましょう。"d98b01bb7e2a47f473db567ec077df5d16aacf89"先ほど作成したblobをステージング(index)の参照ツリーに追加し、その状態をtreeオブジェクトとしてGitのに記録します。その時に発行されたSHA1を利用して、コミットオブジェクトを作成しました。コミットもオブジェクトで、SHA1のオブジェクトIDが発行されます。この後も同じように、新しいblobオブジェクトを作成し、ステージングしてtreeオブジェクトを作成し、そのtreeと先に作成したコミットのオブジェクトIDを元に次のcommitオブジェクトを作成します。これが、Gitで行われているバージョン管理の最も根本的な部分です。libgit2を使って、Gitをバックエンドにしたキューを作成する全体のコードは長いので、GitQueueというGemを作りました。次のコードは、簡単な使い方のスニペットです。==================Arrayのようなインターフェイスを持っていますが、Arrayとは違いFIFOの処理を邪魔するようなメソッドは生えていません。例外的にタスクの順序入れ替えは出来ますが、末尾挿入と先頭取り出し以外の方法でタスクを出し入れすることは出来ません。今回の実装では入っていませんが、Ruggedではlibgit2のバックエンドに、OSのファイルシステム以外にもRedisなどのKVSを選択することも可能です。普通にGitのバックエンドをKVSにすると書くとよく意味がわかりませんが、Gitという内容アドレスファイルシステムのバックエンドを本物のKVSにすると考えると、すんなりわかりやすいと思います。libgit2のバックエンドを本物のKVSにすることによって、より堅牢で様々な場所からアクセスできるGitQueueの運用などの夢も膨らみそうです。作るこのエントリの目的はGitをバックエンドとした履歴記憶機能付きキューでタスク管理botを作ることなので、スニペット程度ですが簡単にbotを実装してみます。dobegin@rtm.start!endend色々と処理が雑ですが、スニペットなので許してください。このbotの機能としてはAdminユーザーが、タスク管理を行うユーザーを追加できる各ユーザーごとに、GitQueueを使ってタスクのキューを作成するユーザーは、タスクの追加とタスクの完了ができるユーザーは、タスクの一覧を見ることができるです。肝心の操作履歴をどこに使ってんだって気はしますが、概ね操作履歴なんていうのは何かしら問題が合った時の調査に使うものなので、別にbotからは見えなくていいと思います。実際にリポジトリにアクセスすれば、使い慣れたGitコマンドでキューの変更履歴を参照することができます。このパーマリンクをブックマークする。公開されたり共有されたりしません。最近の投稿APIがカオスってたプロダクトでOpenAPI対応やってみた技術書典7で、「ActiveRecord完全に理解した」という本を出します。Framework.。