--- name: jj-conflicts description: Help users identify, understand, and resolve conflicts in jj repositories. Use when user mentions 'conflict', 'resolve conflicts', 'merge conflict', 'conflicted commits', '2-sided conflict', or encounters conflict-related errors. allowed-tools: - Bash(jj status:*) - Bash(jj log -r 'conflicts()':*) - Bash(jj resolve:*) - Bash(jj restore:*) - Bash(jj diff:*) - Bash(jj edit:*) --- # Jj Conflict Resolution This skill helps you identify and resolve conflicts in jj repositories, with special emphasis on safely using `jj restore` with path specifications. ## Understanding Jj Conflicts ### How Jj Differs from Git - **Git**: Conflicts block operations; you must resolve before continuing - **Jj**: Conflicts are stored in commits; you can continue working and resolve later When jj encounters a conflict, it: 1. Marks the commit with a conflict indicator (× in `jj log`) 2. Stores conflict markers in the affected files 3. Allows you to continue working on descendants ### Types of Conflicts **"2-sided conflict"**: Two versions of the same content that can't be automatically merged **"2-sided conflict including 1 deletion"**: One side deleted a file/content, the other modified it - Common when adding files to `.gitignore` after they were already tracked - One of the most frequent conflict scenarios ## Identifying Conflicts ### Find All Conflicted Commits ```bash jj log -r 'conflicts()' ``` This shows all commits with unresolved conflicts. Look for the × marker. ### Check Current Status ```bash jj status ``` If you're in a conflicted commit, this will show: ``` Warning: There are unresolved conflicts at these paths: .obsidian/workspace.json 2-sided conflict including 1 deletion ``` ### Inspect Specific Conflict ```bash jj edit jj diff ``` ## Resolution Strategies ### Strategy A: Using jj resolve (Recommended for Most Cases) The `jj resolve` command is purpose-built for conflict resolution: ```bash # Navigate to conflicted commit jj edit # List all conflicts jj resolve --list # Accept parent's version (side 1) - "ours" jj resolve --tool :ours # Accept child's version (side 2) - "theirs" jj resolve --tool :theirs # Use interactive merge tool (if configured) jj resolve ``` **When to use:** - Most conflict scenarios - When you want semantic clarity (`:ours` vs `:theirs`) - When working with merge tools ### Strategy B: Using jj restore (Safe When Paths Specified) The `jj restore` command can restore files from any commit: ```bash # Navigate to conflicted commit jj edit # Restore SPECIFIC path from parent jj restore --from @- ``` **When to use:** - Accepting parent's version for specific files - When you want more control over the source (`--from` can be any revision) - For deletion conflicts (equivalent to `:ours` in resolve) ### ⚠️ CRITICAL: The Path Argument This is the **most important safety rule** when using `jj restore`: ```bash # ❌ DANGEROUS - Restores ALL files from parent # This will LOSE ALL CHANGES in the current commit! jj restore --from @- # ✅ SAFE - Restores ONLY the specified path # All other changes in the commit are preserved jj restore --from @- .obsidian/ jj restore --from @- src/config.rs ``` **Why this matters:** - Without a path argument, `jj restore` operates on ALL files - With a path argument, it operates ONLY on that specific path - The difference between preserving your work and losing it entirely ### Strategy C: Manual Editing For complex conflicts, you can edit the conflict markers directly: ```bash jj edit # Edit files with conflict markers # Remove markers and keep desired content jj diff # Verify your resolution ``` Conflict markers look like: ``` <<<<<<< Content from side 1 (parent) %%%%%%% Common ancestor content +++++++ Content from side 2 (child) >>>>>>> ``` ### Strategy D: New Commit Then Squash (Jj's Recommended Pattern) For complex resolutions, jj recommends creating a resolution commit: ```bash # Create new commit on top of conflicted one jj new # Resolve using any method above jj resolve --tool :ours # Review the resolution jj diff # Squash resolution back into parent jj squash ``` **Benefits:** - Separates resolution from original changes - Easy to review resolution before committing - Can undo resolution easily ## Common Conflict Scenarios ### Scenario 1: Parent Deleted, Child Modified **Situation:** Parent commit deleted files (e.g., added to `.gitignore`), but child commits still have changes to those files. **Example:** You added `.obsidian/` to `.gitignore` and untracked it in commit `oo`, but 13 descendant commits still had `.obsidian/` changes. **Conflict message:** ``` .obsidian/workspace.json 2-sided conflict including 1 deletion ``` **Resolution:** Accept the deletion by restoring from parent ```bash # Method 1: Using jj resolve (more semantic) jj edit jj resolve --tool :ours .obsidian/ # Method 2: Using jj restore (equally correct) jj edit jj restore --from @- .obsidian/ ``` **For multiple commits:** ```bash # Process each conflicted commit for commit in $(jj log -r 'conflicts()' --no-graph -T 'change_id.short(4)'); do jj edit "$commit" jj restore --from @- .obsidian/ done ``` ### Scenario 2: Both Sides Modified Same Content **Situation:** Both parent and child modified the same lines in a file. **Resolution options:** 1. Accept one side: `jj resolve --tool :ours` or `:theirs` 2. Merge manually: Edit conflict markers 3. Use merge tool: `jj resolve ` (if configured) ### Scenario 3: Rename Conflicts **Situation:** One side renamed a file, the other modified it. **Resolution:** Choose which version to keep, potentially applying changes from other side manually. ## Key Differences: jj restore vs jj resolve | Aspect | jj resolve | jj restore --from @- | | -------------------- | --------------------------------- | ------------------------------------------- | | **Purpose** | Conflict resolution | Generic file restoration | | **Semantic clarity** | `:ours`/`:theirs` explicit | Less explicit (must know parent/child) | | **Merge tools** | Supported | Not supported | | **Flexibility** | Limited to conflict resolution | Can restore from any revision | | **Safety** | Only operates on conflicted files | **MUST specify paths** or affects all files | **Both are correct for accepting deletions, but resolve is more semantically clear.** ## Safety Checklist Before resolving conflicts: - ✅ **Always specify path arguments** when using `jj restore --from` - ✅ **Use `jj diff` to verify changes** before and after resolution - ✅ **Test resolution** with one commit before batch processing - ✅ **Check `jj status`** to confirm conflict is resolved - ❌ **Never use `jj restore --from @-` without paths** unless you intend to reset entire commit ## Real-World Example: Untracking Previously-Committed Files This documents a real scenario that illustrates the critical importance of path specification: ### The Situation 1. You added `.obsidian/` to `.gitignore` in commit `oo` 2. You untracked `.obsidian/` files in that commit: `jj file untrack .obsidian/` 3. 13 descendant commits still contained `.obsidian/` changes 4. After rebasing: `jj rebase -r 'oo..@' -d oo` 5. Result: All 13 descendant commits now have conflicts ### The Conflicts Each conflict shows: ``` .obsidian/workspace.json 2-sided conflict including 1 deletion ``` This means: - Parent (commit `oo`): Deleted `.obsidian/` files - Child commits: Still have changes to `.obsidian/` files ### The Wrong Approach (What NOT to Do) ```bash # ❌ WRONG - This was tried first jj edit jj restore --from @- # No path specified! # Result: ALL files restored from parent # - All task files: DELETED - All document changes: LOST # - Only .obsidian/ should have been affected, but EVERYTHING was reset ``` **Why this failed:** Without a path argument, `jj restore --from @-` restores **every file** from the parent, effectively undoing all changes in the commit. ### The Correct Solution ```bash # ✅ CORRECT - Specify the path jj edit jj restore --from @- .obsidian/ # Path specified! # Result: Only .obsidian/ restored from parent # - Task files: PRESERVED ✓ # - Document changes: PRESERVED ✓ # - .obsidian/ conflicts: RESOLVED ✓ ``` **Or using jj resolve (more semantic):** ```bash jj edit jj resolve --tool :ours .obsidian/ ``` ### Processing All Conflicts ```bash # Get list of conflicted commits jj log -r 'conflicts()' # Process each one with PATHS SPECIFIED for commit in oymp zzyv knzl xlxr lutt xznz uvnk zosw vzxv utmq xtsk qvot pqnr; do echo "Resolving $commit" jj edit "$commit" jj restore --from @- .obsidian/ # ← The critical path argument done # Verify all conflicts resolved jj log -r 'conflicts()' # Should return empty ``` ### Key Takeaway The difference between these two commands is **losing all your work** vs **safely resolving conflicts**: ```bash jj restore --from @- # ← Danger: ALL files jj restore --from @- .obsidian/ # ← Safe: ONLY specified path ``` **Always specify the path when resolving conflicts with `jj restore`.** ## Quick Reference ### Find conflicts ```bash jj log -r 'conflicts()' jj status ``` ### Resolve with jj resolve ```bash jj edit jj resolve --tool :ours # Accept parent's version jj resolve --tool :theirs # Accept child's version ``` ### Resolve with jj restore (MUST SPECIFY PATH) ```bash jj edit jj restore --from @- # Accept parent's version for PATH ONLY ``` ### Verify resolution ```bash jj diff jj status jj log -r 'conflicts()' # Should not include current commit ``` ## Integration with Other Workflows ### After Rebase Rebasing often creates conflicts: ```bash jj rebase -r -d # Check for new conflicts jj log -r 'conflicts()' # Resolve as needed ``` ### Before Push Always resolve conflicts before pushing: ```bash # Check for unresolved conflicts jj log -r 'conflicts() & mine()' # If any found, resolve them first # Then proceed with push ``` ### With jj-spr Conflicts can appear when updating stacked PRs: ```bash jj rebase -d main # Resolve any conflicts jj restore --from @- # Update PRs jj spr update ``` ## When to Use This Skill Invoke this skill when you encounter: - "There are unresolved conflicts at these paths" - × markers in `jj log` output - "2-sided conflict" messages - Questions about using `jj restore` safely - Need to accept parent's or child's version in conflicts - Rebase operations that create conflicts - Files that were added to `.gitignore` after being tracked