忙しい開発者のための Jujutsu
(maddie.wtf)- Jujutsu(jj) は Git よりもシンプルな概念とコマンドでありながら、強力な機能を提供するバージョン管理システム
- Git をバックエンドとして使うため、同時に利用したり Git に簡単に戻ったりできる利点がある
- スタック型 diff、簡単な rebase、一時的なリビジョンといった機能が自然に提供される
- ブランチ (Branch) の代わりにブックマーク (Bookmarks) の概念を活用し、実務のワークフローにより直感的
- コンフリクト (Conflict) 処理の方法が柔軟で、日常的なバージョン管理作業をはるかに簡単にしてくれる
Elevator Pitch
Jujutsu(jj) は Git と比べて、はるかにシンプルなメンタルモデルとコマンドラインインターフェースを提供するバージョン管理システム。
機能を犠牲にしていないどころか、むしろより強力だと言えるかもしれない
スタック型 diff、容易な rebase、一時的なリビジョンといった機能が自然に動作する
バックエンドに Git を使うため、たった 1 行で Git リポジトリと並行して使い始められ、いつでも Git に戻れる
ほかの利用者によるリポジトリの扱い方にも影響しない
Getting Started
jjコマンドラインツールをインストールしたあと、ユーザー情報とシェル補完の設定を推奨- 既存リポジトリに適用する場合は、新しく clone するか、変更がない状態で使い始めるのが望ましい
- 既存リポジトリで
jj git init --colocate .コマンドを実行すると、Git と Jujutsu のリポジトリを並行して作成できる jjリポジトリのデータは Git の.gitとは別の.jj/フォルダーに保存される- Git と Jujutsu 間のデータ同期は基本的に問題なく運用できる
- ただし、
git clean -fdxコマンドは.jj/フォルダーを削除してしまうため注意が必要 - remote branch のトラッキングもそのコマンドで一度に設定できる
How To Use It
Jujutsu の コマンドラインインターフェースは Git よりも範囲が狭く簡潔
基本概念はシンプルだが、作業フローには少し慣れが必要
簡単な手順と例で主要な使い方を説明する
Starting A New Revision
- Git で新しい作業を始めるときは通常ブランチ作成から始まるが、Jujutsu では新しいリビジョンを作る方式
- 最新のリモートリポジトリの状態を反映:
jj git fetch - リポジトリ履歴を確認:
jj log - 履歴内のリビジョンは固有のJujutsu 用リビジョン IDとGit コミットハッシュで区別される
- 新しい作業の開始は
jj new mainで現在の main の上に新しいリビジョンを作成 - 現在編集中のリビジョンは
@記号で区別される - 同じ接頭辞がなくなると、リビジョン ID の短い接頭辞もそれに応じて変わる
Making Changes
- ファイルを修正すると、即座にそのリビジョンに含まれる(別個の staging area はない)
- 修正が完了したら
jj describe -m "メッセージ"でリビジョンにメッセージを追加 - 新しいリビジョンを作るときは
jj new(または前のリビジョンを指定) jj commitはjj describe+jj newの組み合わせ操作
Navigating
jj newで作成した空のリビジョンは移動時に自動で破棄される- 明示的にリビジョンを削除:
jj abandon <rev ID> - 削除を取り消す:
jj op undo - 既存のリビジョンに戻るには
jj edit <rev ID> - リビジョン参照時には revset expressions も使用可能:
@: 現在のリビジョンx-: 親リビジョンy+: 子リビジョンz::: z のすべての子孫
Branches (Bookmarks)
- Jujutsu には Git のような「ブランチ上に留まる状態」そのものがない
- その代わり、ブックマーク (Bookmarks) は特定のリビジョンを指すポインターにすぎない
- 新しいブックマークを作成:
jj bookmark create <名前> -r <リビジョン> - コミットしてもブックマークは自動では移動しないため、必要なら
jj bookmark move <名前> --to <リビジョン>で別途移動が必要 - push 時には
--allow-newフラグでリモートの新規ブランチ作成を許可 - ブックマークがリモートと異なる場合は
*で表示され、jj git pushで同期できる
Conflicts
- Jujutsu の コンフリクト処理の方法は Git より柔軟
- コンフリクト発生時は、リビジョンおよびその子孫リビジョンに
conflictedの印が表示される - コンフリクトしたファイルにはコンフリクトマーカーが挿入され、それを削除することで解決する
- 直接修正することも、新しいリビジョンで変更したあと
jj squashでまとめることもできる
結論
以上、Git で最もよく使われる 80% の作業を、Jujutsu でもっと簡単に処理する方法を紹介した
今後は一般的な活用法や高度な活用法を、クイックリファレンス形式で提供する予定
Git と同様に jj status コマンドもサポートしている
問い合わせやフィードバックは本文中のメールで受け付けている
1件のコメント
Hacker News の意見
jj を学ぶ価値があるか迷っている人に強調したい点がある
Hacker News で jj の話をすると、まだ試していない人と強く勧める人の二派に分かれる
1週間 jj を使ったあとで git に戻る人はほとんど見たことがない
本気で使ってみた人のほとんどは jj に定着している
今日がまさに jj を試すきっかけになればと思う
思っているより移行はずっと簡単で、私はその日から生産性を維持できたし、1週間以内に git コマンドへ戻る必要はまったくなかった
短時間でより生産的になり、以前どうやって git を使っていたのか不思議なくらいだ
私は git が生産性の面でまったく不便だとは感じていない
git コマンドを毎日長時間使うわけでもなく、たいていの場合は作業に支障がない
リポジトリを git で頻繁にいじる必要があるなら jj のほうがよいかもしれないが、私には当てはまらない
たとえば、vim ととても似ているが別の機能が追加された bim をとにかく試せと言われるのに近い
でも「i」を数回多く打てることに興味はないし、julia の自動補完も必要ない
jj がより良いという人は楽しめばいいと思う
jj は VCS の UI として確かに進歩しているが、git の上級ユーザーにとってはいくつか制約がある
gitattributesをサポートしていないため、git-crypt や git-lfs のようなフィルタが必要だと不便だ改行コード処理など、Windows での互換性も落ちることがある
また、git-annex や git-bug のような外部ツールは oplog と統合されず、履歴を散らかす可能性もある
私は実際に1週間以上使ってから git に戻ったことがある
個人的には staging area のワークフローのほうが好きで、多くの人は git の staging を嫌うけれど、私は jj でそれを捨てるほどの利点は感じなかった
習慣は変えられるだろうが、まだ jj にあえて縛られる気はない
いつかまた試す気持ちはある
数か月 jj を使ってから git に戻った理由はパフォーマンスの問題だった
jj の操作には数秒かかり、git はプロジェクトの大きさに関係なく即応だった
それに jj を使うと少しだけ精神的に複雑になる感じがして、git に戻るとずっと軽く感じた
これは私が長年 git を使ってきた経験のせいかもしれない
「どうせ二派しかいない」と言うこと自体が、jj 伝道師の典型的なセリフに思える
jj で気が狂いそうなのは、変更が無条件で自動 staging されることだ
昔の SVN 体系がそうだったし、git は明示的に staging することでずっと改善されたと感じる
リポジトリには常に次のコミットに入れたくない変更も残っているもので、次のコミットに入れたいものだけを選ぶのは git では当然だが
jj(SVN も含む)では、コミット前の変更を一時的に外へ退避させるなど、面倒な手作業が必要になる
この問題は私にとってもジレンマだ
staging はいつも面倒で、Mercurial を使っていたときも大変だった
90%以上のケースで自動追加が必要ならむしろ便利だが、
.gitignoreに入れられないファイルが問題になるauto add を切っても不便だし、入れても微妙だ
どちらも完全にきれいではない
JJ のワークフローは少し違っていて、たとえばベースコミットを先に作り、その上に無名コミットを積み上げて作業する
準備ができたら
jj squashやjj splitで整理することになる私は
jj commit -iや各種コマンドの-iオプション、それに config のsnapshot.auto-track="none()"を使っているMercurial を使っていたときも似たような感じだった
実際には absorb 機能を多用すると不要なファイルやチャンクは自動で無視される
「jj new」コマンドが欲しいワークフローに合っているのでは?
jj はツリー上で head ではないブランチもきちんと追跡するので、rebase や merge が stash なしでスムーズに動く
自動で変更が追加されることに慣れるのは簡単ではない
ローカルで特定のファイルはコミットするつもりがなく、開発中に一時的に変更することがあるが
git では staging しない限り絶対に commit/push されないので安心できる一方
jj では何かを unstage したり、注意したりする必要がありそうに感じる
習慣の問題かもしれないが、私は自分がコミットするものだけを明確に指定するほうが楽だ
もしかして私が jj を誤解しているのだろうか
jj では
jj splitで変更を分けられる選んだものは最初の revision に、残りは2つ目の revision に整理される
複数の作業を一度にして、それをひと口サイズの revision に分けるなら git よりずっと楽だ
git のように新しい revision(
jj new)を作って変更し、その後jj squash -iで本当に欲しい部分だけを移せる概念的には「@」が現在の revision、「@-」が1つ前なので、git の working copy / stage のように考えればよい
jj の自動ステージングが常によいとは限らない
jj の公式ドキュメントや入門者向け資料で、デフォルト設定を簡単にオフにできることをもっと明確に伝えてほしい
~/.jjconfig に
[snapshot]
auto-track = "none()"
と書けばよい
私は主に作業後に
jj splitでコミットする部分をレビューして整理しているこのワークフローは git の
add -pに近い動き方をする一番上のコミットは通常 push しない working copy のように使える
stash なしでブランチを切り替えても、進行中の内容はしっかり保持される
そう、その習慣は私にとっても簡単には変えられない
変えるべきだという主張の合理的な根拠は、untracked 状態でコードを走らせるべきではないという点だ
意味のある変更はコミットに残し、それ以外は記録しないほうが安全だ
問題は、リポジトリ全体の設定とローカル設定が分離されていないときで、VSCode が代表例だ
こういう場合は環境ごとにコミットしてはいけない変更が必要になり、さらに複雑になる
この種のワークフローが欲しいなら、jj の「@」を git の index のように使い、コミット時に「@-」へ squash すれば、git add --patch に近い効果が得られる
チームを jj に切り替えようとしているが難航している
git の複雑な作業が jj だとどれほど簡単になるかを一目で見せるページがあるとよさそうだ
エレベーターピッチのように簡潔に
自分でデモして見せる必要もありそうだと思っている
多くの人が日常的に行う git の作業は、jujutsu で特に簡単になるわけではないように思う
むしろ git では不可能だったり面倒だったりする新しいワークフローが jujutsu にはある
これは既存の git に慣れた開発者には響きにくいかもしれない
私も git が特別好きなわけではない
昔は mercurial しか使っていなかったが、今では git がすっかり頭に染みついている
Jujutsu に明確な利点があるとしても、初期セットアップ(複雑な色設定や慣れたスクリプト設定など)まで気を配るだけの魅力はまだ感じていない
私は jj と git の代表的な作業を比較した記事が理解の助けになった
https://lottia.net/notes/0013-git-jujutsu-miniature.html
役に立ちそうな良いリンクも勧めておく
https://v5.chriskrycho.com/essays/jj-init/
https://v5.chriskrycho.com/journal/jujutsu-megamerges-and-jj-absorb/
https://ofcr.se/jujutsu-merge-workflow
この投稿に近いうち追加する次の内容は、たぶん気に入ると思う
ページ下部と別投稿としてまもなく公開する予定だ
数週間 jj を使って、自動コミットメッセージ用のスクリプトまで作った
今日、長年使っている git リポジトリに同じスクリプトを移植しようとして、git では commit と amend のタイミングを自動判別するコードが必要だと気づいた
jj は immutable なコミット構造のおかげでスクリプトが非常に簡単だった
結局そのリポジトリを jj で新しく clone した
commit と amend の混乱から一瞬で解放された
https://codeberg.org/jcdickinson/nix/src/branch/main/home/common/scripts/jj-auto.fish
git は十分うまく動いている
学習曲線が少しあるだけで、いったん覚えればすべて理解できる
正直、95% の状況では pull、add、reset、branch、commit くらいを知っていれば十分だ
私のワークフローでは stacked diff が必須だ
レビュー待ちで詰まらずに、先の作業まで積み上げておけるのがよい
git でセットアップ自体は難しくないが、スタックの下のほうに変更を追加して全体を restack するのは本当に地獄だ
共同作業では rebase/squash の理解が必須だ
チーム全体に使わないよう説得するまでは避けられない
特に複数人が同じブランチで急いで開発すると、git の習慣は不便に感じられる
基本的には便利だが、複雑な状況では問題が多い
変わった状況を少し経験するだけでも git の欠点は見えてくる
jj を使い始めて2週間ほどだが、初めてコマンドラインだけで快適にバージョン管理できている
git を使っていたときは常に GUI(VSCode の Git Graph)で右クリック中心だったが
jj はチュートリアルを読むだけで、すぐ一貫したコマンドライン運用に入れた
ただし、jj のログで id を何度もコピペしないようにするには revset 言語を学ぶ必要がありそうだ
私も似たような状況だ
git を長く使ってきたが、複雑なことはいつも UI で処理していた
1か月近く jj CLI だけを使っていて、かなり満足している
ただし jj の公式ドキュメントは前提知識があることを想定して書かれているので、初心者には混乱しやすいことがある
ブログやチュートリアルがもっと増えてほしい
私も revset 言語と rebase をもう少し学びたい
簡潔な CLI、競合解決、oplog が気に入っている
私も id のコピペが不便だったが、jjui(https://github.com/idursun/jjui)を使ってからずっとスムーズになった
ログをなぞるような感覚で素早く作業できる
jj の最高の機能は undo だと思う
git では undo はミスの種類ごとにコマンドが分かれていて難しい
jj は
jj undo一発で済むバックエンドシステムにも縛られないので、ローカルで一人で jj を使っていても同僚には影響しない
jj が何なのか混乱している
git に混乱する人向けのフロントエンドなのだろうか
バックエンドを抽象化すると言うが、git に強く影響されすぎていて、Perforce や Piper のように連番コミットが強制されるシステムではうまく想像できない
working copy = commit という設計では品質管理ができないと思う
コミットの本来の意味は、本当に意図したものだけを載せることなのに、この構造では「ゴミコミット」を見分けるのがさらに難しくなる
git も jj も大規模プロジェクトには弱く、コミット乱立を防げない点は改善されていないように見える
jj が実際にどんな問題を解決するのか気になる、単に作者の好みを反映したフロントエンドなのだろうか
Piper バックエンドのほうが、むしろ Git バックエンドより自然に動く
jj のコミットは git のコミットと完全に対応するものではないので、誤解しなくてよい
実際に「コミット」と言うときは「名前付き diff」という概念に近く、push 前の変更はいつでも簡単に作り直して整理できる
git の rebase や履歴編集よりずっと楽だ
私は実験やドキュメントなどのコミットを作業の途中でいくつも作るが、git なら stash や一時ブランチに無理やり入れていたはずだ
jj は git フロントエンド以上のものだ
バージョン管理システムそのもので、backend-agnostic だ
代表的な backend が git なので、既存の repo でそのまま切り替えられる
私は GitHub が出る前から git を使っているユーザーだが、jj を使ったらもう git には戻れない
jj はよりシンプルでありながら強力だ
working copy = commit は、実際には「index も1つのコミットである」と考えるとよい
たとえば feature x の作業を始めるとき、
jj new -m "working on feature x" trunkで新しいコミットを作り、その上に空のコミットをもう1つ積む作業内容は working copy(@)に入り、それを前のコミット(@-)へ「移す(squash)」ことで、git の add-p や reset など複雑なオプションの代わりに、コミット間 diff ですべてを扱う
この構造のおかげで、git の index より柔軟かつ強力にコミットを分割・整理できる
コミット乱立の問題は pull request 文化により近く、jj でもある程度しか解決できない
working copy が GitHub にそのまま push されるわけではなく、コミット説明を付けるときにレビューや整理をすればよい
jj を数日使ってみたが、既存の lazygit と自分のワークフロー、スクリプト設定に十分満足している
jj は新鮮で悪くないが、これからバージョン管理を始める人でもない限り、わざわざ乗り換える動機はあまりない
今でも Lazygit の diff は使っているが、detached HEAD 状態でもまったく問題ない
JJ は git と高い互換性があり、複雑なコマンドを覚えなくてもずっと簡単に何でも処理できる
ブランチ間を移動するとき、未コミットのファイルが自動で一緒に移動するのは最高の機能だ
開発、バグ修正、文言変更などを行き来しながら作業するのがとても楽になる
AI エージェントに変更作業をさせるなら、worktree を分けて使えばよい
作業完了前に変更の説明(=コミットメッセージ)を付けてツリー上で先に確認できるのも本当に良い
JJ は全体としてかなり素晴らしい