feat(FN-1614): convert git helper functions to async
Convert all 11 git helper functions from sync execSync calls to async runGitCommand: - getGitHubRemotes - isGitRepo - getGitStatus - getGitCommits - getGitCommitsForBranch - getAheadCommits - getCommitDiff - getGitBranches - getGitWorktrees - listGitRemotes - getGitStashList Each function now uses await runGitCommand([...args], cwd, timeout) instead of execSync.
This commit is contained in:
@@ -676,11 +676,10 @@ function parseGitHubBadgeUrl(url: string | undefined): { owner: string; repo: st
|
||||
* Get GitHub remotes from the current git repository.
|
||||
* Executes `git remote -v` and parses the output.
|
||||
*/
|
||||
function getGitHubRemotes(cwd?: string): GitRemote[] {
|
||||
async function getGitHubRemotes(cwd?: string): Promise<GitRemote[]> {
|
||||
try {
|
||||
// Execute git remote -v to get all remotes with their URLs
|
||||
const execOptions = { encoding: "utf-8" as const, timeout: 5000, cwd };
|
||||
const output = execSync("git remote -v", execOptions);
|
||||
const output = await runGitCommand(["remote", "-v"], cwd, 5000);
|
||||
|
||||
const remotes: GitRemote[] = [];
|
||||
const seen = new Set<string>();
|
||||
@@ -720,10 +719,9 @@ function getGitHubRemotes(cwd?: string): GitRemote[] {
|
||||
* Check if the current directory is a git repository.
|
||||
* Used to validate git operations before executing commands.
|
||||
*/
|
||||
function isGitRepo(cwd?: string): boolean {
|
||||
async function isGitRepo(cwd?: string): Promise<boolean> {
|
||||
try {
|
||||
const execOptions = { encoding: "utf-8" as const, timeout: 5000, cwd, stdio: "pipe" as const };
|
||||
execSync("git rev-parse --git-dir", execOptions);
|
||||
await runGitCommand(["rev-parse", "--git-dir"], cwd, 5000);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
@@ -734,30 +732,30 @@ function isGitRepo(cwd?: string): boolean {
|
||||
* Get the current git status including branch, commit hash, and dirty state.
|
||||
* Returns structured data for the Git Manager UI.
|
||||
*/
|
||||
function getGitStatus(cwd?: string): {
|
||||
async function getGitStatus(cwd?: string): Promise<{
|
||||
branch: string;
|
||||
commit: string;
|
||||
isDirty: boolean;
|
||||
ahead: number;
|
||||
behind: number;
|
||||
} | null {
|
||||
} | null> {
|
||||
try {
|
||||
const execOptions = { encoding: "utf-8" as const, timeout: 5000, cwd };
|
||||
// Get current branch
|
||||
const branch = execSync("git branch --show-current", execOptions).trim() || "HEAD detached";
|
||||
const branchOutput = await runGitCommand(["branch", "--show-current"], cwd, 5000);
|
||||
const branch = branchOutput.trim() || "HEAD detached";
|
||||
|
||||
// Get current commit hash (short)
|
||||
const commit = execSync("git rev-parse --short HEAD", execOptions).trim();
|
||||
const commit = (await runGitCommand(["rev-parse", "--short", "HEAD"], cwd, 5000)).trim();
|
||||
|
||||
// Check if working directory is dirty
|
||||
const statusOutput = execSync("git status --porcelain", execOptions).trim();
|
||||
const statusOutput = (await runGitCommand(["status", "--porcelain"], cwd, 5000)).trim();
|
||||
const isDirty = statusOutput.length > 0;
|
||||
|
||||
// Get ahead/behind counts from origin
|
||||
let ahead = 0;
|
||||
let behind = 0;
|
||||
try {
|
||||
const revListOutput = execSync("git rev-list --left-right --count HEAD...@{u}", execOptions).trim();
|
||||
const revListOutput = (await runGitCommand(["rev-list", "--left-right", "--count", "HEAD...@{u}"], cwd, 5000)).trim();
|
||||
const match = revListOutput.match(/(\d+)\s+(\d+)/);
|
||||
if (match) {
|
||||
ahead = parseInt(match[1], 10);
|
||||
@@ -787,12 +785,11 @@ export interface GitCommit {
|
||||
* Get recent commits from the git log.
|
||||
* @param limit Maximum number of commits to return (default 20)
|
||||
*/
|
||||
function getGitCommits(limit: number = 20, cwd?: string): GitCommit[] {
|
||||
async function getGitCommits(limit: number = 20, cwd?: string): Promise<GitCommit[]> {
|
||||
try {
|
||||
// Format: hash|shortHash|message|author|date|parents
|
||||
const format = "%H|%h|%s|%an|%aI|%P";
|
||||
const execOptions = { encoding: "utf-8" as const, timeout: 10000, cwd };
|
||||
const output = execSync(`git log --max-count=${limit} --pretty=format:"${format}"`, execOptions);
|
||||
const output = await runGitCommand(["log", `--max-count=${limit}`, `--pretty=format:${format}`], cwd, 10000);
|
||||
|
||||
const commits: GitCommit[] = [];
|
||||
for (const line of output.split("\n")) {
|
||||
@@ -845,11 +842,10 @@ function isValidGitRef(ref: string): boolean {
|
||||
* @param limit Maximum number of commits to return
|
||||
* @param cwd Working directory
|
||||
*/
|
||||
function getGitCommitsForBranch(branch: string, limit: number = 10, cwd?: string): GitCommit[] {
|
||||
async function getGitCommitsForBranch(branch: string, limit: number = 10, cwd?: string): Promise<GitCommit[]> {
|
||||
try {
|
||||
const format = "%H|%h|%s|%an|%aI|%P";
|
||||
const execOptions = { encoding: "utf-8" as const, timeout: 10000, cwd, stdio: "pipe" as const };
|
||||
const output = execSync(`git log --max-count=${limit} --pretty=format:"${format}" "${branch}"`, execOptions);
|
||||
const output = await runGitCommand(["log", `--max-count=${limit}`, `--pretty=format:${format}`, branch], cwd, 10000);
|
||||
|
||||
const commits: GitCommit[] = [];
|
||||
for (const line of output.split("\n")) {
|
||||
@@ -880,12 +876,11 @@ function getGitCommitsForBranch(branch: string, limit: number = 10, cwd?: string
|
||||
* Returns the list of local commits not yet present on the upstream.
|
||||
* Returns an empty array if there is no upstream configured.
|
||||
*/
|
||||
function getAheadCommits(cwd?: string): GitCommit[] {
|
||||
async function getAheadCommits(cwd?: string): Promise<GitCommit[]> {
|
||||
try {
|
||||
const execOptions = { encoding: "utf-8" as const, timeout: 10000, cwd, stdio: "pipe" as const };
|
||||
// Check if an upstream is configured
|
||||
try {
|
||||
execSync("git rev-parse --abbrev-ref @{u}", execOptions);
|
||||
await runGitCommand(["rev-parse", "--abbrev-ref", "@{u}"], cwd, 10000);
|
||||
} catch {
|
||||
// No upstream configured
|
||||
return [];
|
||||
@@ -893,7 +888,7 @@ function getAheadCommits(cwd?: string): GitCommit[] {
|
||||
|
||||
// Format: hash|shortHash|message|author|date|parents
|
||||
const format = "%H|%h|%s|%an|%aI|%P";
|
||||
const output = execSync(`git log @{u}..HEAD --pretty=format:"${format}"`, execOptions);
|
||||
const output = await runGitCommand(["log", "@{u}..HEAD", `--pretty=format:${format}`], cwd, 10000);
|
||||
|
||||
const commits: GitCommit[] = [];
|
||||
for (const line of output.split("\n")) {
|
||||
@@ -970,17 +965,16 @@ async function getRemoteCommits(remoteRef: string, limit: number = 10, cwd?: str
|
||||
* @param hash The commit hash
|
||||
* @returns Object with stat and patch
|
||||
*/
|
||||
function getCommitDiff(hash: string, cwd?: string): { stat: string; patch: string } | null {
|
||||
async function getCommitDiff(hash: string, cwd?: string): Promise<{ stat: string; patch: string } | null> {
|
||||
try {
|
||||
const execOptions = { encoding: "utf-8" as const, timeout: 10000, cwd, stdio: "pipe" as const };
|
||||
// Validate the hash is a valid git object
|
||||
execSync(`git cat-file -t ${hash}`, { encoding: "utf-8", timeout: 5000, cwd, stdio: "pipe" });
|
||||
await runGitCommand(["cat-file", "-t", hash], cwd, 5000);
|
||||
|
||||
// Get diff stat
|
||||
const stat = execSync(`git show --stat --format="" ${hash}`, execOptions).trim();
|
||||
const stat = (await runGitCommand(["show", "--stat", `--format=""`, hash], cwd, 10000)).trim();
|
||||
|
||||
// Get patch
|
||||
const patch = execSync(`git show --format="" ${hash}`, execOptions);
|
||||
const patch = await runGitCommand(["show", `--format=""`, hash], cwd, 10000);
|
||||
|
||||
return { stat, patch };
|
||||
} catch {
|
||||
@@ -999,27 +993,22 @@ export interface GitBranch {
|
||||
/**
|
||||
* Get all local branches with their info.
|
||||
*/
|
||||
function getGitBranches(cwd?: string): GitBranch[] {
|
||||
async function getGitBranches(cwd?: string): Promise<GitBranch[]> {
|
||||
try {
|
||||
const execOptions = { encoding: "utf-8" as const, timeout: 5000, cwd };
|
||||
// Get current branch name
|
||||
let currentBranch = "";
|
||||
try {
|
||||
currentBranch = execSync("git branch --show-current", execOptions).trim();
|
||||
currentBranch = (await runGitCommand(["branch", "--show-current"], cwd, 5000)).trim();
|
||||
} catch {
|
||||
// Detached HEAD - no current branch
|
||||
}
|
||||
|
||||
// Get all branches with info
|
||||
const format = "%(refname:short)|%(upstream:short)|%(committerdate:iso8601)|%(HEAD)";
|
||||
const output = execSync(`git for-each-ref --format="${format}" refs/heads/`, {
|
||||
encoding: "utf-8",
|
||||
timeout: 10000,
|
||||
cwd,
|
||||
});
|
||||
const output = (await runGitCommand(["for-each-ref", `--format=${format}`, "refs/heads/"], cwd, 10000)).trim();
|
||||
|
||||
const branches: GitBranch[] = [];
|
||||
for (const line of output.trim().split("\n")) {
|
||||
for (const line of output.split("\n")) {
|
||||
const parts = line.split("|");
|
||||
if (parts.length < 4) continue;
|
||||
|
||||
@@ -1053,10 +1042,9 @@ export interface GitWorktree {
|
||||
* Get all git worktrees.
|
||||
* @param tasks Optional task list to correlate worktrees with tasks
|
||||
*/
|
||||
function getGitWorktrees(tasks: { id: string; worktree?: string }[] = [], cwd?: string): GitWorktree[] {
|
||||
async function getGitWorktrees(tasks: { id: string; worktree?: string }[] = [], cwd?: string): Promise<GitWorktree[]> {
|
||||
try {
|
||||
const execOptions = { encoding: "utf-8" as const, timeout: 10000, cwd };
|
||||
const output = execSync("git worktree list --porcelain", execOptions);
|
||||
const output = await runGitCommand(["worktree", "list", "--porcelain"], cwd, 10000);
|
||||
|
||||
const worktrees: GitWorktree[] = [];
|
||||
let currentWorktree: Partial<GitWorktree> = {};
|
||||
@@ -1304,10 +1292,9 @@ function isValidGitUrl(url: string): boolean {
|
||||
* Get all git remotes with their fetch and push URLs.
|
||||
* Executes `git remote -v` and parses the output.
|
||||
*/
|
||||
function listGitRemotes(cwd?: string): GitRemoteDetailed[] {
|
||||
async function listGitRemotes(cwd?: string): Promise<GitRemoteDetailed[]> {
|
||||
try {
|
||||
const execOptions = { encoding: "utf-8" as const, timeout: 5000, cwd };
|
||||
const output = execSync("git remote -v", execOptions);
|
||||
const output = await runGitCommand(["remote", "-v"], cwd, 5000);
|
||||
|
||||
const remotes = new Map<string, { fetchUrl: string; pushUrl: string }>();
|
||||
|
||||
@@ -1457,13 +1444,9 @@ export interface GitFileChange {
|
||||
/**
|
||||
* Get list of stash entries.
|
||||
*/
|
||||
function getGitStashList(cwd?: string): GitStash[] {
|
||||
async function getGitStashList(cwd?: string): Promise<GitStash[]> {
|
||||
try {
|
||||
const output = execSync('git stash list --format="%gd|%gs|%ai"', {
|
||||
encoding: "utf-8",
|
||||
timeout: 5000,
|
||||
cwd,
|
||||
}).trim();
|
||||
const output = (await runGitCommand(["stash", "list", '--format="%gd|%gs|%ai"'], cwd, 5000)).trim();
|
||||
if (!output) return [];
|
||||
|
||||
const stashes: GitStash[] = [];
|
||||
|
||||
Reference in New Issue
Block a user