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: "pipe", ...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: string = await runGitCommand(["branch", "-r"]); console.log(branches) 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: string[] = await fetchBranches(); console.log(branches) 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();