|
|
@@ -70,74 +70,148 @@ chmod 600 "$AUTH_FILE"
|
|
|
|
echo "Configured provider: ${PI_PROVIDER}"
|
|
|
|
echo "Configured provider: ${PI_PROVIDER}"
|
|
|
|
echo "::endgroup::"
|
|
|
|
echo "::endgroup::"
|
|
|
|
|
|
|
|
|
|
|
|
# ─── Phase 2: Generate diff ───────────────────────────────────────────────────
|
|
|
|
# ─── Phase 2: Fetch diff via API ───────────────────────────────────────────────
|
|
|
|
echo "::group::Generate diff"
|
|
|
|
echo "Generate diff"
|
|
|
|
|
|
|
|
|
|
|
|
# Find the base branch.
|
|
|
|
# Git operations inside the Docker container have no auth credentials
|
|
|
|
# Strategy: check if remote tracking refs already exist (from a pre-step),
|
|
|
|
# (actions/checkout@v5 stores them in $RUNNER_TEMP, which isn't mounted).
|
|
|
|
# then try Gitea/GitHub event context, then try fetching (may fail without auth).
|
|
|
|
# Instead, we get the diff directly from the Gitea/GitHub API using the token
|
|
|
|
|
|
|
|
# we already have for posting comments.
|
|
|
|
|
|
|
|
|
|
|
|
BASE=""
|
|
|
|
# Gitea Actions mirrors GitHub Actions env vars ($GITHUB_*) and exposes the
|
|
|
|
|
|
|
|
# event payload at $GITHUB_EVENT_PATH. There is no flat $GITEA_EVENT_*
|
|
|
|
|
|
|
|
# variable for the PR number, so we parse it out of the event JSON.
|
|
|
|
|
|
|
|
|
|
|
|
# 1. Check if remote tracking refs already exist (e.g., workflow pre-fetch step)
|
|
|
|
REPO="${GITHUB_REPOSITORY:-${GITEA_REPOSITORY:-}}"
|
|
|
|
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
|
|
|
|
# Detect Gitea vs GitHub. Gitea sets $GITHUB_SERVER_URL too, so the only
|
|
|
|
if [ -z "$BASE" ]; then
|
|
|
|
# reliable signal is $GITEA_ACTIONS (or a non-github.com server URL).
|
|
|
|
TARGET_BRANCH="${GITEA_BASE_REF:-${GITHUB_BASE_REF:-}}"
|
|
|
|
#
|
|
|
|
if [ -n "${TARGET_BRANCH}" ] && git rev-parse --verify "origin/${TARGET_BRANCH}" >/dev/null 2>&1; then
|
|
|
|
# $PI_SERVER_URL (server_url input) overrides the auto-detected server URL.
|
|
|
|
BASE="origin/${TARGET_BRANCH}"
|
|
|
|
# Gitea reports its *external* hostname (e.g. git.example.com), which may not
|
|
|
|
echo "Found target branch from event: ${BASE}"
|
|
|
|
# be routable from inside the runner's container network. Setting server_url
|
|
|
|
fi
|
|
|
|
# to the internal address (e.g. http://server:3000) fixes API calls.
|
|
|
|
|
|
|
|
SERVER_URL="${PI_SERVER_URL:-${GITHUB_SERVER_URL:-${GITEA_SERVER_URL:-}}}"
|
|
|
|
|
|
|
|
if [ -n "${GITEA_ACTIONS:-}" ] || { [ -n "$SERVER_URL" ] && [ "${SERVER_URL#*github.com}" = "$SERVER_URL" ]; }; then
|
|
|
|
|
|
|
|
API_BASE="${SERVER_URL}/api/v1"
|
|
|
|
|
|
|
|
PLATFORM="gitea"
|
|
|
|
|
|
|
|
echo "Platform: Gitea (${SERVER_URL})"
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
API_BASE="${GITHUB_API_URL:-https://api.github.com}"
|
|
|
|
|
|
|
|
PLATFORM="github"
|
|
|
|
|
|
|
|
echo "Platform: GitHub"
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# 3. Last resort: try to fetch (will likely fail inside Docker without auth)
|
|
|
|
# Resolve PR number from the event payload.
|
|
|
|
if [ -z "$BASE" ]; then
|
|
|
|
PR_NUMBER=""
|
|
|
|
echo "::warning::No base ref found locally. Attempting fetch (may fail without auth)..."
|
|
|
|
if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -f "${GITHUB_EVENT_PATH}" ]; then
|
|
|
|
git fetch --unshallow origin 2>/dev/null || true
|
|
|
|
PR_NUMBER=$(node -e "
|
|
|
|
for branch in main master; do
|
|
|
|
try {
|
|
|
|
if git fetch origin "refs/heads/${branch}:refs/remotes/origin/${branch}" 2>/dev/null; then
|
|
|
|
const e = require(process.env.GITHUB_EVENT_PATH);
|
|
|
|
BASE="origin/${branch}"
|
|
|
|
const n = (e.pull_request && e.pull_request.number) || e.number || '';
|
|
|
|
break
|
|
|
|
process.stdout.write(String(n || ''));
|
|
|
|
fi
|
|
|
|
} catch { process.stdout.write(''); }
|
|
|
|
done
|
|
|
|
")
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [ -z "$BASE" ]; then
|
|
|
|
echo "Repo: ${REPO}, PR: ${PR_NUMBER}"
|
|
|
|
echo "::error::Could not determine base branch. Add a 'Fetch base branch' step before this action: git fetch origin refs/heads/main:refs/remotes/origin/main"
|
|
|
|
echo "DEBUG: PLATFORM=${PLATFORM} API_BASE=${API_BASE} EVENT=${GITHUB_EVENT_PATH:-}"
|
|
|
|
exit 1
|
|
|
|
|
|
|
|
|
|
|
|
if [ -z "$PR_NUMBER" ]; then
|
|
|
|
|
|
|
|
echo "Not a pull request event. Skipping review."
|
|
|
|
|
|
|
|
exit 0
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
echo "Base ref: ${BASE} -> $(git rev-parse --short "${BASE}" 2>/dev/null || echo 'NOT FOUND')"
|
|
|
|
# Fetch diff via API — works regardless of git auth inside the container.
|
|
|
|
echo "HEAD: $(git rev-parse --short HEAD)"
|
|
|
|
# Gitea: GET /repos/{owner}/{repo}/pulls/{index}.diff
|
|
|
|
echo "Files changed:"
|
|
|
|
# GitHub: GET /repos/{owner}/{repo}/pulls/{index} (Accept: application/diff)
|
|
|
|
git diff --stat "${BASE}...HEAD" 2>/dev/null | tail -3 || echo "(could not stat diff)"
|
|
|
|
node -e "
|
|
|
|
|
|
|
|
const http = require('http');
|
|
|
|
|
|
|
|
const https = require('https');
|
|
|
|
|
|
|
|
|
|
|
|
# Build exclude pathspecs
|
|
|
|
const apiBase = '${API_BASE}';
|
|
|
|
EXCLUDE_ARGS=""
|
|
|
|
const repo = '${REPO}';
|
|
|
|
for pattern in $PI_EXCLUDE; do
|
|
|
|
const prNumber = '${PR_NUMBER}';
|
|
|
|
EXCLUDE_ARGS="$EXCLUDE_ARGS ':!$pattern'"
|
|
|
|
const token = '${PI_TOKEN}';
|
|
|
|
done
|
|
|
|
const platform = '${PLATFORM}';
|
|
|
|
|
|
|
|
const maxBytes = ${PI_MAX_DIFF:-80000};
|
|
|
|
|
|
|
|
|
|
|
|
eval "git diff ${BASE}...HEAD ${EXCLUDE_ARGS}" > /tmp/pi-diff.txt 2>/dev/null || true
|
|
|
|
function fetchDiff() {
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
|
|
// Gitea: GET {api}/repos/{owner}/{repo}/pulls/{n}.diff
|
|
|
|
|
|
|
|
// GitHub: GET {api}/repos/{owner}/{repo}/pulls/{n} with Accept: application/vnd.github.v3.diff
|
|
|
|
|
|
|
|
const path = platform === 'gitea'
|
|
|
|
|
|
|
|
? '/repos/' + repo + '/pulls/' + prNumber + '.diff'
|
|
|
|
|
|
|
|
: '/repos/' + repo + '/pulls/' + prNumber;
|
|
|
|
|
|
|
|
|
|
|
|
# Truncate if needed
|
|
|
|
const url = new URL(apiBase + path);
|
|
|
|
if [ "${PI_MAX_DIFF}" -gt 0 ]; then
|
|
|
|
const transport = url.protocol === 'http:' ? http : https;
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DIFF_SIZE=$(wc -c < /tmp/pi-diff.txt || echo 0)
|
|
|
|
const headers = {
|
|
|
|
echo "Diff size: ${DIFF_SIZE} bytes"
|
|
|
|
'Authorization': 'token ' + token,
|
|
|
|
echo "::endgroup::"
|
|
|
|
'User-Agent': 'pi-code-review',
|
|
|
|
|
|
|
|
'Accept': platform === 'gitea' ? 'text/plain' : 'application/vnd.github.v3.diff',
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if [ "${DIFF_SIZE}" -eq 0 ]; then
|
|
|
|
const options = {
|
|
|
|
|
|
|
|
hostname: url.hostname,
|
|
|
|
|
|
|
|
port: url.port || (url.protocol === 'http:' ? 80 : 443),
|
|
|
|
|
|
|
|
path: url.pathname,
|
|
|
|
|
|
|
|
method: 'GET',
|
|
|
|
|
|
|
|
headers,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const req = transport.request(options, (res) => {
|
|
|
|
|
|
|
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
|
|
|
|
|
|
let body = '';
|
|
|
|
|
|
|
|
res.on('data', (c) => { body += c; });
|
|
|
|
|
|
|
|
res.on('end', () => { reject(new Error('API ' + res.statusCode + ' for ' + url.href + ': ' + body.slice(0, 200))); });
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let data = '';
|
|
|
|
|
|
|
|
res.on('data', (chunk) => { data += chunk; });
|
|
|
|
|
|
|
|
res.on('end', () => { resolve(data); });
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
req.on('error', (e) => { reject(e); });
|
|
|
|
|
|
|
|
req.end();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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."
|
|
|
|
echo "No changes to review. Skipping."
|
|
|
|
exit 0
|
|
|
|
exit 0
|
|
|
|
fi
|
|
|
|
fi
|
|
|
@@ -242,17 +316,7 @@ echo "::endgroup::"
|
|
|
|
# ─── Phase 4: Post comment ────────────────────────────────────────────────────
|
|
|
|
# ─── Phase 4: Post comment ────────────────────────────────────────────────────
|
|
|
|
echo "::group::Post review comment"
|
|
|
|
echo "::group::Post review comment"
|
|
|
|
|
|
|
|
|
|
|
|
# Detect Gitea vs GitHub
|
|
|
|
# API_BASE / REPO / PR_NUMBER were resolved in Phase 2.
|
|
|
|
if [ -n "${GITEA_SERVER_URL:-}" ]; then
|
|
|
|
|
|
|
|
API_BASE="${GITEA_SERVER_URL}/api/v1"
|
|
|
|
|
|
|
|
PR_NUMBER="${GITEA_EVENT_PULL_REQUEST_NUMBER:-}"
|
|
|
|
|
|
|
|
REPO="${GITEA_REPOSITORY:-}"
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
API_BASE="${GITHUB_API_URL:-https://api.github.com}"
|
|
|
|
|
|
|
|
PR_NUMBER="${GITHUB_EVENT_PULL_REQUEST_NUMBER:-}"
|
|
|
|
|
|
|
|
REPO="${GITHUB_REPOSITORY:-}"
|
|
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if [ -z "$PR_NUMBER" ]; then
|
|
|
|
if [ -z "$PR_NUMBER" ]; then
|
|
|
|
echo "Not a pull request event. Review written to /tmp/pi-review.md only."
|
|
|
|
echo "Not a pull request event. Review written to /tmp/pi-review.md only."
|
|
|
|
echo "::endgroup::"
|
|
|
|
echo "::endgroup::"
|
|
|
|