fix(core): Handle git fetch failure during source control startup (#28422)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ali Elkhateeb 2026-04-14 14:40:06 +02:00 committed by GitHub
parent 24015b3449
commit fa3299d042
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 58 additions and 5 deletions

View file

@ -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<any>();
const gitService = new SourceControlGitService(mockLogger, mock(), mock());
const prefs = mock<SourceControlPreferences>({ branchName: 'main' });
const user = mock<User>();
const git = mock<SimpleGit>();
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<SourceControlPreferences>({ branchName: 'main' });
const user = mock<User>();
const git = mock<SimpleGit>();
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<SourceControlPreferencesService>();

View file

@ -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<void> {
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();