世界を動かす技術を、日本語で。

バージョン管理の未来

概要

Manyana は、CRDTを用いた新しいバージョン管理のビジョンを提案するプロジェクト CRDTマージ により競合は情報提示のみで、実際のマージ失敗が発生しない 競合表示が従来よりも遥かに 分かりやすく、編集内容の構造を把握可能 履歴管理 やリベースも従来手法より優れた設計 Python製デモとして公開、設計思想をREADMEで解説

Manyana:CRDTベースの次世代バージョン管理の提案

  • Manyana は、バージョン管理の将来像を提示するプロジェクト
  • CRDT(Conflict-Free Replicated Data Types) を基盤とした設計
  • 従来のバージョン管理で課題だった マージ失敗や競合 を根本から解決
  • 競合は「発生しない」が、 編集内容の重なり は分かりやすく可視化
  • 競合箇所には「誰が」「何を」したかが 明確に表示 されるUI設計
  • 例:関数を片方が削除、もう片方が中身を編集した場合も 構造を保ったまま表示

CRDTの特長とバージョン管理への影響

  • CRDT は「最終的整合性」を保証し、どんな順序でマージしても結果は同じ
  • マージ時に 失敗やブロック が発生しない
  • 並行編集が近接した場合のみ 情報として競合 を提示
  • 競合解決は「どちらの編集内容か」を 明確に示す ため、従来より容易
  • 履歴は「 weave構造」で管理され、各行の追加・削除時期も記録
  • マージ時に 共通祖先の探索やDAGの走査 が不要

リベースと履歴管理の革新

  • 従来のrebase は履歴を「書き換え」てしまう問題
  • CRDT方式では、 プライマリアンセスター の注釈を加えるだけで、履歴を保ったまま「上書き」可能
  • 複雑なマージトポロジー でも破綻しない設計
  • 履歴は DAG再構成不要、weave構造に全て記録

Manyanaの現状と今後

  • Manyana は約470行のPythonデモで、個別ファイル単位の動作
  • cherry-pickローカルundo は未実装だが、READMEで実現方法を提案
  • CRDTベースVCS がUX課題を解決できることを 実証
  • コードはパブリックドメイン、設計思想はREADMEに全文記載

まとめ

  • CRDTによるバージョン管理 は、従来の課題(競合・履歴破壊・複雑なマージ)を根本解決
  • Manyana はその実現性と有用性を示す デモ兼設計書
  • 今後のバージョン管理システム設計に 大きな示唆

Hackerたちの意見

これは、ブラムのアイデアのいくつかを復活させて詳しく説明したものだね。Codevilleっていう、2000年代初頭のDVCSのカンブリア爆発の頃の試みから来てる。Codevilleもストレージとマージにウィーブを使っていて、これはSCCSから始まった概念なんだよね(それがTeamwareやBitKeeperに繋がっていった)。CodevilleはCRDTの導入の約10年前に作られたもので、少なくとも見た目にはこの2つの概念は自然に合ってるように思える。でも、ウィーブがgitやMercurialなどの経験則に基づくアプローチよりも明確に良いマージ結果を出すって主張するのはいつも難しかった。テストケースを作るために必要な編集履歴が、少なくとも私には理解しづらかったからね。ブラムがその問題を手放さず、新しいアイデアを試し続けているのが好きだな。

CRDTは「何か」ではないことに注意してね。CRDTの論文は、最終的に一貫性のあるレプリケーションメカニズムについて考えたり分析したりする方法を提供しているだけ。だからCRDTは「導入された」わけではなく、「レプリケーションを議論するCRDTの方法」があるだけ。CRDTの論文で説明されている具体的なメカニズムは、非常に古くて、数十年前から広く使われているものなんだ。つまり、最終的な一貫性を実装しているもの(Gitを含む)は「CRDTを使っている」ということになる。

2007年にブラムが、「君のCausal Treeアルゴリズムはweaveのバリアントだよ」って言ってくれたんだ。それは大体合ってる。ここ20年で、weave系のアルゴリズムはかなり増えたよ。2020年の記事では、彼らのファミリーポートレートを作ることにイントロを捧げたんだ。https://arxiv.org/abs/2002.09511 これ、別の記事にできたかもね。

三文目の重要な洞察は? > ... バージョン管理のためのCRDT、これはずっと待たれていたけどまだ実現していない。Pijulは実際に存在していて、何百時間、場合によっては何千時間もの専門家の努力が注がれている。ブラムもその一人ではあるけど、投稿を読むとみんなが知ってることのように感じるね。

Pijulのことは聞いたことがなかったな。最初の検索で https://github.com/8l/pijul に行ったけど、11年間更新されてなかった。でも、実はそれは誤解で、公式リポジトリの https://nest.pijul.com/pijul/pijul は先月コミットがあったんだ。... もちろん、Pijulは開発にPijulを使っていて、GitやGitHubは使ってないからね!

変な趣味があるんだ。年に一回くらい、pijulマニュアルの理論ページ[0]を見に行って、TeXのフォーマットが直ってるか確認するんだよね。パッチを保存するためのより良い、より堅実なモデルが売りなら、プロジェクトに興味がある人が理解しやすいようにするのが普通だと思うんだけど。好奇心旺盛な読者に対して、マニュアルの第一印象を気にしないのは本当に変だよ。今、実験を始めてから約6年経ってる。約2年前(4年くらい前)に、Pijul Nestに行って[1]この問題を報告したこともあるんだ。ローカルでこの問題を直す方法の説明はもらったけど、なぜかその修正は公のバージョンには実装されてなかったんだよね。実験の進捗については、また1年後に報告するつもりだよ。[0] https://pijul.org/manual/theory.html [1] https://nest.pijul.com/pijul/manual/discussions/46

Pijul使ってる?たまに'pijul pull -a'をpijulソースツリーにやるんだけど、コンフリクトが出るんだ(自分のローカル作業はなし)。トラッキングアップデートプルの方法ってあるのかな?見当たらなかったから、リポジトリを捨てて再クローンしてる。そこでの進捗を追うのに何がうまくいってる?

失敗しないマージがあるのは良いことなのかな? マージの失敗は、単に「同じ場所での2つの変更」だけじゃなくて、意味的な衝突を示すことが多いよね。そういうケースには気づいて手動で対処する必要があると思う。提案されたシステムがそれに対処しているとは思うけど、私のざっと読んだ限りではよくわからなかったな。

これ

重複があるマージはユーザーにフラグが立てられるって書いてあるけど、実際にはgitのデフォルトの違い以上のものではないと思う。衝突の警告を出して、両側を無条件に結合するだけのgitのバージョンも作れるんじゃないかな。

これについては触れられてるよね。実際に失敗しないわけじゃないけど…重要なのは、変更が互いに触れ合うときに対立としてフラグが立てられるべきだってこと。そうすることで、実際には失敗しないシステムの上に、情報を提供する対立の表示が得られるんだ。

同意するけど、Gitが時々苦しむ他の場面、たとえば同じdiffだけど親が違う二つのコミットを同等とみなすべきかどうか、これにこのアプローチが役立つか気になる。一般的には、そういうコミットは同じとはみなせないよね。あるブランチが別のファイルでフリップしたブール値をひっくり返すコミットを考えてみて。だけど、リベースされたブランチのように、同等とみなすべき一般的なケースもある。例えば、git branch -d BRANCHがリベースされたBRANCHがマージされたときに成功すべきだと決めるのにCRDTアプローチは役立つのかな?

力不足なテキストマージの混乱に頼って、そんな問題を見つけるべきなの?スマートなマージではコードの問題じゃないマージの問題に反応するし、マージが成功しても深い問題を引き起こすものを見逃すこともある。マージ後の構文チェックの方がその目的にはいいよ。そして、すぐにでも: 意図を保持したエージェントベースのサニティチェック – 論理的に完全な結果ファイルで、マージツールの雑音なしで動作する。行の重なりがあるときや、目的の交差のより意味のあるヒントがあるときには、さらに高い強度で行われるかもしれないね。

確かに。成功したマージでもコンパイルできないコードになることが多いよね。ちなみに、AIツールがマージコンフリクト(特にリベース)をうまく処理できないのも、同じ根本的な理由で苦労してる。

これが提示されている方法の理解としては、マージがワークフローを「ブロック」しないってことだね。Gitではマージコンフリクトはマージの失敗だけど、このアイデアではマージコンフリクトは存在するけどマージは成功する。未解決のコンフリクトでコミットできるから、後でコンフリクト解決を先延ばしにできるんだ。jjもこれをやってると思う?技術的には、コミットにコンフリクトマーカーを含めることもできるけど、あんまり好まれないと思う。

これ、すごく短いね。 https://github.com/bramcohen/manyana/blob/main/manyana.py は依存関係なしのPythonで473行もある(そのファイルはdifflib、itertools、inspectだけをインポートしてる)けど、そのうち約240行が実装で、残りはテストだよ。

数百行のよく考えられたPythonで、厳しいハックに頼らずにできることは本当にすごいよね。JS界隈ではleft-pad事件とかで文句言われてるけど、正直Pythonエコシステムはもっと小さなパッケージがあった方がいいと思う。責任感のある人が、ポイントを主張したり人工的な指標を膨らませたりしないで、提案していく必要があるね。

マージの表示方法は、履歴を表現する方法とは別の問題のように思える。私もgitのデフォルトが嫌いだけど、だからこそp4mergeをマージツールとして使って、適切な4ペインのマージツール(左、右、共通ベース、マージ結果)を使って、なぜ衝突があるのか、どう解決するのかを理解するために必要なものを全部表示させてる。どうしてその問題を解決するためにVCSを切り替える必要があるのか理解できないな。

簡単に使える三分割マージにはp4mergeがいいよね。Gitの他の問題と同じように、マージが辛いのは、ひどいネイティブUXデザインが原因だと思う。Git自体に根本的な問題があるわけじゃないよ。

p4mergeを使わなくても、Gitのmerge.conflictStyleを"diff3"か"zdiff3"に設定できるよ(https://git-scm.com/docs/git-config#Documentation/git-config...)。そうすると、Gitのコンフリクトマーカーにベースバージョンも表示されるんだ。>>>右側 この設定にすると、生のコンフリクトマーカーを読む開発者は、Manyanaのコンフリクトマーカーが提供するのと同じ情報を推測できるんだよね。つまり、右側がログ行を追加したってこと。

なぜその問題を解決するためにVCSを切り替える必要があるのか理解できない。なぜか、この件に関してはほとんどの人が問題について考えていると思っているけど、実際にはあまり考えていないみたい。最近、人気のある尊敬されているポッドキャストのエピソードを聞いたんだけど、そこにゲストが出てきてバージョン管理システムについて話してたんだ。自分たちの新しいシステムを宣伝するために来てたんだけど、彼らの業界が他のソフトウェア開発のサブフィールドとどう違うのか、なぜ新しいアプローチが必要なのかを語ってた。彼らは思慮深いけど、現状に対して苛立っているように見えたし、考慮に値する問題を挙げてたけど、ほとんど高レベルの主張にとどまってた。でも、エピソードの半分くらい、30分か45分経った頃に、彼らが新しいVCSの詳細に入ろうと準備しているときに、Gitの能力と比較する軽いコメントをしたんだ。Gitがファイルのリビジョン間で「diff」をどう保存するかに関するアプローチ/デザインを参照してね。驚いたよ。そんな立場の人が、数ヶ月(数年)かけて設計、実装して、プレゼンテーションをするために話し回るプロジェクトを始める前に、ほんの少しのリサーチもしていないなんて、本当にNIHの熟知したストレインがまだ生きていることを強調してるよ。今の時代、NPM/Cargo/PyPI/何でもいいけど、問題を解決すると主張している既存のパッケージがないと、自分で数十行のコードを書くことに抵抗を感じる人が多いのにね。

ブラム・コーエンはすごいけど、ちょっと物足りない感じがするな。バージョン管理についてはもっと考えを巡らせてるよ([1])。CRDTの使い方についても考えてるし("# History Model"を検索して、"Implementing CRDTs"のセクションを読んでみて)。[1]: https://gavinhoward.com/uploads/designs/yore.md

それは別の投稿にする価値があるね!(HTMLにレンダリングすることをお勧めするよ)でも、「物足りない」ってのはコーエンの投稿の価値の一部だと思う。パラダイムシフトを広めたいなら、小さくて消化しやすい部分に分けるのが助けになるよ。

これってビットトレントを作ったブラム・コーエン?このページには意外と情報が少ないね。

CRDTに焦点を当てることのメリットがよくわからないな。コンフリクトの意味的な問題はどちらにしても存在するし。結果は一貫してるし、コンフリクトの説明も少し良くなるけど、変更が入り混じる可能性があるから、全然改善とは思えない。私は完全にリベース派だよ。マージコミットは絶対避けるべきで、すべてのコミットはファストフォワードコミットで、孤立してロールバックできる作業単位であるべきだと思ってる。しかも、すべてのコミットは小さくあるべき。Gitflowはアンチパターンで、避けるべきだよ。長期間のブランチはパッチリリース用で、機能開発には向かないと思う。これがVCSの未来だとは思えない。Jujutsu(とGerrit)は、実際のGitの問題を解決してるんだ。変更の複数のリビジョンがあるっていう問題ね。フィードバックに基づいてリベースする必要があるコミットの連鎖があると、Gitでは痛みを伴うんだ。

CRDTが衝突を起こさないってことで、みんながそれを全ての問題の解決策だって言ってるけど、実際には衝突が必然的にある問題もあるんだよね。そういうのはCRDTでは全く表現できないか、衝突解決の方法が考えられた場合よりも悪くなることもある。例えば、同時に編集された文字が交互に表示されるマルチプレイヤーテキストエディタとか。

前はマージよりもリベースをよく使ってたけど、年を重ねるごとに考え方が変わってきた。メインからフィーチャーブランチへのマージは全然問題ないし、リベースよりも簡単だよ。フィーチャーブランチが完成したら、最後にメインからフィーチャーブランチをマージして、フィーチャーブランチをメインにスカッシュコミットでマージする。リモートからブランチを更新する時は、常にプルリベースをして、シンプルなプルからマージコミットを避けてる。これが99.99%の確率でうまくいくのは、何を変更したかとリモートの変更が明らかだから。開発ブランチでプロジェクトをやってる時は、フィーチャーブランチをメインじゃなくてdevから派生したものとして扱う。この場合、devをフィーチャーブランチにマージして、フィーチャーブランチをスカッシュコミットでdevにマージして、最後にメインをdevに、devをメインにマージする。こうすれば、devとメインにはいくつかのマージコミットができるけど、緊急修正みたいなことがメインで起こった時だけだよ。リベースを常に使う問題は、最終結果だけじゃなくて、途中の全てのコミットで衝突を解決しなきゃいけないこと。これって、実際には使われないコミットのために余計な手間がかかるし、履歴をめちゃくちゃにすることもある。こう考えてみて:1. メインからfooブランチを作成。2. メインに緊急コミットXを追加。3. fooでフィーチャー作業をしてコミットA、B、Cを作成。フィーチャーが完成した。4. fooをメインからリベースして、XがAの前にあることによる衝突を解決しなきゃいけない。仮にそれがA、B、Cの全てと衝突したとしたら。5. その後、fooをメインにファストフォワードコミットでマージできる。XAやXABの状態でコードベースを実行したいとは思わないよね。XABCとして実行したいだけ。実際、XAやXABの状態でコードが動くかテストすることもないから、そのチェックポイントを持つ意味はほとんどない。気にするのは、これらの状態:何も起こる前のメイン、Xが追加されたメイン、そしてXABCを持つメイン。git blameを使うのは、AとBのコミットを個別に見る唯一の時かもしれないけど、その有用性は限られてるから、やる価値はあまりない。実際、ファストフォワードコミットだけを望むなら、古いバージョンのコードを取り出すためにほとんど何もしていない可能性が高い。自分にこう問いかけてみて:「もしメインのgit履歴を全部削除して、現在の状態とフィーチャーブランチだけが残ったら、プロダクションシステムに何か悪いことが起こる?」もし起こらないなら、実際にはgitのほとんどの機能を使ってないってことだよ(それは良いことだけど)。

「作業の単位」って言った時、どの作業の単位を指してるの?リベースの問題は、一つのスナップショットのセットを取り、それを別のセットの上に再生することだから、結局二つの「同等な」作業の単位ができちゃう。実際にはそれらは「同じ」なんだけど、もし「作業」が変更を意味するなら、Gitは明らかに二つの異なる履歴を示すことになる。これはPijulと対照的で、Pijulでは変更がパッチで、交換可能なんだ。全体のセットを適用しても、パッチの適用順序に関係なく結果は同じであるべきなんだ。これが「作業の単位」だと理解できるのは、"孤立"で適用したり元に戻したりできるもの。私の目には他のものはごちゃごちゃしてるけど、他の人には秩序があるのかもしれない。コードで定義されたソフトウェアシステムが、各パッチが「原子的」でフィーチャーや修正などを表現できる独立したパッチのセットで表現できるといいんだけど、Gitではそれはグラフの中ではほぼ不可能だよね。もちろん、フィーチャーに属するコミットのセットをチェリーピックしたりリベースしたりすることはできるけど、なんでそんなことをするの?

CRDTが解決するのは、システムレベルでの衝突。セマンティックレベルではない。2人以上のエンジニアが異なる値を変数に設定することは、CRDTでは処理できない。エンジニアAの意図した値は1、エンジニアBの意図した値は2、CRDTは2を選ぶ。結果としてセマンティックに間違っている可能性がある。意図を反映していないんだ。Gitや他のバージョン管理システムの主な問題は、全ての名前がひどいことだと思う。プル、プッシュ、マージ、ファストフォワード、スタッシュ、スカッシュ、リベース、ザース、アワーズ、オリジン、アップストリーム、これだけでも一部に過ぎない。そしてGUIも。これらは、10年やってるエンジニアにとっても非常に混乱する。さらに、衝突解決も混乱するのは、事前に警告がないから。ファイルを編集する前に、バージョン管理システムが他の誰かがすでに変更を加えたか、現在作業中であることを警告してくれたら、すごく便利だよね。大きなチームでは、この種の自動化が衝突を減らすだろうし、人間が同じファイルに触れないことに同意すれば、悪い衝突解決から生じる品質の後退も減るだろう。ちょっと宣伝させて:私は、これらの問題を解決するために、gitの周りにシンプルなUIを作って自動化していて、無料で提供してるよ。 https://www.satishmaha.com/BetterGit

ファイルを編集する前に、バージョン管理システムが他の誰かがすでに変更を加えたか、現在作業中であることを警告してくれたら、すごく便利だよね。大きなチームでは、この種の自動化が衝突を減らすだろうし、人間が同じファイルに触れないことに同意すれば、悪い衝突解決から生じる品質の後退も減るだろう。これで私のVSS時代を思い出させる(できれば思い出させないでほしいけど)。

ここでのミスマッチは、ほとんどの人が中央の目立つリモートリポジトリを使ってgitを利用していることから広がっているように見える。Gitは真の分散ビジョンで開発されたのにね。確かに、その真の分散というのは、最終的に何らかの「中央」リポジトリに到達した時にのみ完了するけど、私たちがやっていることとはかなり違う。

それには、非常に中央集権的なVCSが必要で、分散型のはダメだよ。Perforceはファイルをロックできるから、他の人が編集できなくなるんだ。もしファイル内でより細かいロックを実装したり、他のユーザーが編集のためにチェックアウトしようとしたときに警告を出したりしたら、まさに理想的なVCSになるだろうね。どうして、あるいはむしろ、なぜGitが事前に潜在的な競合について警告してくれるの?使い方としては、みんながリポジトリのローカルクローンを持っていて、異なる方向に進んでいる可能性があるのに。結局、誰かのローカルブランチからコミットをプルしたり、そこにプッシュしたりするのが前提だから、その言い回しなんだよね。同じ方向に協力して作業することが理にかなっていて、摩擦や痛みを避けるのは、使っている人間から自然に生まれた偶然の産物であって、ツールの設計に組み込まれているわけじゃない。俺たちは、Gitの可能性の中で最もバカバカしくて単純な部分、つまり中央の真実のソースを持つVCSとして使っているだけで、分散ワークロード向けに設計されたツールが持つ複雑さの負担を背負っているんだ。

これが何を解決するのか、ちょっと混乱してる。誰かが関数を編集して、誰かが同じ関数を削除する例を挙げて、マージが決して失敗しないと主張しているけど、実際にはマージが失敗することを示している。ソースにはまだマージマーカーが残ってる。具体的に何が改善されてるの?

うん、著者はイントロの段階で自分の主張をうまく伝えられてない。 > CRDTマージは定義上常に成功するので、従来の意味での衝突はない — 重要な洞察は、変更が互いに触れるときに衝突としてフラグを立てるべきだということで、実際には決して失敗しないシステムの上に情報的な衝突プレゼンテーションを提供してくれる。このプロジェクトはそれを解決している。明らかな矛盾がある。CRDTは定義上常に成功し、従来の意味での衝突はないから(言い換えれば)衝突する変更は衝突としてマークされる。えっと、他のソース管理でもそうじゃない?実際、答えを書いている間にそのイントロを再読してみて、AIが書いた匂いを感じ始めたよ。

みんな、自分の好きな言語でゼロからVCSを作ってみるべきだよ。めっちゃいい週末プロジェクトになるし、コミットをいろんな方法で可視化するのが楽しいんだ(今、シェーダーで実験中)。それに、これが未来への道なんだよね。今はS3とかのラッパーみたいなソフトウェアが多いから、自分のツールセットを作るチャンスだよ。DIY好きな人には特に響くと思う(俺はPulsar IDE使ってるけど、笑)