Skip to content

Commit 327b8b7

Browse files
committed
fix(connectors): keep the Bitbucket cap flag set when it skips the pull request phase
Review round 3. Fixes a regression from 622a21b. maxItems is shared across the code and pull request phases, so a code walk that ends with exactly maxItems documents and no next link or frontier stops pagination before the pull request phase runs. Scoping listingCapped to "this phase had more" left the flag unset in that case, and the engine then treated the run as a complete enumeration and could hard-delete previously indexed pr:* documents that were never listed. The cap flag now asks whether anything the connector was configured to list remains unlisted -- including a later phase the cap is about to stop us reaching.
1 parent 622a21b commit 327b8b7

2 files changed

Lines changed: 31 additions & 10 deletions

File tree

apps/sim/connectors/bitbucket/bitbucket.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,22 @@ describe('bitbucket maxItems cap', () => {
372372
expect(syncContext.listingCapped).toBeUndefined()
373373
})
374374

375+
it('flags the listing capped when the code walk ends on the cap and the pull request phase never runs', async () => {
376+
mockApi([[/\/src\//, () => jsonResponse({ values: [fileEntry('a.md'), fileEntry('b.md')] })]])
377+
378+
const syncContext: Record<string, unknown> = {}
379+
const result = await bitbucketConnector.listDocuments(
380+
ACCESS_TOKEN,
381+
{ ...CONFIG, contentTypes: 'all', maxItems: '2' },
382+
undefined,
383+
syncContext
384+
)
385+
386+
expect(result.documents).toHaveLength(2)
387+
expect(result.hasMore).toBe(false)
388+
expect(syncContext.listingCapped).toBe(true)
389+
})
390+
375391
it('flags the listing capped when the cap lands on a page boundary with more to come', async () => {
376392
mockApi([
377393
[

apps/sim/connectors/bitbucket/bitbucket.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -715,19 +715,24 @@ function pendingDirectories(syncContext: Record<string, unknown> | undefined): s
715715
* syncContext and flagging `listingCapped` when the cap truncates the listing.
716716
* Skipped (oversized) documents ride along without consuming the cap.
717717
*
718-
* `sourceHasMore` is whether Bitbucket still had content beyond this page — a
719-
* `next` link, or directories still queued on the frontier. It is required because
720-
* `takeIndexableWithinCap` reports `capReached` as soon as the running total
721-
* *equals* maxItems, which is also true of a listing that ended at exactly that
722-
* count. Setting `listingCapped` there would suppress deletion reconciliation for
723-
* a listing that was in fact complete, leaving upstream-deleted files indexed
724-
* indefinitely, so the flag is set only when the cap actually withheld something.
718+
* `moreToEnumerate` is whether anything the connector was configured to list
719+
* remains unlisted beyond this page: a `next` link, directories still queued on
720+
* the frontier, or a later phase that the cap is about to stop us reaching.
721+
*
722+
* It is required because `takeIndexableWithinCap` reports `capReached` as soon as
723+
* the running total *equals* maxItems, which is also true of a listing that ended
724+
* at exactly that count. Setting `listingCapped` unconditionally there suppresses
725+
* deletion reconciliation for a complete listing; not setting it when a later
726+
* phase is skipped is worse still, because the engine would treat the run as a
727+
* complete enumeration and hard-delete that phase's previously indexed documents.
728+
* `maxItems` is shared across phases, so the code walk ending exactly on the cap
729+
* is precisely when the pull-request phase never runs.
725730
*/
726731
function applyMaxItemsCap(
727732
documents: ExternalDocument[],
728733
maxItems: number,
729734
syncContext: Record<string, unknown> | undefined,
730-
sourceHasMore: boolean
735+
moreToEnumerate: boolean
731736
): { documents: ExternalDocument[]; capped: boolean } {
732737
if (maxItems <= 0) return { documents, capped: false }
733738
const alreadyIndexed = (syncContext?.totalDocsFetched as number) ?? 0
@@ -743,7 +748,7 @@ function applyMaxItemsCap(
743748
)
744749
if (syncContext) {
745750
syncContext.totalDocsFetched = alreadyIndexed + indexableCount
746-
const withheld = taken.length < documents.length || sourceHasMore
751+
const withheld = taken.length < documents.length || moreToEnumerate
747752
if (capReached && withheld) syncContext.listingCapped = true
748753
}
749754
return { documents: taken, capped: capReached }
@@ -933,7 +938,7 @@ export const bitbucketConnector: ConnectorConfig = {
933938
documents,
934939
maxItems,
935940
syncContext,
936-
Boolean(page.next) || frontier.length > 0
941+
Boolean(page.next) || frontier.length > 0 || Boolean(nextPhase('code', choice))
937942
)
938943
if (hitLimit) return { documents: capped, hasMore: false }
939944

0 commit comments

Comments
 (0)