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
2 changes: 1 addition & 1 deletion backend/src/database/repositories/githubReposRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default class GithubReposRepository {
)
throw new Error400(
options.language,
'errors.integrations.githubRepoAlreadyMapped',
'errors.integrations.repoAlreadyMapped',
row.url,
integrationId,
row.integrationId,
Expand Down
31 changes: 30 additions & 1 deletion backend/src/database/repositories/gitlabReposRepository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import trim from 'lodash/trim'
import { QueryTypes } from 'sequelize'

import { DEFAULT_TENANT_ID } from '@crowd/common'
import { DEFAULT_TENANT_ID, Error400 } from '@crowd/common'
import { RedisCache } from '@crowd/redis'

import { IRepositoryOptions } from './IRepositoryOptions'
Expand Down Expand Up @@ -51,6 +51,35 @@ export default class GitlabReposRepository {
}

static async updateMapping(integrationId, mapping, options: IRepositoryOptions) {
const transaction = SequelizeRepository.getTransaction(options)

// Check for repositories already mapped to other integrations
for (const url of Object.keys(mapping)) {
const existingRows = await options.database.sequelize.query(
`SELECT * FROM "gitlabRepos" WHERE url = :url AND "deletedAt" IS NULL`,
{
replacements: { url },
type: QueryTypes.SELECT,
transaction,
},
)

for (const row of existingRows as any[]) {
if (!row.deletedAt && row.integrationId !== integrationId) {
options.log.warn(
`Trying to update gitlab repo ${row.url} mapping with integrationId ${integrationId} but it is already mapped to integration ${row.integrationId}!`,
)

throw new Error400(
options.language,
'errors.integrations.repoAlreadyMapped',
row.url,
integrationId,
row.integrationId,
)
}
}
}
await GitlabReposRepository.bulkInsert(
'gitlabRepos',
['tenantId', 'integrationId', 'segmentId', 'url'],
Expand Down
29 changes: 29 additions & 0 deletions backend/src/database/repositories/segmentRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,35 @@ class SegmentRepository extends RepositoryBase<
return this.findById(records[0].id)
}

async findByName(name: string, level: SegmentLevel) {
const transaction = this.transaction

let findByNameQuery = `SELECT * FROM segments WHERE name = :name AND "tenantId" = :tenantId`

if (level === SegmentLevel.SUB_PROJECT) {
findByNameQuery += ` and "parentSlug" is not null and "grandparentSlug" is not null`
} else if (level === SegmentLevel.PROJECT) {
findByNameQuery += ` and "parentSlug" is not null and "grandparentSlug" is null`
} else if (level === SegmentLevel.PROJECT_GROUP) {
findByNameQuery += ` and "parentSlug" is null and "grandparentSlug" is null`
}

const records = await this.options.database.sequelize.query(findByNameQuery, {
replacements: {
name,
tenantId: this.options.currentTenant.id,
},
type: QueryTypes.SELECT,
transaction,
})

if (records.length === 0) {
return null
}

return this.findById(records[0].id)
}

async findInIds(ids: string[]): Promise<SegmentData[]> {
if (ids.length === 0) {
return []
Expand Down
95 changes: 81 additions & 14 deletions backend/src/services/integrationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { request } from '@octokit/request'
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'
import lodash from 'lodash'
import moment from 'moment'
import { Transaction } from 'sequelize'
import { QueryTypes, Transaction } from 'sequelize'

import { EDITION, Error400, Error404, Error542, encryptData } from '@crowd/common'
import { CommonIntegrationService, getGithubInstallationToken } from '@crowd/common_services'
Expand Down Expand Up @@ -1391,6 +1391,38 @@ export default class IntegrationService {
options,
)

// Check for repositories already mapped to other integrations
const seq = SequelizeRepository.getSequelize({ ...(options || this.options), transaction })
const urls = remotes.map((r) => r.url)

const existingRows = await seq.query(
`
SELECT url, "integrationId" FROM git.repositories
WHERE url IN (:urls) AND "deletedAt" IS NULL
`,
{
replacements: { urls },
type: QueryTypes.SELECT,
transaction,
},
)

for (const row of existingRows as any[]) {
if (row.integrationId !== integration.id) {
this.options.log.warn(
`Trying to update git repo ${row.url} mapping with integrationId ${integration.id} but it is already mapped to integration ${row.integrationId}!`,
)

throw new Error400(
(options || this.options).language,
'errors.integrations.repoAlreadyMapped',
row.url,
integration.id,
row.integrationId,
)
}
}

// upsert repositories to git.repositories in order to be processed by git-integration V2
const qx = SequelizeRepository.getQueryExecutor({
...(options || this.options),
Expand Down Expand Up @@ -1681,6 +1713,54 @@ export default class IntegrationService {
host = orgUrl
}

const stripGit = (url: string) => {
if (url.endsWith('.git')) {
return url.slice(0, -4)
}
return url
}

// Build full repository URLs from orgURL and repo names
const remotes = integrationData.remote.repoNames.map((repoName) => {
const fullUrl = stripGit(`${integrationData.remote.orgURL}/${repoName}`)
return { url: fullUrl, forkedFrom: null }
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gerrit remotes built before enableAllRepos updates repoNames

The remotes array is built from integrationData.remote.repoNames before getGerritServerRepos is called. When enableAllRepos is true, integrationData.remote.repoNames gets updated with the full server list at line 1767, but remotes still contains the initial (possibly empty or partial) list. This causes two problems: the conflict check (lines 1729-1763) only validates the initial repos, and gitConnectOrUpdate (line 1806) only syncs the initial repos, while the integration settings store the complete list. This mismatch means repositories could be added without proper conflict checking and won't be synced to git.

Additional Locations (1)

Fix in Cursor Fix in Web


// Check for conflicts with existing Gerrit integrations
for (const remote of remotes) {
const existingGerritIntegrations = await this.options.database.sequelize.query(
`SELECT id, settings FROM integrations
WHERE platform = 'gerrit' AND "deletedAt" IS NULL`,
{
type: QueryTypes.SELECT,
transaction,
},
)

for (const existingIntegration of existingGerritIntegrations as any[]) {
const settings = existingIntegration.settings
if (settings?.remote?.repoNames && settings?.remote?.orgURL) {
const existingRemotes = settings.remote.repoNames.map((repoName) =>
stripGit(`${settings.remote.orgURL}/${repoName}`),
)

if (existingRemotes.includes(remote.url)) {
this.options.log.warn(
`Trying to map Gerrit repository ${remote.url} with integrationId ${integration?.id || connectionId} but it is already mapped to integration ${existingIntegration.id}!`,
)

throw new Error400(
this.options.language,
'errors.integrations.repoAlreadyMapped',
remote.url,
integration?.id || connectionId,
existingIntegration.id,
)
}
}
}
}

const res = await IntegrationService.getGerritServerRepos(orgUrl)
if (integrationData.remote.enableAllRepos) {
integrationData.remote.repoNames = res
Expand Down Expand Up @@ -1711,13 +1791,6 @@ export default class IntegrationService {
)

if (integrationData.remote.enableGit) {
const stripGit = (url: string) => {
if (url.endsWith('.git')) {
return url.slice(0, -4)
}
return url
}

const segmentOptions: IRepositoryOptions = {
...this.options,
transaction,
Expand All @@ -1728,12 +1801,6 @@ export default class IntegrationService {
],
}

// Build full repository URLs from orgURL and repo names
const remotes = integrationData.remote.repoNames.map((repoName) => {
const fullUrl = stripGit(`${integrationData.remote.orgURL}/${repoName}`)
return { url: fullUrl, forkedFrom: null }
})

await this.gitConnectOrUpdate(
{
remotes,
Expand Down
Loading