Skip to content

fix(dest-handling): prevent concurrent clones#28

Merged
fbosch merged 4 commits intomasterfrom
fix/existing-destination-handling
Feb 23, 2026
Merged

fix(dest-handling): prevent concurrent clones#28
fbosch merged 4 commits intomasterfrom
fix/existing-destination-handling

Conversation

@fbosch
Copy link
Owner

@fbosch fbosch commented Feb 23, 2026

Summary

Prevent concurrent clones to same destination by deduplicating inflight requests.

Changes

  • Add in-flight request map to track concurrent clone operations
  • Return existing promise when request already pending
  • Extract implementation to separate function

Summary by CodeRabbit

  • Chores

    • Enforces minimatch v10.2.1 or higher across the workspace.
  • Refactor

    • Improved repository synchronization to safely handle concurrent sync requests so a repository is processed only once at a time.
  • Tests

    • Added an end-to-end test confirming concurrent syncs against the same repository result in a single clone operation.

Copilot AI review requested due to automatic review settings February 23, 2026 10:00
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 23, 2026

Open in StackBlitz

npx https://pkg.pr.new/fbosch/docs-cache@28

commit: 076cd83

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

Adds an inflight concurrency guard and refactors clone/update logic to prevent duplicate repository clones under concurrent requests; includes an end-to-end test for concurrent syncs and a pnpm workspace override requiring minimatch >= 10.2.1.

Changes

Cohort / File(s) Summary
Concurrency Guard Implementation
src/git/fetch-source.ts
Adds a cloneOrUpdateInFlight map and refactors clone/update into a wrapper (cloneOrUpdateRepo) and implementation (cloneOrUpdateRepoImpl). Wrapper reuses in-flight promises keyed by cache path and ensures cleanup of entries on completion.
Concurrency Test
tests/fetch-source-file-protocol.test.js
Adds test "concurrent syncs for the same repo clone only once" that runs three concurrent sources pointing to the same repo, uses a fake git shim, and asserts the repository is cloned exactly once.
Dependency Version Override
pnpm-workspace.yaml
Adds an overrides block enforcing minimatch >= 10.2.1 by redirecting older resolutions to >=10.2.1.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Wrapper as cloneOrUpdateRepo (Wrapper)
    participant InFlightMap as In-Flight Map
    participant Impl as cloneOrUpdateRepoImpl (Impl)
    participant Git as Git Ops

    Client->>Wrapper: request cloneOrUpdateRepo(params, outDir)
    Wrapper->>InFlightMap: check cachePath
    alt no in-flight entry
        Wrapper->>Impl: start cloneOrUpdateRepoImpl(params, outDir, cachePath)
        Wrapper->>InFlightMap: store promise for cachePath
        Impl->>Git: perform clone or fetch+update
        Git-->>Impl: CloneResult
        Impl-->>Wrapper: resolve CloneResult
    else existing promise
        InFlightMap-->>Wrapper: return existing promise
        Wrapper-->>Client: reuse promise result
    end
    Wrapper->>InFlightMap: remove cachePath entry
    Wrapper-->>Client: return { usedCache, cleanup }
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐇 I hopped in code where clones once raced,
One promise held them—no duplicates traced.
The map kept watch, the tests gave cheer,
Minimatch pinned so builds stay clear.
Hooray! I nibble carrots near. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(dest-handling): prevent concurrent clones' directly summarizes the main change—adding concurrency guards to prevent duplicate clone operations for the same destination.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/existing-destination-handling

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.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR attempts to prevent concurrent clone operations to the same git repository cache by introducing an in-flight request deduplication mechanism. The implementation uses a Map to track ongoing operations and makes waiting requests recursively retry after the first operation completes.

Changes:

  • Added cloneOrUpdateInFlight Map to track concurrent operations by cache path
  • Refactored cloneOrUpdateRepo to check for in-flight operations before proceeding
  • Extracted the implementation logic into cloneOrUpdateRepoImpl function
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pnpm-workspace.yaml`:
- Around line 1-2: Update the PR description to explain why the pnpm override
for minimatch is required: state that the override in pnpm-workspace.yaml (the
overrides entry for minimatch@>=10.2.1) is to remediate CVE-2026-26996, a ReDoS
vulnerability, and that the project requires minimatch versions >=10.2.1;
include the CVE identifier, the vulnerability type (ReDoS), and a brief note
that this is a security-driven change, not a functional change, so reviewers
understand the intent.

In `@src/git/fetch-source.ts`:
- Around line 564-585: The current cloneOrUpdateRepo uses
cloneOrUpdateInFlight.get(cachePath) and returns inflight.then(() =>
cloneOrUpdateRepo(params, outDir)) which only retries on fulfillment; when the
in-flight promise rejects all waiters immediately get that rejection and never
retry. Update cloneOrUpdateRepo so the returned promise for waiters attaches a
.catch handler to the inflight promise (e.g., inflight.catch(() =>
undefined).then(() => cloneOrUpdateRepo(params, outDir))) to swallow the initial
failure and allow each waiter to call cloneOrUpdateRepoImpl (via the normal code
path) independently; keep the existing finally cleanup that deletes
cloneOrUpdateInFlight and continue using cloneOrUpdateInFlight,
cloneOrUpdateRepo, cloneOrUpdateRepoImpl and cachePath as before.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c863886 and 01d895d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • pnpm-workspace.yaml
  • src/git/fetch-source.ts
  • tests/fetch-source-file-protocol.test.js

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: 1

🧹 Nitpick comments (1)
tests/fetch-source-file-protocol.test.js (1)

453-466: GIT_TERMINAL_PROMPT is never restored in the finally block.

Line 466 sets process.env.GIT_TERMINAL_PROMPT = "0" but the finally block (lines 497-503) doesn't restore it. This is a pre-existing pattern across all tests in this file, but since you're already touching the cleanup block it would be low effort to fix here and prevent env pollution between tests.

Proposed fix
 	const previousPath = process.env.PATH ?? process.env.Path;
 	const previousPathExt = process.env.PATHEXT;
 	const previousGitDir = process.env.DOCS_CACHE_GIT_DIR;
+	const previousGitTerminalPrompt = process.env.GIT_TERMINAL_PROMPT;
 	const nextPath =
 		process.platform === "win32"
 			? binDir
 			: `${binDir}${path.delimiter}${previousPath ?? ""}`;
 	} finally {
 		process.env.PATH = previousPath;
 		process.env.Path = previousPath;
 		process.env.PATHEXT = previousPathExt;
 		process.env.DOCS_CACHE_GIT_DIR = previousGitDir;
+		process.env.GIT_TERMINAL_PROMPT = previousGitTerminalPrompt;
 		await rm(tmpRoot, { recursive: true, force: true });
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/fetch-source-file-protocol.test.js` around lines 453 - 466, The test
sets process.env.GIT_TERMINAL_PROMPT = "0" but never restores it in the finally
cleanup; capture the original value (e.g., const previousGitTerminalPrompt =
process.env.GIT_TERMINAL_PROMPT) alongside the existing
previousPath/previousPathExt/previousGitDir assignments and then restore
process.env.GIT_TERMINAL_PROMPT = previousGitTerminalPrompt (or delete it if
undefined) inside the finally block where
previousPath/previousPathExt/previousGitDir are reverted so the environment is
returned to its prior state after the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/fetch-source-file-protocol.test.js`:
- Around line 488-496: The test currently asserts that the repository is cloned
only once by checking entries parsed from logPath (logRaw → entries → clones),
but it needs an additional assertion that all three sources performed their
individual checkouts; after building entries (the array of JSON-parsed log
lines) filter for lines that include "checkout" and the repo identifier (similar
to how clones is computed) and assert that its length equals 3 to verify each
source completed its checkout; use the same variables (entries, repo, logPath)
and pattern as the existing clones check to add this assertion.

---

Nitpick comments:
In `@tests/fetch-source-file-protocol.test.js`:
- Around line 453-466: The test sets process.env.GIT_TERMINAL_PROMPT = "0" but
never restores it in the finally cleanup; capture the original value (e.g.,
const previousGitTerminalPrompt = process.env.GIT_TERMINAL_PROMPT) alongside the
existing previousPath/previousPathExt/previousGitDir assignments and then
restore process.env.GIT_TERMINAL_PROMPT = previousGitTerminalPrompt (or delete
it if undefined) inside the finally block where
previousPath/previousPathExt/previousGitDir are reverted so the environment is
returned to its prior state after the test.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01d895d and 076cd83.

📒 Files selected for processing (1)
  • tests/fetch-source-file-protocol.test.js

@fbosch fbosch merged commit 7311d26 into master Feb 23, 2026
16 checks passed
@fbosch fbosch deleted the fix/existing-destination-handling branch February 23, 2026 11:27
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