herme5
Version:
HERME5 is a simple tool for managing multiple Git repositories in a workspace.
111 lines (91 loc) • 3.6 kB
JavaScript
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
function parseArguments() {
if (process.argv.length < 3 || process.argv.length > 5) {
console.error("Usage: hermes <workspace-path> [--pull] [--stash]");
process.exit(1);
}
const workspacePath = path.resolve(process.argv[2]);
const options = {
pull: process.argv.includes("--pull"),
stash: process.argv.includes("--stash"),
};
return { workspacePath, options };
}
function validateWorkspace(workspacePath) {
if (
!fs.existsSync(workspacePath) ||
!fs.lstatSync(workspacePath).isDirectory()
) {
console.error("❌ Invalid workspace path:", workspacePath);
process.exit(1);
}
}
function findGitRepositories(workspacePath) {
return fs.readdirSync(workspacePath).filter((dir) => {
const gitPath = path.join(workspacePath, dir, ".git");
return fs.existsSync(gitPath) && fs.lstatSync(gitPath).isDirectory();
});
}
function checkRepositoryStatus(repoPath) {
try {
execSync(`git -C "${repoPath}" fetch`, { stdio: "ignore" });
const status = execSync(`git -C "${repoPath}" status -sb`).toString();
return status.includes("behind");
} catch (error) {
console.error(`⚠️ Error checking ${repoPath}:`, error.message);
return false;
}
}
function pullUpdates(repoPath, useStash) {
try {
// Check `git status --porcelain` to detect untracked files or conflicts
const statusOutput = execSync(`git -C "${repoPath}" status --porcelain`).toString();
const hasUntrackedFiles = statusOutput.split("\n").some(line => line.startsWith("??"));
const hasConflicts = statusOutput.includes("U ") || statusOutput.includes("AA");
if (useStash && !hasUntrackedFiles && !hasConflicts) {
console.log(`💾 Stashing changes in ${repoPath}...`);
execSync(`git -C "${repoPath}" stash`, { stdio: 'ignore' });
} else if (useStash) {
console.log(`⚠️ Skipping stash for ${repoPath}: Untracked files or conflicts detected.`);
}
console.log(`🔄 Pulling updates for ${repoPath}...`);
execSync(`git -C "${repoPath}" pull --rebase`, { stdio: 'inherit' });
if (useStash && !hasUntrackedFiles && !hasConflicts) {
console.log(`📂 Restoring stashed changes in ${repoPath}...`);
execSync(`git -C "${repoPath}" stash pop`, { stdio: 'inherit' });
}
console.log(`✅ Successfully pulled updates for ${repoPath}.\n`);
} catch (error) {
console.error(`❌ Failed to pull updates for ${repoPath}:`, error.message);
}
}
function processRepositories(repos, workspacePath, options) {
if (repos.length === 0) {
console.log("🚫 No Git repositories found.");
process.exit(0);
}
let updatesFound = false;
repos.forEach((repo) => {
const repoPath = path.join(workspacePath, repo); // ✅ workspacePath now correctly passed
if (checkRepositoryStatus(repoPath)) {
console.log(`📌 ${repo} is BEHIND the remote branch! Needs pull.`);
updatesFound = true;
if (options.pull) {
pullUpdates(repoPath, options.stash);
}
} else {
console.log(`✅ ${repo} is up to date.`);
}
});
if (!updatesFound) {
console.log("✅ All repositories are up to date.");
}
}
// --- MAIN EXECUTION ---
const { workspacePath, options } = parseArguments();
validateWorkspace(workspacePath);
const repos = findGitRepositories(workspacePath);
processRepositories(repos, workspacePath, options);