Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,10 @@ describe('gitlabService', () => {
expect(gitlab.deleteProjectGroupRepo).toHaveBeenCalledTimes(1)
})

it('should not delete orphan repositories if purge disabled', async () => {
it('should delete owned orphan repositories even when purge is disabled', async () => {
// Aligns with the legacy Fastify server: an explicit repository deletion drops the
// DB row and relies on this reconciliation to remove the GitLab project. The opt-in
// `purge` gate must not block that path, so owned orphans are always deleted.
const project = makeProjectWithDetails({
repositories: [],
})
Expand All @@ -168,12 +171,14 @@ describe('gitlabService', () => {
gitlab.getRepos.mockImplementation(() => (async function* () {
yield orphanRepo
})())
gitlab.deleteProjectGroupRepo.mockResolvedValue(undefined)
gitlab.upsertProjectMirrorRepo.mockResolvedValue(makeProjectSchema({ id: 1, name: 'mirror', path: 'mirror', path_with_namespace: 'forge/console/project-1/mirror', empty_repo: false }))
gitlab.getOrCreateMirrorPipelineTriggerToken.mockResolvedValue(makePipelineTriggerToken())

await service.handleUpsert(project)

expect(gitlab.deleteProjectGroupRepo).not.toHaveBeenCalled()
expect(gitlab.deleteProjectGroupRepo).toHaveBeenCalledWith(project.slug, 'orphan-repo')
expect(gitlab.deleteProjectGroupRepo).toHaveBeenCalledTimes(1)
})

it('should not delete orphan repositories without the correct topic even if purge enabled', async () => {
Expand Down
26 changes: 8 additions & 18 deletions apps/server-nestjs/src/modules/gitlab/gitlab.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,28 +398,18 @@ export class GitlabService {
const gitlabRepositories = await getAll(this.gitlab.getRepos(project.slug))
span?.setAttribute('gitlab.repositories.count', gitlabRepositories.length)

// Delete console-owned repos no longer tracked (e.g. console repository deletion).
const orphanRepos = gitlabRepositories.filter(r => isOwnedRepo(r) && !isSystemRepo(project, r))
span?.setAttribute('orphan.repositories.count', orphanRepos.length)

if (specificallyEnabled(getProjectPluginConfig(project, PURGE_PLUGIN_KEY))) {
span?.setAttribute('purge.enabled', true)
let removedCount = 0
await Promise.all(orphanRepos.map(async (orphan) => {
await this.gitlab.deleteProjectGroupRepo(project.slug, orphan.name)
removedCount++
this.logger.log(`Removed a repository from the GitLab project (project=${project.slug}, repoName=${orphan.name})`)
}))
let removedCount = 0
await Promise.all(orphanRepos.map(async (orphan) => {
await this.gitlab.deleteProjectGroupRepo(project.slug, orphan.name)
removedCount++
this.logger.log(`Removed a repository from the GitLab project (project=${project.slug}, repoName=${orphan.name})`)
}))

span?.setAttribute('orphan.repositories.removed.count', removedCount)
} else {
span?.setAttribute('purge.enabled', false)
let warnedCount = 0
for (const orphan of orphanRepos) {
warnedCount++
this.logger.warn(`Repository is in GitLab but not in the project definition (purge disabled, project=${project.slug}, repoName=${orphan.name})`)
}
span?.setAttribute('managed.repositories.warned.count', warnedCount)
}
span?.setAttribute('orphan.repositories.removed.count', removedCount)
}

@StartActiveSpan()
Expand Down