'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var _Promise = _interopDefault(require('babel-runtime/core-js/promise'));
var _regeneratorRuntime = _interopDefault(require('babel-runtime/regenerator'));
var _asyncToGenerator = _interopDefault(require('babel-runtime/helpers/asyncToGenerator'));
var _classCallCheck = _interopDefault(require('babel-runtime/helpers/classCallCheck'));
var _createClass = _interopDefault(require('babel-runtime/helpers/createClass'));
var _Map = _interopDefault(require('babel-runtime/core-js/map'));
var path = _interopDefault(require('path'));
var pify = _interopDefault(require('pify'));
var utils_js = require('./utils.js');
var _getIterator = _interopDefault(require('babel-runtime/core-js/get-iterator'));
var _extends = _interopDefault(require('babel-runtime/helpers/extends'));
var _typeof = _interopDefault(require('babel-runtime/helpers/typeof'));
var _slicedToArray = _interopDefault(require('babel-runtime/helpers/slicedToArray'));
var _Math$sign = _interopDefault(require('babel-runtime/core-js/math/sign'));
var buffer = require('buffer');
var _Object$getPrototypeOf = _interopDefault(require('babel-runtime/core-js/object/get-prototype-of'));
var _possibleConstructorReturn = _interopDefault(require('babel-runtime/helpers/possibleConstructorReturn'));
var _inherits = _interopDefault(require('babel-runtime/helpers/inherits'));
var _toConsumableArray = _interopDefault(require('babel-runtime/helpers/toConsumableArray'));
var shasum = _interopDefault(require('shasum'));
var BufferCursor = _interopDefault(require('buffercursor'));
var pad = _interopDefault(require('pad'));
var streamSource = _interopDefault(require('stream-source'));
var _Object$values = _interopDefault(require('babel-runtime/core-js/object/values'));
var _Object$keys = _interopDefault(require('babel-runtime/core-js/object/keys'));
var _Object$assign = _interopDefault(require('babel-runtime/core-js/object/assign'));
var applyDelta = _interopDefault(require('git-apply-delta'));
var listpack = _interopDefault(require('git-list-pack'));
var crc32 = _interopDefault(require('crc/lib/crc32.js'));
var stream = require('stream');
var pako = _interopDefault(require('pako'));
var marky = require('marky');
var _Symbol$iterator = _interopDefault(require('babel-runtime/core-js/symbol/iterator'));
var sortby = _interopDefault(require('lodash.sortby'));

var delayedReleases = new _Map();
/**
 * @ignore
 * This is just a collection of helper functions really. At least that's how it started.
 */
var FileSystem = function () {
  function FileSystem(fs) {
    _classCallCheck(this, FileSystem);

    if (typeof fs._readFile !== 'undefined') return fs;
    this._readFile = pify(fs.readFile.bind(fs));
    this._writeFile = pify(fs.writeFile.bind(fs));
    this._mkdir = pify(fs.mkdir.bind(fs));
    this._rmdir = pify(fs.rmdir.bind(fs));
    this._unlink = pify(fs.unlink.bind(fs));
    this._stat = pify(fs.stat.bind(fs));
    this._lstat = pify(fs.lstat.bind(fs));
    this._readdir = pify(fs.readdir.bind(fs));
  }
  /**
   * Return true if a file exists, false if it doesn't exist.
   * Rethrows errors that aren't related to file existance.
   */


  _createClass(FileSystem, [{
    key: 'exists',
    value: function () {
      var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(filepath /*: string */) {
        return _regeneratorRuntime.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                _context.prev = 0;
                _context.next = 3;
                return this._stat(filepath);

              case 3:
                return _context.abrupt('return', true);

              case 6:
                _context.prev = 6;
                _context.t0 = _context['catch'](0);

                if (!(_context.t0.code === 'ENOENT' || _context.t0.code === 'ENOTDIR')) {
                  _context.next = 12;
                  break;
                }

                return _context.abrupt('return', false);

              case 12:
                console.log('Unhandled error in "FileSystem.exists()" function', _context.t0);
                throw _context.t0;

              case 14:
              case 'end':
                return _context.stop();
            }
          }
        }, _callee, this, [[0, 6]]);
      }));

      function exists(_x2) {
        return _ref.apply(this, arguments);
      }

      return exists;
    }()
    /**
     * Return the contents of a file if it exists, otherwise returns null.
     */

  }, {
    key: 'read',
    value: function () {
      var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(filepath /*: string */) {
        var options /*: Object */ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        var buffer$$1;
        return _regeneratorRuntime.wrap(function _callee2$(_context2) {
          while (1) {
            switch (_context2.prev = _context2.next) {
              case 0:
                _context2.prev = 0;
                _context2.next = 3;
                return this._readFile(filepath, options);

              case 3:
                buffer$$1 = _context2.sent;
                return _context2.abrupt('return', buffer$$1);

              case 7:
                _context2.prev = 7;
                _context2.t0 = _context2['catch'](0);
                return _context2.abrupt('return', null);

              case 10:
              case 'end':
                return _context2.stop();
            }
          }
        }, _callee2, this, [[0, 7]]);
      }));

      function read(_x4) {
        return _ref2.apply(this, arguments);
      }

      return read;
    }()
    /**
     * Write a file (creating missing directories if need be) without throwing errors.
     */

  }, {
    key: 'write',
    value: function () {
      var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(filepath /*: string */
      , contents /*: string|Buffer */
      ) {
        var options /*: Object */ = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
        return _regeneratorRuntime.wrap(function _callee3$(_context3) {
          while (1) {
            switch (_context3.prev = _context3.next) {
              case 0:
                _context3.prev = 0;
                _context3.next = 3;
                return this._writeFile(filepath, contents, options);

              case 3:
                return _context3.abrupt('return');

              case 6:
                _context3.prev = 6;
                _context3.t0 = _context3['catch'](0);
                _context3.next = 10;
                return this.mkdir(path.dirname(filepath));

              case 10:
                _context3.next = 12;
                return this._writeFile(filepath, contents, options);

              case 12:
              case 'end':
                return _context3.stop();
            }
          }
        }, _callee3, this, [[0, 6]]);
      }));

      function write(_x6, _x7) {
        return _ref3.apply(this, arguments);
      }

      return write;
    }()
    /**
     * Make a directory (or series of nested directories) without throwing an error if it already exists.
     */

  }, {
    key: 'mkdir',
    value: function () {
      var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(filepath /*: string */) {
        var parent;
        return _regeneratorRuntime.wrap(function _callee4$(_context4) {
          while (1) {
            switch (_context4.prev = _context4.next) {
              case 0:
                _context4.prev = 0;
                _context4.next = 3;
                return this._mkdir(filepath);

              case 3:
                return _context4.abrupt('return');

              case 6:
                _context4.prev = 6;
                _context4.t0 = _context4['catch'](0);

                if (!(_context4.t0 === null)) {
                  _context4.next = 10;
                  break;
                }

                return _context4.abrupt('return');

              case 10:
                if (!(_context4.t0.code === 'EEXIST')) {
                  _context4.next = 12;
                  break;
                }

                return _context4.abrupt('return');

              case 12:
                if (!(_context4.t0.code === 'ENOENT')) {
                  _context4.next = 20;
                  break;
                }

                parent = path.dirname(filepath);
                // Check to see if we've gone too far

                if (!(parent === '.' || parent === '/' || parent === filepath)) {
                  _context4.next = 16;
                  break;
                }

                throw _context4.t0;

              case 16:
                _context4.next = 18;
                return this.mkdir(parent);

              case 18:
                _context4.next = 20;
                return this._mkdir(filepath);

              case 20:
              case 'end':
                return _context4.stop();
            }
          }
        }, _callee4, this, [[0, 6]]);
      }));

      function mkdir(_x8) {
        return _ref4.apply(this, arguments);
      }

      return mkdir;
    }()
    /**
     * Delete a file without throwing an error if it is already deleted.
     */

  }, {
    key: 'rm',
    value: function () {
      var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5(filepath) {
        return _regeneratorRuntime.wrap(function _callee5$(_context5) {
          while (1) {
            switch (_context5.prev = _context5.next) {
              case 0:
                _context5.prev = 0;
                _context5.next = 3;
                return this._unlink(filepath);

              case 3:
                _context5.next = 9;
                break;

              case 5:
                _context5.prev = 5;
                _context5.t0 = _context5['catch'](0);

                if (!(_context5.t0.code !== 'ENOENT')) {
                  _context5.next = 9;
                  break;
                }

                throw _context5.t0;

              case 9:
              case 'end':
                return _context5.stop();
            }
          }
        }, _callee5, this, [[0, 5]]);
      }));

      function rm(_x9) {
        return _ref5.apply(this, arguments);
      }

      return rm;
    }()
    /**
     * Read a directory without throwing an error is the directory doesn't exist
     */

  }, {
    key: 'readdir',
    value: function () {
      var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6(filepath) {
        return _regeneratorRuntime.wrap(function _callee6$(_context6) {
          while (1) {
            switch (_context6.prev = _context6.next) {
              case 0:
                _context6.prev = 0;
                _context6.next = 3;
                return this._readdir(filepath);

              case 3:
                return _context6.abrupt('return', _context6.sent);

              case 6:
                _context6.prev = 6;
                _context6.t0 = _context6['catch'](0);
                return _context6.abrupt('return', []);

              case 9:
              case 'end':
                return _context6.stop();
            }
          }
        }, _callee6, this, [[0, 6]]);
      }));

      function readdir(_x10) {
        return _ref6.apply(this, arguments);
      }

      return readdir;
    }()
    /**
     * Return a flast list of all the files nested inside a directory
     *
     * Based on an elegant concurrent recursive solution from SO
     * https://stackoverflow.com/a/45130990/2168416
     */

  }, {
    key: 'readdirDeep',
    value: function () {
      var _ref7 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee8(dir) {
        var _this = this;

        var subdirs, files;
        return _regeneratorRuntime.wrap(function _callee8$(_context8) {
          while (1) {
            switch (_context8.prev = _context8.next) {
              case 0:
                _context8.next = 2;
                return this._readdir(dir);

              case 2:
                subdirs = _context8.sent;
                _context8.next = 5;
                return _Promise.all(subdirs.map(function () {
                  var _ref8 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7(subdir) {
                    var res;
                    return _regeneratorRuntime.wrap(function _callee7$(_context7) {
                      while (1) {
                        switch (_context7.prev = _context7.next) {
                          case 0:
                            res = dir + '/' + subdir;
                            _context7.next = 3;
                            return _this._stat(res);

                          case 3:
                            if (!_context7.sent.isDirectory()) {
                              _context7.next = 7;
                              break;
                            }

                            _context7.t0 = _this.readdirDeep(res);
                            _context7.next = 8;
                            break;

                          case 7:
                            _context7.t0 = res;

                          case 8:
                            return _context7.abrupt('return', _context7.t0);

                          case 9:
                          case 'end':
                            return _context7.stop();
                        }
                      }
                    }, _callee7, _this);
                  }));

                  return function (_x12) {
                    return _ref8.apply(this, arguments);
                  };
                }()));

              case 5:
                files = _context8.sent;
                return _context8.abrupt('return', files.reduce(function (a, f) {
                  return a.concat(f);
                }, []));

              case 7:
              case 'end':
                return _context8.stop();
            }
          }
        }, _callee8, this);
      }));

      function readdirDeep(_x11) {
        return _ref7.apply(this, arguments);
      }

      return readdirDeep;
    }()
  }, {
    key: 'lock',
    value: function () {
      var _ref9 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee9(filename) {
        var triesLeft = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3;
        return _regeneratorRuntime.wrap(function _callee9$(_context9) {
          while (1) {
            switch (_context9.prev = _context9.next) {
              case 0:
                if (!delayedReleases.has(filename)) {
                  _context9.next = 4;
                  break;
                }

                clearTimeout(delayedReleases.get(filename));
                delayedReleases.delete(filename);
                return _context9.abrupt('return');

              case 4:
                if (!(triesLeft === 0)) {
                  _context9.next = 6;
                  break;
                }

                throw new Error('Unable to acquire lockfile \'' + filename + '\'. Exhausted tries.');

              case 6:
                _context9.prev = 6;
                _context9.next = 9;
                return this._mkdir(filename + '.lock');

              case 9:
                _context9.next = 18;
                break;

              case 11:
                _context9.prev = 11;
                _context9.t0 = _context9['catch'](6);

                if (!(_context9.t0.code === 'EEXIST')) {
                  _context9.next = 18;
                  break;
                }

                _context9.next = 16;
                return utils_js.sleep(100);

              case 16:
                _context9.next = 18;
                return this.lock(filename, triesLeft - 1);

              case 18:
              case 'end':
                return _context9.stop();
            }
          }
        }, _callee9, this, [[6, 11]]);
      }));

      function lock(_x14) {
        return _ref9.apply(this, arguments);
      }

      return lock;
    }()
  }, {
    key: 'unlock',
    value: function () {
      var _ref10 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11(filename) {
        var _this2 = this;

        var delayRelease = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 50;
        return _regeneratorRuntime.wrap(function _callee11$(_context11) {
          while (1) {
            switch (_context11.prev = _context11.next) {
              case 0:
                if (!delayedReleases.has(filename)) {
                  _context11.next = 2;
                  break;
                }

                throw new Error('Cannot double-release lockfile');

              case 2:
                // Basically, we lie and say it was deleted ASAP.
                // But really we wait a bit to see if you want to acquire it again.
                delayedReleases.set(filename, setTimeout(_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
                  return _regeneratorRuntime.wrap(function _callee10$(_context10) {
                    while (1) {
                      switch (_context10.prev = _context10.next) {
                        case 0:
                          delayedReleases.delete(filename);
                          _context10.next = 3;
                          return _this2._rmdir(filename + '.lock');

                        case 3:
                        case 'end':
                          return _context10.stop();
                      }
                    }
                  }, _callee10, _this2);
                })), delayRelease));

              case 3:
              case 'end':
                return _context11.stop();
            }
          }
        }, _callee11, this);
      }));

      function unlock(_x16) {
        return _ref10.apply(this, arguments);
      }

      return unlock;
    }()
  }]);

  return FileSystem;
}();

function formatTimezoneOffset(minutes /*: number */) {
  var sign = _Math$sign(minutes) || 1;
  minutes = Math.abs(minutes);
  var hours = Math.floor(minutes / 60);
  minutes -= hours * 60;
  var strHours = String(hours);
  var strMinutes = String(minutes);
  if (strHours.length < 2) strHours = '0' + strHours;
  if (strMinutes.length < 2) strMinutes = '0' + strMinutes;
  return (sign === 1 ? '-' : '+') + strHours + strMinutes;
}

function parseTimezoneOffset(offset) {
  var _offset$match = offset.match(/(\+|-)(\d\d)(\d\d)/),
      _offset$match2 = _slicedToArray(_offset$match, 4),
      sign = _offset$match2[1],
      hours = _offset$match2[2],
      minutes = _offset$match2[3];

  minutes = (sign === '-' ? 1 : -1) * Number(hours) * 60 + Number(minutes);
  return minutes;
}

function parseAuthor(author) {
  var _author$match = author.match(/^(.*) <(.*)> (.*) (.*)$/),
      _author$match2 = _slicedToArray(_author$match, 5),
      name = _author$match2[1],
      email = _author$match2[2],
      timestamp = _author$match2[3],
      offset = _author$match2[4];

  return {
    name: name,
    email: email,
    timestamp: Number(timestamp),
    timezoneOffset: parseTimezoneOffset(offset)
  };
}

function normalize(str) {
  // remove all <CR>
  str = str.replace(/\r/g, '');
  // no extra newlines up front
  str = str.replace(/^\n+/, '');
  // and a single newline at the end
  str = str.replace(/\n+$/, '') + '\n';
  return str;
}

function indent(str) {
  return str.trim().split('\n').map(function (x) {
    return ' ' + x;
  }).join('\n') + '\n';
}

function outdent(str) {
  return str.split('\n').map(function (x) {
    return x.replace(/^ /, '');
  }).join('\n');
}

// TODO: Make all functions have static async signature?

/** @ignore */
var GitCommit = function () {
  /*::
  _commit : string
  */
  function GitCommit(commit /*: string|Buffer|Object */) {
    _classCallCheck(this, GitCommit);

    if (typeof commit === 'string') {
      this._commit = commit;
    } else if (buffer.Buffer.isBuffer(commit)) {
      this._commit = commit.toString('utf8');
    } else if ((typeof commit === 'undefined' ? 'undefined' : _typeof(commit)) === 'object') {
      this._commit = GitCommit.render(commit);
    } else {
      throw new Error('invalid type passed to GitCommit constructor');
    }
  }

  _createClass(GitCommit, [{
    key: 'toObject',
    value: function toObject() {
      return buffer.Buffer.from(this._commit, 'utf8');
    }

    // Todo: allow setting the headers and message

  }, {
    key: 'headers',
    value: function headers() {
      return this.parseHeaders();
    }

    // Todo: allow setting the headers and message

  }, {
    key: 'message',
    value: function message() {
      return GitCommit.justMessage(this._commit);
    }
  }, {
    key: 'parse',
    value: function parse() {
      return _extends({ message: this.message() }, this.headers());
    }
  }, {
    key: 'parseHeaders',
    value: function parseHeaders() {
      var headers = GitCommit.justHeaders(this._commit).split('\n');
      var hs = [];
      var _iteratorNormalCompletion = true;
      var _didIteratorError = false;
      var _iteratorError = undefined;

      try {
        for (var _iterator = _getIterator(headers), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
          var h = _step.value;

          if (h[0] === ' ') {
            // combine with previous header (without space indent)
            hs[hs.length - 1] += '\n' + h.slice(1);
          } else {
            hs.push(h);
          }
        }
      } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion && _iterator.return) {
            _iterator.return();
          }
        } finally {
          if (_didIteratorError) {
            throw _iteratorError;
          }
        }
      }

      var obj = {};
      var _iteratorNormalCompletion2 = true;
      var _didIteratorError2 = false;
      var _iteratorError2 = undefined;

      try {
        for (var _iterator2 = _getIterator(hs), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
          var _h = _step2.value;

          var key = _h.slice(0, _h.indexOf(' '));
          var value = _h.slice(_h.indexOf(' ') + 1);
          obj[key] = value;
        }
      } catch (err) {
        _didIteratorError2 = true;
        _iteratorError2 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion2 && _iterator2.return) {
            _iterator2.return();
          }
        } finally {
          if (_didIteratorError2) {
            throw _iteratorError2;
          }
        }
      }

      obj.parent = obj.parent ? obj.parent.split(' ') : [];
      if (obj.author) {
        obj.author = parseAuthor(obj.author);
      }
      if (obj.committer) {
        obj.committer = parseAuthor(obj.committer);
      }
      return obj;
    }
  }, {
    key: 'render',
    value: function render() {
      return this._commit;
    }
  }, {
    key: 'withoutSignature',
    value: function withoutSignature() {
      var commit = normalize(this._commit);
      if (commit.indexOf('\ngpgsig') === -1) return commit;
      var headers = commit.slice(0, commit.indexOf('\ngpgsig'));
      var message = commit.slice(commit.indexOf('-----END PGP SIGNATURE-----\n') + '-----END PGP SIGNATURE-----\n'.length);
      return normalize(headers + '\n' + message);
    }
  }, {
    key: 'isolateSignature',
    value: function isolateSignature() {
      var signature = this._commit.slice(this._commit.indexOf('-----BEGIN PGP SIGNATURE-----'), this._commit.indexOf('-----END PGP SIGNATURE-----') + '-----END PGP SIGNATURE-----'.length);
      return outdent(signature);
    }
  }], [{
    key: 'fromPayloadSignature',
    value: function fromPayloadSignature(_ref) {
      var payload = _ref.payload,
          signature = _ref.signature;

      var headers = GitCommit.justHeaders(payload);
      var message = GitCommit.justMessage(payload);
      var commit = normalize(headers + '\ngpgsig' + indent(signature) + '\n' + message);
      return new GitCommit(commit);
    }
  }, {
    key: 'from',
    value: function from(commit) {
      return new GitCommit(commit);
    }
  }, {
    key: 'justMessage',
    value: function justMessage(commit) {
      return normalize(commit.slice(commit.indexOf('\n\n') + 2));
    }
  }, {
    key: 'justHeaders',
    value: function justHeaders(commit) {
      return commit.slice(0, commit.indexOf('\n\n'));
    }
  }, {
    key: 'renderHeaders',
    value: function renderHeaders(obj) {
      var headers = '';
      if (obj.tree) {
        headers += 'tree ' + obj.tree + '\n';
      } else {
        headers += 'tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n'; // the null tree
      }
      if (obj.parent && obj.parent.length) {
        headers += 'parent';
        var _iteratorNormalCompletion3 = true;
        var _didIteratorError3 = false;
        var _iteratorError3 = undefined;

        try {
          for (var _iterator3 = _getIterator(obj.parent), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
            var p = _step3.value;

            headers += ' ' + p;
          }
        } catch (err) {
          _didIteratorError3 = true;
          _iteratorError3 = err;
        } finally {
          try {
            if (!_iteratorNormalCompletion3 && _iterator3.return) {
              _iterator3.return();
            }
          } finally {
            if (_didIteratorError3) {
              throw _iteratorError3;
            }
          }
        }

        headers += '\n';
      }
      var author = obj.author;
      headers += 'author ' + author.name + ' <' + author.email + '> ' + author.timestamp + ' ' + formatTimezoneOffset(author.timezoneOffset) + '\n';
      var committer = obj.committer || obj.author;
      headers += 'committer ' + committer.name + ' <' + committer.email + '> ' + committer.timestamp + ' ' + formatTimezoneOffset(committer.timezoneOffset) + '\n';
      if (obj.gpgsig) {
        headers += 'gpgsig' + indent(obj.gpgsig);
      }
      return headers;
    }
  }, {
    key: 'render',
    value: function render(obj) {
      return GitCommit.renderHeaders(obj) + '\n' + normalize(obj.message);
    }
  }]);

  return GitCommit;
}();

function normalize$1(str) {
  // remove all <CR>
  str = str.replace(/\r/g, '');
  // no extra newlines up front
  str = str.replace(/^\n+/, '');
  // and a single newline at the end
  str = str.replace(/\n+$/, '') + '\n';
  return str;
}

function indent$1(str) {
  return str.trim().split('\n').map(function (x) {
    return ' ' + x;
  }).join('\n') + '\n';
}

/** @ignore */
var SignedGitCommit = function (_GitCommit) {
  _inherits(SignedGitCommit, _GitCommit);

  function SignedGitCommit() {
    _classCallCheck(this, SignedGitCommit);

    return _possibleConstructorReturn(this, (SignedGitCommit.__proto__ || _Object$getPrototypeOf(SignedGitCommit)).apply(this, arguments));
  }

  _createClass(SignedGitCommit, [{
    key: 'sign',
    value: function () {
      var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(openpgp, privateKeys /*: string */) {
        var commit, headers, message, privKeyObj, _ref2, signature, signedCommit;

        return _regeneratorRuntime.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                commit = this.withoutSignature();
                headers = GitCommit.justHeaders(this._commit);
                message = GitCommit.justMessage(this._commit);
                privKeyObj = openpgp.key.readArmored(privateKeys).keys;
                _context.next = 6;
                return openpgp.sign({
                  data: openpgp.util.str2Uint8Array(commit),
                  privateKeys: privKeyObj,
                  detached: true,
                  armor: true
                });

              case 6:
                _ref2 = _context.sent;
                signature = _ref2.signature;

                // renormalize the line endings to the one true line-ending
                signature = normalize$1(signature);
                signedCommit = headers + '\n' + 'gpgsig' + indent$1(signature) + '\n' + message;
                // return a new commit object

                return _context.abrupt('return', GitCommit.from(signedCommit));

              case 11:
              case 'end':
                return _context.stop();
            }
          }
        }, _callee, this);
      }));

      function sign(_x, _x2) {
        return _ref.apply(this, arguments);
      }

      return sign;
    }()
  }, {
    key: 'listSigningKeys',
    value: function () {
      var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(openpgp) {
        var msg;
        return _regeneratorRuntime.wrap(function _callee2$(_context2) {
          while (1) {
            switch (_context2.prev = _context2.next) {
              case 0:
                msg = openpgp.message.readSignedContent(this.withoutSignature(), this.isolateSignature());
                return _context2.abrupt('return', msg.getSigningKeyIds().map(function (keyid) {
                  return keyid.toHex();
                }));

              case 2:
              case 'end':
                return _context2.stop();
            }
          }
        }, _callee2, this);
      }));

      function listSigningKeys(_x3) {
        return _ref3.apply(this, arguments);
      }

      return listSigningKeys;
    }()
  }, {
    key: 'verify',
    value: function () {
      var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(openpgp, publicKeys /*: string */) {
        var pubKeyObj, msg, results, validity;
        return _regeneratorRuntime.wrap(function _callee3$(_context3) {
          while (1) {
            switch (_context3.prev = _context3.next) {
              case 0:
                pubKeyObj = openpgp.key.readArmored(publicKeys).keys;
                msg = openpgp.message.readSignedContent(this.withoutSignature(), this.isolateSignature());
                results = msg.verify(pubKeyObj);
                validity = results.reduce(function (a, b) {
                  return a.valid && b.valid;
                }, { valid: true });
                return _context3.abrupt('return', validity);

              case 5:
              case 'end':
                return _context3.stop();
            }
          }
        }, _callee3, this);
      }));

      function verify(_x4, _x5) {
        return _ref4.apply(this, arguments);
      }

      return verify;
    }()
  }], [{
    key: 'from',
    value: function from(commit) {
      return new SignedGitCommit(commit);
    }
  }]);

  return SignedGitCommit;
}(GitCommit);

// This is straight from parse_unit_factor in config.c of canonical git
var num = function num(val) {
  val = val.toLowerCase();
  var n = parseInt(val);
  if (val.endsWith('k')) n *= 1024;
  if (val.endsWith('m')) n *= 1024 * 1024;
  if (val.endsWith('g')) n *= 1024 * 1024 * 1024;
  return n;
};

// This is straight from git_parse_maybe_bool_text in config.c of canonical git
var bool = function bool(val) {
  val = val.trim().toLowerCase();
  if (val === 'true' || val === 'yes' || val === 'on') return true;
  if (val === 'false' || val === 'no' || val === 'off') return false;
  throw Error('Expected \'true\', \'false\', \'yes\', \'no\', \'on\', or \'off\', but got ' + val);
};

var schema = {
  core: {
    _named: false,
    repositoryformatversion: String,
    filemode: bool,
    bare: bool,
    logallrefupdates: bool,
    symlinks: bool,
    ignorecase: bool,
    bigFileThreshold: num
  },
  remote: {
    _named: true,
    url: String,
    fetch: String
  },
  branch: {
    _named: true,
    remote: String,
    merge: String
  }
};

var isSection = function isSection(line) {
  return line.trim().startsWith('[');
};

var extractSection = function extractSection(line) {
  var indices = [line.indexOf(']'), line.indexOf(' ')].filter(function (i) {
    return i > -1;
  });
  return line.slice(line.indexOf('[') + 1, Math.min.apply(Math, _toConsumableArray(indices))).trim();
};

var isNamedSection = function isNamedSection(section) {
  return schema[section]._named;
};

var isKeyValuePair = function isKeyValuePair(line) {
  return line.includes('=');
};

var extractSectionName = function extractSectionName(line) {
  return line.slice(line.indexOf('"') + 1, line.lastIndexOf('"'));
};

// Note: there are a LOT of edge cases that aren't covered (e.g. keys in sections that also
// have subsections, [include] directives, etc.
/** @ignore */
var GitConfig = function () {
  function GitConfig(text) {
    _classCallCheck(this, GitConfig);

    this.lines = text.split('\n');
  }

  _createClass(GitConfig, [{
    key: 'get',
    value: function () {
      var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(path$$1) {
        var getall = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;

        var parts, section, sectionName, key, currentSection, currentSectionName, allValues, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, line, _line$split, _line$split2, _key, _value, fn;

        return _regeneratorRuntime.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                parts = path$$1.split('.');
                section = parts.shift();
                sectionName = isNamedSection(section) ? parts.shift() : null;
                key = parts.shift();
                currentSection = '';
                currentSectionName = null;
                allValues = [];
                _iteratorNormalCompletion = true;
                _didIteratorError = false;
                _iteratorError = undefined;
                _context.prev = 10;

                for (_iterator = _getIterator(this.lines); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
                  line = _step.value;

                  // zero in on section
                  if (isSection(line)) {
                    currentSection = extractSection(line);
                    if (isNamedSection(currentSection)) {
                      currentSectionName = extractSectionName(line);
                    }
                  } else if (currentSection === section && (sectionName === null || currentSectionName === sectionName)) {
                    if (isKeyValuePair(line)) {
                      _line$split = line.split('=', 2), _line$split2 = _slicedToArray(_line$split, 2), _key = _line$split2[0], _value = _line$split2[1];

                      if (_key.trim() === key) {
                        allValues.push(_value.trim());
                      }
                    }
                  }
                }
                // Cast value to correct type
                _context.next = 18;
                break;

              case 14:
                _context.prev = 14;
                _context.t0 = _context['catch'](10);
                _didIteratorError = true;
                _iteratorError = _context.t0;

              case 18:
                _context.prev = 18;
                _context.prev = 19;

                if (!_iteratorNormalCompletion && _iterator.return) {
                  _iterator.return();
                }

              case 21:
                _context.prev = 21;

                if (!_didIteratorError) {
                  _context.next = 24;
                  break;
                }

                throw _iteratorError;

              case 24:
                return _context.finish(21);

              case 25:
                return _context.finish(18);

              case 26:
                fn = schema[section][key];

                if (fn) {
                  allValues = allValues.map(fn);
                }
                return _context.abrupt('return', getall ? allValues : allValues.pop());

              case 29:
              case 'end':
                return _context.stop();
            }
          }
        }, _callee, this, [[10, 14, 18, 26], [19,, 21, 25]]);
      }));

      function get(_x2) {
        return _ref.apply(this, arguments);
      }

      return get;
    }()
  }, {
    key: 'getall',
    value: function () {
      var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(path$$1) {
        return _regeneratorRuntime.wrap(function _callee2$(_context2) {
          while (1) {
            switch (_context2.prev = _context2.next) {
              case 0:
                return _context2.abrupt('return', this.get(path$$1, true));

              case 1:
              case 'end':
                return _context2.stop();
            }
          }
        }, _callee2, this);
      }));

      function getall(_x3) {
        return _ref2.apply(this, arguments);
      }

      return getall;
    }()
  }, {
    key: 'append',
    value: function () {
      var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(path$$1, value) {
        return _regeneratorRuntime.wrap(function _callee3$(_context3) {
          while (1) {
            switch (_context3.prev = _context3.next) {
              case 0:
                return _context3.abrupt('return', this.set(path$$1, value, true));

              case 1:
              case 'end':
                return _context3.stop();
            }
          }
        }, _callee3, this);
      }));

      function append(_x4, _x5) {
        return _ref3.apply(this, arguments);
      }

      return append;
    }()
  }, {
    key: 'set',
    value: function () {
      var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(path$$1, value) {
        var append = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;

        var parts, section, sectionName, key, currentSection, currentSectionName, lastSectionMatch, lastMatch, i, line, _line$split3, _line$split4, _key;

        return _regeneratorRuntime.wrap(function _callee4$(_context4) {
          while (1) {
            switch (_context4.prev = _context4.next) {
              case 0:
                parts = path$$1.split('.');
                section = parts.shift();
                sectionName = isNamedSection(section) ? parts.shift() : null;
                key = parts.shift();
                currentSection = '';
                currentSectionName = null;
                lastSectionMatch = null;
                lastMatch = null;

                for (i = 0; i < this.lines.length; i++) {
                  line = this.lines[i];

                  if (isSection(line)) {
                    currentSection = extractSection(line);
                    if (currentSection === section) {
                      if (sectionName) {
                        currentSectionName = extractSectionName(line);
                      }
                      if (currentSectionName === sectionName) {
                        lastSectionMatch = i;
                      }
                    } else {
                      currentSectionName = null;
                    }
                  } else if (currentSection === section && (sectionName === null || currentSectionName === sectionName)) {
                    if (isKeyValuePair(line)) {
                      _line$split3 = line.split('=', 1), _line$split4 = _slicedToArray(_line$split3, 1), _key = _line$split4[0];

                      if (_key.trim() === key) {
                        lastMatch = i;
                      }
                    }
                  }
                }
                if (lastMatch !== null) {
                  if (value === undefined) {
                    this.lines.splice(lastMatch, 1);
                  } else if (append) {
                    this.lines.splice(lastMatch + 1, 0, ['\t' + key + ' = ' + value]);
                  } else {
                    this.lines[lastMatch] = '\t' + key + ' = ' + value;
                  }
                } else if (lastSectionMatch !== null) {
                  if (value !== undefined) {
                    this.lines.splice(lastSectionMatch + 1, 0, ['\t' + key + ' = ' + value]);
                  }
                } else if (value !== undefined) {
                  if (sectionName) {
                    this.lines.push('[' + section + ' "' + sectionName + '"]');
                  } else {
                    this.lines.push('[' + section + ']');
                  }
                  this.lines.push(['\t' + key + ' = ' + value]);
                }

              case 10:
              case 'end':
                return _context4.stop();
            }
          }
        }, _callee4, this);
      }));

      function set(_x7, _x8) {
        return _ref4.apply(this, arguments);
      }

      return set;
    }()
  }, {
    key: 'toString',
    value: function toString() {
      return this.lines.join('\n') + '\n';
    }
  }], [{
    key: 'from',
    value: function from(text) {
      return new GitConfig(text);
    }
  }]);

  return GitConfig;
}();

/** @ignore */
var GitObject = function () {
  function GitObject() {
    _classCallCheck(this, GitObject);
  }

  _createClass(GitObject, null, [{
    key: 'hash',
    value: function hash(_ref) /*: Promise<string> */{
      var type = _ref.type,
          object = _ref.object;

      var buffer$$1 = buffer.Buffer.concat([buffer.Buffer.from(type + ' ' + object.byteLength.toString() + '\0'), buffer.Buffer.from(object)]);
      var oid = shasum(buffer$$1);
      return oid;
    }
  }, {
    key: 'wrap',
    value: function wrap(_ref2) {
      var type = _ref2.type,
          object = _ref2.object;

      var buffer$$1 = buffer.Buffer.concat([buffer.Buffer.from(type + ' ' + object.byteLength.toString() + '\0'), object]);
      var oid = shasum(buffer$$1);
      return {
        oid: oid,
        buffer: buffer$$1
      };
    }
  }, {
    key: 'unwrap',
    value: function unwrap(_ref3) {
      var oid = _ref3.oid,
          buffer$$1 = _ref3.buffer;

      if (oid) {
        var sha = shasum(buffer$$1);
        if (sha !== oid) {
          throw new Error('SHA check failed! Expected ' + oid + ', computed ' + sha);
        }
      }
      var s = buffer$$1.indexOf(32); // first space
      var i = buffer$$1.indexOf(0); // first null value
      var type = buffer$$1.slice(0, s).toString('utf8'); // get type of object
      var length = buffer$$1.slice(s + 1, i).toString('utf8'); // get type of object
      var actualLength = buffer$$1.length - (i + 1);
      // verify length
      if (parseInt(length) !== actualLength) {
        throw new Error('Length mismatch: expected ' + length + ' bytes but got ' + actualLength + ' instead.');
      }
      return {
        type: type,
        object: buffer.Buffer.from(buffer$$1.slice(i + 1))
      };
    }
  }]);

  return GitObject;
}();

// I'm really using this more as a namespace.
// There's not a lot of "state" in a pkt-line

/** @ignore */
var GitPktLine = function () {
  function GitPktLine() {
    _classCallCheck(this, GitPktLine);
  }

  _createClass(GitPktLine, null, [{
    key: 'flush',
    value: function flush() {
      return buffer.Buffer.from('0000', 'utf8');
    }
  }, {
    key: 'encode',
    value: function encode(line /*: string|Buffer */) /*: Buffer */{
      if (typeof line === 'string') {
        line = buffer.Buffer.from(line);
      }
      var length = line.length + 4;
      var hexlength = pad(4, length.toString(16), '0');
      return buffer.Buffer.concat([buffer.Buffer.from(hexlength, 'utf8'), line]);
    }
  }, {
    key: 'reader',
    value: function reader(buffer$$1 /*: Buffer */) {
      var buffercursor = new BufferCursor(buffer$$1);
      return function () {
        var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
          var length;
          return _regeneratorRuntime.wrap(function _callee$(_context) {
            while (1) {
              switch (_context.prev = _context.next) {
                case 0:
                  if (!buffercursor.eof()) {
                    _context.next = 2;
                    break;
                  }

                  return _context.abrupt('return', true);

                case 2:
                  length = parseInt(buffercursor.slice(4).toString('utf8'), 16);

                  if (!(length === 0)) {
                    _context.next = 5;
                    break;
                  }

                  return _context.abrupt('return', null);

                case 5:
                  return _context.abrupt('return', buffercursor.slice(length - 4).buffer);

                case 6:
                case 'end':
                  return _context.stop();
              }
            }
          }, _callee, this);
        }));

        function read() {
          return _ref.apply(this, arguments);
        }

        return read;
      }();
    }
  }, {
    key: 'streamReader',
    value: function streamReader(stream$$1) {
      var bufferstream = streamSource(stream$$1);
      return function () {
        var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
          var length, buffer$$1;
          return _regeneratorRuntime.wrap(function _callee2$(_context2) {
            while (1) {
              switch (_context2.prev = _context2.next) {
                case 0:
                  _context2.prev = 0;
                  _context2.next = 3;
                  return bufferstream.slice(4);

                case 3:
                  length = _context2.sent;

                  if (!(length === null)) {
                    _context2.next = 6;
                    break;
                  }

                  return _context2.abrupt('return', true);

                case 6:
                  length = parseInt(length.toString('utf8'), 16);

                  if (!(length === 0)) {
                    _context2.next = 9;
                    break;
                  }

                  return _context2.abrupt('return', null);

                case 9:
                  _context2.next = 11;
                  return bufferstream.slice(length - 4);

                case 11:
                  buffer$$1 = _context2.sent;

                  if (!(buffer$$1 === null)) {
                    _context2.next = 14;
                    break;
                  }

                  return _context2.abrupt('return', true);

                case 14:
                  return _context2.abrupt('return', buffer$$1);

                case 17:
                  _context2.prev = 17;
                  _context2.t0 = _context2['catch'](0);

                  console.log('error', _context2.t0);
                  return _context2.abrupt('return', true);

                case 21:
                case 'end':
                  return _context2.stop();
              }
            }
          }, _callee2, this, [[0, 17]]);
        }));

        function read() {
          return _ref2.apply(this, arguments);
        }

        return read;
      }();
    }
  }]);

  return GitPktLine;
}();

function buffer2stream(buffer$$1) {
  var stream$$1 = new stream.PassThrough();
  stream$$1.end(buffer$$1);
  return stream$$1;
}

function decodeVarInt(reader) {
  var bytes = [];
  var byte = 0;
  var multibyte = 0;
  do {
    byte = reader.readUInt8();
    // We keep bits 6543210
    var lastSeven = byte & 127;
    bytes.push(lastSeven);
    // Whether the next byte is part of the variable-length encoded number
    // is encoded in bit 7
    multibyte = byte & 128;
  } while (multibyte);
  // Now that all the bytes are in big-endian order,
  // alternate shifting the bits left by 7 and OR-ing the next byte.
  // And... do a weird increment-by-one thing that I don't quite understand.
  return bytes.reduce(function (a, b) {
    return a + 1 << 7 | b;
  }, -1);
}

// I'm pretty much copying this one from the git C source code,
// because it makes no sense.
function otherVarIntDecode(reader, startWith) {
  var result = startWith;
  var shift = 4;
  var byte = null;
  do {
    byte = reader.readUInt8();
    result |= (byte & 127) << shift;
    shift += 7;
  } while (byte & 128);
  return result;
}

/** @ignore */
var GitPackIndex = function () {
  function GitPackIndex(stuff) {
    _classCallCheck(this, GitPackIndex);

    _Object$assign(this, stuff);
    this.offsetCache = {};
  }

  _createClass(GitPackIndex, [{
    key: 'toBuffer',
    value: function toBuffer() {
      var buffers = [];
      var write = function write(str, encoding) {
        buffers.push(buffer.Buffer.from(str, encoding));
      };
      // Write out IDX v2 magic number
      write('ff744f63', 'hex');
      // Write out version number 2
      write('00000002', 'hex');
      // Write fanout table
      var fanoutBuffer = new BufferCursor(buffer.Buffer.alloc(256 * 4));
      for (var i = 0; i < 256; i++) {
        var count = 0;
        var _iteratorNormalCompletion = true;
        var _didIteratorError = false;
        var _iteratorError = undefined;

        try {
          for (var _iterator = _getIterator(this.hashes), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
            var hash = _step.value;

            if (parseInt(hash.slice(0, 2), 16) <= i) count++;
          }
        } catch (err) {
          _didIteratorError = true;
          _iteratorError = err;
        } finally {
          try {
            if (!_iteratorNormalCompletion && _iterator.return) {
              _iterator.return();
            }
          } finally {
            if (_didIteratorError) {
              throw _iteratorError;
            }
          }
        }

        fanoutBuffer.writeUInt32BE(count);
      }
      buffers.push(fanoutBuffer.buffer);
      // Write out hashes
      var _iteratorNormalCompletion2 = true;
      var _didIteratorError2 = false;
      var _iteratorError2 = undefined;

      try {
        for (var _iterator2 = _getIterator(this.hashes), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
          var _hash = _step2.value;

          write(_hash, 'hex');
        }
        // Write out crcs
      } catch (err) {
        _didIteratorError2 = true;
        _iteratorError2 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion2 && _iterator2.return) {
            _iterator2.return();
          }
        } finally {
          if (_didIteratorError2) {
            throw _iteratorError2;
          }
        }
      }

      var crcsBuffer = new BufferCursor(buffer.Buffer.alloc(this.hashes.length * 4));
      var _iteratorNormalCompletion3 = true;
      var _didIteratorError3 = false;
      var _iteratorError3 = undefined;

      try {
        for (var _iterator3 = _getIterator(this.hashes), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
          var _hash2 = _step3.value;

          crcsBuffer.writeUInt32BE(this.crcs[_hash2]);
        }
      } catch (err) {
        _didIteratorError3 = true;
        _iteratorError3 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion3 && _iterator3.return) {
            _iterator3.return();
          }
        } finally {
          if (_didIteratorError3) {
            throw _iteratorError3;
          }
        }
      }

      buffers.push(crcsBuffer.buffer);
      // Write out offsets
      var offsetsBuffer = new BufferCursor(buffer.Buffer.alloc(this.hashes.length * 4));
      var _iteratorNormalCompletion4 = true;
      var _didIteratorError4 = false;
      var _iteratorError4 = undefined;

      try {
        for (var _iterator4 = _getIterator(this.hashes), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
          var _hash3 = _step4.value;

          offsetsBuffer.writeUInt32BE(this.offsets[_hash3]);
        }
      } catch (err) {
        _didIteratorError4 = true;
        _iteratorError4 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion4 && _iterator4.return) {
            _iterator4.return();
          }
        } finally {
          if (_didIteratorError4) {
            throw _iteratorError4;
          }
        }
      }

      buffers.push(offsetsBuffer.buffer);
      // Write out packfile checksum
      write(this.packfileSha, 'hex');
      // Write out shasum
      var totalBuffer = buffer.Buffer.concat(buffers);
      var sha = shasum(totalBuffer);
      var shaBuffer = buffer.Buffer.alloc(20);
      shaBuffer.write(sha, 'hex');
      return buffer.Buffer.concat([totalBuffer, shaBuffer]);
    }
  }, {
    key: 'load',
    value: function () {
      var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(_ref) {
        var pack = _ref.pack;
        return _regeneratorRuntime.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                this.pack = pack;

              case 1:
              case 'end':
                return _context.stop();
            }
          }
        }, _callee, this);
      }));

      function load(_x) {
        return _ref2.apply(this, arguments);
      }

      return load;
    }()
  }, {
    key: 'unload',
    value: function () {
      var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
        return _regeneratorRuntime.wrap(function _callee2$(_context2) {
          while (1) {
            switch (_context2.prev = _context2.next) {
              case 0:
                this.pack = null;

              case 1:
              case 'end':
                return _context2.stop();
            }
          }
        }, _callee2, this);
      }));

      function unload() {
        return _ref3.apply(this, arguments);
      }

      return unload;
    }()
  }, {
    key: 'read',
    value: function () {
      var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(_ref4 /*: {oid: string} */) {
        var oid = _ref4.oid;
        var start;
        return _regeneratorRuntime.wrap(function _callee3$(_context3) {
          while (1) {
            switch (_context3.prev = _context3.next) {
              case 0:
                if (this.offsets[oid]) {
                  _context3.next = 7;
                  break;
                }

                if (!this.getExternalRefDelta) {
                  _context3.next = 6;
                  break;
                }

                this.externalReadDepth++;
                return _context3.abrupt('return', this.getExternalRefDelta(oid));

              case 6:
                throw new Error('Could not read object ' + oid + ' from packfile');

              case 7:
                start = this.offsets[oid];
                return _context3.abrupt('return', this.readSlice({ start: start }));

              case 9:
              case 'end':
                return _context3.stop();
            }
          }
        }, _callee3, this);
      }));

      function read(_x2) {
        return _ref5.apply(this, arguments);
      }

      return read;
    }()
  }, {
    key: 'readSlice',
    value: function () {
      var _ref7 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(_ref6) {
        var start = _ref6.start;

        var types, raw, reader, byte, btype, type, lastFour, length, multibyte, base, object, offset, baseOffset, _ref8, oid, _ref9, buffer$$1;

        return _regeneratorRuntime.wrap(function _callee4$(_context4) {
          while (1) {
            switch (_context4.prev = _context4.next) {
              case 0:
                if (!this.offsetCache[start]) {
                  _context4.next = 2;
                  break;
                }

                return _context4.abrupt('return', this.offsetCache[start]);

              case 2:
                this.readDepth++;
                types = {
                  16: 'commit',
                  32: 'tree',
                  48: 'blob',
                  64: 'tag',
                  96: 'ofs_delta',
                  112: 'ref_delta'
                };

                if (this.pack) {
                  _context4.next = 6;
                  break;
                }

                throw new Error('Tried to read from a GitPackIndex with no packfile loaded into memory');

              case 6:
                raw = this.pack.slice(start);
                reader = new BufferCursor(raw);
                byte = reader.readUInt8();
                // Object type is encoded in bits 654

                btype = byte & 112;
                type = types[btype];

                if (!(type === undefined)) {
                  _context4.next = 13;
                  break;
                }

                throw new Error('Unrecognized type: 0b' + btype.toString(2));

              case 13:
                // The length encoding get complicated.
                // Last four bits of length is encoded in bits 3210
                lastFour = byte & 15;
                length = lastFour;
                // Whether the next byte is part of the variable-length encoded number
                // is encoded in bit 7

                multibyte = byte & 128;

                if (multibyte) {
                  length = otherVarIntDecode(reader, lastFour);
                }
                base = null;
                object = null;
                // Handle deltified objects

                if (!(type === 'ofs_delta')) {
                  _context4.next = 27;
                  break;
                }

                offset = decodeVarInt(reader);
                baseOffset = start - offset;
                _context4.next = 24;
                return this.readSlice({ start: baseOffset });

              case 24:
                _ref8 = _context4.sent;
                base = _ref8.object;
                type = _ref8.type;

              case 27:
                if (!(type === 'ref_delta')) {
                  _context4.next = 34;
                  break;
                }

                oid = reader.slice(20).toString('hex');
                _context4.next = 31;
                return this.read({ oid: oid });

              case 31:
                _ref9 = _context4.sent;
                base = _ref9.object;
                type = _ref9.type;

              case 34:
                // Handle undeltified objects
                buffer$$1 = raw.slice(reader.tell());

                object = buffer.Buffer.from(pako.inflate(buffer$$1));
                // Assert that the object length is as expected.

                if (!(object.byteLength !== length)) {
                  _context4.next = 38;
                  break;
                }

                throw new Error('Packfile told us object would have length ' + length + ' but it had length ' + object.byteLength);

              case 38:
                if (base) {
                  object = buffer.Buffer.from(applyDelta(object, base));
                }
                // Cache the result based on depth.
                if (this.readDepth > 3) {
                  // hand tuned for speed / memory usage tradeoff
                  this.offsetCache[start] = { type: type, object: object };
                }
                return _context4.abrupt('return', { type: type, format: 'content', object: object });

              case 41:
              case 'end':
                return _context4.stop();
            }
          }
        }, _callee4, this);
      }));

      function readSlice(_x3) {
        return _ref7.apply(this, arguments);
      }

      return readSlice;
    }()
  }], [{
    key: 'fromIdx',
    value: function () {
      var _ref11 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5(_ref10) {
        var idx = _ref10.idx,
            getExternalRefDelta = _ref10.getExternalRefDelta;

        var reader, magic, version, shaComputed, shaClaimed, fanout, i, size, hashes, _i, crcs, _i2, offsets, _i3, packfileSha;

        return _regeneratorRuntime.wrap(function _callee5$(_context5) {
          while (1) {
            switch (_context5.prev = _context5.next) {
              case 0:
                reader = new BufferCursor(idx);
                magic = reader.slice(4).toString('hex');
                // Check for IDX v2 magic number

                if (!(magic !== 'ff744f63')) {
                  _context5.next = 4;
                  break;
                }

                return _context5.abrupt('return');

              case 4:
                version = reader.readUInt32BE();

                if (!(version !== 2)) {
                  _context5.next = 7;
                  break;
                }

                throw new Error('Unable to read version ' + version + ' packfile IDX. (Only version 2 supported)');

              case 7:
                // Verify checksums
                shaComputed = shasum(idx.slice(0, -20));
                shaClaimed = idx.slice(-20).toString('hex');

                if (!(shaClaimed !== shaComputed)) {
                  _context5.next = 11;
                  break;
                }

                throw new Error('Invalid checksum in IDX buffer: expected ' + shaClaimed + ' but saw ' + shaComputed);

              case 11:
                if (!(idx.byteLength > 2048 * 1024 * 1024)) {
                  _context5.next = 13;
                  break;
                }

                throw new Error('To keep implementation simple, I haven\'t implemented the layer 5 feature needed to support packfiles > 2GB in size.');

              case 13:
                fanout = [];

                for (i = 0; i < 256; i++) {
                  fanout.push(reader.readUInt32BE());
                }
                size = fanout[255];
                // For now we'll parse the whole thing. We can optimize later if we need to.

                hashes = [];

                for (_i = 0; _i < size; _i++) {
                  hashes.push(reader.slice(20).toString('hex'));
                }
                crcs = {};

                for (_i2 = 0; _i2 < size; _i2++) {
                  crcs[hashes[_i2]] = reader.readUInt32BE();
                }
                offsets = {};

                for (_i3 = 0; _i3 < size; _i3++) {
                  offsets[hashes[_i3]] = reader.readUInt32BE();
                }
                packfileSha = reader.slice(20).toString('hex');
                return _context5.abrupt('return', new GitPackIndex({
                  hashes: hashes,
                  crcs: crcs,
                  offsets: offsets,
                  packfileSha: packfileSha,
                  getExternalRefDelta: getExternalRefDelta
                }));

              case 24:
              case 'end':
                return _context5.stop();
            }
          }
        }, _callee5, this);
      }));

      function fromIdx(_x4) {
        return _ref11.apply(this, arguments);
      }

      return fromIdx;
    }()
  }, {
    key: 'fromPack',
    value: function () {
      var _ref13 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7(_ref12) {
        var _this = this;

        var pack = _ref12.pack,
            getExternalRefDelta = _ref12.getExternalRefDelta;

        var listpackTypes, offsetToObject, packfileSha, hashes, crcs, offsets, totalObjectCount, lastPercent, times, histogram, bytesProcessed, offsetArray, _iteratorNormalCompletion5, _didIteratorError5, _iteratorError5, _iterator5, _step5, _ref16, _ref17, i, start, end, o, crc, p, count, callsToReadSlice, callsToGetExternal, timeByDepth, objectsByDepth, offset, percent, _ref18, type, object, time, oid, totalElapsedTime;

        return _regeneratorRuntime.wrap(function _callee7$(_context7) {
          while (1) {
            switch (_context7.prev = _context7.next) {
              case 0:
                listpackTypes = {
                  1: 'commit',
                  2: 'tree',
                  3: 'blob',
                  4: 'tag',
                  6: 'ofs-delta',
                  7: 'ref-delta'
                };
                offsetToObject = {};

                // Older packfiles do NOT use the shasum of the pack itself,
                // so it is recommended to just use whatever bytes are in the trailer.
                // Source: https://github.com/git/git/commit/1190a1acf800acdcfd7569f87ac1560e2d077414
                // let packfileSha = shasum(pack.slice(0, -20))

                packfileSha = pack.slice(-20).toString('hex');
                hashes = [];
                crcs = {};
                offsets = {};
                totalObjectCount = null;
                lastPercent = null;
                times = {
                  hash: 0,
                  readSlice: 0,
                  offsets: 0,
                  crcs: 0,
                  sort: 0
                };
                histogram = {
                  commit: 0,
                  tree: 0,
                  blob: 0,
                  tag: 0,
                  'ofs-delta': 0,
                  'ref-delta': 0
                };
                bytesProcessed = 0;


                utils_js.log('Indexing objects');
                utils_js.log('percent\tmilliseconds\tbytesProcessed\tcommits\ttrees\tblobs\ttags\tofs-deltas\tref-deltas');
                marky.mark('total');
                marky.mark('offsets');
                marky.mark('percent');
                _context7.next = 18;
                return new _Promise(function (resolve, reject) {
                  buffer2stream(pack).pipe(listpack()).on('data', function () {
                    var _ref15 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6(_ref14) {
                      var data = _ref14.data,
                          type = _ref14.type,
                          reference = _ref14.reference,
                          offset = _ref14.offset,
                          num = _ref14.num;
                      var percent;
                      return _regeneratorRuntime.wrap(function _callee6$(_context6) {
                        while (1) {
                          switch (_context6.prev = _context6.next) {
                            case 0:
                              if (totalObjectCount === null) totalObjectCount = num;
                              percent = Math.floor((totalObjectCount - num) * 100 / totalObjectCount);

                              if (percent !== lastPercent) {
                                utils_js.log(percent + '%\t' + Math.floor(marky.stop('percent').duration) + '\t' + bytesProcessed + '\t' + histogram.commit + '\t' + histogram.tree + '\t' + histogram.blob + '\t' + histogram.tag + '\t' + histogram['ofs-delta'] + '\t' + histogram['ref-delta']);

                                histogram = {
                                  commit: 0,
                                  tree: 0,
                                  blob: 0,
                                  tag: 0,
                                  'ofs-delta': 0,
                                  'ref-delta': 0
                                };
                                bytesProcessed = 0;
                                marky.mark('percent');
                              }
                              lastPercent = percent;
                              // Change type from a number to a meaningful string
                              type = listpackTypes[type];

                              histogram[type]++;
                              bytesProcessed += data.byteLength;

                              if (['commit', 'tree', 'blob', 'tag'].includes(type)) {
                                offsetToObject[offset] = {
                                  type: type,
                                  offset: offset
                                };
                              } else if (type === 'ofs-delta') {
                                offsetToObject[offset] = {
                                  type: type,
                                  offset: offset
                                };
                              } else if (type === 'ref-delta') {
                                offsetToObject[offset] = {
                                  type: type,
                                  offset: offset
                                };
                              }
                              if (num === 0) resolve();

                            case 9:
                            case 'end':
                              return _context6.stop();
                          }
                        }
                      }, _callee6, _this);
                    }));

                    return function (_x6) {
                      return _ref15.apply(this, arguments);
                    };
                  }());
                });

              case 18:
                times['offsets'] = Math.floor(marky.stop('offsets').duration);

                utils_js.log('Computing CRCs');
                marky.mark('crcs');
                // We need to know the lengths of the slices to compute the CRCs.
                offsetArray = _Object$keys(offsetToObject).map(Number);
                _iteratorNormalCompletion5 = true;
                _didIteratorError5 = false;
                _iteratorError5 = undefined;
                _context7.prev = 25;

                for (_iterator5 = _getIterator(offsetArray.entries()); !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
                  _ref16 = _step5.value;
                  _ref17 = _slicedToArray(_ref16, 2);
                  i = _ref17[0];
                  start = _ref17[1];
                  end = i + 1 === offsetArray.length ? pack.byteLength - 20 : offsetArray[i + 1];
                  o = offsetToObject[start];
                  crc = crc32(pack.slice(start, end));

                  o.end = end;
                  o.crc = crc;
                }
                _context7.next = 33;
                break;

              case 29:
                _context7.prev = 29;
                _context7.t0 = _context7['catch'](25);
                _didIteratorError5 = true;
                _iteratorError5 = _context7.t0;

              case 33:
                _context7.prev = 33;
                _context7.prev = 34;

                if (!_iteratorNormalCompletion5 && _iterator5.return) {
                  _iterator5.return();
                }

              case 36:
                _context7.prev = 36;

                if (!_didIteratorError5) {
                  _context7.next = 39;
                  break;
                }

                throw _iteratorError5;

              case 39:
                return _context7.finish(36);

              case 40:
                return _context7.finish(33);

              case 41:
                times['crcs'] = Math.floor(marky.stop('crcs').duration);

                // We don't have the hashes yet. But we can generate them using the .readSlice function!
                p = new GitPackIndex({
                  pack: pack,
                  packfileSha: packfileSha,
                  crcs: crcs,
                  hashes: hashes,
                  offsets: offsets,
                  getExternalRefDelta: getExternalRefDelta
                });

                // Resolve deltas and compute the oids

                utils_js.log('Resolving deltas');
                utils_js.log('percent2\tmilliseconds2\tcallsToReadSlice\tcallsToGetExternal');
                marky.mark('percent');
                lastPercent = null;
                count = 0;
                callsToReadSlice = 0;
                callsToGetExternal = 0;
                timeByDepth = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
                objectsByDepth = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
                _context7.t1 = _regeneratorRuntime.keys(offsetToObject);

              case 53:
                if ((_context7.t2 = _context7.t1()).done) {
                  _context7.next = 92;
                  break;
                }

                offset = _context7.t2.value;

                offset = Number(offset);
                percent = Math.floor(count++ * 100 / totalObjectCount);

                if (percent !== lastPercent) {
                  utils_js.log(percent + '%\t' + Math.floor(marky.stop('percent').duration) + '\t' + callsToReadSlice + '\t' + callsToGetExternal);
                  marky.mark('percent');
                  callsToReadSlice = 0;
                  callsToGetExternal = 0;
                }
                lastPercent = percent;

                o = offsetToObject[offset];

                if (!o.oid) {
                  _context7.next = 62;
                  break;
                }

                return _context7.abrupt('continue', 53);

              case 62:
                _context7.prev = 62;

                p.readDepth = 0;
                p.externalReadDepth = 0;
                marky.mark('readSlice');
                _context7.next = 68;
                return p.readSlice({ start: offset });

              case 68:
                _ref18 = _context7.sent;
                type = _ref18.type;
                object = _ref18.object;
                time = marky.stop('readSlice').duration;

                times.readSlice += time;
                callsToReadSlice += p.readDepth;
                callsToGetExternal += p.externalReadDepth;
                timeByDepth[p.readDepth] += time;
                objectsByDepth[p.readDepth] += 1;
                marky.mark('hash');
                oid = GitObject.hash({ type: type, object: object });

                times.hash += marky.stop('hash').duration;
                o.oid = oid;
                hashes.push(oid);
                offsets[oid] = offset;
                crcs[oid] = o.crc;
                _context7.next = 90;
                break;

              case 86:
                _context7.prev = 86;
                _context7.t3 = _context7['catch'](62);

                utils_js.log('ERROR', _context7.t3);
                return _context7.abrupt('continue', 53);

              case 90:
                _context7.next = 53;
                break;

              case 92:

                marky.mark('sort');
                hashes.sort();
                times['sort'] = Math.floor(marky.stop('sort').duration);
                totalElapsedTime = marky.stop('total').duration;

                times.hash = Math.floor(times.hash);
                times.readSlice = Math.floor(times.readSlice);
                times.misc = Math.floor(_Object$values(times).reduce(function (a, b) {
                  return a - b;
                }, totalElapsedTime));
                utils_js.log(_Object$keys(times).join('\t'));
                utils_js.log(_Object$values(times).join('\t'));
                utils_js.log('by depth:');
                utils_js.log([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].join('\t'));
                utils_js.log(objectsByDepth.slice(0, 12).join('\t'));
                utils_js.log(timeByDepth.map(Math.floor).slice(0, 12).join('\t'));
                return _context7.abrupt('return', p);

              case 106:
              case 'end':
                return _context7.stop();
            }
          }
        }, _callee7, this, [[25, 29, 33, 41], [34,, 36, 40], [62, 86]]);
      }));

      function fromPack(_x5) {
        return _ref13.apply(this, arguments);
      }

      return fromPack;
    }()
  }]);

  return GitPackIndex;
}();

var MAX_UINT32 = Math.pow(2, 32);
/*::
import type {Stats} from 'fs'

type CacheEntryFlags = {
  assumeValid: boolean,
  extended: boolean,
  stage: number,
  nameLength: number
}

type CacheEntry = {
  ctime: Date,
  ctimeNanoseconds?: number,
  mtime: Date,
  mtimeNanoseconds?: number,
  dev: number,
  ino: number,
  mode: number,
  uid: number,
  gid: number,
  size: number,
  oid: string,
  flags: CacheEntryFlags,
  path: string
}
*/

// Extract 1-bit assume-valid, 1-bit extended flag, 2-bit merge state flag, 12-bit path length flag
function parseCacheEntryFlags(bits /*: number */) /*: CacheEntryFlags */{
  return {
    assumeValid: Boolean(bits & 32768),
    extended: Boolean(bits & 16384),
    stage: (bits & 12288) >> 12,
    nameLength: bits & 4095
  };
}

function renderCacheEntryFlags(flags /*: CacheEntryFlags */) /*: number */{
  return (flags.assumeValid ? 32768 : 0) + (flags.extended ? 16384 : 0) + ((flags.stage & 3) << 12) + (flags.nameLength & 4095);
}

function parseBuffer(buffer$$1) {
  // Verify shasum
  var shaComputed = shasum(buffer$$1.slice(0, -20));
  var shaClaimed = buffer$$1.slice(-20).toString('hex');
  if (shaClaimed !== shaComputed) {
    throw new Error('Invalid checksum in GitIndex buffer: expected ' + shaClaimed + ' but saw ' + shaComputed);
  }
  var reader = new BufferCursor(buffer$$1);
  var _entries /*: Map<string, CacheEntry> */ = new _Map();
  var magic = reader.toString('utf8', 4);
  if (magic !== 'DIRC') {
    throw new Error('Inavlid dircache magic file number: ' + magic);
  }
  var version = reader.readUInt32BE();
  if (version !== 2) throw new Error('Unsupported dircache version: ' + version);
  var numEntries = reader.readUInt32BE();
  var i = 0;
  while (!reader.eof() && i < numEntries) {
    var entry = {};
    var ctimeSeconds = reader.readUInt32BE();
    var ctimeNanoseconds = reader.readUInt32BE();
    entry.ctime = new Date(ctimeSeconds * 1000 + ctimeNanoseconds / 1000000);
    entry.ctimeNanoseconds = ctimeNanoseconds;
    var mtimeSeconds = reader.readUInt32BE();
    var mtimeNanoseconds = reader.readUInt32BE();
    entry.mtime = new Date(mtimeSeconds * 1000 + mtimeNanoseconds / 1000000);
    entry.mtimeNanoseconds = mtimeNanoseconds;
    entry.dev = reader.readUInt32BE();
    entry.ino = reader.readUInt32BE();
    entry.mode = reader.readUInt32BE();
    entry.uid = reader.readUInt32BE();
    entry.gid = reader.readUInt32BE();
    entry.size = reader.readUInt32BE();
    entry.oid = reader.slice(20).toString('hex');
    var flags = reader.readUInt16BE();
    entry.flags = parseCacheEntryFlags(flags);
    // TODO: handle if (version === 3 && entry.flags.extended)
    var pathlength = buffer$$1.indexOf(0, reader.tell() + 1) - reader.tell();
    if (pathlength < 1) throw new Error('Got a path length of: ' + pathlength);
    entry.path = reader.toString('utf8', pathlength);
    // The next bit is awkward. We expect 1 to 8 null characters
    var tmp = reader.readUInt8();
    if (tmp !== 0) {
      throw new Error('Expected 1-8 null characters but got \'' + tmp + '\'');
    }
    var numnull = 1;
    while (!reader.eof() && reader.readUInt8() === 0 && numnull < 9) {
      numnull++;
    }reader.seek(reader.tell() - 1);
    // end of awkward part
    _entries.set(entry.path, entry);
    i++;
  }
  return _entries;
}

/** @ignore */
var GitIndex = function () {
  /*::
   _entries: Map<string, CacheEntry>
   _dirty: boolean // Used to determine if index needs to be saved to filesystem
   */
  function GitIndex(index /*: any */) {
    _classCallCheck(this, GitIndex);

    this._dirty = false;
    if (buffer.Buffer.isBuffer(index)) {
      this._entries = parseBuffer(index);
    } else if (index === null) {
      this._entries = new _Map();
    } else {
      throw new Error('invalid type passed to GitIndex constructor');
    }
  }

  _createClass(GitIndex, [{
    key: _Symbol$iterator,
    value: /*#__PURE__*/_regeneratorRuntime.mark(function value() {
      var _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, entry;

      return _regeneratorRuntime.wrap(function value$(_context) {
        while (1) {
          switch (_context.prev = _context.next) {
            case 0:
              _iteratorNormalCompletion = true;
              _didIteratorError = false;
              _iteratorError = undefined;
              _context.prev = 3;
              _iterator = _getIterator(this.entries);

            case 5:
              if (_iteratorNormalCompletion = (_step = _iterator.next()).done) {
                _context.next = 12;
                break;
              }

              entry = _step.value;
              _context.next = 9;
              return entry;

            case 9:
              _iteratorNormalCompletion = true;
              _context.next = 5;
              break;

            case 12:
              _context.next = 18;
              break;

            case 14:
              _context.prev = 14;
              _context.t0 = _context['catch'](3);
              _didIteratorError = true;
              _iteratorError = _context.t0;

            case 18:
              _context.prev = 18;
              _context.prev = 19;

              if (!_iteratorNormalCompletion && _iterator.return) {
                _iterator.return();
              }

            case 21:
              _context.prev = 21;

              if (!_didIteratorError) {
                _context.next = 24;
                break;
              }

              throw _iteratorError;

            case 24:
              return _context.finish(21);

            case 25:
              return _context.finish(18);

            case 26:
            case 'end':
              return _context.stop();
          }
        }
      }, value, this, [[3, 14, 18, 26], [19,, 21, 25]]);
    })
  }, {
    key: 'insert',
    value: function insert(_ref) /*: {filepath: string, stats: Stats, oid: string } */{
      var filepath = _ref.filepath,
          stats = _ref.stats,
          oid = _ref.oid;

      var entry = {
        ctime: stats.ctime,
        mtime: stats.mtime,
        dev: stats.dev % MAX_UINT32,
        ino: stats.ino % MAX_UINT32,
        mode: stats.mode % MAX_UINT32,
        uid: stats.uid % MAX_UINT32,
        gid: stats.gid % MAX_UINT32,
        size: stats.size % MAX_UINT32,
        path: filepath,
        oid: oid,
        flags: {
          assumeValid: false,
          extended: false,
          stage: 0,
          nameLength: filepath.length < 0xfff ? filepath.length : 0xfff
        }
      };
      this._entries.set(entry.path, entry);
      this._dirty = true;
    }
  }, {
    key: 'delete',
    value: function _delete(_ref2 /*: {filepath: string} */) {
      var filepath = _ref2.filepath;

      if (this._entries.has(filepath)) {
        this._entries.delete(filepath);
      } else {
        var _iteratorNormalCompletion2 = true;
        var _didIteratorError2 = false;
        var _iteratorError2 = undefined;

        try {
          for (var _iterator2 = _getIterator(this._entries.keys()), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
            var key = _step2.value;

            if (key.startsWith(filepath + '/')) {
              this._entries.delete(key);
            }
          }
        } catch (err) {
          _didIteratorError2 = true;
          _iteratorError2 = err;
        } finally {
          try {
            if (!_iteratorNormalCompletion2 && _iterator2.return) {
              _iterator2.return();
            }
          } finally {
            if (_didIteratorError2) {
              throw _iteratorError2;
            }
          }
        }
      }
      this._dirty = true;
    }
  }, {
    key: 'clear',
    value: function clear() {
      this._entries.clear();
      this._dirty = true;
    }
  }, {
    key: 'render',
    value: function render() {
      return this.entries.map(function (entry) {
        return entry.mode.toString(8) + ' ' + entry.oid + '    ' + entry.path;
      }).join('\n');
    }
  }, {
    key: 'toObject',
    value: function toObject() {
      var header = buffer.Buffer.alloc(12);
      var writer = new BufferCursor(header);
      writer.write('DIRC', 4, 'utf8');
      writer.writeUInt32BE(2);
      writer.writeUInt32BE(this.entries.length);
      var body = buffer.Buffer.concat(this.entries.map(function (entry) {
        // the fixed length + the filename + at least one null char => align by 8
        var length = Math.ceil((62 + entry.path.length + 1) / 8) * 8;
        var written = buffer.Buffer.alloc(length);
        var writer = new BufferCursor(written);
        var ctimeMilliseconds = entry.ctime.valueOf();
        var ctimeSeconds = Math.floor(ctimeMilliseconds / 1000);
        var ctimeNanoseconds = entry.ctimeNanoseconds || ctimeMilliseconds * 1000000 - ctimeSeconds * 1000000 * 1000;
        var mtimeMilliseconds = entry.mtime.valueOf();
        var mtimeSeconds = Math.floor(mtimeMilliseconds / 1000);
        var mtimeNanoseconds = entry.mtimeNanoseconds || mtimeMilliseconds * 1000000 - mtimeSeconds * 1000000 * 1000;
        writer.writeUInt32BE(ctimeSeconds % MAX_UINT32);
        writer.writeUInt32BE(ctimeNanoseconds % MAX_UINT32);
        writer.writeUInt32BE(mtimeSeconds % MAX_UINT32);
        writer.writeUInt32BE(mtimeNanoseconds % MAX_UINT32);
        writer.writeUInt32BE(entry.dev % MAX_UINT32);
        writer.writeUInt32BE(entry.ino % MAX_UINT32);
        writer.writeUInt32BE(entry.mode % MAX_UINT32);
        writer.writeUInt32BE(entry.uid % MAX_UINT32);
        writer.writeUInt32BE(entry.gid % MAX_UINT32);
        writer.writeUInt32BE(entry.size % MAX_UINT32);
        writer.write(entry.oid, 20, 'hex');
        writer.writeUInt16BE(renderCacheEntryFlags(entry.flags));
        writer.write(entry.path, entry.path.length, 'utf8');
        return written;
      }));
      var main = buffer.Buffer.concat([header, body]);
      var sum = shasum(main);
      return buffer.Buffer.concat([main, buffer.Buffer.from(sum, 'hex')]);
    }
  }, {
    key: 'entries',
    get: function get() /*: Array<CacheEntry> */{
      return sortby([].concat(_toConsumableArray(this._entries.values())), 'path');
    }
  }], [{
    key: 'from',
    value: function from(buffer$$1) {
      return new GitIndex(buffer$$1);
    }
  }]);

  return GitIndex;
}();

/*::
type TreeEntry = {
  mode: string,
  path: string,
  oid: string,
  type?: string
}
*/

function parseBuffer$1(buffer$$1) /*: Array<TreeEntry> */{
  var _entries = [];
  var cursor = 0;
  while (cursor < buffer$$1.length) {
    var space = buffer$$1.indexOf(32, cursor);
    if (space === -1) {
      throw new Error('GitTree: Error parsing buffer at byte location ' + cursor + ': Could not find the next space character.');
    }
    var nullchar = buffer$$1.indexOf(0, cursor);
    if (nullchar === -1) {
      throw new Error('GitTree: Error parsing buffer at byte location ' + cursor + ': Could not find the next null character.');
    }
    var mode = buffer$$1.slice(cursor, space).toString('utf8');
    if (mode === '40000') mode = '040000'; // makes it line up neater in printed output
    var type = mode === '040000' ? 'tree' : 'blob';
    var path$$1 = buffer$$1.slice(space + 1, nullchar).toString('utf8');
    var oid = buffer$$1.slice(nullchar + 1, nullchar + 21).toString('hex');
    cursor = nullchar + 21;
    _entries.push({ mode: mode, path: path$$1, oid: oid, type: type });
  }
  return _entries;
}

function limitModeToAllowed(mode /*: string */) {
  if (typeof mode === 'number') {
    mode = mode.toString(8);
  }
  // tree
  if (mode.match(/^0?4.*/)) return '40000'; // Directory
  if (mode.match(/^1006.*/)) return '100644'; // Regular non-executable file
  if (mode.match(/^1007.*/)) return '100755'; // Regular executable file
  if (mode.match(/^120.*/)) return '120000'; // Symbolic link
  if (mode.match(/^160.*/)) return '160000'; // Commit (git submodule reference)
  throw new Error('Could not understand file mode: ' + mode);
}

function nudgeIntoShape(entry) {
  if (!entry.oid && entry.sha) {
    entry.oid = entry.sha; // Github
  }
  entry.mode = limitModeToAllowed(entry.mode); // index
  if (!entry.type) {
    entry.type = 'blob'; // index
  }
  return entry;
}

/** @ignore */
var GitTree = function () {
  /*::
  _entries: Array<TreeEntry>
  */
  function GitTree(entries /*: any */) {
    _classCallCheck(this, GitTree);

    if (buffer.Buffer.isBuffer(entries)) {
      this._entries = parseBuffer$1(entries);
    } else if (Array.isArray(entries)) {
      this._entries = entries.map(nudgeIntoShape);
    } else {
      throw new Error('invalid type passed to GitTree constructor');
    }
  }

  _createClass(GitTree, [{
    key: 'render',
    value: function render() {
      return this._entries.map(function (entry) {
        return entry.mode + ' ' + entry.type + ' ' + entry.oid + '    ' + entry.path;
      }).join('\n');
    }
  }, {
    key: 'toObject',
    value: function toObject() {
      return buffer.Buffer.concat(this._entries.map(function (entry) {
        var mode = buffer.Buffer.from(entry.mode.replace(/^0/, ''));
        var space = buffer.Buffer.from(' ');
        var path$$1 = buffer.Buffer.from(entry.path, { encoding: 'utf8' });
        var nullchar = buffer.Buffer.from([0]);
        var oid = buffer.Buffer.from(entry.oid.match(/../g).map(function (n) {
          return parseInt(n, 16);
        }));
        return buffer.Buffer.concat([mode, space, path$$1, nullchar, oid]);
      }));
    }
  }, {
    key: 'entries',
    value: function entries() {
      return this._entries;
    }
  }, {
    key: _Symbol$iterator,
    value: /*#__PURE__*/_regeneratorRuntime.mark(function value() {
      var _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, entry;

      return _regeneratorRuntime.wrap(function value$(_context) {
        while (1) {
          switch (_context.prev = _context.next) {
            case 0:
              _iteratorNormalCompletion = true;
              _didIteratorError = false;
              _iteratorError = undefined;
              _context.prev = 3;
              _iterator = _getIterator(this._entries);

            case 5:
              if (_iteratorNormalCompletion = (_step = _iterator.next()).done) {
                _context.next = 12;
                break;
              }

              entry = _step.value;
              _context.next = 9;
              return entry;

            case 9:
              _iteratorNormalCompletion = true;
              _context.next = 5;
              break;

            case 12:
              _context.next = 18;
              break;

            case 14:
              _context.prev = 14;
              _context.t0 = _context['catch'](3);
              _didIteratorError = true;
              _iteratorError = _context.t0;

            case 18:
              _context.prev = 18;
              _context.prev = 19;

              if (!_iteratorNormalCompletion && _iterator.return) {
                _iterator.return();
              }

            case 21:
              _context.prev = 21;

              if (!_didIteratorError) {
                _context.next = 24;
                break;
              }

              throw _iteratorError;

            case 24:
              return _context.finish(21);

            case 25:
              return _context.finish(18);

            case 26:
            case 'end':
              return _context.stop();
          }
        }
      }, value, this, [[3, 14, 18, 26], [19,, 21, 25]]);
    })
  }], [{
    key: 'from',
    value: function from(tree) {
      return new GitTree(tree);
    }
  }]);

  return GitTree;
}();

var GitRefSpec = function () {
  function GitRefSpec(_ref) {
    var remotePath = _ref.remotePath,
        localPath = _ref.localPath,
        force = _ref.force,
        matchPrefix = _ref.matchPrefix;

    _classCallCheck(this, GitRefSpec);

    _Object$assign(this, {
      remotePath: remotePath,
      localPath: localPath,
      force: force,
      matchPrefix: matchPrefix
    });
  }

  _createClass(GitRefSpec, [{
    key: 'translate',
    value: function translate(remoteBranch) {
      if (this.matchPrefix) {
        if (remoteBranch.startsWith(this.remotePath)) {
          return this.localPath + remoteBranch.replace(this.remotePath, '');
        }
      } else {
        if (remoteBranch === this.remotePath) return this.localPath;
      }
      return null;
    }
  }], [{
    key: 'from',
    value: function from(refspec) {
      var _refspec$match$slice = refspec.match(/^(\+?)(.*?)(\*?):(.*?)(\*?)$/).slice(1),
          _refspec$match$slice2 = _slicedToArray(_refspec$match$slice, 5),
          forceMatch = _refspec$match$slice2[0],
          remotePath = _refspec$match$slice2[1],
          remoteGlobMatch = _refspec$match$slice2[2],
          localPath = _refspec$match$slice2[3],
          localGlobMatch = _refspec$match$slice2[4];

      var force = forceMatch === '+';
      var remoteIsGlob = remoteGlobMatch === '*';
      var localIsGlob = localGlobMatch === '*';
      // validate
      // TODO: Make this check more nuanced, and depend on whether this is a fetch refspec or a push refspec
      if (remoteIsGlob !== localIsGlob) throw new Error('Invalid refspec');
      return new GitRefSpec({
        remotePath: remotePath,
        localPath: localPath,
        force: force,
        matchPrefix: remoteIsGlob
      });
      // TODO: We need to run resolveRef on both paths to expand them to their full name.
    }
  }]);

  return GitRefSpec;
}();

var GitRefSpecSet = function () {
  function GitRefSpecSet() {
    var rules = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];

    _classCallCheck(this, GitRefSpecSet);

    this.rules = rules;
  }

  _createClass(GitRefSpecSet, [{
    key: 'add',
    value: function add(refspec) {
      var rule = GitRefSpec.from(refspec); // might throw
      this.rules.push(rule);
    }
  }, {
    key: 'translate',
    value: function translate(remoteRefs) {
      var result = [];
      var _iteratorNormalCompletion = true;
      var _didIteratorError = false;
      var _iteratorError = undefined;

      try {
        for (var _iterator = _getIterator(this.rules), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
          var rule = _step.value;
          var _iteratorNormalCompletion2 = true;
          var _didIteratorError2 = false;
          var _iteratorError2 = undefined;

          try {
            for (var _iterator2 = _getIterator(remoteRefs), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
              var remoteRef = _step2.value;

              var localRef = rule.translate(remoteRef);
              if (localRef) {
                result.push([remoteRef, localRef]);
              }
            }
          } catch (err) {
            _didIteratorError2 = true;
            _iteratorError2 = err;
          } finally {
            try {
              if (!_iteratorNormalCompletion2 && _iterator2.return) {
                _iterator2.return();
              }
            } finally {
              if (_didIteratorError2) {
                throw _iteratorError2;
              }
            }
          }
        }
      } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion && _iterator.return) {
            _iterator.return();
          }
        } finally {
          if (_didIteratorError) {
            throw _iteratorError;
          }
        }
      }

      return result;
    }
  }, {
    key: 'translateOne',
    value: function translateOne(remoteRef) {
      var result = null;
      var _iteratorNormalCompletion3 = true;
      var _didIteratorError3 = false;
      var _iteratorError3 = undefined;

      try {
        for (var _iterator3 = _getIterator(this.rules), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
          var rule = _step3.value;

          var localRef = rule.translate(remoteRef);
          if (localRef) {
            result = localRef;
          }
        }
      } catch (err) {
        _didIteratorError3 = true;
        _iteratorError3 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion3 && _iterator3.return) {
            _iterator3.return();
          }
        } finally {
          if (_didIteratorError3) {
            throw _iteratorError3;
          }
        }
      }

      return result;
    }
  }], [{
    key: 'from',
    value: function from(refspecs) {
      var rules = [];
      var _iteratorNormalCompletion4 = true;
      var _didIteratorError4 = false;
      var _iteratorError4 = undefined;

      try {
        for (var _iterator4 = _getIterator(refspecs), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
          var refspec = _step4.value;

          rules.push(GitRefSpec.from(refspec)); // might throw
        }
      } catch (err) {
        _didIteratorError4 = true;
        _iteratorError4 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion4 && _iterator4.return) {
            _iterator4.return();
          }
        } finally {
          if (_didIteratorError4) {
            throw _iteratorError4;
          }
        }
      }

      return new GitRefSpecSet(rules);
    }
  }]);

  return GitRefSpecSet;
}();

exports.FileSystem = FileSystem;
exports.GitCommit = GitCommit;
exports.SignedGitCommit = SignedGitCommit;
exports.GitConfig = GitConfig;
exports.GitObject = GitObject;
exports.GitPktLine = GitPktLine;
exports.GitPackIndex = GitPackIndex;
exports.GitIndex = GitIndex;
exports.GitTree = GitTree;
exports.GitRefSpec = GitRefSpec;
exports.GitRefSpecSet = GitRefSpecSet;
//# sourceMappingURL=models.js.map
