<commit_before>'use babel'

import fs from 'fs-plus'
import Git from 'nodegit'
import path from 'path'
import {Emitter, CompositeDisposable} from 'event-kit'

const modifiedStatusFlags = Git.Status.STATUS.WT_MODIFIED | Git.Status.STATUS.INDEX_MODIFIED | Git.Status.STATUS.WT_DELETED | Git.Status.STATUS.INDEX_DELETED | Git.Status.STATUS.WT_TYPECHANGE | Git.Status.STATUS.INDEX_TYPECHANGE
const newStatusFlags = Git.Status.STATUS.WT_NEW | Git.Status.STATUS.INDEX_NEW
const deletedStatusFlags = Git.Status.STATUS.WT_DELETED | Git.Status.STATUS.INDEX_DELETED
const indexStatusFlags = Git.Status.STATUS.INDEX_NEW | Git.Status.STATUS.INDEX_MODIFIED | Git.Status.STATUS.INDEX_DELETED | Git.Status.STATUS.INDEX_RENAMED | Git.Status.STATUS.INDEX_TYPECHANGE

// Just using this for _.isEqual and _.object, we should impl our own here
import _ from 'underscore-plus'

module.exports = class GitRepositoryAsync {
  static open (path, options = {}) {
    // QUESTION: Should this wrap Git.Repository and reject with a nicer message?
    return new GitRepositoryAsync(path, options)
  }

  static get Git () {
    return Git
  }

  constructor (path, options) {
    this.repo = null
    this.emitter = new Emitter()
    this.subscriptions = new CompositeDisposable()
    this.pathStatusCache = {}
    this.repoPromise = Git.Repository.open(path)
    this.isCaseInsensitive = fs.isCaseInsensitive()

    const {project} = options
    this.project = project

    if (this.project) {
      this.subscriptions.add(this.project.onDidAddBuffer(buffer => {
        this.subscribeToBuffer(buffer)
      }))

      this.project.getBuffers().forEach(buffer => { this.subscribeToBuffer(buffer) })
    }
  }

  destroy () {
    if (this.emitter) {
      this.emitter.emit('did-destroy')
      this.emitter.dispose()
      this.emitter = null
    }
    if (this.subscriptions) {
      this.subscriptions.dispose()
      this.subscriptions = null
    }
  }

  getPath () {
    return this.repoPromise.then(repo => repo.path().replace(/\/$/, ''))
  }

  isPathIgnored (_path) {
    return this.repoPromise.then(repo => Git.Ignore.pathIsIgnored(repo, _path))
  }

  isPathModified (_path) {
    return this._filterStatusesByPath(_path).then(statuses => {
      return statuses.filter(status => status.isModified()).length > 0
    })
  }

  isPathNew (_path) {
    return this._filterStatusesByPath(_path).then(statuses => {
      return statuses.filter(status => status.isNew()).length > 0
    })
  }

  checkoutHead (_path) {
    return this.repoPromise
      .then(repo => {
        let checkoutOptions = new Git.CheckoutOptions()
        checkoutOptions.paths = [this.relativize(_path, repo.workdir())]
        checkoutOptions.checkoutStrategy = Git.Checkout.STRATEGY.FORCE | Git.Checkout.STRATEGY.DISABLE_PATHSPEC_MATCH
        return Git.Checkout.head(repo, checkoutOptions)
      })
      .then(() => this.getPathStatus(_path))
  }

  checkoutHeadForEditor (editor) {
    return new Promise((resolve, reject) => {
      const filePath = editor.getPath()
      if (filePath) {
        if (editor.buffer.isModified()) {
          editor.buffer.reload()
        }
        resolve(filePath)
      } else {
        reject()
      }
    }).then(filePath => this.checkoutHead(filePath))
  }

  // Returns a Promise that resolves to the status bit of a given path if it has
  // one, otherwise 'current'.
  getPathStatus (_path) {
    let relativePath
    return this.repoPromise
      .then(repo => {
        relativePath = this.relativize(_path, repo.workdir())
        return this._filterStatusesByPath(_path)
      })
      .then(statuses => {
        const cachedStatus = this.pathStatusCache[relativePath] || 0
        const status = statuses[0] ? statuses[0].statusBit() : Git.Status.STATUS.CURRENT
        if (status !== cachedStatus && this.emitter != null) {
          this.emitter.emit('did-change-status', {path: _path, pathStatus: status})
        }
        this.pathStatusCache[relativePath] = status
        return status
      })
  }

  // Get the status of a directory in the repository's working directory.
  //
  // * `directoryPath` The {String} path to check.
  //
  // Returns a promise resolving to a {Number} representing the status. This value can be passed to
  // {::isStatusModified} or {::isStatusNew} to get more information.

  getDirectoryStatus (directoryPath) {
    let relativePath
    // XXX _filterSBD already gets repoPromise
    return this.repoPromise
      .then(repo => {
        relativePath = this.relativize(directoryPath, repo.workdir())
        return this._filterStatusesByDirectory(relativePath)
      })
      .then(statuses => {
        return Promise.all(statuses.map(s => s.statusBit())).then(bits => {
          let directoryStatus = 0
          const filteredBits = bits.filter(b => b > 0)
          if (filteredBits.length > 0) {
            filteredBits.forEach(bit => directoryStatus |= bit)
          }

          return directoryStatus
        })
      })
  }

  // Get the current branch and update this.branch.
  //
  // Returns :: Promise<String>
  //            The branch name.
  _refreshBranch () {
    return this.repoPromise
      .then(repo => repo.getCurrentBranch())
      .then(ref => ref.name())
      .then(branchRef => this.branch = branchRef)
  }

  // Refreshes the git status. Note: the sync GitRepository class does this with
  // a separate process, let's see if we can avoid that.
  refreshStatus () {
    // TODO add upstream, branch, and submodule tracking
    const status = this.repoPromise
      .then(repo => repo.getStatus())
      .then(statuses => {
        // update the status cache
        return Promise.all(statuses.map(status => [status.path(), status.statusBit()]))
          .then(statusesByPath => _.object(statusesByPath))
      })
      .then(newPathStatusCache => {
        if (!_.isEqual(this.pathStatusCache, newPathStatusCache) && this.emitter != null) {
          this.emitter.emit('did-change-statuses')
        }
        this.pathStatusCache = newPathStatusCache
        return newPathStatusCache
      })

    const branch = this._refreshBranch()

    return Promise.all([status, branch])
  }

  // Section: Private
  // ================

  subscribeToBuffer (buffer) {
    const bufferSubscriptions = new CompositeDisposable()

    let getBufferPathStatus = () => {
      let _path = buffer.getPath()
      if (_path) {
        // We don't need to do anything with this promise, we just want the<commit_msg>These can be const too.<commit_after>'use babel'

import fs from 'fs-plus'
import Git from 'nodegit'
import path from 'path'
import {Emitter, CompositeDisposable} from 'event-kit'

const modifiedStatusFlags = Git.Status.STATUS.WT_MODIFIED | Git.Status.STATUS.INDEX_MODIFIED | Git.Status.STATUS.WT_DELETED | Git.Status.STATUS.INDEX_DELETED | Git.Status.STATUS.WT_TYPECHANGE | Git.Status.STATUS.INDEX_TYPECHANGE
const newStatusFlags = Git.Status.STATUS.WT_NEW | Git.Status.STATUS.INDEX_NEW
const deletedStatusFlags = Git.Status.STATUS.WT_DELETED | Git.Status.STATUS.INDEX_DELETED
const indexStatusFlags = Git.Status.STATUS.INDEX_NEW | Git.Status.STATUS.INDEX_MODIFIED | Git.Status.STATUS.INDEX_DELETED | Git.Status.STATUS.INDEX_RENAMED | Git.Status.STATUS.INDEX_TYPECHANGE

// Just using this for _.isEqual and _.object, we should impl our own here
import _ from 'underscore-plus'

module.exports = class GitRepositoryAsync {
  static open (path, options = {}) {
    // QUESTION: Should this wrap Git.Repository and reject with a nicer message?
    return new GitRepositoryAsync(path, options)
  }

  static get Git () {
    return Git
  }

  constructor (path, options) {
    this.repo = null
    this.emitter = new Emitter()
    this.subscriptions = new CompositeDisposable()
    this.pathStatusCache = {}
    this.repoPromise = Git.Repository.open(path)
    this.isCaseInsensitive = fs.isCaseInsensitive()

    const {project} = options
    this.project = project

    if (this.project) {
      this.subscriptions.add(this.project.onDidAddBuffer(buffer => {
        this.subscribeToBuffer(buffer)
      }))

      this.project.getBuffers().forEach(buffer => { this.subscribeToBuffer(buffer) })
    }
  }

  destroy () {
    if (this.emitter) {
      this.emitter.emit('did-destroy')
      this.emitter.dispose()
      this.emitter = null
    }
    if (this.subscriptions) {
      this.subscriptions.dispose()
      this.subscriptions = null
    }
  }

  getPath () {
    return this.repoPromise.then(repo => repo.path().replace(/\/$/, ''))
  }

  isPathIgnored (_path) {
    return this.repoPromise.then(repo => Git.Ignore.pathIsIgnored(repo, _path))
  }

  isPathModified (_path) {
    return this._filterStatusesByPath(_path).then(statuses => {
      return statuses.filter(status => status.isModified()).length > 0
    })
  }

  isPathNew (_path) {
    return this._filterStatusesByPath(_path).then(statuses => {
      return statuses.filter(status => status.isNew()).length > 0
    })
  }

  checkoutHead (_path) {
    return this.repoPromise
      .then(repo => {
        const checkoutOptions = new Git.CheckoutOptions()
        checkoutOptions.paths = [this.relativize(_path, repo.workdir())]
        checkoutOptions.checkoutStrategy = Git.Checkout.STRATEGY.FORCE | Git.Checkout.STRATEGY.DISABLE_PATHSPEC_MATCH
        return Git.Checkout.head(repo, checkoutOptions)
      })
      .then(() => this.getPathStatus(_path))
  }

  checkoutHeadForEditor (editor) {
    return new Promise((resolve, reject) => {
      const filePath = editor.getPath()
      if (filePath) {
        if (editor.buffer.isModified()) {
          editor.buffer.reload()
        }
        resolve(filePath)
      } else {
        reject()
      }
    }).then(filePath => this.checkoutHead(filePath))
  }

  // Returns a Promise that resolves to the status bit of a given path if it has
  // one, otherwise 'current'.
  getPathStatus (_path) {
    let relativePath
    return this.repoPromise
      .then(repo => {
        relativePath = this.relativize(_path, repo.workdir())
        return this._filterStatusesByPath(_path)
      })
      .then(statuses => {
        const cachedStatus = this.pathStatusCache[relativePath] || 0
        const status = statuses[0] ? statuses[0].statusBit() : Git.Status.STATUS.CURRENT
        if (status !== cachedStatus && this.emitter != null) {
          this.emitter.emit('did-change-status', {path: _path, pathStatus: status})
        }
        this.pathStatusCache[relativePath] = status
        return status
      })
  }

  // Get the status of a directory in the repository's working directory.
  //
  // * `directoryPath` The {String} path to check.
  //
  // Returns a promise resolving to a {Number} representing the status. This value can be passed to
  // {::isStatusModified} or {::isStatusNew} to get more information.

  getDirectoryStatus (directoryPath) {
    let relativePath
    // XXX _filterSBD already gets repoPromise
    return this.repoPromise
      .then(repo => {
        relativePath = this.relativize(directoryPath, repo.workdir())
        return this._filterStatusesByDirectory(relativePath)
      })
      .then(statuses => {
        return Promise.all(statuses.map(s => s.statusBit())).then(bits => {
          let directoryStatus = 0
          const filteredBits = bits.filter(b => b > 0)
          if (filteredBits.length > 0) {
            filteredBits.forEach(bit => directoryStatus |= bit)
          }

          return directoryStatus
        })
      })
  }

  // Get the current branch and update this.branch.
  //
  // Returns :: Promise<String>
  //            The branch name.
  _refreshBranch () {
    return this.repoPromise
      .then(repo => repo.getCurrentBranch())
      .then(ref => ref.name())
      .then(branchRef => this.branch = branchRef)
  }

  // Refreshes the git status. Note: the sync GitRepository class does this with
  // a separate process, let's see if we can avoid that.
  refreshStatus () {
    // TODO add upstream, branch, and submodule tracking
    const status = this.repoPromise
      .then(repo => repo.getStatus())
      .then(statuses => {
        // update the status cache
        return Promise.all(statuses.map(status => [status.path(), status.statusBit()]))
          .then(statusesByPath => _.object(statusesByPath))
      })
      .then(newPathStatusCache => {
        if (!_.isEqual(this.pathStatusCache, newPathStatusCache) && this.emitter != null) {
          this.emitter.emit('did-change-statuses')
        }
        this.pathStatusCache = newPathStatusCache
        return newPathStatusCache
      })

    const branch = this._refreshBranch()

    return Promise.all([status, branch])
  }

  // Section: Private
  // ================

  subscribeToBuffer (buffer) {
    const bufferSubscriptions = new CompositeDisposable()

    const getBufferPathStatus = () => {
      const _path = buffer.getPath()
      if (_path) {
        // We don't need to do anything with this promise, we just want the
{'fraction_non_alphanumeric': 0.11957191253464737, 'fraction_numerical': 0.0012319063751154912, 'mean_word_length': 2.8383569739952716, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 71, 'https://': 0, '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': '2200190', 'n_tokens_mistral': 3887, 'n_tokens_neox': 3790, 'n_words': 871}