<commit_before>import path from 'path';
import {Emitter} from 'event-kit';
import fs from 'fs-extra';

import State from './state';

import {LargeRepoError} from '../../git-shell-out-strategy';
import {FOCUS} from '../workspace-change-observer';
import FilePatch from '../file-patch';
import Hunk from '../hunk';
import HunkLine from '../hunk-line';
import DiscardHistory from '../discard-history';
import Branch, {nullBranch} from '../branch';
import Author from '../author';
import BranchSet from '../branch-set';
import Remote from '../remote';
import RemoteSet from '../remote-set';
import Commit from '../commit';
import OperationStates from '../operation-states';
import {addEvent} from '../../reporter-proxy';

/**
 * State used when the working directory contains a valid git repository and can be interacted with. Performs
 * actual git operations, caching the results, and broadcasts `onDidUpdate` events when write actions are
 * performed.
 */
export default class Present extends State {
  constructor(repository, history) {
    super(repository);

    this.cache = new Cache();

    this.discardHistory = new DiscardHistory(
      this.createBlob.bind(this),
      this.expandBlobToFile.bind(this),
      this.mergeFile.bind(this),
      this.workdir(),
      {maxHistoryLength: 60},
    );

    this.operationStates = new OperationStates({didUpdate: this.didUpdate.bind(this)});

    this.commitMessage = '';
    this.setCommitMessageFromTemplate();

    if (history) {
      this.discardHistory.updateHistory(history);
    }
  }

  setCommitMessage(message) {
    this.commitMessage = message;
  }

  getCommitMessage() {
    return this.commitMessage;
  }

  async setCommitMessageFromTemplate() {
    const message = await this.getCommitMessageFromTemplate();
    if (!message) {
      return;
    }
    this.setCommitMessage(message);
    this.didUpdate();
  }

  async getCommitMessageFromTemplate() {
    return await this.git().getCommitMessageFromTemplate();
  }

  getOperationStates() {
    return this.operationStates;
  }

  isPresent() {
    return true;
  }

  destroy() {
    this.cache.destroy();
    super.destroy();
  }

  showStatusBarTiles() {
    return true;
  }

  acceptInvalidation(spec) {
    this.cache.invalidate(spec());
    this.didUpdate();
  }

  invalidateCacheAfterFilesystemChange(events) {
    const paths = events.map(e => e.special || e.path);
    const keys = new Set();
    for (let i = 0; i < paths.length; i++) {
      const fullPath = paths[i];

      if (fullPath === FOCUS) {
        keys.add(Keys.statusBundle);
        for (const k of Keys.filePatch.eachWithOpts({staged: false})) {
          keys.add(k);
        }
        continue;
      }

      const endsWith = (...segments) => fullPath.endsWith(path.join(...segments));
      const includes = (...segments) => fullPath.includes(path.join(...segments));

      if (endsWith('.git', 'index')) {
        keys.add(Keys.stagedChangesSinceParentCommit);
        keys.add(Keys.filePatch.all);
        keys.add(Keys.index.all);
        keys.add(Keys.statusBundle);
        continue;
      }

      if (endsWith('.git', 'HEAD')) {
        keys.add(Keys.branches);
        keys.add(Keys.lastCommit);
        keys.add(Keys.recentCommits);
        keys.add(Keys.statusBundle);
        keys.add(Keys.headDescription);
        keys.add(Keys.authors);
        continue;
      }

      if (includes('.git', 'refs', 'heads')) {
        keys.add(Keys.branches);
        keys.add(Keys.lastCommit);
        keys.add(Keys.recentCommits);
        keys.add(Keys.headDescription);
        keys.add(Keys.authors);
        continue;
      }

      if (includes('.git', 'refs', 'remotes')) {
        keys.add(Keys.remotes);
        keys.add(Keys.statusBundle);
        keys.add(Keys.headDescription);
        continue;
      }

      if (endsWith('.git', 'config')) {
        keys.add(Keys.config.all);
        keys.add(Keys.statusBundle);
        continue;
      }

      // File change within the working directory
      const relativePath = path.relative(this.workdir(), fullPath);
      keys.add(Keys.filePatch.oneWith(relativePath, {staged: false}));
      keys.add(Keys.statusBundle);
    }

    if (keys.size > 0) {
      this.cache.invalidate(Array.from(keys));
      this.didUpdate();
    }
  }

  isCommitMessageClean() {
    if (this.commitMessage === '') {
      return true;
    } else if (this.commitMessageTemplate) {
      return this.commitMessage === this.commitMessageTemplate;
    }
  }

  async updateCommitMessageAfterFileSystemChange(events) {
    for (let i = 0; i < events.length; i++) {
      const event = events[i];
      const endsWith = (...segments) => event.path.endsWith(path.join(...segments));

      if (endsWith('.git', 'MERGE_HEAD')) {
        if (event.action === 'created') {
          if (this.isCommitMessageClean()) { // is it really necessary to check if commit message is clean?
            this.setCommitMessage(await this.repository.getMergeMessage());
            // this.didUpdate();
          }
        } else if (event.action === 'deleted') {
          this.setCommitMessage(this.commitMessageTemplate || '');
          // this.didUpdate();<commit_msg>:shirt:<commit_after>import path from 'path';
import {Emitter} from 'event-kit';
import fs from 'fs-extra';

import State from './state';

import {LargeRepoError} from '../../git-shell-out-strategy';
import {FOCUS} from '../workspace-change-observer';
import FilePatch from '../file-patch';
import Hunk from '../hunk';
import HunkLine from '../hunk-line';
import DiscardHistory from '../discard-history';
import Branch, {nullBranch} from '../branch';
import Author from '../author';
import BranchSet from '../branch-set';
import Remote from '../remote';
import RemoteSet from '../remote-set';
import Commit from '../commit';
import OperationStates from '../operation-states';
import {addEvent} from '../../reporter-proxy';

/**
 * State used when the working directory contains a valid git repository and can be interacted with. Performs
 * actual git operations, caching the results, and broadcasts `onDidUpdate` events when write actions are
 * performed.
 */
export default class Present extends State {
  constructor(repository, history) {
    super(repository);

    this.cache = new Cache();

    this.discardHistory = new DiscardHistory(
      this.createBlob.bind(this),
      this.expandBlobToFile.bind(this),
      this.mergeFile.bind(this),
      this.workdir(),
      {maxHistoryLength: 60},
    );

    this.operationStates = new OperationStates({didUpdate: this.didUpdate.bind(this)});

    this.commitMessage = '';
    this.setCommitMessageFromTemplate();

    if (history) {
      this.discardHistory.updateHistory(history);
    }
  }

  setCommitMessage(message) {
    this.commitMessage = message;
  }

  getCommitMessage() {
    return this.commitMessage;
  }

  async setCommitMessageFromTemplate() {
    const message = await this.getCommitMessageFromTemplate();
    if (!message) {
      return;
    }
    this.setCommitMessage(message);
    this.didUpdate();
  }

  async getCommitMessageFromTemplate() {
    return await this.git().getCommitMessageFromTemplate();
  }

  getOperationStates() {
    return this.operationStates;
  }

  isPresent() {
    return true;
  }

  destroy() {
    this.cache.destroy();
    super.destroy();
  }

  showStatusBarTiles() {
    return true;
  }

  acceptInvalidation(spec) {
    this.cache.invalidate(spec());
    this.didUpdate();
  }

  invalidateCacheAfterFilesystemChange(events) {
    const paths = events.map(e => e.special || e.path);
    const keys = new Set();
    for (let i = 0; i < paths.length; i++) {
      const fullPath = paths[i];

      if (fullPath === FOCUS) {
        keys.add(Keys.statusBundle);
        for (const k of Keys.filePatch.eachWithOpts({staged: false})) {
          keys.add(k);
        }
        continue;
      }

      const endsWith = (...segments) => fullPath.endsWith(path.join(...segments));
      const includes = (...segments) => fullPath.includes(path.join(...segments));

      if (endsWith('.git', 'index')) {
        keys.add(Keys.stagedChangesSinceParentCommit);
        keys.add(Keys.filePatch.all);
        keys.add(Keys.index.all);
        keys.add(Keys.statusBundle);
        continue;
      }

      if (endsWith('.git', 'HEAD')) {
        keys.add(Keys.branches);
        keys.add(Keys.lastCommit);
        keys.add(Keys.recentCommits);
        keys.add(Keys.statusBundle);
        keys.add(Keys.headDescription);
        keys.add(Keys.authors);
        continue;
      }

      if (includes('.git', 'refs', 'heads')) {
        keys.add(Keys.branches);
        keys.add(Keys.lastCommit);
        keys.add(Keys.recentCommits);
        keys.add(Keys.headDescription);
        keys.add(Keys.authors);
        continue;
      }

      if (includes('.git', 'refs', 'remotes')) {
        keys.add(Keys.remotes);
        keys.add(Keys.statusBundle);
        keys.add(Keys.headDescription);
        continue;
      }

      if (endsWith('.git', 'config')) {
        keys.add(Keys.config.all);
        keys.add(Keys.statusBundle);
        continue;
      }

      // File change within the working directory
      const relativePath = path.relative(this.workdir(), fullPath);
      keys.add(Keys.filePatch.oneWith(relativePath, {staged: false}));
      keys.add(Keys.statusBundle);
    }

    if (keys.size > 0) {
      this.cache.invalidate(Array.from(keys));
      this.didUpdate();
    }
  }

  isCommitMessageClean() {
    if (this.commitMessage === '') {
      return true;
    } else if (this.commitMessageTemplate) {
      return this.commitMessage === this.commitMessageTemplate;
    }
    return false;
  }

  async updateCommitMessageAfterFileSystemChange(events) {
    for (let i = 0; i < events.length; i++) {
      const event = events[i];
      const endsWith = (...segments) => event.path.endsWith(path.join(...segments));

      if (endsWith('.git', 'MERGE_HEAD')) {
        if (event.action === 'created') {
          if (this.isCommitMessageClean()) { // is it really necessary to check if commit message is clean?
            this.setCommitMessage(await this.repository.getMergeMessage());
            // this.didUpdate();
          }
        } else if (event.action === 'deleted') {
          this.setCommitMessage(this.commitMessageTemplate || '');
          // this.didUpdate();
{'fraction_non_alphanumeric': 0.1460576923076923, 'fraction_numerical': 0.0009615384615384616, 'mean_word_length': 2.7454087144400434, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 13, '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': '236724', 'n_tokens_mistral': 3099, 'n_tokens_neox': 3060, 'n_words': 657}