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

const logger = require('heimdalljs-logger');
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 pendingChanges = new WeakMap();

module.exports = class Writer {
  constructor({ repo, idGenerator, basePath, branchPrefix }) {
    this.repoPath = repo;
    this.basePath = basePath;
    this.branchPrefix = branchPrefix || "";
    this.repo = null;
    this.log = logger('writer');
    let hostname = os.hostname();
    this.myName = `PID${process.pid} on ${hostname}`;
    this.myEmail = `${os.userInfo().username}@${hostname}`;
    this.idGenerator = idGenerator;
  }

  async prepareCreate(branch, user, type, document, isSchema) {
    return withErrorHandling(document.id, type, async () => {
      await this._ensureRepo();

      let change = await Change.create(this.repo, null, this.branchPrefix + branch);
      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);
      let signature = this._commitOptions('create', document.type, id, user);
      pendingChanges.set(pending, { type: document.type, id, signature, change, file });
      return pending;
    });
  }

  async prepareUpdate(branch, user, 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);
      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 = this._commitOptions('update', type, id, user);
      let pending = new PendingChange(before, after, finalizer);
      pendingChanges.set(pending, { type, id, signature, change, file });
      return pending;
    });
  }

  async prepareDelete(branch, user, 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);
      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);
      let signature = this._commitOptions('delete', type, id, user);
      pendingChanges.set(pending, { type, id, signature, change });
      return pending;
    });
  }

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

  _filenameFor(type, id, isSchema) {<commit_msg>Fix server error when user model has no attributes<commit_after>const {
  Repository
} = require('nodegit');

const logger = require('heimdalljs-logger');
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 pendingChanges = new WeakMap();

module.exports = class Writer {
  constructor({ repo, idGenerator, basePath, branchPrefix }) {
    this.repoPath = repo;
    this.basePath = basePath;
    this.branchPrefix = branchPrefix || "";
    this.repo = null;
    this.log = logger('writer');
    let hostname = os.hostname();
    this.myName = `PID${process.pid} on ${hostname}`;
    this.myEmail = `${os.userInfo().username}@${hostname}`;
    this.idGenerator = idGenerator;
  }

  async prepareCreate(branch, user, type, document, isSchema) {
    return withErrorHandling(document.id, type, async () => {
      await this._ensureRepo();

      let change = await Change.create(this.repo, null, this.branchPrefix + branch);
      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);
      let signature = this._commitOptions('create', document.type, id, user);
      pendingChanges.set(pending, { type: document.type, id, signature, change, file });
      return pending;
    });
  }

  async prepareUpdate(branch, user, 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);
      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 = this._commitOptions('update', type, id, user);
      let pending = new PendingChange(before, after, finalizer);
      pendingChanges.set(pending, { type, id, signature, change, file });
      return pending;
    });
  }

  async prepareDelete(branch, user, 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);
      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);
      let signature = this._commitOptions('delete', type, id, user);
      pendingChanges.set(pending, { type, id, signature, change });
      return pending;
    });
  }

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

  _filenameFor(type, id, isSchema) {
{'fraction_non_alphanumeric': 0.13727002313539716, 'fraction_numerical': 0.0017626969262972347, 'mean_word_length': 3.0544886109870477, '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': '2605610', 'n_tokens_mistral': 2609, 'n_tokens_neox': 2592, 'n_words': 686}