name: Update on: # Runs everyday at noon schedule: - cron: "0 12 * * *" # Allow manual triggering workflow_dispatch: inputs: root_lock: type: boolean default: true description: Update root flake.lock dev_lock: type: boolean default: true description: Update dev flake.lock 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: Checkout repository uses: actions/checkout@v4 with: ssh-key: ${{ secrets.CI_UPDATE_SSH_KEY }} - name: Install Nix uses: cachix/install-nix-action@v31 with: nix_path: nixpkgs=channel:nixos-unstable github_access_token: ${{ secrets.GITHUB_TOKEN }} - name: Configure git run: | git config user.name 'github-actions[bot]' git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - 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: ${{ secrets.GITHUB_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 root flake.lock id: root_flake_lock if: inputs.root_lock || github.event_name == 'schedule' run: | old=$(git show --no-patch --format=%h) nix flake update --commit-lock-file new=$(git show --no-patch --format=%h) if [ "$old" != "$new" ]; then echo "body<> "$GITHUB_OUTPUT" git show --no-patch --format=%b >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" fi - name: Update dev flake.lock id: dev_flake_lock if: inputs.dev_lock || github.event_name == 'schedule' run: | root_nixpkgs=$(nix eval --raw --file . 'inputs.nixpkgs.rev') old=$(git show --no-patch --format=%h) nix flake update --commit-lock-file \ --override-input 'dev-nixpkgs' "github:NixOS/nixpkgs/$root_nixpkgs" \ --flake './flake/dev' new=$(git show --no-patch --format=%h) if [ "$old" != "$new" ]; then echo "body<> "$GITHUB_OUTPUT" git show --no-patch --format=%b >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" fi - name: Update generated files id: generate if: inputs.generate || github.event_name == 'schedule' run: | old=$(git show --no-patch --format=%h) nix-build ./update-scripts -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 run: | # The base is the most recent commit on the remote branch by github-actions[bot] # 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" author_rxp='^github-actions\[bot\] <41898282+github-actions\[bot\]@users\.noreply\.github\.com>$' base=$(git rev-list --author="$author_rxp" --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: ${{ secrets.GITHUB_TOKEN }} pr_num: ${{ steps.open_pr_info.outputs.number }} title: | [${{ github.ref_name }}] Update flake.lock & generated files root_lock: ${{ steps.root_flake_lock.outputs.body }} dev_lock: ${{ steps.dev_flake_lock.outputs.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