Jujutsu(jj)を試してみる
概要
Gitを毎日使っていると、便利な反面「またステージングし忘れた」「rebase中にコンフリクトが出て手が止まった」「間違えてreset --hardしてしまった……」みたいな場面、地味に多いですよね。
慣れで乗り切ってはいるものの、ふとしたときに「これ、もっと素直な仕組みにならないのかな」と思うことはないでしょうか。
そこで最近よく名前を聞くのが Jujutsu、コマンド名から jj と呼ばれるバージョン管理システムです。
GoogleのMartin von Zweigbergk氏が作ったRust製のツールで、面白いのは「Gitを置き換えるのではなく、Gitのリポジトリの上にかぶせて使える」という点です。裏側は.gitをそのまま読み書きするので、チームの他のメンバーはGitのまま、自分だけjjで作業する、といったことができます。
この記事では、Gitユーザーの視点でjjをインストールし、普段のGit操作がjjだとどう変わるのかを対比しながら触っていきます。最後に、乗り換える価値があるか・どんな人に向くかも考えてみましょう。
名前の由来
その前に、ちょっと寄り道を。「Jujutsu」って柔術のこと?と気になった人もいるはずです。
実は、名前の付き方が少し面白いんです。普通は「ツール名を決めて、その略称をコマンドにする」順番ですよね。ところがjjは逆で、先にコマンド名jjが決まっています。
公式のREADMEには、こう書かれています。
コマンドラインツールを
jjと呼んでいるのは、タイプしやすく、(英語では珍しい綴りなので)他のコマンドと衝突しても差し替えやすいから。
つまり「打つのが速い」「jjなんて単語は英語にまず無いので、既存のコマンドとかぶらない」という、実用本位の理由で先にjjが決まったわけですね。キーボードのホームポジションで同じ指を2回叩くだけ、というのは確かに速いです。
そのうえで、プロジェクトの正式名称については、
プロジェクトを「Jujutsu」と呼んでいるのは、それが
jjに合うから。
とあります。jjという略称に後から名前を合わせにいった、というわけです。柔術という武術の意味そのものに深いこだわりがあるというより、「jjで始まる、語呂のいい言葉」として選ばれた、という温度感ですね。
ちなみに公式でも、コマンド名については「jj for now(いまのところjj)」という言い回しが使われていて、衝突したら差し替える余地を残してあるのが、いかにも実装者らしいなと感じます。
jjとGitの一番大きな違い
細かいコマンドの前に、考え方の違いを押さえておくと一気に分かりやすくなります。
ひとつめは ステージングエリアが無い こと。
Gitだと「作業ツリー → ステージ(index) → コミット」の3段階がありますが、jjでは作業中のファイルがそのまま「作業コピー=ひとつのコミット」として常に存在しています。git addに相当する操作が要らない、と言うとイメージしやすいでしょうか。ファイルをいじった時点で、その変更は自動的に今のコミットに反映されていきます。
ふたつめは やり直しがとにかく簡単 なこと。
jjはすべての操作を「オペレーションログ」として記録しているので、jj undo一発で直前の操作を取り消せます。リベースだろうがコミットの編集だろうが、間違えたら戻せる、という安心感があります。
みっつめは コンフリクトを抱えたまま先に進める こと。
Gitだとコンフリクトが出た瞬間に作業が止まりますが、jjはコンフリクトをコミットの中に記録しておけるので、いったん置いて別の作業をして、あとから落ち着いて解決する、という進め方ができます。
この3つが腑に落ちると、あとのコマンドはわりとすんなり入ってきます。
jjをインストールする
まずは手元にjjを用意しましょう。devnixらしくNixOSを軸に、他のディストリも触れておきます。
NixOS
NixOSならjujutsuというパッケージ名で入っています。configuration.nixに書いて宣言的に入れておくのが王道ですね。
# configuration.nix の environment.systemPackages に追記
environment.systemPackages = with pkgs; [
jujutsu
];
書き換えたら、いつものように反映します。
sudo nixos-rebuild switch
home-managerを使っているなら、専用のモジュールがあるのでそちらが便利です。ユーザー名やメールアドレスの設定までまとめて宣言できます。
# home.nix など
programs.jujutsu = {
enable = true;
settings.user = {
name = "Your Name";
email = "you@example.com";
};
};
Ubuntu / Debian系・Fedora
ディストリのリポジトリに無い、もしくは古いことが多いので、Rustのパッケージマネージャcargoから入れるのが手堅いです。
cargo install --locked jj-cli
cargoが無ければrustupを先に入れるか、GitHubのリリースページから配布バイナリを落としてくる方法もあります。Fedoraなど一部の新しめのディストリではdnf install jujutsuで入る場合もあるので、まずはそちらを試してみてもよいでしょう。
インストールの確認
入ったらバージョンを確認しておきます。コマンドはjujutsuではなくjjです。
jj --version
まずは名前とメールを設定する
Gitでいうgit config --global user.nameに相当する設定を済ませておきましょう。home-managerで設定済みならこの手順は不要です。
jj config set --user user.name "Your Name"
jj config set --user user.email "you@example.com"
設定は~/.config/jj/config.tomlに書かれます。あとから直接編集してもかまいません。
既存のGitリポジトリでjjを使い始める
ここがjjの嬉しいところで、いまあるGitリポジトリにそのまま乗せられます。
リポジトリのルートで、次のコマンドを実行します。
jj git init --colocate
--colocateを付けると、.gitと.jjが同じディレクトリに同居する形になります。つまりGitコマンドとjjコマンドを同じリポジトリで併用できるわけです。チームはGitのまま、自分はjjで、というのはこの仕組みのおかげですね。
ゼロから新しくクローンしたいときは、こちらです。
jj git clone https://github.com/user/repo
普段のGit作業をjjでやってみる
ここからは「Gitならこう、jjならこう」と並べていきます。
状態を見る
まずは今どうなっているかの確認。Gitのgit statusにあたるのがjj stです。
jj st
履歴を見るときはjj log。Gitのログより、コミット同士のつながりがグラフで見やすく出ます。
jj log
ここで覚えておきたいのが、jjの各変更には change ID という安定したIDが振られること。
Gitのコミットハッシュは内容を書き換えるたびに変わってしまいますが、jjのchange IDはコミットを編集しても変わりません。「あの変更」をずっと同じ名前で指し続けられるので、後述のリベースなどがやりやすくなっています。
変更をコミットする
ここが一番Gitと感覚が違うところです。
jjではgit addが要りません。ファイルを編集すると、その内容は自動で「今いるコミット」に取り込まれていきます。あとは、そのコミットに説明文を付けてあげるイメージです。
# 今の作業内容に説明(コミットメッセージ)を付ける
jj describe -m "ログイン処理のバグを修正"
そして、次の作業に移るときはjj newで新しい空のコミットを作ります。
# 今のコミットを確定させ、新しい作業用コミットに移る
jj new
Gitの「コミットして、次の変更を始める」が、jjだと「describeで説明を付けて、newで次に進む」という流れになるわけです。addが消えたぶん、手数はむしろ減ります。
過去のコミットを直す
「ひとつ前のコミットに修正を入れ忘れた」みたいなとき、Gitだとgit rebase -iやgit commit --amendの出番ですよね。jjではもっと気軽です。
直したいコミットそのものに移動して、編集してしまえます。
# change ID を指定してそのコミットを編集状態にする
jj edit <change-id>
あとは普通にファイルを直せば、その変更はそのコミットに反映されます。終わったらjj newで先頭に戻ればOKです。
今の作業を、すぐ下(親)のコミットに混ぜ込みたいときはsquashが便利です。
jj squash
やり直す
操作を間違えたら、迷わずこれです。
jj undo
直前の操作が取り消されます。「どこまで戻れるんだっけ」と不安になったら、オペレーションログを見れば、これまでの操作の履歴が全部残っています。
jj op log
Gitのreflogに近いものですが、こちらはリベースもコミット編集もぜんぶ記録されていて、特定の地点まで丸ごと戻すこともできます。reset --hardで青ざめる、みたいな事故が起きにくいのは安心感が違いますね。
リモートとやり取りする
リモートへのpush/fetchもjjから行えます。fetchはそのままです。
jj git fetch
pushで少し注意したいのが、jjは普段「名前のないブランチ(匿名ブランチ)」で作業する点です。
リモートに上げるときは、Gitのブランチにあたる bookmark(ブックマーク) を付けてからpushします。
# 直前のコミット(@-)に main という bookmark を付ける
jj bookmark create main -r @-
# bookmark をリモートへ push する
jj git push
@が今いる作業コピー、@-がその親、という指定の仕方も合わせて覚えておくと、コミットを指すのがぐっと楽になります。
Gitに戻りたくなったら
colocateで使っていれば、.gitはずっとそこにあります。jjをやめたくなったら.jjディレクトリを消すだけで、元のGitリポジトリがそのまま残ります。
rm -rf .jj
この「いつでも普通のGitに戻れる」という後腐れのなさは、試しに導入するうえで大きな安心材料です。合わなければ消せばいいだけ、と思えば気楽に始められますね。
まとめ
GitユーザーがJujutsu(jj)を試す、という視点で、インストールから普段の操作の対比、リモート連携、そして撤退方法までひととおり見てきました。
あらためて振り返ると、jjの良さは「Gitの面倒だった部分が素直になっている」ところに尽きます。ステージングを意識しなくていい、間違えてもjj undoで戻せる、コンフリクトを抱えたまま先に進める——このあたりは、一度味わうと地味にクセになります。change IDで「あの変更」を安定して指せるのも、履歴を編集する作業をぐっと楽にしてくれます。
一方で、bookmarkやchange IDといった独自の概念には最初こそ戸惑いますし、GUIツールやエディタ連携のエコシステムはGitほど成熟していません。チーム全体の標準を置き換えるには、まだ早い場面も多いでしょう。
とはいえ、嬉しいのは Gitを捨てずに試せる こと。colocateで既存リポジトリに乗せて、合わなければ.jjを消すだけで元通りです。
「Gitのrebaseやindexまわりにずっとモヤモヤしている」「履歴を頻繁に編集する」という人ほど、刺さるツールだと思います。リスクはほとんど無いので、まずは普段使いのリポジトリでjj git init --colocateから、こっそり始めてみてはどうでしょうか。