@sun-asterisk/sunlint
Version: 
☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards
171 lines (148 loc) • 5.21 kB
JavaScript
/**
 * Git Utils - Handle git operations for file filtering
 * Following Rule C005: Single responsibility - only handle git operations
 * Following Rule C014: Dependency injection for git operations
 */
const { execSync } = require('child_process');
const path = require('path');
const fs = require('fs');
class GitUtils {
  /**
   * Check if current directory is a git repository
   */
  static isGitRepository(cwd = process.cwd()) {
    try {
      execSync('git rev-parse --git-dir', { cwd, stdio: 'ignore' });
      return true;
    } catch (error) {
      return false;
    }
  }
  /**
   * Get list of changed files compared to base reference
   * @param {string} baseRef - Base git reference (e.g., 'origin/main')
   * @param {string} cwd - Working directory
   * @returns {string[]} Array of changed file paths
   */
  static getChangedFiles(baseRef = 'HEAD', cwd = process.cwd()) {
    if (!this.isGitRepository(cwd)) {
      throw new Error('Not a git repository');
    }
    try {
      // Get git root directory
      const gitRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf8' }).trim();
      
      const command = `git diff --name-only ${baseRef}`;
      const output = execSync(command, { cwd: gitRoot, encoding: 'utf8' });
      
      return output
        .split('\n')
        .filter(file => file.trim() !== '')
        .map(file => path.resolve(gitRoot, file))
        .filter(file => fs.existsSync(file)); // Only existing files
    } catch (error) {
      throw new Error(`Failed to get changed files: ${error.message}`);
    }
  }
  /**
   * Get list of staged files
   * @param {string} cwd - Working directory
   * @returns {string[]} Array of staged file paths
   */
  static getStagedFiles(cwd = process.cwd()) {
    if (!this.isGitRepository(cwd)) {
      throw new Error('Not a git repository');
    }
    try {
      // Get git root directory
      const gitRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf8' }).trim();
      
      const command = 'git diff --cached --name-only';
      const output = execSync(command, { cwd: gitRoot, encoding: 'utf8' });
      
      return output
        .split('\n')
        .filter(file => file.trim() !== '')
        .map(file => path.resolve(gitRoot, file))
        .filter(file => fs.existsSync(file));
    } catch (error) {
      throw new Error(`Failed to get staged files: ${error.message}`);
    }
  }
  /**
   * Get files changed since specific commit
   * @param {string} commit - Commit hash or reference
   * @param {string} cwd - Working directory
   * @returns {string[]} Array of changed file paths
   */
  static getFilesSince(commit, cwd = process.cwd()) {
    if (!this.isGitRepository(cwd)) {
      throw new Error('Not a git repository');
    }
    try {
      // Get git root directory
      const gitRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf8' }).trim();
      
      const command = `git diff --name-only ${commit}..HEAD`;
      const output = execSync(command, { cwd: gitRoot, encoding: 'utf8' });
      
      return output
        .split('\n')
        .filter(file => file.trim() !== '')
        .map(file => path.resolve(gitRoot, file))
        .filter(file => fs.existsSync(file));
    } catch (error) {
      throw new Error(`Failed to get files since ${commit}: ${error.message}`);
    }
  }
  /**
   * Get current branch name
   * @param {string} cwd - Working directory
   * @returns {string} Current branch name
   */
  static getCurrentBranch(cwd = process.cwd()) {
    if (!this.isGitRepository(cwd)) {
      throw new Error('Not a git repository');
    }
    try {
      const command = 'git rev-parse --abbrev-ref HEAD';
      const output = execSync(command, { cwd, encoding: 'utf8' });
      return output.trim();
    } catch (error) {
      throw new Error(`Failed to get current branch: ${error.message}`);
    }
  }
  /**
   * Filter TypeScript/JavaScript files from file list
   * @param {string[]} files - Array of file paths
   * @returns {string[]} Filtered TypeScript/JavaScript files
   */
  static filterSourceFiles(files) {
    const extensions = ['.ts', '.tsx', '.js', '.jsx'];
    return files.filter(file => {
      const ext = path.extname(file);
      return extensions.includes(ext);
    });
  }
  /**
   * Get git diff base reference for PR mode
   * @param {string} targetBranch - Target branch (e.g., 'main', 'develop')
   * @param {string} cwd - Working directory
   * @returns {string} Git reference for comparison
   */
  static getPRDiffBase(targetBranch = 'main', cwd = process.cwd()) {
    if (!this.isGitRepository(cwd)) {
      throw new Error('Not a git repository');
    }
    // Try common remote references
    const candidates = [
      `origin/${targetBranch}`,
      `upstream/${targetBranch}`,
      targetBranch
    ];
    for (const candidate of candidates) {
      try {
        execSync(`git rev-parse --verify ${candidate}`, { cwd, stdio: 'ignore' });
        return candidate;
      } catch (error) {
        // Continue to next candidate
      }
    }
    throw new Error(`No valid git reference found for target branch: ${targetBranch}`);
  }
}
module.exports = GitUtils;