sync-worktrees 3.1.0 → 3.2.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 CHANGED
@@ -1978,6 +1978,43 @@ import * as fs4 from "fs/promises";
1978
1978
  import * as path4 from "path";
1979
1979
  import simpleGit3 from "simple-git";
1980
1980
 
1981
+ // src/errors/index.ts
1982
+ var SyncWorktreesError = class extends Error {
1983
+ constructor(message, code, cause) {
1984
+ super(message);
1985
+ this.code = code;
1986
+ this.cause = cause;
1987
+ this.name = this.constructor.name;
1988
+ Object.setPrototypeOf(this, new.target.prototype);
1989
+ if (cause && cause.stack) {
1990
+ this.stack = `${this.stack}
1991
+ Caused by: ${cause.stack}`;
1992
+ }
1993
+ }
1994
+ };
1995
+ var GitError = class extends SyncWorktreesError {
1996
+ constructor(message, code, cause) {
1997
+ super(message, `GIT_${code}`, cause);
1998
+ }
1999
+ };
2000
+ var GitOperationError = class extends GitError {
2001
+ constructor(operation, details, cause) {
2002
+ super(`Git operation '${operation}' failed: ${details}`, "OPERATION_FAILED", cause);
2003
+ }
2004
+ };
2005
+ var WorktreeError = class extends SyncWorktreesError {
2006
+ constructor(message, code, cause) {
2007
+ super(message, `WORKTREE_${code}`, cause);
2008
+ }
2009
+ };
2010
+ var WorktreeNotCleanError = class extends WorktreeError {
2011
+ constructor(path12, reasons) {
2012
+ super(`Worktree at '${path12}' is not clean: ${reasons.join(", ")}`, "NOT_CLEAN");
2013
+ this.path = path12;
2014
+ this.reasons = reasons;
2015
+ }
2016
+ };
2017
+
1981
2018
  // src/utils/git-url.ts
1982
2019
  function extractRepoNameFromUrl(gitUrl) {
1983
2020
  const url = gitUrl.trim();
@@ -2375,45 +2412,6 @@ var WorktreeMetadataService = class {
2375
2412
  import * as fs3 from "fs/promises";
2376
2413
  import * as path3 from "path";
2377
2414
  import simpleGit2 from "simple-git";
2378
-
2379
- // src/errors/index.ts
2380
- var SyncWorktreesError = class extends Error {
2381
- constructor(message, code, cause) {
2382
- super(message);
2383
- this.code = code;
2384
- this.cause = cause;
2385
- this.name = this.constructor.name;
2386
- Object.setPrototypeOf(this, new.target.prototype);
2387
- if (cause && cause.stack) {
2388
- this.stack = `${this.stack}
2389
- Caused by: ${cause.stack}`;
2390
- }
2391
- }
2392
- };
2393
- var GitError = class extends SyncWorktreesError {
2394
- constructor(message, code, cause) {
2395
- super(message, `GIT_${code}`, cause);
2396
- }
2397
- };
2398
- var GitOperationError = class extends GitError {
2399
- constructor(operation, details, cause) {
2400
- super(`Git operation '${operation}' failed: ${details}`, "OPERATION_FAILED", cause);
2401
- }
2402
- };
2403
- var WorktreeError = class extends SyncWorktreesError {
2404
- constructor(message, code, cause) {
2405
- super(message, `WORKTREE_${code}`, cause);
2406
- }
2407
- };
2408
- var WorktreeNotCleanError = class extends WorktreeError {
2409
- constructor(path12, reasons) {
2410
- super(`Worktree at '${path12}' is not clean: ${reasons.join(", ")}`, "NOT_CLEAN");
2411
- this.path = path12;
2412
- this.reasons = reasons;
2413
- }
2414
- };
2415
-
2416
- // src/services/worktree-status.service.ts
2417
2415
  var OPERATION_FILES = [
2418
2416
  { file: GIT_OPERATIONS.MERGE_HEAD, type: "merge" },
2419
2417
  { file: GIT_OPERATIONS.CHERRY_PICK_HEAD, type: "cherry-pick" },
@@ -3029,24 +3027,19 @@ var GitService = class {
3029
3027
  } catch {
3030
3028
  }
3031
3029
  try {
3032
- const branches = await bareGit.branch();
3033
- const localBranchExists = branches.all.includes(branchName);
3034
- if (localBranchExists || branchName.includes("/")) {
3035
- await bareGit.raw(["worktree", "add", absoluteWorktreePath, branchName]);
3036
- const worktreeGit = this.getCachedGit(absoluteWorktreePath, this.isLfsSkipEnabled());
3037
- await worktreeGit.branch(["--set-upstream-to", `origin/${branchName}`, branchName]);
3030
+ const { local: localBranchExists, remote: remoteBranchExists } = await this.branchExists(branchName);
3031
+ await this.runWorktreeAddByMatrix(
3032
+ bareGit,
3033
+ branchName,
3034
+ absoluteWorktreePath,
3035
+ localBranchExists,
3036
+ remoteBranchExists
3037
+ );
3038
+ if (localBranchExists && !remoteBranchExists) {
3039
+ this.logger.info(` - Created worktree for '${branchName}' (no remote yet \u2014 push to set upstream)`);
3038
3040
  } else {
3039
- await bareGit.raw([
3040
- "worktree",
3041
- "add",
3042
- "--track",
3043
- "-b",
3044
- branchName,
3045
- absoluteWorktreePath,
3046
- `origin/${branchName}`
3047
- ]);
3041
+ this.logger.info(` - Created worktree for '${branchName}' with tracking to origin/${branchName}`);
3048
3042
  }
3049
- this.logger.info(` - Created worktree for '${branchName}' with tracking to origin/${branchName}`);
3050
3043
  if (!this.isLfsSkipEnabled()) {
3051
3044
  await this.verifyLfsFilesDownloaded(absoluteWorktreePath, branchName);
3052
3045
  }
@@ -3062,6 +3055,9 @@ var GitService = class {
3062
3055
  }
3063
3056
  } catch (error) {
3064
3057
  const errorMessage = getErrorMessage(error);
3058
+ if (error?.isUpstreamSetupFailure) {
3059
+ throw error;
3060
+ }
3065
3061
  if (errorMessage.includes("Metadata creation failed")) {
3066
3062
  throw error;
3067
3063
  }
@@ -3079,15 +3075,14 @@ var GitService = class {
3079
3075
  } catch {
3080
3076
  }
3081
3077
  try {
3082
- await bareGit.raw([
3083
- "worktree",
3084
- "add",
3085
- "--track",
3086
- "-b",
3078
+ const { local: localBranchExists, remote: remoteBranchExists } = await this.branchExists(branchName);
3079
+ await this.runWorktreeAddByMatrix(
3080
+ bareGit,
3087
3081
  branchName,
3088
3082
  absoluteWorktreePath,
3089
- `origin/${branchName}`
3090
- ]);
3083
+ localBranchExists,
3084
+ remoteBranchExists
3085
+ );
3091
3086
  this.logger.info(` - Created worktree for '${branchName}' after pruning`);
3092
3087
  if (!this.isLfsSkipEnabled()) {
3093
3088
  await this.verifyLfsFilesDownloaded(absoluteWorktreePath, branchName);
@@ -3156,6 +3151,43 @@ var GitService = class {
3156
3151
  }
3157
3152
  }
3158
3153
  }
3154
+ async runWorktreeAddByMatrix(bareGit, branchName, absoluteWorktreePath, localExists, remoteExists) {
3155
+ if (localExists && remoteExists) {
3156
+ await bareGit.raw(["worktree", "add", absoluteWorktreePath, branchName]);
3157
+ try {
3158
+ const worktreeGit = this.getCachedGit(absoluteWorktreePath, this.isLfsSkipEnabled());
3159
+ await worktreeGit.branch(["--set-upstream-to", `origin/${branchName}`, branchName]);
3160
+ } catch (error) {
3161
+ let rollbackFailed = false;
3162
+ try {
3163
+ await bareGit.raw(["worktree", "remove", "--force", absoluteWorktreePath]);
3164
+ } catch (rollbackError) {
3165
+ rollbackFailed = true;
3166
+ this.logger.warn(
3167
+ ` - Rollback failed for '${branchName}' at '${absoluteWorktreePath}' after upstream setup error: ${getErrorMessage(rollbackError)}`
3168
+ );
3169
+ }
3170
+ const detail = getErrorMessage(error);
3171
+ const suffix = rollbackFailed ? " (rollback failed; partial worktree may remain)" : "";
3172
+ const wrapped = new Error(`Failed to set upstream for '${branchName}': ${detail}${suffix}`);
3173
+ wrapped.isUpstreamSetupFailure = true;
3174
+ throw wrapped;
3175
+ }
3176
+ return;
3177
+ }
3178
+ if (localExists) {
3179
+ await bareGit.raw(["worktree", "add", absoluteWorktreePath, branchName]);
3180
+ return;
3181
+ }
3182
+ if (remoteExists) {
3183
+ await bareGit.raw(["worktree", "add", "--track", "-b", branchName, absoluteWorktreePath, `origin/${branchName}`]);
3184
+ return;
3185
+ }
3186
+ throw new WorktreeError(
3187
+ `Branch '${branchName}' does not exist locally or on origin; create it first`,
3188
+ "BRANCH_NOT_FOUND"
3189
+ );
3190
+ }
3159
3191
  async removeWorktree(worktreePath) {
3160
3192
  const bareGit = this.getCachedGit(this.bareRepoPath);
3161
3193
  await bareGit.raw(["worktree", "remove", worktreePath, "--force"]);
@@ -3349,10 +3381,18 @@ var GitService = class {
3349
3381
  }
3350
3382
  async branchExists(branchName) {
3351
3383
  const bareGit = this.getCachedGit(this.bareRepoPath);
3352
- const localBranches = await bareGit.branch();
3353
- const local = localBranches.all.includes(branchName);
3354
- const remoteBranches = await bareGit.branch(["-r"]);
3355
- const remote = remoteBranches.all.includes(`origin/${branchName}`);
3384
+ const checkRef = async (ref) => {
3385
+ try {
3386
+ await bareGit.raw(["show-ref", "--verify", "--quiet", ref]);
3387
+ return true;
3388
+ } catch {
3389
+ return false;
3390
+ }
3391
+ };
3392
+ const [local, remote] = await Promise.all([
3393
+ checkRef(`${GIT_CONSTANTS.REFS.HEADS}${branchName}`),
3394
+ checkRef(`${GIT_CONSTANTS.REFS.REMOTES}/${branchName}`)
3395
+ ]);
3356
3396
  return { local, remote };
3357
3397
  }
3358
3398
  async getLocalBranches() {