2 Commits
v5 ... v7

Author SHA1 Message Date
Markus Hofstetter
f1a4958b34 fix: fetch diff via Gitea/GitHub API instead of git
Git operations inside the Docker container have no auth credentials
(actions/checkout@v5 stores them in $RUNNER_TEMP, not mounted).

Instead of fighting git auth, fetch the diff directly from the
Gitea API: GET /repos/{owner}/{repo}/pulls/{index}.diff

This uses the same token already passed for posting comments.
No pre-fetch workflow step needed. No git required in the container.

Also filters excluded patterns (lockfiles, etc.) from the API diff.
2026-05-21 01:08:21 +02:00
Markus Hofstetter
15df936641 fix: use token for git auth inside Docker container
actions/checkout@v5 stores credentials in $RUNNER_TEMP which is not
mounted into the Docker container. Instead of requiring a pre-fetch
step in the workflow, we now inject the token into the remote URL
so git operations work inside the container.

Workflow no longer needs the 'Fetch base branch' pre-step.
2026-05-20 00:18:03 +02:00

View File

@@ -70,74 +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"
# Find the base branch.
# Strategy: check if remote tracking refs already exist (from a pre-step),
# then try Gitea/GitHub event context, then try fetching (may fail without auth).
# 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.
BASE=""
# 1. Check if remote tracking refs already exist (e.g., workflow pre-fetch 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
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
# 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
# 3. Last resort: try to fetch (will likely fail inside Docker without auth)
if [ -z "$BASE" ]; then
echo "::warning::No base ref found locally. Attempting fetch (may fail without auth)..."
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}"
break
fi
done
echo "Repo: ${REPO}, PR: ${PR_NUMBER}"
if [ -z "$PR_NUMBER" ]; then
echo "Not a pull request event. Skipping review."
exit 0
fi
if [ -z "$BASE" ]; then
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"
exit 1
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');
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)"
const apiBase = '${API_BASE}';
const repo = '${REPO}';
const prNumber = '${PR_NUMBER}';
const token = '${PI_TOKEN}';
const maxBytes = ${PI_MAX_DIFF:-80000};
# Build exclude pathspecs
EXCLUDE_ARGS=""
for pattern in $PI_EXCLUDE; do
EXCLUDE_ARGS="$EXCLUDE_ARGS ':!$pattern'"
done
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;
eval "git diff ${BASE}...HEAD ${EXCLUDE_ARGS}" > /tmp/pi-diff.txt 2>/dev/null || true
const url = new URL(apiBase + giteaPath);
const transport = url.protocol === 'http:' ? http : https;
# 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 options = {
hostname: url.hostname,
port: url.port || (url.protocol === 'http:' ? 80 : 443),
path: url.pathname,
method: 'GET',
headers: {
'Authorization': 'token ' + token,
'Accept': 'text/plain',
},
};
DIFF_SIZE=$(wc -c < /tmp/pi-diff.txt || echo 0)
echo "Diff size: ${DIFF_SIZE} bytes"
echo "::endgroup::"
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;
}
if [ "${DIFF_SIZE}" -eq 0 ]; then
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();
});
}
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