name: Munge PR

on:
  pull_request_target:

permissions:
  contents: read
  pull-requests: write

defaults:
  run:
    shell: 'bash -Eeuo pipefail -x {0}'

env:
  # https://github.com/docker-library/bashbrew/issues/10
  GIT_LFS_SKIP_SMUDGE: 1

# limit to one run at a time per pull request
# no cancel-in-progress so that the running diff comment can finish before the next since the old diff could be helpful for review
concurrency:
  # this should use "github.ref", but:
  # "For pull requests events except pull_request_target, it is refs/pull/<pr_number>/merge. pull_request_target events have the ref from the base branch." 🙃
  # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#github-context
  #group: ${{ github.workflow }}-${{ github.ref }}
  # https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_target
  group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
  cancel-in-progress: false

jobs:

  gather:
    name: Gather Metadata
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          # ideally this would be "github.event.pull_request.merge_commit_sha" but according to https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls#get-a-pull-request if "mergeable" is null (meaning there's a background job in-progress to check mergeability), that value is undefined...
          ref: ${{ github.event.pull_request.head.sha }}
          fetch-depth: 0
      - id: gather
        name: Affected Images
        run: |
          (set +x; echo "::stop-commands::$(echo -n ${{ github.token }} | sha256sum | head -c 64)")
          git fetch --quiet https://github.com/docker-library/official-images.git master
          externalPins="$(git diff --no-renames --name-only FETCH_HEAD...HEAD -- '.external-pins/*/**')"
          externalPinTags="$(
            if [ -n "$externalPins" ]; then
              # doing backflips to run "tag.sh" from master instead of from the PR
              git show FETCH_HEAD:.external-pins/tag.sh > ~/master-external-pins-tag.sh
              chmod +x ~/master-external-pins-tag.sh
              ~/master-external-pins-tag.sh $externalPins
            fi
          )"
          images="$(git diff --no-renames --name-only FETCH_HEAD...HEAD -- library/)"
          if [ -n "$images" ]; then
            new="$(git diff --no-renames --name-only --diff-filter=A FETCH_HEAD...HEAD -- $images)"
            deleted="$(git diff --no-renames --name-only --diff-filter=D FETCH_HEAD...HEAD -- $images)"
          else
            new=
            deleted=
          fi
          export images new deleted externalPins externalPinTags
          images="$(jq -cn '
            (env.images | rtrimstr("\n") | split("\n")) as $images
            | (env.new | rtrimstr("\n") | split("\n")) as $new
            | (env.deleted | rtrimstr("\n") | split("\n")) as $deleted
            | (env.externalPins | rtrimstr("\n") | split("\n")) as $externalPins
            | (env.externalPinTags | rtrimstr("\n") | split("\n")) as $externalPinTags
            | {
              images: $images,
              count: ($images | length),
              new: $new,
              deleted: $deleted,
              externalPins: $externalPins,
              externalPinTags: $externalPinTags,
              externalPinsCount: ($externalPins | length),
            }
            | .imagesAndExternalPinsCount = (.count + .externalPinsCount) # man, I *really* do not love GitHub Actions expressions...
          ')"
          jq . <<<"$images"
          set +x
          echo "::$(echo -n ${{ github.token }} | sha256sum | head -c 64)::"
          echo "images=$images" >> "$GITHUB_OUTPUT"
    outputs:
      images: '${{ steps.gather.outputs.images }}'

  apply-labels:
    name: Apply Labels
    runs-on: ubuntu-latest
    needs: gather
    permissions:
      contents: read
      pull-requests: write
      # "issues: write" is now needed to create new labels
      # https://github.com/orgs/community/discussions/156181
      # but documentation still shows that pull-requests write should be enough 😓
      # https://docs.github.com/en/rest/issues/labels?apiVersion=2022-11-28#create-a-label
      issues: write
    if: fromJSON(needs.gather.outputs.images).imagesAndExternalPinsCount > 0
    steps:
      - name: Apply Labels
        uses: actions/github-script@v7
        env:
          IMAGES: ${{ needs.gather.outputs.images }}
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const data = JSON.parse(process.env.IMAGES);
            var labels = [
              ...data.images, // "library/debian", ...
              ...new Set(data.externalPinTags.map(x => 'external/' + x.replace(/:.+$/, ''))), // "external/mcr.microsoft.com/windows/servercore", ...
            ];
            if (data.new.length > 0) {
              labels.push('new-image');
            }
            console.log(labels);
            await github.rest.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.payload.pull_request.number,
              labels: labels,
            });

  diff:
    name: Diff Comment
    runs-on: ubuntu-latest
    needs: gather
    if: fromJSON(needs.gather.outputs.images).imagesAndExternalPinsCount > 0
    steps:
      - uses: actions/checkout@v4
        with:
          # again, this would ideally be "github.event.pull_request.merge_commit_sha" but we might not have that yet when this runs, so we compromise by checkout out the latest code from the target branch (so we get the latest "diff-pr.sh" script to run)
          ref: ${{ github.event.pull_request.base.ref }}
          fetch-depth: 0
      - uses: ./.github/workflows/.bashbrew
        with:
          build: 'docker'
      - name: Prepare Environment
        run: |
          # this avoids running repo-local scripts (to avoid CVE-2020-15228 via the scripts being updated to write nasty things to $GITHUB_ENV)
          docker build --tag oisupport/bashbrew:diff-pr .
      - name: Gather Maintainers
        env:
          IMAGES: ${{ needs.gather.outputs.images }}
        run: |
          files="$(jq <<<"$IMAGES" -r '.images | map(@sh) | join(" ")')"
          eval "set -- $files"
          for f; do
            if [ -s "$f" ]; then
              docker run --rm --read-only --tmpfs /tmp oisupport/bashbrew:diff-pr \
                bashbrew cat --format '  - `{{ $.RepoName }}`:{{ range .Manifest.Global.Maintainers }} @{{ .Handle }}{{ end }}' "$f"
            fi
          done | tee "$GITHUB_WORKSPACE/oi-pr.maint"
      - name: Generate Diff
        env:
          GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
        run: |
          docker run --rm --read-only --tmpfs /tmp oisupport/bashbrew:diff-pr ./diff-pr.sh "$GITHUB_PR_NUMBER" | tee "$GITHUB_WORKSPACE/oi-pr.diff"
      - name: Comment
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const commentText = 'Diff for ' + context.payload.pull_request.head.sha + ':';

            const fs = require('fs');
            const diff = fs.readFileSync(process.env.GITHUB_WORKSPACE + '/oi-pr.diff').toString().trimEnd();
            var body = "<details>\n<summary>" + commentText + "</summary>\n\n```diff\n" + diff + "\n```\n\n</details>";
            const maint = fs.readFileSync(process.env.GITHUB_WORKSPACE + '/oi-pr.maint').toString().trimEnd();
            if (maint.length > 0) {
              body += "\n\nRelevant Maintainers:\n\n" + maint;
            }
            fs.writeFileSync(process.env.GITHUB_STEP_SUMMARY, body);

            // https://docs.github.com/en/graphql/reference/mutations#minimizecomment
            const minimizeql = `
              mutation($comment: ID!) {
                minimizeComment(input: { classifier: OUTDATED, clientMutationId: "doi-munge-pr", subjectId: $comment }) {
                  clientMutationId
                  minimizedComment {
                    isMinimized
                    minimizedReason
                  }
                }
              }
            `;
            // https://docs.github.com/en/graphql/reference/mutations#unminimizecomment
            const unminimizeql = `
              mutation($comment: ID!) {
                unminimizeComment(input: { clientMutationId: "doi-munge-pr", subjectId: $comment }) {
                  clientMutationId
                  unminimizedComment {
                    isMinimized
                    minimizedReason
                  }
                }
              }
            `;

            needNewComment = true;
            console.log('Reviewing existing comments...');
            for await (const { data: comments } of github.paginate.iterator(
              github.rest.issues.listComments,
              {
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.payload.pull_request.number,
              }
            )) {
              for (const comment of comments) {
                if (comment.user.login === 'github-actions[bot]') {
                  if (needNewComment && comment.body.includes(commentText)) {
                    needNewComment = false;
                    console.log('Unhiding comment: ' + comment.id + ' (' + comment.node_id + ')');
                    const result = await github.graphql(unminimizeql, { comment: comment.node_id });
                    console.log('- result: ' + JSON.stringify(result));
                  } else {
                    console.log('Hiding comment: ' + comment.id + ' (' + comment.node_id + ')');
                    const result = await github.graphql(minimizeql, { comment: comment.node_id });
                    console.log('- result: ' + JSON.stringify(result));
                  }
                }
              }
            }
            if (needNewComment) {
              console.log('Creating new comment...');
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.payload.pull_request.number,
                body: body,
              });
            }
