From fb0d9ac524d16c89eb2bda96ff1faf4c58dd14f1 Mon Sep 17 00:00:00 2001 From: Sergio Date: Fri, 27 Sep 2024 15:45:17 +0200 Subject: [PATCH] add BranchError and BranchErrorReason --- src/env/node/git/git.ts | 33 ++++++++++++++++++++++++- src/git/errors.ts | 54 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/env/node/git/git.ts b/src/env/node/git/git.ts index b4545b2f6a73c..44eecc3b19e23 100644 --- a/src/env/node/git/git.ts +++ b/src/env/node/git/git.ts @@ -74,6 +74,10 @@ const textDecoder = new TextDecoder('utf8'); const rootSha = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'; export const GitErrors = { + noRemoteReference: /unable to delete '.+?': remote ref does not exist/i, + invalidBranchName: /fatal: '.+?' is not a valid branch name/i, + branchAlreadyExists: /fatal: A branch named '.+?' already exists/i, + branchNotFullyMerged: /error: The branch '.+?' is not fully merged/i, badRevision: /bad revision '(.*?)'/i, cantLockRef: /cannot lock ref|unable to update local ref/i, changesWouldBeOverwritten: /Your local changes to the following files would be overwritten/i, @@ -509,7 +513,34 @@ export class Git { } async branch(repoPath: string, ...args: string[]): Promise { - return this.git({ cwd: repoPath }, 'branch', ...args); + try { + await this.git({ cwd: repoPath }, 'branch', ...args); + } catch (ex) { + const msg: string = ex?.toString() ?? ''; + let reason: BranchErrorReason = BranchErrorReason.Other; + switch (true) { + case GitErrors.noRemoteReference.test(msg) || GitErrors.noRemoteReference.test(ex.stderr ?? ''): + reason = BranchErrorReason.NoRemoteReference; + break; + case GitErrors.invalidBranchName.test(msg) || GitErrors.invalidBranchName.test(ex.stderr ?? ''): + reason = BranchErrorReason.InvalidBranchName; + break; + case GitErrors.branchAlreadyExists.test(msg) || GitErrors.branchAlreadyExists.test(ex.stderr ?? ''): + reason = BranchErrorReason.BranchAlreadyExists; + break; + case GitErrors.branchNotFullyMerged.test(msg) || GitErrors.branchNotFullyMerged.test(ex.stderr ?? ''): + reason = BranchErrorReason.BranchNotFullyMerged; + break; + case GitErrors.branchNotYetBorn.test(msg) || GitErrors.branchNotYetBorn.test(ex.stderr ?? ''): + reason = BranchErrorReason.BranchNotYetBorn; + break; + case GitErrors.branchFastForwardRejected.test(msg) || + GitErrors.branchFastForwardRejected.test(ex.stderr ?? ''): + reason = BranchErrorReason.BranchFastForwardRejected; + break; + } + throw new BranchError(reason, ex); + } } branch__set_upstream(repoPath: string, branch: string, remote: string, remoteBranch: string) { diff --git a/src/git/errors.ts b/src/git/errors.ts index d4979cc8110d0..9ac4495da5ecf 100644 --- a/src/git/errors.ts +++ b/src/git/errors.ts @@ -218,6 +218,60 @@ export class PushError extends Error { } } +export const enum BranchErrorReason { + BranchAlreadyExists, + BranchNotFullyMerged, + NoRemoteReference, + InvalidBranchName, + Other, +} + +export class BranchError extends Error { + static is(ex: unknown, reason?: BranchErrorReason): ex is BranchError { + return ex instanceof BranchError && (reason == null || ex.reason === reason); + } + + readonly original?: Error; + readonly reason: BranchErrorReason | undefined; + + constructor(reason?: BranchErrorReason, original?: Error, branch?: string); + constructor(message?: string, original?: Error); + constructor(messageOrReason: string | BranchErrorReason | undefined, original?: Error, branch?: string) { + let message; + const baseMessage = `Unable to perform action on branch${branch ? ` '${branch}'` : ''}`; + let reason: BranchErrorReason | undefined; + if (messageOrReason == null) { + message = baseMessage; + } else if (typeof messageOrReason === 'string') { + message = messageOrReason; + reason = undefined; + } else { + reason = messageOrReason; + switch (reason) { + case BranchErrorReason.BranchAlreadyExists: + message = `${baseMessage} because it already exists.`; + break; + case BranchErrorReason.BranchNotFullyMerged: + message = `${baseMessage} because it is not fully merged.`; + break; + case BranchErrorReason.NoRemoteReference: + message = `${baseMessage} because the remote reference does not exist.`; + break; + case BranchErrorReason.InvalidBranchName: + message = `${baseMessage} because the branch name is invalid.`; + break; + default: + message = baseMessage; + } + } + super(message); + + this.original = original; + this.reason = reason; + Error.captureStackTrace?.(this, BranchError); + } +} + export const enum PullErrorReason { Conflict, GitIdentity,