( worktreePath: string, operation: () => Promise<T>, maxRetries: number = 3, retryDelayMs: number = 1000 )
| 169 | * @returns Result of the operation |
| 170 | */ |
| 171 | export async function withLockRetry<T>( |
| 172 | worktreePath: string, |
| 173 | operation: () => Promise<T>, |
| 174 | maxRetries: number = 3, |
| 175 | retryDelayMs: number = 1000 |
| 176 | ): Promise<T> { |
| 177 | let lastError: unknown; |
| 178 | |
| 179 | for (let attempt = 0; attempt <= maxRetries; attempt++) { |
| 180 | try { |
| 181 | return await operation(); |
| 182 | } catch (error) { |
| 183 | lastError = error; |
| 184 | |
| 185 | if (isLockFileError(error) && attempt < maxRetries) { |
| 186 | console.log(`[git-factory] Lock file conflict on attempt ${attempt + 1}, cleaning and retrying...`); |
| 187 | |
| 188 | // Try to clean stale locks |
| 189 | await cleanStaleLockFiles(worktreePath); |
| 190 | |
| 191 | // Wait before retry with exponential backoff |
| 192 | await new Promise((resolve) => |
| 193 | setTimeout(resolve, retryDelayMs * Math.pow(2, attempt)) |
| 194 | ); |
| 195 | } else { |
| 196 | throw error; |
| 197 | } |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | throw lastError; |
| 202 | } |
| 203 | |
| 204 | /** |
| 205 | * Checks if a repository has uncommitted changes. |
no test coverage detected