Open70

次世代バージョン管理システム jj を勉強するスレ

hymkorhymkor

(2024-02-03: 追記) 本内容は、Book: 君のレポジトリを領域展開 - 次世代バージョン管理システム Jujutsu の世界 にまとめなおしました


ちょっと前にマストドンかどこかで、存在を知った次世代バージョン管理システム jj (Jujutsu-呪術)について勉強している。

日本語の解説ページが見つからなかったため、英語のレポジトリのドキュメントをブラウザの翻訳アドオンを使って読まざるを得ない。

  • 開発者の martinvonz 氏はGoogle所属。Google でも使われている
    • ググラビリティーが低くて困る。呪術廻戦ばかりヒットする(Goといい、Google はいつもそうだ)
  • Rust 製のワンバイナリ。Windows 対応するにあたって、Git のように MSYS をまるごと梱包するような大掛かりなプロダクトにせずに済んでいる。
    • scoop install jj でもインストールできるようになった。
  • Git と互換。レポジトリは独自方式(.jjディレクトリ)だが、リモートブランチとして GitHub の git レポジトリを指定できる。
  • コミット本体の他、操作そのもの履歴をもっている。Git や Mercurial だと直前の操作によって、その取り消しの方法が違うが、jj は jj op undo で統一されている。
  • やや Mercurial に近い
    • いちいち add しなくてもよい
    • また、ワークフォルダーが最新コミット扱いとなっているらしい
      • が、まだ詳しくは分かっていない。どうもコンフリクトを避けるための工夫らしい
    • 無名ブランチに相当するものがある

jj git clone で、git レポジトリのクローンが出来るみたいなので、チュートリアル の和訳を読みながら、試行錯誤してゆくしかないか

hymkorhymkor

ドキュメントでは jj git clone https://github.com/octocat/Hello-World で Git レポジトリをクローンできるとあるが、https でクローンすると今度は push する時、エラーになるのではないかと思い、ssh 認証で clone を試みる

$ jj git clone git@github.com:hymkor/go-htnblog.git
Fetching into new repo in "\\?\C:\Users\hymkor\go-htnblog"
Error: Failed to authenticate SSH session: ; class=Ssh (23)
Hint: Jujutsu uses libssh2, which doesn't respect ~/.ssh/config. Does `ssh -F /dev/null` to the host work?

なんか怒られた。ちょっと分からんので、後回し。とりあえず jj git clone https://github.com/hymkor/go-htnblogでクローンする。

hymkorhymkor
  • Git や Mercurial ではコミットログは、コミットのタイミングで書くが、jj では変更を行っている最中に書け、しかも何回でも書き直せるらしい( jj describe )。
    • これは分かる。変更内容は早く書いておかないと、人は忘れる。
  • 変更を終了する時は jj new というコマンドを発行するらしい。commit じゃないんだ!

jj -h で確認すると commit も存在していて「commit Update the description and create a new change on top [aliases: ci]」となっている。つまり、jj commit == jj describe + jj new というわけか!

hymkorhymkor

コミットログは jj log

$ jj log
@  srzrvzxt iyahaya@nifty.com 2024-01-23 02:28:38.000 +09:00 2a57cc70
│  README.md: append the describe to the install section
◉  usuwytps iyahaya@nifty.com 2024-01-21 12:18:12.000 +09:00 master d2ce65ac
│  release_note.md: wrote about category
~

操作ログは jj op log

$ jj op log
@  db7d78867c93 hymkor@desktop-npotg52 7 hours ago, lasted 4 milliseconds
│  edit commit 2a57cc704d7a364faeb6c1eaf15da3eb7b507187
│  args: jj edit srzrvzxt
◉  e99bdd132f32 hymkor@desktop-npotg52 7 hours ago, lasted 22 milliseconds
│  squash commit 68e1728e5675e9a91b11c22790f4395cfee99616
│  args: jj squash
◉  fbdea27a4d16 hymkor@desktop-npotg52 7 hours ago, lasted 25 milliseconds
:略

履歴の ID が二つならんでいて混乱する。

  • 左のものが「変更 ID」。
  • 右のものが「コミット ID」。書き換えられると変わってしまう

どちらを指定してもよいらしいが、通常は変更IDを使うことが多いらしい(よく分かっていないので「らしい」が連続する)

hymkorhymkor

Git とだいたい同じコマンド

  • jj status … Mercurial みたいに短縮形の名前がある。git st
  • jj restore
  • jj diff … コミットの指定に -r というオプションが必要(Mercurial みたいだ)。操作ID、コミットIDの指定には @~1 みたいなものは使えないが、短縮指定ができるらしい(jj log で表示されるIDの強調部分が短縮指定で使える名前)。あと、色ベースで差異が表示されるので、解説記事では引用しにくい。そういう場合は、jj diff --git みたいに Git 互換表示

jj new でコミットを終了させた後も、またやり直しが出来る

  • jj squashgit commit --amend に似てる
  • jj edit … 終了したコミットを再編集できる(まだ試してない)
hymkorhymkor

jj には index はなく、Mercurial のようにすべての変更を記録する。が、記録してほしくないファイルもある。自分は mklink /J dist %USERPROFILE%\OneDrive\Documents\dist\htnblog という風にジャンクションをワークフォルダー直下に作成し、GitHub の releases ページにアップロードした zip ファイルなどを移動させたりするが、jj はこの dist を作った記録をつけてしまうので、どうしたもんだか悩んでいた。

結論としては .gitignore を作成すると、jj もその中の無視パターンを認識するらしい。なんぞそれ…

ただし、.gitignore は過去に記録されたファイルに遡及的に無視できない。無視できるのは .gitignore を更新した後のファイルとなる。つまり

$ echo dist > .gitignore
$ mklink /J dist %USERPROFILE%\OneDrive\Documents\dist\htnblog
$ jj st
Working copy changes:
A .gitignore
Working copy : zrvlyqkp 5e518392 (no description set)
Parent commit: srzrvzxt 2a57cc70 README.md: append the describe to the install section

→ 期待どおりの動作

$ mklink /J dist ~\OneDrive\Documents\dist\htnblog
Junction created for dist <<===>> C:\Users\hymkor\OneDrive\Documents\dist\htnblog
$ jj st
Working copy changes:
A dist
Working copy : zrvlyqkp 4534918b (no description set)
Parent commit: srzrvzxt 2a57cc70 README.md: append the describe to the install section
$ echo dist > .gitignore
$ jj st
Working copy changes:
A .gitignore
A dist
Working copy : zrvlyqkp 7ed0f3eb (no description set)
Parent commit: srzrvzxt 2a57cc70 README.md: append the describe to the install section

→ ダメー

これ、個別の .gitignore に書くのではなく、PCグローバルな gitignore に書いた方がいいかもしれませんな。グローバルな gitignore は明示的に場所を指定しなくてはいけないらしい。~/.gitconfigに

[core]
        excludesfile = C:/Users/hymkor/.gitignore

と書いてから、~/.gitignore に

dist
*.zip

を記入した。このやり方でもジャンクション dist は登録対象から含まれないようになった。

hymkorhymkor

GitHub への push の仕方が分からない

$ jj st
The working copy is clean
Working copy : zrvlyqkp 408806ec (empty) (no description set)
Parent commit: srzrvzxt 2a57cc70 README.md: append the describe to the install section
$ jj git push
No branches point to the specified revisions.
Nothing changed.
$ jj git push -c srzrvzxt
Creating branch push-srzrvzxtzuyz for revision s
Branch changes to push to origin:
  Add branch push-srzrvzxtzuyz to 2a57cc704d7a
Username for https://github.com/hymkor/go-htnblog: hymkor
Passphrase for https://github.com/hymkor/go-htnblog: :
Username for https://github.com/hymkor/go-htnblog:

-c オプションで ID を指定すれば push しようとはするが、認証が通らない。やはり、https で clone したせいだろうか

hymkorhymkor

2種類の方法が候補

$ git credential-manager get
protocol=https
host=github.com

これで一応できるようになったみたいだ(が、master ではなく、push-xxxxxx というブランチになってしまった。リモートブランチ名を指定しなければいけなかった)

  • ssh-agentに鍵を読み込ませる

Windows 10 で SSH Agent を使用する - メモ用紙 に従えばよさそうだが、時間がなくて、まだできていない。

hymkorhymkor

$ jj git push -h
Push to a Git remote
:(中略)
  -c, --change <CHANGE>        Push this commit by creating a branch based on its change
                               ID (can be repeated)

「ChangeID をベースとしたブランチを作ることで、コミットを push する」ってしっかり書いてあるじゃあないか。馬鹿か、俺は

hymkorhymkor

Working with GitHub - Jujutsu docs

あー、ローカルブランチの先端を更新するかのような操作をしないといけなかったのか

$ jj branch set master -r @-
$ jj log
@  vtmszomk iyahaya@nifty.com 2024-01-24 02:09:16.000 +09:00 85780220
│  (empty) (no description set)
◉  mwlxturo iyahaya@nifty.com 2024-01-24 02:08:31.000 +09:00 master* fd091d1c
│  README.md: inserted `Non standard functions`
◉  srupqnto iyahaya@nifty.com 2024-01-20 03:49:36.000 +09:00 master@origin 91690e70
│  README.md: rewrite the support function list to follow the table of contents of ISLisp's Working Draft.
~
$ jj git push --dry-run
Branch changes to push to origin:
  Move branch master from 91690e70a5fc to fd091d1cb4d7
Dry-run requested, not pushing.

@- は現在のコミットの一つ前を指す ID のエイリアスか。だけどオプションが -c だったり -r だったり、省略してよかったり、ややこしいな

hymkorhymkor

うまくいった。

$ git credential-manager get
protocol=https ← 入力
host=github.com ← 入力
^Z
info: please complete authentication in your browser...
protocol=https
host=github.com
username=hymkor
password=(httpsでpushする時に入力するワンタイムっぽいパスワード)

$ jj git push
Branch changes to push to origin:
  Move branch master from 91690e70a5fc to fd091d1cb4d7
Username for https://github.com/hymkor/gmnlisp: hymkor
Passphrase for https://github.com/hymkor/gmnlisp: :

ちゃんと、リモートの master ブランチに push されてた!

hymkorhymkor

最初にやっておかないといけない設定について書くのを忘れていた。

jj config set --user user.name 名前
jj config set --user user.email "メールアドレス"
hymkorhymkor

Tutorial and Birds-Eye View - Jujutsu docs

The @ indicates the working-copy commit

@ は現在のワーキングコピーのコミットを指す

here are also operators for getting the parents (foo-), children (foo+), ancestors (::foo), descendants (foo::), DAG range (foo::bar, like git log --ancestry-path), range (foo..bar, same as Git's)

→ 親(foo-)、子(foo+)、先祖(::foo)、子孫(foo::)、DAG範囲(foo::bar)

それで @- が現在のワーキングコピーの親になるわけか!(チュートリアルを全部最初から読まず、知りたいところからつまみ食いしているから、ところどころいっぱい分かってなかった)

hymkorhymkor

git add 的な操作は不要だが、この修正は今のコミットに含めたくないという場合は、jj split - i で現在のワーキングコピーの内容を、その親と新しいワーキングコピーの二つに分けるという形になるらしい。

FAQ - Jujutsu docs # Can I interactively create a new commit from only some of the changes in the working copy, like git add -p && git commit or hg commit -i?

hymkorhymkor

3ファイルだけあるレポジトリの2つだけのコミットを作りたくて jj split(引数なし)を実行してみた。

C:> jj init --git
C:> copy ~/src/go-startup-source/{.gitattributes,LICENSE} .
C:> copy ../gmnlisp.jj/tagdesc.ps1 .
C:> jj split

おぉ、かっこいい。だが、キーバインドがわからん。試行錯誤してみた結果:

  • q Ctrl-C 破棄して終了
  • j 下へ移動
  • k 上へ移動
  • f, Enter 展開状態・折りたたみ状態をトグル
  • l 展開する
  • h 折りたたむ
  • c 分離する二つのコミットのログを作成して終了
hymkorhymkor

これ、l で展開すると、ソース内の「行単位」で選択できるのが、地味にすごいですねぇ

hymkorhymkor

あ、自分が書く Makefile の定番コード:

VERSION:=$(shell git describe --tags 2>$(NUL) || echo v0.0.0)

が効かなくなるのは痛いなぁ

hymkorhymkor
$ git --git-dir=.jj/repo/store/git tag
v0.1.0
v0.1.1

(略)

v0.9.0
$ git --git-dir=.jj/repo/store/git describe --tags
fatal: Not a valid object name HEAD

おしい、残念

hymkorhymkor

タグの一覧に限っていうと、v0.14 から

C:> jj tag list

で出せるようになったので、 git --git-dir=.jj/repo/store/git tag は使う必要がなくなった(だが、タグはまだ作成できない)

hymkorhymkor
$ jj.exe st
Error: Failed to snapshot the working copy: New file \\?\C:\Users\hymkor\src\go-htnblog.jj\htnblog.jj.exe of size ~5.4MiB exceeds snapshot.max-new-file-size (1.0MiB)
Hint: Increase the value of the `snapshot.max-new-file-size` config option if you
want this file to be snapshotted. Otherwise add it to your `.gitignore` file.

デフォルトだと 1MB までしかワーキングコピーの中のファイルして扱えないのか。しかし、それで jj status すら動かなくなるのは、ちょっとどうなんかなぁ

hymkorhymkor
$ jj log
@  mxstwpln iyahaya@nifty.com 2024-01-25 05:32:47.000 +09:00 5a01de7e
│  (empty) (no description set)
◉  vwssuxqk iyahaya@nifty.com 2024-01-25 05:32:22.000 +09:00 dbe249f0
│  htnblog.exe: prevent from refering $EDITOR twice to edit draft
│ ◉  tpvpryrw iyahaya@nifty.com 2024-01-23 15:39:25.000 +09:00 master 6a372369
╭─┤  (empty) Merge pull request #1 from hymkor/push-srzrvzxtzuyz
│ │
│ ~
│
◉  srzrvzxt iyahaya@nifty.com 2024-01-23 02:28:38.000 +09:00 2a57cc70
│  README.md: append the describe to the install section
~

意図せず分岐してしまった。どうするか

hymkorhymkor

jj new -A masterjj move --from ~ --to ~ で直った。
(コンフリクト要素がなかったので、移動だけで済んだ)

$ jj new -A master
Working copy now at: yymmusnx a00d71c2 (empty) (no description set)
Parent commit      : tpvpryrw 6a372369 master | (empty) Merge pull request #1 from hymkor/push-srzrvzxtzuyz
Added 0 files, modified 1 files, removed 0 files

$ jj
Hint: Use `jj -h` for a list of available commands.
Set the config `ui.default-command = "log"` to disable this message.
@  yymmusnx iyahaya@nifty.com 2024-01-25 05:52:38.000 +09:00 a00d71c2
│  (empty) (no description set)
◉    tpvpryrw iyahaya@nifty.com 2024-01-23 15:39:25.000 +09:00 master 6a372369
├─╮  (empty) Merge pull request #1 from hymkor/push-srzrvzxtzuyz
│ │
│ ~
│
│ ◉  vwssuxqk iyahaya@nifty.com 2024-01-25 05:32:22.000 +09:00 dbe249f0
├─╯  htnblog.exe: prevent from refering $EDITOR twice to edit draft
◉  srzrvzxt iyahaya@nifty.com 2024-01-23 02:28:38.000 +09:00 2a57cc70
│  README.md: append the describe to the install section
~

$ jj move --from vwssuxqk --to yymmusnx 
Working copy now at: yymmusnx 7e60edf3 htnblog.exe: prevent from refering $EDITOR twice to edit draft
Parent commit      : tpvpryrw 6a372369 master | (empty) Merge pull request #1 from hymkor/push-srzrvzxtzuyz
Added 0 files, modified 1 files, removed 0 files
<DESKTOP-NPOTG52:~/src/go-htnblog.jj>
$ jj log
@  yymmusnx iyahaya@nifty.com 2024-01-25 06:21:27.000 +09:00 7e60edf3
│  htnblog.exe: prevent from refering $EDITOR twice to edit draft
◉  tpvpryrw iyahaya@nifty.com 2024-01-23 15:39:25.000 +09:00 master 6a372369
│  (empty) Merge pull request #1 from hymkor/push-srzrvzxtzuyz
~
hymkorhymkor

git describe --tags に相当する動作が jj で直接実行できない件、jj log -r (revset) をスクリプトで書こうすることで代替できそうだ。

4.4.14_0-54-g6a7de652 のような文字列、つまり (直近のタグ)-(そのタグからの距離)-(ハッシュ) が作れればよい。revset は jj log などで、対象となるリビジョンを指定するためのミニ言語だ。

tags() はすべてのタグを持つリビジョンの集合。latest(tags()) でその中で最も新しいリビジョンを得る。それに「それ以降のリビジョン」を表す :: をくっつけて latest(tags())::-r オプションで指定すれば、現在のリビジョン~最新タグの履歴が全部出る。

C:> jj log --no-graph -r "latest(tags())::"
vtmszomk iyahaya@nifty.com 2024-01-29 01:01:42.000 +09:00 808c852d
(no description set)
mwlxturo iyahaya@nifty.com 2024-01-24 02:08:31.000 +09:00 master fd091d1c
README.md: inserted `Non standard functions`
srupqnto iyahaya@nifty.com 2024-01-20 03:49:36.000 +09:00 91690e70
README.md: rewrite the support function list to follow the table of contents of ISLisp's Working Draft.
:(中略)
qmoxpurp iyahaya@nifty.com 2023-10-01 16:27:32.000 +09:00 v0.4.1 de6c980a
bump to v0.4.1

1リビジョンは2行一組で表示される。2行目は要らないので捨てればよく、1行目のフィールドが7個ある行は6フィールド目にタグが入る。そのタグから何リビジョン過ぎているかはそれまでのリビジョン数を gawk なり PowerShell なりでカウントすればよさそうだ。

hymkorhymkor

PowerShell で書いてみた。

$count = 0
jj log --no-graph -r "latest(tags())::" | ForEach-Object {
    $count++
    if ( $count % 2 -ne 0 ){
        $fields = ($_ -split " +")
        if ( $count -eq 1 ){
            $current = $fields[0]
        }
        if ( $fields.Length -eq 7 ){
            if ( $count -eq 1 ){
                $result = $fields[5]
            } else {
                $result = ("{0}-{1}-{2}" -f $fields[5],$count,$current)
            }
        }
    }
}
Write-Output $result
C:> tagdesc.ps1
v0.4.1-41-vtmszomk
hymkorhymkor

git describe --tags の出力は

  • 今のリビジョンにつけられたタグがあれば、そのタグを
  • なければ (直近のタグ)-(そのタグからの距離)-(ハッシュ)

を出力する。後者についてはテストできた。さて、前者をテストしようとしたが、今度は jj でタグをつける方法が分からない(ない気がする)。やれやれ

hymkorhymkor
C:> git --git-dir .jj/repo/store/git tag
v0.0.0
v0.1.0
:
v0.4.1

がいけるのならば…

C:> git --git-dir .jj/repo/store/git tag tmp
fatal: Failed to resolve 'HEAD' as a valid ref.

ダメだった。

hymkorhymkor

PowerShell だと、Linux 上で使えないので、go run tagdesc.go で使える Go バージョンも作成 → hymkor/tagdesc

$ copy ../tagdesc/tagdesc.go .
../tagdesc/tagdesc.go -> tagdesc.go
$ go run tagdesc.go
v0.4.1-41-vtmszomk
  • ヘッダに //go:build run という行を設定しているため、go build では認識されない。
  • .git/ ディレクトリがある場合は代わりに git describe --tags を内部で呼ぶので、jejutsu 管理されていない git 管理下でも使用可能
  • MIT LICENSE。ご随意にファイル単品でコピーして使ってください。
hymkorhymkor

tagdesc という名前だと jujutsu 関連だと分かりづらいので、jjtagdesc という名前に改めた。

リモートブランチの URL を変わった場合、そのままだとエラーになってしまうので、リモートブランチの URL の変更手続きの他、jj branch track master@origin というコマンドを発行しなければいけないらしい。

$ jj git remote remove origin 
$ jj git remote add origin https://github.com/hymkor/jjtagdesc.git 
$ jj branch set -r @- master
$ jj git push
Non-tracking remote branch master@origin exists
Hint: Run `jj branch track master@origin` to import the remote branch.
Nothing changed.
$ jj branch track master@origin
$ jj git push
Branch changes to push to origin:
  Move branch master from f1e94c3072ca to e2a522c7dbd1
hymkorhymkor

jj init から jj git push まで通せた。

C:> jj init --git
:(いろいろとコミット作る)
C:> jj git remote add origin https://github.com/hymkor/tagdesc.git
C:> jj branch create -r @- master
C:> jj git push
hymkorhymkor

※ v0.14.0 から、git と相互運用可能な形での初期化は jj init --git ではなく、jj git init となった。

hymkorhymkor

Release v0.14.0 · martinvonz/jj 来た。

  • scoop の main bucket には、まだ落ちてきていない(Feb.08 9:38 時点)
    → 更新された (コミット時刻は 9:32 だからいきちがった?)
  • jj init --git, jj init --git-repo が非推奨 → jj git init を使う
  • jj checkoutjj merge も非推奨らしいが、そこまで使いこなしてない(汗
  • タグを作るコマンドはまだだったよ(jj tag list が作られたので、そこに tag サブコマンド以下に作られる可能性)
hymkorhymkor

自動翻訳 with 少し手直し

jj init --git-repo=. を実行すると、Git は切り離された HEAD 状態 になりますが、これはあまり一般的ではありません。というのも Git は普通は主にブランチ上で動作するからです。(Gitと)同一位置にあるリポジトリでは、すべての jj コマンドによって、Jujuku のリポジトリのビューと Git のビューが自動的に同期されます。たとえば、jj commit は Git リポジトリの HEAD を更新し、段階的に移行を可能とします。

とりあえず、既存の Git のローカルレポジトリのあるディレクトリで jj init --git-repo=. を実行すると、jj に Git の既存のコミットがインポートされた形で初期化された。git も jj も一見それっぽく動くが、どういう制限があるんじゃろ。

hymkorhymkor

Gitの 切り離された HEAD 状態 とは

  • Git において、通常、HEAD はブランチ名を指している
  • ブランチ名以外で checkout した後、コミットを重ねると、HEAD がコミットを直接指す状態になる。これが「切り離された HEAD 状態
  • この状態でも、通常の Git 操作はすべて出来る。ただし、master を checkout すると HEAD が master を指すようになるが、旧HEADを指していた commit を参照するものが何もなくなってしまう。そのままだと Garbage Collection による削除対象となってしまう
  • HEADを特定コミットから移動させてしまう前に、タグを打つなり、ブランチ化するなりすれば、参照なしを避けることができる
  • 機械翻訳『commit f から離れた場合は、まずそのオブジェクト名を (通常は git reflog を使用して) 回復する必要があります。その後、それへの参照を作成できます。たとえば、HEAD が参照した最後の 2 つのコミットを確認するには、次のコマンドのいずれかを使用できます。』
$ git reflog -2 HEAD # or
$ git log -g -2 HEAD
hymkorhymkor

jj init --git-repo=. のあと、git tag git2jj とタグを打つと、jj の中でも参照できるタグを作成することができた。

git tag -d git2jjgit describe --tags も機能している。うーむ、このまま jj でいろいろやって大丈夫なのかな?

hymkorhymkor

jj init --git-repo=. という初期化の仕方をすると、同じレポジトリで jj と git を共存・自動同期できそうに見えるのだが、何か問題はないだろうか。いきなり他人向けのテキストに起こすのは危険なので、実際に使いながら検証しないといけないなぁ

(ありえるパターンとしては「jj 側の操作は可能だが、git 側の操作は許されない」という可能性も考えられる。単に git から jj の移行をしやすくするだけという初期化の仕方)

hymkorhymkor

jj-init

--git-repo <GIT_REPO> — DEPRECATED: Use jj git init Path to a git repo the jj repo will be backed by

jj init --git-repo は非推奨だった。jj git init にパスをパラメーターとして与えるべきだった。

hymkorhymkor
$ jj init --git-repo=.
Done importing changes from the underlying Git repo.
Error: Failed to snapshot the working copy
Caused by: New file \\?\C:\Users\hymkor\src\csview\csview of size ~3.3MiB exceeds snapshot.max-new-file-size (1.0MiB)
Hint: Increase the value of the `snapshot.max-new-file-size` config option if you
want this file to be snapshotted. Otherwise add it to your `.gitignore` file.
exit status 1

「3メガバイト以上のファイルはワーキングコピーに加えられない」というエラー、どないしよう。前は .gitignore にファイルを登録したけど、snapshot.max-new-file-size を変更したい。が、設定が見つからない。

hymkorhymkor

ドキュメントを探したが、設定についての記述が見つからなかったため、雰囲気で

jj config set --repo snapshot.max-new-file-size 5MiB

を実行してみたところ、エラーが出なくなった。大丈夫か?

hymkorhymkor
jj config set --repo snapshot.max-new-file-size 5MiB

はすべきではなかった。これをやると、バイナリファイルがコミットファイルに含まれてしまう可能性が高まる。ローカルでコミットを編集できるうちはよいが、jj git push をやってしまうと、簡単に戻せなくなる

snapshot.max-new-file-size は 1MB のままにし、.gitignore の設定して、でかいファイルを含まないようにすべきだった。

hymkorhymkor

既存の Git 管理のローカルレポジトリをjj init --git-repo=. で jj 管理下にした状態で:

$ jj new
Working copy now at: vktkywvy f8b16e2e (empty) (no description set)
Parent commit      : mtpvrvqp 463ee09b bump to v1.1.3

$ git tag v1.1.3

$ jj log
Done importing changes from the underlying Git repo.
@  vktkywvy iyahaya@nifty.com 2024-02-16 21:19:18.000 +09:00 f8b16e2e
│  (empty) (no description set)
◉  mtpvrvqp iyahaya@nifty.com 2024-02-16 21:19:08.000 +09:00 v1.1.3 HEAD@git 463ee09b
│  bump to v1.1.3
~

$ git describe --tag
v1.1.3

やだ、期待どおり動いてる

hymkorhymkor
$ jj git push
No branches found in the default push revset, `remote_branches(remote=origin)..@`.
Nothing changed.

$ jj git remote add origin https://github.com/hymkor/csview
Error: Git remote named 'origin' already exists

$ git push
fatal: You are not currently on a branch.
To push the history leading to the current (detached HEAD)
state now, use

    git push origin HEAD:<name-of-remote-branch>

無理?

hymkorhymkor
$ jj branch set master -r @-

$ jj git push
Non-tracking remote branch master@origin exists
Hint: Run `jj branch track master@origin` to import the remote branch.
Nothing changed.

$ jj branch track master@origin

$ jj git push
Branch changes to push to origin:
  Move branch master from 9ef96ee97618 to 56955254915c

変更自体の push は出来た!(ブラウザでギッハブ側のコミットを確認済み)

hymkorhymkor

同ディレクトリにて:

$ git push --tag
Enter passphrase for key '******************************':
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:hymkor/csview.git
 * [new tag]         v1.1.3 -> v1.1.3

あ、タグの push も出来てしまった!(ブラウザでギッハブ側のタグも確認済み)

hymkorhymkor

操作を間違えて、バイナリファイルを push してしまった(これは jj が悪いわけではない)

  • ブラウザ側でタグ・リリースを削除
  • ローカルでタグを削除 git tag -d v1.1.3
  • ローカルでブランチを一旦削除 jj branch delete master
  • ローカルでリモートブランチを忘れさせる jj branch untrack master@origin

だが、肝心の push してしまったコミットを編集しようとすると:

$ jj edit n
Working copy now at: nylqnusn 75dbb3b1 Delete an executable file that were included by mistake
Parent commit      : qrmnlknl 56955254 master@origin | bump to v1.1.3
Added 0 files, modified 1 files, removed 0 files

$ jj squash
Error: Commit 56955254915c is immutable
Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`.

push したコミットは immutable にされるんですな(予想はしていた)

hymkorhymkor
$ git reset @~

$ jj log
Reset the working copy parent to the new Git HEAD.
@  ynyvvxly iyahaya@nifty.com 2024-02-16 22:14:47.000 +09:00 f9428bf5
│  (no description set)
│ ◉  vmkkksvo iyahaya@nifty.com 2024-02-16 22:14:46.000 +09:00 0fc9727e
│ │  (no description set)
│ ◉  qrmnlknl iyahaya@nifty.com 2024-02-16 21:35:41.000 +09:00 master@origin 56955254
├─╯  bump to v1.1.3
◉  vktkywvy iyahaya@nifty.com 2024-02-16 21:32:07.000 +09:00 HEAD@git 5294c556
│  Makefile: Update with recent styles
~

git コマンド側でなんとかできるかな?と思ったが、git が許しても、jj はどうかな? という状態になってしまった(壊れているわけではない)

hymkorhymkor

結局、別フォルダーで

  1. git clone
  2. cd ... && git reset @~
  3. git checkout -f
  4. (必要な修正を改めて Git として実行)
  5. git push -f

で、GitHub のコミットを削除してから、ローカルの jj 側レポジトリーで jj git fetch で GitHub の変更を取り込み、その後、immutable でないコミットを

  1. jj edit (ID)
  2. jj restore

を繰り返したところ、だいたい綺麗になった。やれやれ

(疲れたが、ハマりどころを一つ見つけられたのは収穫だった)

hymkorhymkor

GitHub へ push する際、毎回

C:> jj branch set master -r @-
C:> jj git push

するのが面倒くさい。エイリアスで一つにまとめられないかを見たが、1コマンドだけのようだ。結局、jj config edit --user

[alias]
sync-master = [ "branch" , "set" , "master", "-r" , "HEAD@git" ]
sync-main = [ "branch" , "set" , "main", "-r" , "HEAD@git" ]

を設定に追加し、 jj branch set master -r @- の代替に jj sync-master を使うという形で妥協した。引数やマルチステートメントは無いのかなぁ

hymkorhymkor

v0.15.1 がリリースされたが、gvim でコミットログが編集できなくなっちゃった。jj commit を実行すると、gvim がファイルなしモードで起動してしまう。gvim を終了すると、gvim がエラー終了したと jj が報告してくる。

環境変数 JJ_EDITOR に次のバッチファイル foo.cmd を登録して、実行してみたところ:

echo %1,%2,%3,%4,%5,%6
gvim "%1"

コミットログを書く一時ファイルのパスを \\?\C:\... 形式のパスで渡すようになったためのようだ。

  • コマンドラインから、同パスを gvim で起動すると症状は再現する
  • だが、メモ帳(notepad.exe)だと、問題なく編集できる

Windows では CON や AUX といったファイル名はディレクトリを問わずデバイスとして扱われるので、下手にアクセスするとアプリケーションがハングアップを起こしてしまう。だが、\\?\C:\... 形式だと、普通のファイルとみなしてくれる。だから、jj が一時ファイル名の取り扱いを変えたのは理解は出来るし、notepad で開けるファイルであるから gvim で開けない方が悪いといえば悪いんだが…難儀だなぁ

対応としては5種類:

  • \\?\C:\... を通常の形式に変換して、%EDITOR% を起動するラッパーを作成する
  • 日常的に使う jj を v0.14.0 に戻す
  • jj に issue 報告
  • vim 側で issue 報告
  • jj 使うの辞める

うーん

hymkorhymkor

とりあえず、こういうラッパーを書いて回避した

jj_edit.cmd :

@ setlocal
@ set "PROMPT=$$ "
set "TARGET=%~1"
set "TARGET=%TARGET:\\?\=%"
"%EDITOR%" "%TARGET%"
@ exit /b %ERRORLEVEL%

→ 環境変数 EDITOR には gvim.exe のフルパスを、環境変数 JJ_EDITOR の方に、このバッチファイルのフルパスを書く

hymkorhymkor

ダメ元で、jj の Discord に前の仕様に戻してもらうことは可能か?と投稿してみた。

hymkorhymkor

解決した!どうやら、\\?\C:\… 形式のパスに含まれるクエスチョンマークを、ワイルドカードと誤認してしまったらしい。結果、自前展開にトライした挙句、失敗したようだ。

vim に --literalオプションをつけて、ワイルドカード展開を抑制すればよかった。

jj config edit --user で以下の設定を追加した。

[ui]
editor = [ "C:/Users/hymkor/scoop/apps/vim/current/gvim.exe" , "--literal" ]

jj の discord にも報告したんだけど、なんでみんな 😱 なリアクション返すの?

hymkorhymkor

うーん、 jj split の表示が乱れて、使い物にならない時がある。
1行の文字数が多い時、次の行に折り返してしまうのが原因かと思われる。

git add -p -e みたいな取捨選択方式で回避できなくもないが、かなり面倒くさい。jj git init --colocate で初期化されたレポジトリであれば、

  • 最後の登録コミットを A 、現在の仕掛かりコミットBの状態で git add -p -e, git commit する
  • commit した部分は C 、commit していない部分を D とすると、次のようなツリーとなる
 A ┬ B
  └ C ─ D
  • jj edit D jj rebase -r B -d D とすると、A - C - D - B となるが、DとBは同じなので、B の差分は empty となる

ただ、これ、面倒くさいんだよな。ゆえに、本来は jj split --tool マージツール を使えるのが望ましい

hymkorhymkor

https://martinvonz.github.io/jj/v0.14.0/config/#using-vim-as-a-diff-editor

Using ui.diff-editor = "vimdiff" is possible but not recommended. For a better experience, you can follow instructions from the Wiki to configure the DirDiff Vim plugin and/or the vimtabdiff Python script.

機械翻訳いわく「ui.diff-editor = "vimdiff" の使用は可能ですが、推奨されません。より良いエクスペリエンスを得るには、Wiki の指示に従って DirDiff Vim プラグインや vimtabdiff Python スクリプトを構成できます。」

非常につらみがある。

  • \\?\ がワイルドカードとみなされてしまうので、--literal オプションをつけなくてはいけない
  • vimdiff はディレクトリ単位での比較ができなさそう (だから推奨されていないのでは?)
  • scoop でインストールした gvim はコンソールプロセスはすぐ終了して、GUIプロセスを別途起動するので、それを抑制するために -f オプションをつけないといけない
hymkorhymkor

コンソール画面を最大化して、折り返しをなるべくしないようにするかなさそう

hymkorhymkor

jj split で画面が乱れる件、いつからこうなったか分からないけど、現状使い物にならなくなっているので、これがずっと続くようであれば、さすがに使用をやめるのを検討せざるを得ない

hymkorhymkor

試しに 0.17.1 に戻してみる。scoop installer で古いバージョンに戻す方法がなかなか分からなかったが、どうやら scoop reset <package>@<version> とするらしい[1]

$ scoop reset jj@0.17.1
Resetting jj (0.17.1).
Linking ~\scoop\apps\jj\current => ~\scoop\apps\jj\0.17.1
Creating shim for 'jj'.

$ jj version
jj 0.17.1-e1d8705546d3971fef23bc6a4a7589283e5e0717

v0.18.0 の New Features の次の変更が関係しているのかもしれない

脚注
  1. 【Scoop】Appsのバージョンを切り替える | 一本の矢は折れやすい ↩︎

hymkorhymkor

v0.17.1 だと症状が出ない。

v0.17.1 と v0.18.0 の表示を見比べてみると、どうも、v0.18.0 の方はタブの扱いがおかしい気がする。v0.17.1 の方はインデント部分をカットしているが、v0.18.0 の方はタブを表示しているものの、その桁数分、画面の右端を突き抜けている感じがする

v0.17.1

v0.18.0

hymkorhymkor

v0.18.0 で、差分のところのタブコードをスペース4つにすると、表示は乱れない。

なんで、こんなすぐ分かりそうな問題に気付かないのかと思っていたんだけれども、Rust 開発者の人はタブコードでインデントをつけないから、気づいていないのかもしれない

(Go で開発すると、デフォルトインデントがタブなので、乱れまくってどうしようもなくなってしまう)

で、これ、多分、jj 本体の不具合じゃなくて、jjが import している外部ライブラリの不具合なんだよな…「バージョン戻してよ!」というくらいしか言えないかもしれない