From 4aa2db4d654eeb9becdc73d831e8b49e7118cd3f Mon Sep 17 00:00:00 2001 From: Anil Belur Date: Sat, 20 Jul 2024 22:30:49 +1000 Subject: [PATCH 1/2] Feat: Add pr as commit message - Support USE_PR_AS_COMMIT flag, allows commit message to be replaced the pull request body and tittle. Note: the 50/72 rule of the title and body needs to be taken care of by the developer or the pre-commit verify would fail. - Comment the PR URL link alone with the workflow run URL on the Gerrit change request using `ssh gerrit review` CLI. This is useful to Jenkins jobs to query comments on the change and search for PR-### URL therefore allow developer comments to be sync back to the pull request on Github. - Prefix the git-review topic with the project name followed by PR-### to ensure same topic does not overused from diff projects under the same organization. - Update docs and examples caller workflows accordingly. Signed-off-by: Anil Belur --- .../workflows/call-g2g-composite-action.yaml | 1 + .github/workflows/github2gerrit.yaml | 56 ++++++++++++++++++- README.md | 16 +++++- action.yaml | 55 +++++++++++++++++- 4 files changed, 121 insertions(+), 7 deletions(-) diff --git a/.github/workflows/call-g2g-composite-action.yaml b/.github/workflows/call-g2g-composite-action.yaml index dc9e6c8..f00aa67 100644 --- a/.github/workflows/call-g2g-composite-action.yaml +++ b/.github/workflows/call-g2g-composite-action.yaml @@ -28,6 +28,7 @@ jobs: id: gerrit-upload uses: lfit/github2gerrit@main with: + USE_PR_AS_COMMIT: false FETCH_DEPTH: 10 GERRIT_KNOWN_HOSTS: ${{ vars.GERRIT_KNOWN_HOSTS }} GERRIT_SERVER: ${{ vars.GERRIT_SERVER }} diff --git a/.github/workflows/github2gerrit.yaml b/.github/workflows/github2gerrit.yaml index 82872ab..9f92b91 100644 --- a/.github/workflows/github2gerrit.yaml +++ b/.github/workflows/github2gerrit.yaml @@ -7,6 +7,11 @@ name: github2gerrit-reusable-workflow on: workflow_call: inputs: + USE_PR_AS_COMMIT: + description: "Use PR body and tittle as commit message" + required: false + default: false + type: boolean FETCH_DEPTH: description: "fetch-depth for the clone. (Default: 10)" required: false @@ -245,6 +250,33 @@ jobs: git commit -s -v --no-edit -m "$(cat $commit_message)" git log -n2 + - name: Overwrite commit message with PR tittle and body + if: ${{ (github.event_name == 'pull_request_target') && (env.PR_COMMITS > 0) && (inputs.USE_PR_AS_COMMIT == true) }} + shell: bash + # yamllint disable rule:line-length + run: | + set -x + + pr_tittle_length="$(gh pr view ${{ env.PR_NUMBER }} --json title | jq '.[] | length')" + pr_body_length="$(gh pr view ${{ env.PR_NUMBER }} --json body | jq '.[] | length')" + + gh pr view ${{ env.PR_NUMBER }} --json title | jq -r '.title | select( . != null )' > pr_title.txt + # add blank line between title and body + echo "" >> pr_title.txt + gh pr view ${{ env.PR_NUMBER }} --json body | jq -r '.body | select( . != null )' >> pr_body.txt + + # Note: its upto the dev to ensure the 50/72 rule + cat pr_title.txt pr_body.txt > pr_commit.txt + echo "" >> pr_commit.txt + cat signed-off-by-final.txt >> pr_commit.txt + + if [[ -f pr_commit.txt ]] && [[ ($pr_body_length -gt 0) ]] && [[ ($pr_tittle_length -gt 0) ]]; then + git commit -s -v --amend --no-edit -F "pr_commit.txt" + git log -n2 + fi + env: + GH_TOKEN: ${{ github.token }} + - name: Submit the change to Gerrit repository id: submit if: env.PR_NUMBER != '' @@ -257,8 +289,9 @@ jobs: # If the reviewers email is unset/empty then use a default reviewers=${reviewers_emails_list:-"${{ inputs.GERRIT_SSH_USER_G2G_EMAIL }}"} + topic="GH-${{ env.PROJECT_REPO_GITHUB }}-${{ env.PR_NUMBER }}" echo "git review .... inprogress" - git review --yes -t "GH-PR-${{ env.PR_NUMBER }}" --reviewers "$reviewers" + git review --yes -t "$topic" --reviewers "$reviewers" # retrive change-id from the submitted PR gerrit_change_id=$(git show HEAD --format=%B -s | grep Change-Id: | cut -d " " -f2;) @@ -284,9 +317,28 @@ jobs: query_result_url=$(jq -r '.url | select( . != null )' query_result.txt) query_result_number=$(jq -r '.number | select( . != null )' query_result.txt) + query_result_commit_sha=$(jq -r '.currentPatchSet.revision | select( . != null )' query_result.txt) echo "GERRIT_CHANGE_REQUEST_URL=${query_result_url}" >> "$GITHUB_ENV" echo "GERRIT_CHANGE_REQUEST_NUMBER=${query_result_number}" >> "$GITHUB_ENV" + echo "GERRIT_COMMIT_SHA=${query_result_commit_sha}" >> "$GITHUB_ENV" + + - name: Add source of Github PR URL as a gerrit comment + if: env.GERRIT_CHANGE_ID != '' + shell: bash + # yamllint disable rule:line-length + run: | + set -x + + run_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + pr_path="${{ github.server_url }}/${{ github.repository }}/pull/${{ env.PR_NUMBER }}" + message="$(printf 'GHPR: %s\nAction-Run: %s\n' "${pr_path}" "${run_url}")" + + # Add a comment on the change was create from Github. + ssh -v -p 29418 "${{ inputs.GERRIT_SSH_USER_G2G }}@${{ env.GERRIT_SERVER }}" \ + gerrit review -m "'""${message}""'" \ + --branch ${{ env.GERRIT_BRANCH }} --project "${{ env.PROJECT_REPO_GERRIT }}" \ + "${{ env.GERRIT_COMMIT_SHA }}" - name: PR Comment update CR number if: env.GERRIT_CHANGE_REQUEST_URL != '' @@ -296,7 +348,7 @@ jobs: retries: 3 retry-exempt-status-codes: 400,401 script: | - const output = `The pull-request PR-${{ env.PR_NUMBER }} is submitted to Gerrit [${{ vars.ORGANIZATION }}](https://${{ env.GERRIT_SERVER }})! \n + const output = `The pull-request PR-${{ env.PR_NUMBER }} is submitted to Gerrit [${{ inputs.ORGANIZATION }}](https://${{ env.GERRIT_SERVER }})! \n To follow up on the change visit: [${{ env.GERRIT_CHANGE_REQUEST_NUMBER }}](${{ env.GERRIT_CHANGE_REQUEST_URL }}) \n \n NOTE: The pull-request PR-${{ env.PR_NUMBER }} will be closed, re-opening the pull-request will not update the same commit and may result in duplicate changes on Gerrit.` github.rest.issues.createComment({ diff --git a/README.md b/README.md index b7c23b2..b56c0c2 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,22 @@ The action and workflow are written with bash scripts using well known Git SCM t 2. Squash all the commits in the pull request into a single commit. 3. Check for a Change-Id line in the pull request commit message. If it is not present, add the Change-Id to the commit. If the Change-Id is found in any of the commits, it will be reused along with the patch. 4. Create a Gerrit patch with the Change-Id, squashing all PR changes into a single commit. -5. Close the pull request once the Gerrit patch is submitted. A comment is added to the pull request with the URL to the change. Any updates will require the pull request to be reopened. Updates to the pull request are done with a force push, which triggers the workflows to ensure the change is resubmitted. +5. Add a pull-request reference as a comment on the Gerrit change for committers or reviewers to refer to the source of change request. +6. Close the pull request once the Gerrit patch is submitted. A comment is added to the pull request with the URL to the change. Any updates will require the pull request to be reopened. Updates to the pull request are done with a force push, which triggers the workflows to ensure the change is resubmitted. -## Caveats - Future Improvements +## Features + +### Use pull-request a commit message - Commits in a pull request are squashed into a single commit before submitting the change request to Gerrit. + +### Use pull-request a commit message + +- Set the commit message as pull request body and title along with the change-Id and Signed-off-by lines. Commit message are squashed and the commit body and tittle are discarded. + +## Caveats - Future Improvements + +- Commits in a pull request are submitted as independent change requests to Gerrit under the same topic. - Code review comments on Gerrit will not be updated back on the pull request, requiring developers to follow up on the Gerrit change request URL. ## Required Inputs @@ -33,6 +44,7 @@ The action and workflow are written with bash scripts using well known Git SCM t ## Optional Inputs +- `USE_PR_AS_COMMIT`: Use commit body and tittle from pull-request (Default: false) - `FETCH_DEPTH`: fetch-depth of the clone repo. (Default: 10) - `GERRIT_PROJECT`: Gerrit project repository (Default read from .gitreview). - `GERRIT_SERVER`: Gerrit server FQDN (Default read from .gitreview). diff --git a/action.yaml b/action.yaml index ab351ae..049a6a1 100644 --- a/action.yaml +++ b/action.yaml @@ -6,6 +6,10 @@ name: github2gerrit-composite-action description: "Submit a Githib pull-request to Gerrit repository" inputs: + USE_PR_AS_COMMIT: + description: "Use PR body and tittle as commit message" + required: false + type: boolean FETCH_DEPTH: description: "fetch-depth for the clone." required: true @@ -240,6 +244,33 @@ runs: git commit -s -v --no-edit -m "$(cat $commit_message)" git log -n2 + - name: Overwrite commit message with PR tittle and body + if: ${{ (github.event_name == 'pull_request_target') && (env.PR_COMMITS > 0) && (inputs.USE_PR_AS_COMMIT == true) }} + shell: bash + # yamllint disable rule:line-length + run: | + set -x + + pr_tittle_length="$(gh pr view ${{ env.PR_NUMBER }} --json title | jq '.[] | length')" + pr_body_length="$(gh pr view ${{ env.PR_NUMBER }} --json body | jq '.[] | length')" + + gh pr view ${{ env.PR_NUMBER }} --json title | jq -r '.title | select( . != null )' > pr_title.txt + # add blank line between title and body + echo "" >> pr_title.txt + gh pr view ${{ env.PR_NUMBER }} --json body | jq -r '.body | select( . != null )' >> pr_body.txt + + # Note: its upto the dev to ensure the 50/72 rule + cat pr_title.txt pr_body.txt > pr_commit.txt + echo "" >> pr_commit.txt + cat signed-off-by-final.txt >> pr_commit.txt + + if [[ -f pr_commit.txt ]] && [[ ($pr_body_length -gt 0) ]] && [[ ($pr_tittle_length -gt 0) ]]; then + git commit -s -v --amend --no-edit -F "pr_commit.txt" + git log -n2 + fi + env: + GH_TOKEN: ${{ github.token }} + - name: Submit the change to Gerrit repository id: submit if: env.PR_NUMBER != '' @@ -252,8 +283,9 @@ runs: # If the reviewers email is unset/empty then use a default reviewers=${reviewers_emails_list:-"${{ inputs.GERRIT_SSH_USER_G2G_EMAIL }}"} + topic="GH-${{ env.PROJECT_REPO_GITHUB }}-${{ env.PR_NUMBER }}" echo "git review .... inprogress" - git review --yes -t "GH-PR-${{ env.PR_NUMBER }}" --reviewers "$reviewers" + git review --yes -t "$topic" --reviewers "$reviewers" # retrive change-id from the submitted PR gerrit_change_id=$(git show HEAD --format=%B -s | grep Change-Id: | cut -d " " -f2;) @@ -279,11 +311,28 @@ runs: query_result_url=$(jq -r '.url | select( . != null )' query_result.txt) query_result_number=$(jq -r '.number | select( . != null )' query_result.txt) + query_result_commit_sha=$(jq -r '.currentPatchSet.revision | select( . != null )' query_result.txt) echo "GERRIT_CHANGE_REQUEST_URL=${query_result_url}" >> "$GITHUB_ENV" echo "GERRIT_CHANGE_REQUEST_NUMBER=${query_result_number}" >> "$GITHUB_ENV" - echo "GERRIT_CHANGE_REQUEST_URL=${query_result_url}" >> "$GITHUB_OUTPUT" - echo "GERRIT_CHANGE_REQUEST_NUMBER=${query_result_number}" >> "$GITHUB_OUTPUT" + echo "GERRIT_COMMIT_SHA=${query_result_commit_sha}" >> "$GITHUB_ENV" + + - name: Add source of Github PR URL as a gerrit comment + if: env.GERRIT_CHANGE_ID != '' + shell: bash + # yamllint disable rule:line-length + run: | + set -x + + run_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + pr_path="${{ github.server_url }}/${{ github.repository }}/pull/${{ env.PR_NUMBER }}" + message="$(printf 'GHPR: %s\nAction-Run: %s\n' "${pr_path}" "${run_url}")" + + # Add a comment on the change was create from Github. + ssh -v -p 29418 "${{ inputs.GERRIT_SSH_USER_G2G }}@${{ env.GERRIT_SERVER }}" \ + gerrit review -m "'""${message}""'" \ + --branch ${{ env.GERRIT_BRANCH }} --project "${{ env.PROJECT_REPO_GERRIT }}" \ + "${{ env.GERRIT_COMMIT_SHA }}" - name: PR Comment update CR number if: env.GERRIT_CHANGE_REQUEST_URL != '' From b84f60c788f1d9b875f7f1ff1aa6bd0989d106fd Mon Sep 17 00:00:00 2001 From: Anil Belur Date: Tue, 23 Jul 2024 12:22:57 +1000 Subject: [PATCH 2/2] Feat: Enable support to submit single commits Each commit in the pull request are processed individually as a single commit before submitting to Gerrit repository. This option allows you to preserve git history. This requires `inputs.SUBMIT_SINGLE_COMMITS` to be set to 'true' from the caller. Remove extra inputs. Gerrit server and port and project has be read from .gitreview file. Composite actions have a limitation on the number of the inputs that can be passed to 10. Update doc example with new feat options. Signed-off-by: Anil Belur --- .../workflows/call-g2g-composite-action.yaml | 7 +- .github/workflows/github2gerrit.yaml | 146 +++++++++--- README.md | 43 ++-- action.yaml | 220 +++++++++++------- 4 files changed, 277 insertions(+), 139 deletions(-) diff --git a/.github/workflows/call-g2g-composite-action.yaml b/.github/workflows/call-g2g-composite-action.yaml index f00aa67..6169699 100644 --- a/.github/workflows/call-g2g-composite-action.yaml +++ b/.github/workflows/call-g2g-composite-action.yaml @@ -28,11 +28,8 @@ jobs: id: gerrit-upload uses: lfit/github2gerrit@main with: - USE_PR_AS_COMMIT: false - FETCH_DEPTH: 10 - GERRIT_KNOWN_HOSTS: ${{ vars.GERRIT_KNOWN_HOSTS }} - GERRIT_SERVER: ${{ vars.GERRIT_SERVER }} - GERRIT_SERVER_PORT: "29418" + SUBMIT_SINGLE_COMMITS: "true" + USE_PR_AS_COMMIT: "false" GERRIT_SSH_PRIVKEY_G2G: ${{ secrets.GERRIT_SSH_PRIVKEY_G2G }} GERRIT_SSH_USER_G2G: ${{ vars.GERRIT_SSH_USER_G2G }} GERRIT_SSH_USER_G2G_EMAIL: ${{ vars.GERRIT_SSH_USER_G2G_EMAIL }} diff --git a/.github/workflows/github2gerrit.yaml b/.github/workflows/github2gerrit.yaml index 9f92b91..266ac7f 100644 --- a/.github/workflows/github2gerrit.yaml +++ b/.github/workflows/github2gerrit.yaml @@ -7,6 +7,11 @@ name: github2gerrit-reusable-workflow on: workflow_call: inputs: + SUBMIT_SINGLE_COMMITS: + description: "Submit one commit at a time to the Gerrit repository" + required: false + default: false + type: boolean USE_PR_AS_COMMIT: description: "Use PR body and tittle as commit message" required: false @@ -73,6 +78,14 @@ jobs: with: python-version: "3.11" + - name: Validate workflow inputs + if: ${{ (inputs.USE_PR_AS_COMMIT == true) && (inputs.SUBMIT_SINGLE_COMMITS == true) }} + shell: bash + # yamllint disable rule:line-length + run: | + echo "Error: USE_PR_AS_COMMIT and SUBMIT_SINGLE_COMMITS cannot be enabled at the same time" + exit 1 + - name: "Install required dependencies: git-review,jq" shell: bash run: | @@ -212,9 +225,37 @@ jobs: env: GH_TOKEN: ${{ github.token }} + - name: Prepare commits to submit one at a time + shell: bash + if: ${{ (env.PR_COMMITS > 0) && (inputs.SUBMIT_SINGLE_COMMITS == true) }} + # yamllint disable rule:line-length + run: | + set -x + commit_shas=$(gh pr view ${{ env.PR_NUMBER }} --json commits --jq '.commits[] | .oid') + git checkout -b tmp_branch ${{ github.event.pull_request.base.sha }} + + # Cherry-pick the commits into diff branch and amend to add change-Id + for csha in ${commit_shas}; do + git checkout tmp_branch + git cherry-pick "${csha}" + git commit -s -v --no-edit --amend + change_id="$(git log --format="%(trailers:key=Change-Id,valueonly,separator=%x2C)" -n1)" + if [ -n "${change_id}" ]; then + # Capture the newly created change-Id + echo "$change_id" >> change-Id.txt + else + echo "FAIL: Change-Id not created, exit job!" + exit 1 + fi + git checkout ${{ env.GERRIT_BRANCH }} + done + git log -n3 + env: + GH_TOKEN: ${{ github.token }} + - name: Squash commits into a single commit shell: bash - if: ${{ (env.PR_COMMITS > 0) }} + if: ${{ (env.PR_COMMITS > 0) && (inputs.SUBMIT_SINGLE_COMMITS == false) }} # yamllint disable rule:line-length run: | set -x @@ -251,7 +292,7 @@ jobs: git log -n2 - name: Overwrite commit message with PR tittle and body - if: ${{ (github.event_name == 'pull_request_target') && (env.PR_COMMITS > 0) && (inputs.USE_PR_AS_COMMIT == true) }} + if: ${{ (github.event_name == 'pull_request_target') && (env.PR_COMMITS > 0) && (inputs.USE_PR_AS_COMMIT == true) && (inputs.SUBMIT_SINGLE_COMMITS == false) }} shell: bash # yamllint disable rule:line-length run: | @@ -285,6 +326,10 @@ jobs: run: | set -x + if ${{ inputs.SUBMIT_SINGLE_COMMITS == true }}; then + git checkout tmp_branch + fi + reviewers_emails_list="${{ inputs.REVIEWERS_EMAIL }}" # If the reviewers email is unset/empty then use a default reviewers=${reviewers_emails_list:-"${{ inputs.GERRIT_SSH_USER_G2G_EMAIL }}"} @@ -293,35 +338,68 @@ jobs: echo "git review .... inprogress" git review --yes -t "$topic" --reviewers "$reviewers" - # retrive change-id from the submitted PR - gerrit_change_id=$(git show HEAD --format=%B -s | grep Change-Id: | cut -d " " -f2;) - - if [[ "$gerrit_change_id" != '' ]]; then - echo "GERRIT_CHANGE_ID=${gerrit_change_id}" >> "$GITHUB_ENV" - fi - - - name: Retrive the Gerrit change number from Change-ID - if: env.GERRIT_CHANGE_ID != '' + - name: Validate Change-ID and retrive the Gerrit change number id: change_num shell: bash # yamllint disable rule:line-length run: | set -x - # Query for a pre-existing gerrit review to retrive Change-Id - ssh -v -p 29418 "${{ inputs.GERRIT_SSH_USER_G2G }}@${{ env.GERRIT_SERVER }}" \ - gerrit query limit:1 owner:self is:open \ - project:"${{ env.PROJECT_REPO_GERRIT }}" \ - --current-patch-set --format=JSON \ - "${{ env.GERRIT_CHANGE_ID }}" > query_result.txt + if [[ ! -s change-Id.txt ]]; then + # retrive change-id from the submitted PR + gerrit_change_id=$(git show HEAD --format=%B -s | grep Change-Id: | cut -d " " -f2;) + if [[ "$gerrit_change_id" == '' ]]; then + echo "GERRIT_CHANGE_ID: null"; exit 1 + fi + echo "$gerrit_change_id" >> change-Id.txt + fi - query_result_url=$(jq -r '.url | select( . != null )' query_result.txt) - query_result_number=$(jq -r '.number | select( . != null )' query_result.txt) - query_result_commit_sha=$(jq -r '.currentPatchSet.revision | select( . != null )' query_result.txt) + if [[ -s change-Id.txt ]]; then + GERRIT_CHANGE_ID_VALUES="$(> "$GITHUB_ENV" + fi - echo "GERRIT_CHANGE_REQUEST_URL=${query_result_url}" >> "$GITHUB_ENV" - echo "GERRIT_CHANGE_REQUEST_NUMBER=${query_result_number}" >> "$GITHUB_ENV" - echo "GERRIT_COMMIT_SHA=${query_result_commit_sha}" >> "$GITHUB_ENV" + while IFS="" read -r cid; do + # Query for a pre-existing gerrit review to retrive Change-Id + ssh -v -n -p 29418 "${{ inputs.GERRIT_SSH_USER_G2G }}@${{ env.GERRIT_SERVER }}" \ + "gerrit query limit:1 owner:self is:open" \ + "project:${{ env.PROJECT_REPO_GERRIT }}" \ + --current-patch-set --format=JSON \ + "$cid" > query_result.txt + + query_result_url=$(jq -r '.url | select( . != null )' query_result.txt) + query_result_number=$(jq -r '.number | select( . != null )' query_result.txt) + query_result_commit_sha=$(jq -r '.currentPatchSet.revision | select( . != null )' query_result.txt) + + echo "${query_result_url}" >> change-url.txt + echo "${query_result_commit_sha}" >> commit-sha.txt + echo "${query_result_number}" >> change-request-number.txt + done < change-Id.txt + + GERRIT_CHANGE_REQUEST_URL_VALUES="$(> "$GITHUB_ENV" + + GERRIT_COMMIT_SHA_VALUES="$(> "$GITHUB_ENV" + + GERRIT_CHANGE_REQUEST_NUM_VALUES="$(> "$GITHUB_ENV" - name: Add source of Github PR URL as a gerrit comment if: env.GERRIT_CHANGE_ID != '' @@ -334,14 +412,18 @@ jobs: pr_path="${{ github.server_url }}/${{ github.repository }}/pull/${{ env.PR_NUMBER }}" message="$(printf 'GHPR: %s\nAction-Run: %s\n' "${pr_path}" "${run_url}")" - # Add a comment on the change was create from Github. - ssh -v -p 29418 "${{ inputs.GERRIT_SSH_USER_G2G }}@${{ env.GERRIT_SERVER }}" \ - gerrit review -m "'""${message}""'" \ - --branch ${{ env.GERRIT_BRANCH }} --project "${{ env.PROJECT_REPO_GERRIT }}" \ - "${{ env.GERRIT_COMMIT_SHA }}" - - - name: PR Comment update CR number - if: env.GERRIT_CHANGE_REQUEST_URL != '' + # Add comment backref change request to Github PR and workflow job. + while IFS="" read -r csha; do + ssh -v -n -p 29418 "${{ inputs.GERRIT_SSH_USER_G2G }}@${{ env.GERRIT_SERVER }}" \ + gerrit review -m "'""${message}""'" \ + --branch ${{ env.GERRIT_BRANCH }} --project "${{ env.PROJECT_REPO_GERRIT }}" \ + "$csha" + done < commit-sha.txt + + - name: Add comment to refrence the change-request on the pull-request + if: | + hashFiles('commit-sha.txt') != '' || + (! startsWith(env.GERRIT_CHANGE_REQUEST_URL, '')) uses: actions/github-script@v7 with: result-encoding: string @@ -349,7 +431,7 @@ jobs: retry-exempt-status-codes: 400,401 script: | const output = `The pull-request PR-${{ env.PR_NUMBER }} is submitted to Gerrit [${{ inputs.ORGANIZATION }}](https://${{ env.GERRIT_SERVER }})! \n - To follow up on the change visit: [${{ env.GERRIT_CHANGE_REQUEST_NUMBER }}](${{ env.GERRIT_CHANGE_REQUEST_URL }}) \n \n + To follow up on the change visit: \n \n ${{ env.GERRIT_CHANGE_REQUEST_URL }} \n \n NOTE: The pull-request PR-${{ env.PR_NUMBER }} will be closed, re-opening the pull-request will not update the same commit and may result in duplicate changes on Gerrit.` github.rest.issues.createComment({ issue_number: context.issue.number, diff --git a/README.md b/README.md index b56c0c2..ae12027 100644 --- a/README.md +++ b/README.md @@ -14,26 +14,41 @@ The action extracts the commits from a GitHub pull-request and submits them to a The action and workflow are written with bash scripts using well known Git SCM tools, gh, jq and git-review. 1. The action is triggered when a new pull request is created on a GitHub repository configured with the action. -2. Squash all the commits in the pull request into a single commit. +2. One of the below options can be used depending on the workflow followed by the community. + +- Squash all the commits in the pull request into a single commit and use consolidate the commit titles and body into a single commit (Default). +- Squash all the commits in the pull request into a single commit and using the pull request title and body as the commit title and body (USE_PR_AS_COMMIT). +- Submit each commit as a separate single commit preserving the git history (SUBMIT_SINGLE_COMMITS). + 3. Check for a Change-Id line in the pull request commit message. If it is not present, add the Change-Id to the commit. If the Change-Id is found in any of the commits, it will be reused along with the patch. -4. Create a Gerrit patch with the Change-Id, squashing all PR changes into a single commit. -5. Add a pull-request reference as a comment on the Gerrit change for committers or reviewers to refer to the source of change request. -6. Close the pull request once the Gerrit patch is submitted. A comment is added to the pull request with the URL to the change. Any updates will require the pull request to be reopened. Updates to the pull request are done with a force push, which triggers the workflows to ensure the change is resubmitted. +4. Add the Change-Id (and optionally squash changes into a single commit if required). +5. Add a pull-request and workflow run reference link as a comment on the Gerrit change that was created for committers or reviewers to back reference to the source of change request. +6. Add a comment to the pull request with the URL to the change. Any updates will require the pull request to be reopened and updates to the pull request must be done with a force push, which triggers the workflows to ensure the change is resubmitted. +7. Close the pull request once the Gerrit patch is submitted successfully. ## Features -### Use pull-request a commit message +### Use pull-request as a single squashed commit message + +- Commits in a pull request are squashed into a single commit before submitting the change request to Gerrit. This is the default behavior shown in the caller workflow examples. +- Merge commits get filtered out. +- Here `inputs.SUBMIT_SINGLE_COMMITS` is set to 'false' by default. + +### Use pull-request body and title in the commit message -- Commits in a pull request are squashed into a single commit before submitting the change request to Gerrit. +- The commit message title and body is extracted from the pull request body and title along with the change-Id and Signed-off-by lines. Commits are still squashed and while only the commit body and title are discarded. +- Requires setting `inputs.USE_PR_AS_COMMIT` to 'true'. +- This option is exclusive with `inputs.SUBMIT_SINGLE_COMMITS` and cannot be used together. -### Use pull-request a commit message +### Submit each commit as a separate single commit -- Set the commit message as pull request body and title along with the change-Id and Signed-off-by lines. Commit message are squashed and the commit body and tittle are discarded. +- Each commit in the pull request are processed individually as a single commit before submitting to Gerrit repository. This option allows you to preserve git history. +- Requires `inputs.SUBMIT_SINGLE_COMMITS` to be set to 'true' in the caller. ## Caveats - Future Improvements -- Commits in a pull request are submitted as independent change requests to Gerrit under the same topic. -- Code review comments on Gerrit will not be updated back on the pull request, requiring developers to follow up on the Gerrit change request URL. +- `inputs.SUBMIT_SINGLE_COMMITS` has not be tested extensively for handling large pull requests. +- Code review comments on Gerrit are not synchronized back to the pull request comment, therefore requires developers to follow up on the Gerrit change request URL. Rework through the recommended changes can be done by reopening the pull request and updating to the commits through a force push. ## Required Inputs @@ -44,7 +59,8 @@ The action and workflow are written with bash scripts using well known Git SCM t ## Optional Inputs -- `USE_PR_AS_COMMIT`: Use commit body and tittle from pull-request (Default: false) +- `SUBMIT_SINGLE_COMMITS`: Submit one commit at a time to the Gerrit repository (Default: false) +- `USE_PR_AS_COMMIT`: Use commit body and title from pull-request (Default: false) - `FETCH_DEPTH`: fetch-depth of the clone repo. (Default: 10) - `GERRIT_PROJECT`: Gerrit project repository (Default read from .gitreview). - `GERRIT_SERVER`: Gerrit server FQDN (Default read from .gitreview). @@ -55,6 +71,7 @@ The action and workflow are written with bash scripts using well known Git SCM t ## Full Example Usage with Composite Action Use the composite action as a step in the workflow for further processing. +Example workflow does not enable `SUBMIT_SINGLE_COMMITS` and `USE_PR_AS_COMMIT` ```yaml --- @@ -85,10 +102,10 @@ jobs: id: gerrit-upload uses: lfit/github2gerrit@main with: + SUBMIT_SINGLE_COMMITS: "false" + USE_PR_AS_COMMIT: "false" FETCH_DEPTH: 10 GERRIT_KNOWN_HOSTS: ${{ vars.GERRIT_KNOWN_HOSTS }} - GERRIT_SERVER: ${{ vars.GERRIT_SERVER }} - GERRIT_SERVER_PORT: "29418" GERRIT_SSH_PRIVKEY_G2G: ${{ secrets.GERRIT_SSH_PRIVKEY_G2G }} GERRIT_SSH_USER_G2G: ${{ vars.GERRIT_SSH_USER_G2G }} GERRIT_SSH_USER_G2G_EMAIL: ${{ vars.GERRIT_SSH_USER_G2G_EMAIL }} diff --git a/action.yaml b/action.yaml index 049a6a1..70a20bc 100644 --- a/action.yaml +++ b/action.yaml @@ -6,51 +6,38 @@ name: github2gerrit-composite-action description: "Submit a Githib pull-request to Gerrit repository" inputs: + SUBMIT_SINGLE_COMMITS: + description: "Submit one commit at a time to the Gerrit repository" + required: false + default: "false" USE_PR_AS_COMMIT: description: "Use PR body and tittle as commit message" required: false - type: boolean + default: "false" FETCH_DEPTH: description: "fetch-depth for the clone." - required: true - type: string + required: false + default: "10" GERRIT_KNOWN_HOSTS: description: "known hosts" required: true - type: string - GERRIT_SERVER: - description: "Gerrit hostname ex: git.opendaylight.org" - required: false - type: string - GERRIT_SERVER_PORT: - description: "Gerrit port. (Default: 29418)" - required: false - type: string - GERRIT_PROJECT: - description: "Gerrit project name. ex: releng/builder" - required: false - type: string GERRIT_SSH_PRIVKEY_G2G: description: "SSH Private key" required: true - type: string GERRIT_SSH_USER_G2G: description: "Gerrit user-id for SSH" required: true - type: string GERRIT_SSH_USER_G2G_EMAIL: description: "Email of the SSH user" required: true - type: string ORGANIZATION: description: "Orginazation name ex: OpendayLight.org" required: true - type: string + default: "OpendayLight.org" REVIEWERS_EMAIL: description: "Committers email list (comma separated) to notify on code-reviews" required: false default: "" - type: string outputs: url: @@ -69,6 +56,14 @@ runs: with: python-version: "3.11" + - name: Validate workflow inputs + if: ${{ (inputs.USE_PR_AS_COMMIT == 'true') && (inputs.SUBMIT_SINGLE_COMMITS == 'true') }} + shell: bash + # yamllint disable rule:line-length + run: | + echo "Error: USE_PR_AS_COMMIT and SUBMIT_SINGLE_COMMITS cannot be set at the same time!" + exit 1 + - name: "Install required dependencies: git-review,jq" shell: bash run: | @@ -83,24 +78,18 @@ runs: fetch-depth: ${{ inputs.FETCH_DEPTH }} ref: ${{ github.event.pull_request.head.sha }} - - name: "Read inputs to set PROJECT_REPO_{GERRIT,GITHUB} if .gitreview is undefined" - if: ${{ hashFiles('.gitreview') == '' }} + - name: "Report error when .gitreview is undefined" + if: "${{ hashFiles('.gitreview') == '' }}" shell: bash run: | set -x - project_repo_github="${{ github.repository }}" - if [[ ${{ inputs.GERRIT_PROJECT }} != "$project_repo_github" ]]; then - # remove repo owner name - project_repo_github="${project_repo_github#*/}" - # change any '-' to '/' and - project_repo_gerrit="${project_repo_github//-//}" - echo "PROJECT_REPO_GITHUB=${project_repo_github}" >> "$GITHUB_ENV" - echo "PROJECT_REPO_GERRIT=${project_repo_gerrit}" >> "$GITHUB_ENV" - fi + echo "Error: .gitreview file is required for the composite action to run" + echo "Gerrit not supported as inputs for composite action" + exit 1 - name: Read .gitreview to set PROJECT_REPO_GERRIT & PROJECT_REPO_GITHUB - if: ${{ hashFiles('.gitreview') != '' }} + if: "${{ hashFiles('.gitreview') != '' }}" shell: bash # yamllint disable rule:line-length run: | @@ -116,7 +105,7 @@ runs: echo "PROJECT_REPO_GITHUB=${project_repo_github}" >> "$GITHUB_ENV" - name: Read .gitreview and set env GERRIT_SERVER and GERRIT_PORT - if: ${{ hashFiles('.gitreview') != '' }} + if: "${{ hashFiles('.gitreview') != '' }}" shell: bash # yamllint disable rule:line-length run: | @@ -127,21 +116,6 @@ runs: echo "GERRIT_SERVER=${gerrit_server}" >> "$GITHUB_ENV" echo "GERRIT_SERVER_PORT=${gerrit_server_port}" >> "$GITHUB_ENV" - - name: Set GERRIT_SERVER and GERRIT_PORT when .gitreview is undefined - if: ${{ hashFiles('.gitreview') == '' }} - shell: bash - # yamllint disable rule:line-length - run: | - set -x - - if [[ ${{ inputs.GERRIT_SERVER }} != '' ]]; then - echo "GERRIT_SERVER=${{ inputs.GERRIT_SERVER }}" >> "$GITHUB_ENV" - fi - - if [[ ${{ inputs.GERRIT_SERVER_PORT }} != '' ]]; then - echo "GERRIT_SERVER_PORT=${{ inputs.GERRIT_SERVER_PORT }}" >> "$GITHUB_ENV" - fi - - name: Set env GITHUB branch in env shell: bash run: | @@ -207,9 +181,37 @@ runs: env: GH_TOKEN: ${{ github.token }} + - name: Prepare commits to submit one at a time + if: "${{ (env.PR_COMMITS > 0) && (inputs.SUBMIT_SINGLE_COMMITS == 'true') }}" + shell: bash + # yamllint disable rule:line-length + run: | + set -x + commit_shas=$(gh pr view ${{ env.PR_NUMBER }} --json commits --jq '.commits[] | .oid') + git checkout -b tmp_branch ${{ github.event.pull_request.base.sha }} + + # Cherry-pick the commits into diff branch and amend to add change-Id + for csha in ${commit_shas}; do + git checkout tmp_branch + git cherry-pick "${csha}" + git commit -s -v --no-edit --amend + change_id="$(git log --format="%(trailers:key=Change-Id,valueonly,separator=%x2C)" -n1)" + if [ -n "${change_id}" ]; then + # Capture the newly created change-Id + echo "$change_id" >> change-Id.txt + else + echo "FAIL: Change-Id not created, exit job!" + exit 1 + fi + git checkout "origin/${{ env.GERRIT_BRANCH }}" + done + git log -n3 tmp_branch + env: + GH_TOKEN: ${{ github.token }} + - name: Squash commits into a single commit + if: ${{ (env.PR_COMMITS > 0) && (inputs.SUBMIT_SINGLE_COMMITS == 'false') }} shell: bash - if: ${{ (env.PR_COMMITS > 0) }} # yamllint disable rule:line-length run: | set -x @@ -227,25 +229,25 @@ runs: git log -v --format=%B --reverse "HEAD..HEAD@{1}" | grep -Ev "^(Signed-off-by|Change-Id)" > commit-msg.txt if [[ -f commit-msg.txt ]]; then - commit_message="${commit_message:-commit-msg.txt}" + commit_message="${commit_message:-commit-msg.txt}" fi if [[ -f change-Id.txt ]]; then - commit_message+=' ' - commit_message+="change-Id.txt" + commit_message+=' ' + commit_message+="change-Id.txt" fi if [[ -f signed-off-by.txt ]]; then - sort -u signed-off-by.txt -o signed-off-by-final.txt - commit_message+=' ' - commit_message+="signed-off-by-final.txt" + sort -u signed-off-by.txt -o signed-off-by-final.txt + commit_message+=' ' + commit_message+="signed-off-by-final.txt" fi git commit -s -v --no-edit -m "$(cat $commit_message)" git log -n2 - name: Overwrite commit message with PR tittle and body - if: ${{ (github.event_name == 'pull_request_target') && (env.PR_COMMITS > 0) && (inputs.USE_PR_AS_COMMIT == true) }} + if: ${{ (github.event_name == 'pull_request_target') && (env.PR_COMMITS > 0) && (inputs.USE_PR_AS_COMMIT == 'true') && (inputs.SUBMIT_SINGLE_COMMITS == 'false') }} shell: bash # yamllint disable rule:line-length run: | @@ -279,6 +281,10 @@ runs: run: | set -x + if ${{ inputs.SUBMIT_SINGLE_COMMITS == 'true' }}; then + git checkout tmp_branch + fi + reviewers_emails_list="${{ inputs.REVIEWERS_EMAIL }}" # If the reviewers email is unset/empty then use a default reviewers=${reviewers_emails_list:-"${{ inputs.GERRIT_SSH_USER_G2G_EMAIL }}"} @@ -287,35 +293,68 @@ runs: echo "git review .... inprogress" git review --yes -t "$topic" --reviewers "$reviewers" - # retrive change-id from the submitted PR - gerrit_change_id=$(git show HEAD --format=%B -s | grep Change-Id: | cut -d " " -f2;) - - if [[ "$gerrit_change_id" != '' ]]; then - echo "GERRIT_CHANGE_ID=${gerrit_change_id}" >> "$GITHUB_ENV" - fi - - - name: Retrive the Gerrit change number from Change-ID - if: env.GERRIT_CHANGE_ID != '' + - name: Validate Change-ID and retrive the Gerrit change number id: change_num shell: bash # yamllint disable rule:line-length run: | set -x - # Query for a pre-existing gerrit review to retrive Change-Id - ssh -v -p 29418 "${{ inputs.GERRIT_SSH_USER_G2G }}@${{ env.GERRIT_SERVER }}" \ - gerrit query limit:1 owner:self is:open \ - project:"${{ env.PROJECT_REPO_GERRIT }}" \ - --current-patch-set --format=JSON \ - "${{ env.GERRIT_CHANGE_ID }}" > query_result.txt + if [[ ! -s change-Id.txt ]]; then + # retrive change-id from the submitted PR + gerrit_change_id=$(git show HEAD --format=%B -s | grep Change-Id: | cut -d " " -f2;) + if [[ "$gerrit_change_id" == '' ]]; then + echo "GERRIT_CHANGE_ID: null"; exit 1 + fi + echo "$gerrit_change_id" >> change-Id.txt + fi - query_result_url=$(jq -r '.url | select( . != null )' query_result.txt) - query_result_number=$(jq -r '.number | select( . != null )' query_result.txt) - query_result_commit_sha=$(jq -r '.currentPatchSet.revision | select( . != null )' query_result.txt) + if [[ -s change-Id.txt ]]; then + GERRIT_CHANGE_ID_VALUES="$(> "$GITHUB_ENV" + fi - echo "GERRIT_CHANGE_REQUEST_URL=${query_result_url}" >> "$GITHUB_ENV" - echo "GERRIT_CHANGE_REQUEST_NUMBER=${query_result_number}" >> "$GITHUB_ENV" - echo "GERRIT_COMMIT_SHA=${query_result_commit_sha}" >> "$GITHUB_ENV" + while IFS="" read -r cid; do + # Query for a pre-existing gerrit review to retrive Change-Id + ssh -v -n -p 29418 "${{ inputs.GERRIT_SSH_USER_G2G }}@${{ env.GERRIT_SERVER }}" \ + "gerrit query limit:1 owner:self is:open" \ + "project:${{ env.PROJECT_REPO_GERRIT }}" \ + --current-patch-set --format=JSON \ + "$cid" > query_result.txt + + query_result_url=$(jq -r '.url | select( . != null )' query_result.txt) + query_result_number=$(jq -r '.number | select( . != null )' query_result.txt) + query_result_commit_sha=$(jq -r '.currentPatchSet.revision | select( . != null )' query_result.txt) + + echo "${query_result_url}" >> change-url.txt + echo "${query_result_commit_sha}" >> commit-sha.txt + echo "${query_result_number}" >> change-request-number.txt + done < change-Id.txt + + GERRIT_CHANGE_REQUEST_URL_VALUES="$(> "$GITHUB_ENV" + + GERRIT_COMMIT_SHA_VALUES="$(> "$GITHUB_ENV" + + GERRIT_CHANGE_REQUEST_NUM_VALUES="$(> "$GITHUB_ENV" - name: Add source of Github PR URL as a gerrit comment if: env.GERRIT_CHANGE_ID != '' @@ -328,23 +367,26 @@ runs: pr_path="${{ github.server_url }}/${{ github.repository }}/pull/${{ env.PR_NUMBER }}" message="$(printf 'GHPR: %s\nAction-Run: %s\n' "${pr_path}" "${run_url}")" - # Add a comment on the change was create from Github. - ssh -v -p 29418 "${{ inputs.GERRIT_SSH_USER_G2G }}@${{ env.GERRIT_SERVER }}" \ - gerrit review -m "'""${message}""'" \ - --branch ${{ env.GERRIT_BRANCH }} --project "${{ env.PROJECT_REPO_GERRIT }}" \ - "${{ env.GERRIT_COMMIT_SHA }}" - - - name: PR Comment update CR number - if: env.GERRIT_CHANGE_REQUEST_URL != '' + # Add comment backref change request to Github PR and workflow job. + while IFS="" read -r csha; do + ssh -v -n -p 29418 "${{ inputs.GERRIT_SSH_USER_G2G }}@${{ env.GERRIT_SERVER }}" \ + gerrit review -m "'""${message}""'" \ + --branch ${{ env.GERRIT_BRANCH }} --project "${{ env.PROJECT_REPO_GERRIT }}" \ + "$csha" + done < commit-sha.txt + + - name: Add comment to refrence the change-request on the pull-request + if: | + hashFiles('commit-sha.txt') != '' || + (! startsWith(env.GERRIT_CHANGE_REQUEST_URL, '')) uses: actions/github-script@v7 with: result-encoding: string retries: 3 retry-exempt-status-codes: 400,401 script: | - const output = `Thank you for contributing a pull request! 🙏 \n - The pull-request PR-${{ env.PR_NUMBER }} is submitted to Gerrit [${{ inputs.ORGANIZATION }}](https://${{ env.GERRIT_SERVER }})! \n - To follow up on the change visit: [${{ env.GERRIT_CHANGE_REQUEST_NUMBER }}](${{ env.GERRIT_CHANGE_REQUEST_URL }}) \n \n + const output = `The pull-request PR-${{ env.PR_NUMBER }} is submitted to Gerrit [${{ inputs.ORGANIZATION }}](https://${{ env.GERRIT_SERVER }})! \n + To follow up on the change visit: \n \n ${{ env.GERRIT_CHANGE_REQUEST_URL }} \n \n NOTE: The pull-request PR-${{ env.PR_NUMBER }} will be closed, re-opening the pull-request will not update the same commit and may result in duplicate changes on Gerrit.` github.rest.issues.createComment({ issue_number: context.issue.number,