mirror of
https://github.com/nix-community/nixvim.git
synced 2025-07-12 02:04:31 +02:00
ci: add tag-maintainers workflow
Used to parse files changed and determine who should be notified of the changes.
This commit is contained in:
parent
a610befe67
commit
860754350d
1 changed files with 233 additions and 0 deletions
233
.github/workflows/tag-maintainers.yml
vendored
Normal file
233
.github/workflows/tag-maintainers.yml
vendored
Normal file
|
@ -0,0 +1,233 @@
|
|||
name: Sync Plugin Maintainer Reviewers
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, ready_for_review, reopened, synchronize]
|
||||
|
||||
# Concurrency settings to ensure that only one instance of this workflow runs per PR.
|
||||
# If a new commit is pushed, it cancels the previous run.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read # To checkout code
|
||||
pull-requests: write # To add/remove reviewers and comment
|
||||
|
||||
jobs:
|
||||
tag-maintainers:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event.pull_request.draft == false &&
|
||||
github.event.pull_request.state == 'open'
|
||||
steps:
|
||||
# Generate a GitHub App token if configured, so we can use custom `bot`.
|
||||
- name: Create GitHub App token
|
||||
uses: actions/create-github-app-token@v2
|
||||
if: vars.CI_APP_ID
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.CI_APP_ID }}
|
||||
private-key: ${{ secrets.CI_APP_PRIVATE_KEY }}
|
||||
permission-contents: write
|
||||
permission-pull-requests: write
|
||||
permission-members: read
|
||||
|
||||
# Checkout the code from the base branch.
|
||||
# This is a security measure for `pull_request_target` to run trusted code.
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
# Install Nix
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
|
||||
# Identify which plugin files have changed in the PR.
|
||||
- name: Get changed plugin files
|
||||
id: changed-files
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
||||
PR_NUM: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
CHANGED_FILES=$(gh pr diff "$PR_NUM" --name-only || true)
|
||||
echo "Changed files:"
|
||||
echo "$CHANGED_FILES"
|
||||
{
|
||||
echo "changed_files<<EOF"
|
||||
echo "$CHANGED_FILES"
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Evaluate Nix code to find maintainers for the changed files.
|
||||
- name: Extract maintainers from changed files
|
||||
id: extract-maintainers
|
||||
env:
|
||||
PR_AUTHOR: "${{ github.event.pull_request.user.login }}"
|
||||
CHANGED_FILES: '${{ steps.changed-files.outputs.changed_files }}'
|
||||
run: |
|
||||
if [[ -z "$CHANGED_FILES" ]]; then
|
||||
echo "No plugin files changed. No maintainers to tag."
|
||||
echo "maintainers=" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Evaluating nixvim meta system for all maintainers..."
|
||||
# Get a JSON object mapping plugin paths to maintainer data.
|
||||
ALL_MAINTAINERS=$(nix eval --impure --expr "
|
||||
let
|
||||
nixvim = import ./.;
|
||||
lib = nixvim.inputs.nixpkgs.lib.extend nixvim.lib.overlay;
|
||||
emptyConfig = lib.nixvim.evalNixvim {
|
||||
modules = [ { _module.check = false; } ];
|
||||
extraSpecialArgs = { pkgs = null; };
|
||||
};
|
||||
inherit (emptyConfig.config.meta) maintainers;
|
||||
in
|
||||
maintainers
|
||||
" --json 2>/dev/null || echo "{}")
|
||||
|
||||
echo "Finding maintainers for changed files..."
|
||||
FOUND_MAINTAINERS=$(
|
||||
echo "$CHANGED_FILES" | while IFS= read -r FILE; do
|
||||
[[ -z "$FILE" ]] && continue
|
||||
PLUGIN_DIR=$(dirname "$FILE")
|
||||
|
||||
# Use jq to find maintainers whose path key ends with the plugin directory.
|
||||
echo "$ALL_MAINTAINERS" | jq -r --arg plugindir "$PLUGIN_DIR" '
|
||||
to_entries[] |
|
||||
select(.key | endswith($plugindir)) |
|
||||
.value[] |
|
||||
select(has("github")) |
|
||||
.github
|
||||
' 2>/dev/null
|
||||
done
|
||||
)
|
||||
|
||||
# De-duplicate the list, remove the PR author, and format as a space-separated string.
|
||||
MAINTAINERS_LIST=$(echo "$FOUND_MAINTAINERS" | grep -v -w -F "$PR_AUTHOR" | sort -u | tr '\n' ' ' | sed 's/ *$//')
|
||||
|
||||
if [[ -z "$MAINTAINERS_LIST" ]]; then
|
||||
echo "No maintainers found for changed files (or only the PR author is a maintainer)."
|
||||
else
|
||||
echo "Found maintainers to notify: $MAINTAINERS_LIST"
|
||||
fi
|
||||
|
||||
echo "maintainers=$MAINTAINERS_LIST" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Get lists of existing reviewers to avoid duplicates.
|
||||
- name: Get current reviewers
|
||||
id: current-reviewers
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
||||
PR_NUM: ${{ github.event.pull_request.number }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
PENDING_REVIEWERS=$(gh pr view "$PR_NUM" --json reviewRequests --jq '.reviewRequests[].login')
|
||||
PAST_REVIEWERS=$(gh api "repos/$REPO/pulls/$PR_NUM/reviews" --jq '.[].user.login')
|
||||
USERS_TO_EXCLUDE=$(printf "%s\n%s" "$PENDING_REVIEWERS" "$PAST_REVIEWERS" | sort -u)
|
||||
|
||||
{
|
||||
echo "pending_reviewers<<EOF"
|
||||
echo "$PENDING_REVIEWERS"
|
||||
echo EOF
|
||||
echo "users_to_exclude<<EOF"
|
||||
echo "$USERS_TO_EXCLUDE"
|
||||
echo EOF
|
||||
} >> $GITHUB_OUTPUT
|
||||
|
||||
echo "Current pending reviewers: $PENDING_REVIEWERS"
|
||||
echo "Complete list of users to exclude: $USERS_TO_EXCLUDE"
|
||||
|
||||
# Filter the maintainer list to only include repository collaborators.
|
||||
# You can only request reviews from users with at least triage permissions.
|
||||
- name: Check maintainer collaborator status
|
||||
id: check-collaborators
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
||||
MAINTAINERS: ${{ steps.extract-maintainers.outputs.maintainers }}
|
||||
USERS_TO_EXCLUDE: ${{ steps.current-reviewers.outputs.users_to_exclude }}
|
||||
REPO: "${{ github.repository }}"
|
||||
run: |
|
||||
NEW_REVIEWERS=()
|
||||
|
||||
# If there are no maintainers, exit early.
|
||||
if [[ -z "$MAINTAINERS" ]]; then
|
||||
echo "No maintainers to check."
|
||||
echo "new_reviewers=" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for MAINTAINER in $MAINTAINERS; do
|
||||
if echo "$USERS_TO_EXCLUDE" | grep -q -w "$MAINTAINER"; then
|
||||
echo "$MAINTAINER is already involved in the review, skipping."
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Checking if $MAINTAINER is a collaborator..."
|
||||
if gh api "/repos/$REPO/collaborators/$MAINTAINER" --silent; then
|
||||
echo "User $MAINTAINER is a collaborator, adding to new reviewers list."
|
||||
NEW_REVIEWERS+=("$MAINTAINER")
|
||||
else
|
||||
echo "User $MAINTAINER is not a repository collaborator, skipping."
|
||||
fi
|
||||
done
|
||||
|
||||
NEW_REVIEWERS_LIST=$(printf "%s " "${NEW_REVIEWERS[@]}")
|
||||
echo "new_reviewers=${NEW_REVIEWERS_LIST% }" >> "$GITHUB_OUTPUT"
|
||||
echo "New reviewers to add: ${NEW_REVIEWERS_LIST% }"
|
||||
|
||||
# Remove reviewers who are no longer maintainers of the changed files.
|
||||
- name: Remove outdated reviewers
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
||||
MAINTAINERS: ${{ steps.extract-maintainers.outputs.maintainers }}
|
||||
PENDING_REVIEWERS: ${{ steps.current-reviewers.outputs.pending_reviewers }}
|
||||
REPO: ${{ github.repository }}
|
||||
PR_NUM: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
remove_reviewers() {
|
||||
local reviewers_to_remove="$1"
|
||||
local reason="$2"
|
||||
for REVIEWER in $reviewers_to_remove; do
|
||||
echo "Removing review request from $REVIEWER ($reason)"
|
||||
REVIEWER_JSON=$(jq -n -c --arg r "$REVIEWER" '{reviewers: [$r]}')
|
||||
gh api --method DELETE "/repos/$REPO/pulls/$PR_NUM/requested_reviewers" \
|
||||
--input - <<< "$REVIEWER_JSON" > /dev/null
|
||||
done
|
||||
}
|
||||
|
||||
# If no maintainers were found for the current set of files, remove all pending reviewers.
|
||||
if [[ -z "$MAINTAINERS" ]]; then
|
||||
if [[ -n "$PENDING_REVIEWERS" ]]; then
|
||||
echo "No plugin maintainers found, removing all pending reviewers."
|
||||
remove_reviewers "$PENDING_REVIEWERS" "no longer a maintainer of changed files"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Find reviewers who are in the pending list but NOT in the current maintainer list.
|
||||
CURRENT_MAINTAINERS=$(echo "$MAINTAINERS" | tr ' ' '\n' | sort -u)
|
||||
OUTDATED_REVIEWERS=$(comm -23 <(echo "$PENDING_REVIEWERS" | tr ' ' '\n' | sort -u) <(echo "$CURRENT_MAINTAINERS"))
|
||||
|
||||
if [[ -n "$OUTDATED_REVIEWERS" ]]; then
|
||||
remove_reviewers "$OUTDATED_REVIEWERS" "no longer a maintainer of changed files"
|
||||
else
|
||||
echo "No outdated reviewers to remove."
|
||||
fi
|
||||
|
||||
# Add the new, filtered list of maintainers as reviewers to the PR.
|
||||
- name: Add new reviewers
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
||||
NEW_REVIEWERS: ${{ steps.check-collaborators.outputs.new_reviewers }}
|
||||
PR_NUM: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
if [[ -n "$NEW_REVIEWERS" ]]; then
|
||||
REVIEWERS_CSV=$(echo "$NEW_REVIEWERS" | tr ' ' ',')
|
||||
echo "Requesting reviews from: $REVIEWERS_CSV"
|
||||
gh pr edit "$PR_NUM" --add-reviewer "$REVIEWERS_CSV"
|
||||
else
|
||||
echo "No new reviewers to add."
|
||||
fi
|
Loading…
Add table
Add a link
Reference in a new issue