mirror of
https://github.com/n8n-io/n8n
synced 2026-04-21 15:47:20 +00:00
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:
parent
24015b3449
commit
fa3299d042
2 changed files with 58 additions and 5 deletions
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue