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

jjとは何か、そしてなぜ気にするべきなのか?

概要

  • Jujutsu(jj) は、分散型バージョン管理システム(DVCS)の一種
  • git 利用経験者向けに設計された、より単純かつ強力なツール
  • gitMercurial の長所を統合し、操作性とパワーを両立
  • git互換バックエンド により既存プロジェクトとの共存が可能
  • 導入リスクが低く、いつでも git へ戻せる柔軟性

Jujutsu(jj)とは何か

  • jj は、JujutsuのCLI(コマンドラインインターフェース)名称
  • 分散型バージョン管理システム(DVCS) として設計
  • gitやMercurial(hg) など他のDVCSの長所を取り入れた構造
  • 操作の容易さ高機能性 を両立した設計思想
  • 本質的なコマンド数の削減各コマンドの強化 が特徴

jjのメリットと特徴

  • gitより簡単かつ強力 という稀有な特徴
  • 機能と複雑さのトレードオフ を打破した設計
  • 連携のしやすさ により、コマンド同士の相互作用が向上
  • 高度な使い方 でgitでは困難な操作も可能
  • git互換バックエンド により、既存のワークフローを損なわない

導入のしやすさとリスクの少なさ

  • 他のユーザーがjjへ移行する必要なし
  • jjで作業した履歴もgitに戻せる 互換性
  • 試しやすく、失敗しても元に戻せる 安心感
  • 既存プロジェクトへの影響が最小限 で済む柔軟性

まとめ

  • jjはgitユーザーにとって魅力的な新世代DVCS
  • シンプルさとパワフルさを両立
  • 導入リスクが低いので気軽に試せる選択肢

Hackerたちの意見

最後の段落が一番大事かもね。 > 「jjを試してみるべき理由がもう一つあるんだ。それは、gitと互換性のあるバックエンドを持っているから、他の人に変換を強要することなく、自分だけでjjを使えるってこと。だから、試してみるデメリットは本当にないよ。もし合わなかったら、書いた履歴を全部失うわけじゃないし、問題なくgitに戻れるから。」

面白いことに、俺もCVSやSubversionでgitをそんな風に使ってたよ。

でも、それは違うよ。互換性はあるけど、スムーズではない。そういう機能は主に移行のユースケースや、jjで管理されているリポからのgitデプロイメントをサポートしてるだけ。gitがやる操作は、jjのログには載ってないから、常にインポートしなきゃいけないんだ。プロジェクトは単一の主要インターフェースを推奨してるよ。

大事な注意点:GitとJJを同じディレクトリで使おうとしない方がいいよ。JJだけを使うなら多分大丈夫だけど、混ぜるとひどいことになるから。

LFS、サブモジュール、またはフックを使ってない限りね。

jjを試してるんだけど、嫌なところが一つあって、ファイルの編集が自動でコミットされちゃうんだよね。だから、変更のために空の新しいコミットを作らなきゃいけない。例えば、2週間前のコミットからリポジトリを見たいとき、ただそのコミットをチェックアウトしてファイルを編集すると、そのコミットが自動で変更されちゃって、その後のすべてが新しい変更の上にリベースされるんだ。だから、古いコミットから新しいブランチを作って、そのブランチに空のコミットを追加して、ファイルの変更が過去2週間の履歴を書き換えないようにしてる。gitは、ファイルに何をしても、自分が指示するまで リポジトリが変わらないから、ずっといいよね。

だから、editは絶対使わないで、代わりにnewを使った方がいいよ。そうすれば、変更が追跡されて、ゴチャゴチャにならないから。gitでスタッシュを jugglingするより、ずっといいと思う。

うわ、それは完全に無理だわ。gitを使うのは複雑なメンタルモデルが必要だけど、少なくとも自分が頼んだこと以外は何もしないからね。

jj newは、空のリビジョンを上に作ることで、git checkoutに似てる。逆にjj editは、git checkout; [編集...]; git add -A; git commit --amend --no-editに似てるね。

最近、新しいファイルの自動ステージングを無効にできるようになったから、これでメインの不満が解消されたよ。

jj editは、他のコメントでも言われてる通り、最大の罠だと思う。だから、jj newを使った方がいいよ。でも、もしうっかり編集したり変更したりしても、jj undoが意外とよく効くんだ。jjを使ってるときは、コミットを考えずに(jjはそれを非常に安価な「スナップショット」として扱う)「変更」に集中した方がうまくいった。最初は変な感じだったけど、gitでリベースしてるときに、自分がした論理的な変更をそう見てたことに気づいた。jjはそれを明示的にしてくれる。jjの自動リベースは、変更をプッシュするまで関係ないし、一度プッシュするとそれが不変になるから、共有された変更をうっかりリベースするのを防いでくれる。

これはまさにjjの存在理由だから、もし自分には合わないと決めても驚かないよ。

古いコミットを「チェックアウト」するってどういうこと?jj editを使ってるみたいだけど、これはそのままの意味で動いてると思うよ。jj newに切り替えれば、その問題は解決するよ。

Jujutsuには、可変コミットと不変コミットの概念があって、これを解決しているんだ。通常、リモートブランチのすべては不変なんだけど、ブランチで作業するためにはそれを追跡して可変にするんだ。

編集を失ったら、jjの操作ログは本当に素晴らしいよ。AIが間違えることが増えたから、たくさんの作業を救ってもらった。あと、ワークスペースはgitのワークツリーと比べてめっちゃ速いし、同じコンセプトだけど実装が違うんだよね。確かに、あれはちょっと興味深いアプローチだったけど、長期的には「忘れる」必要があるにしても、個人的にはメリットがあったと思う。測りやすいものではないけどね。

JJって本当に逆に考えろって言ってるの?新しいコマンドから始めて説明しろって言うけど、gitだとまず変更を加えて、ワークフローの最後に変更セットに名前を付けるんだよね。たまに、複数の機能や抽象に関連する変更が混ざって、汚れたリポ状態になっちゃうことも多いし。だから、コミットにまとめたい変更を選んで、状態を整理することが多いんだ。gitと互換性があるから、ファイルを追加したり、コミットしないでおくのがうまくいくはずだと思うけど、このチュートリアルを読んでもよくわからないな。

そんな風に考える必要は全然ないよ。jj newは単に「新しいコミットを作る」って意味だから、すぐに説明する必要はないんだ。俺は絶対にしないしね。そうするつもりだったのはわかるけど、その習慣を強制しようとしたら、逆に非生産的だって感じたよ。

僕の好みのワークフローは、新しい変更から始めて、欲しい変更を選んで、jj commitで変更を説明して新しい空のコミットを作るって感じ。昔のgitのワークフローにすごく似てる。もし一つの変更に複数の機能や抽象が含まれちゃったら(いわゆる「汚れたリポ」)、jj splitがgitのadd/commit/繰り返しのワークフローを整理する代わりにうまく機能するよ。

JJって本当に逆に考えろって言ってるの?新しいコマンドから始めて説明しろって言うけど、gitだとまず変更を加えて、ワークフローの最後に変更セットに名前を付けるんだよね。いい考え方は、jj newは空のgitステージングエリアだってこと。まだjj commitコマンドがあって、説明した後にjj newを使えるよ。 > たまに、複数の機能や抽象に関連する変更が混ざって、汚れたリポ状態になっちゃうことも多いし。だから、コミットにまとめたい変更を選んで、状態を整理することが多いんだ。jj splitを使えば、これもかなりうまくいくよ。 > gitと互換性があるから、ファイルを追加したり、コミットしないでおくのがうまくいくはずだと思うけど、このチュートリアルを読んでもよくわからないな。JJでは常にコミットがあるんだ。時には空だったり、時には満杯だったりするけど、安定したchangeidがある。JJはコミットをフォルダの内容に基づいて計算された値として扱うんだ。変更の単位としてではなくてね。

JJって本当に逆に考えろって言ってるの?新しいコマンドから始めて説明しろって言うけど、gitだとまず変更を加えて、ワークフローの最後に変更セットに名前を付けるんだよね。そうだけど、これは逆じゃないよ。gitのやり方が逆なんだ。 =)

説明がないコミットで変更を加えて、最後にjj commit -mで説明して新しいコミットを一気に作ることを止めるものは何もないよ。それは基本的にgitと同じだから。違いは、最初にステージする必要がなくて、変更を加えながらその場で修正している感じなんだ。

個人的にはjjは使ったことないけど、dvcsに関してはFossilはGitのいい補完になると思う。Gitとは違うやり方をするし、ほんとに分散型の感じがするんだよね。自動同期機能もすごくいいし、バックアップリポジトリをクラウドストレージのフォルダに保存して、自動で同期することもできるよ。

jjはワークフローに関してすごく柔軟だよ。ひとつ注意すべきことは、コミットにメッセージが必要ないってこと。僕は何か作業してるときに頻繁にjj newを実行して、メッセージなしで全部残しておくんだ。で、実際のコミットを作る準備ができたら、一時的なコミットをまとめてメッセージを追加する。もし変更が分けられるなら、スカッシュする前にコミットを分けることもできる。このワークフローは一種のアンドゥ履歴みたいな感じで、5分前の状態に簡単に戻って別のアプローチを試せるし、元の変更に戻ることもできる。Gitに比べて実験がずっとやりやすいよ。

jjのことを考えると、xyzを作りたいとするなら、jj desc -m "feat: x y & z" で作業を進めて、jj split で分けたい部分やファイルを分けて名前を付ける。これで名前の変更もできるよ。jj bookmark create worklabel-1 -r rev1 jj bookmark create worklabel-2 -r rev2 # 両方のコミットをプッシュする # 分けたばかりだから、相互依存してない可能性が高い # だから両方をベースにリベースできる # rev1がすでにベースの上にあると仮定して jj rebase -s rev2 -d base jj git push これで完了だよ。

実際には、逆に考えさせるのはgitなんだよね。jjでは作業ツリーがコミットなんだけど、gitでは少なくともステージするまでそうじゃない。作業ツリーがコミットであることには広範な影響があって、コミットと一緒に動作するすべてのコマンドがデフォルトで作業ツリーに対して動作するようになるんだ。

これ、私のことだ!一つの変更をする過程で、他にもいくつかの変更をしてしまって、自然な結論に至った後にそれらが別々だと気づくことがよくある。だから、複数のワークスペースを持っていて、変更を棚上げすることも多い(IntelliJ)。汚れたリポジトリもできちゃうし、そこからチェリーピックするのは痛いこともある。時にはgitパッチを作って、差分をtmpファイルに保存しながらコミット候補を整理することもあるよ。他のことをしている間に数日間変更を放置することもあって、そうすることで本当に正しいかどうかを判断できるんだ。カオスだし、同僚にはちょっとプロフェッショナルに見えるようにこれを隠してる。計画的で意図的な人を尊敬してるけど、私は自分の指の下にコードがあることが必要なんだ。

多くの人が知らないと思うけど、Jujutsu用のオープンソースでクロスプラットフォームのデスクトップGUIアプリケーション「GG」もあるよ。リンクはここね: https://github.com/gulbanana/gg jjのコマンドラインインターフェースは素晴らしいけど、グラフィカルユーザーインターフェースの方がやりやすいタスクもあるんだ。例えば、いろんなリビジョンをサクッと見て、その差分を確認したいときとか。GGを使うと、そういうリポジトリのブラウジングや特定のスカッシュ操作がすごく効率的になるよ。GGについてもっと知りたいなら、最近のAbstractionsポッドキャストの8:17あたりで、共演者と話したから聞いてみてね: https://shows.arrowloop.com/@abstractions/episodes/052-they-...

jjuiのTUIもすごいよ!

「もっと強力で簡単」っていうのはすごくいい主張だけど、ここに具体例がないと、どれだけの痛みを減らせるか、どんな素晴らしいことを見逃してるのか、納得できないな。

3つのPRを一つのチェックアウトでトリビアルに作業して、各PRに焦点を当てた変更を独立してプッシュするのが簡単にできるってことは、価値があるってこと?もしこれが必要ないなら、jjに価値を見出せないかもしれないけど、それも全然OKだよ。magitを使って同じワークフローを得ることもできるかもしれないし(多分?個人的にはmagitは使ったことないけど)、それも全然OKだよ。

確かにいいフィードバックだね、ありがとう。真に簡潔な例を考えるのは時々難しいけど、それが価値を生む理由でもあるんだよね。

ページの左側にある目次を使って、「実世界のワークフロー」や「ブランチ、マージ、コンフリクト」、それから「他の人とコードを共有する」を見てみて、JJがどうやってやってるかを今のgitワークフローと比べてみて。これにはちょっとした努力が必要だけどね。公式のJJドキュメントにも「全体像」の紹介とチュートリアルがあるよ: https://docs.jj-vcs.dev/latest/tutorial/

そうだね、SVNからgitに移行したのは、SVNのブランチが本当に扱いにくかったから。日常のgitワークフローには全く粗さや大きな痛みはないよ。

もうインデックスはないよ。これが「簡単な」部分なんだろうね。

特定のコマンドだけじゃ、jjの魅力は伝わらないよね。むしろ、最初はちょっと怖がられるかも。直感的なワークフローがあって、何かをやるのに助けを求めることがないのがいいところなんだ。実際に使ってみないと、その良さは分からないよ。

jjの好きな機能の一つは「jj absorb」なんだ。今のリビジョンで変更したところの近くで、最後に変更を加えたコミットを見つけて、そのコミットに自分の変更を移してくれるんだ。設定ファイルや.gitignoreに変更を加え忘れたときにすごく便利だよ。「jj new」で変更を加えて、「jj absorb」すればOK。新しいコミットを作ったり、どこでリベースするか考えたりする必要がないんだ。

git absorbもあるからね、参考までに。

jjは素晴らしいよ!最初は慣れるのに時間がかかったけど、もう戻れないね。他の人と一緒に作業していると、物事が思ったようにすぐにレビューされてマージされることってないから。jjを使うと、同時にたくさんのPRを開いてもコストが低いし、jj newみたいにして3つ全部必要なものを作ることもできる。これで、機能追加と大きなリファクタリングを同じPRでやらずに済むんだ。両方を独立させておいて、全部マージされる前に次のステップを始めることもできる。変更を追加するのも簡単だし、コメントが出てきたら個別のPRに切り替えたりもできる。こういうのが好きなんだ。GoogleでPerforceのカスタムフォークを使ってたとき、「スタックされたCLは絶対にやるな、もう学ばなかったのか?」って自分に言い聞かせてた。もし一つのCLが別のCLに依存してたら…やらない方がいい。gitでも同じことを考えてた、無限のインタラクティブリベースやマージコンフリクトのコミットに座ってるときにね(「git rebase abort」は多分一番使ったコマンドだった)。でもjjだと、それが問題じゃない。マージコンフリクトはあるけど、解決を別のコミットとして追跡できるから安心して解決できる。jj new -d 'resolve merge conflict -A @で、コンフリクトが起きた後に新しいコミットを追加できる。解決が満足いくまでハックして、jj squash --into @-`でマージコンフリクトを解決。ほんとに美しいモデルだよ。メンタルヘルスにも大きく貢献してる。人と一緒に作業するのがめっちゃ楽になるんだ。

JJを好きになりたかったんだけど、数ヶ月使った後に普通のgitに戻っちゃった。戻るきっかけになったのは、GithubのPRをどう管理するか、origin/mainから変更を取り込む方法がきちんと頭に入らなかったから。結局、複数の貢献者が作業している機能ブランチをめちゃくちゃにしちゃったんだ。いつかまた試してみるかもしれないけど、GithubのPRを通じてチームで作業するのが一番の障壁だったな。

みんな!お久しぶり!チュートリアルの更新をずっとしてなかったんだ。アップストリームするつもりなんだけど、今いるスタートアップ(ersc.io)でめっちゃ忙しくて、なかなか時間が取れなかった。今も毎日jjを使ってて、すごく気に入ってるよ。質問があったら気軽に聞いてね!

jjは本当に素晴らしいと思うし、もっと広まるべきだと思う。メンタルモデルがgitよりずっとクリーンだし、元に戻す機能も期待通りに動くし、スタックされた変更を扱うのも自然に感じる。何かを壊すことへの常に低い不安感がないのがいいよね。今のところ、バージョン管理のためのベストなフロントエンドだと思う。ただ、ここ数ヶ月、君が最後に言ったことについてずっと考えてる。もしバージョン管理が追跡しているコードを、単なるテキストの行としてではなく、実際に書いて考える構造として理解したらどうなるんだろう?ブランチでのリネームと別のブランチでの無関係な関数追加は、実際には意味のあるコンフリクトではないし、今日のツールがソースコードをフラットなテキストファイルとして扱うからそう見えるだけなんだよね。この種の構造的なインテリジェンスを強化するために、github.com/ataraxy-labs/semに取り組み始めたんだ。これはtree-sitterを使ってコードをセマンティックエンティティにパースして、そのレベルで動作するんだ。