Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 2 additions & 2 deletions .github/workflows/coderabbit-autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ concurrency:

jobs:
trigger-autofix:
if: ${{ github.event.review.user.login == 'coderabbitai' || github.event.review.user.login == 'coderabbitai[bot]' }}
if: ${{ (github.event.review.user.login == 'coderabbitai' || github.event.review.user.login == 'coderabbitai[bot]') && github.event.pull_request.head.repo.fork == false }}
runs-on: ubuntu-latest
steps:
- name: Request CodeRabbit autofix when unresolved threads exist
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
const pr = context.payload.pull_request;
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/coderabbit-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Request CodeRabbit review
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
const pr = context.payload.pull_request;
const body = [
"@coderabbitai review",
"@coderabbitai re-review !Stop making breaking changes, do a proper review!",
].join("\n");

await github.rest.issues.createComment({
Expand Down
214 changes: 180 additions & 34 deletions lib/text-diff.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function normalizeLineFeed(input) {
function normalizeLineFeed(input) {
if (typeof input !== 'string') {
return '';
}
Expand Down Expand Up @@ -34,23 +34,58 @@ function buildLcsMatrix(beforeLines, afterLines) {
return matrix;
}

function buildLineDiff(beforeText, afterText) {
const beforeLines = splitLines(beforeText);
const afterLines = splitLines(afterText);
const LINE_LIMIT = 3000;
if (beforeLines.length > LINE_LIMIT || afterLines.length > LINE_LIMIT) {
return {
lines: [],
stats: {
added: 0,
removed: 0,
unchanged: 0
},
oldLineCount: beforeLines.length,
newLineCount: afterLines.length,
truncated: true
};
function countDiffStats(lines) {
let added = 0;
let removed = 0;
let unchanged = 0;
for (const line of lines) {
if (line.type === 'add') {
added += 1;
} else if (line.type === 'del') {
removed += 1;
} else {
unchanged += 1;
}
}
return { added, removed, unchanged };
}

function buildCollapsedContextLine(hiddenCount) {
return {
type: 'context',
value: `... ${hiddenCount} unchanged lines ...`,
oldNumber: null,
newNumber: null
};
}

function compactContextRuns(lines, contextSize = 3) {
const compacted = [];
const keepCount = Number.isFinite(contextSize) ? Math.max(1, Math.floor(contextSize)) : 3;
let index = 0;
while (index < lines.length) {
if (!lines[index] || lines[index].type !== 'context') {
compacted.push(lines[index]);
index += 1;
continue;
}
const start = index;
while (index < lines.length && lines[index] && lines[index].type === 'context') {
index += 1;
}
const run = lines.slice(start, index);
if (run.length <= keepCount * 2 + 1) {
compacted.push(...run);
continue;
}
compacted.push(...run.slice(0, keepCount));
compacted.push(buildCollapsedContextLine(run.length - keepCount * 2));
compacted.push(...run.slice(-keepCount));
}
return compacted;
}

function buildExactDiffLines(beforeLines, afterLines) {
const matrix = buildLcsMatrix(beforeLines, afterLines);
const lines = [];
let i = beforeLines.length;
Expand Down Expand Up @@ -90,29 +125,140 @@ function buildLineDiff(beforeText, afterText) {
}
}
lines.reverse();
return lines;
}

let added = 0;
let removed = 0;
let unchanged = 0;
for (const line of lines) {
if (line.type === 'add') {
added += 1;
} else if (line.type === 'del') {
removed += 1;
} else {
unchanged += 1;
function findNextSyncPoint(beforeLines, afterLines, beforeIndex, afterIndex, lookahead = 64) {
const maxBeforeOffset = Math.min(lookahead, beforeLines.length - beforeIndex - 1);
const maxAfterOffset = Math.min(lookahead, afterLines.length - afterIndex - 1);

for (let offset = 1; offset <= maxAfterOffset; offset += 1) {
if (beforeLines[beforeIndex] === afterLines[afterIndex + offset]) {
return { beforeIndex, afterIndex: afterIndex + offset };
}
}
for (let offset = 1; offset <= maxBeforeOffset; offset += 1) {
if (beforeLines[beforeIndex + offset] === afterLines[afterIndex]) {
return { beforeIndex: beforeIndex + offset, afterIndex };
}
}

const maxDistance = maxBeforeOffset + maxAfterOffset;
for (let distance = 2; distance <= maxDistance; distance += 1) {
const beforeStart = Math.max(1, distance - maxAfterOffset);
const beforeEnd = Math.min(maxBeforeOffset, distance - 1);
for (let beforeOffset = beforeStart; beforeOffset <= beforeEnd; beforeOffset += 1) {
const afterOffset = distance - beforeOffset;
if (beforeLines[beforeIndex + beforeOffset] === afterLines[afterIndex + afterOffset]) {
return {
beforeIndex: beforeIndex + beforeOffset,
afterIndex: afterIndex + afterOffset
};
}
}
}
return null;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

function buildLargeDiffLines(beforeLines, afterLines) {
const rawLines = [];
let beforeIndex = 0;
let afterIndex = 0;

while (beforeIndex < beforeLines.length && afterIndex < afterLines.length) {
if (beforeLines[beforeIndex] === afterLines[afterIndex]) {
rawLines.push({
type: 'context',
value: beforeLines[beforeIndex],
oldNumber: beforeIndex + 1,
newNumber: afterIndex + 1
});
beforeIndex += 1;
afterIndex += 1;
continue;
}

const syncPoint = findNextSyncPoint(beforeLines, afterLines, beforeIndex, afterIndex);
if (!syncPoint) {
rawLines.push({
type: 'del',
value: beforeLines[beforeIndex],
oldNumber: beforeIndex + 1,
newNumber: null
});
rawLines.push({
type: 'add',
value: afterLines[afterIndex],
oldNumber: null,
newNumber: afterIndex + 1
});
beforeIndex += 1;
afterIndex += 1;
continue;
}

while (beforeIndex < syncPoint.beforeIndex) {
rawLines.push({
type: 'del',
value: beforeLines[beforeIndex],
oldNumber: beforeIndex + 1,
newNumber: null
});
beforeIndex += 1;
}
while (afterIndex < syncPoint.afterIndex) {
rawLines.push({
type: 'add',
value: afterLines[afterIndex],
oldNumber: null,
newNumber: afterIndex + 1
});
afterIndex += 1;
}
}

while (beforeIndex < beforeLines.length) {
rawLines.push({
type: 'del',
value: beforeLines[beforeIndex],
oldNumber: beforeIndex + 1,
newNumber: null
});
beforeIndex += 1;
}
while (afterIndex < afterLines.length) {
rawLines.push({
type: 'add',
value: afterLines[afterIndex],
oldNumber: null,
newNumber: afterIndex + 1
});
afterIndex += 1;
}

return {
lines: compactContextRuns(rawLines),
stats: countDiffStats(rawLines)
};
}

function buildLineDiff(beforeText, afterText) {
const beforeLines = splitLines(beforeText);
const afterLines = splitLines(afterText);
const LINE_LIMIT = 3000;
const result = (beforeLines.length > LINE_LIMIT || afterLines.length > LINE_LIMIT)
? buildLargeDiffLines(beforeLines, afterLines)
: {
lines: buildExactDiffLines(beforeLines, afterLines),
stats: null
};
const stats = result.stats || countDiffStats(result.lines);
return {
lines,
stats: {
added,
removed,
unchanged
},
lines: result.lines,
stats,
oldLineCount: beforeLines.length,
newLineCount: afterLines.length
newLineCount: afterLines.length,
truncated: false
};
}

Expand Down
5 changes: 5 additions & 0 deletions tests/unit/agents-diff-ui.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,9 @@ test('agents diff preview avoids extra file reads and caps api payload size', ()
assert.match(cliSource, /MAX_API_BODY_SIZE/);
assert.match(cliSource, /bodySize\s*>\s*MAX_API_BODY_SIZE/);
assert.match(cliSource, /buildAgentsDiff[\s\S]*metaOnly/);

const appSource = readProjectFile('web-ui/app.js');
assert.match(appSource, /buildAgentsDiffPreviewRequest\(/);
assert.match(appSource, /previewRequest\.exceedsBodyLimit/);
assert.match(appSource, /applyPreviewState\(buildAgentsDiffPreview\(/);
});
24 changes: 24 additions & 0 deletions tests/unit/coderabbit-workflows.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import assert from 'assert';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.resolve(__dirname, '..', '..');

function readProjectFile(relativePath) {
return fs.readFileSync(path.join(projectRoot, relativePath), 'utf8');
}

test('coderabbit autofix workflow skips fork pull requests and uses github-script v8', () => {
const workflow = readProjectFile('.github/workflows/coderabbit-autofix.yml');
assert.match(workflow, /github\.event\.pull_request\.head\.repo\.fork == false/);
assert.match(workflow, /uses:\s+actions\/github-script@v8/);
});

test('coderabbit review workflow uses github-script v8 and sends the re-review command', () => {
const workflow = readProjectFile('.github/workflows/coderabbit-review.yml');
assert.match(workflow, /uses:\s+actions\/github-script@v8/);
assert.match(workflow, /@coderabbitai re-review !Stop making breaking changes, do a proper review!/);
});
1 change: 1 addition & 0 deletions tests/unit/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ await import(pathToFileURL(path.join(__dirname, 'claude-settings-sync.test.mjs')
await import(pathToFileURL(path.join(__dirname, 'unzip-ext.test.mjs')));
await import(pathToFileURL(path.join(__dirname, 'provider-share-command.test.mjs')));
await import(pathToFileURL(path.join(__dirname, 'codex-proxy-options.test.mjs')));
await import(pathToFileURL(path.join(__dirname, 'coderabbit-workflows.test.mjs')));
await import(pathToFileURL(path.join(__dirname, 'session-tab-switch-performance.test.mjs')));

let failures = 0;
Expand Down
23 changes: 14 additions & 9 deletions tests/unit/text-diff.test.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import assert from 'assert';
import assert from 'assert';
import { createRequire } from 'module';

const require = createRequire(import.meta.url);
Expand Down Expand Up @@ -42,14 +42,19 @@ test('buildLineDiff ignores single trailing newline', () => {
assert.strictEqual(withoutTrailing.lines.length, 2);
});

test('buildLineDiff truncates large inputs', () => {
const lines = Array.from({ length: 3001 }, (_, index) => `line-${index}`);
const text = lines.join('\n');
const result = buildLineDiff(text, text);
assert.strictEqual(result.truncated, true);
assert.strictEqual(result.oldLineCount, 3001);
assert.strictEqual(result.newLineCount, 3001);
assert.strictEqual(result.lines.length, 0);
test('buildLineDiff still surfaces diff points for large inputs', () => {
const beforeLines = Array.from({ length: 3200 }, (_, index) => `line-${index}`);
const afterLines = beforeLines.slice();
afterLines.splice(1600, 1, 'line-1600-updated');
const result = buildLineDiff(beforeLines.join('\n'), afterLines.join('\n'));

assert.strictEqual(result.truncated, false);
assert.strictEqual(result.oldLineCount, 3200);
assert.strictEqual(result.newLineCount, 3200);
assert.strictEqual(result.stats.added, 1);
assert.strictEqual(result.stats.removed, 1);
assert.ok(result.lines.some(line => line.type === 'del' && line.value === 'line-1600'));
assert.ok(result.lines.some(line => line.type === 'add' && line.value === 'line-1600-updated'));
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

test('buildLineDiff tolerates non-string input', () => {
Expand Down
Loading
Loading