Skip to content

Conversation

@markthebest12
Copy link

Summary

  • Add GitLab REST API client (internal/gitlab/client.go) for issue operations
  • Add bidirectional mapping between GitLab issues and beads issues (internal/gitlab/mapping.go)
  • Add bd gitlab command with sync, status, and projects subcommands (cmd/bd/gitlab.go)
  • Implement conflict resolution strategies: --prefer-local, --prefer-gitlab, --prefer-newer (default)
  • Support incremental sync using updated_after parameter
  • 92.2% test coverage for GitLab package (51 unit tests + 4 integration tests)

Features

  • Pull from GitLab: Import GitLab issues into beads with label-based mapping for priority, status, and type
  • Push to GitLab: Create/update GitLab issues from beads with proper label generation
  • Conflict Detection: Detect when both local and GitLab copies have been modified
  • Conflict Resolution: Three strategies for handling conflicts automatically
  • Issue Links: Support for blocks/blocked_by/relates_to relationships

Test Plan

  • Unit tests for types, client, and mapping (92.2% coverage)
  • Integration tests with mock HTTP servers (4 tests)
  • Manual testing with real GitLab instance

🤖 Generated with Claude Code

markthebest12 and others added 15 commits January 23, 2026 15:23
TDD implementation of internal/gitlab/types.go:
- Issue, User, Label, Milestone, IssueLink, Project types
- SyncStats, SyncResult, Conflict for sync operations
- Label prefix parsing for priority/status/type mapping
- IsValidState helper for GitLab state validation

All 8 tests passing.

Relates to: se-devsvc/roz#4

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
TDD implementation of GitLab client for beads integration:
- NewClient constructor with builder pattern (WithHTTPClient)
- FetchIssues with pagination support via X-Next-Page header
- FetchIssuesSince for incremental sync with updated_after param
- CreateIssue for POST /projects/:id/issues
- UpdateIssue for PUT /projects/:id/issues/:iid
- GetIssueLinks for GET /projects/:id/issues/:iid/links
- Retry logic with exponential backoff for rate limiting (429)
- Proper error handling with status code context

All 11 new tests pass (19 total in gitlab package).

Closes steveyegge#5

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add bidirectional mapping between GitLab issues and beads issues:
- MappingConfig for configurable field mappings (priority, status, type, relations)
- GitLabIssueToBeads for import/pull operations
- BeadsIssueToGitLabFields for export/push operations
- IssueLinksToDependencies for dependency conversion
- Support for scoped labels (priority::*, status::*, type::*)
- FilterNonScopedLabels to preserve custom labels

All 11 mapping tests passing (30 total in gitlab package).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements Issue steveyegge#7 from the beads GitLab backend epic.

Features:
- bd gitlab status: Show GitLab configuration and sync status
- bd gitlab sync: Sync issues with GitLab (--dry-run, --pull-only, --push-only)
- bd gitlab projects: List accessible GitLab projects

Configuration via bd config or environment variables:
- gitlab.url / GITLAB_URL
- gitlab.token / GITLAB_TOKEN
- gitlab.project_id / GITLAB_PROJECT_ID

Tests: 6 passing tests for config, validation, and command registration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…IssueLink

Code review identified these issues:
- ProjectID now URL-encoded via projectPath() helper for path-based IDs
- Added WithEndpoint() builder for custom API endpoints
- Added FetchIssueByIID() for fetching single issues
- Added CreateIssueLink() for creating issue dependencies

All 15 client tests pass. Ready for sync implementation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ction

Added gitlab_sync.go with full sync implementation:
- doPullFromGitLab: imports GitLab issues to beads with incremental sync
- doPushToGitLab: creates/updates GitLab issues from local beads issues
- detectGitLabConflicts: finds issues modified on both sides since last sync
- resolveGitLabConflictsByTimestamp: resolves conflicts using newer-wins
- parseGitLabSourceSystem: parses "gitlab:projectID:iid" format

Updated gitlab.go:
- runGitLabSync now uses sync functions with --pull-only/--push-only flags
- Added proper error handling and progress output

8 new tests in gitlab_sync_test.go, all passing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix path-based project ID comparison in doPushToGitLab (skip numeric
  comparison when client.ProjectID contains "/" since we can't reliably
  compare "group/project" with numeric 789)
- Add atomic counter to generateIssueID() to prevent collisions when
  generating multiple IDs rapidly
- Add Warnings field to PullStats to track non-fatal issues like failed
  gitlab.last_sync saves
- Add tests for conflict resolution (resolveGitLabConflictsByTimestamp)

All 4 code review issues addressed with TDD.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add --prefer-local, --prefer-gitlab, and --prefer-newer flags to bd gitlab sync:
- --prefer-local: Always keep local beads version on conflict
- --prefer-gitlab: Always use GitLab version on conflict
- --prefer-newer: Use most recently updated version (default)

Features:
- Unified resolveGitLabConflicts() function with strategy parameter
- Flag validation (only one conflict strategy allowed)
- Automatic conflict detection and resolution in bidirectional sync
- 7 new tests for conflict resolution

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Improve test coverage from 82.3% to 92.2% for internal/gitlab package:

- types_test.go: Add tests for GetPriorityFromLabel, GetStatusFromLabel,
  GetTypeFromLabel helper functions (0% → 100%)
- mapping_test.go: Add tests for priorityToLabel (42.9% → 100%) and
  IssueLinksToDependencies edge cases (73.3% → 100%)
- client_test.go: Add error handling tests for UpdateIssue, GetIssueLinks,
  FetchIssueByIID, CreateIssueLink, CreateIssue, FetchIssuesSince

All 379 new lines of tests pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add gitlab_integration_test.go with end-to-end tests:
- TestGitLabSyncRoundtrip: Pull from GitLab, create local, push back
- TestGitLabConflictStrategiesIntegration: prefer-local/gitlab/newer flags
- TestGitLabConflictDetectionIntegration: Detect conflicts between local/GitLab
- TestIncrementalSync: Full sync then incremental sync with last_sync timestamp

Uses httptest mock servers and in-memory SQLite stores to test complete
sync flows without external dependencies.

Run with: go test -tags=integration ./cmd/bd/...

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
P0 steveyegge#1: Conflict detection now happens BEFORE push to prevent data loss.
Previously: Pull -> Push -> Detect conflicts (wrong - conflicts overwritten)
Now: Pull -> Detect conflicts -> Push (skip conflicting issues)

P0 steveyegge#2: Added SyncContext struct for thread-safe sync operations.
- SyncContext holds store, actor, dbPath, issueIDCounter
- WithContext variants of all sync functions
- globalContextIDCounter for cross-context uniqueness
- Enables concurrent sync operations without race conditions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add safeguards to prevent infinite loops in FetchIssues and FetchIssuesSince:

1. MaxPages constant (1000) prevents runaway pagination from malformed
   X-Next-Page headers that never return empty
2. Context cancellation check at start of each pagination loop iteration
   allows graceful shutdown and returns partial results when cancelled
   between requests

Fixes potential DoS from malformed GitLab responses and improves
responsiveness to cancellation signals.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix swallowed json.Marshal error during retry (client.go line 114)
  Now logs the error and continues to next retry attempt instead of
  silently ignoring with _.

- Add GetIssue() helper to IssueConversion for type-safe access
  Returns *types.Issue from interface{} field, nil if wrong type.
  Avoids import cycle by keeping interface{} but providing safe accessor.

- Eliminate duplicate mapping definitions between types.go and mapping.go
  Export PriorityMapping, StatusMapping, TypeMapping from types.go as
  single source of truth. DefaultMappingConfig now uses these exports.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
P2 Issue steveyegge#9: Remove deprecated resolveGitLabConflictsByTimestamp function
- Delete unused function that was superseded by resolveGitLabConflicts
- Remove associated tests that tested the deprecated function

P3 Issue steveyegge#10: Document cross-project link limitation
- Add doc comment to CreateIssueLink clarifying same-project constraint
- Note that cross-project links will error at GitLab API level

P3 Issue steveyegge#11: Fix ID collision on restart
- Add 4 random bytes to generateIssueID to prevent collisions
- Counter resets on process restart, random component ensures uniqueness
- Add test to verify random component is present

P3 Issue steveyegge#12: Document error handling philosophy
- Add Error Handling Contract comment at top of gitlab_sync.go
- Documents fatal vs non-fatal error handling approach
- Clarifies that stats track error counts for reporting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changes based on PR steveyegge#1290 code review:

1. Wire up SyncContext in runGitLabSync - SyncContext was created
   but not actually used in the main entry point

2. Delete legacy non-WithContext functions - removes code duplication
   and eliminates dead code maintenance burden. All sync operations
   now use WithContext variants exclusively

3. Fix --prefer-local behavior - conflicting issues now go to
   forceUpdateIDs (to force push) instead of skipUpdateIDs. This
   ensures local changes actually win when that strategy is selected

4. Document conflict detection 1-second tolerance - explains the
   timestamp comparison window and its limitations for clock skew

5. Document UUID alternative for ID generation - notes that
   distributed deployments may need UUIDs instead of the current
   timestamp+counter+random approach

Test updates:
- All tests now use WithContext functions
- Removed global store manipulation from tests
- All 18 GitLab tests pass

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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