<commit_before>const {
  Repository,
  Cred,
  Clone
} = require('nodegit');

const crypto = require('crypto');
const Change = require('./change');
const os = require('os');
const process = require('process');
const Error = require('@cardstack/plugin-utils/error');
const PendingChange = require('@cardstack/plugin-utils/pending-change');
const { promisify } = require('util');
const temp = require('temp').track();

const mkdir = promisify(temp.mkdir);

const pendingChanges = new WeakMap();

module.exports = class Writer {
  static create(params) {
    return new this(params);
  }
  constructor({ repo, idGenerator, basePath, branchPrefix, remote }) {
    this.repoPath = repo;
    this.basePath = basePath;
    this.branchPrefix = branchPrefix || "";
    this.repo = null;
    let hostname = os.hostname();
    this.myName = `PID${process.pid} on ${hostname}`;
    this.myEmail = `${os.userInfo().username}@${hostname}`;
    this.idGenerator = idGenerator;
    this.remote = remote;

    if(remote) {
      this.fetchOpts = {
        callbacks: {
          credentials: (url, userName) => {
            if (remote && remote.privateKey) {
              return Cred.sshKeyMemoryNew(userName, remote.publicKey || '', remote.privateKey, remote.passphrase || '');
            }
            return Cred.sshKeyFromAgent(userName);
          }
        }
      };
    }
  }

  async prepareCreate(branch, session, type, document, isSchema) {
    return withErrorHandling(document.id, type, async () => {
      await this._ensureRepo();
      let change = await Change.create(this.repo, null, this.branchPrefix + branch, this.fetchOpts);

      let id = document.id;
      let file;
      while (id == null) {
        let candidateId = this._generateId();
        let candidateFile = await change.get(this._filenameFor(document.type, candidateId, isSchema), { allowCreate: true });
        if (!candidateFile.exists()) {
          id = candidateId;
          file = candidateFile;
        }
      }

      if (!file) {
        file = await change.get(this._filenameFor(document.type, id, isSchema), { allowCreate: true });
      }

      let gitDocument = { id, type: document.type };
      if (document.attributes) {
        gitDocument.attributes = document.attributes;
      }
      if (document.relationships) {
        gitDocument.relationships = document.relationships;
      }

      let pending = new PendingChange(null, gitDocument, finalizer.bind(this));
      let signature = await this._commitOptions('create', document.type, id, session);
      pendingChanges.set(pending, { type: document.type, id, signature, change, file });
      return pending;
    });
  }

  async prepareUpdate(branch, session, type, id, document, isSchema) {
    if (!document.meta || !document.meta.version) {
      throw new Error('missing required field "meta.version"', {
        status: 400,
        source: { pointer: '/data/meta/version' }
      });
    }

    await this._ensureRepo();
    return withErrorHandling(id, type, async () => {
      let change = await Change.create(this.repo, document.meta.version, this.branchPrefix + branch, this.fetchOpts);

      let file = await change.get(this._filenameFor(type, id, isSchema), { allowUpdate: true });
      let before = JSON.parse(await file.getBuffer());
      let after = patch(before, document);
      // we don't write id & type into the actual file (they're part
      // of the filename). But we want them present on the
      // PendingChange as complete valid documents.
      before.id = id;
      before.type = type;
      after.id = document.id;
      after.type = document.type;
      let signature = await this._commitOptions('update', type, id, session);
      let pending = new PendingChange(before, after, finalizer.bind(this));
      pendingChanges.set(pending, { type, id, signature, change, file });
      return pending;
    });
  }

  async prepareDelete(branch, session, version, type, id, isSchema) {
    if (!version) {
      throw new Error('version is required', {
        status: 400,
        source: { pointer: '/data/meta/version' }
      });
    }
    await this._ensureRepo();
    return withErrorHandling(id, type, async () => {
      let change = await Change.create(this.repo, version, this.branchPrefix + branch, this.fetchOpts);

      let file = await change.get(this._filenameFor(type, id, isSchema));
      let before = JSON.parse(await file.getBuffer());
      file.delete();
      before.id = id;
      before.type = type;
      let pending = new PendingChange(before, null, finalizer.bind(this));
      let signature = await this._commitOptions('delete', type, id, session);
      pendingChanges.set(pending, { type, id, signature, change });
      return pending;
    });
  }

  async _commitOptions(operation, type, id, session) {
    let user = session && await session.loadUser();
    let userAttributes = {};
    try {
      userAttributes = user.data.attributes;
    } catch (e) {
      // do nothing
    }

    return {
      authorName: userAttributes['full-name'] || userAttributes.name || '<NAME>',
      authorEmail: userAttributes.email || '<EMAIL>',
      committerName: this.myName,
      committerEmail: this.myEmail,
      message: `${operation} ${type} ${String(id).slice(12)}`
    };
  }

  _filenameFor(type, id, isSchema) {
    let category = isSchema ? 'schema' : 'contents';
    let base = this.basePath ? this.basePath + '/' : '';
    return `${base}${category}/${type}/${id}.json`;
  }

  async _ensureRepo() {
    if (!this.repo) {
      if (this.remote) {
        let tempRepoPath = await mkdir('cardstack-temp-repo');
        this.repo = await Clone(this.remote.url, tempRepoPath, {
          fetchOpts: this.fetchOpts,
        });
        return;
      }

      this.repo = await Repository.open(this.repoPath);
    }
  }<commit_msg>removing try-catch<commit_after>const {
  Repository,
  Cred,
  Clone
} = require('nodegit');

const crypto = require('crypto');
const Change = require('./change');
const os = require('os');
const process = require('process');
const Error = require('@cardstack/plugin-utils/error');
const PendingChange = require('@cardstack/plugin-utils/pending-change');
const { promisify } = require('util');
const temp = require('temp').track();

const mkdir = promisify(temp.mkdir);

const pendingChanges = new WeakMap();

module.exports = class Writer {
  static create(params) {
    return new this(params);
  }
  constructor({ repo, idGenerator, basePath, branchPrefix, remote }) {
    this.repoPath = repo;
    this.basePath = basePath;
    this.branchPrefix = branchPrefix || "";
    this.repo = null;
    let hostname = os.hostname();
    this.myName = `PID${process.pid} on ${hostname}`;
    this.myEmail = `${os.userInfo().username}@${hostname}`;
    this.idGenerator = idGenerator;
    this.remote = remote;

    if(remote) {
      this.fetchOpts = {
        callbacks: {
          credentials: (url, userName) => {
            if (remote && remote.privateKey) {
              return Cred.sshKeyMemoryNew(userName, remote.publicKey || '', remote.privateKey, remote.passphrase || '');
            }
            return Cred.sshKeyFromAgent(userName);
          }
        }
      };
    }
  }

  async prepareCreate(branch, session, type, document, isSchema) {
    return withErrorHandling(document.id, type, async () => {
      await this._ensureRepo();
      let change = await Change.create(this.repo, null, this.branchPrefix + branch, this.fetchOpts);

      let id = document.id;
      let file;
      while (id == null) {
        let candidateId = this._generateId();
        let candidateFile = await change.get(this._filenameFor(document.type, candidateId, isSchema), { allowCreate: true });
        if (!candidateFile.exists()) {
          id = candidateId;
          file = candidateFile;
        }
      }

      if (!file) {
        file = await change.get(this._filenameFor(document.type, id, isSchema), { allowCreate: true });
      }

      let gitDocument = { id, type: document.type };
      if (document.attributes) {
        gitDocument.attributes = document.attributes;
      }
      if (document.relationships) {
        gitDocument.relationships = document.relationships;
      }

      let pending = new PendingChange(null, gitDocument, finalizer.bind(this));
      let signature = await this._commitOptions('create', document.type, id, session);
      pendingChanges.set(pending, { type: document.type, id, signature, change, file });
      return pending;
    });
  }

  async prepareUpdate(branch, session, type, id, document, isSchema) {
    if (!document.meta || !document.meta.version) {
      throw new Error('missing required field "meta.version"', {
        status: 400,
        source: { pointer: '/data/meta/version' }
      });
    }

    await this._ensureRepo();
    return withErrorHandling(id, type, async () => {
      let change = await Change.create(this.repo, document.meta.version, this.branchPrefix + branch, this.fetchOpts);

      let file = await change.get(this._filenameFor(type, id, isSchema), { allowUpdate: true });
      let before = JSON.parse(await file.getBuffer());
      let after = patch(before, document);
      // we don't write id & type into the actual file (they're part
      // of the filename). But we want them present on the
      // PendingChange as complete valid documents.
      before.id = id;
      before.type = type;
      after.id = document.id;
      after.type = document.type;
      let signature = await this._commitOptions('update', type, id, session);
      let pending = new PendingChange(before, after, finalizer.bind(this));
      pendingChanges.set(pending, { type, id, signature, change, file });
      return pending;
    });
  }

  async prepareDelete(branch, session, version, type, id, isSchema) {
    if (!version) {
      throw new Error('version is required', {
        status: 400,
        source: { pointer: '/data/meta/version' }
      });
    }
    await this._ensureRepo();
    return withErrorHandling(id, type, async () => {
      let change = await Change.create(this.repo, version, this.branchPrefix + branch, this.fetchOpts);

      let file = await change.get(this._filenameFor(type, id, isSchema));
      let before = JSON.parse(await file.getBuffer());
      file.delete();
      before.id = id;
      before.type = type;
      let pending = new PendingChange(before, null, finalizer.bind(this));
      let signature = await this._commitOptions('delete', type, id, session);
      pendingChanges.set(pending, { type, id, signature, change });
      return pending;
    });
  }

  async _commitOptions(operation, type, id, session) {
    let user = session && await session.loadUser();
    let userAttributes = (user && user.data && user.data.attributes) || {};

    return {
      authorName: userAttributes['full-name'] || userAttributes.name || '<NAME>',
      authorEmail: userAttributes.email || '<EMAIL>',
      committerName: this.myName,
      committerEmail: this.myEmail,
      message: `${operation} ${type} ${String(id).slice(12)}`
    };
  }

  _filenameFor(type, id, isSchema) {
    let category = isSchema ? 'schema' : 'contents';
    let base = this.basePath ? this.basePath + '/' : '';
    return `${base}${category}/${type}/${id}.json`;
  }

  async _ensureRepo() {
    if (!this.repo) {
      if (this.remote) {
        let tempRepoPath = await mkdir('cardstack-temp-repo');
        this.repo = await Clone(this.remote.url, tempRepoPath, {
          fetchOpts: this.fetchOpts,
        });
        return;
      }

      this.repo = await Repository.open(this.repoPath);
    }
  }
{'fraction_non_alphanumeric': 0.13923941969267747, 'fraction_numerical': 0.00137350845566143, 'mean_word_length': 2.8588936734017887, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 15, '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': '3588316', 'n_tokens_mistral': 3398, 'n_tokens_neox': 3378, 'n_words': 835}