Skip to content

fix: detect conflicts in committed tip trees on Review tab - #228

Merged
Ziinc merged 5 commits into
mainfrom
cursor/review-committed-conflicts-af70
Aug 9, 2026
Merged

fix: detect conflicts in committed tip trees on Review tab#228
Ziinc merged 5 commits into
mainfrom
cursor/review-committed-conflicts-af70

Conversation

@Ziinc

@Ziinc Ziinc commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Detect unresolved conflicts via jj-lib MergedTree::conflicts() only (WC tip + unsaved snapshot). No file-text marker scans — those false-positive on literal <<<<<<< content.
  • Workspace status sets has_conflicts when either the WC tree is unresolved or conflicted_files is non-empty.
  • Review keeps conflicted committed files (and their hunks/cards) visible when the Committed toggle is hidden.

App QA

Before: Conflicts + Committed both visible
After: Committed hidden, conflict card still shown

Test plan

  • Rust: test_detects_conflicts_in_committed_tip_when_tree_diff_is_empty + core_changes_test
  • Integration: committed conflict visible with Committed hidden
  • Screenshot/app-qa: review-committed-conflict-hidden.spec.tsx
  • npm run format && npm run lint && npm run check
    To show artifacts inline, enable in settings.
Open in Web Open in Cursor 

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rebase conflicts live in committed hunks. Hiding Committed used to wipe those hunks and leave an empty Review pane even when Conflicts still listed the file.

  • Always load getWorkspaceDiff for workspaces
  • When Committed is off, retain only conflicted committed paths/hunks so the inline card still renders
  • setRefreshing(true);
    onRefreshingChange?.(true);
    try {
    if (repoPath && workspaceId !== undefined) {
    const diff = await getWorkspaceDiff(repoPath, workspaceId);
    const parsed = parseJjChangedFiles(diff.uncommitted_files ?? []);
    applyChangedFilesRef.current(parsed);
    const conflictedHint = new Set<string>([
    ...conflictedFilesHint,
    ...(diff.conflicted_files ?? []),
    ]);
    const uncommittedPaths = new Set(parsed.map((file) => file.path));
    // When Committed is hidden, still keep conflicted committed files —
    // rebase conflicts live in committed hunks, not dirty WC changes.
    let committed = (diff.committed_files ?? []).filter(
    (file) => showCommittedChanges || conflictedHint.has(file.path),
    );
    for (const path of conflictedHint) {
    if (
    uncommittedPaths.has(path) ||
    committed.some((file) => file.path === path)
    ) {
    continue;
    }
    committed = [
    ...committed,
    {
    path,
    status: "C",
    previous_path: null,
    changed_line_count: 0,
    diff_deferred: false,
    },
    ];
    }
    setCommittedFiles(committed);
    const retainedCommittedPaths = new Set(
    committed.map((file) => file.path),
    );
    setCommittedFileHunks(
    new Map(
    (diff.hunks_by_file ?? [])
    .filter(
    (fileDiff) =>
    showCommittedChanges ||
    retainedCommittedPaths.has(fileDiff.path) ||
    conflictedHint.has(fileDiff.path),
    )
    .map((fileDiff) => [
    fileDiff.path,
    {
    filePath: fileDiff.path,
    hunks: fileDiff.hunks,
    isLoading: false,
    },
    ]),
    ),
    );

Comment thread src-tauri/src/jj.rs

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pairwise tree-diffs miss conflicts when tip and target trees are identical (empty WC on a conflicted bookmark tip). Walking MergedTree::conflicts() reports those paths directly.

  • treq/src-tauri/src/jj.rs

    Lines 3918 to 3922 in e05b50f

    fn collect_conflict_paths_from_tree(tree: &MergedTree) -> Vec<String> {
    tree.conflicts()
    .map(|(path, _value)| path.as_internal_file_string().to_string())
    .collect()
    }
  • Keeps marker + diff fallbacks for materialization edge cases

Comment thread src-tauri/src/core/workspaces.rs
Comment thread src-tauri/src/jj.rs Outdated
Comment thread src-tauri/src/jj.rs Outdated
cursoragent and others added 5 commits August 9, 2026 11:19
Walk MergedTree::conflicts() so conflicted paths are found even when a
pairwise tip/target tree-diff is empty (clean WC on a conflicted tip).
Keep conflicted committed files visible in Review when Committed is hidden.

Co-authored-by: Ziinc <Ziinc@users.noreply.github.com>
Always load workspace diffs for conflicted paths, and stop clearing those
hunks when the Committed toggle is off so Review can still show the card.

Co-authored-by: Ziinc <Ziinc@users.noreply.github.com>
Drop pairwise parent/target tree-diff fallbacks and committed marker
scans. Empty WC children inherit conflicted tip trees, so jj-lib's
tree.conflicts() is sufficient for committed-tip conflicts.

Co-authored-by: Ziinc <Ziinc@users.noreply.github.com>
jj-lib MergedTree::conflicts() is the only source of truth for unresolved
conflicts. Scanning dirty WC file text for markers can false-positive on
literal conflict-marker strings.

Co-authored-by: Ziinc <Ziinc@users.noreply.github.com>
Default [] / new array identity each render recreated loadChangedFiles,
retrigging the committed-toggle effect in a loop and blanking Review unit
tests (empty body / afterEach hookTimeout).

Co-authored-by: Ziinc <Ziinc@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/review-committed-conflicts-af70 branch from 2577979 to cdfe583 Compare August 9, 2026 11:20
@Ziinc
Ziinc marked this pull request as ready for review August 9, 2026 11:22
@Ziinc
Ziinc merged commit 7cb91d4 into main Aug 9, 2026
7 checks passed
@Ziinc
Ziinc deleted the cursor/review-committed-conflicts-af70 branch August 9, 2026 14:43
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.

2 participants