Fork me on GitHub
nodegit by tbranyen & faceleg
var git = require('../'),
    path = require('path'),
    success = require('./utilities').success;

var fileModeIsFile = function(fileMode, instance) {
  return fileMode === instance.fileModes.GIT_FILEMODE_BLOB ||
          fileMode === instance.fileModes.GIT_FILEMODE_BLOB_EXECUTABLE;
};

var fileModeIsDirectory = function(fileMode, instance) {
  return fileMode === instance.fileModes.GIT_FILEMODE_TREE;
};

/**
 * Convenience tree entry constructor.
 *
 * @constructor
 * @param {git.raw.Repo} rawRepo Raw repository object.
 * @param {git.raw.TreeEntry} rawTreeEntry Raw tree entry object.
 */
var TreeEntry = function(rawRepo, rawTreeEntry) {
  if(!(rawRepo instanceof git.raw.Repo)) {
    throw new git.error('First parameter for Tree Entry must be a raw repo', 0);
  }

  if(!(rawTreeEntry instanceof git.raw.TreeEntry)) {
    throw new git.error('Second parameter for Tree Entry must be a raw tree entry', 0);
  }

  this.rawRepo = rawRepo;
  this.rawEntry = rawTreeEntry;
  this._cache = {};
};

/**
 * Refer to vendor/libgit2/include/git2/types.h for filemode definitions.
 *
 * @readonly
 * @enum {Integer}
 */
TreeEntry.prototype.fileModes = {
  /** 0     (0000000) */ GIT_FILEMODE_NEW: git.raw.TreeEntry.fileModes.GIT_FILEMODE_NEW,
  /** 16384 (0040000) */ GIT_FILEMODE_TREE: git.raw.TreeEntry.fileModes.GIT_FILEMODE_TREE,
  /** 33188 (0100644) */ GIT_FILEMODE_BLOB: git.raw.TreeEntry.fileModes.GIT_FILEMODE_BLOB,
  /** 33261 (0100755) */ GIT_FILEMODE_BLOB_EXECUTABLE: git.raw.TreeEntry.fileModes.GIT_FILEMODE_BLOB_EXECUTABLE,
  /** 40960 (0120000) */ GIT_FILEMODE_LINK: git.raw.TreeEntry.fileModes.GIT_FILEMODE_LINK,
  /** 57344 (0160000) */ GIT_FILEMODE_COMMIT: git.raw.TreeEntry.fileModes.GIT_FILEMODE_COMMIT
};

/**
 * Retrieve the Oid for this TreeEntry.
 *
 * @param {TreeEntry~oidCallback} callback
 */
TreeEntry.prototype.oid = function(callback) {
  /**
   * @callback TreeEntry~oidCallback Callback executed after the Oid is retrieved.
   * @param {GitError|null} error An Error or null if successful.
   * @param {TreeEntry|null} oid The Oid object or null.
   */
  this.rawEntry.oid(function(error, rawOid) {
    if (success(error, callback)) {
      callback(null, new git.oid(rawOid));
    }
  });
};

/**
 * Retrieve the SHA for this TreeEntry.
 *
 * @param {TreeEntry~shaCallback} callback
 */
TreeEntry.prototype.sha = function(callback) {
  /**
   * @callback TreeEntry~shaCallback Callback executed after the SHA is retrieved.
   * @param {GitError|null} error An Error or null if successful.
   * @param {String|null} sha The SHA object or null.
   */
  this.rawEntry.oid(function(error, oid) {
    if (!success(error, callback)) {
      return;
    }
    (new git.oid(oid)).sha(function(error, sha) {
      if (success(error, callback)) {
        callback(null, sha);
      }
    });
  });
};

/**
 * Determine whether this TreeEntry is a file (blob or blob executable).
 *
 * @param {TreeEntry~isFileCallback} callback
 */
TreeEntry.prototype.isFile = function(callback) {
  /**
   * @callback TreeEntry~isFileCallback Callback executed after type is determined.
   * @param {GitError|null} error An Error or null if successful.
   * @param {Boolean|null} content True if the entry is a blob or blob executable, false otherwise.
   */
  var self = this;
  if (typeof self._cache.fileMode !== 'undefined') {
    callback(null, fileModeIsFile(self._cache.fileMode, self));
    return;
  }
  self.rawEntry.fileMode(function(error, fileMode) {
    if (success(error, callback)) {
      self._cache.fileMode = fileMode;
      callback(null, fileModeIsFile(self._cache.fileMode, self));
    }
  });
};

/**
 * Determine whether this Tree Entry is a directory.
 *
 * @param {TreeEntry~isDirectoryCallback} callback
 */
TreeEntry.prototype.isDirectory = function(callback) {
  /**
   * @callback TreeEntry~isDirectoryCallback Callback executed after type is determined.
   * @param {GitError|null} error An Error or null if successful.
   * @param {Boolean|null} content True if the entry is a directory, false otherwise.
   */
  var self = this;
  if (typeof self._cache.fileMode !== 'undefined') {
    callback(null, fileModeIsDirectory(self._cache.fileMode, self));
    return;
  }
  self.rawEntry.fileMode(function(error, fileMode) {
    if (success(error, callback)) {
      self._cache.fileMode = fileMode;
      callback(null, fileModeIsDirectory(self._cache.fileMode, self));
    }
  });
};

/**
 * Retrieve the name for this TreeEntry.
 *
 * @param {TreeEntry~nameCallback} callback
 */
TreeEntry.prototype.name = function(callback) {
  /**
   * @callback TreeEntry~nameCallback Callback executed after name is retrieved.
   * @param {GitError|null} error An Error or null if successful.
   * @param {String|null} name the entry's name.
   */
  this.rawEntry.name(function treeEntryName(error, name) {
    if (success(error, callback)) {
      callback(null, name);
    }
  });
};

/**
 * Retrieve the entry's root path.
 *
 * @param {TreeEntry~rootCallback} callback
 */
TreeEntry.prototype.root = function(callback) {
  /**
   * @callback TreeEntry~rootCallback Callback executed after root path is retrieved.
   * @param {GitError|null} error An Error or null if successful.
   * @param {String|null} root the entry's root path, relative to repository.
   */
  this.rawEntry.root(function treeEntryRoot(error, root) {
    if (success(error, callback)) {
      callback(null, root);
    }
  });
};

/**
 * Retrieve the path relative to the repository root for this TreeEntry.
 *
 * @param {TreeEntry~pathCallback} callback
 */
TreeEntry.prototype.path = function(callback) {
  /**
   * @callback TreeEntry~pathCallback Callback executed after path is retrieved.
   * @param {GitError|null} error An Error or null if successful.
   * @param {String|null} path the entry's full path relative to repository.
   */
  var self = this;
  self.root(function treeEntryRoot(error, root) {
    if (!success(error, callback)) {
      return;
    }
    self.rawEntry.name(function treeEntryName(error, name) {
      if (success(error, callback)) {
        callback(null, path.join(root, name));
      }
    });
  });
};

/**
 * Retrieve the TreeEntry's content.
 *
 * @param {TreeEntry~contentCallback} callback
 */
TreeEntry.prototype.content = function(callback) {
  /**
   * @callback TreeEntry~contentCallback Callback executed after content is retrieved.
   * @param {GitError|null} error An Error or null if successful.
   * @param {String|null} content the entry's content.
   */
  this.toBlob(function convertBlob(error, blob) {
    if (!success(error, callback)) {
      return;
    }
    blob.content(function blobContent(error, content) {
      if (success(error, callback)) {
        callback(null, content);
      }
    });
  });
};

/**
 * Convert the TreeEntry to a blob.
 *
 * @param {TreeEntry~blobCallback} callback
 */
TreeEntry.prototype.toBlob = function(callback) {
  /**
   * @callback TreeEntry~blobCallback Callback executed after blob is retrieved.
   * @param {GitError|null} error An Error or null if successful.
   * @param {Blob|null} blob the blob representation of the entry.
   */
  var self = this;
  self.rawEntry.toBlob(self.rawRepo, function blobCallback(error, rawBlob) {
    if (success(error, callback)) {
      callback(null, new git.blob(self.rawRepo, rawBlob));
    }
  });
};

/**
 * Retrieve the TreeEntry's Tree.
 *
 * @param {TreeEntry~treeCallback} callback
 */
TreeEntry.prototype.tree = function(callback) {
  /**
   * @callback TreeEntry~treeCallback Callback executed after tree is retrieved.
   * @param {GitError|null} error An Error or null if successful.
   * @param {Tree|null} tree the entry's tree.
   */
  var self = this;
  self.oid(function treeEntryOid(error, oid) {
    if (!success(error, callback)) {
      return;
    }
    (new git.tree(self.rawRepo)).lookup(oid, function(error, tree) {
      if (!success(error, callback)) {
        return;
      }
      callback(null, tree);
    });
  });
};

exports.entry = TreeEntry;