branchforge
Advanced tools
Comparing version 0.2.0 to 0.3.0
@@ -42,13 +42,93 @@ #!/usr/bin/env node | ||
const commander_1 = require("commander"); | ||
const yaml = __importStar(require("yaml")); | ||
const simple_git_1 = __importDefault(require("simple-git")); | ||
const yaml = __importStar(require("yaml")); | ||
const git = (0, simple_git_1.default)(); | ||
// Git utility class to handle git-related operations | ||
class GitService { | ||
constructor() { | ||
this.git = (0, simple_git_1.default)(); | ||
} | ||
getCurrentBranch() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
return yield this.git.revparse(["--abbrev-ref", "HEAD"]); | ||
}); | ||
} | ||
branchExists(branch) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
const branches = yield this.git.branch(); | ||
return branches.all.includes(branch); | ||
} | ||
catch (error) { | ||
if (error instanceof Error) { | ||
console.error(`Error checking branch: ${error.message}`); | ||
} | ||
return false; | ||
} | ||
}); | ||
} | ||
getBranchStatus(branch, base) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const status = yield this.git.status(); | ||
const logDiff = yield this.git.log([`${base}..${branch}`]); | ||
return { status, logDiff }; | ||
}); | ||
} | ||
deleteBranch(branchName) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
yield this.git.branch(["-D", branchName]); | ||
console.log(`Deleted branch "${branchName}".`); | ||
}); | ||
} | ||
recreateBranch(base, branchName, includes) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
console.log(`Rebuilding branch "${branchName}" from base "${base}" and includes: ${includes.join(", ")}.`); | ||
yield this.git.checkout(base); | ||
yield this.git.checkoutLocalBranch(branchName); | ||
for (const include of includes) { | ||
yield this.git.merge([include]); | ||
} | ||
console.log(`Branch "${branchName}" has been rebuilt.`); | ||
}); | ||
} | ||
isBranchEqualToBaseAndIncludes(branchName, base, includes) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const baseAndIncludesBranches = [base, ...includes]; | ||
try { | ||
const combinedCommits = yield this.git.raw([ | ||
"rev-list", | ||
"--no-merges", | ||
...baseAndIncludesBranches, | ||
]); | ||
const branchCommits = yield this.git.raw([ | ||
"rev-list", | ||
"--no-merges", | ||
branchName, | ||
]); | ||
return branchCommits.trim() === combinedCommits.trim(); | ||
} | ||
catch (error) { | ||
if (error instanceof Error) { | ||
console.error(`Error comparing branches: ${error.message}`); | ||
} | ||
return false; | ||
} | ||
}); | ||
} | ||
checkout(branch) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
yield this.git.checkout(branch); | ||
console.log(`Checked out branch "${branch}".`); | ||
}); | ||
} | ||
} | ||
// Load and parse branch specification from YAML files | ||
const loadBranchSpec = (yamlFiles) => { | ||
for (const filePath of yamlFiles) { | ||
console.debug("Attempting to load", filePath); | ||
try { | ||
const file = fs.readFileSync(filePath, 'utf8'); | ||
const file = fs.readFileSync(filePath, "utf8"); | ||
return yaml.parse(file); | ||
} | ||
catch (error) { | ||
if (error.code !== 'ENOENT') { | ||
if (error.code !== "ENOENT") { | ||
console.error(`Failed to load or parse ${filePath}:`, error); | ||
@@ -58,27 +138,6 @@ } | ||
} | ||
console.error('No valid branch specification files found.'); | ||
return null; | ||
throw new Error("No valid branch specification files found."); | ||
}; | ||
// Function to check if a branch has changes or is behind its base | ||
const isBranchBehind = (branchName, base) => __awaiter(void 0, void 0, void 0, function* () { | ||
// Check if the branch is behind the base branch | ||
const countBehind = yield git.raw(['rev-list', '--count', `${branchName}..${base}`]); | ||
return parseInt(countBehind) > 0; // Returns true if the branch is behind | ||
}); | ||
const hasLocalChanges = (branchName) => __awaiter(void 0, void 0, void 0, function* () { | ||
const status = yield git.status(); | ||
return (status.modified.includes(branchName) || | ||
status.renamed.some((renamed) => renamed.from === branchName || renamed.to === branchName) || | ||
status.deleted.includes(branchName)); | ||
}); | ||
const rebuildBranch = (branchName) => __awaiter(void 0, void 0, void 0, function* () { | ||
const yamlFiles = [ | ||
path.join(__dirname, '../branch-spec.yml'), | ||
path.join(__dirname, '../branch-spec.yaml') | ||
]; | ||
const spec = loadBranchSpec(yamlFiles); | ||
if (!spec || !spec.branches) { | ||
console.error(`Branch specification file is missing or improperly formatted.`); | ||
return; | ||
} | ||
// Rebuild the branch if it doesn't match the spec or is behind | ||
const rebuildBranch = (branchName, spec, gitService, force) => __awaiter(void 0, void 0, void 0, function* () { | ||
const branchConfig = spec.branches[branchName]; | ||
@@ -90,72 +149,73 @@ if (!branchConfig) { | ||
const { base, includes } = branchConfig; | ||
try { | ||
const branches = yield git.branch(); | ||
// Checkout the base branch | ||
yield git.checkout(base); | ||
// Check if the base branch is ahead of the current branch | ||
const isBaseAhead = yield isBranchBehind(branchName, base); | ||
if (isBaseAhead) { | ||
console.log(`Base branch ${base} is ahead of ${branchName}. It has been updated.`); | ||
const baseExists = yield gitService.branchExists(base); | ||
const includesExist = yield Promise.all(includes.map(gitService.branchExists.bind(gitService))); | ||
if (!baseExists) { | ||
console.error(`Base branch "${base}" does not exist.`); | ||
return; | ||
} | ||
const missingIncludes = includes.filter((_, i) => !includesExist[i]); | ||
if (missingIncludes.length > 0) { | ||
console.error(`Includes branches do not exist: ${missingIncludes.join(", ")}`); | ||
return; | ||
} | ||
const branchExistsAlready = yield gitService.branchExists(branchName); | ||
if (!branchExistsAlready) { | ||
console.log(`Branch "${branchName}" does not exist, creating it.`); | ||
if (force) { | ||
yield gitService.recreateBranch(base, branchName, includes); | ||
} | ||
else { | ||
console.log(`Base branch ${base} is not ahead of ${branchName}.`); | ||
return; | ||
} | ||
const isEqual = yield gitService.isBranchEqualToBaseAndIncludes(branchName, base, includes); | ||
if (!isEqual) { | ||
console.log(`Branch "${branchName}" differs from "${base}" + [${includes.join(", ")}], rebuilding.`); | ||
if (force) { | ||
yield gitService.deleteBranch(branchName); | ||
yield gitService.recreateBranch(base, branchName, includes); | ||
} | ||
// If the branch does not exist, create it | ||
if (!branches.all.includes(branchName)) { | ||
console.log(`Creating branch: ${branchName} from ${base}`); | ||
yield git.checkout(base); | ||
yield git.checkoutLocalBranch(branchName); | ||
} | ||
else { | ||
// Check for local changes and if the branch is behind the base | ||
const localChanges = yield hasLocalChanges(branchName); | ||
if (localChanges || isBaseAhead) { | ||
console.log(`Branch ${branchName} has changes or is behind the base branch, rebuilding...`); | ||
} | ||
else { | ||
console.log(`Branch ${branchName} has no changes since last build. Skipping.`); | ||
return; | ||
} | ||
console.log(`Deleting existing branch: ${branchName}`); | ||
yield git.branch(['-D', branchName]); | ||
// Create the branch again from base | ||
console.log(`Creating branch: ${branchName} from ${base}`); | ||
yield git.checkout(base); | ||
yield git.checkoutLocalBranch(branchName); | ||
} | ||
// Cherry-pick the specified commits into the new branch | ||
for (const include of includes) { | ||
console.log(`Cherry-picking ${include} into ${branchName}`); | ||
yield git.raw(['cherry-pick', include]); | ||
} | ||
console.log(`Branch ${branchName} created successfully.`); | ||
} | ||
catch (error) { | ||
console.error(`Error: ${error.message}`); | ||
else { | ||
console.log(`Branch "${branchName}" is exactly the same as "${base}" + [${includes.join(", ")}], no need to rebuild.`); | ||
} | ||
}); | ||
// Function to handle command-line input | ||
// Handle CLI input | ||
const runCLI = () => __awaiter(void 0, void 0, void 0, function* () { | ||
const gitService = new GitService(); | ||
const originalBranch = yield gitService.getCurrentBranch(); // Use the new method | ||
console.log('starting out in branch', originalBranch); | ||
commander_1.program | ||
.version('1.0.0') | ||
.command('update [branch]') | ||
.description('Rebuild branches based on the spec') | ||
.action((branch) => __awaiter(void 0, void 0, void 0, function* () { | ||
.version("1.0.0") | ||
.command("update [branch]") | ||
.description("Rebuild branches based on the spec") | ||
.option("--force", "Force the operation, making changes.") | ||
.action((branch, options) => __awaiter(void 0, void 0, void 0, function* () { | ||
console.log("OPTIONS", options); | ||
const yamlFiles = [ | ||
path.join(__dirname, '../branch-spec.yml'), | ||
path.join(__dirname, '../branch-spec.yaml') | ||
path.join(__dirname, "../branch-spec.yml"), | ||
path.join(__dirname, "../branch-spec.yaml"), | ||
]; | ||
const spec = loadBranchSpec(yamlFiles); | ||
if (!spec || !spec.branches) { | ||
console.error(`Branch specification file is missing or improperly formatted.`); | ||
return; | ||
try { | ||
const spec = loadBranchSpec(yamlFiles); | ||
if (!spec || !spec.branches) { | ||
console.error("Branch specification file is missing or improperly formatted."); | ||
return; | ||
} | ||
if (branch) { | ||
yield rebuildBranch(branch, spec, gitService, options.force); | ||
} | ||
else { | ||
for (const branchName of Object.keys(spec.branches)) { | ||
yield rebuildBranch(branchName, spec, gitService, options.force); | ||
} | ||
} | ||
} | ||
if (branch) { | ||
yield rebuildBranch(branch); | ||
} | ||
else { | ||
for (const branchName of Object.keys(spec.branches)) { | ||
yield rebuildBranch(branchName); | ||
catch (error) { | ||
if (error instanceof Error) { | ||
console.error(error.message); | ||
} | ||
} | ||
finally { | ||
// Ensure the original branch is checked out again | ||
yield gitService.checkout(originalBranch); | ||
} | ||
})); | ||
@@ -162,0 +222,0 @@ commander_1.program.parse(process.argv); |
{ | ||
"name": "branchforge", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"private": false, | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
9949
221