11#! /bin/bash
22
3+ # Print base and head repository information
34echo " base or target repo : $BASE_REPO "
45echo " head or source repo : $HEAD_REPO "
56
6- if [[ $HEAD_REPO == $BASE_REPO ]]; then
7+ # Determine if the PR is from a forked repository
8+ if [[ $HEAD_REPO == $BASE_REPO ]]; then
79 export forked=false
810else
911 export forked=true
@@ -14,112 +16,170 @@ if [[ "$TITLE" =~ ^(doc:|docs:|chore:|misc:|Release:|release:|Sync:|sync:) ]]; t
1416 echo " Skipping validation for docs/chore PR."
1517 echo " PR NUMBER-: $PRNUM "
1618 if [[ " $forked " == " false" ]]; then
19+ # If not a forked PR, remove 'Issue-verification-failed' and add 'Ready-to-Review' label
1720 gh pr edit $PRNUM --remove-label " PR:Issue-verification-failed"
1821 gh pr edit $PRNUM --add-label " PR:Ready-to-Review"
1922 fi
2023 exit 0
2124fi
2225
2326# Define all issue matching patterns
27+ # These patterns cover various ways issues can be linked in a PR body
2428patterns=(
25- " ((Fixes|fixes|Resolves|resolves) #[0-9]+)"
26- " ((Fixes|fixes|Resolves|resolves) https://github.com/devtron-labs/(devtron|sprint-tasks|devops-sprint|devtron-enterprise)/issues/[0-9]+)"
27- " ((Fixes|fixes|Resolves|resolves):? https://github.com/devtron-labs/(devtron|sprint-tasks|devops-sprint|devtron-enterprise)/issues/[0-9]+)"
28- " ((Fixes|fixes|Resolves|resolves) devtron-labs/devtron#[0-9]+)"
29+ " ((Fixes|fixes|Resolves|resolves) #[0-9]+)" # e.g., Fixes #123
30+ " ((Fixes|fixes|Resolves|resolves) https://github.com/devtron-labs/(devtron|sprint-tasks|devops-sprint|devtron-enterprise)/issues/[0-9]+)" # e.g., Fixes https://github.com/devtron-labs/devtron/issues/123
31+ " ((Fixes|fixes|Resolves|resolves):? https://github.com/devtron-labs/(devtron|sprint-tasks|devops-sprint|devtron-enterprise)/issues/[0-9]+)" # e.g., Fixes: https://github.com/devtron-labs/devtron/issues/123
32+ " ((Fixes|fixes|Resolves|resolves) devtron-labs/devtron#[0-9]+)" # e.g., Fixes devtron-labs/devtron#123
2933 " ((Fixes|fixes|Resolves|resolves) devtron-labs/sprint-tasks#[0-9]+)"
3034 " ((Fixes|fixes|Resolves|resolves) devtron-labs/devops-sprint#[0-9]+)"
31- " (Fixes|fixes|Resolves|resolves):?\\ s+\\ [#([0-9]+)\\ ]"
32- " ((Fixes|fixes|Resolves|resolves):? #devtron-labs/devops-sprint/issues/[0-9]+)"
35+ " (Fixes|fixes|Resolves|resolves):?\\ s+\\ [#([0-9]+)\\ ]" # e.g., Fixes [#123]
36+ " ((Fixes|fixes|Resolves|resolves):? #devtron-labs/devops-sprint/issues/[0-9]+)" # e.g., Fixes: #devtron-labs/devops-sprint/issues/123
3337 " ((Fixes|fixes|Resolves|resolves):? #devtron-labs/sprint-tasks/issues/[0-9]+)"
3438)
3539
36- # Extract issue number and repo from PR body
37- extract_issue_number () {
38- local pattern=" $1 " # Get the pattern as the first argument to the function
39-
40- # Check if PR_BODY matches the provided pattern using Bash's =~ regex operator
41- if [[ " $PR_BODY " =~ $pattern ]]; then
42- echo " matched for this pattern $pattern "
43-
44- issue_num=$( echo " $PR_BODY " | grep -oE " $pattern " | grep -oE " [0-9]+" )
45-
46- # Extract the repository name (e.g., devtron-labs/devtron) from PR_BODY using grep
47- repo=$( echo " $PR_BODY " | grep -oE " devtron-labs/[a-zA-Z0-9_-]+" )
48- echo " Extracted issue number: $issue_num from repo: $repo "
49-
50- return 0 # Return success
51- else
52- echo " No match for the pattern $pattern "
53- fi
54- return 1 # Return failure if no match
40+ # Function to extract all unique issue numbers and their corresponding repositories from PR_BODY
41+ # It iterates through defined patterns and extracts all matches.
42+ # Returns a list of "issue_num,repo" pairs, one per line, to standard output.
43+ # All diagnostic messages are redirected to stderr to prevent interference with readarray.
44+ extract_all_issues () {
45+ # Removed 'local' keyword for array declaration
46+ found_issues=()
47+ # Removed 'local' keyword for variable declaration
48+ default_repo=" devtron-labs/devtron" # Default repository if not explicitly mentioned in the link
49+
50+ # Loop through each defined pattern
51+ for pattern in " ${patterns[@]} " ; do
52+ # Use grep -oE to find all non-overlapping matches for the current pattern in PR_BODY
53+ matches=$( echo " $PR_BODY " | grep -oE " $pattern " )
54+
55+ # If matches are found for the current pattern
56+ if [[ -n " $matches " ]]; then
57+ # IMPORTANT: Redirect all diagnostic echo statements to stderr (>&2)
58+ echo " Matched for pattern: $pattern " >&2
59+ # Read each match into the 'match' variable
60+ while IFS= read -r match; do
61+ # Extract the issue number (sequence of digits) from the matched string
62+ # Removed 'local' keyword for variable declaration
63+ current_issue_num=$( echo " $match " | grep -oE " [0-9]+" )
64+ # Extract the repository name (e.g., devtron-labs/devtron) from the matched string
65+ # Removed 'local' keyword for variable declaration
66+ current_repo=$( echo " $match " | grep -oE " devtron-labs/[a-zA-Z0-9_-]+" )
67+
68+ # If no specific repository is found in the link, use the default
69+ if [[ -z " $current_repo " ]]; then
70+ current_repo=" $default_repo "
71+ fi
72+
73+ # If a valid issue number was extracted, add it to the list
74+ if [[ -n " $current_issue_num " ]]; then
75+ found_issues+=(" $current_issue_num ,$current_repo " )
76+ # IMPORTANT: Redirect all diagnostic echo statements to stderr (>&2)
77+ echo " Extracted issue: $current_issue_num from repo: $current_repo " >&2
78+ fi
79+ done <<< " $matches" # Use a here-string to feed matches into the while loop
80+ fi
81+ done
82+ # Print unique issue-repo pairs, sorted, to standard output for readarray
83+ printf " %s\n" " ${found_issues[@]} " | sort -u
5584}
5685
57- issue_num=" "
58- repo=" devtron-labs/devtron" # Default repo
59- for pattern in " ${patterns[@]} " ; do
60- echo " Now checking for $pattern "
61- extract_issue_number " $pattern " && break
62- done
86+ # Call the function to extract all unique issue-repo pairs and store them in an array
87+ readarray -t all_issues < <( extract_all_issues)
6388
64- if [[ -z " $issue_num " ]]; then
65- echo " No valid issue number found."
89+ # Check if any issues were found in the PR body
90+ if [[ ${# all_issues[@]} -eq 0 ]]; then
91+ echo " No valid issue number found in PR body."
6692 if [[ " $forked " == " false" ]]; then
93+ # If not a forked PR, add 'Issue-verification-failed' and remove 'Ready-to-Review' label
6794 gh pr edit $PRNUM --add-label " PR:Issue-verification-failed"
6895 gh pr edit $PRNUM --remove-label " PR:Ready-to-Review"
6996 fi
7097 exit 1
7198fi
7299
73- # Form the issue API URL dynamically
74- issue_api_url= " https://api.github.com/repos/ $repo /issues/ $issue_num "
75- echo " API URL: $issue_api_url "
100+ # Initialize a flag to track overall validation status and a string to collect failed links
101+ all_issues_valid=true
102+ failed_issue_links= " "
76103
77- if [[ $repo == " devtron-labs/devtron" || $repo == " devtron-labs/devtron-services" || $repo == " devtron-labs/dashboard" ]]; then
78- echo " No extra arguments needed: public repository detected."
79- response=$( curl -s -w " %{http_code}" " $issue_api_url " ) # Get the response body and status code in one go
80- else
81- echo " Adding extra arguments for authentication: private repository detected."
82- response=$( curl -s -w " %{http_code}" --header " Authorization: Bearer $GH_PR_VALIDATOR_TOKEN " \
83- --header " Accept: application/vnd.github+json" " $issue_api_url " )
84- fi
104+ # Loop through each unique issue-repo pair found
105+ for issue_repo_pair in " ${all_issues[@]} " ; do
106+ # Split the pair into issue_num and repo using comma as delimiter
107+ # Variables are now global, no 'local' needed here
108+ IFS=' ,' read -r issue_num repo <<< " $issue_repo_pair"
85109
86- # Extract HTTP status code from the response
87- response_code=$( echo " $response " | tail -n 1) # Status code is the last line
88- response_body=$( echo " $response " | head -n -1) # The body is everything except the last line (status code)
110+ echo " Validating issue number: #$issue_num in repo: $repo "
89111
90- echo " Response Code: $response_code "
91- html_url=$( echo " $response_body " | jq -r ' .html_url' ) # Extract html_url from the JSON response
112+ # Form the GitHub API URL for the issue
113+ issue_api_url=" https://api.github.com/repos/$repo /issues/$issue_num "
114+ echo " API URL: $issue_api_url "
92115
93- # Check if the html_url contains "pull-request"
94- if [[ " $html_url " == * " pull " * ]] ; then
95- echo " The issue URL contains a pull-request link, marking as invalid. "
96- gh pr comment $PRNUM --body " PR is linked to a pull request URL, which is invalid. Please update the issue link. "
116+ # Variables are now global, no 'local' needed here
117+ response= " "
118+ response_code= " "
119+ response_body= " "
97120
98- if [[ " $forked " == " false" ]]; then
99- # Apply 'Issue-verification-failed' label and remove 'Ready-to-Review' label.
100- gh pr edit $PRNUM --add-label " PR:Issue-verification-failed"
101- gh pr edit $PRNUM --remove-label " PR:Ready-to-Review"
121+ # Determine if the repository is public or private to apply authentication
122+ if [[ " $repo " == " devtron-labs/devtron" || " $repo " == " devtron-labs/devtron-services" || " $repo " == " devtron-labs/dashboard" ]]; then
123+ echo " No extra arguments needed: public repository detected."
124+ # Use curl to get the response body and HTTP status code
125+ response=$( curl -s -w " %{http_code}" " $issue_api_url " )
126+ else
127+ echo " Adding extra arguments for authentication: private repository detected."
128+ # Use curl with authentication headers for private repositories
129+ response=$( curl -s -w " %{http_code}" --header " Authorization: Bearer $GH_PR_VALIDATOR_TOKEN " \
130+ --header " Accept: application/vnd.github+json" " $issue_api_url " )
102131 fi
103- exit 1
104- fi
105132
106- # If response_code is 200, proceed with validating the issue
107- if [[ " $response_code " -eq 200 ]]; then
108- echo " Issue Number: #$issue_num is valid and exists in Repo: $repo ."
109- if [[ " $forked " == " false" ]]; then
110- gh pr edit $PRNUM --remove-label " PR:Issue-verification-failed"
111- gh pr edit $PRNUM --add-label " PR:Ready-to-Review"
133+ # Extract HTTP status code (last line of curl output) and response body
134+ response_code=$( echo " $response " | tail -n 1)
135+ response_body=$( echo " $response " | head -n -1)
136+
137+ echo " Response Code: $response_code "
138+ # Extract the 'html_url' from the JSON response body using jq
139+ # Variable is now global, no 'local' needed here
140+ html_url=$( echo " $response_body " | jq -r ' .html_url' )
141+
142+ # Check if the extracted URL points to a pull request instead of an issue
143+ if [[ " $html_url " == * " pull" * ]]; then
144+ echo " The issue URL contains a pull-request link, marking as invalid."
145+ # Append error message to the failed_issue_links string
146+ failed_issue_links+=" Issue #$issue_num in $repo is linked to a pull request URL, which is invalid.\n"
147+ all_issues_valid=false # Set overall status to invalid
148+ elif [[ " $response_code " -eq 200 ]]; then
149+ # If HTTP status code is 200, the issue is valid
150+ echo " Issue Number: #$issue_num is valid and exists in Repo: $repo ."
151+ else
152+ # If issue not found or invalid HTTP status code
153+ echo " Issue not found. Invalid URL or issue number: #$issue_num in $repo ."
154+ failed_issue_links+=" Issue #$issue_num in $repo is not found or invalid (HTTP $response_code ).\n"
155+ all_issues_valid=false # Set overall status to invalid
112156 fi
113- echo " PR:Ready-to-Review, exiting gracefully"
114- exit 0
115- else
116- echo " Issue not found. Invalid URL or issue number."
117- gh pr comment $PRNUM --body " PR is not linked to a valid issue. Please update the issue link."
157+ done
118158
119- if [[ " $forked " == " false" ]]; then
120- # Apply 'Issue-verification-failed' label and remove 'Ready-to-Review' label.
159+ # Final label application and comments based on the overall validation status
160+ if [[ " $forked " == " false" ]]; then
161+ # If not a forked PR, modify labels and add comments
162+ if [[ " $all_issues_valid " == " true" ]]; then
163+ # All issues are valid, remove 'Issue-verification-failed' and add 'Ready-to-Review'
164+ gh pr edit $PRNUM --remove-label " PR:Issue-verification-failed"
165+ gh pr edit $PRNUM --add-label " PR:Ready-to-Review"
166+ echo " All linked issues are valid. PR:Ready-to-Review."
167+ exit 0
168+ else
169+ # Some issues are invalid, add a comment with details and update labels
170+ gh pr comment $PRNUM --body " Some linked issues are invalid. Please update the issue links:\n$failed_issue_links "
121171 gh pr edit $PRNUM --add-label " PR:Issue-verification-failed"
122172 gh pr edit $PRNUM --remove-label " PR:Ready-to-Review"
173+ echo " Some linked issues are invalid. PR:Issue-verification-failed."
174+ exit 1
175+ fi
176+ else
177+ # For forked PRs, just output the status, do not modify labels
178+ if [[ " $all_issues_valid " == " true" ]]; then
179+ echo " All linked issues are valid for forked PR."
180+ exit 0
181+ else
182+ echo " Some linked issues are invalid for forked PR:\n$failed_issue_links "
183+ exit 1
123184 fi
124- exit 1
125185fi
0 commit comments