Skip to main content

Git Utilities

Async utilities for git operations including branch management, merging with conflict detection, diffing, and repository inspection.

Branch Management

createBranch

Create and checkout a new git branch.
async function createBranch(branchName: string, cwd?: string): Promise<void>
branchName
string
required
Name of the branch to create
cwd
string
Working directory (defaults to process.cwd())
Throws: Error if branch creation fails. Example:
import { createBranch } from "@longshot/core";

await createBranch("feature-new-api");

checkoutBranch

Checkout an existing branch.
async function checkoutBranch(branchName: string, cwd?: string): Promise<void>
branchName
string
required
Name of the branch to checkout
cwd
string
Working directory (defaults to process.cwd())
Throws: Error if checkout fails. Example:
import { checkoutBranch } from "@longshot/core";

await checkoutBranch("main");

getCurrentBranch

Get the name of the current branch.
async function getCurrentBranch(cwd?: string): Promise<string>
cwd
string
Working directory (defaults to process.cwd())
Returns: Name of the current branch. Example:
import { getCurrentBranch } from "@longshot/core";

const branch = await getCurrentBranch();
console.log(`Current branch: ${branch}`);

Merging and Rebasing

mergeBranch

Merge a source branch into a target branch with configurable strategy and conflict detection.
async function mergeBranch(
  source: string,
  target?: string,
  strategy?: "fast-forward" | "rebase" | "merge-commit",
  cwd?: string
): Promise<MergeResult>
source
string
required
Source branch to merge from
target
string
Target branch to merge into (defaults to current branch)
strategy
string
Merge strategy: fast-forward, rebase, or merge-commit (default)
cwd
string
Working directory (defaults to process.cwd())
Returns: MergeResult object with success status, conflict information, and details. Example:
import { mergeBranch } from "@longshot/core";

const result = await mergeBranch("feature-branch", "main", "fast-forward");

if (result.success) {
  console.log(result.message);
} else if (result.conflicted) {
  console.error(`Conflict in files: ${result.conflictingFiles?.join(", ")}`);
} else {
  console.error(result.message);
}

MergeResult Type

interface MergeResult {
  success: boolean;
  conflicted?: boolean;
  message: string;
  conflictingFiles?: string[];
}
success
boolean
required
Whether the merge succeeded
conflicted
boolean
Whether the merge failed due to conflicts
message
string
required
Human-readable result message
conflictingFiles
string[]
List of files with conflicts (populated before merge is aborted)

rebaseBranch

Rebase a branch onto another branch.
async function rebaseBranch(
  branchName: string,
  onto: string,
  cwd?: string
): Promise<RebaseResult>
branchName
string
required
Branch to rebase
onto
string
required
Branch to rebase onto
cwd
string
Working directory (defaults to process.cwd())
Returns: RebaseResult object with success and conflict status. Example:
import { rebaseBranch } from "@longshot/core";

const result = await rebaseBranch("feature-branch", "main");

if (!result.success) {
  if (result.conflicted) {
    console.error("Rebase conflict occurred");
  } else {
    console.error(result.message);
  }
}
If a rebase fails, the operation is automatically aborted to leave the repository in a clean state.

RebaseResult Type

interface RebaseResult {
  success: boolean;
  conflicted: boolean;
  message: string;
}
success
boolean
required
Whether the rebase succeeded
conflicted
boolean
required
Whether the rebase failed due to conflicts
message
string
required
Human-readable result message

Diff and Stats

getDiffStat

Get statistics about uncommitted changes in the working directory.
async function getDiffStat(cwd?: string): Promise<DiffStat>
cwd
string
Working directory (defaults to process.cwd())
Returns: DiffStat object with change statistics. Example:
import { getDiffStat } from "@longshot/core";

const stats = await getDiffStat();
console.log(`${stats.filesChanged} files changed`);
console.log(`+${stats.linesAdded} -${stats.linesRemoved}`);

DiffStat Type

interface DiffStat {
  filesChanged: number;
  linesAdded: number;
  linesRemoved: number;
}
filesChanged
number
required
Number of files with changes
linesAdded
number
required
Number of lines added
linesRemoved
number
required
Number of lines removed

hasUncommittedChanges

Check if there are any uncommitted changes in the working directory.
async function hasUncommittedChanges(cwd?: string): Promise<boolean>
cwd
string
Working directory (defaults to process.cwd())
Returns: true if there are uncommitted changes, false otherwise. Example:
import { hasUncommittedChanges } from "@longshot/core";

if (await hasUncommittedChanges()) {
  console.log("You have uncommitted changes");
}

Repository Inspection

getRecentCommits

Get recent commit history.
async function getRecentCommits(count: number, cwd?: string): Promise<CommitInfo[]>
count
number
required
Number of commits to retrieve
cwd
string
Working directory (defaults to process.cwd())
Returns: Array of CommitInfo objects. Example:
import { getRecentCommits } from "@longshot/core";

const commits = await getRecentCommits(10);

for (const commit of commits) {
  console.log(`${commit.hash.slice(0, 7)} - ${commit.message}`);
  console.log(`  by ${commit.author} on ${new Date(commit.date)}`);
}

CommitInfo Type

interface CommitInfo {
  hash: string;
  message: string;
  author: string;
  date: number;
}
hash
string
required
Full commit hash (SHA-1)
message
string
required
Commit message (first line)
author
string
required
Author name
date
number
required
Commit timestamp in Unix milliseconds

getFileTree

Get a list of all tracked files in the repository.
async function getFileTree(cwd?: string, maxDepth?: number): Promise<string[]>
cwd
string
Working directory (defaults to process.cwd())
maxDepth
number
Optional maximum directory depth to include
Returns: Array of file paths relative to repository root. Example:
import { getFileTree } from "@longshot/core";

// Get all files
const allFiles = await getFileTree();

// Get only top-level and one-level-deep files
const shallowFiles = await getFileTree(undefined, 2);

console.log(`Total files: ${allFiles.length}`);
console.log(`Shallow files: ${shallowFiles.length}`);

Complete Example

import {
  createBranch,
  checkoutBranch,
  getCurrentBranch,
  mergeBranch,
  getDiffStat,
  hasUncommittedChanges,
  getRecentCommits
} from "@longshot/core";

async function processTask() {
  // Check current state
  const currentBranch = await getCurrentBranch();
  console.log(`Starting on branch: ${currentBranch}`);
  
  if (await hasUncommittedChanges()) {
    console.error("Please commit or stash changes first");
    return;
  }
  
  // Create task branch
  await createBranch("task-branch-123");
  console.log("Created task branch");
  
  // Do work...
  // (make changes to files)
  
  // Check what changed
  const stats = await getDiffStat();
  console.log(`Changed ${stats.filesChanged} files (+${stats.linesAdded} -${stats.linesRemoved})`);
  
  // Commit work (using shell command, not shown)
  
  // Merge back to main
  await checkoutBranch("main");
  const mergeResult = await mergeBranch("task-branch-123", "main", "fast-forward");
  
  if (mergeResult.success) {
    console.log("Successfully merged task branch");
    
    // Show recent commits
    const commits = await getRecentCommits(5);
    console.log("\nRecent commits:");
    for (const commit of commits) {
      console.log(`  ${commit.hash.slice(0, 7)} - ${commit.message}`);
    }
  } else if (mergeResult.conflicted) {
    console.error("Merge conflicts detected:");
    console.error(mergeResult.conflictingFiles?.join("\n"));
  } else {
    console.error(`Merge failed: ${mergeResult.message}`);
  }
}

processTask().catch(console.error);

Merge Strategies

Fast-Forward

Performs a fast-forward merge with --ff-only. Fails if fast-forward is not possible.
await mergeBranch("feature", "main", "fast-forward");
Best for linear history. Fails if the target branch has diverged from the source.

Rebase

Rebases the source branch onto the target, then performs a fast-forward merge.
await mergeBranch("feature", "main", "rebase");
Creates a linear history by replaying commits. Uses a temporary branch internally to avoid polluting the branch namespace.

Merge Commit

Creates a merge commit with --no-ff.
await mergeBranch("feature", "main", "merge-commit");
// or simply:
await mergeBranch("feature", "main");
Default strategy. Preserves the full branch history with an explicit merge commit.

Conflict Handling

All merge and rebase operations automatically detect conflicts and abort the operation to leave the repository in a clean state.
const result = await mergeBranch("feature", "main");

if (result.conflicted) {
  console.log("Conflicts detected in:");
  for (const file of result.conflictingFiles || []) {
    console.log(`  - ${file}`);
  }
  // Repository is automatically returned to pre-merge state
}
When conflicts occur:
  • The merge/rebase is automatically aborted
  • Conflicting files are captured before abort
  • Repository is returned to a clean state
  • The conflictingFiles array contains all files with conflicts

Build docs developers (and LLMs) love