fix(ci): workflow reliability and architecture improvements
- Reconstruct e2e-tests-split.yml to match spec (15 jobs, security isolation) - Update docker-build.yml to authenticate Docker Hub for PRs (fixes 401) - Refactor propagate-changes.yml to enforce strict hierarchy (Pittsburgh model) - Implement API-based loop prevention to stop rebase loops Ref: #660
This commit is contained in:
47
.github/workflows/propagate-changes.yml
vendored
47
.github/workflows/propagate-changes.yml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- development
|
||||
- 'feature/**'
|
||||
- 'hotfix/**'
|
||||
|
||||
concurrency:
|
||||
@@ -35,6 +36,25 @@ jobs:
|
||||
with:
|
||||
script: |
|
||||
const currentBranch = context.ref.replace('refs/heads/', '');
|
||||
let excludedBranch = null;
|
||||
|
||||
// Loop Prevention: Identify if this commit is from a merged PR
|
||||
try {
|
||||
const associatedPRs = await github.rest.repos.listPullRequestsAssociatedWithCommit({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
commit_sha: context.sha,
|
||||
});
|
||||
|
||||
// If the commit comes from a PR, we identify the source branch
|
||||
// so we don't try to merge changes back into it immediately.
|
||||
if (associatedPRs.data.length > 0) {
|
||||
excludedBranch = associatedPRs.data[0].head.ref;
|
||||
core.info(`Commit ${context.sha} is associated with PR #${associatedPRs.data[0].number} coming from '${excludedBranch}'. This branch will be excluded from propagation to prevent loops.`);
|
||||
}
|
||||
} catch (err) {
|
||||
core.warning(`Failed to check associated PRs: ${err.message}`);
|
||||
}
|
||||
|
||||
async function createPR(src, base) {
|
||||
if (src === base) return;
|
||||
@@ -148,22 +168,35 @@ jobs:
|
||||
|
||||
if (currentBranch === 'main') {
|
||||
// Main -> Development
|
||||
await createPR('main', 'development');
|
||||
// Only propagate if development is not the source (loop prevention)
|
||||
if (excludedBranch !== 'development') {
|
||||
await createPR('main', 'development');
|
||||
} else {
|
||||
core.info('Push originated from development (excluded). Skipping propagation back to development.');
|
||||
}
|
||||
} else if (currentBranch === 'development') {
|
||||
// Development -> Feature branches (direct, no nightly intermediary)
|
||||
// Development -> Feature/Hotfix branches (The Pittsburgh Model)
|
||||
// We propagate changes from dev DOWN to features/hotfixes so they stay up to date.
|
||||
|
||||
const branches = await github.paginate(github.rest.repos.listBranches, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
|
||||
const featureBranches = branches
|
||||
// Filter for feature/* and hotfix/* branches using regex
|
||||
// AND exclude the branch that just got merged in (if any)
|
||||
const targetBranches = branches
|
||||
.map(b => b.name)
|
||||
.filter(name => name.startsWith('feature/'));
|
||||
.filter(name => {
|
||||
const isTargetType = /^feature\/|^hotfix\//.test(name);
|
||||
const isExcluded = (name === excludedBranch);
|
||||
return isTargetType && !isExcluded;
|
||||
});
|
||||
|
||||
core.info(`Found ${featureBranches.length} feature branches: ${featureBranches.join(', ')}`);
|
||||
core.info(`Found ${targetBranches.length} target branches (excluding '${excludedBranch || 'none'}'): ${targetBranches.join(', ')}`);
|
||||
|
||||
for (const featureBranch of featureBranches) {
|
||||
await createPR('development', featureBranch);
|
||||
for (const targetBranch of targetBranches) {
|
||||
await createPR('development', targetBranch);
|
||||
}
|
||||
}
|
||||
env:
|
||||
|
||||
Reference in New Issue
Block a user