Skip to content

Conversation

@wesm
Copy link
Collaborator

@wesm wesm commented Jan 26, 2026

Summary

This PR fixes two related issues and improves sync compatibility:

1. Allow multiple clones of the same repository (#131)

Previously, registering a second local clone of the same remote repository failed with:

UNIQUE constraint failed: repos.identity (2067)

Fix: Changed repos.identity index from UNIQUE to non-unique. Multiple local clones (e.g., ~/project-main and ~/project-feature) can now coexist with the same identity.

2. Fix roborev address command to use job_id (residual from e956d59)

The address command and API were updated to use job_id instead of review_id, but the daemon client was missed. This caused the refine command and other callers to fail with 400/404 errors.

Fix: Updated daemon.Client.MarkReviewAddressed to send job_id instead of review_id, and updated all callers in refine.go to pass review.JobID.

3. Handle duplicate identities in PostgreSQL sync

With multiple clones now allowed, the sync system needed updates to handle ambiguous identity matches.

Example scenario - Two developers collaborating:

Alice's local SQLite (~/.roborev/roborev.db):

repos table:
| id | root_path              | name   | identity                        |
|----|------------------------|--------|---------------------------------|
| 1  | /home/alice/myapp      | myapp  | git@github.com:org/myapp.git    |

Bob's local SQLite (~/.roborev/roborev.db):

repos table:
| id | root_path              | name   | identity                        |
|----|------------------------|--------|---------------------------------|
| 1  | /home/bob/projects/app | app    | git@github.com:org/myapp.git    |

Shared PostgreSQL (after both sync):

repos table:
| id | root_path              | name   | identity                        | machine_id |
|----|------------------------|--------|---------------------------------|------------|
| 1  | /home/alice/myapp      | myapp  | git@github.com:org/myapp.git    | alice-mac  |
| 2  | /home/bob/projects/app | app    | git@github.com:org/myapp.git    | bob-linux  |

When Alice pulls Bob's jobs from Postgres:

  • GetOrCreateRepoByIdentity("git@github.com:org/myapp.git") is called
  • Alice has exactly 1 local repo with that identity → returns her repo (id=1)
  • Bob's synced jobs appear under Alice's local repo ✓

Example scenario - One developer with multiple clones:

repos table (local SQLite):
| id | root_path                | name         | identity                        |
|----|--------------------------|--------------|--------------------------------|
| 1  | /home/alice/myapp-main   | myapp-main   | git@github.com:org/myapp.git   |
| 2  | /home/alice/myapp-feature| myapp-feature| git@github.com:org/myapp.git   |

When syncing:

  • GetOrCreateRepoByIdentity("git@github.com:org/myapp.git") is called
  • Alice has 2 local repos with that identity → ambiguous!
  • Creates a placeholder repo:
repos table (after sync):
| id | root_path                      | name   | identity                        |
|----|--------------------------------|--------|---------------------------------|
| 1  | /home/alice/myapp-main         | myapp-main   | git@github.com:org/myapp.git |
| 2  | /home/alice/myapp-feature      | myapp-feature| git@github.com:org/myapp.git |
| 3  | git@github.com:org/myapp.git   | myapp        | git@github.com:org/myapp.git |  ← placeholder

Synced jobs attach to the placeholder (id=3), avoiding ambiguity about which clone they belong to.

Selection logic:

  1. If exactly 1 local repo has the identity → use it (always preferred)
  2. If a placeholder exists → use it
  3. If 0 or 2+ local repos → create placeholder

4. Repo display name extraction

Added ExtractRepoNameFromIdentity() to derive human-readable names:

  • git@github.com:org/repo.gitrepo
  • https://github.com/org/my-project.gitmy-project

Config override available via sync.repo_names in config.toml.

Test plan

  • TestRepoIdentity/multiple_clones_with_same_identity_allowed - verifies duplicate identities work
  • TestUniqueIndexMigration - verifies old UNIQUE indexes are migrated
  • TestGetOrCreateRepoByIdentity/* - covers all sync selection scenarios
  • TestExtractRepoNameFromIdentity - covers name extraction edge cases
  • TestSyncConfigGetRepoDisplayName - covers config override functionality
  • TestHTTPClientMarkReviewAddressed - verifies job_id is sent
  • TestMarkReviewAddressedByJobID - verifies storage method
  • All existing tests pass

Commits

  1. e6eb6e1 - Allow multiple clones (drop UNIQUE constraint, add migration)
  2. c4bbaec - Fix daemon client to send job_id instead of review_id
  3. 69d2b39 - Handle duplicate identities in sync with placeholder repos
  4. 96adb97 - Address review findings (nil checks, tests, empty identity handling)
  5. d3ba7b5 - Prefer single local repo over existing placeholder

🤖 Generated with Claude Code

wesm and others added 5 commits January 26, 2026 10:01
Remove unique constraint on repos.identity to allow multiple local clones
of the same remote repository. The identity column is for correlating repos
during sync, not enforcing uniqueness.

This fixes the error:
  UNIQUE constraint failed: repos.identity (2067)

Changes:
- Migration now creates a non-unique index on repos.identity
- Migration detects and converts existing unique indexes to non-unique
- Removed duplicate identity check that blocked migration
- Added test for multiple clones with same identity
- Updated migration tests to verify duplicates are now allowed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The daemon client's MarkReviewAddressed method was still sending
review_id after the server was updated to require job_id. This would
cause all call sites (e.g., refine command) to fail with 400/404.

Changes:
- Update Client interface and HTTPClient to accept job_id
- Update all callers in refine.go to pass review.JobID
- Update client_test.go to verify job_id is sent
- Update refine_test.go mock and assertions for job IDs
- Add tests for MarkReviewAddressedByJobID storage method

Addresses review #2649.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When syncing from PostgreSQL, multiple local clones can share the same
identity (e.g., two clones of the same remote). Previously this caused
sync to fail because GetRepoByIdentity returns an error for duplicates.

This change introduces placeholder repos for sync:
- Placeholder repos have root_path == identity (not a real directory)
- GetOrCreateRepoByIdentity now creates/uses placeholder repos
- Synced jobs attach to the placeholder, not ambiguous local clones
- Local reviews stay with their actual local clone repos

Also adds:
- ExtractRepoNameFromIdentity for human-readable display names
  (e.g., "git@github.com:org/repo.git" -> "repo")
- Config support for custom repo name overrides via sync.repo_names
- Comprehensive tests for duplicate identity handling

Addresses review #2650.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes from reviews #2655, #2656, #2657:

1. Fix nil receiver in GetRepoDisplayName (Medium)
   - Added nil receiver check to prevent panic

2. Add test coverage for GetRepoDisplayName (Medium)
   - Tests for nil receiver, nil map, missing key, and configured name

3. Handle empty identity in ExtractRepoNameFromIdentity (Medium)
   - Returns "unknown" instead of empty string to avoid empty repo names

4. Reuse local repo when exactly one exists (Medium, #2655)
   - GetOrCreateRepoByIdentity now returns the single local repo directly
   - Only creates placeholder when 0 or 2+ local repos have the identity
   - This ensures synced jobs attach to the local repo when unambiguous

5. Added test for single-clone reuse scenario

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fix bug where placeholders were always returned if they existed, even
when exactly one local repo now has the identity. The comment said we
prefer single local repos, but the code checked for placeholder first.

Reordered the logic:
1. Check for local repos (excluding placeholders)
2. If exactly 1 → return it (always preferred)
3. Look for placeholder → if exists, return it
4. If 0 or 2+ → create placeholder

This ensures that if a user:
1. Syncs with no local clone (placeholder created)
2. Clones the repo (now has 1 local clone)
3. Syncs again → local repo is used (not placeholder)

Added test: "prefers single local repo over existing placeholder"

Addresses review #2658.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@wesm wesm changed the title Allow multiple clones of the same repository Allow multiple clones and fix job_id address API Jan 26, 2026
@wesm wesm merged commit 12f0cb3 into main Jan 26, 2026
7 checks passed
@wesm wesm deleted the multiple-clones branch January 26, 2026 15:58
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