name: Update on: # Runs everyday at noon schedule: - cron: "0 12 * * *" # Allow manual triggering workflow_dispatch: inputs: update_lock: type: boolean default: true description: Update flake.lock files generate: type: boolean default: true description: Update generated files re_apply: type: boolean default: true description: Re-apply additional commits from the PR # Allow one concurrent update per branch concurrency: group: "update-${{ github.ref_name }}" cancel-in-progress: true # Allow pushing and creating PRs permissions: contents: write pull-requests: write jobs: update: name: Update the flake inputs and generate options runs-on: ubuntu-latest timeout-minutes: 40 if: github.event_name != 'schedule' || github.repository == 'nix-community/nixvim' env: repo: ${{ github.repository }} base_branch: ${{ github.ref_name }} pr_branch: update/${{ github.ref_name }} workflow_run_id: ${{ github.run_id }} workflow_run_url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} steps: - name: Create GitHub App token uses: actions/create-github-app-token@v2 id: app-token with: app-id: ${{ vars.CI_APP_ID }} private-key: ${{ secrets.CI_APP_PRIVATE_KEY }} - name: Get GitHub App user info id: user-info env: GH_TOKEN: ${{ steps.app-token.outputs.token }} slug: ${{ steps.app-token.outputs.app-slug }} run: | name="$slug[bot]" name_regex="$slug"'\[bot\]' id=$(gh api "/users/$name" --jq .id) { echo "id=$id" echo "name=$name" echo "email=$id+$name@users.noreply.github.com" echo 'author-regex=^'"$name_regex"' <'"$id+$name_regex"'@users\.noreply\.github\.com>$' } >> "$GITHUB_OUTPUT" - name: Configure git env: name: ${{ steps.user-info.outputs.name }} email: ${{ steps.user-info.outputs.email }} run: | git config --global user.name "$name" git config --global user.email "$email" - name: Checkout repository uses: actions/checkout@v4 with: token: ${{ steps.app-token.outputs.token }} - name: Install Nix uses: cachix/install-nix-action@v31 with: nix_path: nixpkgs=channel:nixos-unstable github_access_token: ${{ steps.app-token.outputs.token }} - name: Create update branch run: | git branch -D "$pr_branch" || echo "Nothing to delete" git switch -c "$pr_branch" - name: Get info on the current PR id: open_pr_info env: GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | # Query for info about the already open update PR info=$( gh api graphql -F owner='{owner}' -F repo='{repo}' -F branch="$pr_branch" -f query=' query($owner:String!, $repo:String!, $branch:String!) { repository(owner: $owner, name: $repo) { pullRequests(first: 1, states: OPEN, headRefName: $branch) { nodes { number url } } } } ' | jq --raw-output ' .data.repository.pullRequests.nodes[] | to_entries[] | "\(.key)=\(.value)" ' ) if [[ -n "$info" ]]; then echo "PR info:" echo "$info" echo "$info" >> $GITHUB_OUTPUT else echo "No PR is currently open" fi - name: Fetch current PR's branch if: steps.open_pr_info.outputs.number run: | git fetch origin "$pr_branch" git branch --set-upstream-to "origin/$pr_branch" - name: Update flake.lock files id: update_flake_lock if: inputs.update_lock || github.event_name == 'schedule' run: | nix-build ./ci -A update ./result/bin/update --commit --github-output - name: Update generated files id: generate if: inputs.generate || github.event_name == 'schedule' run: | old=$(git show --no-patch --format=%h) nix-build ./ci -A generate ./result/bin/generate --commit new=$(git show --no-patch --format=%h) if [ "$old" != "$new" ]; then body=$(git show --no-patch --format=%b) echo "body<> "$GITHUB_OUTPUT" if [ -n "$body" ]; then # Multi-file changes are listed in the body echo "$body" >> "$GITHUB_OUTPUT" else # Single-file changes are only in the summary, # e.g. "generated: Updated none-ls.nix" git show --no-patch --format=%s | \ sed -e 's/^generated:/-/' >> "$GITHUB_OUTPUT" fi echo "EOF" >> "$GITHUB_OUTPUT" fi - name: Apply commits from the open PR id: re_apply if: (inputs.re_apply || github.event_name == 'schedule') && steps.open_pr_info.outputs.number env: author_regex: ${{ steps.user-info.outputs.author-regex }} run: | # The base is the most recent commit on the remote branch authored by nixvim-ci # This should be a flake.lock bump or a generated-files update # We will cherry-pick all commits on the remote _after_ the $base commit remote="origin/$pr_branch" base=$(git rev-list --author="$author_regex" --max-count=1 "$remote") commits=( $(git rev-list --reverse "$base..$remote") ) if [[ -n "$commits" ]]; then echo "Applying ${#commits[@]} commits..." echo "count=${#commits[@]}" >> $GITHUB_OUTPUT git cherry-pick \ --strategy-option=theirs \ --empty=drop \ "${commits[@]}" else echo "Nothing to re-apply" fi - name: Check if there are differences to push id: diff env: pr_num: ${{ steps.open_pr_info.outputs.number }} run: | if [[ -n "$pr_num" ]]; then remote="origin/$pr_branch" else remote="origin/$base_branch" fi diff=( $(git diff --cached --name-only "$remote") ) if [[ -n "$diff" ]]; then echo "${#diff[@]} files different to $remote" for file in "${diff[@]}"; do echo "- $file" done echo "count=${#diff[@]}" >> $GITHUB_OUTPUT else echo "No files are different to $remote" fi - name: Create or Update Pull Request id: updated_pr if: steps.diff.outputs.count env: GH_TOKEN: ${{ steps.app-token.outputs.token }} pr_num: ${{ steps.open_pr_info.outputs.number }} title: | [${{ github.ref_name }}] Update flake.lock & generated files root_lock: ${{ steps.update_flake_lock.outputs.root_lock_body }} dev_lock: ${{ steps.update_flake_lock.outputs.dev_lock_body }} generated: ${{ steps.generate.outputs.body }} run: | echo "Pushing to remote branch $pr_branch" git push --force --set-upstream origin "$pr_branch" echo "Writing PR body file" ( if [[ -z "$root_lock$dev_lock$generated" ]]; then echo '## No changes' echo fi if [[ -n "$root_lock" ]]; then echo '## Root lockfile' echo '```' echo "$root_lock" echo '```' echo fi if [[ -n "$dev_lock" ]]; then echo '## Dev lockfile' echo '```' echo "$dev_lock" echo '```' echo fi if [[ -n "$generated" ]]; then echo '## Generated files' echo "$generated" echo fi run_cmd='gh workflow run update.yml' if [[ "$base_branch" != "main" ]]; then run_cmd+=" --ref $base_branch" fi echo '---' echo echo -n 'This PR was most recently updated by workflow run ' echo "[$workflow_run_id]($workflow_run_url)." echo echo -n 'You can re-run the update by going to the ' echo -n '[workflow'"'"'s page](https://github.com/nix-community/nixvim/actions/workflows/update.yml) ' echo 'or by using the `gh` command:' echo '```sh' echo "$run_cmd" echo '```' echo echo -n 'If needed, you can also specify workflow inputs on the command line, ' echo 'using the `-F --field`, `-f --raw-field`, or `--json` flags.' echo 'See `gh workflow run --help`.' echo ) > body.md if [[ -n "$pr_num" ]]; then echo "Editing existing PR #$pr_num" operation=updated gh pr edit "$pr_num" --body-file body.md else echo "Creating new PR" operation=created gh pr create \ --base "$base_branch" \ --title "$title" \ --body-file body.md fi pr_info=$( # Get info from `gh pr view` gh pr view --json 'headRefName,number,url' --jq ' to_entries[] | .key |= # Rename headRefName -> branch if . == "headRefName" then "branch" else . end | "\(.key)=\(.value)" ' # Get additional info locally echo "head=$(git rev-parse HEAD)" echo "operation=$operation" ) echo "PR Info:" echo "$pr_info" echo "$pr_info" >> $GITHUB_OUTPUT - name: Print summary if: steps.updated_pr.outputs.number env: pr_num: ${{ steps.updated_pr.outputs.number }} pr_url: ${{ steps.updated_pr.outputs.url }} pr_branch: ${{ steps.updated_pr.outputs.branch }} head: ${{ steps.updated_pr.outputs.head }} operation: ${{ steps.updated_pr.outputs.operation }} re_apply_count: ${{ steps.re_apply.outputs.count }} run: | short=${head:0:6} # stdout echo "${short} pushed to ${pr_branch}" echo "#${pr_num} was ${operation}: ${pr_url}" ( # markdown summary echo "## ${{ github.ref_name }}" echo echo "\`${short}\` pushed to \`${pr_branch}\`" echo echo "[#${pr_num}](${pr_url}) was ${operation}." echo if [[ -n "$re_apply_count" ]]; then echo "Re-applied $re_apply_count commits from the existing PR." fi echo ) >> $GITHUB_STEP_SUMMARY - name: Print cancellation summary if: (!steps.updated_pr.outputs.number) env: pr_num: ${{ steps.open_pr_info.outputs.number }} pr_url: ${{ steps.open_pr_info.outputs.url }} changes: ${{ steps.diff.outputs.count || '0' }} re_apply_count: ${{ steps.re_apply.outputs.count }} run: | ( echo "## Not updated" echo echo -n "$changes files with differences compared to " if [[ -n "$pr_num" ]]; then echo "[#$pr_num]($pr_url)." else echo "\`$base_branch\`" fi echo if [[ -n "$re_apply_count" ]]; then echo "Re-applied $re_apply_count commits from the existing PR." fi echo ) >> $GITHUB_STEP_SUMMARY