Skip to content

chore: conflicted bookmarks 棚卸し + push 前 bookmark 自動前進 - #49

Merged
aloekun merged 1 commit into
masterfrom
chore/bookmark-cleanup
Apr 17, 2026
Merged

chore: conflicted bookmarks 棚卸し + push 前 bookmark 自動前進#49
aloekun merged 1 commit into
masterfrom
chore/bookmark-cleanup

Conversation

@aloekun

@aloekun aloekun commented Apr 17, 2026

Copy link
Copy Markdown
Owner

Summary

Summary by CodeRabbit

  • New Features

    • プッシュ実行時にローカルJJブックマークを自動で検出してターゲットへ更新する処理を追加。個別の更新失敗は情報ログに出力されてもプッシュは継続します。
  • Documentation

    • タスク管理ドキュメントを整理し、現在進行中タスクの順序を更新、完了履歴にブックマーク削除/完了扱いを反映しました。
  • Tests

    • ブックマーク文字列の解析・空要素除去・順序保持・重複除去を検証するユニットテストを追加しました。

@coderabbitai

coderabbitai Bot commented Apr 17, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

プッシュ前にローカルjjブックマークをターゲットリビジョンまで自動で進める処理を新規モジュールとして追加し、run_push 実行前に条件付きで呼び出す統合を行った。ドキュメントの進行中タスクと完了履歴も更新。

Changes

Cohort / File(s) Summary
ドキュメント更新
docs/todo.md
進行中タスクの番号付けを調整、SessionStart フック依存表記を更新、conflicted bookmarks の完了履歴を追加。
ステージ統合
src/cli-push-runner/src/stages/mod.rs, src/cli-push-runner/src/stages/push.rs
push_jj_bookmark モジュールを宣言し、run_push の開始前に config.command"jj " で始まる場合に advance_jj_bookmarks() を呼び出す挿入(失敗はログ化して push 継続)。
ブックマーク自動更新実装
src/cli-push-runner/src/stages/push_jj_bookmark.rs
新ファイル追加:advance_jj_bookmarks() を実装。ターゲット判定(@ の空判定→必要なら @-)、複数 revset(trunk()..target, main..target, master..target)でブックマーク検出、テンプレート出力のパースと重複除去(順序保持)、各ブックマークへ jj bookmark set -r <target> -- <name> を逐次実行。個別失敗は情報ログ化。パーサと重複除去のユニットテストを追加。

Sequence Diagram(s)

sequenceDiagram
    participant PushRunner as Push Runner
    participant JJ as JJ CLI
    participant PushCmd as Push Command

    PushRunner->>PushRunner: run_push 開始(config.command が "jj " か判定)
    alt is "jj "
        PushRunner->>JJ: jj log -r @ (テンプレ出力) -> target 決定(@ or `@-`)
        JJ-->>PushRunner: template 出力
        PushRunner->>JJ: jj log -r 'revset' for trunk/main/master ...(bookmark 名取得)
        JJ-->>PushRunner: bookmark 名一覧(改行/カンマ)
        alt bookmarks found
            loop 各ブックマーク
                PushRunner->>JJ: jj bookmark set -r <target> -- <name>
                JJ-->>PushRunner: 成功/失敗
                PushRunner->>PushRunner: 成功は通常ログ、失敗は情報ログで続行
            end
        else no bookmarks
            PushRunner-->>PushRunner: 情報ログ(更新対象なし)
        end
    end
    PushRunner->>PushCmd: 実際の push コマンド実行
    PushCmd-->>PushRunner: push 結果
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed プルリクエストのタイトルは、競合したブックマークの整理と push 前の自動ブックマーク前進という主要な変更内容を正確に反映しており、簡潔かつ具体的です。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/cli-push-runner/src/stages/push_jj_bookmark.rs (3)

36-57: スタックした bookmark を巻き込む懸念。

(trunk()..target) & bookmarks() は target の祖先にあるすべての local bookmark を返すため、作業中にスタックしている複数 bookmark (例: feat/a の上に feat/b を積んでいるケース) があると、feat/a まで target へ前進させてしまい履歴が崩れます。対象を「現在 @ から辿れる直近の bookmark のみ」「push 対象の bookmark のみ」等に絞り込むことを検討してください (例: heads((trunk()..target) & bookmarks())jj git push --dry-run の対象 bookmark との突合)。

また、最初に成功した revset でたとえ空でも return Ok(dedup(...)) している (Line 48) ため、trunk() が解決したが該当 bookmark が 0 件の場合、main / master フォールバックには落ちません。これが意図どおりか確認してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli-push-runner/src/stages/push_jj_bookmark.rs` around lines 36 - 57,
get_bookmarks_in_range currently queries "(trunk()..target) & bookmarks()" which
returns all ancestor-local bookmarks (risking stacked bookmarks) and also
returns early on the first successful revset even if it yields zero bookmarks;
update get_bookmarks_in_range to (1) restrict the revset to only head bookmarks
by using heads((... ) & bookmarks()) or otherwise filter the parsed bookmarks
from run_jj_log to only include the single nearest bookmark reachable from @ or
to cross-check against the push-target list, and (2) change the logic around
run_jj_log/parse_bookmarks_from_template/dedup so that if a revset succeeds but
yields an empty list you do not return immediately but continue to the next
revset (trunk → main → master) before finally falling back to logging and
Ok(Vec::new()).

103-185: テスト観点: advance_jj_bookmarks / set_bookmark の引数組み立ても検証したい。

純関数 (dedup / parse_bookmarks_from_template) には良い網羅がありますが、今回の主要バグ候補である jj bookmark set の引数列や、main()/master() revset 構築文字列といった「外部コマンドに渡す文字列の組み立て」がテストされていません。Command を直接使わず、args を組み立てる純関数を切り出しておくとテスト容易性が上がります。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli-push-runner/src/stages/push_jj_bookmark.rs` around lines 103 - 185,
テストが純関数に偏っていて、外部コマンドへ渡す引数列(`advance_jj_bookmarks` / `set_bookmark` が組み立てる `jj
bookmark set` の args や `main()/master()` 用 revset
文字列)が検証されていないので、これらの文字列組み立てロジックを副作用なしの helper 関数に切り出してテスト可能にしてください:たとえば新しく
`build_set_bookmark_args(...)` と `build_revset_for_branch(branch_name: &str)`
のような純関数を `advance_jj_bookmarks` と `set_bookmark` から呼び出すようにリファクタし、元の関数は `Command`
を実行するだけにすることで、ユニットテストから直接これらの helper を呼んで期待する args ベクタや revset
文字列をアサートできるようにします。

95-101: HashSet の代わりに短い Vec なら線形探索で十分。

dedup 対象は通常数件〜十数件の bookmark 名なので、HashSet + clone() より Vec::contains の方が簡潔でアロケーションも減らせます。ホットパスではないので必須ではありません。

♻️ 提案
 fn dedup(items: Vec<String>) -> Vec<String> {
-    let mut seen = std::collections::HashSet::new();
-    items
-        .into_iter()
-        .filter(|s| seen.insert(s.clone()))
-        .collect()
+    let mut out = Vec::with_capacity(items.len());
+    for s in items {
+        if !out.contains(&s) {
+            out.push(s);
+        }
+    }
+    out
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli-push-runner/src/stages/push_jj_bookmark.rs` around lines 95 - 101,
The dedup function currently creates a HashSet and clones each string; replace
that with a simple Vec-based linear check since input is small: in dedup, create
seen: Vec<String>, iterate items.into_iter(), for each s check if
seen.contains(&s) and if not push s into seen, then return seen—this removes the
HashSet and the need to clone each item while preserving ordering and reducing
allocations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cli-push-runner/src/stages/push_jj_bookmark.rs`:
- Around line 37-41: revset 配列 revsets 内で main() と master() は jj の組み込み revset
関数ではないためエラーになるので、main/master をブックマーク参照に置き換えてください:具体的には revsets を生成している箇所で
format!("(main()..{}) & bookmarks()", target) と format!("(master()..{}) &
bookmarks()", target) をそれぞれ format!("(bookmark(main)..{}) & bookmarks()",
target) と format!("(bookmark(master)..{}) & bookmarks()", target) のように修正して、既存の
trunk() entry はそのままにしてください(参照する識別子:revsets, target, trunk(), bookmarks())。
- Around line 67-79: The call in set_bookmark builds the jj command arguments in
the wrong order causing -r <rev> to be treated as a positional bookmark name;
change the args passed to Command::new("jj") in the set_bookmark function so the
-r option comes before the positional bookmark name (use the sequence equivalent
to ["bookmark","set","-r", target, "--", name] rather than the current
["bookmark","set","--", name, "-r", target]) so jj interprets the revision
correctly.

---

Nitpick comments:
In `@src/cli-push-runner/src/stages/push_jj_bookmark.rs`:
- Around line 36-57: get_bookmarks_in_range currently queries "(trunk()..target)
& bookmarks()" which returns all ancestor-local bookmarks (risking stacked
bookmarks) and also returns early on the first successful revset even if it
yields zero bookmarks; update get_bookmarks_in_range to (1) restrict the revset
to only head bookmarks by using heads((... ) & bookmarks()) or otherwise filter
the parsed bookmarks from run_jj_log to only include the single nearest bookmark
reachable from @ or to cross-check against the push-target list, and (2) change
the logic around run_jj_log/parse_bookmarks_from_template/dedup so that if a
revset succeeds but yields an empty list you do not return immediately but
continue to the next revset (trunk → main → master) before finally falling back
to logging and Ok(Vec::new()).
- Around line 103-185: テストが純関数に偏っていて、外部コマンドへ渡す引数列(`advance_jj_bookmarks` /
`set_bookmark` が組み立てる `jj bookmark set` の args や `main()/master()` 用 revset
文字列)が検証されていないので、これらの文字列組み立てロジックを副作用なしの helper 関数に切り出してテスト可能にしてください:たとえば新しく
`build_set_bookmark_args(...)` と `build_revset_for_branch(branch_name: &str)`
のような純関数を `advance_jj_bookmarks` と `set_bookmark` から呼び出すようにリファクタし、元の関数は `Command`
を実行するだけにすることで、ユニットテストから直接これらの helper を呼んで期待する args ベクタや revset
文字列をアサートできるようにします。
- Around line 95-101: The dedup function currently creates a HashSet and clones
each string; replace that with a simple Vec-based linear check since input is
small: in dedup, create seen: Vec<String>, iterate items.into_iter(), for each s
check if seen.contains(&s) and if not push s into seen, then return seen—this
removes the HashSet and the need to clone each item while preserving ordering
and reducing allocations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3cfe6f11-a637-4d0f-97a2-2c389b791093

📥 Commits

Reviewing files that changed from the base of the PR and between ef870f5 and 4f76df3.

📒 Files selected for processing (4)
  • docs/todo.md
  • src/cli-push-runner/src/stages/mod.rs
  • src/cli-push-runner/src/stages/push.rs
  • src/cli-push-runner/src/stages/push_jj_bookmark.rs

Comment on lines +37 to +41
let revsets = [
format!("(trunk()..{}) & bookmarks()", target),
format!("(main()..{}) & bookmarks()", target),
format!("(master()..{}) & bookmarks()", target),
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

jj revset functions: is main()ormaster()a built-in revset function, or onlytrunk()?

💡 Result:

Only trunk is a built-in revset function (specifically, a built-in alias) in Jujutsu (jj). Neither main nor master is a built-in revset function or alias. The trunk alias is defined in the default revsets.toml configuration as latest(remote_bookmarks(exact:"main", exact:"origin") | remote_bookmarks(exact:"master", exact:"origin") | ... | root), which tries remote bookmarks named "main", "master", or "trunk" on "origin" or "upstream" remotes, falling back to root. It is explicitly listed under "Built-in Aliases" in the official documentation, alongside others like mutable and visible. The comprehensive lists of functions and built-in aliases in the revset documentation do not include main or master.

Citations:


🏁 Script executed:

fd -t f "push_jj_bookmark.rs" --exec cat -n {}

Repository: aloekun/claude-code-hook-test

Length of output: 6402


main()/master() は jj の revset 関数ではありません。修正が必要です。

Web 検索で確認したところ、jj に組み込みの revset 関数は trunk() のみで、main()master() という関数は存在しません。したがって (main()..{target}) & bookmarks()(master()..{target}) & bookmarks() は常に revset エラーで失敗し、trunk() が未設定な環境では自動更新が常にスキップされます。

bookmark 名を直接参照する構文に修正してください:

修正パッチ
     let revsets = [
         format!("(trunk()..{}) & bookmarks()", target),
-        format!("(main()..{}) & bookmarks()", target),
-        format!("(master()..{}) & bookmarks()", target),
+        format!("(main..{}) & bookmarks()", target),
+        format!("(master..{}) & bookmarks()", target),
     ];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli-push-runner/src/stages/push_jj_bookmark.rs` around lines 37 - 41,
revset 配列 revsets 内で main() と master() は jj の組み込み revset
関数ではないためエラーになるので、main/master をブックマーク参照に置き換えてください:具体的には revsets を生成している箇所で
format!("(main()..{}) & bookmarks()", target) と format!("(master()..{}) &
bookmarks()", target) をそれぞれ format!("(bookmark(main)..{}) & bookmarks()",
target) と format!("(bookmark(master)..{}) & bookmarks()", target) のように修正して、既存の
trunk() entry はそのままにしてください(参照する識別子:revsets, target, trunk(), bookmarks())。

Comment thread src/cli-push-runner/src/stages/push_jj_bookmark.rs Outdated
@aloekun
aloekun force-pushed the chore/bookmark-cleanup branch from 4f76df3 to 33b9222 Compare April 17, 2026 11:36
@aloekun

aloekun commented Apr 17, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 17, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cli-push-runner/src/stages/push_jj_bookmark.rs`:
- Around line 43-57: The loop currently returns as soon as run_jj_log(...)
succeeds even if parse_bookmarks_from_template(&output) yields an empty Vec, so
the fallback to other revsets (and final trunk/main/master warning) never runs;
change the logic in the loop that iterates revsets so that on Ok(output) you
parse bookmarks (via parse_bookmarks_from_template), dedup them, and only return
Ok(dedup(bookmarks)) when the resulting Vec is non-empty—if the parsed bookmarks
is empty, continue to the next revset (same as on Err), and after the loop keep
the existing log_info(...) and Ok(Vec::new()) fallback behavior. Ensure you
reference run_jj_log, parse_bookmarks_from_template, dedup and the revsets loop
when making the change.
- Around line 27-34: determine_target_revision currently always returns "@-"
when the working root is empty, but that fails when "@" is the root commit
because "@-" doesn't exist; change determine_target_revision to return
Result<Option<String>, String> and, when you detect the empty case, probe
whether "@-" exists (e.g., call run_jj_log for "@-" and check for an
empty/missing result or error); if "@-" exists return
Ok(Some("@-".to_string())), otherwise return Ok(None) to indicate “skip bookmark
update”; update the caller (set_bookmark or wherever determine_target_revision
is used) to skip running jj bookmark set when it receives None instead of a
revision.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0e7bec06-3b38-4fb5-b3a1-a2faaaf1ddde

📥 Commits

Reviewing files that changed from the base of the PR and between 4f76df3 and 33b9222.

📒 Files selected for processing (4)
  • docs/todo.md
  • src/cli-push-runner/src/stages/mod.rs
  • src/cli-push-runner/src/stages/push.rs
  • src/cli-push-runner/src/stages/push_jj_bookmark.rs
✅ Files skipped from review due to trivial changes (1)
  • src/cli-push-runner/src/stages/mod.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/cli-push-runner/src/stages/push.rs
  • docs/todo.md

Comment thread src/cli-push-runner/src/stages/push_jj_bookmark.rs Outdated
Comment thread src/cli-push-runner/src/stages/push_jj_bookmark.rs
@aloekun
aloekun force-pushed the chore/bookmark-cleanup branch from 33b9222 to 12c0674 Compare April 17, 2026 13:28
@aloekun

aloekun commented Apr 17, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 17, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/cli-push-runner/src/stages/push_jj_bookmark.rs (1)

52-76: テンプレートの区切りを改行のみに統一すると解析が単純になります

local_bookmarks.map(|b| b.name()).join(",") + 行内カンマ split という 2 段階パースは、1 行に複数 bookmark 名を収める都合で採用されていますが、jj テンプレートで 1 bookmark = 1 行に揃えてしまえば parse_bookmarks_from_templatelines() のみで済み、将来的に bookmark 名仕様が変わった際の事故率も下がります。既存テストの期待値も単純化できます。

♻️ 提案パッチ
     for revset in &revsets {
-        let template = "local_bookmarks.map(|b| b.name()).join(\",\") ++ \"\\n\"";
+        let template = "local_bookmarks.map(|b| b.name() ++ \"\\n\").join(\"\")";
         match run_jj_log(revset, template) {
 fn parse_bookmarks_from_template(raw: &str) -> Vec<String> {
     raw.lines()
-        .flat_map(|line| line.split(','))
         .map(|s| s.trim().to_string())
         .filter(|s| !s.is_empty())
         .collect()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli-push-runner/src/stages/push_jj_bookmark.rs` around lines 52 - 76, The
current code emits a comma-joined template and then splits by commas in
parse_bookmarks_from_template, which is brittle; change the jj template in the
loop (used in run_jj_log call) to produce one bookmark per line (e.g.
"local_bookmarks.map(|b| b.name()).join(\"\\n\") ++ \"\\n\"") and update
parse_bookmarks_from_template to parse only by raw.lines(), trimming each line
and filtering empties (remove the .split(',') step) so each bookmark is one
line; keep dedup and the surrounding logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/cli-push-runner/src/stages/push_jj_bookmark.rs`:
- Around line 52-76: The current code emits a comma-joined template and then
splits by commas in parse_bookmarks_from_template, which is brittle; change the
jj template in the loop (used in run_jj_log call) to produce one bookmark per
line (e.g. "local_bookmarks.map(|b| b.name()).join(\"\\n\") ++ \"\\n\"") and
update parse_bookmarks_from_template to parse only by raw.lines(), trimming each
line and filtering empties (remove the .split(',') step) so each bookmark is one
line; keep dedup and the surrounding logic unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f691949f-3a78-4b5b-b50b-e5a8dfc4ed16

📥 Commits

Reviewing files that changed from the base of the PR and between 33b9222 and 12c0674.

📒 Files selected for processing (4)
  • docs/todo.md
  • src/cli-push-runner/src/stages/mod.rs
  • src/cli-push-runner/src/stages/push.rs
  • src/cli-push-runner/src/stages/push_jj_bookmark.rs
✅ Files skipped from review due to trivial changes (1)
  • src/cli-push-runner/src/stages/mod.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/cli-push-runner/src/stages/push.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant