import * as core from '@actions/core'; import { spawn } from "child_process"; import fs from "fs/promises"; import path from 'path'; import {ChildProcess, SpawnOptions} from "node:child_process"; const myToken = core.getInput('branches'); // Ignored branches const IGNORED_BRANCHES = ['master', 'main', 'dev', 'release']; type BranchDependencies = Record; /** * Helper function to run a Git command and capture stdout and stderr. */ const runGitCommand = (args: string[], options?: SpawnOptions): Promise => new Promise((resolve, reject) => { const git: ChildProcess = spawn('git', args, { stdio: "inherit", ...options }); let stdout: string = ""; let stderr: string = ""; git.stdout.on('data', (data) => stdout += data.toString()); git.stderr.on('data', (data) => stderr += data.toString()); // Handle completion git.on('close', (code) => (code === 0) ? resolve(stdout) : reject(new Error(`Git command failed with code ${code}: ${stderr}`))); }); /** * Fetch all remote branches. */ const fetchBranches = async (): Promise => { console.log('Fetching all branches...'); await runGitCommand(['fetch', '--all']); const branches = await runGitCommand(['branch', '-r']); return branches .split('\n') .map((branch) => branch.trim().replace('origin/', '')) .filter((branch) => !IGNORED_BRANCHES.includes(branch) && branch !== ''); } /** * Detect dependencies between branches. */ const detectDependencies = async (branches: string[]): Promise => { console.log('Detecting dependencies...'); const dependencies: BranchDependencies = {}; for (const branch of branches) { dependencies[branch] = null; // Default: no dependency for (const otherBranch of branches) { if (branch !== otherBranch) { const base = await runGitCommand(['merge-base', `origin/${branch}`, `origin/${otherBranch}`]); const isAncestor = await runGitCommand(['merge-base', '--is-ancestor', base.trim(), `origin/${branch}`]).catch(() => false); if (isAncestor) { dependencies[branch] = otherBranch; break; } } } } return dependencies; } /** * Perform a topological sort to determine the rebase order. */ const determineRebaseOrder = (dependencies: BranchDependencies) => { console.log('Determining rebase order...'); const visited = new Set(); const order = []; const visit = (branch) => { if (visited.has(branch)) return; visited.add(branch); if (dependencies[branch]) visit(dependencies[branch]); order.push(branch); }; Object.keys(dependencies).forEach((branch) => visit(branch)); return order; } /** * Rebase a branch onto its base branch. */ const rebaseBranch = async (branch: string, baseBranch = 'master'): Promise => { console.log(`Rebasing ${branch} onto ${baseBranch}...`); try { await runGitCommand(['checkout', branch]); await runGitCommand(['fetch', 'origin', baseBranch]); await runGitCommand(['rebase', `origin/${baseBranch}`]); console.log(`Rebase successful for ${branch}. Pushing...`); await runGitCommand(['push', '--force-with-lease']); } catch (error) { console.error(`Rebase failed for ${branch}: ${error.message}`); await runGitCommand(['rebase', '--abort']); } } /** * Main function to execute the workflow. */ const main = async (): Promise => { try { // Step 1: Fetch branches const branches = await fetchBranches(); await fs.writeFile(BRANCHES_FILE, branches.join('\n'), 'utf-8'); console.log('Branches:', branches); // Step 2: Detect dependencies const dependencies = await detectDependencies(branches); await fs.writeFile(DEPENDENCIES_FILE, JSON.stringify(dependencies, null, 2), 'utf-8'); console.log('Dependencies:', dependencies); // Step 3: Determine rebase order const order = determineRebaseOrder(dependencies); console.log('Rebase order:', order); // Step 4: Rebase branches for (const branch of order) { const baseBranch = dependencies[branch] || 'master'; await rebaseBranch(branch, baseBranch); } } catch (error) { console.error('Error during workflow execution:', error.message); } } // Execute the script main();