fix(actions): harden proposer queue against duplicate batch PRs
This commit is contained in:
@@ -130,8 +130,6 @@ jobs:
|
|||||||
echo "event=$EVENT_NAME label=${LABEL_NAME:-<empty>}"
|
echo "event=$EVENT_NAME label=${LABEL_NAME:-<empty>}"
|
||||||
|
|
||||||
if [[ "$EVENT_NAME" == "issues" ]]; then
|
if [[ "$EVENT_NAME" == "issues" ]]; then
|
||||||
# Gitea peut fournir un payload "issues/labeled" sans label exploitable.
|
|
||||||
# On ne skip QUE si le label est explicitement présent ET différent de state/approved.
|
|
||||||
if [[ -n "${LABEL_NAME:-}" && "$LABEL_NAME" != "state/approved" ]]; then
|
if [[ -n "${LABEL_NAME:-}" && "$LABEL_NAME" != "state/approved" ]]; then
|
||||||
echo "issues/labeled with explicit non-approved label=$LABEL_NAME -> skip"
|
echo "issues/labeled with explicit non-approved label=$LABEL_NAME -> skip"
|
||||||
echo 'SKIP=1' >> /tmp/proposer.env
|
echo 'SKIP=1' >> /tmp/proposer.env
|
||||||
@@ -218,6 +216,43 @@ jobs:
|
|||||||
echo "Target batch:"
|
echo "Target batch:"
|
||||||
grep -E '^(TARGET_PRIMARY_ISSUE|TARGET_ISSUES|TARGET_COUNT|TARGET_CHEMIN)=' /tmp/proposer.env
|
grep -E '^(TARGET_PRIMARY_ISSUE|TARGET_ISSUES|TARGET_COUNT|TARGET_CHEMIN)=' /tmp/proposer.env
|
||||||
|
|
||||||
|
- name: Derive deterministic batch identity
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
source /tmp/proposer.env
|
||||||
|
[[ "${SKIP:-0}" != "1" ]] || { echo "Skipped"; exit 0; }
|
||||||
|
|
||||||
|
export TARGET_ISSUES TARGET_CHEMIN
|
||||||
|
|
||||||
|
node --input-type=module - <<'NODE'
|
||||||
|
import fs from "node:fs";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
|
const issues = String(process.env.TARGET_ISSUES || "")
|
||||||
|
.trim()
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter(Boolean)
|
||||||
|
.sort((a, b) => Number(a) - Number(b));
|
||||||
|
|
||||||
|
const chemin = String(process.env.TARGET_CHEMIN || "").trim();
|
||||||
|
const keySource = `${chemin}::${issues.join(",")}`;
|
||||||
|
const hash = crypto.createHash("sha1").update(keySource).digest("hex").slice(0, 12);
|
||||||
|
const primary = issues[0] || "0";
|
||||||
|
const batchBranch = `bot/proposer-${primary}-${hash}`;
|
||||||
|
|
||||||
|
fs.appendFileSync(
|
||||||
|
"/tmp/proposer.env",
|
||||||
|
[
|
||||||
|
`BATCH_KEY=${JSON.stringify(keySource)}`,
|
||||||
|
`BATCH_HASH=${JSON.stringify(hash)}`,
|
||||||
|
`BATCH_BRANCH=${JSON.stringify(batchBranch)}`
|
||||||
|
].join("\n") + "\n"
|
||||||
|
);
|
||||||
|
NODE
|
||||||
|
|
||||||
|
echo "Batch identity:"
|
||||||
|
grep -E '^(BATCH_KEY|BATCH_HASH|BATCH_BRANCH)=' /tmp/proposer.env
|
||||||
|
|
||||||
- name: Inspect open proposer PRs
|
- name: Inspect open proposer PRs
|
||||||
env:
|
env:
|
||||||
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
|
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
|
||||||
@@ -233,6 +268,8 @@ jobs:
|
|||||||
-o /tmp/open_pulls.json
|
-o /tmp/open_pulls.json
|
||||||
|
|
||||||
export TARGET_ISSUES="${TARGET_ISSUES:-}"
|
export TARGET_ISSUES="${TARGET_ISSUES:-}"
|
||||||
|
export BATCH_BRANCH="${BATCH_BRANCH:-}"
|
||||||
|
export BATCH_KEY="${BATCH_KEY:-}"
|
||||||
|
|
||||||
node --input-type=module - <<'NODE' >> /tmp/proposer.env
|
node --input-type=module - <<'NODE' >> /tmp/proposer.env
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
@@ -243,15 +280,21 @@ jobs:
|
|||||||
.split(/\s+/)
|
.split(/\s+/)
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const batchBranch = String(process.env.BATCH_BRANCH || "");
|
||||||
|
const batchKey = String(process.env.BATCH_KEY || "");
|
||||||
|
|
||||||
const proposerOpen = Array.isArray(pulls)
|
const proposerOpen = Array.isArray(pulls)
|
||||||
? pulls.filter((pr) => String(pr?.head?.ref || "").startsWith("bot/proposer-"))
|
? pulls.filter((pr) => String(pr?.head?.ref || "").startsWith("bot/proposer-"))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const current = proposerOpen.find((pr) => {
|
const sameBatch = proposerOpen.find((pr) => {
|
||||||
const ref = String(pr?.head?.ref || "");
|
const ref = String(pr?.head?.ref || "");
|
||||||
const title = String(pr?.title || "");
|
const title = String(pr?.title || "");
|
||||||
const body = String(pr?.body || "");
|
const body = String(pr?.body || "");
|
||||||
|
|
||||||
|
if (batchBranch && ref === batchBranch) return true;
|
||||||
|
if (batchKey && body.includes(`Batch-Key: ${batchKey}`)) return true;
|
||||||
|
|
||||||
return issues.some((n) =>
|
return issues.some((n) =>
|
||||||
ref.startsWith(`bot/proposer-${n}-`) ||
|
ref.startsWith(`bot/proposer-${n}-`) ||
|
||||||
title.includes(`#${n}`) ||
|
title.includes(`#${n}`) ||
|
||||||
@@ -262,10 +305,11 @@ jobs:
|
|||||||
|
|
||||||
const out = [];
|
const out = [];
|
||||||
|
|
||||||
if (current) {
|
if (sameBatch) {
|
||||||
out.push("SKIP=1");
|
out.push("SKIP=1");
|
||||||
out.push(`SKIP_REASON=${JSON.stringify("issue_already_has_open_pr")}`);
|
out.push(`SKIP_REASON=${JSON.stringify("issue_already_has_open_pr")}`);
|
||||||
out.push(`OPEN_PR_URL=${JSON.stringify(String(current.html_url || current.url || ""))}`);
|
out.push(`OPEN_PR_URL=${JSON.stringify(String(sameBatch.html_url || sameBatch.url || ""))}`);
|
||||||
|
out.push(`OPEN_PR_BRANCH=${JSON.stringify(String(sameBatch?.head?.ref || ""))}`);
|
||||||
} else if (proposerOpen.length > 0) {
|
} else if (proposerOpen.length > 0) {
|
||||||
const first = proposerOpen[0];
|
const first = proposerOpen[0];
|
||||||
out.push("SKIP=1");
|
out.push("SKIP=1");
|
||||||
@@ -277,6 +321,22 @@ jobs:
|
|||||||
process.stdout.write(out.join("\n") + (out.length ? "\n" : ""));
|
process.stdout.write(out.join("\n") + (out.length ? "\n" : ""));
|
||||||
NODE
|
NODE
|
||||||
|
|
||||||
|
- name: Guard on remote batch branch before heavy work
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
source /tmp/proposer.env
|
||||||
|
[[ "${SKIP:-0}" != "1" ]] || { echo "Skipped"; exit 0; }
|
||||||
|
|
||||||
|
if git ls-remote --exit-code --heads origin "$BATCH_BRANCH" >/dev/null 2>&1; then
|
||||||
|
echo 'SKIP=1' >> /tmp/proposer.env
|
||||||
|
echo 'SKIP_REASON="batch_branch_exists_without_pr"' >> /tmp/proposer.env
|
||||||
|
echo "OPEN_PR_BRANCH=${BATCH_BRANCH}" >> /tmp/proposer.env
|
||||||
|
echo "Remote batch branch already exists -> skip duplicate materialization"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Remote batch branch is free"
|
||||||
|
|
||||||
- name: Comment issue if queued / skipped
|
- name: Comment issue if queued / skipped
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
env:
|
env:
|
||||||
@@ -306,7 +366,13 @@ jobs:
|
|||||||
MSG="Ticket queued in proposer queue. An open proposer PR already exists: ${OPEN_PR_URL:-"(URL unavailable)"}. The workflow will resume after merge on main."
|
MSG="Ticket queued in proposer queue. An open proposer PR already exists: ${OPEN_PR_URL:-"(URL unavailable)"}. The workflow will resume after merge on main."
|
||||||
;;
|
;;
|
||||||
issue_already_has_open_pr)
|
issue_already_has_open_pr)
|
||||||
MSG="This ticket already has an open proposer PR: ${OPEN_PR_URL:-"(URL unavailable)"}"
|
MSG="This batch already has an open proposer PR: ${OPEN_PR_URL:-"(URL unavailable)"}"
|
||||||
|
;;
|
||||||
|
batch_branch_exists_without_pr)
|
||||||
|
MSG="This batch already has a remote batch branch (${OPEN_PR_BRANCH:-"(unknown branch)"}). Manual inspection is required before any new proposer PR is created."
|
||||||
|
;;
|
||||||
|
batch_branch_already_materialized)
|
||||||
|
MSG="This batch was already materialized by another run on branch ${OPEN_PR_BRANCH:-"(unknown branch)"}. No duplicate PR was created."
|
||||||
;;
|
;;
|
||||||
explicit_issue_missing_chemin)
|
explicit_issue_missing_chemin)
|
||||||
MSG="Proposer Apply: cannot process this ticket automatically because field Chemin is missing or unreadable."
|
MSG="Proposer Apply: cannot process this ticket automatically because field Chemin is missing or unreadable."
|
||||||
@@ -328,6 +394,7 @@ jobs:
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
export MSG
|
||||||
node --input-type=module - <<'NODE' > /tmp/proposer.skip.comment.json
|
node --input-type=module - <<'NODE' > /tmp/proposer.skip.comment.json
|
||||||
const msg = process.env.MSG || "";
|
const msg = process.env.MSG || "";
|
||||||
process.stdout.write(JSON.stringify({ body: msg }));
|
process.stdout.write(JSON.stringify({ body: msg }));
|
||||||
@@ -384,8 +451,7 @@ jobs:
|
|||||||
git config user.email "${BOT_GIT_EMAIL:-bot@archicratie.local}"
|
git config user.email "${BOT_GIT_EMAIL:-bot@archicratie.local}"
|
||||||
|
|
||||||
START_SHA="$(git rev-parse HEAD)"
|
START_SHA="$(git rev-parse HEAD)"
|
||||||
TS="$(date -u +%Y%m%d-%H%M%S)"
|
BR="$BATCH_BRANCH"
|
||||||
BR="bot/proposer-${TARGET_PRIMARY_ISSUE}-${TS}"
|
|
||||||
echo "BRANCH=$BR" >> /tmp/proposer.env
|
echo "BRANCH=$BR" >> /tmp/proposer.env
|
||||||
git checkout -b "$BR"
|
git checkout -b "$BR"
|
||||||
|
|
||||||
@@ -518,6 +584,27 @@ jobs:
|
|||||||
--data-binary @/tmp/proposer.failure.comment.json || true
|
--data-binary @/tmp/proposer.failure.comment.json || true
|
||||||
done
|
done
|
||||||
|
|
||||||
|
- name: Late guard against duplicate batch materialization
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
source /tmp/proposer.env || true
|
||||||
|
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
||||||
|
[[ "${APPLY_RC:-0}" == "0" ]] || exit 0
|
||||||
|
[[ "${REBASE_RC:-0}" == "0" ]] || exit 0
|
||||||
|
[[ "${NOOP:-0}" == "0" ]] || exit 0
|
||||||
|
|
||||||
|
REMOTE_SHA="$(git ls-remote --heads origin "$BATCH_BRANCH" | awk 'NR==1 {print $1}')"
|
||||||
|
|
||||||
|
if [[ -n "${REMOTE_SHA:-}" && "${REMOTE_SHA}" != "${END_SHA:-}" ]]; then
|
||||||
|
echo 'SKIP=1' >> /tmp/proposer.env
|
||||||
|
echo 'SKIP_REASON="batch_branch_already_materialized"' >> /tmp/proposer.env
|
||||||
|
echo "OPEN_PR_BRANCH=${BATCH_BRANCH}" >> /tmp/proposer.env
|
||||||
|
echo "Remote batch branch already exists at $REMOTE_SHA -> skip duplicate push/PR"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Late guard OK"
|
||||||
|
|
||||||
- name: Push bot branch
|
- name: Push bot branch
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
env:
|
env:
|
||||||
@@ -557,13 +644,39 @@ jobs:
|
|||||||
|
|
||||||
test -n "${FORGE_TOKEN:-}" || { echo "Missing FORGE_TOKEN"; exit 1; }
|
test -n "${FORGE_TOKEN:-}" || { echo "Missing FORGE_TOKEN"; exit 1; }
|
||||||
|
|
||||||
|
OPEN_PRS_JSON="$(curl -fsS \
|
||||||
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/pulls?state=open&limit=100")"
|
||||||
|
|
||||||
|
export OPEN_PRS_JSON BATCH_BRANCH BATCH_KEY
|
||||||
|
|
||||||
|
EXISTING_PR_URL="$(node --input-type=module -e '
|
||||||
|
const pulls = JSON.parse(process.env.OPEN_PRS_JSON || "[]");
|
||||||
|
const branch = String(process.env.BATCH_BRANCH || "");
|
||||||
|
const key = String(process.env.BATCH_KEY || "");
|
||||||
|
const current = Array.isArray(pulls)
|
||||||
|
? pulls.find((pr) => {
|
||||||
|
const ref = String(pr?.head?.ref || "");
|
||||||
|
const body = String(pr?.body || "");
|
||||||
|
return (branch && ref === branch) || (key && body.includes(`Batch-Key: ${key}`));
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
process.stdout.write(current ? String(current.html_url || current.url || "") : "");
|
||||||
|
')"
|
||||||
|
|
||||||
|
if [[ -n "${EXISTING_PR_URL:-}" ]]; then
|
||||||
|
echo "PR already exists for this batch: $EXISTING_PR_URL"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "${TARGET_COUNT:-0}" == "1" ]]; then
|
if [[ "${TARGET_COUNT:-0}" == "1" ]]; then
|
||||||
PR_TITLE="proposer: apply ticket #${TARGET_PRIMARY_ISSUE}"
|
PR_TITLE="proposer: apply ticket #${TARGET_PRIMARY_ISSUE}"
|
||||||
else
|
else
|
||||||
PR_TITLE="proposer: apply ${TARGET_COUNT} tickets on ${TARGET_CHEMIN}"
|
PR_TITLE="proposer: apply ${TARGET_COUNT} tickets on ${TARGET_CHEMIN}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export PR_TITLE TARGET_CHEMIN TARGET_ISSUES BRANCH END_SHA DEFAULT_BRANCH OWNER
|
export PR_TITLE TARGET_CHEMIN TARGET_ISSUES BRANCH END_SHA DEFAULT_BRANCH OWNER BATCH_KEY
|
||||||
|
|
||||||
node --input-type=module -e '
|
node --input-type=module -e '
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
@@ -581,6 +694,7 @@ jobs:
|
|||||||
...issues.map((n) => ` - #${n}`),
|
...issues.map((n) => ` - #${n}`),
|
||||||
`- Branche: ${process.env.BRANCH || ""}`,
|
`- Branche: ${process.env.BRANCH || ""}`,
|
||||||
`- Commit: ${process.env.END_SHA || "unknown"}`,
|
`- Commit: ${process.env.END_SHA || "unknown"}`,
|
||||||
|
`- Batch-Key: ${process.env.BATCH_KEY || ""}`,
|
||||||
"",
|
"",
|
||||||
"Merge si CI OK."
|
"Merge si CI OK."
|
||||||
].join("\n");
|
].join("\n");
|
||||||
@@ -597,97 +711,6 @@ jobs:
|
|||||||
);
|
);
|
||||||
'
|
'
|
||||||
|
|
||||||
echo "Creating proposer PR..."
|
|
||||||
PR_JSON="$(curl -fsS -X POST \
|
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$API_BASE/api/v1/repos/$OWNER/$REPO/pulls" \
|
|
||||||
--data-binary @/tmp/proposer.pr.json)"
|
|
||||||
|
|
||||||
PR_URL="$(node --input-type=module -e 'const pr = JSON.parse(process.argv[1] || "{}"); console.log(pr.html_url || pr.url || "");' "$PR_JSON")"
|
|
||||||
|
|
||||||
test -n "$PR_URL" || {
|
|
||||||
echo "PR URL missing. Raw: $PR_JSON"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "PR created: $PR_URL"
|
|
||||||
|
|
||||||
for ISSUE in $TARGET_ISSUES; do
|
|
||||||
export ISSUE PR_URL
|
|
||||||
|
|
||||||
node --input-type=module -e '
|
|
||||||
import fs from "node:fs";
|
|
||||||
|
|
||||||
const issue = process.env.ISSUE || "";
|
|
||||||
const url = process.env.PR_URL || "";
|
|
||||||
const msg =
|
|
||||||
`PR proposer créée pour le ticket #${issue} : ${url}\n\n` +
|
|
||||||
`Le ticket est clôturé automatiquement ; la discussion peut se poursuivre dans la PR.`;
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
"/tmp/proposer.issue.close.comment.json",
|
|
||||||
JSON.stringify({ body: msg })
|
|
||||||
);
|
|
||||||
'
|
|
||||||
|
|
||||||
echo "Commenting issue #$ISSUE ..."
|
|
||||||
COMMENT_HTTP="$(curl -sS -o /tmp/proposer.comment.out.json -w '%{http_code}' -X POST \
|
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE/comments" \
|
|
||||||
--data-binary @/tmp/proposer.issue.close.comment.json || true)"
|
|
||||||
echo "Issue #$ISSUE comment HTTP=$COMMENT_HTTP"
|
|
||||||
|
|
||||||
if [[ ! "$COMMENT_HTTP" =~ ^2 ]]; then
|
|
||||||
echo "Failed to comment issue #$ISSUE"
|
|
||||||
cat /tmp/proposer.comment.out.json || true
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Closing issue #$ISSUE ..."
|
|
||||||
CLOSE_HTTP="$(curl -sS -o /tmp/proposer.close.out.json -w '%{http_code}' -X PATCH \
|
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE" \
|
|
||||||
--data-binary '{"state":"closed"}' || true)"
|
|
||||||
echo "Issue #$ISSUE close HTTP=$CLOSE_HTTP"
|
|
||||||
|
|
||||||
if [[ ! "$CLOSE_HTTP" =~ ^2 ]]; then
|
|
||||||
echo "Failed to close issue #$ISSUE"
|
|
||||||
cat /tmp/proposer.close.out.json || true
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Verifying issue #$ISSUE state ..."
|
|
||||||
VERIFY_HTTP="$(curl -sS -o /tmp/proposer.verify.out.json -w '%{http_code}' \
|
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
|
||||||
-H "Accept: application/json" \
|
|
||||||
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE" || true)"
|
|
||||||
echo "Issue #$ISSUE verify HTTP=$VERIFY_HTTP"
|
|
||||||
|
|
||||||
if [[ ! "$VERIFY_HTTP" =~ ^2 ]]; then
|
|
||||||
echo "Failed to re-read issue #$ISSUE after close"
|
|
||||||
cat /tmp/proposer.verify.out.json || true
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ISSUE_STATE="$(node --input-type=module -e '
|
|
||||||
import fs from "node:fs";
|
|
||||||
const j = JSON.parse(fs.readFileSync("/tmp/proposer.verify.out.json", "utf8"));
|
|
||||||
console.log(String(j.state || ""));
|
|
||||||
')"
|
|
||||||
|
|
||||||
echo "Issue #$ISSUE state=$ISSUE_STATE"
|
|
||||||
[[ "$ISSUE_STATE" == "closed" ]] || {
|
|
||||||
echo "Issue #$ISSUE is not closed after PATCH"
|
|
||||||
cat /tmp/proposer.verify.out.json || true
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "PR: $PR_URL"
|
|
||||||
|
|
||||||
PR_JSON="$(curl -fsS -X POST \
|
PR_JSON="$(curl -fsS -X POST \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
@@ -710,8 +733,8 @@ jobs:
|
|||||||
const issue = process.env.ISSUE || "";
|
const issue = process.env.ISSUE || "";
|
||||||
const url = process.env.PR_URL || "";
|
const url = process.env.PR_URL || "";
|
||||||
const msg =
|
const msg =
|
||||||
`PR proposer créée pour le ticket #${issue} : ${url}\n\n` +
|
`PR proposer creee pour le ticket #${issue} : ${url}\n\n` +
|
||||||
`Le ticket est clôturé automatiquement ; la discussion peut se poursuivre dans la PR.`;
|
`Le ticket est cloture automatiquement ; la discussion peut se poursuivre dans la PR.`;
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
"/tmp/proposer.issue.close.comment.json",
|
"/tmp/proposer.issue.close.comment.json",
|
||||||
@@ -730,6 +753,17 @@ jobs:
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE" \
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE" \
|
||||||
--data-binary '{"state":"closed"}'
|
--data-binary '{"state":"closed"}'
|
||||||
|
|
||||||
|
ISSUE_STATE="$(curl -fsS \
|
||||||
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE" | \
|
||||||
|
node --input-type=module -e 'let s=""; process.stdin.on("data", d => s += d); process.stdin.on("end", () => { const j = JSON.parse(s || "{}"); process.stdout.write(String(j.state || "")); });')"
|
||||||
|
|
||||||
|
[[ "$ISSUE_STATE" == "closed" ]] || {
|
||||||
|
echo "Issue #$ISSUE is still not closed after PATCH"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "PR: $PR_URL"
|
echo "PR: $PR_URL"
|
||||||
|
|||||||
Reference in New Issue
Block a user