Skip to content

Conversation

@pilat
Copy link
Owner

@pilat pilat commented Dec 22, 2025

This PR improves the way of detecting source properly and fixes the issue when TTY is needed fot git operations

Summary by CodeRabbit

  • Bug Fixes

    • Update process now runs per-source concurrently with fail-fast cancellation on first error.
    • Improved error messages include actionable git configuration hints.
    • Remote detection and comparison are more reliable due to unified URL normalization.
  • New Features

    • Per-source sync status flow adjusted: items start as "Pending", switch to "Syncing" during work, and report "Synced" or "Failed" on completion.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 22, 2025

Walkthrough

Adds a CommandRunner abstraction and mock, centralizes git URL normalization, switches manager to use NormalizeURL, and refactors source updates to run concurrently with a semaphore-limited, fail-fast cancellable workflow. Also adds mockery config, a Makefile mocks target, and minor .gitignore/go.mod edits.

Changes

Cohort / File(s) Change Summary
Build & Tools
\.gitignore, \.mockery.yaml, Makefile, go\.mod
Added coverage.out to ignore rules; added .mockery.yaml to configure mockery; added mocks phony target to Makefile; added indirect dependency github.com/stretchr/objx v0.5.2 to go.mod.
Command execution abstraction & mocks
internal/git/exec.go, internal/git/mock_CommandRunner.go
New CommandRunner interface with Run / RunWithTTY and defaultRunner implementation; generated MockCommandRunner mock via mockery.
Git service refactor
internal/git/git.go
Replaced direct os/exec usages with CommandRunner calls; added runner field; introduced gitConfigHint for enriched error messages; removed legacy exec helper.
URL normalization
internal/git/normalizer.go, internal/manager/manager.go
Added NormalizeURL(rawURL string) string; replaced local normalizeRemoteURL usages in manager with git.NormalizeURL and removed the duplicated helper.
Concurrent update workflow
cmd/devbox/update.go
Refactored source sync to concurrent workers with a semaphore (limit 4), cancellable context and fail-fast behavior on first error; changed per-source status lifecycle to Pending → Syncing → Synced/Failed; centralized error aggregation.

Sequence Diagram

sequenceDiagram
    participant Client as Caller
    participant Controller as updateSources()
    participant Semaphore as Semaphore (max 4)
    participant Worker as Worker Goroutine
    participant GitSvc as Git Client
    participant Runner as CommandRunner
    participant ErrCh as Error Channel

    Client->>Controller: call UpdateSources(ctx)
    activate Controller

    Controller->>Semaphore: create (limit=4)
    Controller->>Controller: create cancellable ctx, errCh

    loop for each source
        Controller->>Semaphore: acquire slot
        Controller->>Worker: spawn goroutine
        activate Worker

        Worker->>Worker: set status "Pending"
        Worker->>Worker: set status "Syncing"
        Worker->>GitSvc: New per-source git client (uses Runner)
        activate GitSvc

        GitSvc->>Runner: Run / RunWithTTY git commands
        alt command success
            Runner-->>GitSvc: stdout, nil
            GitSvc-->>Worker: nil
            Worker->>Worker: set status "Synced"
        else command failure
            Runner-->>GitSvc: "", error
            GitSvc-->>Worker: wrapped error
            Worker->>ErrCh: send error (non-blocking)
            Worker->>Controller: call cancel()
            Worker->>Worker: set status "Failed"
        end

        deactivate GitSvc
        Worker->>Semaphore: release slot
        deactivate Worker
    end

    Controller->>Controller: wait for workers
    Controller->>ErrCh: collect first error (if any)
    alt error present
        Controller-->>Client: return first error
    else
        Controller-->>Client: return nil
    end
    deactivate Controller
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • cmd/devbox/update.go: semaphore acquire/release, context cancellation, error channel buffering/ordering, and timeout handling for the overall operation.
  • internal/git/git.go: correctness of argument mapping to Runner, handling of Run vs RunWithTTY, and consistency of wrapped error messages including gitConfigHint.
  • internal/git/normalizer.go: edge-case URL parsing (Azure DevOps variants, scp-style SSH, fallback behavior) and impacts where normalization is used for comparisons.
  • Mock integration: .mockery.yaml, generated mock file, and Makefile target to ensure developer workflow and CI generation are consistent.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'fix: improve git sync' partially relates to the changeset—it addresses git synchronization improvements, but obscures the substantial architectural changes including concurrent updates, error handling, URL normalization, and runner abstraction. Consider a more specific title like 'refactor: implement concurrent git sync with runner abstraction and URL normalization' to better reflect the scope and nature of changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/git-src

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.

@pilat pilat self-assigned this Dec 22, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22365b3 and 9b2731f.

⛔ Files ignored due to path filters (2)
  • go.sum is excluded by !**/*.sum
  • internal/git/git_test.go is excluded by !**/*_test.go
📒 Files selected for processing (10)
  • .gitignore
  • .mockery.yaml
  • Makefile
  • cmd/devbox/update.go
  • go.mod
  • internal/git/exec.go
  • internal/git/git.go
  • internal/git/mock_CommandRunner.go
  • internal/git/normalizer.go
  • internal/manager/manager.go
🧰 Additional context used
📓 Path-based instructions (3)
internal/**/*.go

⚙️ CodeRabbit configuration file

internal/**/*.go: Core packages providing project functionality:

  • project/: Project configuration, Docker Compose extensions (x-devbox-*)
  • manager/: Project/service autodetection from current directory
  • git/: Git operations (clone, sparse checkout, sync)
  • cert/: SSL certificate generation
  • hosts/: /etc/hosts management with project-scoped markers
  • table/: CLI table output formatting

Review for:

  • Clean interfaces and proper encapsulation
  • Error wrapping with context
  • No interface{} - use 'any' instead (enforced by linter)
  • YAGNI principle - no speculative features or premature abstractions
  • Security considerations for host file and certificate operations

Files:

  • internal/manager/manager.go
  • internal/git/normalizer.go
  • internal/git/exec.go
  • internal/git/mock_CommandRunner.go
  • internal/git/git.go
internal/manager/**/*.go

⚙️ CodeRabbit configuration file

internal/manager/**/*.go: Project autodetection with three-step process:

  1. Check if directory is a local mount of any project
  2. Match Git remote URL + path against project source definitions
  3. Check if directory is the project's manifest repository

Review for edge cases in path matching and Git remote detection.

Files:

  • internal/manager/manager.go
cmd/devbox/**/*.go

⚙️ CodeRabbit configuration file

cmd/devbox/**/*.go: This directory contains CLI commands using Cobra framework.
Each command is in a separate file with an init() function that registers via root.AddCommand().

Review for:

  • Proper error handling with context wrapping (fmt.Errorf with %w)
  • Consistent command structure following existing patterns
  • Appropriate use of runWrapper for context handling
  • Clear, concise command descriptions
  • No over-engineering - only implement what's needed

Files:

  • cmd/devbox/update.go
🧬 Code graph analysis (3)
internal/manager/manager.go (1)
internal/git/normalizer.go (1)
  • NormalizeURL (21-60)
cmd/devbox/update.go (3)
internal/project/config.go (1)
  • SourceConfig (4-9)
internal/app/const.go (1)
  • SourcesDir (13-13)
internal/git/git.go (1)
  • New (16-21)
internal/git/git.go (1)
internal/git/exec.go (1)
  • CommandRunner (10-13)
🪛 checkmake (0.2.2)
Makefile

[warning] 1-1: Missing required phony target "clean"

(minphony)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Lint & Test
  • GitHub Check: E2E Tests
🔇 Additional comments (9)
.gitignore (1)

12-12: LGTM.

Adding coverage.out to .gitignore is idiomatic for Go projects—coverage artifacts shouldn't be tracked in version control. The change aligns with the existing pattern of excluding build artifacts.

Makefile (1)

1-6: LGTM!

The mocks target is correctly added to support mock generation via mockery, and the .PHONY declaration is properly updated.

.mockery.yaml (1)

1-13: LGTM!

The mockery configuration is well-structured for generating in-package mocks with the expecter pattern enabled, which aligns with modern testing practices.

internal/manager/manager.go (1)

84-265: Excellent refactoring to centralize URL normalization.

Replacing the local normalizeRemoteURL function with git.NormalizeURL across all call sites (lines 84, 107, 155, 230, 265) follows the DRY principle and improves maintainability. The centralized implementation in the git package provides consistent URL handling across the codebase.

internal/git/git.go (2)

13-20: LGTM - Clean abstraction for testability.

The introduction of the CommandRunner field and its initialization with defaultRunner provides good separation of concerns and enables testing through dependency injection.


169-190: Helpful git configuration hints for common authentication issues.

The gitConfigHint function provides contextual guidance for SSH vs HTTPS configuration issues, which improves the developer experience when troubleshooting authentication failures.

cmd/devbox/update.go (2)

124-183: Well-implemented concurrent updates with proper fail-fast behavior.

The semaphore-based concurrency control (max 4 concurrent syncs) combined with cancellable context provides good resource management and fail-fast error handling. Key strengths:

  1. Semaphore acquisition properly checks ctx.Done() to avoid blocking when cancelled
  2. cancelSync() on first error prevents wasted work on remaining sources
  3. Error collection loop waits for all goroutines to prevent leaks
  4. Per-source git client creation correctly isolates operations

The implementation correctly handles the edge case where goroutines are waiting for the semaphore when cancellation occurs.


144-167: Status progression provides clear feedback.

The status text flow from "Pending" → "Syncing" → "Synced"/"Failed" with appropriate progress status (Done/Error) gives users clear visibility into the update process.

internal/git/mock_CommandRunner.go (1)

1-180: Generated mock implementation looks correct.

This auto-generated mock properly implements the CommandRunner interface with both Run and RunWithTTY methods. The variadic argument handling, type assertions, and expecter pattern all follow standard mockery conventions. The constructor correctly integrates with testing.T cleanup for automatic expectation verification.

Note: As stated in line 1, this file should not be manually edited—regenerate via mockery if the CommandRunner interface changes.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
internal/git/normalizer.go (1)

19-60: Comprehensive URL normalization with minor redundancy.

The implementation correctly handles various git URL formats including SSH, HTTPS, Azure DevOps variants, and git:// protocol. The normalization logic is sound and the fallback provides reasonable behavior for edge cases.

Note: As mentioned in a previous review, the TrimSuffix(matches[3], ".git") calls are redundant since the regex patterns already handle the optional .git suffix with (?:\.git)?$. This is cosmetic and doesn't affect correctness.

internal/git/exec.go (1)

24-29: Correct TTY handling with combined output capture.

The RunWithTTY implementation properly connects Stdin for interactive operations and uses CombinedOutput() to capture both stdout and stderr, addressing the diagnostic concerns from previous review feedback.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b2731f and 0f152c7.

⛔ Files ignored due to path filters (2)
  • go.sum is excluded by !**/*.sum
  • internal/git/git_test.go is excluded by !**/*_test.go
📒 Files selected for processing (10)
  • .gitignore
  • .mockery.yaml
  • Makefile
  • cmd/devbox/update.go
  • go.mod
  • internal/git/exec.go
  • internal/git/git.go
  • internal/git/mock_CommandRunner.go
  • internal/git/normalizer.go
  • internal/manager/manager.go
🧰 Additional context used
📓 Path-based instructions (3)
internal/**/*.go

⚙️ CodeRabbit configuration file

internal/**/*.go: Core packages providing project functionality:

  • project/: Project configuration, Docker Compose extensions (x-devbox-*)
  • manager/: Project/service autodetection from current directory
  • git/: Git operations (clone, sparse checkout, sync)
  • cert/: SSL certificate generation
  • hosts/: /etc/hosts management with project-scoped markers
  • table/: CLI table output formatting

Review for:

  • Clean interfaces and proper encapsulation
  • Error wrapping with context
  • No interface{} - use 'any' instead (enforced by linter)
  • YAGNI principle - no speculative features or premature abstractions
  • Security considerations for host file and certificate operations

Files:

  • internal/manager/manager.go
  • internal/git/normalizer.go
  • internal/git/git.go
  • internal/git/exec.go
  • internal/git/mock_CommandRunner.go
internal/manager/**/*.go

⚙️ CodeRabbit configuration file

internal/manager/**/*.go: Project autodetection with three-step process:

  1. Check if directory is a local mount of any project
  2. Match Git remote URL + path against project source definitions
  3. Check if directory is the project's manifest repository

Review for edge cases in path matching and Git remote detection.

Files:

  • internal/manager/manager.go
cmd/devbox/**/*.go

⚙️ CodeRabbit configuration file

cmd/devbox/**/*.go: This directory contains CLI commands using Cobra framework.
Each command is in a separate file with an init() function that registers via root.AddCommand().

Review for:

  • Proper error handling with context wrapping (fmt.Errorf with %w)
  • Consistent command structure following existing patterns
  • Appropriate use of runWrapper for context handling
  • Clear, concise command descriptions
  • No over-engineering - only implement what's needed

Files:

  • cmd/devbox/update.go
🧬 Code graph analysis (4)
internal/manager/manager.go (1)
internal/git/normalizer.go (1)
  • NormalizeURL (21-60)
internal/git/normalizer.go (1)
internal/project/config.go (1)
  • URL (4-9)
cmd/devbox/update.go (3)
internal/project/config.go (1)
  • SourceConfig (4-9)
internal/app/const.go (1)
  • SourcesDir (13-13)
internal/git/git.go (1)
  • New (16-21)
internal/git/git.go (1)
internal/git/exec.go (1)
  • CommandRunner (10-13)
🪛 checkmake (0.2.2)
Makefile

[warning] 1-1: Missing required phony target "clean"

(minphony)

🔇 Additional comments (15)
.gitignore (1)

12-12: LGTM - Standard Go coverage artifact.

Adding coverage.out to .gitignore is standard practice for Go projects to prevent coverage artifacts from being committed.

go.mod (1)

140-140: LGTM - Expected transitive dependency.

The objx package is a dependency of testify/mock and is appropriately marked as indirect. This aligns with the new mock generation infrastructure introduced in this PR.

Makefile (1)

1-6: LGTM - Clean mock generation integration.

The mocks target properly integrates with the .mockery.yaml configuration to generate test mocks. The .PHONY declaration is correct.

.mockery.yaml (1)

1-13: LGTM - Well-configured mock generation setup.

The mockery configuration properly targets the CommandRunner interface with sensible defaults. The inpackage: True setting will place mocks alongside the implementation, which is appropriate for internal packages.

internal/manager/manager.go (1)

107-107: Consistent application of centralized normalization.

All remote URL comparisons now correctly use git.NormalizeURL(), ensuring consistent behavior across autodetection and source matching logic.

Also applies to: 155-155, 230-230, 265-265

internal/git/exec.go (2)

9-13: Clean abstraction for command execution.

The CommandRunner interface provides a testable abstraction for git command execution, enabling mock-based testing. The separation between Run and RunWithTTY appropriately handles interactive vs. non-interactive scenarios.


17-22: Proper non-interactive execution setup.

Setting GIT_TERMINAL_PROMPT=0 prevents git from prompting for credentials in non-interactive contexts, which aligns with the PR objective of fixing TTY-related issues. Using CombinedOutput() correctly captures both stdout and stderr for diagnostics.

internal/git/git.go (4)

13-13: Clean integration of CommandRunner abstraction.

Adding the runner field and initializing it with &defaultRunner{} properly integrates the new abstraction while maintaining backward compatibility. This enables testability without changing the public API.

Also applies to: 19-19


29-32: Enhanced error diagnostics with helpful hints.

Using RunWithTTY for clone operations and adding gitConfigHint(url) to error messages provides users with actionable guidance when authentication fails. This directly addresses the PR objective of improving git operations.


56-59: Important safety check documented.

The comment clarifies the critical safety check: without a .git directory, git reset would traverse up and potentially lock a parent repository. This defensive approach prevents unintended side effects.


79-87: Proper sparse checkout configuration.

The sparse checkout initialization and configuration correctly uses the runner interface. The append pattern for variadic arguments is handled properly.

cmd/devbox/update.go (3)

109-111: LGTM on timeout and context setup.

30-minute timeout is reasonable for git operations. Context handling with defer cancel() ensures proper cleanup.


149-170: Per-source git client instantiation is correct.

Creating a new git.New(repoDir) per source aligns with the runner-based architecture. The error handling and progress event updates are clean.

One minor observation: the error path sets Status: progress.Error while success sets Status: progress.Done, which is correct. The StatusText values ("Pending", "Syncing", "Synced", "Failed") provide good user feedback.


173-184: Error aggregation is correct.

Collecting all errors and returning the first non-nil is the right approach for fail-fast. The buffered channel prevents goroutine leaks.

internal/git/mock_CommandRunner.go (1)

1-180: Auto-generated mock file - no review needed.

This file is generated by mockery v2.52.2 as indicated in the header comment. The generated code follows standard testify/mock patterns and should not be manually modified. Ensure this file is regenerated when the CommandRunner interface changes (via make mocks or equivalent).

@pilat pilat merged commit 7d7a534 into main Dec 22, 2025
3 checks passed
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