diff --git a/scripts/review.sh b/scripts/review.sh index e476d54..7fbf034 100755 --- a/scripts/review.sh +++ b/scripts/review.sh @@ -70,94 +70,134 @@ chmod 600 "$AUTH_FILE" echo "Configured provider: ${PI_PROVIDER}" echo "::endgroup::" -# ─── Phase 2: Generate diff ─────────────────────────────────────────────────── -echo "::group::Generate diff" +# ─── Phase 2: Fetch diff via API ─────────────────────────────────────────────── +echo "Generate diff" -# Configure git auth using the provided token, so we can fetch inside Docker. -# actions/checkout@v5 stores credentials in $RUNNER_TEMP which isn't mounted -# into the container, so we re-authenticate using the token input. -if [ -n "${PI_TOKEN}" ]; then - REMOTE_URL=$(git remote get-url origin 2>/dev/null || echo "") - if echo "$REMOTE_URL" | grep -q '://'; then - # HTTP(S) remote: inject token into URL - # e.g. https://git.example.com/owner/repo.git → https://token:xxx@git.example.com/owner/repo.git - PROTOCOL=$(echo "$REMOTE_URL" | sed -E 's|^(https?://).*|\1|') - HOST_PATH=$(echo "$REMOTE_URL" | sed -E 's|^https?://||') - git remote set-url origin "${PROTOCOL}token:${PI_TOKEN}@${HOST_PATH}" - echo "Git auth configured via remote URL" - fi +# Git operations inside the Docker container have no auth credentials +# (actions/checkout@v5 stores them in $RUNNER_TEMP, which isn't mounted). +# Instead, we get the diff directly from the Gitea/GitHub API using the token +# we already have for posting comments. + +# Detect platform and resolve PR info +if [ -n "${GITEA_SERVER_URL:-}" ]; then + API_BASE="${GITEA_SERVER_URL}/api/v1" + PR_NUMBER="${GITEA_EVENT_PULL_REQUEST_NUMBER:-}" + REPO="${GITEA_REPOSITORY:-}" + echo "Platform: Gitea (${GITEA_SERVER_URL})" +else + API_BASE="${GITHUB_API_URL:-https://api.github.com}" + PR_NUMBER="${GITHUB_EVENT_PULL_REQUEST_NUMBER:-}" + REPO="${GITHUB_REPOSITORY:-}" + echo "Platform: GitHub" fi -# Now find the base branch. With auth configured, fetch should work. -BASE="" +echo "Repo: ${REPO}, PR: ${PR_NUMBER}" -# 1. Check if remote tracking refs already exist (from a pre-step) -for candidate in origin/main origin/master; do - if git rev-parse --verify "$candidate" >/dev/null 2>&1; then - BASE="$candidate" - echo "Found existing ref: ${BASE}" - break - fi -done - -# 2. Try Gitea/GitHub event context for target branch name -if [ -z "$BASE" ]; then - TARGET_BRANCH="${GITEA_BASE_REF:-${GITHUB_BASE_REF:-}}" - if [ -n "${TARGET_BRANCH}" ] && git rev-parse --verify "origin/${TARGET_BRANCH}" >/dev/null 2>&1; then - BASE="origin/${TARGET_BRANCH}" - echo "Found target branch from event: ${BASE}" - fi +if [ -z "$PR_NUMBER" ]; then + echo "Not a pull request event. Skipping review." + exit 0 fi -# 3. Fetch the base branch (now works with auth) -if [ -z "$BASE" ]; then - echo "No base ref found locally. Fetching..." - git fetch --unshallow origin 2>/dev/null || true - for branch in main master; do - if git fetch origin "+refs/heads/${branch}:refs/remotes/origin/${branch}" 2>/dev/null; then - BASE="origin/${branch}" - echo "Fetched: ${BASE}" - break - fi - done - # Also try the target branch from event context - if [ -z "$BASE" ] && [ -n "${TARGET_BRANCH}" ]; then - if git fetch origin "+refs/heads/${TARGET_BRANCH}:refs/remotes/origin/${TARGET_BRANCH}" 2>/dev/null; then - BASE="origin/${TARGET_BRANCH}" - echo "Fetched target: ${BASE}" - fi - fi -fi +# Fetch diff via API — works regardless of git auth inside the container. +# Gitea: GET /repos/{owner}/{repo}/pulls/{index}.diff +# GitHub: GET /repos/{owner}/{repo}/pulls/{index} (Accept: application/diff) +node -e " +const http = require('http'); +const https = require('https'); -if [ -z "$BASE" ]; then - echo "::error::Could not determine base branch. Ensure 'token' input has repo read access." - exit 1 -fi +const apiBase = '${API_BASE}'; +const repo = '${REPO}'; +const prNumber = '${PR_NUMBER}'; +const token = '${PI_TOKEN}'; +const maxBytes = ${PI_MAX_DIFF:-80000}; -echo "Base ref: ${BASE} -> $(git rev-parse --short "${BASE}" 2>/dev/null || echo 'NOT FOUND')" -echo "HEAD: $(git rev-parse --short HEAD)" -echo "Files changed:" -git diff --stat "${BASE}...HEAD" 2>/dev/null | tail -3 || echo "(could not stat diff)" +function fetchDiff() { + return new Promise((resolve, reject) => { + // Try Gitea diff endpoint first + const giteaPath = '/repos/' + repo + '/pulls/' + prNumber + '.diff'; + const githubPath = '/repos/' + repo + '/pulls/' + prNumber; -# Build exclude pathspecs -EXCLUDE_ARGS="" -for pattern in $PI_EXCLUDE; do - EXCLUDE_ARGS="$EXCLUDE_ARGS ':!$pattern'" -done + const url = new URL(apiBase + giteaPath); + const transport = url.protocol === 'http:' ? http : https; -eval "git diff ${BASE}...HEAD ${EXCLUDE_ARGS}" > /tmp/pi-diff.txt 2>/dev/null || true + const options = { + hostname: url.hostname, + port: url.port || (url.protocol === 'http:' ? 80 : 443), + path: url.pathname, + method: 'GET', + headers: { + 'Authorization': 'token ' + token, + 'Accept': 'text/plain', + }, + }; -# Truncate if needed -if [ "${PI_MAX_DIFF}" -gt 0 ]; then - head -c "${PI_MAX_DIFF}" /tmp/pi-diff.txt > /tmp/pi-diff-trunc.txt - mv /tmp/pi-diff-trunc.txt /tmp/pi-diff.txt -fi + const req = transport.request(options, (res) => { + if (res.statusCode === 404 && apiBase.indexOf('github.com') !== -1) { + // Fallback to GitHub diff format + reject(new Error('GitHub fallback not implemented')); + return; + } + if (res.statusCode < 200 || res.statusCode >= 300) { + let body = ''; + res.on('data', (c) => { body += c; }); + res.on('end', () => { reject(new Error('API ' + res.statusCode + ': ' + body.slice(0, 200))); }); + return; + } -DIFF_SIZE=$(wc -c < /tmp/pi-diff.txt || echo 0) -echo "Diff size: ${DIFF_SIZE} bytes" -echo "::endgroup::" + let data = ''; + let bytes = 0; + res.on('data', (chunk) => { + bytes += chunk.length; + if (maxBytes > 0 && bytes <= maxBytes) { + data += chunk; + } + }); + res.on('end', () => { + if (maxBytes > 0 && data.length >= maxBytes) { + data = data.slice(0, maxBytes) + '\\n... (truncated at ' + maxBytes + ' bytes)'; + } + resolve(data); + }); + }); + req.on('error', (e) => { reject(e); }); + req.end(); + }); +} -if [ "${DIFF_SIZE}" -eq 0 ]; then +fetchDiff().then((diff) => { + const fs = require('fs'); + + // Filter out excluded patterns (lockfiles, generated code, etc.) + const excludePatterns = '${PI_EXCLUDE}'.split(' ').filter(Boolean); + if (excludePatterns.length > 0) { + const lines = diff.split('\\n'); + const filtered = []; + let skipFile = false; + for (const line of lines) { + if (line.startsWith('diff --git')) { + skipFile = excludePatterns.some(p => { + const glob = p.replace(/\\./g, '\\\\.').replace(/\\*/g, '.*'); + return new RegExp(glob).test(line); + }); + } + if (!skipFile) filtered.push(line); + } + diff = filtered.join('\\n'); + } + + if (maxBytes > 0 && diff.length > maxBytes) { + diff = diff.slice(0, maxBytes) + '\\n... (truncated at ' + maxBytes + ' bytes)'; + } + + fs.writeFileSync('/tmp/pi-diff.txt', diff); + console.log('Diff fetched: ' + diff.length + ' bytes'); +}).catch((e) => { + console.error('Failed to fetch diff: ' + e.message); + process.exit(1); +}); +" + +if [ ! -s /tmp/pi-diff.txt ]; then echo "No changes to review. Skipping." exit 0 fi