@harryisfish/gitt
Version:
A command-line tool to help you manage Git repositories and remote repositories, such as keeping in sync, pushing, pulling, etc.
153 lines (152 loc) • 5.51 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMainBranch = getMainBranch;
exports.setMainBranch = setMainBranch;
exports.isBranchMerged = isBranchMerged;
exports.getWorktrees = getWorktrees;
exports.getBranchLastCommitTime = getBranchLastCommitTime;
const simple_git_1 = require("simple-git");
const errors_1 = require("../errors");
const config_1 = require("./config");
const git = (0, simple_git_1.simpleGit)();
/**
* Get the main branch name for the current repository.
* Priority:
* 1. User config (gitt.mainBranch)
* 2. Remote HEAD (origin/HEAD)
* 3. Common names (main, master)
*/
async function getMainBranch() {
try {
// 1. Check .gitt config file
const fileConfig = await (0, config_1.readConfigFile)();
if (fileConfig.mainBranch) {
return fileConfig.mainBranch;
}
// 2. Check user config (legacy/fallback)
const configMain = await git.getConfig('gitt.mainBranch');
if (configMain.value) {
return configMain.value;
}
// 3. Try to detect from remote HEAD
try {
const remotes = await git.listRemote(['--symref', 'origin', 'HEAD']);
// Output format example: "ref: refs/heads/main\tHEAD"
const match = remotes.match(/ref: refs\/heads\/([^\s]+)\s+HEAD/);
if (match && match[1]) {
return match[1];
}
}
catch (e) {
// Ignore network/remote errors during detection
}
// 4. Check for common local branches
const localBranches = await git.branchLocal();
if (localBranches.all.includes('main'))
return 'main';
if (localBranches.all.includes('master'))
return 'master';
// Default fallback
return 'main';
}
catch (error) {
throw new errors_1.GitError('Failed to detect main branch');
}
}
/**
* Set the preferred main branch for the current repository.
*/
async function setMainBranch(branch) {
try {
// Verify branch exists locally or remotely
const localBranches = await git.branchLocal();
if (!localBranches.all.includes(branch)) {
// If not local, check if we can fetch it
try {
await git.fetch(['origin', branch]);
}
catch (e) {
throw new errors_1.GitError(`Branch '${branch}' does not exist locally or on remote`);
}
}
await (0, config_1.writeConfigFile)({ mainBranch: branch });
}
catch (error) {
if (error instanceof errors_1.GitError)
throw error;
throw new errors_1.GitError('Failed to set main branch configuration');
}
}
/**
* Check if a branch is merged into the main branch.
* Supports regular merge, squash merge, and rebase merge detection.
*/
async function isBranchMerged(branch, mainBranch) {
try {
// Method 1: Check regular merge with git branch --merged
const mergedBranches = await git.branch(['--merged', mainBranch]);
if (mergedBranches.all.includes(branch)) {
return true;
}
// Method 2: Check squash/rebase merge with git cherry
// git cherry returns empty or all lines start with '-' if fully merged
// '-' means the commit exists in upstream (merged)
// '+' means the commit does not exist in upstream (not merged)
const cherryOutput = await git.raw(['cherry', mainBranch, branch]);
const lines = cherryOutput.trim().split('\n').filter(Boolean);
// If no commits unique to branch, or all commits are merged (start with -)
if (lines.length === 0) {
return true;
}
// Check if all commits are merged (all lines start with '-')
const hasUnmergedCommits = lines.some(line => line.startsWith('+'));
return !hasUnmergedCommits;
}
catch (error) {
return false;
}
}
/**
* Get a list of branches that are currently checked out in worktrees.
*/
async function getWorktrees() {
try {
// Output format:
// /path/to/repo (HEAD detached at 123456)
// /path/to/worktree [branch-name]
const worktrees = await git.raw(['worktree', 'list']);
const lines = worktrees.split('\n').filter(Boolean);
const branches = [];
for (const line of lines) {
// Extract branch name from [branch-name]
const match = line.match(/\[(.*?)\]/);
if (match && match[1]) {
branches.push(match[1]);
}
}
return branches;
}
catch (error) {
// If worktrees are not supported or error occurs, return empty list
return [];
}
}
/**
* Get the last commit time (in days) for a branch.
* Returns the number of days since the last commit.
*/
async function getBranchLastCommitTime(branch) {
try {
// Get unix timestamp of last commit
const timestamp = await git.raw(['log', '-1', '--format=%at', branch]);
const lastCommitDate = new Date(parseInt(timestamp.trim()) * 1000);
const now = new Date();
const diffTime = Math.abs(now.getTime() - lastCommitDate.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
}
catch (error) {
// If branch doesn't exist or error, return 0 (treat as active/new to be safe)
return 0;
}
}