Files
automaker/libs/git-utils/README.md
Kacper 493c392422 refactor: Address PR review feedback on shared packages
- Standardize vitest to v4.0.16 across all packages
- Clean up type imports in events.ts (remove verbose inline casting)
- Expand skipDirs to support Python, Rust, Go, PHP, Gradle projects
- Document circular dependency prevention in @automaker/types
- Add comprehensive error handling documentation to @automaker/git-utils

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-20 23:03:44 +01:00

6.7 KiB

@automaker/git-utils

Git operations and utilities for AutoMaker.

Overview

This package provides git-related utilities including repository detection, status parsing, and diff generation for both tracked and untracked files.

Installation

npm install @automaker/git-utils

Exports

Repository Detection

Check if a path is a git repository.

import { isGitRepo } from '@automaker/git-utils';

const isRepo = await isGitRepo('/project/path');
if (isRepo) {
  console.log('This is a git repository');
}

Status Parsing

Parse git status output into structured data.

import { parseGitStatus } from '@automaker/git-utils';
import type { FileStatus } from '@automaker/git-utils';

const statusOutput = await execAsync('git status --porcelain');
const files: FileStatus[] = parseGitStatus(statusOutput.stdout);

files.forEach(file => {
  console.log(`${file.statusText}: ${file.path}`);
  // Example: "Modified: src/index.ts"
  // Example: "Untracked: new-file.ts"
});

Diff Generation

Generate diffs including untracked files.

import {
  generateSyntheticDiffForNewFile,
  appendUntrackedFileDiffs,
  getGitRepositoryDiffs
} from '@automaker/git-utils';

// Generate diff for single untracked file
const diff = await generateSyntheticDiffForNewFile(
  '/project/path',
  'src/new-file.ts'
);

// Get complete repository diffs (tracked + untracked)
const result = await getGitRepositoryDiffs('/project/path');
console.log(result.diff);        // Combined diff string
console.log(result.files);       // Array of FileStatus
console.log(result.hasChanges);  // Boolean

Non-Git Directory Support

Handle non-git directories by treating all files as new.

import {
  listAllFilesInDirectory,
  generateDiffsForNonGitDirectory
} from '@automaker/git-utils';

// List all files (excluding build artifacts)
const files = await listAllFilesInDirectory('/project/path');

// Generate diffs for non-git directory
const result = await generateDiffsForNonGitDirectory('/project/path');
console.log(result.diff);   // Synthetic diffs for all files
console.log(result.files);  // All files as "New" status

Types

FileStatus

interface FileStatus {
  status: string;      // Git status code (M/A/D/R/C/U/?/!)
  path: string;        // File path relative to repo root
  statusText: string;  // Human-readable status
}

Status Codes

  • M - Modified
  • A - Added
  • D - Deleted
  • R - Renamed
  • C - Copied
  • U - Updated
  • ? - Untracked
  • ! - Ignored
  • - Unmodified

Status Text Examples

  • "Modified" - File has changes
  • "Added" - New file in staging
  • "Deleted" - File removed
  • "Renamed" - File renamed
  • "Untracked" - New file not in git
  • "Modified (staged), Modified (unstaged)" - Changes in both areas

Usage Example

import {
  isGitRepo,
  getGitRepositoryDiffs,
  parseGitStatus
} from '@automaker/git-utils';

async function getProjectChanges(projectPath: string) {
  const isRepo = await isGitRepo(projectPath);

  if (!isRepo) {
    console.log('Not a git repository, analyzing all files...');
  }

  const result = await getGitRepositoryDiffs(projectPath);

  if (!result.hasChanges) {
    console.log('No changes detected');
    return;
  }

  console.log(`Found ${result.files.length} changed files:\n`);

  // Group by status
  const byStatus = result.files.reduce((acc, file) => {
    acc[file.statusText] = acc[file.statusText] || [];
    acc[file.statusText].push(file.path);
    return acc;
  }, {} as Record<string, string[]>);

  Object.entries(byStatus).forEach(([status, paths]) => {
    console.log(`${status}:`);
    paths.forEach(path => console.log(`  - ${path}`));
  });

  return result.diff;
}

Features

Binary File Detection

Automatically detects binary files by extension and generates appropriate diff markers.

Supported binary extensions:

  • Images: .png, .jpg, .jpeg, .gif, .svg, etc.
  • Documents: .pdf, .doc, .docx, etc.
  • Archives: .zip, .tar, .gz, etc.
  • Media: .mp3, .mp4, .wav, etc.
  • Fonts: .ttf, .otf, .woff, etc.

Large File Handling

Files larger than 1MB show size information instead of full content.

Synthetic Diff Format

Generates unified diff format for untracked files:

diff --git a/new-file.ts b/new-file.ts
new file mode 100644
index 0000000..0000000
--- /dev/null
+++ b/new-file.ts
@@ -0,0 +1,10 @@
+export function hello() {
+  console.log('Hello');
+}

Directory Filtering

When scanning non-git directories, automatically excludes:

  • node_modules, .git, .automaker
  • Build outputs: dist, build, out, tmp, .tmp
  • Framework caches: .next, .nuxt, .cache, coverage
  • Language-specific: __pycache__ (Python), target (Rust), vendor (Go/PHP), .gradle (Gradle), .venv/venv (Python)

Error Handling

Git operations can fail for various reasons. This package provides graceful error handling patterns:

Common Error Scenarios

1. Repository Not Found

const isRepo = await isGitRepo('/path/does/not/exist');
// Returns: false (no exception thrown)

2. Not a Git Repository

const result = await getGitRepositoryDiffs('/not/a/git/repo');
// Fallback behavior: treats all files as "new"
// Returns synthetic diffs for all files in directory

3. Git Command Failures

// Permission errors, corrupted repos, or git not installed
try {
  const result = await getGitRepositoryDiffs('/project');
} catch (error) {
  // Handle errors from git commands
  // Errors are logged via @automaker/utils logger
  console.error('Git operation failed:', error);
}

4. File Read Errors

// When generating synthetic diffs for inaccessible files
const diff = await generateSyntheticDiffForNewFile('/path', 'locked-file.txt');
// Returns placeholder: "[Unable to read file content]"
// Error is logged but doesn't throw

Best Practices

  1. Check repository status first:

    const isRepo = await isGitRepo(path);
    if (!isRepo) {
      // Handle non-git case appropriately
    }
    
  2. Expect non-git directories:

    • getGitRepositoryDiffs() automatically handles both cases
    • Always returns a valid result structure
  3. Monitor logs:

    • Errors are logged with the [GitUtils] prefix
    • Check logs for permission issues or git configuration problems
  4. Handle edge cases:

    • Empty repositories (no commits yet)
    • Detached HEAD states
    • Corrupted git repositories
    • Missing git binary

Dependencies

  • @automaker/types - FileStatus type definition
  • @automaker/utils - Logger utilities

Used By

  • @automaker/server - Git routes, worktree operations, feature context