sync-worktrees 3.3.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +74 -4
- package/dist/index.js.map +2 -2
- package/dist/mcp-server.js +74 -4
- package/dist/mcp-server.js.map +2 -2
- package/package.json +3 -1
package/dist/mcp-server.js
CHANGED
|
@@ -57,7 +57,11 @@ var DEFAULT_CONFIG = {
|
|
|
57
57
|
MAX_SAFE_TOTAL_CONCURRENT_OPS: 100
|
|
58
58
|
},
|
|
59
59
|
UPDATE_EXISTING_WORKTREES: true,
|
|
60
|
-
HOOK_TIMEOUT_MS: 6e4
|
|
60
|
+
HOOK_TIMEOUT_MS: 6e4,
|
|
61
|
+
FETCH_TIMEOUT_MS: 3e5,
|
|
62
|
+
CLONE_TIMEOUT_MS: 9e5,
|
|
63
|
+
LOCK_STALE_MS: 6e5,
|
|
64
|
+
LOCK_UPDATE_MS: 3e4
|
|
61
65
|
};
|
|
62
66
|
var ERROR_MESSAGES = {
|
|
63
67
|
GIT_NOT_INITIALIZED: "Git service not initialized. Call initialize() first.",
|
|
@@ -753,6 +757,7 @@ function defaultConsoleOutput(msg, level) {
|
|
|
753
757
|
import * as fs6 from "fs/promises";
|
|
754
758
|
import * as path7 from "path";
|
|
755
759
|
import pLimit from "p-limit";
|
|
760
|
+
import * as lockfile from "proper-lockfile";
|
|
756
761
|
|
|
757
762
|
// src/utils/date-filter.ts
|
|
758
763
|
function parseDuration(durationStr) {
|
|
@@ -1722,6 +1727,13 @@ var WorktreeStatusService = class {
|
|
|
1722
1727
|
};
|
|
1723
1728
|
|
|
1724
1729
|
// src/services/git.service.ts
|
|
1730
|
+
function sanitizeGitEnv(env) {
|
|
1731
|
+
const sanitized = { ...env };
|
|
1732
|
+
delete sanitized.EDITOR;
|
|
1733
|
+
delete sanitized.GIT_EDITOR;
|
|
1734
|
+
delete sanitized.GIT_SEQUENCE_EDITOR;
|
|
1735
|
+
return sanitized;
|
|
1736
|
+
}
|
|
1725
1737
|
var GitService = class {
|
|
1726
1738
|
constructor(config, logger) {
|
|
1727
1739
|
this.config = config;
|
|
@@ -1746,11 +1758,21 @@ var GitService = class {
|
|
|
1746
1758
|
getSparseCheckoutService() {
|
|
1747
1759
|
return this.sparseCheckoutService;
|
|
1748
1760
|
}
|
|
1761
|
+
getFetchTimeoutMs() {
|
|
1762
|
+
if (process.env.NODE_ENV === ENV_CONSTANTS.NODE_ENV_TEST) return 0;
|
|
1763
|
+
return this.config.fetchTimeoutMs ?? DEFAULT_CONFIG.FETCH_TIMEOUT_MS;
|
|
1764
|
+
}
|
|
1765
|
+
getCloneTimeoutMs() {
|
|
1766
|
+
if (process.env.NODE_ENV === ENV_CONSTANTS.NODE_ENV_TEST) return 0;
|
|
1767
|
+
return this.config.cloneTimeoutMs ?? DEFAULT_CONFIG.CLONE_TIMEOUT_MS;
|
|
1768
|
+
}
|
|
1749
1769
|
getCachedGit(dirPath, useLfsSkip = false) {
|
|
1750
1770
|
const key = `${path5.resolve(dirPath)}::${useLfsSkip ? "1" : "0"}`;
|
|
1751
1771
|
let git = this.gitInstances.get(key);
|
|
1752
1772
|
if (!git) {
|
|
1753
|
-
|
|
1773
|
+
const block = this.getFetchTimeoutMs();
|
|
1774
|
+
const base = block > 0 ? simpleGit4(dirPath, { timeout: { block } }) : simpleGit4(dirPath);
|
|
1775
|
+
git = useLfsSkip ? base.env({ [ENV_CONSTANTS.GIT_LFS_SKIP_SMUDGE]: "1" }) : base;
|
|
1754
1776
|
this.gitInstances.set(key, git);
|
|
1755
1777
|
}
|
|
1756
1778
|
return git;
|
|
@@ -1766,7 +1788,9 @@ var GitService = class {
|
|
|
1766
1788
|
} catch {
|
|
1767
1789
|
this.logger.info(`Cloning from "${repoUrl}" as bare repository into "${this.bareRepoPath}"...`);
|
|
1768
1790
|
await fs4.mkdir(path5.dirname(this.bareRepoPath), { recursive: true });
|
|
1769
|
-
const
|
|
1791
|
+
const cloneBlock = this.getCloneTimeoutMs();
|
|
1792
|
+
const cloneBase = cloneBlock > 0 ? simpleGit4({ timeout: { block: cloneBlock } }) : simpleGit4();
|
|
1793
|
+
const cloneGit = this.isLfsSkipEnabled() ? cloneBase.env({ [ENV_CONSTANTS.GIT_LFS_SKIP_SMUDGE]: "1" }) : cloneBase;
|
|
1770
1794
|
await cloneGit.clone(repoUrl, this.bareRepoPath, ["--bare"]);
|
|
1771
1795
|
this.logger.info("\u2705 Clone successful.");
|
|
1772
1796
|
}
|
|
@@ -1852,6 +1876,9 @@ var GitService = class {
|
|
|
1852
1876
|
getDefaultBranch() {
|
|
1853
1877
|
return this.defaultBranch;
|
|
1854
1878
|
}
|
|
1879
|
+
getBareRepoPath() {
|
|
1880
|
+
return this.bareRepoPath;
|
|
1881
|
+
}
|
|
1855
1882
|
async fetchAll() {
|
|
1856
1883
|
this.assertInitialized();
|
|
1857
1884
|
this.logger.info("Fetching latest data from remote...");
|
|
@@ -1898,7 +1925,7 @@ var GitService = class {
|
|
|
1898
1925
|
return branches;
|
|
1899
1926
|
}
|
|
1900
1927
|
async verifyLfsFilesDownloaded(worktreePath, branchName) {
|
|
1901
|
-
const worktreeGit = this.config.sparseCheckout ? simpleGit4(worktreePath).env({ ...process.env, [ENV_CONSTANTS.GIT_ATTR_SOURCE]: "HEAD" }) : this.getCachedGit(worktreePath);
|
|
1928
|
+
const worktreeGit = this.config.sparseCheckout ? simpleGit4(worktreePath).env({ ...sanitizeGitEnv(process.env), [ENV_CONSTANTS.GIT_ATTR_SOURCE]: "HEAD" }) : this.getCachedGit(worktreePath);
|
|
1902
1929
|
try {
|
|
1903
1930
|
const lfsFiles = await worktreeGit.raw(["lfs", "ls-files", "--name-only"]);
|
|
1904
1931
|
let lfsFileList = lfsFiles.trim().split("\n").filter((f) => f.length > 0);
|
|
@@ -2561,6 +2588,11 @@ var WorktreeSyncService = class {
|
|
|
2561
2588
|
this.logger.warn("\u26A0\uFE0F Sync already in progress, skipping...");
|
|
2562
2589
|
return { started: false, reason: "in_progress" };
|
|
2563
2590
|
}
|
|
2591
|
+
const release = await this.acquireBareLock();
|
|
2592
|
+
if (release === null) {
|
|
2593
|
+
this.logger.warn("\u26A0\uFE0F Another process holds the sync lock for this repo, skipping...");
|
|
2594
|
+
return { started: false, reason: "locked" };
|
|
2595
|
+
}
|
|
2564
2596
|
this.syncInProgress = true;
|
|
2565
2597
|
this.logger.info(`[${(/* @__PURE__ */ new Date()).toISOString()}] Starting worktree synchronization...`);
|
|
2566
2598
|
const totalTimer = new Timer();
|
|
@@ -2577,6 +2609,11 @@ var WorktreeSyncService = class {
|
|
|
2577
2609
|
this.gitService.setLfsSkipEnabled(false);
|
|
2578
2610
|
}
|
|
2579
2611
|
this.syncInProgress = false;
|
|
2612
|
+
try {
|
|
2613
|
+
await release();
|
|
2614
|
+
} catch (releaseError) {
|
|
2615
|
+
this.logger.warn(`Failed to release sync lock: ${getErrorMessage(releaseError)}`);
|
|
2616
|
+
}
|
|
2580
2617
|
this.logger.info(`[${(/* @__PURE__ */ new Date()).toISOString()}] Synchronization finished.
|
|
2581
2618
|
`);
|
|
2582
2619
|
if (this.config.debug) {
|
|
@@ -2588,6 +2625,39 @@ var WorktreeSyncService = class {
|
|
|
2588
2625
|
}
|
|
2589
2626
|
return { started: true };
|
|
2590
2627
|
}
|
|
2628
|
+
async acquireBareLock() {
|
|
2629
|
+
if (process.env.NODE_ENV === ENV_CONSTANTS.NODE_ENV_TEST) {
|
|
2630
|
+
return async () => {
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
if (typeof this.gitService.getBareRepoPath !== "function") {
|
|
2634
|
+
return async () => {
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
const barePath = this.gitService.getBareRepoPath();
|
|
2638
|
+
const lockTarget = path7.join(barePath, "HEAD");
|
|
2639
|
+
try {
|
|
2640
|
+
await fs6.access(lockTarget);
|
|
2641
|
+
} catch {
|
|
2642
|
+
return async () => {
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
try {
|
|
2646
|
+
const release = await lockfile.lock(lockTarget, {
|
|
2647
|
+
stale: DEFAULT_CONFIG.LOCK_STALE_MS,
|
|
2648
|
+
update: DEFAULT_CONFIG.LOCK_UPDATE_MS,
|
|
2649
|
+
retries: 0,
|
|
2650
|
+
realpath: false
|
|
2651
|
+
});
|
|
2652
|
+
return release;
|
|
2653
|
+
} catch (error) {
|
|
2654
|
+
const code = error.code;
|
|
2655
|
+
if (code === "ELOCKED") {
|
|
2656
|
+
return null;
|
|
2657
|
+
}
|
|
2658
|
+
throw error;
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2591
2661
|
createRetryOptions(syncContext) {
|
|
2592
2662
|
return {
|
|
2593
2663
|
maxAttempts: this.config.retry?.maxAttempts ?? 3,
|