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
46 changes: 46 additions & 0 deletions services/cloud-agent-next/wrapper/src/session-bootstrap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,52 @@ describe('prepareWrapperBootstrapWorkspace', () => {
// blob could never be lazily fetched — it keeps a normal full clone.
const cloneCall = gitCalls.find(args => args[0] === 'clone');
expect(cloneCall).not.toContain('--filter=blob:none');
// The raw-token origin is stripped to a credential-free URL.
expect(gitCalls.some(args => args[0] === 'remote' && args[1] === 'set-url')).toBe(true);
});

it('uses a blobless clone and keeps the capability origin for a contained Bitbucket review session', async () => {
const request = makeRequest(tmpDir);
request.materialized.env.KILO_PLATFORM = 'code-review';
request.materialized.setupCommands = [];
request.repo = {
kind: 'git',
url: 'https://bitbucket.org/acme/repo.git',
token: 'kbb1.opaque-capability',
platform: 'bitbucket',
};
request.workspace.branchName = 'feature/login';

const gitCalls: string[][] = [];
await prepareWrapperBootstrapWorkspace(
request,
mock(() => {}),
{
git: async args => {
gitCalls.push(args);
if (args[0] === 'clone') {
await fsp.mkdir(path.join(request.workspace.workspacePath, '.git'), {
recursive: true,
});
}
if (args[0] === 'rev-parse') {
return { stdout: '', stderr: '', exitCode: 1 };
}
return { stdout: '', stderr: '', exitCode: 0 };
},
restoreSession: async () => ({
ok: true,
downloaded: false,
imported: true,
diffs: { applied: 0, skipped: 0, total: 0 },
}),
}
);

// A capability origin stays authenticated through the outbound interceptor,
// so the clone is blobless and the origin is NOT stripped.
expect(gitCalls.find(args => args[0] === 'clone')).toContain('--filter=blob:none');
expect(gitCalls.some(args => args[0] === 'remote' && args[1] === 'set-url')).toBe(false);
});

it('uses a blobless partial clone for GitLab review sessions', async () => {
Expand Down
53 changes: 37 additions & 16 deletions services/cloud-agent-next/wrapper/src/session-bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,14 +345,30 @@ function isBitbucketReviewSession(
return isCodeReviewSession(request) && repo?.kind === 'git' && repo.platform === 'bitbucket';
}

// Wire-format prefix of a Bitbucket outbound session capability (see the
// git-token-service BitbucketSessionCapabilityCodec). A capability in the origin
// stays authenticated through the outbound interceptor, unlike a raw token which
// is stripped after bootstrap.
const BITBUCKET_CAPABILITY_PREFIX = 'kbb1.';

function hasBitbucketReviewCapability(request: WrapperSessionReadyRequest): boolean {
return (
isBitbucketReviewSession(request) &&
typeof request.repo.token === 'string' &&
request.repo.token.startsWith(BITBUCKET_CAPABILITY_PREFIX)
);
}

function isBloblessReviewCloneEligible(request: WrapperSessionReadyRequest): boolean {
if (!isCodeReviewSession(request)) return false;
const repo = request.repo;
// Only GitHub and GitLab: their session origin keeps working credentials via
// outbound injection, so blobs deferred by a partial clone can be fetched
// lazily during the review. Bitbucket's origin is credential-stripped, and
// other/unknown git remotes have no such guarantee, so they keep a full clone.
return repo?.kind === 'github' || (repo?.kind === 'git' && repo.platform === 'gitlab');
// GitHub/GitLab keep working credentials via outbound injection. Bitbucket
// keeps them only when the session uses an outbound capability (a raw-token
// origin is credential-stripped after bootstrap). Other/unknown git remotes
// have no such guarantee, so they keep a full clone.
if (repo?.kind === 'github') return true;
if (repo?.kind === 'git' && repo.platform === 'gitlab') return true;
return hasBitbucketReviewCapability(request);
}

async function cloneRepository(
Expand All @@ -369,14 +385,14 @@ async function cloneRepository(
const gitUrl = repo.kind === 'github' ? `https://github.com/${repo.repo}.git` : repo.url;
const platform = repo.kind === 'git' ? repo.platform : 'github';
const repoUrl = authenticatedUrl(gitUrl, repo.token, platform);
// GitHub/GitLab code review reads changed files from the working tree and gets
// the PR diff from the provider API or a local `git diff <prev>..HEAD`. It needs
// the full commit graph but not every historical file blob, so a blobless
// partial clone keeps the clone bounded by the current working tree (git fetches
// blobs lazily on demand) instead of full history, which on large repositories
// otherwise exceeds the clone timeout. Full history is retained, so incremental
// diffs and merge-base still work. See isBloblessReviewCloneEligible for why
// only GitHub/GitLab qualify.
// Code review reads changed files from the working tree and gets the PR diff
// from the provider API or a local `git diff <prev>..HEAD`. It needs the full
// commit graph but not every historical file blob, so a blobless partial clone
// fetches blobs lazily on demand instead of downloading every historical blob,
// which on large repositories otherwise exceeds the clone timeout. Full history
// is retained, so incremental diffs and merge-base still work. See
// isBloblessReviewCloneEligible for which sessions qualify (GitHub, GitLab, and
// capability-backed Bitbucket, whose origin stays authenticated for lazy fetch).
const useBlobless = isBloblessReviewCloneEligible(request);

const runClone = async (blobless: boolean): Promise<ExecResult> => {
Expand Down Expand Up @@ -539,12 +555,17 @@ async function sanitizeBitbucketCodeReviewRemote(
request: WrapperSessionReadyRequest,
runGit: GitRunner
): Promise<boolean> {
// Single source of truth with the blobless-skip check in cloneRepository: the
// filter is skipped for exactly this session type because this function strips
// origin credentials, which would break a partial clone's later blob fetches.
if (!isBitbucketReviewSession(request)) {
return false;
}
// A capability origin stays authenticated through the outbound interceptor and
// is safe to expose (scoped to one repo, useless outside this container), so it
// must stay in place for a blobless clone's later lazy blob fetches. Only a raw
// workspace token needs stripping. Either way this is a handled code-review
// remote (return true), so callers do not refresh a token over it.
if (hasBitbucketReviewCapability(request)) {
return true;
}
const canonicalUrl = new URL(request.repo.url);
canonicalUrl.username = '';
canonicalUrl.password = '';
Expand Down