diff --git a/.config/pmd/ruleset.xml b/.config/pmd/ruleset.xml new file mode 100644 index 00000000..7a03f17c --- /dev/null +++ b/.config/pmd/ruleset.xml @@ -0,0 +1,162 @@ + + + + + This ruleset checks the code for discouraged programming constructs. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/.lycheeignore b/.github/.lycheeignore new file mode 100644 index 00000000..dc88a070 --- /dev/null +++ b/.github/.lycheeignore @@ -0,0 +1,3 @@ +# Ignorefile for broken link check +localhost +mvnrepository.com diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..648aa49c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,68 @@ +name: 🐞 Bug +description: Create a bug report for something that is broken +labels: [bug] +body: + - type: markdown + attributes: + value: | + Thank you for reporting a bug. + + Please fill in as much information as possible about your bug so that we don't have to play "information ping-pong" and can help you immediately. + + - type: checkboxes + id: checklist + attributes: + label: "Checklist" + options: + - label: "I am able to reproduce the bug with the [latest version](https://github.com/xdev-software/micro-migration/releases/latest)" + required: true + - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/micro-migration/issues) or [closed](https://github.com/xdev-software/micro-migration/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." + required: true + - label: "I have taken the time to fill in all the required details. I understand that the bug report will be dismissed otherwise." + required: true + - label: "This issue contains only one bug." + required: true + + - type: input + id: app-version + attributes: + label: Affected version + description: "In which version did you encounter the bug?" + placeholder: "x.x.x" + validations: + required: true + + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to reproduce the bug + description: | + What did you do for the bug to show up? + + If you can't cause the bug to show up again reliably (and hence don't have a proper set of steps to give us), please still try to give as many details as possible on how you think you encountered the bug. + placeholder: | + 1. Use '...' + 2. Do '...' + validations: + required: true + + - type: textarea + id: expected-behavior + attributes: + label: Expected behavior + description: | + Tell us what you expect to happen. + + - type: textarea + id: actual-behavior + attributes: + label: Actual behavior + description: | + Tell us what happens with the steps given above. + + - type: textarea + id: additional-information + attributes: + label: Additional information + description: | + Any other relevant information you'd like to include diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..b6fb79b1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: 💬 Contact support + url: https://xdev.software/en/services/support + about: "If you need support as soon as possible or/and you can't wait for any pull request" diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml new file mode 100644 index 00000000..40eac88d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -0,0 +1,32 @@ +name: ✨ Feature/Enhancement +description: Suggest a new feature or enhancement +labels: [enhancement] +body: + - type: markdown + attributes: + value: | + Thank you for suggesting a new feature/enhancement. + + - type: checkboxes + id: checklist + attributes: + label: "Checklist" + options: + - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/micro-migration/issues) or [closed](https://github.com/xdev-software/micro-migration/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." + required: true + - label: "I have taken the time to fill in all the required details. I understand that the feature request will be dismissed otherwise." + required: true + - label: "This issue contains only one feature request/enhancement." + required: true + + - type: textarea + id: description + attributes: + label: Description + validations: + required: true + + - type: textarea + id: additional-information + attributes: + label: Additional information diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 00000000..a766c855 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,31 @@ +name: ❓ Question +description: Ask a question +labels: [question] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this form! + + - type: checkboxes + id: checklist + attributes: + label: "Checklist" + options: + - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/micro-migration/issues) or [closed](https://github.com/xdev-software/micro-migration/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." + required: true + - label: "I have taken the time to fill in all the required details. I understand that the question will be dismissed otherwise." + required: true + + - type: textarea + id: what-is-the-question + attributes: + label: What is/are your question(s)? + validations: + required: true + + - type: textarea + id: additional-information + attributes: + label: Additional information + description: "Any other information you'd like to include - for instance logs, screenshots, etc." diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 00000000..325f4ce8 --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,38 @@ +# Default +## Required for template +- name: bug + description: "Something isn't working" + color: 'd73a4a' +- name: enhancement + description: New feature or request + color: '#a2eeef' +- name: question + description: Information is requested + color: '#d876e3' +## Others +- name: duplicate + description: This already exists + color: '#cfd3d7' +- name: good first issue + description: Good for newcomers + color: '#7057ff' +- name: help wanted + description: Extra attention is needed + color: '#008672' +- name: invalid + description: "This doesn't seem right" + color: '#e4e669' +# Custom +- name: automated + description: Created by an automation + color: '#000000' +- name: "can't reproduce" + color: '#e95f2c' +- name: customer-requested + description: Was requested by a customer of us + color: '#068374' +- name: stale + color: '#ededed' +- name: waiting-for-response + description: If no response is received after a certain time the issue will be closed + color: '#202020' diff --git a/.github/workflows/broken-links.yml b/.github/workflows/broken-links.yml new file mode 100644 index 00000000..8f98f1ad --- /dev/null +++ b/.github/workflows/broken-links.yml @@ -0,0 +1,46 @@ +name: Broken links + +on: + workflow_dispatch: + schedule: + - cron: "23 23 * * 0" + +permissions: + issues: write + +jobs: + link-checker: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + + - run: mv .github/.lycheeignore .lycheeignore + + - name: Link Checker + id: lychee + uses: lycheeverse/lychee-action@v2 + with: + fail: false # Don't fail on broken links, create an issue instead + + - name: Find already existing issue + id: find-issue + run: | + echo "number=$(gh issue list -l 'bug' -l 'automated' -L 1 -S 'in:title \"Link Checker Report\"' -s 'open' --json 'number' --jq '.[].number')" >> $GITHUB_OUTPUT + env: + GH_TOKEN: ${{ github.token }} + + - name: Close issue if everything is fine + if: env.lychee_exit_code == 0 && steps.find-issue.outputs.number != '' + run: gh issue close -r 'not planned' ${{ steps.find-issue.outputs.number }} + env: + GH_TOKEN: ${{ github.token }} + + - name: Create Issue From File + if: env.lychee_exit_code != 0 + uses: peter-evans/create-issue-from-file@v5 + with: + issue-number: ${{ steps.find-issue.outputs.number }} + title: Link Checker Report + content-filepath: ./lychee/out.md + labels: bug, automated diff --git a/.github/workflows/checkBuild.yml b/.github/workflows/check-build.yml similarity index 69% rename from .github/workflows/checkBuild.yml rename to .github/workflows/check-build.yml index fa9b7f5d..08181168 100644 --- a/.github/workflows/checkBuild.yml +++ b/.github/workflows/check-build.yml @@ -26,6 +26,7 @@ env: jobs: build: runs-on: ubuntu-latest + timeout-minutes: 30 strategy: matrix: @@ -70,9 +71,10 @@ jobs: path: ${{ env.DEMO_MAVEN_MODULE }}/target/${{ env.DEMO_MAVEN_MODULE }}.jar if-no-files-found: error - code-style: + checkstyle: runs-on: ubuntu-latest if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/') }} + timeout-minutes: 15 strategy: matrix: @@ -91,3 +93,41 @@ jobs: - name: Run Checkstyle run: ./mvnw -B checkstyle:check -P checkstyle -T2C + + pmd: + runs-on: ubuntu-latest + if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/') }} + timeout-minutes: 15 + + strategy: + matrix: + java: [17] + distribution: [temurin] + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: ${{ matrix.distribution }} + java-version: ${{ matrix.java }} + cache: 'maven' + + - name: Run PMD + run: ./mvnw -B test pmd:aggregate-pmd-no-fork pmd:check -P pmd -DskipTests -T2C + + - name: Run CPD (Copy Paste Detector) + run: ./mvnw -B pmd:aggregate-cpd pmd:cpd-check -P pmd -DskipTests -T2C + + - name: Upload report + if: always() + uses: actions/upload-artifact@v4 + with: + name: pmd-report + if-no-files-found: ignore + path: | + target/site/*.html + target/site/css/** + target/site/images/logos/maven-feather.png + target/site/images/external.png diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4ad042f4..236c0f59 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,8 +12,9 @@ permissions: pull-requests: write jobs: - check_code: # Validates the code + check-code: runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 @@ -45,9 +46,10 @@ jobs: exit 1 fi - prepare_release: + prepare-release: runs-on: ubuntu-latest - needs: [check_code] + needs: [check-code] + timeout-minutes: 10 outputs: upload_url: ${{ steps.create_release.outputs.upload_url }} steps: @@ -93,8 +95,8 @@ jobs: release_name: v${{ steps.version.outputs.release }} commitish: master body: | - ## [Changelog](https://github.com/xdev-software/${{ env.PRIMARY_MAVEN_MODULE }}/blob/develop/CHANGELOG.md#${{ steps.version.outputs.releasenumber }}) - See [Changelog#v${{ steps.version.outputs.release }}](https://github.com/xdev-software/${{ env.PRIMARY_MAVEN_MODULE }}/blob/develop/CHANGELOG.md#${{ steps.version.outputs.releasenumber }}) for more information. + ## [Changelog](https://github.com/${{ github.repository }}/blob/develop/CHANGELOG.md#${{ steps.version.outputs.releasenumber }}) + See [Changelog#v${{ steps.version.outputs.release }}](https://github.com/${{ github.repository }}/blob/develop/CHANGELOG.md#${{ steps.version.outputs.releasenumber }}) for more information. ## Installation Add the following lines to your pom: @@ -106,9 +108,10 @@ jobs: ``` - publish_central: # Publish the code to central + publish-maven: runs-on: ubuntu-latest - needs: [prepare_release] + needs: [prepare-release] + timeout-minutes: 60 steps: - uses: actions/checkout@v4 @@ -130,7 +133,7 @@ jobs: gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} - name: Publish to Apache Maven Central - run: ../mvnw -B deploy -Possrh + run: ../mvnw -B deploy -Possrh -DskipTests env: MAVEN_CENTRAL_USERNAME: ${{ secrets.S01_OSS_SONATYPE_MAVEN_USERNAME }} MAVEN_CENTRAL_TOKEN: ${{ secrets.S01_OSS_SONATYPE_MAVEN_TOKEN }} @@ -139,7 +142,8 @@ jobs: publish-pages: runs-on: ubuntu-latest - needs: [prepare_release] + needs: [prepare-release] + timeout-minutes: 15 steps: - uses: actions/checkout@v4 @@ -157,7 +161,7 @@ jobs: cache: 'maven' - name: Build site - run: ../mvnw -B site + run: ../mvnw -B compile site -DskipTests -T2C working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} - name: Deploy to Github pages @@ -166,9 +170,10 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./${{ env.PRIMARY_MAVEN_MODULE }}/target/site - after_release: + after-release: runs-on: ubuntu-latest - needs: [publish_central] + needs: [publish-maven] + timeout-minutes: 10 steps: - uses: actions/checkout@v4 @@ -186,7 +191,7 @@ jobs: for i in "${modules[@]}" do echo "Processing $i/pom.xml" - (cd "$i" && $mvnwPath -B build-helper:parse-version versions:set -DnewVersion=\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.nextIncrementalVersion} -DgenerateBackupPoms=false -DnextSnapshot=true) + (cd "$i" && $mvnwPath -B build-helper:parse-version versions:set -DnewVersion=\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.nextIncrementalVersion} -DgenerateBackupPoms=false -DnextSnapshot=true -DupdateMatchingVersions=false) done - name: Git Commit and Push diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 1c9b0f8d..df6dbb7e 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -11,7 +11,7 @@ on: - '.idea/**' - 'assets/**' pull_request: - types: [opened, synchronize, reopened] + branches: [ develop ] paths-ignore: - '**.md' - '.config/**' @@ -24,10 +24,24 @@ env: SONARCLOUD_HOST: https://sonarcloud.io jobs: - sonar: - name: SonarCloud Scan + token-check: + runs-on: ubuntu-latest + if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }} + timeout-minutes: 5 + outputs: + hasToken: ${{ steps.check-token.outputs.has }} + steps: + - id: check-token + run: | + [ -z $SONAR_TOKEN ] && echo "has=false" || echo "has=true" >> "$GITHUB_OUTPUT" + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + sonar-scan: runs-on: ubuntu-latest - if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/') }} + needs: token-check + if: ${{ needs.token-check.outputs.hasToken }} + timeout-minutes: 30 steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml new file mode 100644 index 00000000..c9d7ec78 --- /dev/null +++ b/.github/workflows/sync-labels.yml @@ -0,0 +1,25 @@ +name: Sync labels + +on: + push: + branches: develop + paths: + - .github/labels.yml + + workflow_dispatch: + +permissions: + issues: write + +jobs: + labels: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + with: + sparse-checkout: .github/labels.yml + + - uses: EndBug/label-sync@v2 + with: + config-file: .github/labels.yml diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index cdd96cc1..03f5339e 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -7,8 +7,9 @@ env: PRIMARY_MAVEN_MODULE: ${{ github.event.repository.name }} jobs: - publish_central: # Publish the code to central + publish-maven: runs-on: ubuntu-latest + timeout-minutes: 60 steps: - uses: actions/checkout@v4 @@ -24,7 +25,7 @@ jobs: gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} - name: Publish to OSSRH - run: ../mvnw -B deploy -Possrh + run: ../mvnw -B deploy -Possrh -DskipTests working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} env: MAVEN_CENTRAL_USERNAME: ${{ secrets.S01_OSS_SONATYPE_MAVEN_USERNAME }} diff --git a/.github/workflows/update-from-template.yml b/.github/workflows/update-from-template.yml index a325e2b9..6f9497eb 100644 --- a/.github/workflows/update-from-template.yml +++ b/.github/workflows/update-from-template.yml @@ -1,9 +1,10 @@ name: Update from Template # This workflow keeps the repo up to date with changes from the template repo (REMOTE_URL) -# It duplicates the REMOTE_BRANCH (into UPDATE_BRANCH) and tries to merge it into the +# It duplicates the REMOTE_BRANCH (into UPDATE_BRANCH) and tries to merge it into # this repos default branch (which is checked out here) # Note that this requires a PAT (Personal Access Token) - at best from a servicing account +# PAT permissions: read:discussion, read:org, repo, workflow # Also note that you should have at least once merged the template repo into the current repo manually # otherwise a "refusing to merge unrelated histories" error might occur. @@ -11,9 +12,15 @@ on: schedule: - cron: '55 2 * * 1' workflow_dispatch: + inputs: + no_automatic_merge: + type: boolean + description: 'No automatic merge' + default: false env: UPDATE_BRANCH: update-from-template + UPDATE_BRANCH_MERGED: update-from-template-merged REMOTE_URL: https://github.com/xdev-software/standard-maven-template.git REMOTE_BRANCH: master @@ -24,7 +31,10 @@ permissions: jobs: update: runs-on: ubuntu-latest - + timeout-minutes: 60 + outputs: + update_branch_merged_commit: ${{ steps.manage-branches.outputs.update_branch_merged_commit }} + create_update_branch_merged_pr: ${{ steps.manage-branches.outputs.create_update_branch_merged_pr }} steps: - uses: actions/checkout@v4 with: @@ -36,11 +46,11 @@ jobs: - name: Init Git run: | - git config --global user.email "actions@github.com" - git config --global user.name "GitHub Actions" + git config --global user.email "111048771+xdev-gh-bot@users.noreply.github.com" + git config --global user.name "XDEV Bot" - - name: Main workflow - id: main + - name: Manage branches + id: manage-branches run: | echo "Adding remote template-repo" git remote add template ${{ env.REMOTE_URL }} @@ -48,8 +58,9 @@ jobs: echo "Fetching remote template repo" git fetch template - echo "Deleting local branch that will contain the updates - if present" + echo "Deleting local branches that will contain the updates - if present" git branch -D ${{ env.UPDATE_BRANCH }} || true + git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true echo "Checking if the remote template repo has new commits" git rev-list ..template/${{ env.REMOTE_BRANCH }} @@ -57,10 +68,12 @@ jobs: if [ $(git rev-list --count ..template/${{ env.REMOTE_BRANCH }}) -eq 0 ]; then echo "There are no commits new commits on the template repo" - echo "Deleting origin branch that contains the updates - if present" + echo "Deleting origin branch(es) that contain the updates - if present" git push -f origin --delete ${{ env.UPDATE_BRANCH }} || true + git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true - echo "abort=1" >> $GITHUB_OUTPUT + echo "create_update_branch_pr=0" >> $GITHUB_OUTPUT + echo "create_update_branch_merged_pr=0" >> $GITHUB_OUTPUT exit 0 fi @@ -73,21 +86,235 @@ jobs: echo "Pushing update branch" git push -f -u origin ${{ env.UPDATE_BRANCH }} - echo "Getting current branch" - current_branch=$(git branch --show-current) - echo "Current branch is $current_branch" - echo "current_branch=$current_branch" >> $GITHUB_OUTPUT + echo "Getting base branch" + base_branch=$(git branch --show-current) + echo "Base branch is $base_branch" + echo "base_branch=$base_branch" >> $GITHUB_OUTPUT - echo "abort=0" >> $GITHUB_OUTPUT + echo "Trying to create auto-merged branch ${{ env.UPDATE_BRANCH_MERGED }}" + git branch ${{ env.UPDATE_BRANCH_MERGED }} ${{ env.UPDATE_BRANCH }} + git checkout ${{ env.UPDATE_BRANCH_MERGED }} - - name: pull-request - if: steps.main.outputs.abort == 0 + echo "Merging branch $base_branch into ${{ env.UPDATE_BRANCH_MERGED }}" + git merge $base_branch && merge_exit_code=$? || merge_exit_code=$? + if [ $merge_exit_code -ne 0 ]; then + echo "Auto merge failed! Manual merge required" + echo "::notice ::Auto merge failed - Manual merge required" + + echo "Cleaning up failed merge" + git merge --abort + git checkout $base_branch + git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true + + echo "Deleting auto-merge branch - if present" + git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true + + echo "create_update_branch_pr=1" >> $GITHUB_OUTPUT + echo "create_update_branch_merged_pr=0" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "Post processing: Trying to automatically fill in template variables" + find . -type f \ + -not -path "./.git/**" \ + -not -path "./.github/workflows/update-from-template.yml" -print0 \ + | xargs -0 sed -i "s/template-placeholder/${GITHUB_REPOSITORY#*/}/g" + + git status + git add --all + + if [[ "$(git status --porcelain)" != "" ]]; then + echo "Filled in template; Committing" + + git commit -m "Fill in template" + fi + + echo "Pushing auto-merged branch" + git push -f -u origin ${{ env.UPDATE_BRANCH_MERGED }} + + echo "update_branch_merged_commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + echo "Restoring base branch $base_branch" + git checkout $base_branch + + echo "create_update_branch_pr=0" >> $GITHUB_OUTPUT + echo "create_update_branch_merged_pr=1" >> $GITHUB_OUTPUT + echo "try_close_update_branch_pr=1" >> $GITHUB_OUTPUT + + - name: Create/Update PR update_branch + if: steps.manage-branches.outputs.create_update_branch_pr == 1 env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} run: | gh_pr_up() { gh pr create -H "${{ env.UPDATE_BRANCH }}" "$@" || (git checkout "${{ env.UPDATE_BRANCH }}" && gh pr edit "$@") } - gh_pr_up -B "${{ steps.main.outputs.current_branch }}" \ + gh_pr_up -B "${{ steps.manage-branches.outputs.base_branch }}" \ --title "Update from template" \ --body "An automated PR to sync changes from the template into this repo" + + # Ensure that only a single PR is open (otherwise confusion and spam) + - name: Close PR update_branch + if: steps.manage-branches.outputs.try_close_update_branch_pr == 1 + env: + GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} + run: | + gh pr close "${{ env.UPDATE_BRANCH }}" || true + + - name: Create/Update PR update_branch_merged + if: steps.manage-branches.outputs.create_update_branch_merged_pr == 1 + env: + GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} + run: | + gh_pr_up() { + gh pr create -H "${{ env.UPDATE_BRANCH_MERGED }}" "$@" || (git checkout "${{ env.UPDATE_BRANCH_MERGED }}" && gh pr edit "$@") + } + gh_pr_up -B "${{ steps.manage-branches.outputs.base_branch }}" \ + --title "Update from template (auto-merged)" \ + --body "An automated PR to sync changes from the template into this repo" + + # Wait a moment so that checks of PR have higher prio than following job + sleep 3 + + # Split into two jobs to help with executor starvation + auto-merge: + needs: [update] + if: needs.update.outputs.create_update_branch_merged_pr == 1 + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + with: + # Required because otherwise there are always changes detected when executing diff/rev-list + fetch-depth: 0 + # If no PAT is used the following error occurs on a push: + # refusing to allow a GitHub App to create or update workflow `.github/workflows/xxx.yml` without `workflows` permission + token: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} + + - name: Init Git + run: | + git config --global user.email "111048771+xdev-gh-bot@users.noreply.github.com" + git config --global user.name "XDEV Bot" + + - name: Checking if auto-merge for PR update_branch_merged can be done + id: auto-merge-check + env: + GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} + run: | + not_failed_conclusion="skipped|neutral|success" + not_relevant_app_slug="dependabot|github-pages|sonarcloud" + + echo "Waiting for checks to start..." + sleep 40s + + for i in {1..20}; do + echo "Checking if PR can be auto-merged. Try: $i" + + echo "Checking if update-branch-merged exists" + git fetch + if [[ $(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }}) ]]; then + echo "Branch still exists; Continuing..." + else + echo "Branch origin/${{ env.UPDATE_BRANCH_MERGED }} is missing" + exit 0 + fi + + echo "Fetching checks" + cs_response=$(curl -sL \ + --fail-with-body \ + --connect-timeout 60 \ + --max-time 120 \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ github.repository }}/commits/${{ needs.update.outputs.update_branch_merged_commit }}/check-suites) + + cs_data=$(echo $cs_response | jq '.check_suites[] | { conclusion: .conclusion, slug: .app.slug, check_runs_url: .check_runs_url }') + echo $cs_data + + if [[ -z "$cs_data" ]]; then + echo "No check suite data - Assuming that there are no checks to run" + + echo "perform=1" >> $GITHUB_OUTPUT + exit 0 + fi + + cs_failed=$(echo $cs_data | jq --arg x "$not_failed_conclusion" 'select ((.conclusion == null or (.conclusion | test($x))) | not)') + if [[ -z "$cs_failed" ]]; then + echo "No check failed so far; Checking if relevant checks are still running" + + cs_relevant_still_running=$(echo $cs_data | jq --arg x "$not_relevant_app_slug" 'select (.conclusion == null and (.slug | test($x) | not))') + if [[ -z $cs_relevant_still_running ]]; then + echo "All relevant checks finished - PR can be merged" + + echo "perform=1" >> $GITHUB_OUTPUT + exit 0 + else + echo "Relevant checks are still running" + echo $cs_relevant_still_running + fi + else + echo "Detected failed check" + echo $cs_failed + + echo "perform=0" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "Waiting before next run..." + sleep 30s + done + + echo "Timed out - Assuming executor starvation - Forcing merge" + echo "perform=1" >> $GITHUB_OUTPUT + + - name: Auto-merge update_branch_merged + if: steps.auto-merge-check.outputs.perform == 1 + run: | + echo "Getting base branch" + base_branch=$(git branch --show-current) + echo "Base branch is $base_branch" + + echo "Fetching..." + git fetch + if [[ $(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }}) ]]; then + echo "Branch still exists; Continuing..." + else + echo "Branch origin/${{ env.UPDATE_BRANCH_MERGED }} is missing" + exit 0 + fi + + expected_commit="${{ needs.update.outputs.update_branch_merged_commit }}" + actual_commit=$(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }}) + if [[ "$expected_commit" != "$actual_commit" ]]; then + echo "Branch ${{ env.UPDATE_BRANCH_MERGED }} contains unexpected commit $actual_commit" + echo "Expected: $expected_commit" + + exit 0 + fi + + echo "Ensuring that current branch $base_branch is up-to-date" + git pull + + echo "Merging origin/${{ env.UPDATE_BRANCH_MERGED }} into $base_branch" + git merge origin/${{ env.UPDATE_BRANCH_MERGED }} && merge_exit_code=$? || merge_exit_code=$? + if [ $merge_exit_code -ne 0 ]; then + echo "Unexpected merge failure $merge_exit_code - Requires manual resolution" + + exit 0 + fi + + if [[ "${{ inputs.no_automatic_merge }}" == "true" ]]; then + echo "Exiting due no_automatic_merge" + + exit 0 + fi + + echo "Pushing" + git push + + echo "Cleaning up" + git branch -D ${{ env.UPDATE_BRANCH }} || true + git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true + git push -f origin --delete ${{ env.UPDATE_BRANCH }} || true + git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true diff --git a/.gitignore b/.gitignore index d0e81bf1..116a656e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,11 +39,6 @@ buildNumber.properties # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* - -# bin / compiled stuff -target/ - - # JRebel **/resources/rebel.xml **/resources/rebel-remote.xml diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml index eb3fcd83..b52c3e2f 100644 --- a/.idea/checkstyle-idea.xml +++ b/.idea/checkstyle-idea.xml @@ -1,7 +1,7 @@ - 10.15.0 + 10.21.0 JavaOnlyWithTests true true diff --git a/.idea/saveactions_settings.xml b/.idea/saveactions_settings.xml index 71a42c42..848c311a 100644 --- a/.idea/saveactions_settings.xml +++ b/.idea/saveactions_settings.xml @@ -4,11 +4,11 @@ diff --git a/micro-migration/pom.xml b/micro-migration/pom.xml index 4e62e7a5..2ff62056 100644 --- a/micro-migration/pom.xml +++ b/micro-migration/pom.xml @@ -6,7 +6,7 @@ software.xdev micro-migration - 2.0.1-SNAPSHOT + 3.0.0-SNAPSHOT jar micro-migration @@ -88,25 +88,25 @@ org.eclipse.store storage-embedded - 1.3.2 + 2.1.0 org.eclipse.store storage-embedded-configuration - 1.3.2 + 2.1.0 org.junit.jupiter junit-jupiter - 5.10.2 + 5.11.4 test org.slf4j slf4j-simple - 2.0.13 + 2.0.16 test @@ -117,12 +117,12 @@ org.apache.maven.plugins maven-site-plugin - 4.0.0-M14 + 4.0.0-M16 org.apache.maven.plugins maven-project-info-reports-plugin - 3.5.0 + 3.8.0 @@ -130,7 +130,7 @@ com.mycila license-maven-plugin - 4.5 + 4.6 ${project.organization.url} @@ -170,11 +170,11 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.6.3 + 3.11.2 attach-javadocs - verify + package jar @@ -192,7 +192,7 @@ attach-sources - verify + package jar-no-fork @@ -203,7 +203,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.5 + 3.5.2 @@ -212,10 +212,27 @@ ossrh + + org.codehaus.mojo + flatten-maven-plugin + 1.6.0 + + ossrh + + + + flatten + process-resources + + flatten + + + + org.apache.maven.plugins maven-gpg-plugin - 3.2.4 + 3.2.7 sign-artifacts @@ -238,7 +255,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.13 + 1.7.0 true ossrh @@ -258,12 +275,12 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.3.1 + 3.6.0 com.puppycrawl.tools checkstyle - 10.16.0 + 10.21.1 @@ -281,5 +298,46 @@ + + pmd + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.26.0 + + true + true + + ../.config/pmd/ruleset.xml + + + + + net.sourceforge.pmd + pmd-core + 7.9.0 + + + net.sourceforge.pmd + pmd-java + 7.9.0 + + + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 3.6.0 + + + + diff --git a/micro-migration/src/main/java/software/xdev/micromigration/eclipsestore/TunnelingEmbeddedStorageManager.java b/micro-migration/src/main/java/software/xdev/micromigration/eclipsestore/TunnelingEmbeddedStorageManager.java index 553e43f2..a39fa82b 100644 --- a/micro-migration/src/main/java/software/xdev/micromigration/eclipsestore/TunnelingEmbeddedStorageManager.java +++ b/micro-migration/src/main/java/software/xdev/micromigration/eclipsestore/TunnelingEmbeddedStorageManager.java @@ -17,6 +17,7 @@ import java.nio.ByteBuffer; import java.util.Objects; +import java.util.function.Consumer; import java.util.function.Predicate; import org.eclipse.serializer.afs.types.AFile; @@ -26,7 +27,6 @@ import org.eclipse.serializer.persistence.types.PersistenceRootsView; import org.eclipse.serializer.persistence.types.PersistenceTypeDictionaryExporter; import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; -import org.eclipse.store.storage.exceptions.StorageException; import org.eclipse.store.storage.types.Database; import org.eclipse.store.storage.types.StorageConfiguration; import org.eclipse.store.storage.types.StorageConnection; @@ -159,7 +159,7 @@ public boolean shutdown() * {@link EmbeddedStorageManager#close()} */ @Override - public void close() throws StorageException + public void close() { this.nativeManager.close(); } @@ -416,4 +416,34 @@ public void issueTransactionsLogCleanup() { this.nativeManager.issueTransactionsLogCleanup(); } + + @Override + public int markUsedFor(final Object instance) + { + return this.nativeManager.markUsedFor(instance); + } + + @Override + public int unmarkUsedFor(final Object instance) + { + return this.nativeManager.unmarkUsedFor(instance); + } + + @Override + public boolean isUsed() + { + return this.nativeManager.isUsed(); + } + + @Override + public int markUnused() + { + return this.nativeManager.markUnused(); + } + + @Override + public void accessUsageMarks(final Consumer> logic) + { + this.nativeManager.accessUsageMarks(logic); + } } diff --git a/micro-migration/src/main/java/software/xdev/micromigration/migrater/AbstractMigrater.java b/micro-migration/src/main/java/software/xdev/micromigration/migrater/AbstractMigrater.java index bbf0d714..5077ff67 100644 --- a/micro-migration/src/main/java/software/xdev/micromigration/migrater/AbstractMigrater.java +++ b/micro-migration/src/main/java/software/xdev/micromigration/migrater/AbstractMigrater.java @@ -53,7 +53,6 @@ public void registerNotificationConsumer( final Object root ) { - Objects.requireNonNull(fromVersion); Objects.requireNonNull(storageManager); final TreeSet> sortedScripts = this.getSortedScripts(); @@ -78,7 +77,6 @@ public void registerNotificationConsumer( final Object objectToMigrate ) { - Objects.requireNonNull(fromVersion); Objects.requireNonNull(targetVersion); Objects.requireNonNull(storageManager); @@ -86,7 +84,7 @@ public void registerNotificationConsumer( for(final VersionAgnosticMigrationScript script : this.getSortedScripts()) { final VersionAgnosticMigrationScript castedScript = (VersionAgnosticMigrationScript)script; - if(MigrationVersion.COMPARATOR.compare(fromVersion, script.getTargetVersion()) < 0 + if((fromVersion == null || MigrationVersion.COMPARATOR.compare(fromVersion, script.getTargetVersion()) < 0) && MigrationVersion.COMPARATOR.compare(script.getTargetVersion(), targetVersion) <= 0) { LocalDateTime startDate = null; diff --git a/micro-migration/src/main/java/software/xdev/micromigration/migrater/ExplicitMigrater.java b/micro-migration/src/main/java/software/xdev/micromigration/migrater/ExplicitMigrater.java index 4bf3abdd..29f11705 100644 --- a/micro-migration/src/main/java/software/xdev/micromigration/migrater/ExplicitMigrater.java +++ b/micro-migration/src/main/java/software/xdev/micromigration/migrater/ExplicitMigrater.java @@ -37,8 +37,8 @@ public class ExplicitMigrater extends AbstractMigrater * the migrater. * @throws VersionAlreadyRegisteredException if two scripts have the same version */ + @SuppressWarnings("PMD.UseArraysAsList") public ExplicitMigrater(final VersionAgnosticMigrationScript... scripts) - throws VersionAlreadyRegisteredException { for(final VersionAgnosticMigrationScript script : scripts) { diff --git a/micro-migration/src/main/java/software/xdev/micromigration/migrater/reflection/ReflectiveMigrater.java b/micro-migration/src/main/java/software/xdev/micromigration/migrater/reflection/ReflectiveMigrater.java index b6bec993..49210df2 100644 --- a/micro-migration/src/main/java/software/xdev/micromigration/migrater/reflection/ReflectiveMigrater.java +++ b/micro-migration/src/main/java/software/xdev/micromigration/migrater/reflection/ReflectiveMigrater.java @@ -54,7 +54,7 @@ public class ReflectiveMigrater extends AbstractMigrater * @throws ScriptInstantiationException if a class in the given package could not be instantiated */ @SuppressWarnings("unchecked") - public ReflectiveMigrater(final String packagePath) throws ScriptInstantiationException + public ReflectiveMigrater(final String packagePath) { getClasses(packagePath) .stream() @@ -71,7 +71,6 @@ public ReflectiveMigrater(final String packagePath) throws ScriptInstantiationEx @SuppressWarnings("rawtypes") private VersionAgnosticMigrationScript instantiateClass( final Class scriptClass) - throws ScriptInstantiationException { try { diff --git a/micro-migration/src/main/java/software/xdev/micromigration/version/MigrationVersion.java b/micro-migration/src/main/java/software/xdev/micromigration/version/MigrationVersion.java index e63fc3f5..c5c9bae0 100644 --- a/micro-migration/src/main/java/software/xdev/micromigration/version/MigrationVersion.java +++ b/micro-migration/src/main/java/software/xdev/micromigration/version/MigrationVersion.java @@ -76,7 +76,7 @@ public String toString() final StringBuilder sb = new StringBuilder("v"); for(final int version : this.versions) { - sb.append(version).append("."); + sb.append(version).append('.'); } sb.deleteCharAt(sb.length() - 1); return sb.toString(); diff --git a/micro-migration/src/test/java/software/xdev/micromigration/eclipsestore/integration/MigrationScriptWithNullSourceVersionTest.java b/micro-migration/src/test/java/software/xdev/micromigration/eclipsestore/integration/MigrationScriptWithNullSourceVersionTest.java new file mode 100644 index 00000000..12fbb799 --- /dev/null +++ b/micro-migration/src/test/java/software/xdev/micromigration/eclipsestore/integration/MigrationScriptWithNullSourceVersionTest.java @@ -0,0 +1,95 @@ +/* + * Copyright © 2021 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.micromigration.eclipsestore.integration; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.nio.file.Path; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import software.xdev.micromigration.eclipsestore.MigrationEmbeddedStorageManager; +import software.xdev.micromigration.eclipsestore.MigrationManager; +import software.xdev.micromigration.migrater.ExplicitMigrater; +import software.xdev.micromigration.scripts.SimpleTypedMigrationScript; +import software.xdev.micromigration.scripts.VersionAgnosticMigrationScript; +import software.xdev.micromigration.version.MigrationVersion; +import software.xdev.micromigration.version.Versioned; +import software.xdev.micromigration.version.VersionedObject; + + +class MigrationScriptWithNullSourceVersionTest +{ + public static class EmptyVersionedRoot implements Versioned + { + private MigrationVersion version; + + @Override + public void setVersion(final MigrationVersion version) + { + this.version = version; + } + + @Override + public MigrationVersion getVersion() + { + return this.version; + } + } + + @Test + void updateFromEmptyVersion(@TempDir final Path storageFolder) + { + // First run without any migration script + try(final EmbeddedStorageManager storageManager = this.startEmbeddedStorageManagerWithPath(storageFolder)) + { + final EmptyVersionedRoot firstRoot = new EmptyVersionedRoot(); + storageManager.setRoot(firstRoot); + storageManager.storeRoot(); + assertNull(firstRoot.getVersion()); + } + + // Run with one migration script + final VersionAgnosticMigrationScript, MigrationEmbeddedStorageManager> firstScript = + new SimpleTypedMigrationScript<>( + new MigrationVersion(1), + (context) -> { + } + ); + + try(final EmbeddedStorageManager storageManager = this.startEmbeddedStorageManagerWithPath(storageFolder)) + { + new MigrationManager( + (Versioned)storageManager.root(), + new ExplicitMigrater(firstScript), + storageManager + ) + .migrate(storageManager.root()); + @SuppressWarnings("unchecked") + final EmptyVersionedRoot currentRoot = (EmptyVersionedRoot)storageManager.root(); + assertEquals(new MigrationVersion(1), currentRoot.getVersion()); + } + } + + private EmbeddedStorageManager startEmbeddedStorageManagerWithPath(final Path storageFolder) + { + return EmbeddedStorage.start(storageFolder); + } +} diff --git a/micro-migration/src/test/java/software/xdev/micromigration/migrater/reflection/ReflectiveMigraterTest.java b/micro-migration/src/test/java/software/xdev/micromigration/migrater/reflection/ReflectiveMigraterTest.java index 26bc922b..88ac5384 100644 --- a/micro-migration/src/test/java/software/xdev/micromigration/migrater/reflection/ReflectiveMigraterTest.java +++ b/micro-migration/src/test/java/software/xdev/micromigration/migrater/reflection/ReflectiveMigraterTest.java @@ -26,7 +26,7 @@ class ReflectiveMigraterTest { @Test - void testValidScript() throws ScriptInstantiationException + void testValidScript() { final ReflectiveMigrater migrater = new ReflectiveMigrater("software.xdev.micromigration.migrater.reflection.scripts.valid"); @@ -38,7 +38,7 @@ void testValidScript() throws ScriptInstantiationException } @Test - void testValidScriptWithIrrelevantClasses() throws ScriptInstantiationException + void testValidScriptWithIrrelevantClasses() { final ReflectiveMigrater migrater = new ReflectiveMigrater("software.xdev.micromigration.migrater.reflection.scripts" @@ -51,7 +51,7 @@ void testValidScriptWithIrrelevantClasses() throws ScriptInstantiationException } @Test - void testValidScriptWithSubpackages() throws ScriptInstantiationException + void testValidScriptWithSubpackages() { final ReflectiveMigrater migrater = new ReflectiveMigrater("software.xdev.micromigration.migrater.reflection.scripts.includeSubPackages"); @@ -68,7 +68,7 @@ void testValidScriptWithSubpackages() throws ScriptInstantiationException } @Test - void testPackageWithNoScript() throws ScriptInstantiationException + void testPackageWithNoScript() { final ReflectiveMigrater migrater = new ReflectiveMigrater("software.xdev.micromigration.migrater.reflection.scripts.packageNotExisting"); @@ -76,7 +76,7 @@ void testPackageWithNoScript() throws ScriptInstantiationException } @Test - void testExceptionThrowingScript() throws ScriptInstantiationException + void testExceptionThrowingScript() { Assertions.assertThrows(ScriptInstantiationException.class, () -> { new ReflectiveMigrater("software.xdev.micromigration.migrater.reflection.scripts.exceptionThrowing"); @@ -84,7 +84,7 @@ void testExceptionThrowingScript() throws ScriptInstantiationException } @Test - void testErrorThrowingScript() throws ScriptInstantiationException + void testErrorThrowingScript() { Assertions.assertThrows(ScriptInstantiationException.class, () -> { new ReflectiveMigrater("software.xdev.micromigration.migrater.reflection.scripts.errorThrowing"); @@ -92,7 +92,7 @@ void testErrorThrowingScript() throws ScriptInstantiationException } @Test - void testNoCorrectConstructor() throws ScriptInstantiationException + void testNoCorrectConstructor() { Assertions.assertThrows(ScriptInstantiationException.class, () -> { new ReflectiveMigrater("software.xdev.micromigration.migrater.reflection.scripts.noCorrectConstructor"); @@ -100,7 +100,7 @@ void testNoCorrectConstructor() throws ScriptInstantiationException } @Test - void testAbstractSuperClass() throws ScriptInstantiationException + void testAbstractSuperClass() { final ReflectiveMigrater migrater = new ReflectiveMigrater("software.xdev.micromigration.migrater.reflection.scripts.abstractSuperClass"); @@ -112,7 +112,7 @@ void testAbstractSuperClass() throws ScriptInstantiationException } @Test - void testReflectiveVersion() throws ScriptInstantiationException + void testReflectiveVersion() { final ReflectiveMigrater migrater = new ReflectiveMigrater("software.xdev.micromigration.migrater.reflection.scripts.reflectiveVersion"); @@ -124,7 +124,7 @@ void testReflectiveVersion() throws ScriptInstantiationException } @Test - void testReflectiveSuperClass() throws ScriptInstantiationException + void testReflectiveSuperClass() { final ReflectiveMigrater migrater = new ReflectiveMigrater( diff --git a/pom.xml b/pom.xml index e8fb7049..1fabb654 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ software.xdev micro-migration-root - 2.0.1-SNAPSHOT + 3.0.0-SNAPSHOT pom @@ -19,6 +19,11 @@ micro-migration-demo + + UTF-8 + UTF-8 + + Apache License, Version 2.0 @@ -29,20 +34,75 @@ - checkstyle org.apache.maven.plugins maven-checkstyle-plugin - 3.3.1 + 3.6.0 + + + com.puppycrawl.tools + checkstyle + 10.21.1 + + - true + .config/checkstyle/checkstyle.xml + true + + + + check + + + + + pmd + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.26.0 + + true + true + + .config/pmd/ruleset.xml + + + + + net.sourceforge.pmd + pmd-core + 7.9.0 + + + net.sourceforge.pmd + pmd-java + 7.9.0 + + + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 3.6.0 + + + + diff --git a/renovate.json5 b/renovate.json5 index 380b244b..ce1352cc 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -11,6 +11,24 @@ "maven" ], "groupName": "org.eclipse.store" + }, + { + "description": "Ignore project internal dependencies", + "packagePattern": "^software.xdev:micro-migration", + "datasources": [ + "maven" + ], + "enabled": false + }, + { + "description": "Group net.sourceforge.pmd", + "matchPackagePatterns": [ + "^net.sourceforge.pmd" + ], + "datasources": [ + "maven" + ], + "groupName": "net.sourceforge.pmd" } ] }