UNPKG

worktree-tool

Version:

A command-line tool for managing Git worktrees with integrated tmux/shell session management

123 lines 5.34 kB
import chalk from "chalk"; import { Command } from "commander"; import { basename } from "path"; import { countStatuses, displayLegend, displayVerboseFiles, formatWorktreeStatus } from "../utils/status-formatter.js"; import { BaseCommand } from "./base.js"; /** * Status command - shows git status across all worktrees */ export class StatusCommand extends BaseCommand { requiresConfig() { return true; } requiresGitRepo() { return true; } showVerboseStatus() { return false; } validateOptions( /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ _options) { // Validation can be added here if needed // For now, the -w option accepts any string value } async executeCommand(options, context) { const { git, config } = context; // Get all worktrees const worktrees = await git.listWorktrees(); // Find the main worktree const mainWorktree = worktrees.find((w) => w.isMain); const mainBranch = mainWorktree?.branch ?? config?.mainBranch ?? "main"; // Add name property derived from path and filter out main worktree const worktreesWithNames = worktrees .filter((w) => !w.isMain) .map((w) => ({ ...w, name: basename(w.path), })); // Filter by -w option if provided let filteredWorktrees = worktreesWithNames; if (options.worktrees) { const filter = options.worktrees.split(",").map((w) => w.trim()); filteredWorktrees = worktreesWithNames.filter((w) => filter.includes(w.name)); } const statusPromises = filteredWorktrees.map(async (worktree) => { const [statusLines, mainComparison, hasConflicts] = await Promise.all([ git.getWorktreeStatus(worktree.path), git.getAheadBehindBranch(worktree.path, mainBranch), git.hasConflicts(worktree.path, mainBranch), ]); const counts = countStatuses(statusLines); // If there are no active conflicts but hasConflicts is true, get the files that would conflict let potentialConflictFiles = []; if (hasConflicts && counts.conflicts === 0) { // Simple approach: get files changed in this branch vs main try { const result = await git.raw(["-C", worktree.path, "diff", "--name-only", mainBranch]); potentialConflictFiles = result.split("\n").filter((f) => f.trim()); } catch { // If we can't get the list of files, we still know there's at least one conflict // Set a dummy entry so the count is at least 1 potentialConflictFiles = ["<unknown>"]; } } return { name: worktree.name, path: worktree.path, counts, ahead: mainComparison.ahead, behind: mainComparison.behind, hasConflicts, potentialConflictCount: potentialConflictFiles.length, statusLines: options.verbose ? statusLines : undefined, potentialConflictFiles, }; }); const statuses = await Promise.all(statusPromises); // Display legend if verbose if (options.verbose) { displayLegend(); } // Calculate max name length for alignment const maxNameLength = Math.max(...statuses.map((s) => s.name.length)); // Output formatted status for each worktree for (const status of statuses) { const formatted = formatWorktreeStatus(status, maxNameLength); // eslint-disable-next-line no-console console.log(formatted); // Display file listing if verbose if (options.verbose) { // First display potential conflict files if any if (status.potentialConflictFiles && status.potentialConflictFiles.length > 0) { const orangeColor = chalk.hex("#FFA500"); for (const file of status.potentialConflictFiles) { // eslint-disable-next-line no-console console.log(`${orangeColor("(!)")} ${file}`); } } // Then display regular status files if (status.statusLines && status.statusLines.length > 0) { displayVerboseFiles(status.statusLines); } // eslint-disable-next-line no-console console.log(); // Empty line between worktrees } } } } /** * Create the status command */ export const statusCommand = new Command("status") .description("Show git status across all worktrees") .option("-w, --worktrees <names>", "filter worktrees (comma-separated)") .option("-v, --verbose", "show detailed file listing") .action(async function (options) { const command = new StatusCommand(); const parentOpts = this.parent?.opts() ?? {}; const mergedOptions = { ...parentOpts, ...options }; await command.execute(mergedOptions); }); //# sourceMappingURL=status.js.map