From d850fc7d0a31109ff72b238fcce0705bed5b3c66 Mon Sep 17 00:00:00 2001 From: Ivanmeneges Date: Thu, 14 May 2026 17:44:54 +0530 Subject: [PATCH 1/6] (MOSIP-44913) Created link-pr-to-issue.yml This is to update comment of PR for any issues to link Signed-off-by: Ivanmeneges --- .github/workflows/link-pr-to-issue.yml | 206 +++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 .github/workflows/link-pr-to-issue.yml diff --git a/.github/workflows/link-pr-to-issue.yml b/.github/workflows/link-pr-to-issue.yml new file mode 100644 index 00000000..6c114daa --- /dev/null +++ b/.github/workflows/link-pr-to-issue.yml @@ -0,0 +1,206 @@ +name: PR → Issue Linker (smart) + +on: + workflow_call: + secrets: + CROSS_REPO_TOKEN: + required: true + +permissions: + contents: read + issues: write + pull-requests: read + +env: + LOG_PREFIX: "[PR-Linker]" + +jobs: + link: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Validate inputs + id: validate + run: | + echo "${{ env.LOG_PREFIX }} Starting validation..." + BODY="${{ github.event.pull_request.title }} ${{ github.event.pull_request.body }}" + + if [ -z "$BODY" ] || [ "$BODY" == "null" ]; then + echo "${{ env.LOG_PREFIX }} ℹ️ No PR content found" + echo "has_issues=false" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "has_issues=true" >> $GITHUB_OUTPUT + + - name: Extract issue references + id: extract + if: steps.validate.outputs.has_issues == 'true' + run: | + echo "${{ env.LOG_PREFIX }} Extracting issue references..." + + BODY="${{ github.event.pull_request.title }} ${{ github.event.pull_request.body }}" + + ISSUES=$(echo "$BODY" | grep -Eo '([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)?#[0-9]+' | sort -u) + + if [ -z "$ISSUES" ]; then + echo "${{ env.LOG_PREFIX }} No issues found" + echo "count=0" >> $GITHUB_OUTPUT + exit 0 + fi + + ISSUE_COUNT=$(echo "$ISSUES" | wc -l) + echo "${{ env.LOG_PREFIX }} Found $ISSUE_COUNT issue(s)" + + echo "count=$ISSUE_COUNT" >> $GITHUB_OUTPUT + echo "issues<> $GITHUB_OUTPUT + echo "$ISSUES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Process issue links + if: steps.extract.outputs.count > 0 + env: + GH_TOKEN: ${{ secrets.CROSS_REPO_TOKEN }} + PR_URL: ${{ github.event.pull_request.html_url }} + PR_TITLE: ${{ github.event.pull_request.title }} + SOURCE_REPO: ${{ github.repository }} + PR_ACTION: ${{ github.event.action }} + PR_MERGED: ${{ github.event.pull_request.merged }} + run: | + set +e + set +o pipefail + + ISSUES="${{ steps.extract.outputs.issues }}" + MARKER="" + + PROCESSED=0 + FAILED=0 + + echo "${{ env.LOG_PREFIX }} Processing issues..." + + while IFS= read -r ISSUE; do + [ -z "$ISSUE" ] && continue + + if [[ "$ISSUE" == *"/"* ]]; then + REPO=$(echo "$ISSUE" | cut -d'#' -f1) + NUMBER=$(echo "$ISSUE" | cut -d'#' -f2) + else + REPO="$SOURCE_REPO" + NUMBER=$(echo "$ISSUE" | cut -d'#' -f2) + fi + + if ! [[ "$NUMBER" =~ ^[0-9]+$ ]]; then + echo "${{ env.LOG_PREFIX }} Invalid issue number: $NUMBER" + ((FAILED++)) + continue + fi + + echo "${{ env.LOG_PREFIX }} → Processing $REPO#$NUMBER" + + COMMENTS_RESPONSE=$(curl -s -w "\n%{http_code}" \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/issues/$NUMBER/comments") + + HTTP_CODE=$(echo "$COMMENTS_RESPONSE" | tail -n1) + COMMENTS_BODY=$(echo "$COMMENTS_RESPONSE" | head -n-1) + + if [ "$HTTP_CODE" != "200" ]; then + echo "${{ env.LOG_PREFIX }} Failed to fetch comments (HTTP $HTTP_CODE)" + echo "$COMMENTS_BODY" + ((FAILED++)) + continue + fi + + COMMENT_ID=$(echo "$COMMENTS_BODY" | jq -r \ + --arg marker "$MARKER" \ + '.[] | select(.body | contains($marker)) | .id' 2>/dev/null | head -n 1 || true) + + # ✅ Proper multiline formatting (FIXED) + BODY_TEXT="🔗 Linked PR + + - PR: ${PR_URL} + - Source: ${SOURCE_REPO} + + ${MARKER}" + + # 🧹 DELETE if PR closed and NOT merged + if [[ "$PR_ACTION" == "closed" && "$PR_MERGED" == "false" ]]; then + if [ -n "$COMMENT_ID" ]; then + echo "${{ env.LOG_PREFIX }} Deleting comment $COMMENT_ID" + + DELETE_RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \ + -H "Authorization: Bearer $GH_TOKEN" \ + "https://api.github.com/repos/$REPO/issues/comments/$COMMENT_ID") + + DELETE_CODE=$(echo "$DELETE_RESPONSE" | tail -n1) + + if [[ "$DELETE_CODE" =~ ^(200|204)$ ]]; then + ((PROCESSED++)) + else + echo "${{ env.LOG_PREFIX }} Delete failed (HTTP $DELETE_CODE)" + echo "$DELETE_RESPONSE" + ((FAILED++)) + fi + else + echo "${{ env.LOG_PREFIX }} No comment to delete" + ((PROCESSED++)) + fi + continue + fi + + # 🔁 UPDATE + if [ -n "$COMMENT_ID" ]; then + echo "${{ env.LOG_PREFIX }} Updating comment $COMMENT_ID" + + UPDATE_RESPONSE=$(curl -s -w "\n%{http_code}" -X PATCH \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "Content-Type: application/json" \ + "https://api.github.com/repos/$REPO/issues/comments/$COMMENT_ID" \ + -d "$(jq -n --arg body "$BODY_TEXT" '{body: $body}')") + + UPDATE_CODE=$(echo "$UPDATE_RESPONSE" | tail -n1) + + if [[ "$UPDATE_CODE" =~ ^(200|201)$ ]]; then + ((PROCESSED++)) + else + echo "${{ env.LOG_PREFIX }} Update failed (HTTP $UPDATE_CODE)" + echo "$UPDATE_RESPONSE" + ((FAILED++)) + fi + + # ➕ CREATE + else + echo "${{ env.LOG_PREFIX }} Creating new comment" + + CREATE_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "Content-Type: application/json" \ + "https://api.github.com/repos/$REPO/issues/$NUMBER/comments" \ + -d "$(jq -n --arg body "$BODY_TEXT" '{body: $body}')") + + CREATE_CODE=$(echo "$CREATE_RESPONSE" | tail -n1) + + if [[ "$CREATE_CODE" =~ ^(200|201)$ ]]; then + ((PROCESSED++)) + else + echo "${{ env.LOG_PREFIX }} Create failed (HTTP $CREATE_CODE)" + echo "$CREATE_RESPONSE" + ((FAILED++)) + fi + fi + + done <<< "$ISSUES" + + echo "${{ env.LOG_PREFIX }} Done. Processed: $PROCESSED | Failed: $FAILED" + + # ✅ DO NOT fail workflow + exit 0 + + - name: Success summary + if: success() + run: | + echo "${{ env.LOG_PREFIX }} ✅ Workflow completed successfully" From 39f597922076f79180983ae43bcb6ed0868dc569 Mon Sep 17 00:00:00 2001 From: Ivanmeneges Date: Thu, 14 May 2026 20:08:56 +0530 Subject: [PATCH 2/6] Update .github/workflows/link-pr-to-issue.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Ivanmeneges --- .github/workflows/link-pr-to-issue.yml | 28 +++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/.github/workflows/link-pr-to-issue.yml b/.github/workflows/link-pr-to-issue.yml index 6c114daa..9fe73772 100644 --- a/.github/workflows/link-pr-to-issue.yml +++ b/.github/workflows/link-pr-to-issue.yml @@ -22,9 +22,31 @@ jobs: steps: - name: Validate inputs id: validate - run: | - echo "${{ env.LOG_PREFIX }} Starting validation..." - BODY="${{ github.event.pull_request.title }} ${{ github.event.pull_request.body }}" + - name: Validate inputs + id: validate + env: + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} + run: | + echo "${{ env.LOG_PREFIX }} Starting validation..." + BODY="${PR_TITLE} ${PR_BODY}" + + if [ -z "$BODY" ] || [ "$BODY" == "null" ]; then + echo "${{ env.LOG_PREFIX }} ℹ️ No PR content found" + echo "has_issues=false" >> $GITHUB_OUTPUT + exit 0 + fi + + - name: Extract issue references + id: extract + if: steps.validate.outputs.has_issues == 'true' + env: + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} + run: | + echo "${{ env.LOG_PREFIX }} Extracting issue references..." + + BODY="${PR_TITLE} ${PR_BODY}" if [ -z "$BODY" ] || [ "$BODY" == "null" ]; then echo "${{ env.LOG_PREFIX }} ℹ️ No PR content found" From cfb7d41f349a8ba22c0b81d69b7dfeb3b2ef2131 Mon Sep 17 00:00:00 2001 From: Ivanmeneges Date: Thu, 14 May 2026 20:09:55 +0530 Subject: [PATCH 3/6] Update .github/workflows/link-pr-to-issue.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Ivanmeneges --- .github/workflows/link-pr-to-issue.yml | 39 ++++++++++++++++---------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/.github/workflows/link-pr-to-issue.yml b/.github/workflows/link-pr-to-issue.yml index 9fe73772..c8224fe0 100644 --- a/.github/workflows/link-pr-to-issue.yml +++ b/.github/workflows/link-pr-to-issue.yml @@ -120,24 +120,33 @@ jobs: echo "${{ env.LOG_PREFIX }} → Processing $REPO#$NUMBER" - COMMENTS_RESPONSE=$(curl -s -w "\n%{http_code}" \ - -H "Authorization: Bearer $GH_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/issues/$NUMBER/comments") + COMMENT_ID="" + PAGE=1 + while :; do + COMMENTS_RESPONSE=$(curl -s -w "\n%{http_code}" \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/issues/$NUMBER/comments?per_page=100&page=$PAGE") - HTTP_CODE=$(echo "$COMMENTS_RESPONSE" | tail -n1) - COMMENTS_BODY=$(echo "$COMMENTS_RESPONSE" | head -n-1) + HTTP_CODE=$(echo "$COMMENTS_RESPONSE" | tail -n1) + COMMENTS_BODY=$(echo "$COMMENTS_RESPONSE" | head -n-1) - if [ "$HTTP_CODE" != "200" ]; then - echo "${{ env.LOG_PREFIX }} Failed to fetch comments (HTTP $HTTP_CODE)" - echo "$COMMENTS_BODY" - ((FAILED++)) - continue - fi + if [ "$HTTP_CODE" != "200" ]; then + echo "${{ env.LOG_PREFIX }} Failed to fetch comments (HTTP $HTTP_CODE)" + echo "$COMMENTS_BODY" + ((FAILED++)) + continue 2 + fi + + COMMENT_ID=$(echo "$COMMENTS_BODY" | jq -r \ + --arg marker "$MARKER" \ + '.[] | select(.body | contains($marker)) | .id' 2>/dev/null | head -n1 || true) + [ -n "$COMMENT_ID" ] && break - COMMENT_ID=$(echo "$COMMENTS_BODY" | jq -r \ - --arg marker "$MARKER" \ - '.[] | select(.body | contains($marker)) | .id' 2>/dev/null | head -n 1 || true) + PAGE_SIZE=$(echo "$COMMENTS_BODY" | jq 'length') + [ "$PAGE_SIZE" -lt 100 ] && break + PAGE=$((PAGE + 1)) + done # ✅ Proper multiline formatting (FIXED) BODY_TEXT="🔗 Linked PR From ccb3d79f1f680ad9f7095e62c3a23bb0a699cf13 Mon Sep 17 00:00:00 2001 From: Ivanmeneges Date: Mon, 18 May 2026 19:54:14 +0530 Subject: [PATCH 4/6] Update link-pr-to-issue.yml Signed-off-by: Ivanmeneges --- .github/workflows/link-pr-to-issue.yml | 123 +++++++++++++++++++------ 1 file changed, 95 insertions(+), 28 deletions(-) diff --git a/.github/workflows/link-pr-to-issue.yml b/.github/workflows/link-pr-to-issue.yml index c8224fe0..48e56c16 100644 --- a/.github/workflows/link-pr-to-issue.yml +++ b/.github/workflows/link-pr-to-issue.yml @@ -22,32 +22,14 @@ jobs: steps: - name: Validate inputs id: validate - - name: Validate inputs - id: validate - env: - PR_TITLE: ${{ github.event.pull_request.title }} - PR_BODY: ${{ github.event.pull_request.body }} - run: | - echo "${{ env.LOG_PREFIX }} Starting validation..." - BODY="${PR_TITLE} ${PR_BODY}" - - if [ -z "$BODY" ] || [ "$BODY" == "null" ]; then - echo "${{ env.LOG_PREFIX }} ℹ️ No PR content found" - echo "has_issues=false" >> $GITHUB_OUTPUT - exit 0 - fi - - - name: Extract issue references - id: extract - if: steps.validate.outputs.has_issues == 'true' - env: - PR_TITLE: ${{ github.event.pull_request.title }} - PR_BODY: ${{ github.event.pull_request.body }} - run: | - echo "${{ env.LOG_PREFIX }} Extracting issue references..." - - BODY="${PR_TITLE} ${PR_BODY}" - + env: + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} + run: | + echo "${{ env.LOG_PREFIX }} Starting validation..." + + BODY="${PR_TITLE} ${PR_BODY}" + if [ -z "$BODY" ] || [ "$BODY" == "null" ]; then echo "${{ env.LOG_PREFIX }} ℹ️ No PR content found" echo "has_issues=false" >> $GITHUB_OUTPUT @@ -59,12 +41,17 @@ jobs: - name: Extract issue references id: extract if: steps.validate.outputs.has_issues == 'true' + env: + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} run: | echo "${{ env.LOG_PREFIX }} Extracting issue references..." - BODY="${{ github.event.pull_request.title }} ${{ github.event.pull_request.body }}" + BODY="${PR_TITLE} ${PR_BODY}" - ISSUES=$(echo "$BODY" | grep -Eo '([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)?#[0-9]+' | sort -u) + ISSUES=$(echo "$BODY" | \ + grep -Eo '([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)?#[0-9]+' | \ + sort -u) if [ -z "$ISSUES" ]; then echo "${{ env.LOG_PREFIX }} No issues found" @@ -73,13 +60,93 @@ jobs: fi ISSUE_COUNT=$(echo "$ISSUES" | wc -l) + echo "${{ env.LOG_PREFIX }} Found $ISSUE_COUNT issue(s)" echo "count=$ISSUE_COUNT" >> $GITHUB_OUTPUT + echo "issues<> $GITHUB_OUTPUT echo "$ISSUES" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT + - name: Cleanup stale issue links + if: github.event.action == 'edited' + env: + GH_TOKEN: ${{ secrets.CROSS_REPO_TOKEN }} + PR_URL: ${{ github.event.pull_request.html_url }} + SOURCE_REPO: ${{ github.repository }} + run: | + set +e + set +o pipefail + + echo "${{ env.LOG_PREFIX }} Cleaning stale issue links..." + + CURRENT_BODY="${{ github.event.pull_request.title }} ${{ github.event.pull_request.body }}" + + CURRENT_ISSUES=$(echo "$CURRENT_BODY" | \ + grep -Eo '([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)?#[0-9]+' | \ + sort -u || true) + + echo "${{ env.LOG_PREFIX }} Current referenced issues:" + echo "$CURRENT_ISSUES" + + MARKER="pr-linker:${PR_URL}" + + SEARCH_RESPONSE=$(curl -s \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/search/issues?q=${MARKER}+in:comments") + + COMMENT_URLS=$(echo "$SEARCH_RESPONSE" | \ + jq -r '.items[].comments_url' 2>/dev/null || true) + + while IFS= read -r COMMENTS_URL; do + [ -z "$COMMENTS_URL" ] && continue + + ISSUE_API=$(echo "$COMMENTS_URL" | sed 's|/comments||') + + ISSUE_DATA=$(curl -s \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "$ISSUE_API") + + ISSUE_NUMBER=$(echo "$ISSUE_DATA" | jq -r '.number') + + ISSUE_REPO=$(echo "$ISSUE_DATA" | \ + jq -r '.repository_url' | \ + sed 's|https://api.github.com/repos/||') + + ISSUE_REF="${ISSUE_REPO}#${ISSUE_NUMBER}" + + echo "${{ env.LOG_PREFIX }} Checking existing linked issue: $ISSUE_REF" + + if ! echo "$CURRENT_ISSUES" | grep -q "$ISSUE_REF"; then + + echo "${{ env.LOG_PREFIX }} Removing stale link from $ISSUE_REF" + + COMMENTS_DATA=$(curl -s \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "$COMMENTS_URL") + + COMMENT_ID=$(echo "$COMMENTS_DATA" | jq -r \ + --arg marker "$MARKER" \ + '.[] | select(.body | contains($marker)) | .id' | \ + head -n1) + + if [ -n "$COMMENT_ID" ] && [ "$COMMENT_ID" != "null" ]; then + + curl -s -X DELETE \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$ISSUE_REPO/issues/comments/$COMMENT_ID" + + echo "${{ env.LOG_PREFIX }} Deleted stale comment from $ISSUE_REF" + fi + fi + + done <<< "$COMMENT_URLS" + - name: Process issue links if: steps.extract.outputs.count > 0 env: From d0a7d85bcb874413addbaeecfc87a0ea8ca5de4e Mon Sep 17 00:00:00 2001 From: Ivanmeneges Date: Mon, 18 May 2026 20:04:35 +0530 Subject: [PATCH 5/6] Update .github/workflows/link-pr-to-issue.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Ivanmeneges --- .github/workflows/link-pr-to-issue.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/link-pr-to-issue.yml b/.github/workflows/link-pr-to-issue.yml index 48e56c16..f5eda36a 100644 --- a/.github/workflows/link-pr-to-issue.yml +++ b/.github/workflows/link-pr-to-issue.yml @@ -75,13 +75,15 @@ jobs: GH_TOKEN: ${{ secrets.CROSS_REPO_TOKEN }} PR_URL: ${{ github.event.pull_request.html_url }} SOURCE_REPO: ${{ github.repository }} + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} run: | set +e set +o pipefail echo "${{ env.LOG_PREFIX }} Cleaning stale issue links..." - CURRENT_BODY="${{ github.event.pull_request.title }} ${{ github.event.pull_request.body }}" + CURRENT_BODY="${PR_TITLE} ${PR_BODY}" CURRENT_ISSUES=$(echo "$CURRENT_BODY" | \ grep -Eo '([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)?#[0-9]+' | \ From 4148dc8581d57d30594d798811154f057eb5bc44 Mon Sep 17 00:00:00 2001 From: Ivanmeneges Date: Mon, 18 May 2026 20:18:41 +0530 Subject: [PATCH 6/6] Update link-pr-to-issue.yml Signed-off-by: Ivanmeneges --- .github/workflows/link-pr-to-issue.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/link-pr-to-issue.yml b/.github/workflows/link-pr-to-issue.yml index f5eda36a..c1e1a339 100644 --- a/.github/workflows/link-pr-to-issue.yml +++ b/.github/workflows/link-pr-to-issue.yml @@ -126,15 +126,23 @@ jobs: echo "${{ env.LOG_PREFIX }} Removing stale link from $ISSUE_REF" + COMMENT_ID="" + CLEANUP_PAGE=1 + while :; do COMMENTS_DATA=$(curl -s \ -H "Authorization: Bearer $GH_TOKEN" \ -H "Accept: application/vnd.github+json" \ - "$COMMENTS_URL") + "${COMMENTS_URL}?per_page=100&page=$CLEANUP_PAGE") COMMENT_ID=$(echo "$COMMENTS_DATA" | jq -r \ --arg marker "$MARKER" \ - '.[] | select(.body | contains($marker)) | .id' | \ - head -n1) + '.[] | select(.body | contains($marker)) | .id' 2>/dev/null | head -n1 || true) + [ -n "$COMMENT_ID" ] && break + + CLEANUP_PAGE_SIZE=$(echo "$COMMENTS_DATA" | jq 'length') + [ "$CLEANUP_PAGE_SIZE" -lt 100 ] && break + CLEANUP_PAGE=$((CLEANUP_PAGE + 1)) + done if [ -n "$COMMENT_ID" ] && [ "$COMMENT_ID" != "null" ]; then