From b11e3380af9972d586c14a85d54580df4231f728 Mon Sep 17 00:00:00 2001 From: thomvaill Date: Thu, 26 Mar 2026 20:04:59 +0100 Subject: [PATCH] fix(github-provider): validateLocationsExist: true + filters.branch bug Signed-off-by: thomvaill --- .changeset/quiet-streets-laugh.md | 5 + .../src/lib/github.test.ts | 97 ++++++++++++++++++- .../src/lib/github.ts | 8 +- .../providers/GithubEntityProvider.test.ts | 12 ++- .../src/providers/GithubEntityProvider.ts | 2 + 5 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 .changeset/quiet-streets-laugh.md diff --git a/.changeset/quiet-streets-laugh.md b/.changeset/quiet-streets-laugh.md new file mode 100644 index 0000000000..9c4007ff72 --- /dev/null +++ b/.changeset/quiet-streets-laugh.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Fixed a bug where `GithubEntityProvider` with `validateLocationsExist: true` and `filters.branch` configured would always check for the catalog file on the repository's default branch (`HEAD`) instead of the configured branch. This caused repositories to be filtered out when the catalog file only existed on the non-default branch. diff --git a/plugins/catalog-backend-module-github/src/lib/github.test.ts b/plugins/catalog-backend-module-github/src/lib/github.test.ts index 6933c9a245..554a482422 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.test.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.test.ts @@ -29,6 +29,7 @@ import { getOrganizationUsers, getTeamMembers, getOrganizationRepositories, + getOrganizationRepository, QueryResponse, GithubUser, GithubTeam, @@ -778,15 +779,105 @@ describe('github', () => { }; server.use( - graphqlMsw.query('repositories', () => - HttpResponse.json({ data: input }), - ), + graphqlMsw.query('repositories', ({ variables }) => { + expect(variables.catalogPathRef).toBe('HEAD:catalog-info.yaml'); + return HttpResponse.json({ data: input }); + }), ); await expect( getOrganizationRepositories(graphql, 'a', 'catalog-info.yaml'), ).resolves.toEqual(output); }); + + it('uses provided branch for catalog path ref', async () => { + server.use( + graphqlMsw.query('repositories', ({ variables }) => { + expect(variables.catalogPathRef).toBe('develop:catalog-info.yaml'); + return HttpResponse.json({ + data: { + repositoryOwner: { + repositories: { + pageInfo: { hasNextPage: false, endCursor: null }, + nodes: [ + { + name: 'repo1', + url: 'https://github.com/my-org/repo1', + isArchived: false, + isFork: false, + visibility: 'public', + defaultBranchRef: { name: 'main' }, + catalogInfoFile: null, + repositoryTopics: { nodes: [] }, + }, + ], + }, + }, + }, + }); + }), + ); + + await getOrganizationRepositories( + graphql as any, + 'my-org', + '/catalog-info.yaml', + undefined, + 'develop', + ); + }); + }); + + describe('getOrganizationRepository', () => { + const repositoryData = { + repositoryOwner: { + repository: { + name: 'my-repo', + url: 'https://github.com/my-org/my-repo', + isArchived: false, + isFork: false, + visibility: 'public', + defaultBranchRef: { name: 'main' }, + catalogInfoFile: null, + repositoryTopics: { nodes: [] }, + }, + }, + }; + + it('defaults catalogPathRef to HEAD when no branch is provided', async () => { + server.use( + graphqlMsw.query('repository', ({ variables }) => { + expect(variables.catalogPathRef).toBe('HEAD:catalog-info.yaml'); + return HttpResponse.json({ data: repositoryData }); + }), + ); + + await getOrganizationRepository( + graphql as any, + 'my-org', + 'my-repo', + 'catalog-info.yaml', + ); + }); + + it('uses provided branch for catalogPathRef', async () => { + server.use( + graphqlMsw.query('repository', ({ variables }) => { + expect(variables.catalogPathRef).toBe( + 'my-feature-branch:catalog-info.yaml', + ); + return HttpResponse.json({ data: repositoryData }); + }), + ); + + await getOrganizationRepository( + graphql as any, + 'my-org', + 'my-repo', + 'catalog-info.yaml', + 'my-feature-branch', + ); + }); }); describe('createAddEntitiesOperation', () => { diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index 0690ca125b..200c7d260d 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -594,6 +594,7 @@ export async function getOrganizationRepositories( org: string, catalogPath: string, pageSizes: GithubPageSizes = DEFAULT_PAGE_SIZES, + branch?: string, ): Promise<{ repositories: RepositoryResponse[] }> { let relativeCatalogPathRef: string; // We must strip the leading slash or the query for objects does not work @@ -602,7 +603,8 @@ export async function getOrganizationRepositories( } else { relativeCatalogPathRef = catalogPath; } - const catalogPathRef = `HEAD:${relativeCatalogPathRef}`; + const branchRef = branch ?? 'HEAD'; + const catalogPathRef = `${branchRef}:${relativeCatalogPathRef}`; const query = ` query repositories($org: String!, $catalogPathRef: String!, $cursor: String, $repositoriesPageSize: Int!) { repositoryOwner(login: $org) { @@ -663,6 +665,7 @@ export async function getOrganizationRepository( org: string, repoName: string, catalogPath: string, + branch?: string, ): Promise { let relativeCatalogPathRef: string; // We must strip the leading slash or the query for objects does not work @@ -671,7 +674,8 @@ export async function getOrganizationRepository( } else { relativeCatalogPathRef = catalogPath; } - const catalogPathRef = `HEAD:${relativeCatalogPathRef}`; + const branchRef = branch ?? 'HEAD'; + const catalogPathRef = `${branchRef}:${relativeCatalogPathRef}`; const query = ` query repository($org: String!, $repoName: String!, $catalogPathRef: String!) { repositoryOwner(login: $org) { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts index 57d933d165..e9e106cce9 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts @@ -411,7 +411,7 @@ describe('GithubEntityProvider', () => { organization: 'test-org', catalogPath: 'catalog-custom.yaml', filters: { - branch: 'main', + branch: 'backstage', }, validateLocationsExist: true, }, @@ -480,7 +480,15 @@ describe('GithubEntityProvider', () => { expect(taskDef.id).toEqual('github-provider:myProvider:refresh'); await (taskDef.fn as () => Promise)(); - const url = `https://github.com/test-org/another-repo/blob/main/catalog-custom.yaml`; + expect(mockGetOrganizationRepositories).toHaveBeenCalledWith( + expect.anything(), + 'test-org', + 'catalog-custom.yaml', + expect.any(Object), + 'backstage', + ); + + const url = `https://github.com/test-org/another-repo/blob/backstage/catalog-custom.yaml`; const expectedEntities = createExpectedEntitiesForUrl(url); expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index cbb719a04e..e215337d8c 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -275,6 +275,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { organization, catalogPath, pageSizes, + this.config.filters.branch, ); repositories = repositories.concat( repositoriesFromGithub.map(r => @@ -664,6 +665,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { organization, repository.name, catalogPath, + this.config.filters.branch, ).then(r => r ? this.createRepoFromGithubResponse(r, organization) : null, );