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
This commit is contained in:
Finn Evers 2026-01-22 01:13:10 +01:00 committed by GitHub
parent d42fe0bba4
commit 9ac94ce3e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 235 additions and 17 deletions

View file

@ -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

View file

@ -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<String>,
repositories: Option<String>,
permissions: Option<Vec<(String, Level)>>,
}
impl RepositoryTarget {
pub fn new<T: ToString>(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,
}
}

View file

@ -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<Use> {
@ -74,10 +80,74 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob {
.add_with(("path", "extension"))
}
fn copy_workflow_files() -> Step<Run> {
named::bash(indoc! {r#"
fn get_previous_tag_commit() -> (Step<Run>, 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<Run>, 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<Run> {
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<Run>, 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<Use> {
fn create_pull_request(token: &StepOutput, short_sha: &StepOutput) -> Step<Use> {
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<gh_workflow::Run> {
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<Use> {
steps::checkout_repo_with_token(token).add_with(("fetch-depth", "0"))
}
fn update_rollout_tag() -> Step<Run> {
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<Run> {
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)
}