mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
fix: bound github merge metadata cache (#4154)
This commit is contained in:
@@ -100,6 +100,7 @@ import {
|
||||
setPRAutoMerge,
|
||||
updatePRState,
|
||||
updatePRTitle,
|
||||
_getMergeQueueCacheSizeForTests,
|
||||
_resetOwnerRepoCache,
|
||||
_resetMergeQueueCacheForTests
|
||||
} from './client'
|
||||
@@ -1686,6 +1687,41 @@ describe('GitHub GraphQL rate-limit guard', () => {
|
||||
).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('bounds merge metadata cache entries across many base branches', async () => {
|
||||
getOwnerRepoMock.mockResolvedValue({ owner: 'stablyai', repo: 'orca' })
|
||||
let prViewCount = 0
|
||||
ghExecFileAsyncMock.mockImplementation(async (args) => {
|
||||
if (args.includes('graphql')) {
|
||||
return { stdout: JSON.stringify({ data: { repository: { mergeQueue: null } } }) }
|
||||
}
|
||||
prViewCount += 1
|
||||
return {
|
||||
stdout: JSON.stringify({
|
||||
number: prViewCount,
|
||||
title: 'PR',
|
||||
state: 'OPEN',
|
||||
url: `https://github.com/stablyai/orca/pull/${prViewCount}`,
|
||||
statusCheckRollup: [],
|
||||
updatedAt: '2026-04-01T00:00:00Z',
|
||||
isDraft: false,
|
||||
mergeable: 'MERGEABLE',
|
||||
reviewDecision: 'APPROVED',
|
||||
mergeStateStatus: 'CLEAN',
|
||||
autoMergeRequest: null,
|
||||
baseRefName: `base-${prViewCount}`,
|
||||
baseRefOid: 'base-oid',
|
||||
headRefOid: 'head-oid'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
for (let i = 0; i < 260; i++) {
|
||||
await getPRForBranch('/repo-root', `feature/${i}`, i + 1)
|
||||
}
|
||||
|
||||
expect(_getMergeQueueCacheSizeForTests()).toBe(256)
|
||||
})
|
||||
|
||||
it('returns conflicting file details instead of running gh merge when PR is dirty', async () => {
|
||||
ghExecFileAsyncMock.mockResolvedValueOnce({
|
||||
stdout: JSON.stringify({
|
||||
|
||||
@@ -90,6 +90,7 @@ type GhExecOptions = ReturnType<typeof ghRepoExecOptions>
|
||||
const ORCA_REPO = 'stablyai/orca'
|
||||
const MERGE_QUEUE_CACHE_TTL_MS = 10 * 60 * 1000
|
||||
const MERGE_QUEUE_UNKNOWN_CACHE_TTL_MS = 60 * 1000
|
||||
const MERGE_QUEUE_CACHE_MAX_ENTRIES = 256
|
||||
type GitHubRepositoryMergeMetadata = {
|
||||
mergeQueueRequired: boolean | null
|
||||
mergeMethodSettings?: GitHubPRMergeMethodSettings
|
||||
@@ -103,6 +104,25 @@ export function _resetMergeQueueCacheForTests(): void {
|
||||
repositoryMergeMetadataCache.clear()
|
||||
}
|
||||
|
||||
export function _getMergeQueueCacheSizeForTests(): number {
|
||||
return repositoryMergeMetadataCache.size
|
||||
}
|
||||
|
||||
function pruneRepositoryMergeMetadataCache(now = Date.now()): void {
|
||||
for (const [cacheKey, cached] of repositoryMergeMetadataCache) {
|
||||
if (cached.expiresAt <= now) {
|
||||
repositoryMergeMetadataCache.delete(cacheKey)
|
||||
}
|
||||
}
|
||||
while (repositoryMergeMetadataCache.size > MERGE_QUEUE_CACHE_MAX_ENTRIES) {
|
||||
const oldestKey = repositoryMergeMetadataCache.keys().next().value
|
||||
if (oldestKey === undefined) {
|
||||
break
|
||||
}
|
||||
repositoryMergeMetadataCache.delete(oldestKey)
|
||||
}
|
||||
}
|
||||
|
||||
async function assertRateLimitBudget(bucket: RateLimitBucketKind): Promise<void> {
|
||||
await getRateLimit()
|
||||
const guard = rateLimitGuard(bucket)
|
||||
@@ -1803,10 +1823,16 @@ function cacheRepositoryMergeMetadata(
|
||||
value: GitHubRepositoryMergeMetadata,
|
||||
ttlMs: number
|
||||
): void {
|
||||
const now = Date.now()
|
||||
pruneRepositoryMergeMetadataCache(now)
|
||||
// Why: merge metadata is keyed by user-controlled branch names. Keep the
|
||||
// per-session cache bounded even if many short-lived branches are inspected.
|
||||
repositoryMergeMetadataCache.delete(cacheKey)
|
||||
repositoryMergeMetadataCache.set(cacheKey, {
|
||||
value,
|
||||
expiresAt: Date.now() + ttlMs
|
||||
expiresAt: now + ttlMs
|
||||
})
|
||||
pruneRepositoryMergeMetadataCache(now)
|
||||
}
|
||||
|
||||
async function detectRepositoryMergeMetadata(
|
||||
@@ -1817,8 +1843,9 @@ async function detectRepositoryMergeMetadata(
|
||||
const cacheKey = `${ownerRepo.owner.toLowerCase()}/${ownerRepo.repo.toLowerCase()}:${
|
||||
branchName ?? '__repo__'
|
||||
}`
|
||||
pruneRepositoryMergeMetadataCache()
|
||||
const cached = repositoryMergeMetadataCache.get(cacheKey)
|
||||
if (cached && cached.expiresAt > Date.now()) {
|
||||
if (cached) {
|
||||
return cached.value
|
||||
}
|
||||
const guard = rateLimitGuard('graphql')
|
||||
|
||||
Reference in New Issue
Block a user