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/index.js
CHANGED
|
@@ -55,7 +55,11 @@ var DEFAULT_CONFIG = {
|
|
|
55
55
|
MAX_SAFE_TOTAL_CONCURRENT_OPS: 100
|
|
56
56
|
},
|
|
57
57
|
UPDATE_EXISTING_WORKTREES: true,
|
|
58
|
-
HOOK_TIMEOUT_MS: 6e4
|
|
58
|
+
HOOK_TIMEOUT_MS: 6e4,
|
|
59
|
+
FETCH_TIMEOUT_MS: 3e5,
|
|
60
|
+
CLONE_TIMEOUT_MS: 9e5,
|
|
61
|
+
LOCK_STALE_MS: 6e5,
|
|
62
|
+
LOCK_UPDATE_MS: 3e4
|
|
59
63
|
};
|
|
60
64
|
var ERROR_MESSAGES = {
|
|
61
65
|
GIT_NOT_INITIALIZED: "Git service not initialized. Call initialize() first.",
|
|
@@ -1947,6 +1951,7 @@ var App_default = App;
|
|
|
1947
1951
|
import * as fs6 from "fs/promises";
|
|
1948
1952
|
import * as path7 from "path";
|
|
1949
1953
|
import pLimit from "p-limit";
|
|
1954
|
+
import * as lockfile from "proper-lockfile";
|
|
1950
1955
|
|
|
1951
1956
|
// src/utils/date-filter.ts
|
|
1952
1957
|
function parseDuration(durationStr) {
|
|
@@ -2993,6 +2998,13 @@ var WorktreeStatusService = class {
|
|
|
2993
2998
|
};
|
|
2994
2999
|
|
|
2995
3000
|
// src/services/git.service.ts
|
|
3001
|
+
function sanitizeGitEnv(env) {
|
|
3002
|
+
const sanitized = { ...env };
|
|
3003
|
+
delete sanitized.EDITOR;
|
|
3004
|
+
delete sanitized.GIT_EDITOR;
|
|
3005
|
+
delete sanitized.GIT_SEQUENCE_EDITOR;
|
|
3006
|
+
return sanitized;
|
|
3007
|
+
}
|
|
2996
3008
|
var GitService = class {
|
|
2997
3009
|
constructor(config, logger) {
|
|
2998
3010
|
this.config = config;
|
|
@@ -3017,11 +3029,21 @@ var GitService = class {
|
|
|
3017
3029
|
getSparseCheckoutService() {
|
|
3018
3030
|
return this.sparseCheckoutService;
|
|
3019
3031
|
}
|
|
3032
|
+
getFetchTimeoutMs() {
|
|
3033
|
+
if (process.env.NODE_ENV === ENV_CONSTANTS.NODE_ENV_TEST) return 0;
|
|
3034
|
+
return this.config.fetchTimeoutMs ?? DEFAULT_CONFIG.FETCH_TIMEOUT_MS;
|
|
3035
|
+
}
|
|
3036
|
+
getCloneTimeoutMs() {
|
|
3037
|
+
if (process.env.NODE_ENV === ENV_CONSTANTS.NODE_ENV_TEST) return 0;
|
|
3038
|
+
return this.config.cloneTimeoutMs ?? DEFAULT_CONFIG.CLONE_TIMEOUT_MS;
|
|
3039
|
+
}
|
|
3020
3040
|
getCachedGit(dirPath, useLfsSkip = false) {
|
|
3021
3041
|
const key = `${path5.resolve(dirPath)}::${useLfsSkip ? "1" : "0"}`;
|
|
3022
3042
|
let git = this.gitInstances.get(key);
|
|
3023
3043
|
if (!git) {
|
|
3024
|
-
|
|
3044
|
+
const block = this.getFetchTimeoutMs();
|
|
3045
|
+
const base = block > 0 ? simpleGit4(dirPath, { timeout: { block } }) : simpleGit4(dirPath);
|
|
3046
|
+
git = useLfsSkip ? base.env({ [ENV_CONSTANTS.GIT_LFS_SKIP_SMUDGE]: "1" }) : base;
|
|
3025
3047
|
this.gitInstances.set(key, git);
|
|
3026
3048
|
}
|
|
3027
3049
|
return git;
|
|
@@ -3037,7 +3059,9 @@ var GitService = class {
|
|
|
3037
3059
|
} catch {
|
|
3038
3060
|
this.logger.info(`Cloning from "${repoUrl}" as bare repository into "${this.bareRepoPath}"...`);
|
|
3039
3061
|
await fs4.mkdir(path5.dirname(this.bareRepoPath), { recursive: true });
|
|
3040
|
-
const
|
|
3062
|
+
const cloneBlock = this.getCloneTimeoutMs();
|
|
3063
|
+
const cloneBase = cloneBlock > 0 ? simpleGit4({ timeout: { block: cloneBlock } }) : simpleGit4();
|
|
3064
|
+
const cloneGit = this.isLfsSkipEnabled() ? cloneBase.env({ [ENV_CONSTANTS.GIT_LFS_SKIP_SMUDGE]: "1" }) : cloneBase;
|
|
3041
3065
|
await cloneGit.clone(repoUrl, this.bareRepoPath, ["--bare"]);
|
|
3042
3066
|
this.logger.info("\u2705 Clone successful.");
|
|
3043
3067
|
}
|
|
@@ -3123,6 +3147,9 @@ var GitService = class {
|
|
|
3123
3147
|
getDefaultBranch() {
|
|
3124
3148
|
return this.defaultBranch;
|
|
3125
3149
|
}
|
|
3150
|
+
getBareRepoPath() {
|
|
3151
|
+
return this.bareRepoPath;
|
|
3152
|
+
}
|
|
3126
3153
|
async fetchAll() {
|
|
3127
3154
|
this.assertInitialized();
|
|
3128
3155
|
this.logger.info("Fetching latest data from remote...");
|
|
@@ -3169,7 +3196,7 @@ var GitService = class {
|
|
|
3169
3196
|
return branches;
|
|
3170
3197
|
}
|
|
3171
3198
|
async verifyLfsFilesDownloaded(worktreePath, branchName) {
|
|
3172
|
-
const worktreeGit = this.config.sparseCheckout ? simpleGit4(worktreePath).env({ ...process.env, [ENV_CONSTANTS.GIT_ATTR_SOURCE]: "HEAD" }) : this.getCachedGit(worktreePath);
|
|
3199
|
+
const worktreeGit = this.config.sparseCheckout ? simpleGit4(worktreePath).env({ ...sanitizeGitEnv(process.env), [ENV_CONSTANTS.GIT_ATTR_SOURCE]: "HEAD" }) : this.getCachedGit(worktreePath);
|
|
3173
3200
|
try {
|
|
3174
3201
|
const lfsFiles = await worktreeGit.raw(["lfs", "ls-files", "--name-only"]);
|
|
3175
3202
|
let lfsFileList = lfsFiles.trim().split("\n").filter((f) => f.length > 0);
|
|
@@ -3832,6 +3859,11 @@ var WorktreeSyncService = class {
|
|
|
3832
3859
|
this.logger.warn("\u26A0\uFE0F Sync already in progress, skipping...");
|
|
3833
3860
|
return { started: false, reason: "in_progress" };
|
|
3834
3861
|
}
|
|
3862
|
+
const release = await this.acquireBareLock();
|
|
3863
|
+
if (release === null) {
|
|
3864
|
+
this.logger.warn("\u26A0\uFE0F Another process holds the sync lock for this repo, skipping...");
|
|
3865
|
+
return { started: false, reason: "locked" };
|
|
3866
|
+
}
|
|
3835
3867
|
this.syncInProgress = true;
|
|
3836
3868
|
this.logger.info(`[${(/* @__PURE__ */ new Date()).toISOString()}] Starting worktree synchronization...`);
|
|
3837
3869
|
const totalTimer = new Timer();
|
|
@@ -3848,6 +3880,11 @@ var WorktreeSyncService = class {
|
|
|
3848
3880
|
this.gitService.setLfsSkipEnabled(false);
|
|
3849
3881
|
}
|
|
3850
3882
|
this.syncInProgress = false;
|
|
3883
|
+
try {
|
|
3884
|
+
await release();
|
|
3885
|
+
} catch (releaseError) {
|
|
3886
|
+
this.logger.warn(`Failed to release sync lock: ${getErrorMessage(releaseError)}`);
|
|
3887
|
+
}
|
|
3851
3888
|
this.logger.info(`[${(/* @__PURE__ */ new Date()).toISOString()}] Synchronization finished.
|
|
3852
3889
|
`);
|
|
3853
3890
|
if (this.config.debug) {
|
|
@@ -3859,6 +3896,39 @@ var WorktreeSyncService = class {
|
|
|
3859
3896
|
}
|
|
3860
3897
|
return { started: true };
|
|
3861
3898
|
}
|
|
3899
|
+
async acquireBareLock() {
|
|
3900
|
+
if (process.env.NODE_ENV === ENV_CONSTANTS.NODE_ENV_TEST) {
|
|
3901
|
+
return async () => {
|
|
3902
|
+
};
|
|
3903
|
+
}
|
|
3904
|
+
if (typeof this.gitService.getBareRepoPath !== "function") {
|
|
3905
|
+
return async () => {
|
|
3906
|
+
};
|
|
3907
|
+
}
|
|
3908
|
+
const barePath = this.gitService.getBareRepoPath();
|
|
3909
|
+
const lockTarget = path7.join(barePath, "HEAD");
|
|
3910
|
+
try {
|
|
3911
|
+
await fs6.access(lockTarget);
|
|
3912
|
+
} catch {
|
|
3913
|
+
return async () => {
|
|
3914
|
+
};
|
|
3915
|
+
}
|
|
3916
|
+
try {
|
|
3917
|
+
const release = await lockfile.lock(lockTarget, {
|
|
3918
|
+
stale: DEFAULT_CONFIG.LOCK_STALE_MS,
|
|
3919
|
+
update: DEFAULT_CONFIG.LOCK_UPDATE_MS,
|
|
3920
|
+
retries: 0,
|
|
3921
|
+
realpath: false
|
|
3922
|
+
});
|
|
3923
|
+
return release;
|
|
3924
|
+
} catch (error) {
|
|
3925
|
+
const code = error.code;
|
|
3926
|
+
if (code === "ELOCKED") {
|
|
3927
|
+
return null;
|
|
3928
|
+
}
|
|
3929
|
+
throw error;
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3862
3932
|
createRetryOptions(syncContext) {
|
|
3863
3933
|
return {
|
|
3864
3934
|
maxAttempts: this.config.retry?.maxAttempts ?? 3,
|