docs: function MongoProject(projectId, collection) {
          this.projectId = projectId;
          this._collection = collection;
  
          this.closeProject = function (callback) {
              var deferred = Q.defer();
              //TODO: Does this really do something?
              collection = null;
              deferred.resolve();
              return deferred.promise.nodeify(callback);
          };
  
          this.loadObject = function (hash, callback) {
              var deferred = Q.defer();
              if (typeof hash !== 'string') {
                  deferred.reject(new Error('loadObject - given hash is not a string : ' + typeof hash));
              } else if (!REGEXP.HASH.test(hash)) {
                  deferred.reject(new Error('loadObject - invalid hash :' + hash));
              } else {
                  logger.debug('loadObject ' + hash);
                  collection.findOne({_id: hash}, function (err, obj) {
                      if (err) {
                          logger.error(err);
                          deferred.reject(err);
                      } else if (obj) {
                          deferred.resolve(obj);
                      } else {
                          logger.error('object does not exist ' + hash);
                          deferred.reject(new Error('object does not exist ' + hash));
                      }
                  });
              }
  
              return deferred.promise.nodeify(callback);
          };
  
          this.insertObject = function (object, callback) {
              var deferred = Q.defer(),
                  rejected = false;
              if (object === null || typeof object !== 'object') {
                  deferred.reject(new Error('object is not an object'));
                  rejected = true;
              } else if (typeof object._id !== 'string' || !REGEXP.HASH.test(object._id)) {
                  deferred.reject(new Error('object._id is not a valid hash.'));
                  rejected = true;
              }
              if (rejected === false) {
                  collection.insertOne(object, function (err) {
                      // manually check duplicate keys
                      if (err && err.code === 11000) {
                          collection.findOne({
                              _id: object._id
                          }, function (err2, data) {
                              var errMsg;
                              if (err2) {
                                  deferred.reject(err2);
                              } else {
                                  if (CANON.stringify(object) === CANON.stringify(data)) {
                                      logger.debug('tried to insert existing hash - the two objects were equal',
                                          object._id);
                                      deferred.resolve();
                                  } else {
                                      errMsg = 'tried to insert existing hash - the two objects were NOT equal ';
                                      logger.error(errMsg, {
                                          metadata: {
                                              newObject: object,
                                              oldObject: data
                                          }
                                      });
                                      deferred.reject(new Error(errMsg + object._id));
                                  }
                              }
                          });
                      } else if (err) {
                          deferred.reject(err);
                      } else {
                          deferred.resolve();
                      }
                  });
              }
  
              return deferred.promise.nodeify(callback);
          };
  
          this.getBranches = function (callback) {
              var mongoFind = collection.find({
                  _id: {
                      $regex: REGEXP.RAW_BRANCH.source
                  }
              });
  
              return Q.ninvoke(mongoFind, 'toArray')
                  .then(function (docs) {
                      var branches = {};
                      for (var i = 0; i < docs.length; ++i) {
                          branches[docs[i]._id.slice(1)] = docs[i].hash;
                      }
                      return Q(branches);
                  })
                  .nodeify(callback);
          };
  
          this.getBranchHash = function (branch, callback) {
              branch = '*' + branch;
  
              return Q.ninvoke(collection, 'findOne', {_id: branch})
                  .then(function (branchObj) {
                      // FIXME: This behaviour of return empty string rather than an error is the same as before.
                      // FIXME: Consider returning with an error in style with 'Branch does not exist'.
                      return Q((branchObj && branchObj.hash) || '');
                  }).nodeify(callback);
          };
  
          this.setBranchHash = function (branch, oldhash, newhash, callback) {
              var deferred = Q.defer();
              branch = '*' + branch;
  
              if (oldhash === newhash) {
                  collection.findOne({
                      _id: branch
                  }, function (err, obj) {
                      if (!err && oldhash !== ((obj && obj.hash) || '')) {
                          err = new Error('branch hash mismatch');
                      }
                      if (err) {
                          deferred.reject(err);
                      } else {
                          deferred.resolve();
                      }
                  });
              } else if (newhash === '') {
                  collection.deleteOne({
                      _id: branch,
                      hash: oldhash
                  }, function (err, result) {
                      if (!err && result.deletedCount !== 1) {
                          collection.findOne({_id: branch}, function (err, obj) {
                              if (!err && obj) {
                                  err = new Error('branch hash mismatch');
                              }
                              if (err) {
                                  deferred.reject(err);
                              } else {
                                  deferred.resolve();
                              }
                          });
                      } else if (err) {
                          deferred.reject(err);
                      } else {
                          deferred.resolve();
                      }
                  });
              } else if (oldhash === '') {
                  collection.insertOne({
                      _id: branch,
                      hash: newhash
                  }, function (err) {
                      if (err) {
                          if (err.code === 11000) {
                              // insertDocument :: caused by :: 11000 E11000 duplicate key error...
                              deferred.reject(new Error('branch hash mismatch'));
                          } else {
                              deferred.reject(err);
                          }
                      } else {
                          deferred.resolve();
                      }
                  });
              } else {
                  collection.updateOne({
                      _id: branch,
                      hash: oldhash
                  }, {
                      $set: {
                          hash: newhash
                      }
                  }, function (err, result) {
                      if (!err && result.modifiedCount !== 1) {
                          err = new Error('branch hash mismatch');
                      }
                      if (err) {
                          deferred.reject(err);
                      } else {
                          deferred.resolve();
                      }
                  });
              }
  
              return deferred.promise.nodeify(callback);
          };
  
          this.getCommits = function (before, number, callback) {
              var mongoFind = collection.find({
                  type: CONSTANTS.COMMIT_TYPE,
                  time: {
                      $lt: before
                  }
              }).limit(number).sort({
                  time: -1
              });
  
              return Q.ninvoke(mongoFind, 'toArray')
                  .then(function (docs) {
                      return Q(docs);
                  })
                  .nodeify(callback);
          };
  
          this.createTag = function (name, commitHash, callback) {
              var deferred = Q.defer(),
                  query = {
                      _id: self.CONSTANTS.TAGS,
                  },
                  update = {
                      $set: {}
                  };
  
              query[name] = {
                  $exists: false
              };
  
              update.$set[name] = commitHash;
  
              collection.updateOne(query, update, {upsert: true}, function (err/*, num*/) {
                  if (err) {
                      if (err.code === 11000) {
                          deferred.reject(new Error('Tag already exists [' + name + ']'));
                      } else {
                          deferred.reject(err);
                      }
                  } else {
                      deferred.resolve();
                  }
              });
  
              return deferred.promise.nodeify(callback);
          };
  
          this.deleteTag = function (name, callback) {
              var deferred = Q.defer(),
                  query = {
                      _id: self.CONSTANTS.TAGS,
                  },
                  update = {
                      $unset: {}
                  };
  
              update.$unset[name] = '';
  
              collection.updateOne(query, update, function (err/*, num*/) {
                  if (err) {
                      deferred.reject(err);
                  } else {
                      deferred.resolve();
                  }
              });
  
              return deferred.promise.nodeify(callback);
          };
  
          this.getTags = function (callback) {
              var deferred = Q.defer();
  
              collection.findOne({_id: self.CONSTANTS.TAGS}, {}, function (err, result) {
                  if (err) {
                      deferred.reject(err);
                  } else if (result) {
                      delete result._id;
                      deferred.resolve(result);
                  } else {
                      deferred.resolve({});
                  }
              });
  
              return deferred.promise.nodeify(callback);
          };
  
          this.traverse = function (visitFn, callback) {
              var deferred = Q.defer(),
                  cursor = collection.find(),
                  finished = false,
                  ongoingVisits = 0,
                  error = null,
                  next = function (err) {
                      error = error || err;
                      ongoingVisits -= 1;
                      if (finished && ongoingVisits === 0) {
                          if (error) {
                              deferred.reject(error);
                          } else {
                              deferred.resolve();
                          }
                      }
                  };
  
              cursor.batchSize(1000).each(function (err, object) {
                  error = error || err;
                  if (err === null) {
                      if (object === null) {
                          finished = true;
                      } else {
                          ongoingVisits += 1;
                          visitFn(object, next);
                      }
                  } else {
                      finished = true;
                  }
  
                  if (finished && ongoingVisits === 0) {
                      if (error) {
                          deferred.reject(error);
                      } else {
                          deferred.resolve();
                      }
                  }
              });
  
              return deferred.promise.nodeify(callback);
          };
      }