mirror of
https://github.com/firstsun-dev/git-files-sync.git
synced 2026-07-22 17:20:30 +00:00
User reported push-all was still slow on GitHub after the one- commit-per-run batching landed. Root cause: GitHubService/ GiteaService.pushBatch still created each file's blob with a sequential for loop -- one POST .../git/blobs awaited fully before the next started -- so wall-clock time was still O(N) round trips of pure latency even though only one commit came out the other end. - Add BaseGitService.mapWithConcurrency<T,R>: an order-preserving, bounded-concurrency mapper (worker-pool over a shared cursor). - Add BLOB_CREATE_CONCURRENCY = 8 alongside MAX_BATCH_PUSH_SIZE. - GitHubService/GiteaService.pushBatch now create blobs via mapWithConcurrency instead of a for loop; the tree/commit/ref sequence after it is unchanged since each step genuinely depends on the previous one's result. - GitLab is unaffected -- its pushBatch already sends the whole batch in one Commits API request, no per-file blob step to parallelize. Added a regression-guard test: deferred blob-response promises that only resolve after every blob POST has been dispatched, so a reintroduced sequential loop deadlocks the test instead of silently passing. Evidence: npx eslint . -> 0 errors; npm run build -> clean; npx vitest run -> 340/340 passed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
111 lines
8.2 KiB
JSON
111 lines
8.2 KiB
JSON
{
|
|
"_note": "GitHub Issues (firstsun-dev/git-files-sync, Project #6) is the source of truth for the full backlog and priority/estimate fields. This file mirrors only the active feature and the next few candidates so an agent session has a local, offline checkpoint — sync it against `gh issue list --repo firstsun-dev/git-files-sync --state open` at the start of a session rather than treating it as authoritative.",
|
|
"_prPolicy": "User explicitly requested fewer PRs (2026-07-13): all issue work is consolidated onto branch claude/fix-directory-symlink-pull-260713 -> PR #51, not one PR per issue. Commit new work directly onto that branch unless told otherwise.",
|
|
"features": [
|
|
{
|
|
"id": "feat-001",
|
|
"name": "Project Setup",
|
|
"description": "Confirm the project can install dependencies, run verification, and start from a clean checkout",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "Commit 28f4f8e - npm install/lint/build/test all pass from a clean checkout"
|
|
},
|
|
{
|
|
"id": "feat-002",
|
|
"name": "Settings UX bundle (issues #40, #41, #42)",
|
|
"description": "Local ignore-pattern sync setting (#40), persistent connection status badge in settings (#41), and resized/tabbed conflict resolution modal (#42)",
|
|
"dependencies": ["feat-001"],
|
|
"status": "done",
|
|
"evidence": "Consolidated onto PR #51 (branch claude/fix-directory-symlink-pull-260713); 302 tests pass"
|
|
},
|
|
{
|
|
"id": "feat-003",
|
|
"name": "fix: symbolic link pull fails (issue #33)",
|
|
"description": "Directory symlinks were walked as real trees by local scanning code, causing bogus remote .gitignore 404s and a permanently-stuck 'remote only' status; also fixed a missing Windows symlinkSync type hint",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "Commit 4c8896b, consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-004",
|
|
"name": "fix: resolve sonarqube issues (issue #45)",
|
|
"description": "Review current SonarQube findings and fix code smells/bugs/security hotspots where applicable",
|
|
"dependencies": [],
|
|
"status": "not-started",
|
|
"evidence": ""
|
|
},
|
|
{
|
|
"id": "feat-005",
|
|
"name": "feat(settings): folder picker for root path / vault folder (issue #48)",
|
|
"description": "Searchable folder suggester for the Root path and Vault folder settings fields, replacing free-text entry",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "Commit c107979, consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-006",
|
|
"name": "perf(refresh): use tree blob SHAs to avoid per-file content fetches (issue #36)",
|
|
"description": "Status refresh compares a locally-computed git blob SHA against each remote tree entry's SHA instead of one getFile per file; diff content fetched on demand via new getBlob(sha,path); symlink-aware hashing per real/follow/skip modes",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "Commit 2ed5a43, consolidated onto PR #51; 302 tests pass"
|
|
},
|
|
{
|
|
"id": "feat-007",
|
|
"name": "fix: clear error when requestUrl() rejects with HTML (issue #31)",
|
|
"description": "Some Obsidian versions eagerly JSON-parse inside requestUrl() itself, so a login/proxy HTML page rejected the whole call with a raw SyntaxError bypassing the existing parseJson() HTML-detection wrapper; added the same detection to safeRequest's outer catch",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "Commit a867217, consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-008",
|
|
"name": "feat: show new feature tips after update (issue #39)",
|
|
"description": "Hand-curated src/changelog.ts (separate from auto-generated CHANGELOG.md) with a notable flag per entry, numeric compareVersions(), WhatsNewModal shown once per version bump via a new lastSeenVersion setting",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "Commit 4eebebc, consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-009",
|
|
"name": "feat: add i18n (multi-language) support (issue #38)",
|
|
"description": "Extract hardcoded UI strings (settings tab, ribbon/command labels, Notice messages, sync status view/modals) into an i18n key system with locale detection and English fallback",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "Commit 144eb28 on branch claude/i18n-support-260713, merged into claude/fix-directory-symlink-pull-260713 - added src/i18n/{index,locales/en,locales/zh-tw}.ts (159 keys, en + zh-tw) and tests/i18n/index.test.ts; ~130 strings replaced across settings.ts, main.ts, and 7 ui/ files; lint/build clean, 308 tests pass; consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-010",
|
|
"name": "feat: add bitbucket support (issue #37)",
|
|
"description": "Add Bitbucket as a fourth GitServiceInterface provider; Bitbucket's API has no native blob SHA concept and is PR-based rather than direct-commit, so GitFile.sha semantics may need adjusting",
|
|
"dependencies": ["feat-009"],
|
|
"status": "not-started",
|
|
"evidence": ""
|
|
},
|
|
{
|
|
"id": "feat-011",
|
|
"name": "perf(push): batch-commit push-all + SHA-based diffing",
|
|
"description": "Push-all was up to 3N sequential HTTP calls and N separate commits for N files, plus two redundant full-tree fetches per run. Added optional GitServiceInterface.pushBatch (GitHub/Gitea via blob->tree->commit->ref, generalizing pushSymlink's pattern; GitLab via the native multi-action Commits API + a follow-up tree read for blob shas); sync-manager now classifies each file via a locally-computed git blob sha against one pre-fetched remote tree (no getFile per file) and commits all queued files in one grouped call (chunked at MAX_BATCH_PUSH_SIZE=200), falling back to the old per-file path when a provider has no pushBatch; main.ts/GitignoreManager now share one tree fetch instead of two",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "npx eslint . -> 0 errors; npm run build -> clean; npx vitest run -> 330/330 passed; consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-012",
|
|
"name": "perf(delete): batch-commit remote-only file deletion",
|
|
"description": "Follow-up to feat-011: batch-deleting remote-only files via the sync status panel's checkbox multi-select was still N separate commits (SyncStatusView.performRemoteDeletion looped gitService.deleteFile per file). Added optional GitServiceInterface.deleteBatch, mirroring pushBatch: GitHub/Gitea via the git blob->tree->commit->ref Data API (a tree entry with sha:null removes that path); GitLab via the same Commits API actions array used for pushBatch, with action:'delete'. SyncStatusView.performRemoteDeletion now calls deleteBatch once (chunked at MAX_BATCH_PUSH_SIZE) when the provider supports it, falling back to the original sequential per-file performRemoteDeletionSequential otherwise",
|
|
"dependencies": ["feat-011"],
|
|
"status": "done",
|
|
"evidence": "npx eslint . -> 0 errors; npm run build -> clean; npx vitest run -> 339/339 passed; consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-013",
|
|
"name": "perf(push): parallelize blob creation within a batch commit",
|
|
"description": "User reported push-all was still slow on GitHub after feat-011's one-commit-per-run batching landed. Root cause: GitHubService/GiteaService.pushBatch still created each file's blob with a sequential `for` loop (N sequential POST .../git/blobs round trips before the single tree/commit/ref sequence), so wall-clock time was still O(N) latency-bound. Added a shared BaseGitService.mapWithConcurrency helper (order-preserving, bounded concurrency) and a BLOB_CREATE_CONCURRENCY=8 cap in git-service-base.ts; both services' pushBatch now creates blobs concurrently instead of one at a time. GitLab unaffected (its Commits API already sends the whole batch in one request, no per-file blob step)",
|
|
"dependencies": ["feat-011"],
|
|
"status": "done",
|
|
"evidence": "npx eslint . -> 0 errors; npm run build -> clean; npx vitest run -> 340/340 passed; consolidated onto PR #51"
|
|
}
|
|
],
|
|
"_evidenceStyle": "Keep evidence to one line: commit hash + short pointer (e.g. 'Commit abc1234 - added X, tests pass'). Debugging narrative and design discussion belong in the commit message, not this file."
|
|
}
|