Files
automaker/libs/git-utils
Kacper c9e0957dfe feat(diff): add helper function to create synthetic diffs for new files
This update introduces a new function, createNewFileDiff, to streamline the generation of synthetic diffs for untracked files. The function reduces code duplication by handling the diff formatting for new files, including directories and large files, improving overall maintainability.
2025-12-23 18:39:43 +01:00
..
2025-12-23 18:26:02 +01:00

@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