From 9ac94ce3e64b25441e66d7c610b3ea59853c1872 Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Thu, 22 Jan 2026 01:13:10 +0100 Subject: [PATCH] extension_rollout: Add support for renaming and deleting files (#47329) This is in preparation for removing one of the files in favor of having just one larger one (and perhaps renaming that in the future). Release Notes: - N/A --- .../workflows/extension_workflow_rollout.yml | 90 ++++++++++- .../src/tasks/workflows/extension_bump.rs | 22 ++- .../workflows/extension_workflow_rollout.rs | 140 ++++++++++++++++-- 3 files changed, 235 insertions(+), 17 deletions(-) diff --git a/.github/workflows/extension_workflow_rollout.yml b/.github/workflows/extension_workflow_rollout.yml index 10890fe5f2feadda521c5756a37a5c4c12e017b6..502ec1945fc0b03d77dd1930bdfd439511d39be4 100644 --- a/.github/workflows/extension_workflow_rollout.yml +++ b/.github/workflows/extension_workflow_rollout.yml @@ -57,6 +57,7 @@ jobs: with: clean: false path: zed + fetch-depth: '0' - name: steps::checkout_repo_with_token uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: @@ -64,9 +65,61 @@ jobs: token: ${{ steps.generate-token.outputs.token }} repository: zed-extensions/${{ matrix.repo }} path: extension - - name: extension_workflow_rollout::rollout_workflows_to_extension::copy_workflow_files + - id: prev-tag + name: extension_workflow_rollout::rollout_workflows_to_extension::get_previous_tag_commit run: | + PREV_COMMIT=$(git rev-parse "extension-workflows^{commit}" 2>/dev/null || echo "") + if [ -z "$PREV_COMMIT" ]; then + echo "::error::No previous rollout tag 'extension-workflows' found. Cannot determine file changes." + exit 1 + fi + echo "Found previous rollout at commit: $PREV_COMMIT" + echo "prev_commit=$PREV_COMMIT" >> "$GITHUB_OUTPUT" + shell: bash -euxo pipefail {0} + working-directory: zed + - id: calc-changes + name: extension_workflow_rollout::rollout_workflows_to_extension::get_removed_files + run: | + PREV_COMMIT="${{ steps.prev-tag.outputs.prev_commit }}" + + if [ "${{ matrix.repo }}" = "workflows" ]; then + WORKFLOW_DIR="extensions/workflows" + else + WORKFLOW_DIR="extensions/workflows/shared" + fi + + echo "Calculating changes from $PREV_COMMIT to HEAD for $WORKFLOW_DIR" + + # Get deleted files (status D) and renamed files (status R - old name needs removal) + # Using -M to detect renames, then extracting files that are gone from their original location + REMOVED_FILES=$(git diff --name-status -M "$PREV_COMMIT" HEAD -- "$WORKFLOW_DIR" | \ + awk '/^D/ { print $2 } /^R/ { print $2 }' | \ + xargs -I{} basename {} 2>/dev/null | \ + tr '\n' ' ' || echo "") + + REMOVED_FILES=$(echo "$REMOVED_FILES" | xargs) + + echo "Files to remove: $REMOVED_FILES" + echo "removed_files=$REMOVED_FILES" >> "$GITHUB_OUTPUT" + shell: bash -euxo pipefail {0} + working-directory: zed + - name: extension_workflow_rollout::rollout_workflows_to_extension::sync_workflow_files + run: | + REMOVED_FILES="${{ steps.calc-changes.outputs.removed_files }}" + mkdir -p extension/.github/workflows + cd extension/.github/workflows + + if [ -n "$REMOVED_FILES" ]; then + for file in $REMOVED_FILES; do + if [ -f "$file" ]; then + rm -f "$file" + fi + done + fi + + cd - > /dev/null + if [ "${{ matrix.repo }}" = "workflows" ]; then cp zed/extensions/workflows/*.yml extension/.github/workflows/ else @@ -107,3 +160,38 @@ jobs: env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} timeout-minutes: 10 + create_rollout_tag: + needs: + - rollout_workflows_to_extension + runs-on: namespace-profile-2x4-ubuntu-2404 + steps: + - id: generate-token + name: extension_bump::generate_token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.ZED_ZIPPY_APP_ID }} + private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }} + permission-contents: write + - name: steps::checkout_repo_with_token + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + clean: false + token: ${{ steps.generate-token.outputs.token }} + fetch-depth: '0' + - name: extension_workflow_rollout::create_rollout_tag::configure_git + run: | + git config user.name "zed-zippy[bot]" + git config user.email "234243425+zed-zippy[bot]@users.noreply.github.com" + shell: bash -euxo pipefail {0} + - name: extension_workflow_rollout::create_rollout_tag::update_rollout_tag + run: | + if git rev-parse "extension-workflows" >/dev/null 2>&1; then + git tag -d "extension-workflows" + git push origin ":refs/tags/extension-workflows" || true + fi + + echo "Creating new tag 'extension-workflows' at $(git rev-parse --short HEAD)" + git tag "extension-workflows" + git push origin "extension-workflows" + shell: bash -euxo pipefail {0} + timeout-minutes: 1 diff --git a/tooling/xtask/src/tasks/workflows/extension_bump.rs b/tooling/xtask/src/tasks/workflows/extension_bump.rs index f3f886d082bee23f295b20ad6459eaff77bb9bb7..fd95370daf364fb1166b6de30adb51cadee45b81 100644 --- a/tooling/xtask/src/tasks/workflows/extension_bump.rs +++ b/tooling/xtask/src/tasks/workflows/extension_bump.rs @@ -223,8 +223,10 @@ pub(crate) fn generate_token( permissions, }| { input - .add("owner", owner) - .add("repositories", repositories) + .when_some(owner, |input, owner| input.add("owner", owner)) + .when_some(repositories, |input, repositories| { + input.add("repositories", repositories) + }) .when_some(permissions, |input, permissions| { permissions .into_iter() @@ -315,16 +317,24 @@ fn create_pull_request(new_version: StepOutput, generated_token: StepOutput) -> } pub(crate) struct RepositoryTarget { - owner: String, - repositories: String, + owner: Option, + repositories: Option, permissions: Option>, } impl RepositoryTarget { pub fn new(owner: T, repositories: &[&str]) -> Self { Self { - owner: owner.to_string(), - repositories: repositories.join("\n"), + owner: Some(owner.to_string()), + repositories: Some(repositories.join("\n")), + permissions: None, + } + } + + pub fn current() -> Self { + Self { + owner: None, + repositories: None, permissions: None, } } diff --git a/tooling/xtask/src/tasks/workflows/extension_workflow_rollout.rs b/tooling/xtask/src/tasks/workflows/extension_workflow_rollout.rs index d0ff0fb1b0096af407dc0f09a087e43a867058ed..0498aa4aeca84ff73fe8f4926fb1abcbfc7561e9 100644 --- a/tooling/xtask/src/tasks/workflows/extension_workflow_rollout.rs +++ b/tooling/xtask/src/tasks/workflows/extension_workflow_rollout.rs @@ -1,6 +1,7 @@ use gh_workflow::{ Event, Expression, Job, Level, Run, Step, Strategy, Use, Workflow, WorkflowDispatch, }; +use indoc::formatdoc; use indoc::indoc; use serde_json::json; @@ -11,15 +12,19 @@ use crate::tasks::workflows::{ vars::{self, StepOutput}, }; +const ROLLOUT_TAG_NAME: &str = "extension-workflows"; + pub(crate) fn extension_workflow_rollout() -> Workflow { let fetch_repos = fetch_extension_repos(); let rollout_workflows = rollout_workflows_to_extension(&fetch_repos); + let create_tag = create_rollout_tag(&rollout_workflows); named::workflow() .on(Event::default().workflow_dispatch(WorkflowDispatch::default())) .add_env(("CARGO_TERM_COLOR", "always")) .add_job(fetch_repos.name, fetch_repos.job) .add_job(rollout_workflows.name, rollout_workflows.job) + .add_job(create_tag.name, create_tag.job) } fn fetch_extension_repos() -> NamedJob { @@ -28,7 +33,7 @@ fn fetch_extension_repos() -> NamedJob { .id("list-repos") .add_with(( "script", - indoc! {r#" + indoc::indoc! {r#" const repos = await github.paginate(github.rest.repos.listForOrg, { org: 'zed-extensions', type: 'public', @@ -66,6 +71,7 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob { steps::checkout_repo() .name("checkout_zed_repo") .add_with(("path", "zed")) + .add_with(("fetch-depth", "0")) } fn checkout_extension_repo(token: &StepOutput) -> Step { @@ -74,10 +80,74 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob { .add_with(("path", "extension")) } - fn copy_workflow_files() -> Step { - named::bash(indoc! {r#" + fn get_previous_tag_commit() -> (Step, StepOutput) { + let step = named::bash(formatdoc! {r#" + PREV_COMMIT=$(git rev-parse "{ROLLOUT_TAG_NAME}^{{commit}}" 2>/dev/null || echo "") + if [ -z "$PREV_COMMIT" ]; then + echo "::error::No previous rollout tag '{ROLLOUT_TAG_NAME}' found. Cannot determine file changes." + exit 1 + fi + echo "Found previous rollout at commit: $PREV_COMMIT" + echo "prev_commit=$PREV_COMMIT" >> "$GITHUB_OUTPUT" + "#}) + .id("prev-tag") + .working_directory("zed"); + + let step_output = StepOutput::new(&step, "prev_commit"); + + (step, step_output) + } + + fn get_removed_files(prev_commit: &StepOutput) -> (Step, StepOutput) { + let step = named::bash(formatdoc! {r#" + PREV_COMMIT="{prev_commit}" + + if [ "${{{{ matrix.repo }}}}" = "workflows" ]; then + WORKFLOW_DIR="extensions/workflows" + else + WORKFLOW_DIR="extensions/workflows/shared" + fi + + echo "Calculating changes from $PREV_COMMIT to HEAD for $WORKFLOW_DIR" + + # Get deleted files (status D) and renamed files (status R - old name needs removal) + # Using -M to detect renames, then extracting files that are gone from their original location + REMOVED_FILES=$(git diff --name-status -M "$PREV_COMMIT" HEAD -- "$WORKFLOW_DIR" | \ + awk '/^D/ {{ print $2 }} /^R/ {{ print $2 }}' | \ + xargs -I{{}} basename {{}} 2>/dev/null | \ + tr '\n' ' ' || echo "") + + REMOVED_FILES=$(echo "$REMOVED_FILES" | xargs) + + echo "Files to remove: $REMOVED_FILES" + echo "removed_files=$REMOVED_FILES" >> "$GITHUB_OUTPUT" + "#}) + .id("calc-changes") + .working_directory("zed"); + + let removed_files = StepOutput::new(&step, "removed_files"); + + (step, removed_files) + } + + fn sync_workflow_files(removed_files: &StepOutput) -> Step { + named::bash(formatdoc! {r#" + REMOVED_FILES="{removed_files}" + mkdir -p extension/.github/workflows - if [ "${{ matrix.repo }}" = "workflows" ]; then + cd extension/.github/workflows + + if [ -n "$REMOVED_FILES" ]; then + for file in $REMOVED_FILES; do + if [ -f "$file" ]; then + rm -f "$file" + fi + done + fi + + cd - > /dev/null + + if [ "${{{{ matrix.repo }}}}" = "workflows" ]; then cp zed/extensions/workflows/*.yml extension/.github/workflows/ else cp zed/extensions/workflows/shared/*.yml extension/.github/workflows/ @@ -86,7 +156,7 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob { } fn get_short_sha() -> (Step, StepOutput) { - let step = named::bash(indoc! {r#" + let step = named::bash(indoc::indoc! {r#" echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" "#}) .id("short-sha") @@ -97,7 +167,7 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob { (step, step_output) } - fn create_pull_request(token: &StepOutput, short_sha: StepOutput) -> Step { + fn create_pull_request(token: &StepOutput, short_sha: &StepOutput) -> Step { let title = format!("Update CI workflows to `zed@{}`", short_sha); named::uses("peter-evans", "create-pull-request", "v7") @@ -105,7 +175,7 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob { .add_with(("title", title.clone())) .add_with(( "body", - indoc! {r#" + indoc::indoc! {r#" This PR updates the CI workflow files from the main Zed repository based on the commit zed-industries/zed@${{ github.sha }} "#}, @@ -128,7 +198,7 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob { } fn enable_auto_merge(token: &StepOutput) -> Step { - named::bash(indoc! {r#" + named::bash(indoc::indoc! {r#" PR_NUMBER="${{ steps.create-pr.outputs.pull-request-number }}" if [ -n "$PR_NUMBER" ]; then cd extension @@ -149,6 +219,8 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob { ]), ), ); + let (get_prev_tag, prev_commit) = get_previous_tag_commit(); + let (calc_changes, removed_files) = get_removed_files(&prev_commit); let (calculate_short_sha, short_sha) = get_short_sha(); let job = Job::default() @@ -170,10 +242,58 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob { .add_step(authenticate) .add_step(checkout_zed_repo()) .add_step(checkout_extension_repo(&token)) - .add_step(copy_workflow_files()) + .add_step(get_prev_tag) + .add_step(calc_changes) + .add_step(sync_workflow_files(&removed_files)) .add_step(calculate_short_sha) - .add_step(create_pull_request(&token, short_sha)) + .add_step(create_pull_request(&token, &short_sha)) .add_step(enable_auto_merge(&token)); named::job(job) } + +fn create_rollout_tag(rollout_job: &NamedJob) -> NamedJob { + fn checkout_zed_repo(token: &StepOutput) -> Step { + steps::checkout_repo_with_token(token).add_with(("fetch-depth", "0")) + } + + fn update_rollout_tag() -> Step { + named::bash(formatdoc! {r#" + if git rev-parse "{ROLLOUT_TAG_NAME}" >/dev/null 2>&1; then + git tag -d "{ROLLOUT_TAG_NAME}" + git push origin ":refs/tags/{ROLLOUT_TAG_NAME}" || true + fi + + echo "Creating new tag '{ROLLOUT_TAG_NAME}' at $(git rev-parse --short HEAD)" + git tag "{ROLLOUT_TAG_NAME}" + git push origin "{ROLLOUT_TAG_NAME}" + "#}) + } + + fn configure_git() -> Step { + named::bash(indoc! {r#" + git config user.name "zed-zippy[bot]" + git config user.email "234243425+zed-zippy[bot]@users.noreply.github.com" + "#}) + } + + let (authenticate, token) = generate_token( + vars::ZED_ZIPPY_APP_ID, + vars::ZED_ZIPPY_APP_PRIVATE_KEY, + Some( + RepositoryTarget::current() + .permissions([("permission-contents".to_owned(), Level::Write)]), + ), + ); + + let job = Job::default() + .needs([rollout_job.name.clone()]) + .runs_on(runners::LINUX_SMALL) + .timeout_minutes(1u32) + .add_step(authenticate) + .add_step(checkout_zed_repo(&token)) + .add_step(configure_git()) + .add_step(update_rollout_tag()); + + named::job(job) +}