<commit_before>import * as path from 'path'
import * as cp from 'child_process'

const gitNotFoundErrorCode: number = 128

export enum GitErrorCode {
  NotFound
}

/**
 * Encapsulate the error from Git for callers to handle
 */
export class GitError extends Error {
  /**
   * The error code returned from the Git process
   */
  public readonly errorCode: GitErrorCode

  /**
   * The error text returned from the Git process
   */
  public readonly errorOutput: string

  public constructor(errorCode: number, errorOutput: string) {
    super()

    this.errorCode = errorCode
    this.errorOutput = errorOutput
  }
}

export class GitProcess {
  /**
   *  Find the path to the embedded Git environment
   */
  private static resolveGit(): string {
    if (process.env.TEST_ENV) {
      if (process.platform === 'darwin') {
        return path.join(__dirname, '..', '..', '..', 'git', 'git-macos', 'git', 'bin', 'git')
      } else if (process.platform === 'win32') {
        return path.join(__dirname, '..', '..', '..', 'git', 'git-win32', 'git', 'cmd', 'git.exe')
      }
    } else {
      if (process.platform === 'darwin') {
        return path.join(__dirname, 'git/bin/git')
      } else if (process.platform === 'win32') {
        return path.join(__dirname, 'git/cmd/git.exe')
      }
    }

    throw new Error('Git not supported on platform: ' + process.platform)
  }

  /**
   *  Execute a command using the embedded Git environment
   */
  public static exec(args: string[], path: string): Promise<void> {
    return new Promise(function(resolve, reject) {
      const gitLocation = GitProcess.resolveGit()
      const formatArgs = 'executing: git ' + args.join(' ')

      cp.execFile(gitLocation, args, { cwd: path, encoding: 'utf8' }, function(err, output, stdErr) {
        if (err) {
          console.error(formatArgs)
          console.error(err)
          reject(err)
          return
        }

        console.debug(formatArgs)
        resolve()
      })
    })
  }

  /**
   *  Execute a command and read the output using the embedded Git environment
   */
  public static execWithOutput(args: string[], path: string): Promise<string> {
    return new Promise<string>(function(resolve, reject) {
      const gitLocation = GitProcess.resolveGit()
      const formatArgs = 'executing: git ' + args.join(' ')

      cp.execFile(gitLocation, args, { cwd: path, encoding: 'utf8' }, function(err, output, stdErr) {
        if (!err) {
          console.debug(formatArgs)
          resolve(output)
          return
        }

        if ((err as any).code) {
          // TODO: handle more error codes
          const code: number = (err as any).code
          if (code === gitNotFoundErrorCode) {
            reject(new GitError(GitErrorCode.NotFound, stdErr))
            return
          }
        }

        console.error(formatArgs)
        console.error(err)
        reject(err)
      })
    })
  }
}<commit_msg>workaround for 'git diff' returning non-zero exit codes<commit_after>import * as path from 'path'
import * as cp from 'child_process'

const gitNotFoundErrorCode: number = 128
const gitChangesExistErrorCode: number = 1

export enum GitErrorCode {
  NotFound
}

/**
 * Encapsulate the error from Git for callers to handle
 */
export class GitError extends Error {
  /**
   * The error code returned from the Git process
   */
  public readonly errorCode: GitErrorCode

  /**
   * The error text returned from the Git process
   */
  public readonly errorOutput: string

  public constructor(errorCode: number, errorOutput: string) {
    super()

    this.errorCode = errorCode
    this.errorOutput = errorOutput
  }
}

export class GitProcess {
  /**
   *  Find the path to the embedded Git environment
   */
  private static resolveGit(): string {
    if (process.env.TEST_ENV) {
      if (process.platform === 'darwin') {
        return path.join(__dirname, '..', '..', '..', 'git', 'git-macos', 'git', 'bin', 'git')
      } else if (process.platform === 'win32') {
        return path.join(__dirname, '..', '..', '..', 'git', 'git-win32', 'git', 'cmd', 'git.exe')
      }
    } else {
      if (process.platform === 'darwin') {
        return path.join(__dirname, 'git/bin/git')
      } else if (process.platform === 'win32') {
        return path.join(__dirname, 'git/cmd/git.exe')
      }
    }

    throw new Error('Git not supported on platform: ' + process.platform)
  }

  /**
   *  Execute a command using the embedded Git environment
   */
  public static exec(args: string[], path: string): Promise<void> {
    return new Promise(function(resolve, reject) {
      const gitLocation = GitProcess.resolveGit()
      const formatArgs = 'executing: git ' + args.join(' ')

      cp.execFile(gitLocation, args, { cwd: path, encoding: 'utf8' }, function(err, output, stdErr) {
        if (err) {
          console.error(formatArgs)
          console.error(err)
          reject(err)
          return
        }

        console.debug(formatArgs)
        resolve()
      })
    })
  }

  /**
   *  Execute a command and read the output using the embedded Git environment
   */
  public static execWithOutput(args: string[], path: string): Promise<string> {
    return new Promise<string>(function(resolve, reject) {
      const gitLocation = GitProcess.resolveGit()
      const formatArgs = 'executing: git ' + args.join(' ')

      cp.execFile(gitLocation, args, { cwd: path, encoding: 'utf8' }, function(err, output, stdErr) {
        if (!err) {
          console.debug(formatArgs)
          resolve(output)
          return
        }

        if ((err as any).code) {
          // TODO: handle more error codes
          const code: number = (err as any).code
          if (code === gitNotFoundErrorCode) {
            reject(new GitError(GitErrorCode.NotFound, stdErr))
            return
          }

          if (code === gitChangesExistErrorCode && output !== '') {
            // `git diff` seems to emulate the exit codes from `diff`
            // irrespective of whether you set --exit-code
            //
            // this is the behaviour:
            // - 0 if no changes found
            // - 1 if changes found
            // -   and error otherwise
            //
            // citation in source:
            // https://github.com/git/git/blob/1f66975deb8402131fbf7c14330d0c7cdebaeaa2/diff-no-index.c#L300
            console.debug(formatArgs)
            resolve(output)
          }
        }

        console.error(formatArgs)
        console.error(err)
        reject(err)
      })
    })
  }
}
{'fraction_non_alphanumeric': 0.1272671848803536, 'fraction_numerical': 0.007620789513793629, 'mean_word_length': 2.284284284284284, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2456751', 'n_tokens_mistral': 1944, 'n_tokens_neox': 1884, 'n_words': 533}