diff --git a/packages/cli/src/modules/source-control.ee/__tests__/source-control-git.service.test.ts b/packages/cli/src/modules/source-control.ee/__tests__/source-control-git.service.test.ts index 3b2c91d9e19..ca39ab24c33 100644 --- a/packages/cli/src/modules/source-control.ee/__tests__/source-control-git.service.test.ts +++ b/packages/cli/src/modules/source-control.ee/__tests__/source-control-git.service.test.ts @@ -4,8 +4,8 @@ import { simpleGit } from 'simple-git'; import type { SimpleGit } from 'simple-git'; import { SourceControlGitService } from '../source-control-git.service.ee'; -import type { SourceControlPreferences } from '../types/source-control-preferences'; import type { SourceControlPreferencesService } from '../source-control-preferences.service.ee'; +import type { SourceControlPreferences } from '../types/source-control-preferences'; const MOCK_BRANCHES = { all: ['origin/master', 'origin/feature/branch'], @@ -77,6 +77,48 @@ describe('SourceControlGitService', () => { }); }); + describe('when fetch fails during remote tracking setup', () => { + it('should log warning and not throw when tracking fetch failures are tolerated', async () => { + const mockLogger = mock(); + const gitService = new SourceControlGitService(mockLogger, mock(), mock()); + const prefs = mock({ branchName: 'main' }); + const user = mock(); + const git = mock(); + gitService.git = git; + jest.spyOn(gitService, 'setGitCommand').mockResolvedValue(); + + const fetchError = new Error('Authentication failed for HTTPS remote'); + jest.spyOn(gitService, 'fetch').mockRejectedValue(fetchError); + + await gitService.initRepository(prefs, user, { + tolerateTrackingFetchFailure: true, + }); + + expect(mockLogger.warn).toHaveBeenCalledWith( + 'Failed to fetch during remote tracking setup', + { error: fetchError }, + ); + }); + + it('should throw when tracking fetch failures are NOT tolerated', async () => { + const gitService = new SourceControlGitService(mock(), mock(), mock()); + const prefs = mock({ branchName: 'main' }); + const user = mock(); + const git = mock(); + gitService.git = git; + jest.spyOn(gitService, 'setGitCommand').mockResolvedValue(); + + const fetchError = new Error('Authentication failed for HTTPS remote'); + jest.spyOn(gitService, 'fetch').mockRejectedValue(fetchError); + + await expect( + gitService.initRepository(prefs, user, { + tolerateTrackingFetchFailure: false, + }), + ).rejects.toThrow(fetchError); + }); + }); + describe('repository URL authorization', () => { it('should set repositoryUrl URL for SSH connection type', async () => { const mockPreferencesService = mock(); diff --git a/packages/cli/src/modules/source-control.ee/source-control-git.service.ee.ts b/packages/cli/src/modules/source-control.ee/source-control-git.service.ee.ts index ce34b452af7..97ea9797e79 100644 --- a/packages/cli/src/modules/source-control.ee/source-control-git.service.ee.ts +++ b/packages/cli/src/modules/source-control.ee/source-control-git.service.ee.ts @@ -101,7 +101,9 @@ export class SourceControlGitService { if (!(await this.hasRemote(sourceControlPreferences.repositoryUrl))) { if (sourceControlPreferences.connected && sourceControlPreferences.repositoryUrl) { const instanceOwner = await this.ownershipService.getInstanceOwner(); - await this.initRepository(sourceControlPreferences, instanceOwner); + await this.initRepository(sourceControlPreferences, instanceOwner, { + tolerateTrackingFetchFailure: true, + }); } } @@ -222,6 +224,7 @@ export class SourceControlGitService { 'repositoryUrl' | 'branchName' | 'initRepo' | 'connectionType' >, user: User, + options?: { tolerateTrackingFetchFailure?: boolean }, ): Promise { if (!this.git) { throw new UnexpectedError('Git is not initialized (Promise)'); @@ -253,7 +256,7 @@ export class SourceControlGitService { user.email ?? SOURCE_CONTROL_DEFAULT_EMAIL, ); - await this.trackRemoteIfReady(branchName); + await this.trackRemoteIfReady(branchName, options?.tolerateTrackingFetchFailure); if (initRepo) { try { @@ -271,10 +274,18 @@ export class SourceControlGitService { * If this is a new local repository being set up after remote is ready, * then set this local to start tracking remote's target branch. */ - private async trackRemoteIfReady(targetBranch: string) { + private async trackRemoteIfReady(targetBranch: string, tolerateFetchFailure: boolean = false) { if (!this.git) return; - await this.fetch(); + try { + await this.fetch(); + } catch (error) { + if (!tolerateFetchFailure) { + throw error; + } + this.logger.warn('Failed to fetch during remote tracking setup', { error }); + return; // Don't fail startup initialization for recoverable remote issues + } const { currentBranch, branches: remoteBranches } = await this.getBranches();